feat: reconcile licensed subscription quantities - #1637
Conversation
|
Important Review skippedToo many files! This PR contains 215 files, which is 115 over the limit of 100. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (215)
You can disable this status message by setting the 📝 WalkthroughWalkthrough멤버십 스냅샷에서 라이선스 수량을 계산하고, 커밋 후 이벤트와 제한된 복구 스캔으로 결제 제공자 수량을 조정하는 기능을 추가했습니다. 수량 정책, provider capability, 상태·문제·이벤트·진단 API와 관련 테스트 및 문서도 추가했습니다. Changes라이선스 수량 조정 핵심 기능
공개 API와 문서
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant MembershipService
participant EventPublisher
participant ReconciliationHandler
participant SubscriptionQuantityReconciler
participant LicensedQuantityGateway
MembershipService->>EventPublisher: 멤버십 변경을 commitAfter로 발행 요청
EventPublisher->>ReconciliationHandler: 커밋 후 이벤트 전달
ReconciliationHandler->>SubscriptionQuantityReconciler: createIntent 호출
SubscriptionQuantityReconciler->>LicensedQuantityGateway: getQuantity 호출
SubscriptionQuantityReconciler->>LicensedQuantityGateway: setQuantity로 멱등 업데이트
LicensedQuantityGateway-->>SubscriptionQuantityReconciler: applied, duplicate, stale 응답
SubscriptionQuantityReconciler->>SubscriptionQuantityReconciler: snapshot 저장 및 이벤트 발행
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
📊 Benchmark Results✅ All benchmarks passed
Updated: 2026-08-01T13:42:56.818Z · Commit: c713b40 |
4e8fc6a to
3b12864
Compare
There was a problem hiding this comment.
Actionable comments posted: 23
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/billing-polar/src/tests/PolarBillingProviderProfile.spec.ts (1)
5-13: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win테스트 제목을 검증 범위에 맞추십시오.
제목은 usage capability gap만 언급합니다. 이 테스트는 이제
licensed-quantitygap도 검증합니다. 테스트 실패 출력에서 원인을 즉시 알 수 있도록 제목을 갱신하십시오.🔧 제안 수정
- it("publishes checkout support and an explicit usage capability gap", () => { + it("publishes checkout support and explicit usage and licensed-quantity capability gaps", () => {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/billing-polar/src/tests/PolarBillingProviderProfile.spec.ts` around lines 5 - 13, Update the test title in PolarBillingProviderProfile.spec.ts around POLAR_BILLING_PROVIDER_PROFILE to mention both the usage capability gap and the licensed-quantity capability gap, matching the assertions covered by the test.packages/membership-core/src/libs/MembershipManager.ts (1)
35-219: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
MembershipManager와MembershipService가 동일한 구현을 중복 유지함. 두 클래스는 생성자,addMember,removeMember,updateRole,transferOwnership,checkSeatLimit,ensureValidRole,getMembershipOrThrow, 그리고 이번에 추가된publishSafely/publishAfterCommitOrNow까지 거의 전부 동일하다. 근본 원인은 하나의 로직이 두 클래스에 각각 구현되어 있다는 점이며, 이번 이벤트 발행 방식 변경도 두 파일에 똑같이 적용해야 했다.
packages/membership-core/src/libs/MembershipManager.ts#L35-L219: 공유 가능한 로직(특히publishSafely/publishAfterCommitOrNow)을 공통 기반 클래스나 헬퍼로 추출한다.packages/membership-core/src/libs/MembershipService.ts#L35-L219: 추출된 공통 로직에 위임하도록 변경하고, 중복된 메서드 본문을 제거한다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/membership-core/src/libs/MembershipManager.ts` around lines 35 - 219, MembershipManager와 MembershipService에 동일한 멤버십 로직이 중복되어 있으므로 공통 기반 클래스 또는 헬퍼로 생성자와 addMember, removeMember, updateRole, transferOwnership, checkSeatLimit, ensureValidRole, getMembershipOrThrow, publishSafely, publishAfterCommitOrNow를 추출한다. packages/membership-core/src/libs/MembershipManager.ts#L35-219의 구현을 공통 로직으로 정리하고, packages/membership-core/src/libs/MembershipService.ts#L35-219는 해당 공통 구현에 위임하도록 변경해 중복 메서드 본문을 제거한다.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.changeset/calm-seats-reconcile.md:
- Line 4: Update the changeset entry for `@croco/membership-core` from patch to
minor, reflecting the publish timing behavior change introduced by
publishAfterCommitOrNow prioritizing publishAfterCommit on transactional paths.
In `@docs/package-catalog.json`:
- Around line 744-750: Update the package entry’s features array to replace
“licensed quantity capability diagnostic” with wording that clearly identifies
it as unsupported, consistent with the existing knownGaps entry. Then rerun the
catalog generation script so the generated README Features output reflects the
corrected wording.
In `@packages/billing-core/src/libs/problems/BillingProblems.ts`:
- Around line 222-274: Remove externalSubscriptionId from the detail messages in
SubscriptionQuantityProviderMismatchProblem and
SubscriptionQuantityReconciliationFailedProblem, keeping only safe descriptions;
pass the identifier through the Problem options extensions instead so it remains
available for diagnostics without being exposed in serialized 5xx responses.
In `@packages/billing-core/src/libs/SubscriptionQuantity.ts`:
- Around line 697-708: Update
packages/billing-core/src/libs/SubscriptionQuantity.ts lines 697-708 in fail()
so a null saveIfCurrent() result is handled using the findCurrent() fallback
pattern already used by complete() at lines 635-642; if persistence still fails,
record the error with a diagnostic code and skip publishing the failure event.
Update lines 403-423 so the repair flow no longer passes item.snapshot to
fail(); fetch the latest snapshot with findCurrent() and pass that value
instead.
- Around line 429-431: Update SubscriptionQuantity.getDiagnostics so diagnostics
are not limited to listRecent(limit): use a durable-store status aggregation
query that covers the full dataset, and extend SubscriptionQuantityDiagnostics
with a field indicating whether results were truncated when only a limited
sample is available. Preserve ensureRepairLimit validation and clearly populate
the truncation indicator in every result.
- Around line 490-494: Update the provider operation ID selection in the
reconciliation logic around the snapshot state check to reuse
snapshot.providerOperationId only for retryable failures that are not
SubscriptionQuantityProviderMismatchProblem cases. When a provider quantity
mismatch is observed, clear or bypass the persisted operation ID so the next
attempt generates a fresh `${reconciliationId}:${snapshot.revision + 1}`
identity; apply the same behavior to the corresponding logic around the
additional referenced range.
- Around line 438-455: Update the diagnostics aggregation around
providerMismatches and oldestPendingAt: exclude state === "drifted" from
providerMismatches, leaving only the provider-mismatch failure codes, and
calculate oldestPendingAt using createdAt rather than updatedAt while preserving
the pending/drifted filter.
- Around line 681-689: Update the retryable calculation in the subscription
quantity retry flow to include timeout responses with status 408 and rate-limit
problems categorized as ProblemCategory.TooManyRequests, while preserving the
existing exclusion for ProviderCapabilityUnavailableProblem and the current
500-or-higher handling.
- Around line 271-280: Update SubscriptionQuantityReconcilerDependencies to
expose the minimum I/O controls required by LicensedQuantityGateway: AbortSignal
or timeoutMs, plus circuit-breaker options. Thread these settings through
reconcileWithinSpan() so its direct gateway.getQuantity() and
gateway.setQuantity() calls honor cancellation, timeout, and circuit-breaker
behavior.
In `@packages/billing-core/src/tests/BillingProvider.spec.ts`:
- Around line 99-106: BillingProvider 런타임 게이팅 테스트에 licensed-quantity 지원을 true로
설정한 프로필과 해당 LicensedQuantityGateway 구현이 없는 구성을 추가하고, BillingProvider 생성자가
ProviderCapabilityUnavailableProblem을 던지는지 rejects.toThrow 또는
expect(...).toThrow로 검증하십시오. 기존 expectTypeOf 타입 검증은 유지하세요.
In `@packages/billing-core/src/tests/InMemoryPlanRegistry.spec.ts`:
- Around line 252-279: Expand the “requires an internally valid quantity policy
on every plan version” test to also assert rejection for includedSeats < 0,
seatQuota < 0, and an empty billableMembershipRoles array. Reuse the existing
createVersion and InvalidPlanVersionDefinitionProblem expectations while
preserving the current minimumQuantity and duplicate-role cases.
In `@packages/billing-core/src/tests/SubscriptionQuantityReconciler.spec.ts`:
- Around line 613-652: Move the “BillingProvider licensed-quantity capability”
describe block from SubscriptionQuantityReconciler.spec.ts into
packages/billing-core/src/tests/BillingProvider.spec.ts, preserving its existing
assertions and setup. Keep the test focused on defineBillingProvider,
provider.profile, and requireCapability rather than the subscription reconciler.
- Around line 237-247: Wrap the second reconcile flow around
reconcile(reconciler), releaseFirstGet, and staleResult in a try/finally block
so releaseFirstGet is always invoked when the current reconcile throws. Keep the
existing assertions and result handling unchanged while ensuring the blocked
first get is released before the test exits.
In `@packages/billing-core/src/types.ts`:
- Around line 129-134: Define a literal MembershipRole union within billing-core
and update SubscriptionQuantityPolicy.billableMembershipRoles to use readonly
MembershipRole[] instead of readonly string[].
In `@packages/billing-polar/src/libs/PolarBillingDiagnosticsProvider.ts`:
- Around line 102-104: Update the diagnostics capability output in
PolarBillingDiagnosticsProvider to derive entirely from
POLAR_BILLING_PROVIDER_PROFILE.capabilities instead of hardcoded checkout,
subscription-lifecycle, customer-portal, and licensedQuantity fields. Iterate
the profile capabilities and report each capability’s supported status and
reason, using the canonical BILLING_PROVIDER_CAPABILITIES terminology so
unsupported usage is included and representation is consistent. Update the
corresponding expectations in PolarBillingDiagnosticsProvider.spec.ts.
In
`@packages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityDriftDetectedEvent.md`:
- Line 8: Update the source comments for SubscriptionQuantityDriftDetectedEvent
to describe the drift event and its event payload, and for
SubscriptionQuantityPolicy to describe minimum quantity, included seats, seat
limits, and membership role. Then regenerate both public API documents:
packages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityDriftDetectedEvent.md:8-8
and
packages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityPolicy.md:10-10.
In
`@packages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciliationFailedEvent.md`:
- Line 8: packages/billing-core/src/index.ts의 SubscriptionQuantityReconciler,
InMemorySubscriptionQuantityReconciliationStore, 세 reconciliation 이벤트,
calculateDesiredQuantity, createSubscriptionQuantityReconciliationId에 각각의 역할과
evidence/quantity 또는 계산 동작을 설명하는 정확한 TypeDoc JSDoc을 작성한 뒤 astro build로 문서를
재생성하세요. 생성 결과는
packages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciliationFailedEvent.md
8-8,
packages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciliationSucceededEvent.md
8-8,
packages/docs/src/content/docs/api/billing-core/src/functions/calculateDesiredQuantity.md
10-10을 포함해 각 API 설명이 해당 심볼의 역할과 일치하도록 갱신되어야 합니다.
In
`@packages/docs/src/content/docs/api/billing-core/src/functions/createSubscriptionQuantityReconciliationId.md`:
- Line 10: createSubscriptionQuantityReconciliationId의 TypeScript 원본 문서 주석을 찾아
인메모리 저장소 설명을 제거하고, 테넌트·구독·플랜 버전·희망 수량·source version으로 결정적인 reconciliation ID를
생성하는 함수 계약을 설명하도록 수정하세요. 이후 TypeDoc을 재생성해 해당 Markdown 생성물을 갱신하고, 생성된 파일을 직접 편집하지
마세요.
In `@packages/membership-core/src/tests/MembershipManager.spec.ts`:
- Around line 122-135: MembershipManager.updateRole lacks coverage verifying
deferred event publication during an active transaction. Add an updateRole test
in MembershipManager.spec.ts using the existing addMember/removeMember pattern,
asserting the event is sent through publishAfterCommit and publishNow is not
called.
In `@packages/membership-core/src/tests/MembershipService.spec.ts`:
- Around line 147-153: MembershipService 테스트에는 활성 트랜잭션에서 이벤트가 지연 발행되는 동작 검증이
없습니다. MembershipService.spec.ts의 기존 membership-created 및 membership-removed 테스트에
활성 트랜잭션을 설정하고 publishAfterCommit 호출을 검증하는 케이스를 추가하며, 같은 상황에서 publishNow가 호출되지 않는
것도 확인하세요. 기존 publishAfterCommit 폴백 mock과 publishAfterCommitOrNow 경로를 재사용하고,
트랜잭션이 없거나 폴백이 필요한 테스트 동작은 변경하지 마세요.
In `@packages/testing/src/libs/billing-provider-conformance.ts`:
- Line 143: Update LicensedQuantityGatewayConformanceOptions usage in
BillingProviderConformanceOptions and the related
BillingGatewayConformanceOptions definitions so the unused TGateway generic is
removed consistently, unless a concrete gateway type is required; if retained,
add and propagate the gateway generic through BillingProviderConformanceOptions
instead of relying on the default LicensedQuantityGateway.
- Line 296: Update the two assert.equal calls checking first.status in the
billing provider conformance flow to include failure messages containing
providerName, matching the message style used by the other assertions in this
file.
In `@packages/testing/src/tests/testing.spec.ts`:
- Around line 177-202: 분리되지 않은 구독별 상태 때문에 테스트 더블이 다중 구독 격리를 검증하지 못합니다.
packages/testing/src/tests/testing.spec.ts 177-202의
InMemoryLicensedQuantityGateway에서 quantity와 acceptedSourceVersion을
externalSubscriptionId 키의 Map으로 관리하고, getQuantity(externalSubscriptionId)와
setQuantity의 상태 조회·갱신에 해당 키를 사용하십시오.
packages/billing-core/src/tests/SubscriptionQuantityReconciler.spec.ts 74-99의
ConformingQuantityGateway에서도 quantity, acceptedSourceVersion, results를
externalSubscriptionId별로 분리하여 구독별 커서 테스트가 독립적인 수량을 검증하도록 수정하십시오.
---
Outside diff comments:
In `@packages/billing-polar/src/tests/PolarBillingProviderProfile.spec.ts`:
- Around line 5-13: Update the test title in PolarBillingProviderProfile.spec.ts
around POLAR_BILLING_PROVIDER_PROFILE to mention both the usage capability gap
and the licensed-quantity capability gap, matching the assertions covered by the
test.
In `@packages/membership-core/src/libs/MembershipManager.ts`:
- Around line 35-219: MembershipManager와 MembershipService에 동일한 멤버십 로직이 중복되어
있으므로 공통 기반 클래스 또는 헬퍼로 생성자와 addMember, removeMember, updateRole,
transferOwnership, checkSeatLimit, ensureValidRole, getMembershipOrThrow,
publishSafely, publishAfterCommitOrNow를 추출한다.
packages/membership-core/src/libs/MembershipManager.ts#L35-219의 구현을 공통 로직으로
정리하고, packages/membership-core/src/libs/MembershipService.ts#L35-219는 해당 공통 구현에
위임하도록 변경해 중복 메서드 본문을 제거한다.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 3f27ceb3-dc01-481a-9a77-3d2cd960a61d
⛔ Files ignored due to path filters (2)
packages/problems-core/src/generated/problem-code-registry.tsis excluded by!**/generated/**pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (75)
.changeset/calm-seats-reconcile.mdREADME.mddocs/package-catalog.jsondocs/package-docs-report.mddocs/problem-code-registry.jsonexamples/saas-billing-golden-path/README.mdexamples/saas-billing-golden-path/package.jsonexamples/saas-billing-golden-path/src/integrations/MembershipSeatQuantitySource.tsexamples/saas-billing-golden-path/src/tests/membership-quantity.spec.tspackages/billing-core/README.mdpackages/billing-core/src/index.tspackages/billing-core/src/libs/BillingProvider.tspackages/billing-core/src/libs/BillingProviderCapabilities.tspackages/billing-core/src/libs/InMemoryPlanRegistry.tspackages/billing-core/src/libs/SubscriptionQuantity.tspackages/billing-core/src/libs/problems/BillingProblems.tspackages/billing-core/src/tests/BillingProvider.spec.tspackages/billing-core/src/tests/InMemoryPlanRegistry.spec.tspackages/billing-core/src/tests/SubscriptionQuantityReconciler.spec.tspackages/billing-core/src/types.tspackages/billing-polar/src/libs/PolarBillingDiagnosticsProvider.tspackages/billing-polar/src/libs/PolarBillingProviderProfile.tspackages/billing-polar/src/tests/PolarBillingDiagnosticsProvider.spec.tspackages/billing-polar/src/tests/PolarBillingProviderProfile.spec.tspackages/billing-polar/src/tests/PolarWebhookHandler.spec.tspackages/billing-polar/src/tests/PolarWebhookHandlerSdk.spec.tspackages/docs/src/content/docs/api/billing-core/src/classes/InMemorySubscriptionQuantityReconciliationStore.mdpackages/docs/src/content/docs/api/billing-core/src/classes/InvalidSubscriptionQuantityProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityDriftDetectedEvent.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityDriftRecoveredEvent.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityProviderMismatchProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityProviderSourceAheadProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciler.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciliationConflictProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciliationFailedEvent.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciliationFailedProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciliationSucceededEvent.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantitySourceMismatchProblem.mdpackages/docs/src/content/docs/api/billing-core/src/functions/calculateDesiredQuantity.mdpackages/docs/src/content/docs/api/billing-core/src/functions/createSubscriptionQuantityReconciliationId.mdpackages/docs/src/content/docs/api/billing-core/src/interfaces/LicensedQuantityGateway.mdpackages/docs/src/content/docs/api/billing-core/src/interfaces/SubscriptionQuantityReconciliationStore.mdpackages/docs/src/content/docs/api/billing-core/src/interfaces/SubscriptionQuantityRepairSource.mdpackages/docs/src/content/docs/api/billing-core/src/interfaces/SubscriptionQuantitySource.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/CreateSubscriptionQuantityIntent.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/LicensedQuantityObservation.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/PlanVersionDefinition.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/ReconcileSubscriptionQuantitiesResult.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/ReconcileSubscriptionQuantityInput.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SetLicensedQuantityInput.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SetLicensedQuantityResult.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityDiagnostics.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityFailureEvidence.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityPolicy.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityReconcilerDependencies.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityReconciliationEventPublisher.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityReconciliationState.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantitySnapshot.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantitySourceInput.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantitySourceSnapshot.mdpackages/docs/src/content/docs/api/events-core/src/classes/DomainEvent.mdpackages/docs/src/content/docs/api/testing/src/type-aliases/LicensedQuantityGatewayConformanceOptions.mdpackages/docs/src/content/docs/en/reference/extension-matrix.mdpackages/docs/src/content/docs/en/reference/problem-recovery-cookbook.mdpackages/membership-core/README.mdpackages/membership-core/src/libs/MembershipManager.tspackages/membership-core/src/libs/MembershipService.tspackages/membership-core/src/tests/MembershipManager.spec.tspackages/membership-core/src/tests/MembershipService.spec.tspackages/metrics-billing/src/tests/BillingEventHandler.spec.tspackages/testing/src/index.tspackages/testing/src/libs/billing-provider-conformance.tspackages/testing/src/tests/BillingProviderCapabilityConformance.spec.tspackages/testing/src/tests/testing.spec.tspublic-api-surface.snapshot.json
3b12864 to
6d77cc8
Compare
|
@coderabbitai resolve |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/membership-core/src/libs/MembershipManager.ts (1)
206-218: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win중복 멤버십 구현을 단일 클래스로 유지하십시오.
MembershipManager와MembershipService의 본문이 동일하지만MembershipService는 현재 패키지와 테스트에서 사용 중이라 단순히 삭제할 수 없습니다.MembershipService를MembershipManager래퍼로 바꾸거나, 실제 서비스职责이 추가되어야 할 경우 본문 중첩을 피하도록 분리하십시오.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/membership-core/src/libs/MembershipManager.ts` around lines 206 - 218, 중복된 멤버십 구현을 단일 클래스로 유지하도록 packages/membership-core/src/libs/MembershipManager.ts 206-218의 MembershipManager 구현을 기준으로 정리하고, packages/membership-core/src/libs/MembershipService.ts 206-218의 동일한 본문은 MembershipManager를 위임하는 래퍼로 변경하십시오. 기존 패키지와 테스트의 MembershipService 사용 계약은 유지하고, 두 클래스에 동일한 로직이 남지 않도록 하십시오.
♻️ Duplicate comments (1)
packages/billing-core/src/libs/problems/BillingProblems.ts (1)
239-249: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win두 Conflict Problem의
detail에externalSubscriptionId가 남아 있습니다.이전 리뷰에서
SubscriptionQuantityProviderMismatchProblem과SubscriptionQuantityReconciliationFailedProblem의detail에서 식별자를 제거하고extensions로 옮겼습니다. 그러나SubscriptionQuantityReconciliationConflictProblem(Line 246)과SubscriptionQuantityProviderSourceAheadProblem(Line 286)은 여전히detail문자열에externalSubscriptionId를 포함합니다. 두 Problem 모두ProblemCategory.Conflict이므로 409 응답으로 클라이언트에 도달할 수 있습니다. 같은 근본 원인이므로 동일하게 처리하십시오.🔒️ 제안 수정
constructor(externalSubscriptionId: string, sourceVersion: number) { super( undefined, undefined, - `Subscription '${externalSubscriptionId}' source version ${sourceVersion} resolved to conflicting quantity intents`, + `Source version ${sourceVersion} resolved to conflicting quantity intents`, + { extensions: { externalSubscriptionId } }, ); }super( undefined, undefined, - `Subscription '${externalSubscriptionId}' provider accepted source version ${providerSourceVersion} ahead of local version ${localSourceVersion}`, + `Provider accepted source version ${providerSourceVersion} ahead of local version ${localSourceVersion}`, + { extensions: { externalSubscriptionId } }, ); }Also applies to: 275-289
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/billing-core/src/libs/problems/BillingProblems.ts` around lines 239 - 249, Update SubscriptionQuantityReconciliationConflictProblem and SubscriptionQuantityProviderSourceAheadProblem so their detail messages no longer include externalSubscriptionId; move the identifier into the Problems’ extensions, matching SubscriptionQuantityProviderMismatchProblem and SubscriptionQuantityReconciliationFailedProblem while preserving the existing conflict codes and messages otherwise.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/billing-core/README.md`:
- Around line 202-210: Update the README SubscriptionQuantityReconciler example
to replace the nonexistent billingProvider.require() call with
BillingProvider.requireCapability("licensed-quantity"), matching the API used
elsewhere in the README.
- Around line 109-110: Update the defineBillingProviderProfile() example in the
README to include the required "licensed-quantity" capability alongside
"checkout" and "usage", so it conforms to BillingProviderCapabilityProfile.
In `@packages/billing-core/src/index.ts`:
- Around line 155-170: Combine the eight value exports from
./libs/SubscriptionQuantity into a single grouped export block in the index
barrel. Preserve each symbol and its descriptive comment within the block,
following the category-grouping convention; no separate type exports are
involved here.
In `@packages/billing-core/src/libs/InMemoryPlanRegistry.ts`:
- Around line 131-140: Update the plan-version validation around the
quantityPolicy checks to first verify that planVersion.quantityPolicy exists and
is non-null, returning or raising InvalidPlanVersionDefinitionProblem through
the existing validation path when it is missing. Only access minimumQuantity,
includedSeats, seatQuota, or billableMembershipRoles after this guard; preserve
the current field-value validation for present policies.
In `@packages/billing-core/src/libs/SubscriptionQuantity.ts`:
- Around line 914-927: Update freezeSnapshot() to clone every Date field in
SubscriptionQuantitySnapshot before freezing, including createdAt, updatedAt,
lastAttemptAt, and lastSuccessAt, matching cloneSnapshot()’s defensive-copy
behavior; preserve the existing sourceEvidence and lastFailure freezing while
ensuring callers cannot mutate stored timestamp objects.
- Around line 413-418: Update the result-state aggregation in repair() so only
actual failure states increment failed; do not classify drifted or pending as
failures, and preserve the existing in_sync and superseded counts. If those
non-failure states must be exposed, add dedicated pending and drifted fields to
ReconcileSubscriptionQuantitiesResult and populate them separately, including
unsupported only according to its defined failure semantics.
In `@packages/billing-core/src/tests/SubscriptionQuantityReconciler.spec.ts`:
- Around line 159-183: Update createFixture to accept an options object
containing eventPublisher and maxAttempts, using the existing event publisher
and reconciliation configuration as defaults. Pass maxAttempts into
SubscriptionQuantityReconciler and allow callers to override eventPublisher,
then refactor the five affected tests to use these options instead of rebuilding
the registry, sources, gateway, store, and reconciler.
In
`@packages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityProviderMismatchProblem.md`:
- Line 8: Update the original TypeDoc comments for
SubscriptionQuantityProviderMismatchProblem and
SubscriptionQuantityProviderSourceAheadProblem so their descriptions distinguish
provider quantity mismatches from provider source versions ahead of the local
version; do not edit the generated Markdown directly. Regenerate the API
documentation, including both affected files at
packages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityProviderMismatchProblem.md:8-8
and
packages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityProviderSourceAheadProblem.md:8-8,
then validate with pnpm docs:api:check.
In
`@packages/docs/src/content/docs/api/billing-core/src/type-aliases/MembershipRole.md`:
- Line 10: TypeScript 원본의 MembershipRole 문서 주석을 실제 계약인 owner, admin, member,
viewer 역할 집합에 맞게 수정하세요. 생성된 MembershipRole Markdown은 직접 편집하지 말고 pnpm
docs:api:check를 실행해 문서를 재생성하고 검증하세요.
In `@packages/membership-core/src/tests/MembershipService.spec.ts`:
- Around line 133-146: Update the deferred membership-created event test around
MembershipService.addMember to assert the queued event’s data with toEqual,
including tenantId "tenant-1", userId "user-1", and role "member", while
retaining the existing length and publishNow assertions; remove any unused
type-only import or toBeInstanceOf usage related to this test.
---
Outside diff comments:
In `@packages/membership-core/src/libs/MembershipManager.ts`:
- Around line 206-218: 중복된 멤버십 구현을 단일 클래스로 유지하도록
packages/membership-core/src/libs/MembershipManager.ts 206-218의
MembershipManager 구현을 기준으로 정리하고,
packages/membership-core/src/libs/MembershipService.ts 206-218의 동일한 본문은
MembershipManager를 위임하는 래퍼로 변경하십시오. 기존 패키지와 테스트의 MembershipService 사용 계약은 유지하고,
두 클래스에 동일한 로직이 남지 않도록 하십시오.
---
Duplicate comments:
In `@packages/billing-core/src/libs/problems/BillingProblems.ts`:
- Around line 239-249: Update SubscriptionQuantityReconciliationConflictProblem
and SubscriptionQuantityProviderSourceAheadProblem so their detail messages no
longer include externalSubscriptionId; move the identifier into the Problems’
extensions, matching SubscriptionQuantityProviderMismatchProblem and
SubscriptionQuantityReconciliationFailedProblem while preserving the existing
conflict codes and messages otherwise.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 1687c90c-c667-41f5-9fc5-62caaedfdfba
⛔ Files ignored due to path filters (2)
packages/problems-core/src/generated/problem-code-registry.tsis excluded by!**/generated/**pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (84)
.changeset/calm-seats-reconcile.mdREADME.mddocs/package-catalog.jsondocs/package-docs-report.mddocs/problem-code-registry.jsonexamples/saas-billing-golden-path/README.mdexamples/saas-billing-golden-path/package.jsonexamples/saas-billing-golden-path/src/integrations/MembershipSeatQuantitySource.tsexamples/saas-billing-golden-path/src/tests/membership-quantity.spec.tspackages/billing-core/README.mdpackages/billing-core/src/index.tspackages/billing-core/src/libs/BillingProvider.tspackages/billing-core/src/libs/BillingProviderCapabilities.tspackages/billing-core/src/libs/InMemoryPlanRegistry.tspackages/billing-core/src/libs/SubscriptionQuantity.tspackages/billing-core/src/libs/problems/BillingProblems.tspackages/billing-core/src/tests/BillingProvider.spec.tspackages/billing-core/src/tests/InMemoryPlanRegistry.spec.tspackages/billing-core/src/tests/SubscriptionQuantityReconciler.spec.tspackages/billing-core/src/types.tspackages/billing-polar/src/libs/PolarBillingDiagnosticsProvider.tspackages/billing-polar/src/libs/PolarBillingProviderProfile.tspackages/billing-polar/src/tests/PolarBillingDiagnosticsProvider.spec.tspackages/billing-polar/src/tests/PolarBillingProviderProfile.spec.tspackages/billing-polar/src/tests/PolarWebhookHandler.spec.tspackages/billing-polar/src/tests/PolarWebhookHandlerSdk.spec.tspackages/docs/src/content/docs/api/billing-core/src/classes/BillingProvider.mdpackages/docs/src/content/docs/api/billing-core/src/classes/InMemorySubscriptionQuantityReconciliationStore.mdpackages/docs/src/content/docs/api/billing-core/src/classes/InvalidSubscriptionQuantityProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/ProviderCapabilityUnavailableProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityDriftDetectedEvent.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityDriftRecoveredEvent.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityProviderMismatchProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityProviderSourceAheadProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciler.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciliationConflictProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciliationFailedEvent.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciliationFailedProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciliationSucceededEvent.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantitySourceMismatchProblem.mdpackages/docs/src/content/docs/api/billing-core/src/functions/calculateDesiredQuantity.mdpackages/docs/src/content/docs/api/billing-core/src/functions/createSubscriptionQuantityReconciliationId.mdpackages/docs/src/content/docs/api/billing-core/src/interfaces/LicensedQuantityGateway.mdpackages/docs/src/content/docs/api/billing-core/src/interfaces/SubscriptionQuantityReconciliationStore.mdpackages/docs/src/content/docs/api/billing-core/src/interfaces/SubscriptionQuantityRepairSource.mdpackages/docs/src/content/docs/api/billing-core/src/interfaces/SubscriptionQuantitySource.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/BillingProviderCapabilityProfile.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/CreateSubscriptionQuantityIntent.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/LicensedQuantityObservation.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/MembershipRole.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/PlanVersionDefinition.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/ReconcileSubscriptionQuantitiesResult.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/ReconcileSubscriptionQuantityInput.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SetLicensedQuantityInput.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SetLicensedQuantityResult.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityDiagnostics.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityFailureEvidence.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityPolicy.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityReconcilerDependencies.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityReconciliationEventPublisher.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityReconciliationState.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantitySnapshot.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantitySourceInput.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantitySourceSnapshot.mdpackages/docs/src/content/docs/api/billing-core/src/variables/BILLING_PROVIDER_CAPABILITIES.mdpackages/docs/src/content/docs/api/billing-polar/src/variables/POLAR_BILLING_PROVIDER_PROFILE.mdpackages/docs/src/content/docs/api/events-core/src/classes/DomainEvent.mdpackages/docs/src/content/docs/api/problems-core/src/classes/Problem.mdpackages/docs/src/content/docs/api/problems-core/src/variables/CROCO_PROBLEM_CODE_REGISTRY.mdpackages/docs/src/content/docs/api/testing/src/type-aliases/BillingProviderConformanceOptions.mdpackages/docs/src/content/docs/api/testing/src/type-aliases/LicensedQuantityGatewayConformanceOptions.mdpackages/docs/src/content/docs/en/reference/extension-matrix.mdpackages/docs/src/content/docs/en/reference/problem-recovery-cookbook.mdpackages/membership-core/README.mdpackages/membership-core/src/libs/MembershipManager.tspackages/membership-core/src/libs/MembershipService.tspackages/membership-core/src/tests/MembershipManager.spec.tspackages/membership-core/src/tests/MembershipService.spec.tspackages/metrics-billing/src/tests/BillingEventHandler.spec.tspackages/testing/src/index.tspackages/testing/src/libs/billing-provider-conformance.tspackages/testing/src/tests/BillingProviderCapabilityConformance.spec.tspackages/testing/src/tests/testing.spec.tspublic-api-surface.snapshot.json
✅ Action performedComments resolved and changes approved. |
6d77cc8 to
a06ab31
Compare
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/billing-core/src/libs/BillingProvider.ts (1)
64-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick wincapability 동결을
BILLING_PROVIDER_CAPABILITIES순회로 유도하십시오.Line 66-70은 capability 키를 수동으로 나열합니다. 이번 변경도
"licensed-quantity"한 줄을 직접 추가해야 했습니다. 다음 capability 추가 시 이 줄을 빠뜨리면 해당 capability 설정이 동결된 profile에서 누락됩니다. 타입 검사는 이 누락을 잡지 못합니다.BILLING_PROVIDER_CAPABILITIES를 단일 출처로 사용하십시오.♻️ 제안 리팩터
return Object.freeze({ providerName: profile.providerName, - capabilities: Object.freeze({ - checkout: Object.freeze({ ...profile.capabilities.checkout }), - "licensed-quantity": Object.freeze({ ...profile.capabilities["licensed-quantity"] }), - usage: Object.freeze({ ...profile.capabilities.usage }), - }), + capabilities: Object.freeze( + Object.fromEntries( + BILLING_PROVIDER_CAPABILITIES.map((capability) => [ + capability, + Object.freeze({ ...profile.capabilities[capability] }), + ]), + ), + ), }) as BillingProviderProfile<ProviderName, Capabilities>;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/billing-core/src/libs/BillingProvider.ts` around lines 64 - 71, Update the capability-freezing logic in the profile construction block to iterate over BILLING_PROVIDER_CAPABILITIES instead of manually listing checkout, licensed-quantity, and usage. Build the frozen capabilities object from every configured capability key so future additions are automatically included, while preserving the existing shallow-clone and Object.freeze behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.changeset/calm-seats-reconcile.md:
- Line 7: `.changeset/calm-seats-reconcile.md`에서 `@croco/problems-core`의 릴리스 범위를
`patch`에서 `minor`로 변경하세요. `index.ts`를 통해 공개되는 `CrocoProblemCode`에 새 billing 코드가
추가되는 변경이므로 다른 패키지나 변경 내용은 수정하지 마세요.
In `@packages/billing-core/src/libs/InMemoryPlanRegistry.ts`:
- Around line 135-140: Update the quantityPolicy validation in
InMemoryPlanRegistry to reject policies where includedSeats or minimumQuantity
exceeds seatQuota, while preserving the existing independent field checks.
Ensure invalid cross-field combinations are rejected during plan publication
before calculateDesiredQuantity can apply an over-quota quantity.
In `@packages/billing-core/src/libs/problems/BillingProblems.ts`:
- Around line 239-249: Update SubscriptionQuantityReconciliationConflictProblem
in packages/billing-core/src/libs/problems/BillingProblems.ts:239-249 to pass
externalSubscriptionId and sourceVersion through the Problem extensions object
instead of embedding them in the human-readable detail message. Align the
constructor and detail format with SubscriptionQuantityProviderMismatchProblem
at packages/billing-core/src/libs/problems/BillingProblems.ts:275-289; no direct
change is required there because it already uses extensions.
In `@packages/billing-core/src/tests/InMemoryPlanRegistry.spec.ts`:
- Around line 287-289: Update the asynchronous rejection assertion in the
publishPlanVersion test to use Vitest’s rejects.toThrow matcher instead of
rejects.toBeInstanceOf, while preserving validation of the
InvalidPlanVersionDefinitionProblem failure.
In `@packages/billing-polar/src/tests/PolarBillingProviderProfile.spec.ts`:
- Around line 5-13: PolarBillingProviderProfile.spec.ts의 테스트 격리를 위해 beforeEach를
추가하십시오. 각 테스트 전에 DI Container를 reset하고 필요한 테스트 인스턴스를 초기화하도록 구성하며, 기존
POLAR_BILLING_PROVIDER_PROFILE 검증은 변경하지 마십시오.
In
`@packages/docs/src/content/docs/api/billing-core/src/classes/InvalidSubscriptionQuantityProblem.md`:
- Line 8: InvalidSubscriptionQuantityProblem 설명이 의미와 reason 인자를 반영하도록 해당 TypeDoc
원본 주석을 구체화하고, 생성된 Markdown은 직접 수정하지 말고 API 문서를 재생성하세요. 변경 후 pnpm docs:api:check로
생성 결과를 검증하세요.
In
`@packages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityPolicy.md`:
- Around line 8-10: Update the TypeDoc source for SubscriptionQuantityPolicy in
packages/billing-core/src/index.ts by adding an accurate type-specific
description or relocating its comment so TypeDoc no longer inherits the
surrounding domain description. Regenerate the API documentation and verify the
result with pnpm docs:api:check.
In `@packages/membership-core/src/tests/MembershipManager.spec.ts`:
- Around line 122-135: Extend the deferred membership-created test around
MembershipManager.addMember to assert afterCommitEvents[0]?.data with toEqual(),
verifying tenantId, userId, and role values. Apply the same payload assertions
to the deferred membership-deleted and role-changed event tests referenced in
the comment, while preserving their existing event-count and publishNow
expectations.
---
Outside diff comments:
In `@packages/billing-core/src/libs/BillingProvider.ts`:
- Around line 64-71: Update the capability-freezing logic in the profile
construction block to iterate over BILLING_PROVIDER_CAPABILITIES instead of
manually listing checkout, licensed-quantity, and usage. Build the frozen
capabilities object from every configured capability key so future additions are
automatically included, while preserving the existing shallow-clone and
Object.freeze behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f18d6cc4-a7fc-46f9-8b2e-9598e1c85a18
⛔ Files ignored due to path filters (2)
packages/problems-core/src/generated/problem-code-registry.tsis excluded by!**/generated/**pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (85)
.changeset/calm-seats-reconcile.mdREADME.mddocs/package-catalog.jsondocs/package-docs-report.mddocs/problem-code-registry.jsonexamples/saas-billing-golden-path/README.mdexamples/saas-billing-golden-path/package.jsonexamples/saas-billing-golden-path/src/integrations/MembershipSeatQuantitySource.tsexamples/saas-billing-golden-path/src/tests/membership-quantity.spec.tspackages/billing-core/README.mdpackages/billing-core/src/index.tspackages/billing-core/src/libs/BillingProvider.tspackages/billing-core/src/libs/BillingProviderCapabilities.tspackages/billing-core/src/libs/InMemoryPlanRegistry.tspackages/billing-core/src/libs/SubscriptionQuantity.tspackages/billing-core/src/libs/problems/BillingProblems.tspackages/billing-core/src/tests/BillingProvider.spec.tspackages/billing-core/src/tests/InMemoryPlanRegistry.spec.tspackages/billing-core/src/tests/SubscriptionQuantityReconciler.spec.tspackages/billing-core/src/types.tspackages/billing-polar/src/libs/PolarBillingDiagnosticsProvider.tspackages/billing-polar/src/libs/PolarBillingProviderProfile.tspackages/billing-polar/src/tests/PolarBillingDiagnosticsProvider.spec.tspackages/billing-polar/src/tests/PolarBillingProviderProfile.spec.tspackages/billing-polar/src/tests/PolarWebhookHandler.spec.tspackages/billing-polar/src/tests/PolarWebhookHandlerSdk.spec.tspackages/docs/src/content/docs/api/billing-core/src/classes/BillingProvider.mdpackages/docs/src/content/docs/api/billing-core/src/classes/InMemorySubscriptionQuantityReconciliationStore.mdpackages/docs/src/content/docs/api/billing-core/src/classes/InvalidSubscriptionQuantityProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/ProviderCapabilityUnavailableProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityDriftDetectedEvent.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityDriftRecoveredEvent.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityProviderMismatchProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityProviderSourceAheadProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciler.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciliationConflictProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciliationFailedEvent.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciliationFailedProblem.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantityReconciliationSucceededEvent.mdpackages/docs/src/content/docs/api/billing-core/src/classes/SubscriptionQuantitySourceMismatchProblem.mdpackages/docs/src/content/docs/api/billing-core/src/functions/calculateDesiredQuantity.mdpackages/docs/src/content/docs/api/billing-core/src/functions/createSubscriptionQuantityReconciliationId.mdpackages/docs/src/content/docs/api/billing-core/src/interfaces/LicensedQuantityGateway.mdpackages/docs/src/content/docs/api/billing-core/src/interfaces/SubscriptionQuantityReconciliationStore.mdpackages/docs/src/content/docs/api/billing-core/src/interfaces/SubscriptionQuantityRepairSource.mdpackages/docs/src/content/docs/api/billing-core/src/interfaces/SubscriptionQuantitySource.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/BillingProviderCapabilityProfile.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/CreateSubscriptionQuantityIntent.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/LicensedQuantityObservation.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/MembershipRole.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/PlanVersionDefinition.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/ReconcileSubscriptionQuantitiesResult.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/ReconcileSubscriptionQuantityInput.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SetLicensedQuantityInput.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SetLicensedQuantityResult.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityDiagnostics.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityFailureEvidence.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityPolicy.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityReconcilerDependencies.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityReconciliationEventPublisher.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantityReconciliationState.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantitySnapshot.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantitySourceInput.mdpackages/docs/src/content/docs/api/billing-core/src/type-aliases/SubscriptionQuantitySourceSnapshot.mdpackages/docs/src/content/docs/api/billing-core/src/variables/BILLING_PROVIDER_CAPABILITIES.mdpackages/docs/src/content/docs/api/billing-polar/src/variables/POLAR_BILLING_PROVIDER_PROFILE.mdpackages/docs/src/content/docs/api/events-core/src/classes/DomainEvent.mdpackages/docs/src/content/docs/api/problems-core/src/classes/Problem.mdpackages/docs/src/content/docs/api/problems-core/src/variables/CROCO_PROBLEM_CODE_REGISTRY.mdpackages/docs/src/content/docs/api/testing/src/type-aliases/BillingProviderConformanceOptions.mdpackages/docs/src/content/docs/api/testing/src/type-aliases/LicensedQuantityGatewayConformanceOptions.mdpackages/docs/src/content/docs/en/reference/extension-matrix.mdpackages/docs/src/content/docs/en/reference/problem-recovery-cookbook.mdpackages/membership-core/README.mdpackages/membership-core/src/libs/MembershipManager.tspackages/membership-core/src/libs/MembershipService.tspackages/membership-core/src/tests/MembershipManager.spec.tspackages/membership-core/src/tests/MembershipService.spec.tspackages/membership-drizzle/src/tests/DrizzleMembershipStore.postgres.spec.tspackages/metrics-billing/src/tests/BillingEventHandler.spec.tspackages/testing/src/index.tspackages/testing/src/libs/billing-provider-conformance.tspackages/testing/src/tests/BillingProviderCapabilityConformance.spec.tspackages/testing/src/tests/testing.spec.tspublic-api-surface.snapshot.json
a06ab31 to
a4372dc
Compare
|
@coderabbitai resolve |
✅ Action performedComments resolved and changes approved. |
a4372dc to
0716850
Compare
0716850 to
92b0f73
Compare
Outcome
Subscriptions can now converge provider-billed licensed quantities from immutable, versioned membership evidence without coupling membership commits to remote billing availability.
The provider-neutral contract adds explicit licensed-quantity capability discovery, stable reconciliation and provider-operation identities, stale-source protection, deterministic retry and terminal evidence, bounded repair discovery, drift diagnostics, and lifecycle events. The SaaS golden path creates intents only after membership commit and performs provider I/O in the reconciliation worker.
Fixes #1570
Verification
pnpm check— 24/25 repository gates passed, 1 not applicablepnpm test— 232/232 workspace tasks passedpnpm typecheck— 231/231 workspace tasks passedpnpm --filter @croco/billing-core test— 121/121 tests passedpnpm public-api:check— 114 package snapshots matchedpnpm docs:api:check— 115/115 build tasks passed and generated API docs matchedpnpm provider-certification:check— 0 blocking failurespnpm problem-registry:check— 540/540 codes passedReview gates
Review focus
Residual risk
The included reconciliation store and SaaS source are in-memory reference implementations. Production compositions must provide durable implementations of both contracts and schedule the bounded repair worker.
Summary by CodeRabbit
새로운 기능
문서
테스트