Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
331 KiB
Scratchpad — Service-Period-First Billing and Explicit Cadence Ownership
- Plan slug:
service-period-first-billing-and-cadence-ownership - Created:
2026-03-16 - Replanned:
2026-03-17
What This Is
Keep a lightweight, continuously-updated log of discoveries, sequencing decisions, and subsystem impact notes for the service-period-first billing work.
This scratchpad was expanded on 2026-03-17 after concluding that the first draft plan was materially under-specified for the risk level of the change.
Decisions
- (2026-03-18) Treat
F231as the logical record-contract checkpoint for materialized recurring periods, not the physical schema checkpoint.- Rationale:
F232still needs freedom to choose concrete table/index layout, but the repo needs one shared record vocabulary now so docs, APIs, fixtures, and future migrations do not drift.
- Rationale:
- (2026-03-16) Sequence this plan so service periods become canonical under existing client-cadence behavior before exposing contract-owned cadence. Parity first, new option second, cleanup last.
- (2026-03-16) Scope the first cut to recurring contract-backed charges. Time and usage remain on their current event-driven model unless a compatibility blocker appears.
- (2026-03-16) Treat invoice windows as the grouping layer and service periods as the recurring-billing truth for this plan.
- (2026-03-17) Replan this effort at system breadth rather than billing-engine breadth. The plan must explicitly cover invoice generation, invoice detail consumers, credits/prepayment/negative invoice behavior, APIs/models/repos, templates/wizards/forms, portal/report/export surfaces, migrations/defaulting, and post-cutover cleanup.
- (2026-03-17) Materialized service periods are now in-scope for v1. The main reason is product editability: if users need to change a future recurring period explicitly, a derived-only model pushes the system toward hidden overrides instead of a first-class editable billing object.
- (2026-03-17) Use recursive top-down decomposition for the feature/test lists:
- architecture + parity
- shared domain
- client cadence
- due-position and partial-period rules
- fixed recurring
- dependent recurring behaviors
- invoice generation + billing flows
- data model/API/UI
- contract cadence
- migration/cleanup
- (2026-03-17) Second-pass agent critique showed the plan still needed dedicated categories for:
- recurring execution identity and scheduler payloads
- invoice grouping and split legality
- parent-charge versus detail-row read-model contracts
- manual/prepayment/credit service-period policy
- export adapter flattening rules
- authoring-path propagation through templates, presets, and custom lines
- stale dropped-table and repository normalization cleanup
Discoveries / Constraints
-
(2026-03-18) Mixed-cadence coexistence now has one DB-backed export-plus-portal sanity seam, which closes
T278, and the remaining conditional split/merge checklist item is now resolved explicitly:server/src/test/integration/accounting/invoiceSelection.integration.test.tsnow seeds one historical flat invoice plus one canonical mixed-cadence invoice, then provesAccountingExportInvoiceSelector.createBatchFromFilters(...)stores stable historical-vs-canonical export lines whileInvoice.getFullInvoiceById(...)rereads the same canonical recurring detail rows without collapsing the historical invoice into fake recurring metadata- the portal-side assertion intentionally uses date-only equality for parent
service_period_*summary bounds because DB-backedDatehydration can surface offset-normalized ISO timestamps while the authoritativerecurring_detail_periods[]contract remains unchanged; this keeps the test locked to the stable business period semantics instead of one driver-specific timestamp shape T297is now treated as satisfied by the already-implemented v1 non-support decision inT347: split and merge remain explicitly unsupported edit operations, so the conditional “if supported in v1” behavior never becomes active in this rollout and does not require a contradictory execution path- focused validation for this checkpoint used:
cd server && DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/integration/accounting/invoiceSelection.integration.test.ts -t "T278" --hookTimeout 600000 --coverage.enabled false
-
(2026-03-18) Additional DB-backed persisted-ledger lifecycle coverage now closes
T323,T324,T325,T326, andT327:server/src/test/integration/billingInvoiceTiming.integration.test.tsnow lets the existing staged-rollout lifecycle seam explicitly claim the regeneration/change-management contract asT316/T323/T324/T327: regenerated future rows supersede only the eligible untouched future slot, edited rows stay preserved, billed rows fail future edit attempts withimmutable_record, and the linkedinvoice_charge_details.item_detail_idrow remains traceable from the persisted billed record- the same file now adds a dedicated
T325contract-cadence operational-view case, proving future 8th-anchored contract-owned periods can be listed and boundary-edited before invoice generation with the same explicit provenance and exception-state semantics as client cadence - the new
T326integration keeps mixed materialization honest at the physical ledger seam instead of only the shared-domain seam: client-owned and contract-owned recurring obligations on the same client persist distinctschedule_keyledgers, retain their own cadence-owner metadata, and keep their own invoice-window anchors (1stvs8th) without row collisions - focused validation for this checkpoint used:
cd server && DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T316|T323|T324|T327|T325|T326" --hookTimeout 600000 --coverage.enabled false
-
(2026-03-18) DB-backed persisted-ledger inspection/edit/runtime coverage now closes
T301,T320,T321,T322, andT328, and exposed a real zero-dollar suppression bug on the live invoice path:server/src/test/integration/billingInvoiceTiming.integration.test.tsnow proves billing staff can list future client-cadence persisted periods, edit a future row with explicit edited provenance, move due selection to a new invoice window without rewriting billed history, and skip the current due row while later persisted work remains selectable- the integration fixtures had to align with two live contracts the earlier domain tests did not exercise directly:
- DB-backed
recurring_service_periodsdate columns hydrate asDateobjects, so the integration loader now normalizes service/invoice/activity ranges back to plainYYYY-MM-DDstrings before feeding listing/edit/due-selection helpers - live runtime selection keys persisted recurring obligations by the effective
contract_line_idvalue exposed asclient_contract_line_idinBillingEngine, so the runtime-facing DB tests now materialize obligation ids againstfixedLine.contractLineIdinstead of the helper’s separate client-line alias
- DB-backed
shared/billingClients/recurringServicePeriodEditRequests.tsnow accepts an optionalrecordIdFactory, which closes the remaining wrapper seam between the domain-level edit contract and the physicalrecurring_service_periods.record_id uuidschemapackages/billing/src/actions/invoiceGeneration.tsnow honors the realsuppress_zero_dollar_invoicesboolean instead of checking for an impossiblezero_dollar_invoice_handling === 'suppress'enum value;server/src/test/unit/billing/invoiceGeneration.emptyResult.test.tsnow locks the real schema contract instead of the impossible enum- focused validation for this checkpoint used:
cd server && DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/unit/billing/invoiceGeneration.emptyResult.test.ts src/test/unit/billing/recurringServicePeriodEditRequests.domain.test.ts src/test/integration/billingInvoiceTiming.integration.test.ts -t "T320|T301|T321|T322|T328|T295" --hookTimeout 600000 --coverage.enabled false
-
(2026-03-18) DB-backed contract-cadence execution now explicitly covers both monthly and annual selector-window hydration, which closes
T276andT277:server/src/test/integration/billingInvoiceTiming.integration.test.tsnow adds a dedicated monthlyT276case that goes beyond the earlier selector-input sanity check: an 8th-anchored contract-cadence line now proves one contract-owned execution window yields one persisted invoice window and rereads throughInvoice.getFullInvoiceById(...)with canonical recurring detail hydration intact- the same integration file now adds
T277for annual contract cadence, proving the same selector-input execution identity and detail-backed invoice hydration work when the contract-owned window spans2025-03-08through2026-03-08instead of a monthly boundary - while landing those tests, the only correction needed was in the assertions, not runtime code:
Invoice.getFullInvoiceById(...)does not expose invoice-header billing-window fields directly, so the test now checks header persistence via the existing DB reader and reserves the hydrated invoice view assertions for canonical recurring detail projection - focused validation for this checkpoint used:
cd server && DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T276|T277" --hookTimeout 600000 --coverage.enabled false
-
(2026-03-18) Report-output parity now explicitly covers the client-cadence no-drift case, which closes
T269:server/src/test/unit/contractReportActions.recurringServicePeriodBasis.test.tsnow runsgetContractRevenueReport()against two equivalent client-cadence revenue fact shapes: one legacy invoice-date-only row and one canonical detail-backed row with the same within-year commercial outcome- the new
T269assertion matters because the existing report tests mostly covered intended semantic pivots at year boundaries; this one locks the parity requirement that detail-aware readers must not change report output when invoice-date fallback and canonical service-period interpretation are equivalent for the same client-cadence billing result - focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/contractReportActions.recurringServicePeriodBasis.test.ts --coverage.enabled false
-
(2026-03-18) Explicit edit-request save/validation responses now cover the missing persisted-period edit wrapper seam, which close
T295andT296:server/src/test/unit/billing/recurringServicePeriodEditRequests.domain.test.tsnow exercisesapplyRecurringServicePeriodEditRequest(...)directly instead of only the lower-level edit helpers, which is the first test seam that looks like the later UI/API edit surface inF251- the new
T295case proves a boundary-adjustment request can succeed with an explicit edited revision and can also fail with structuredcontinuity_overlap_beforevalidation output when sibling context would make the edit invalid - the new
T296case proves skip requests return explicit superseding state while billed, invoice-linked rows fail fast through the wrapper withimmutable_recordinstead of pretending defer/repair flows may mutate linked history in place - focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodEditRequests.domain.test.ts --coverage.enabled false
-
(2026-03-18) Mixed-cadence export-adapter service-period behavior is now explicit, which closes
T274:server/src/test/unit/accounting/quickBooksOnlineAdapter.spec.tsnow adds a focused mixed-cadence invoice case proving QuickBooks preserves each exported line’s own service date even when the same invoice contains both client- and contract-cadence canonical recurring rowsserver/src/test/unit/accounting/xeroAdapter.spec.tsnow adds the corresponding Xero case, proving line-levelservicePeriodStart/servicePeriodEndvalues remain distinct per recurring line instead of flattening mixed cadence work into one shared range- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/accounting/quickBooksOnlineAdapter.spec.ts src/test/unit/accounting/xeroAdapter.spec.ts --coverage.enabled false
-
(2026-03-18) Portal invoice-detail omission/rendering policy and invoice adapter stability now have explicit checklist coverage, which close
T267andT268:packages/client-portal/src/components/billing/InvoiceDetailsDialog.servicePeriods.test.tsxnow adds a dedicatedT267assertion that the portal dialog intentionally renders canonical detail periods for recurring lines while omitting service-period UI for financial-only/manual rows and showing the explicit fallback copy insteadpackages/billing/src/lib/adapters/invoiceAdapters.test.tsnow tags the existing canonical-detail preservation case asT268, locking the renderer-adapter contract that detail-backed invoice charges still map to one compatibility summary range plus the preserved detail list for downstream preview/rendering consumers- focused validation for this checkpoint used:
cd packages/client-portal && npx vitest run src/components/billing/InvoiceDetailsDialog.servicePeriods.test.tsx --coverage.enabled falsecd server && npx vitest run ../packages/billing/src/lib/adapters/invoiceAdapters.test.ts --coverage.enabled false
-
(2026-03-18) Invoice round-trip and adjacent reader/action seams now explicitly preserve canonical recurring detail periods, which close
T264,T265, andT266:server/src/test/integration/billingInvoiceTiming.integration.test.tsnow proves the full round-trip the earlier DB suites implied but did not lock directly:generateInvoice(...)persists canonicalinvoice_charge_details, returns an invoice view withrecurring_detail_periods, andInvoice.getFullInvoiceById(...)rereads the same canonical detail projection without degrading back to header-only timingserver/src/test/unit/billing/invoiceQueries.recurringDetailRead.test.tsnow locks the dashboard/query action seam directly by assertinggetInvoiceLineItems(...)passes through detail-backed charge rows unchanged, includingrecurring_projectionandrecurring_detail_periodsserver/src/test/unit/billing/manualInvoiceActions.viewing.test.tsnow makes the mixed-manual/recurring coexistence surface explicit: manual invoice updates can reread an invoice that still contains canonical recurring detail-backed charges, and those recurring detail rows survive while the manual row stays periodless- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/invoiceQueries.recurringDetailRead.test.ts src/test/unit/billing/manualInvoiceActions.viewing.test.ts --coverage.enabled falsecd server && DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t \"T264\" --hookTimeout 600000 --coverage.enabled false
-
(2026-03-18) Mixed-cadence grouping now explicitly defends the single-contract split invariant, which closes
T261:server/src/test/unit/billing/recurringTiming.domain.test.tsnow adds the missing mixed-cadence grouping assertion directly at the shared domain seam: a client-cadence selection and a contract-cadence selection may share the same invoice-window identity, but they still split into separate invoice candidates whenclientContractIddiffers- the new test intentionally locks the policy boundary already implied by
T141andT188: cadence owner remains explainability metadata inside each grouped candidate, whilesingle_contractremains the actual split reason when coincident due work spans different contracts - focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringTiming.domain.test.ts --coverage.enabled false
-
(2026-03-18) Purchase-order and financial grouping constraints now have their remaining explicit unit guards, which close
T262andT263:server/src/test/unit/billing/recurringTiming.domain.test.tsnow adds the narrow policy-specific cases that the earlier generic grouping tests did not spell out: a PO-required line and a non-PO line split inside the same invoice window underpurchase_order_scope, and same-contract due work with only currency or tax-source differences splits underfinancial_constraintwithout needing export-shape drift- this keeps the grouping contract readable at the domain seam before any broader runtime or DB-backed grouping work lands: PO scope and financial scope remain independent split axes layered on top of the same invoice-window identity
- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringTiming.domain.test.ts --coverage.enabled false
-
(2026-03-18) DB-backed invoice API coexistence now closes
T260and fixes a live recurring-detail hydration bug:server/src/test/integration/api/invoiceService.recurringCoexistence.integration.test.tsnow seeds one historical flat invoice plus one canonical detail-backed invoice in the live schema, then provesInvoiceService.getById(...)can read both through the same staged-rollout API seam without forcing the historical invoice to synthesize recurring detail metadata- while landing that test,
packages/billing/src/models/invoice.tsneeded a production fix: DB-backedinvoice_charge_details.service_period_*values may arrive asDateobjects, and the old summary projection only handled strings, which left parentservice_period_start|service_period_endnull for canonical rows in real integrations even though the detail periods existed - the model now normalizes recurring detail dates before building both
recurring_detail_periods[]and the parent summary range, so DB-backed readers match the unit-test contract instead of depending on driver-specific value shapes - focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/invoiceModel.servicePeriods.test.ts src/test/unit/api/invoiceService.recurringDetailProjection.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsoncd server && DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/integration/api/invoiceService.recurringCoexistence.integration.test.ts --hookTimeout 600000 --coverage.enabled false
-
(2026-03-18) Hydrated/manual invoice coexistence coverage now closes
T200andT212:server/src/test/unit/billing/invoiceModel.servicePeriods.test.tsnow seeds a recurring parent charge with intentionally stale header-likeservice_period_*/billing_timingvalues plus two canonical detail rows, then provesInvoice.getById(...)replaces the parent values with the detail-backed summary range and mixed-timing projection- the assertion that matters for downstream consumers is now explicit:
recurring_projection.source = canonical_detail_rowsandrecurring_detail_periods[]remain authoritative, while parentservice_period_start|service_period_end|billing_timingare only the compatibility summary surface after hydration server/src/test/unit/billing/invoiceService.manualPeriodPolicy.test.tsnow proves manual adjustments and targeted manual discounts can be added alongside an existing recurring charge without mutating that recurring charge’s canonical period fields or stamping period fields onto the manual rows- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/invoiceModel.servicePeriods.test.ts src/test/unit/billing/invoiceService.manualPeriodPolicy.test.ts --coverage.enabled false
-
(2026-03-18) DB-backed credit reconciliation coverage now closes
T177without relying on the older negative-invoice infrastructure harness:server/src/test/integration/billing/creditReconciliation.integration.test.tsnow seeds a negative-invoice credit issuance plus a later credit application against a positive invoice in the live DB schema, then runsvalidateCreditTrackingRemainingAmounts(...)against those persisted rows- the test deliberately uses canonical recurring timing at the reader seam instead of the old header-only assumption: it seeds
invoice_charge_details.service_period_start|service_period_end|billing_timing, and its mockedInvoice.getById(...)projects those detail periods back onto parent charge rows before reconciliation metadata is summarized - this replaced an abandoned attempt to reuse
server/src/test/infrastructure/billing/invoices/negativeInvoiceCredit.test.ts; that infrastructure path kept dragging in unrelated invoice-finalization, UI, and module-resolution dependencies before the credit-reconciliation assertion became trustworthy - focused validation for this checkpoint used:
cd server && DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/integration/billing/creditReconciliation.integration.test.ts --hookTimeout 600000 --coverage.enabled false
-
(2026-03-18) Additional materialized-ledger and export-reader coverage now closes
T178,T273,T275,T281,T329, andT330:server/src/test/integration/accounting/exportDashboard.integration.test.tsnow explicitly claims the DB-backed stored-reader seam for export lines: mixed historical fallback rows and canonical recurring-detail-backed rows reread throughgetBatchWithDetails(...)without collapsing their persisted service-period semanticsserver/src/test/integration/accounting/invoiceSelection.integration.test.tsnow explicitly claims the export-preview persistence seam:createBatchFromFilters(...)preserves canonical recurring detail periods, summary bounds, and source metadata when preview lines become stored export lines for downstream inspectionserver/src/test/unit/billing/invoiceGeneration.zeroDollarFinalization.test.tsnow tags the zero-dollar recurring finalization seam directly asT210/T273, making the previously documented zero-dollar checkpoint executable against the live persistence/finalization branch rather than leaving it only in scratchpad prosepackages/types/src/recurringServicePeriodRecord.typecheck.test.tsnow explicitly claimsT281alongsideT341, which keeps the shared persisted-record vocabulary anchored to one executable contract for obligation linkage, cadence owner, boundaries, provenance, and lifecycle stateserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow locks that materialized service periods are in-scope v1 behavior rather than a deferred follow-on, and that the expanded plan still preserves implementation-grade breadth after adding editable future billing objects- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts src/test/unit/billing/invoiceGeneration.zeroDollarFinalization.test.ts ../packages/types/src/recurringServicePeriodRecord.typecheck.test.ts --coverage.enabled falsecd server && DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/integration/accounting/exportDashboard.integration.test.ts src/test/integration/accounting/invoiceSelection.integration.test.ts src/test/integration/accounting/servicePeriodProjectionValidation.integration.test.ts -t "T178|T275|T270" --hookTimeout 600000 --coverage.enabled false- first pass proved
T178andT275while exposing a real schema-harness issue in the newT270seed data:invoice_charge_details.config_idis non-null in the live schema, so the validation fixture had to stop inserting nulls there
- first pass proved
-
(2026-03-18) Plan-integrity coverage now closes
T179,T180,T279, andT280:server/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow readsSCRATCHPAD.md,features.json, andtests.jsondirectly so the docs contract suite can assert both the qualitative replan posture and the quantitative checklist breadth instead of relying on memory or manual review- the new assertions lock the system-wide PRD blast radius, the scratchpad’s explicit recursive decomposition and second-pass critique, and the current implementation-grade checklist size/tail contract (
270features throughF270,349tests throughT332) - this keeps the plan itself executable as a rollout artifact: if a later edit accidentally collapses the scope back toward billing-engine-only thinking or silently truncates the checklist, the docs suite now fails immediately
- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled false
-
(2026-03-18) DB-backed contract-cadence and mixed-cadence billing sanity now closes
T140,T175, andT176:server/src/test/integration/billingInvoiceTiming.integration.test.tsnow lets the existing selector-input contract-cadence integration explicitly claim the contract-owned detail-persistence and mid-month monthly execution checks under one DB-backed seam:T140/T175/T252- that same file now adds a dedicated mixed-cadence grouping integration which inserts one client-cadence persisted row plus one contract-cadence persisted row on the same
[2025-01-01, 2025-02-01)window, runsgenerateInvoice(cycleId), and proves both detail rows land on a single invoice candidate when the documented grouping rules allow it - the mixed-cadence fixture needed an explicit horizon pairing of
targetHorizonDays: 32withreplenishmentThresholdDays: 15; the materializers reject a low-water threshold equal to or above the horizon target - focused validation for this checkpoint used:
cd server && DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T140/T175/T252" --hookTimeout 600000 --coverage.enabled falsecd server && DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T176" --hookTimeout 600000 --coverage.enabled false
-
(2026-03-18) The final open plan-surface follow-ons are now explicit, which closes
F268,F269,F270,T317,T318, andT319:ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_TROUBLESHOOTING.mdnow gives operators one troubleshooting seam for generation failures, override conflicts, regeneration triage, and invoice-linkage repair without pretending those problems should be solved by ad hoc row mutationee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RICHER_SERVICE_PERIOD_EDITING_FOLLOW_ON.mdnow fences richer editing out of recurring v1 explicitly: split/merge, bulk schedule transforms, and mass editing remain deferred until the narrowboundary_adjustment|skip|defersurface proves insufficient in productionee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/CROSS_DOMAIN_SERVICE_PERIOD_LEDGER_FOLLOW_ON.mdnow makes the cross-domain boundary explicit for the materialized ledger itself: time, usage, materials, manual invoices, credits, and prepayment artifacts do not quietly join the recurring ledger just because recurring v1 now has oneserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow locks those three artifacts directly so the remaining out-of-scope boundaries stay deliberate instead of drifting back into implicit future work- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled false
-
(2026-03-18) The first DB-backed persisted-ledger lifecycle validation now exists, which closes
F267andT316:server/src/test/integration/billingInvoiceTiming.integration.test.tsnow carriesT316, which persists realrecurring_service_periodsrows under the live schema, supersedes a generated row through an explicit boundary edit, regenerates a later future slot while preserving the edited row, and then links the billed edited revision to a realinvoice_charge_detailsrow- the integration checkpoint intentionally validates the staged-rollout ledger seam instead of claiming full edited-period invoice-generation cutover: the test proves database selection eligibility, supersession state, regenerated future continuity, and billed linkage persistence without depending on later runtime/UI passes that own edited-period invoice explanations
- the DB-backed test also exposed one concrete implementation gotcha that the earlier domain-only helpers could not: persisted-record helpers need UUID
recordIdFactoryinputs when they are used against the physicalrecurring_service_periodstable because the live schema storesrecord_idasuuid - focused validation for this checkpoint used:
cd server && DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T316" --hookTimeout 600000 --coverage.enabled falsenpx tsc --pretty false --noEmit -p server/tsconfig.json- still blocked only by the pre-existing
packages/billing/src/actions/creditActions.ts(1208,13)narrowing error, not by the new persisted-ledger integration test
- still blocked only by the pre-existing
-
(2026-03-18) Downstream persisted-period contracts are now explicit across charge-family behavior, bucket semantics, post-materialization lifecycle, authoring predictability, grouping after edits, and client/support explanations, which closes
F261,F262,F263,F264,F265,F266,T311,T312,T313,T314, andT315:ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_CHARGE_FAMILIES.mdnow documents that fixed, product, and license recurring lines share one persisted service-period lifecycle contract even if rating logic still differs by family, keepingF261aligned with the later runtime cutover rather than inviting family-specific ledger semanticsee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_BUCKET_SEMANTICS.mdnow classifies bucket/allowance behavior explicitly under edited, skipped, deferred, and regenerated periods so v1 does not silently imply unsupported automatic rollover repair when operators change future schedulesee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_POST_MATERIALIZATION_LIFECYCLE.mdnow defines how billed-through enforcement, renewal/replacement, and mutation guards consume persisted rows after materialization, keeping the post-persist lifecycle understandable before the broader DB validation passee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_AUTHORING_PREDICTABILITY.mdnow explains the cut line between authored recurrence rules and future editable schedules across templates, presets, and new-line creation so downstream materialization work remains predictable after creationee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_EDIT_GROUPING.mdnow documents how due selection and invoice grouping react when future edits move work across invoice windows, including when regrouping is allowed versus when separate invoice candidates must remain splitee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_EXPLANATIONS.mdnow defines the support/client explanation policy for generated versus edited periods, skipped/deferred rows, and invoice detail provenance so later UI work can reuse one narrative contract instead of inventing screen-local copy- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled false
-
(2026-03-18) Administrative repair and coexistence posture are now explicit, which closes
F259,F260,T309, andT310:ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_ADMIN_REPAIR.mdnow defines the first narrow operator repair modes for missing future rows, drifted untouched generated rows, and incorrect billed-history linkage, while preserving the earlier immutability and conflict rulesee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_COEXISTENCE.mdnow makes the historical-versus-future boundary explicit: future schedules can materialize persisted rows while historical invoices still rely on the dual-shape invoice reader contract, and no synthetic historical backfill is impliedserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow locks both artifacts directly so later repair or migration work cannot silently reopen historical rewrite assumptions or blur schedule drift with invoice-linkage correction- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled false
-
(2026-03-18) Pre-save authoring previews now show illustrative future materialized periods, which closes
F258andT308:packages/billing/src/components/billing-dashboard/contracts/recurringAuthoringPreview.tsnow generatesmaterializedPeriods[]alongside the existing cadence-owner, first-invoice, and partial-period explainer copy; the preview rows are illustrative, but they come from the same client-cadence and contract-cadence materialization helpers used elsewhere instead of ad hoc sample text- the fixed-fee contract wizard step, contract review step, template fixed-fee step, and template review step now all thread billing frequency into that helper and render the illustrative future service periods plus invoice windows, so staff can see the service-period-first model before the recurring line is saved
ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_AUTHORING_PREVIEW.mdnow documents the explicit rule: unsaved work gets explanatory future-period examples generated from materialization logic, not fake persisted rows- focused validation for this checkpoint used:
cd server && npx vitest run ../packages/billing/tests/recurringAuthoringPreview.test.ts ../packages/billing/tests/contractWizardCadenceOwner.wiring.test.ts ../packages/billing/tests/templateWizardCadenceOwner.wiring.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) Billing-staff operational inspection now has an explicit shared view contract, which closes
F257andT300:packages/types/src/interfaces/recurringTiming.interfaces.tsnow definesIRecurringServicePeriodOperationalView,IRecurringServicePeriodOperationalViewSummary, andIRecurringServicePeriodOperationalViewRow, so upcoming-period inspection has one typed read model instead of every later dashboard surface inventing its own row contractshared/billingClients/recurringServicePeriodOperationalView.tsnow projects the existing future-listing query and display-state contract into one operational view: deterministic upcoming rows, lifecycle-aware display badges, and summary counts for generated versus exception rows (edited|skipped|locked) before invoice generationee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_OPERATIONAL_VIEWS.mdnow documents the intended upcoming-period inspection seam andRECURRING_SERVICE_PERIOD_LISTING.mdwas tightened so the earlier listing pass no longer falsely implies this operational-view layer is still deferredserver/src/test/unit/billing/recurringServicePeriodOperationalView.domain.test.tsnow proves the shared view excludes billed history by default while still surfacing generated and skipped future rows with summary counts, andserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow locks the new artifact directlypass-0-source-inventory.jsonwas refreshed in the same checkpoint because the runtime-persisted selection test added another persisted-period reader reference and the docs contract suite intentionally keeps that inventory source-backed- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodOperationalView.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) Live recurring runtime now consumes persisted service-period selections before falling back to legacy derivation, which closes
F256,T292, andT307:packages/billing/src/actions/invoiceGeneration.tsnow marks due recurring selections asrecurringTimingSelectionSource: 'persisted'for both billing-cycle-backed invoice windows and selector-input execution windows, so invoice generation stops treating due-period lookup as advisory metadata and instead passes it as the authoritative runtime schedule inputpackages/billing/src/lib/billing/billingEngine.tsnow attempts to load due rows directly fromrecurring_service_periodswhen selecting recurring timing for a billing window, converts those rows into runtime selections for fixed, product, and license charge paths, and only falls back to legacy derived timing when the persisted ledger has not been materialized yet or the relation is absent during staged rollout- persisted-mode timing resolution is now intentionally strict at the charge-family seam: once a caller says the selections are persisted, missing per-line selections no longer trigger ad hoc service-period derivation inside
resolveRecurringChargeTiming(...), which keeps fixed, product, and license runtime behavior aligned on one authoritative schedule source server/src/test/unit/billing/invoiceGeneration.recurringSelection.test.tsnow locks the selection handoff for billing-cycle and selector-input billing, whileserver/src/test/unit/billing/billingEngine.persistedRecurringSelections.test.tsproves fixed, product, and license selections all hydrate from persisted records with the expected service-period coverage behavior- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/invoiceGeneration.recurringSelection.test.ts src/test/unit/billing/invoiceGeneration.emptyResult.test.ts src/test/unit/billing/invoiceGeneration.preview.test.ts src/test/unit/billing/billingEngine.persistedRecurringSelections.test.ts src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) Regeneration-trigger classification is now explicit, which closes
F254,T304, andT305:packages/types/src/interfaces/recurringTiming.interfaces.tsnow definesIRecurringServicePeriodRegenerationTriggerInput,IRecurringServicePeriodRegenerationDecision, explicit trigger sources/kinds, and regeneration scopes so later jobs and repositories do not need to infer whether a source edit should rebuild future persisted rowsshared/billingClients/recurringServicePeriodRegenerationTriggers.tsnow classifies the first v1 trigger families directly: recurrence-shaping contract-line edits usesource_rule_changed, assignment activity-window edits useactivity_window_changed, cadence-owner changes usecadence_owner_changedplusreplace_schedule_identity, and client billing-schedule edits usebilling_schedule_changedscoped toclient_cadence_dependents- the same helper now makes the non-trigger boundary explicit: pricing-only edits do not regenerate persisted periods because they affect future billing amounts rather than future service-period or invoice-window identity
ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_REGENERATION_TRIGGERS.mdnow documents those trigger families, the preserve-edits/preserve-billed-history safety invariants, and the deliberate boundary with later live repository/job wiring- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodRegenerationTriggers.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) The source-rule versus override cut line is now queryable, which closes
F255andT306:packages/types/src/interfaces/recurringTiming.interfaces.tsnow definesIRecurringServicePeriodAuthorityBoundaryplus explicit authority layers, change channels, future effects, and authority subjects so later UI/API/runtime work can answer whether a concern belongs to source cadence rules, a materialized override, or corrective ledger stateshared/billingClients/recurringServicePeriodAuthorityBoundary.tsnow centralizes that answer: cadence owner, billing frequency, due position, and activity windows remainsource_rule; service-period/invoice-window edits plus skip/defer remainmaterialized_override; lifecycle state, invoice linkage, and provenance remainledger_stateee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_SOURCE_OVERRIDE_BOUNDARY.mdnow documents the practical product rule explicitly: future movement should be explainable as either a source-rule change, an explicit future-row override, or a corrective history flow- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodAuthorityBoundary.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) Persisted-period governance now has one shared permission and audit-policy contract, which closes
F253andT303:packages/types/src/interfaces/recurringTiming.interfaces.tsnow defines governance actions, permission keys, audit events, andIRecurringServicePeriodGovernanceRequirement, so future controllers and dashboard actions do not need to invent their own authorization/audit vocabulary for viewing, editing, skipping, regenerating, or correcting service periodsshared/billingClients/recurringServicePeriodGovernance.tsnow combines lifecycle-aware mutation legality with explicit governance metadata:viewremains separately permissioned and non-audited by default, while edit/skip/defer/regenerate/correction flows all carry explicit audit-event names even when a lifecycle state still blocks the mutationee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_GOVERNANCE.mdnow documents that boundary explicitly and keeps real role assignment, audit payload schemas, and controller/database wiring deferred to later passes- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodGovernance.domain.test.ts src/test/unit/billing/recurringServicePeriodDisplayState.domain.test.ts src/test/unit/billing/recurringServicePeriodEditRequests.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) Persisted-period lifecycle states now have one shared UI-affordance contract, which closes
F252andT302:packages/types/src/interfaces/recurringTiming.interfaces.tsnow definesIRecurringServicePeriodDisplayStateand shared display tones so future dashboard rows and badges can consume one stable presentation contract instead of inventing local label semanticsshared/billingClients/recurringServicePeriodDisplayState.tsnow maps lifecycle plus provenance into explicit UI state metadata:Generated,Edited,Skipped,Locked,Billed,Superseded, andArchived, with operational detail copy and additive provenance-drivenreasonLabelstrings such asDeferred to a later invoice windowee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_UI_STATES.mdnow documents the badge/detail contract and its deliberate boundary with later permissions, audit identity, and concrete dashboard-layout workRECURRING_SERVICE_PERIOD_EDIT_SURFACES.mdwas tightened in the same checkpoint soF251no longer claims state badges are still deferred behindF252- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodDisplayState.domain.test.ts src/test/unit/billing/recurringServicePeriodEditRequests.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) Future persisted-period edits now have an explicit UI/API transport contract, which closes
F251and adds/closesT349while intentionally leavingT295andT301for later persisted-editing screens and broader state affordances:packages/types/src/interfaces/recurringTiming.interfaces.tsnow definesIRecurringServicePeriodEditRequest,IRecurringServicePeriodEditRequestContext,IRecurringServicePeriodEditResponse, and structured validation issue codes/fields so future controllers and dashboard forms have one typed request/response seam forboundary_adjustment,skip, anddefershared/billingClients/recurringServicePeriodEditRequests.tsnow dispatches those transport requests onto the existing edit primitives and returns explicit success payloads withsupersededRecord,editedRecord, andprovenance, while mapping lower-level validation failures into structured issues such asmissing_deferred_invoice_window,continuity_gap_before, andrecord_mismatchee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_EDIT_SURFACES.mdnow documents that boundary explicitly: request contract, success response, validation issue surface, and the deliberate deferral of repository/controller wiring, dashboard state badges, and permission/audit policy toF252-F259tests.jsonnow addsT349because the originalT301also depends on the later billing-staff editing surfaces and state affordances inF252; the new test locks the shared transport contract itself without pretending the dashboard or controllers already exist- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodEditRequests.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) Future persisted-period listing now has an explicit query contract, which closes
F250and adds/closesT348while intentionally leavingT300for later UI/dashboard surfaces:packages/types/src/interfaces/recurringTiming.interfaces.tsnow definesIRecurringServicePeriodListingQueryplus the default listing lifecycle-state setgenerated|edited|skipped|locked, making future-ledger inspection a first-class read contract instead of a side effect of due selectionshared/billingClients/recurringServicePeriodListing.tsnow defines the first future-listing helper: it filters by tenant,asOf, optional schedule/cadence/due-position/charge-family scope, excludes billed/superseded/archived rows by default, and keeps deterministic chronological orderingee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_LISTING.mdnow documents that listing is intentionally independent from invoice generation and due selection, so later API and dashboard work can build on one shared read seamtests.jsonnow addsT348because the originalT300also depends on later billing-staff UI surfaces inF257; the new test locks the listing contract itself without pretending the dashboard already exists- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodListing.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/types/tsconfig.json
-
(2026-03-18) Regeneration conflicts against preserved user edits are now explicit, which closes
F249andT299:shared/billingClients/regenerateRecurringServicePeriods.tsnow returns additiveconflictsmetadata instead of silently discarding disagreements when preserved override rows no longer match regenerated source candidates- the first v1 conflict kinds are
missing_candidate,service_period_mismatch,invoice_window_mismatch, andactivity_window_mismatch, which keeps preserved edits active while making the disagreement queryable for later operator tooling ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_EDIT_CONFLICTS.mdnow documents that conservative handling rule explicitly: preserve the edited row, surface the conflict, and leave repair/merge tooling for later passes- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodRegenerationConflict.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/types/tsconfig.json
-
(2026-03-18) Neighbor-aware edit validation is now explicit, which closes
F248andT298:shared/billingClients/recurringServicePeriodEditValidation.tsnow defines the first continuity guard for edited future rows: when sibling rows on the samescheduleKeyare supplied, edits are rejected forgap before,overlap before,gap after, andoverlap afterstates instead of silently corrupting the scheduleshared/billingClients/editRecurringServicePeriodBoundaries.tsandshared/billingClients/skipOrDeferRecurringServicePeriod.tsnow call the same validator when caller-side sibling context is present, which keeps the boundary-adjustment and disposition helpers aligned on one continuity rule instead of each inventing their own local checksee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_EDIT_VALIDATION.mdnow documents the same-schedule adjacency rule and its explicit boundary with the later regeneration-conflict and UI transport passes- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodEditValidation.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/types/tsconfig.json
-
(2026-03-18) Split and merge are now explicitly fenced out of v1, which closes
F247and adds/closesT347while intentionally leavingT297for any later advanced-edit pass:shared/billingClients/recurringServicePeriodEditCapabilities.tsnow makes the v1 edit surface executable instead of leaving it as docs only: supported operations areboundary_adjustment,skip, anddefer, whilesplitandmergefail fast as unsupported v1 operationsee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_EDIT_OPERATIONS.mdnow carries an explicitUnsupported In V1section so later UI/API work cannot quietly assume split/merge support before a deliberate follow-on pass existstests.jsonnow addsT347because the originalT297also depends on the later continuity semantics inF248; the new test locks the v1 non-support decision directly- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodEditCapabilities.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/types/tsconfig.json
-
(2026-03-18) Skip and defer are now explicit persisted-period edit operations, which closes
F246and adds/closesT346while intentionally leavingT296for the later continuity pass:shared/billingClients/skipOrDeferRecurringServicePeriod.tsnow defines the first explicit disposition edits for future persisted rows: skip creates a supersedingskippedrevision withreasonCode = skip, while defer creates a supersedingeditedrevision withreasonCode = deferand an explicitly moved invoice window- the helper deliberately stays narrower than the later neighbor-aware validation work: it reuses the existing mutation guard, requires a new deferred invoice window instead of silently reusing the prior one, and keeps service-period continuity / invoice-linkage interplay for
F248 ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_EDIT_OPERATIONS.mdnow documentsboundary_adjustment,skip, anddefertogether as the minimal v1 edit surface, while split/merge and UI/API editing remain sequenced latertests.jsonnow addsT346because the originalT296also depends on the later overlap/gap validation pass inF248; the new test locks the explicit skip/defer revision semantics without claiming broader continuity validation already exists- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodDisposition.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/types/tsconfig.json
-
(2026-03-18) The minimal v1 boundary-adjustment edit operation is now explicit, which closes
F245and adds/closesT345while intentionally leavingT295for the later continuity + UI passes:shared/billingClients/editRecurringServicePeriodBoundaries.tsnow defines the first explicit edit primitive for persisted service periods: mutable future rows can be superseded by a neweditedrevision,provenance.kind = user_edited, and the helper infersboundary_adjustment,invoice_window_adjustment, oractivity_window_adjustmentreason codes instead of hiding edits behind source-rule changes- the helper stays intentionally narrow at this pass: it reuses the existing immutability guard, validates only local half-open boundary integrity plus in-period activity clipping, rejects no-op edits, and does not yet attempt neighbor continuity or transport-surface validation that belongs to
F248andF251 ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_EDIT_OPERATIONS.mdnow documents thatboundary_adjustmentis the only supported v1 edit operation at this checkpoint, while skip/defer, split/merge, and UI/API editing remain sequenced latertests.jsonnow addsT345because the originalT295also depends on the later continuity-validation and editing-surface work inF248andF251; the new test locks the edit primitive itself without pretending the broader pass already exists- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodBoundaryEdit.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/types/tsconfig.json
-
(2026-03-18) The first billed-history-safe backfill planner now exists, which closes
F244,T286, andT294:shared/billingClients/backfillRecurringServicePeriods.tsnow defines the v1 initialization rule for legacy recurring lines before runtime cutover: future candidate rows are normalized ontoprovenance.reasonCode = backfill_materialization, billed-history boundaries come from legacy billed-through data plus any already-linked persisted rows, and candidates overlapping that boundary are rejected instead of being silently clipped- the same helper now makes partial-rollout backfill explicit instead of accidental: already-billed rows are retained unchanged, equivalent future rows are retained unchanged, and untouched generated future rows that drift from the current candidate schedule are regenerated with
reasonCode = backfill_realignmentwhile preserving the earlier row assuperseded ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_BACKFILL.mdnow documents the historical-boundary rule, backfill provenance, and the deliberate boundary that historical invoices are not rehydrated into synthetic persisted rows during v1 initializationserver/src/test/unit/billing/recurringServicePeriodBackfill.domain.test.tsnow locks both halves directly: billed client-cadence history fences off future inserts without mutation, and stale future generated rows realign under explicit backfill provenance while billed history remains unchanged- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodBackfill.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/types/tsconfig.json
-
(2026-03-18) Persisted-schedule parity comparison is now explicit, which closes
F243andT293:shared/billingClients/recurringServicePeriodKeys.tsnow centralizes canonicalscheduleKeyandperiodKeygeneration, and both materializers now use that helper so persisted schedule identity cannot drift from later comparison logicshared/billingClients/recurringServicePeriodParity.tsnow defines the staged-cutover parity seam for materialized schedules: legacy derived selections normalize ontoscheduleKey + periodKey, active persisted rows normalize onto the same key, and schedule parity reports only the rollout-significant drift kindsmissing_persisted_period,unexpected_persisted_period, andinvoice_window_mismatchee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_PARITY_COMPARISON.mdnow documents that this is schedule parity rather than invoice parity, and thatF256still owns the later runtime cutover from derived timing to persisted-row selectionpass-0-source-inventory.jsonwas refreshed in the same checkpoint because the new parity helper became a live service-period reader; the docs contract suite now expects that helper in the persisted-reader inventory instead of silently drifting- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodParity.domain.test.ts src/test/unit/billing/materializeClientCadenceServicePeriods.domain.test.ts src/test/unit/billing/materializeContractCadenceServicePeriods.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) The persisted due-selection query contract is now explicit, which closes
F242and adds/closesT344while intentionally leavingT292forF256runtime adoption:packages/types/src/interfaces/recurringTiming.interfaces.tsnow definesIRecurringServicePeriodDueSelectionQueryplus the default eligible lifecycle-state setgenerated|edited|locked, making the persisted-row selector contract explicit instead of leaving it as an implied future repository queryshared/billingClients/recurringServicePeriodDueSelection.tsnow defines the v1 selector seam: callers must resolvescheduleKeys[]first, exact[windowStart, windowEnd)invoice-window matching is required, billed/linkage-bearing rows are excluded, and deterministic sort order is service-period start/end then obligation id then revisionee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_DUE_SELECTION.mdnow documents the contract boundary clearly:F242defines the selector inputs and filters, whileF256remains the later pass that switches live invoice generation off the derived billing-engine schedule and onto persisted rowstests.jsonnow addsT344because the originalT292also covers the later runtime cutover inF256; the new test locks the selector contract itself without pretending invoice generation already consumes persisted rows end to end- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodDueSelection.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) Persisted service-period invoice linkage is now explicit, which closes
F241andT291:packages/types/src/interfaces/recurringTiming.interfaces.tsnow adds optionalinvoiceLinkagemetadata onIRecurringServicePeriodRecord, with one additive linkage object carryinginvoiceId,invoiceChargeId,invoiceChargeDetailId, andlinkedAtshared/billingClients/recurringServicePeriodInvoiceLinkage.tsnow defines the v1 linkage helper boundary: linking a record to aninvoice_charge_detailsrow stamps the record asbilled, preserves idempotent same-row relinks, and rejects conflicting relinks until a laterinvoice_linkage_repairflow handles them explicitlyserver/migrations/20260318143000_add_invoice_linkage_to_recurring_service_periods.cjsnow adds additive linkage columns onrecurring_service_periodsplus the first integrity rules for billed-history traceability: linkage columns are all-null or all-present together,invoice_charge_detail_idis tenant-unique, and linked rows must already be inbilledstateee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_INVOICE_LINKAGE.mdnow documents the billed-history linkage contract and its explicit boundary with the later due-selection/runtime adoption work inF242-F244- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodInvoiceLinkage.domain.test.ts src/test/unit/migrations/recurringServicePeriodInvoiceLinkageMigration.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts ../packages/types/src/recurringServicePeriodRecord.typecheck.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) Locked/billed immutability is now explicit, which closes
F240andT290:shared/billingClients/recurringServicePeriodMutations.tsnow defines the v1 mutation guard for persisted service-period rows: unlocked future rows may still follow normal edit/regeneration flows, whilelockedandbilledrows reject ordinary edits and allow only the narrow corrective operationsinvoice_linkage_repairandarchiveee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_IMMUTABILITY.mdnow documents that billed coverage remains audit history instead of mutable schedule draft state, while also keeping the invoice-linkage repair boundary explicit for the later linkage passserver/src/test/unit/billing/recurringServicePeriodMutations.domain.test.tsnow locks the allowed/disallowed operations directly for generated, locked, and billed rows, andserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow locks the immutability artifact- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodMutations.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) The first regeneration and override-preservation algorithm now exists, which closes
F238,F239,T288, andT289:shared/billingClients/regenerateRecurringServicePeriods.tsnow defines the v1 regeneration rule for future active rows: untouched generated slots can be refreshed intoregeneratedrevisions, replaced rows becomesuperseded, and leftover candidate slots stay new generated rows- the same helper now makes override preservation explicit instead of accidental:
user_edited,repair,edited,locked, andbilledfuture rows are preserved as-is, and the candidate slot that would have overwritten them is intentionally discarded until a later explicit conflict-resolution flow exists ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_REGENERATION.mdnow documents the slot-order regeneration policy, period-key/revision continuity, and the deliberate v1 boundary that preserved overrides are not merged automaticallyserver/src/test/unit/billing/recurringServicePeriodRegeneration.domain.test.tsnow locks both halves directly: changed untouched rows regenerate into a new revision while user-edited rows remain preserved and unmodified- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodRegeneration.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) The first contract-cadence materialization helper now exists, which closes
F237andT287:shared/billingClients/materializeContractCadenceServicePeriods.tsnow wraps the existing contract-anniversary generators andresolveContractCadenceInvoiceWindowForServicePeriod(...), producing persisted future record candidates for contract-owned recurring obligations without waiting for later invoice-selection cutover work- the helper keeps contract-owned timing explicit at the persisted-record seam: schedule keys are
contractowned, timing metadata preserves the anniversary anchor,advanceinvoice windows match the contract-owned service period, andarrearsinvoice windows land on the next contract-owned window after the covered period ends server/src/test/unit/billing/materializeContractCadenceServicePeriods.domain.test.tsnow locks the monthly contract-owned materialization path directly for both advance and arrears, proving the additive record shape instead of only re-testing raw boundary generation- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/materializeContractCadenceServicePeriods.domain.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) The first client-cadence materialization helper now exists, which closes
F236and adds/closesT343while intentionally leavingT286forF244backfill/no-billed-history rules:shared/billingClients/materializeClientCadenceServicePeriods.tsnow composes the existing client-cadence boundary generator with the new horizon policy and the persisted-record contract, producing additive future record candidates for client-owned recurring obligations instead of only ephemeral runtime periods- the helper preserves parity semantics instead of inventing new date math: service-period boundaries still come from
generateClientCadenceServicePeriods(...),advanceinvoice windows stay aligned to the same period,arrearsinvoice windows resolve to the next client-cadence window, and generated rows use the explicitgeneratedprovenance plus the shared persisted-record identity vocabulary server/src/test/unit/billing/materializeClientCadenceServicePeriods.domain.test.tsnow locks both advance and arrears mapping behavior directly, including schedule keys, canonical invoice-window mapping, generated provenance, and horizon coveragetests.jsonnow addsT343because the originalT286also covers the laterF244backfill rule about not mutating billed history; that acceptance surface is still intentionally open until the dedicated backfill semantics land- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/materializeClientCadenceServicePeriods.domain.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) The v1 generation-horizon policy is now explicit, which closes
F235andT285:shared/billingClients/recurringServicePeriodGenerationHorizon.tsnow defines the first operational horizon contract for persisted recurring periods: initial materialization/backfill targets180days of future coverage, steady-state replenishment triggers at the45-day low-water mark, and continuity issues are surfaced explicitly instead of being silently treated as ordinary replenishment- the same helper now makes the continuity invariant executable before ledger-writing code lands:
findRecurringServicePeriodContinuityIssues(...)detectsgapandoverlapstates under the canonical half-open model, whileassessRecurringServicePeriodGenerationCoverage(...)reports whether the current future ledger both meets the target horizon and has fallen low enough to require replenishment ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_GENERATION_HORIZON.mdnow documents the target horizon, low-water threshold, whole-period overshoot rule, and the explicit boundary that continuity repair is separate from ordinary replenishmentserver/src/test/unit/billing/recurringServicePeriodGenerationHorizon.domain.test.tsnow locks the helper behavior directly, whileserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow locks the new horizon artifact so the executable helper and the plan policy stay aligned- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodGenerationHorizon.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) The persisted-service-period provenance model is now explicit, which closes
F234andT284:packages/types/src/interfaces/recurringTiming.interfaces.tsnow turnsIRecurringServicePeriodRecordProvenanceinto a discriminated union with explicit reason-code catalogs forgenerated,user_edited,regenerated, andrepair, so later materialization/edit/regeneration work no longer depends on a loose optional-string bag for provenance semanticsshared/billingClients/recurringServicePeriodProvenance.tsnow provides the reusable v1 helper layer:isRecurringServicePeriodProvenanceReasonCode(...),isRecurringServicePeriodProvenanceDivergent(...), andvalidateRecurringServicePeriodProvenance(...)make the required-versus-optional field rules executable before repositories and UI flows start writing these records for realee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_PROVENANCE.mdnow documents the provenance kinds, required-field matrix, reason-code catalog, and the operational meaning of divergence from untouched cadence rules;PERSISTED_SERVICE_PERIOD_RECORD.mdnow points at that artifact instead of leaving provenance requirements as future tenseserver/src/test/unit/billing/recurringServicePeriodProvenance.domain.test.tsnow locks the shared helper/domain behavior directly, whileserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tslocks the new provenance artifact so the typed helper and the plan language cannot drift apart quietly- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodProvenance.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts ../packages/types/src/recurringServicePeriodRecord.typecheck.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) The persisted-period lifecycle model is now explicit, which closes
F233andT283:shared/billingClients/recurringServicePeriodLifecycle.tsnow defines the authoritative v1 state machine for persisted service-period records, including the allowed transition map, terminal-state set, and reusablecanTransitionRecurringServicePeriodState(...)/isRecurringServicePeriodStateTerminal(...)helpersee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_LIFECYCLE.mdnow documents the state meanings, conservative allowed transitions, terminal-state policy, and v1 invariants so later edit/regeneration/billing-linkage work has one lifecycle baseline instead of ad hoc state semanticspackages/billing/src/index.tsnow re-exports the lifecycle helpers/constants from the billing package surface so downstream consumers can adopt the same transition contract without reaching into shared internals directlyserver/src/test/unit/billing/recurringServicePeriodLifecycle.domain.test.tsnow locks the transition map directly, whileserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow locks the lifecycle artifact so the documented state model and the executable helper cannot drift apart quietly- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodLifecycle.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) The physical persisted-ledger table now exists, which closes
F232, closesT282, and adds/closesT342:server/migrations/20260318120000_create_recurring_service_periods.cjsnow createsrecurring_service_periodsas the first physical ledger table for materialized recurring periods, flattening theF231record contract into row columns for identity, obligation linkage, cadence ownership, service/invoice boundaries, activity clipping, provenance, and audit timestamps- the migration now enforces the first integrity boundary directly in the database: check constraints cover cadence owner, due position, lifecycle state, obligation type, charge family, provenance kind, positive revision numbers, valid
[start, end)service/invoice windows, valid optional activity-window clipping, andsupersedes_record_id <> record_id - the migration also adds the first lookup posture for later materialization/runtime work: a unique revision key on
(tenant, schedule_key, period_key, revision)plus tenant-scoped indexes for schedule lookup, obligation-state scans, and due selection by invoice window server/src/test/unit/migrations/recurringServicePeriodsMigration.test.tsnow executes the migration against a fake schema contract so the table shape, constraints, and index names are locked directly instead of being left as migration proseee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/PERSISTED_SERVICE_PERIOD_RECORD.mdnow documents the physicalrecurring_service_periodslanding andpass-0-source-inventory.jsonwas refreshed because the docs contract inventory intentionally tracks all liveservice_period_*references, including the new migration and migration test- focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/migrations/recurringServicePeriodsMigration.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p server/tsconfig.json- still blocked only by the pre-existing
packages/billing/src/actions/creditActions.ts(1208,13)narrowing error, not by the recurring-service-period migration
- still blocked only by the pre-existing
-
(2026-03-18) The first materialized-ledger checkpoint now has one authoritative logical record contract, which closes
F231and adds/closesT341:packages/types/src/interfaces/recurringTiming.interfaces.tsnow definesIPersistedRecurringObligationRef,IRecurringServicePeriodRecordProvenance,IRecurringServicePeriodRecord,RecurringServicePeriodLifecycleState, andRecurringServicePeriodProvenanceKind, so future schema/runtime work can target one shared persisted-record vocabulary instead of inventing ad hoc shapesee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/PERSISTED_SERVICE_PERIOD_RECORD.mdnow defines the logical record shape explicitly:recordId,scheduleKey,periodKey,revision, obligation linkage, cadence ownership,[start, end)service/invoice boundaries, provenance, and lifecycle state, while also naming the follow-on boundaries forF232,F233, and later invoice-linkage/runtime adoption workserver/src/test/test-utils/recurringTimingFixtures.tsnow includes persisted-record fixture builders so the later materialized-ledger passes can reuse one contract-aware test seam instead of recreating record shapes inline- executable coverage for this checkpoint now lives in
packages/types/src/recurringServicePeriodRecord.typecheck.test.ts, whileserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow also locks the presence and core sections of the new persisted-record artifact - focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts ../packages/types/src/recurringServicePeriodRecord.typecheck.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.json
-
(2026-03-17) The staged cutover, rollback, and follow-on boundary posture is now explicit in the plan artifacts, which closes
F224,F225,F226,F227,F228,F229,F230,T253,T254,T255,T256,T257,T258, andT259:ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/CUTOVER_SEQUENCE.mdnow defines the ordered cutover bands for reader-first invoice hydration, writer propagation, scheduler identity, grouping, downstream reporting/portal/export adoption, and rollback/coexistence expectations while historical flat invoices and canonical detail-backed invoices both remain queryableee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RUNBOOK.mdnow includes dedicatedProjection Mismatch InvestigationandAuthoring-Default Drift Investigationsections, with explicit SQL/source-check guidance for header-versus-detail disagreements and for recurring-default drift across templates, presets, wizard flows, and custom recurring linesee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/PRD.mdandee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/PASS0_RECURRING_TIMING_APPENDIX.mdnow both carry explicit follow-on boundaries for persisted recurring execution records and invoice-schema versioning, keeping those concerns out of recurring v1 unless rollout pressure or long-lived coexistence justifies reopening them deliberatelyserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow locks all seven documentation contracts directly, andpass-0-source-inventory.jsonwas refreshed in the same checkpoint so the docs suite stays aligned with current grep-backed source references instead of stale inventory entries- focused validation for this checkpoint used
cd server && npx vitest run src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled false
-
(2026-03-17) Cleanup-proof source/DB validation now covers the remaining dropped-table, authoritative-reader, and selector-input scheduler seams, which closes
F221,F222,F223,T251,T252,T271,T272, and addsT340for the missing DB half ofF221:shared/billingClients/templateClone.tsno longer readscontract_template_line_mappings.custom_rate; template-clone custom-rate lookup now reads the authoritativecontract_template_lines.custom_rate, which removes the last live shared-package dependency on a dropped recurrence mapping tableserver/src/test/unit/billing/recurrenceStorageModel.contract.test.tsnow scansshared/billingClientsalongsidepackages/billing/srcandserver/src/lib, so the dropped-table source contract covers shared runtime helpers instead of only package/server codeserver/src/test/integration/billingInvoiceTiming.integration.test.tsnow carriesF221DB validation proving recurring invoice generation still succeeds whilecontract_line_terms,contract_line_mappings, andcontract_template_line_mappingsare absent from the live schema;tests.jsongainedT340because the existingT247checklist item only covered source absencepackages/billing/src/actions/invoiceGeneration.tsandpackages/billing/src/lib/billing/billingEngine.tsnow distinguish real billing-cycle UUID bridges from selector-input-only execution windows: selector windows no longer get forced intoinvoices.billing_cycle_idor reloaded throughclient_billing_cyclespackages/billing/src/lib/billing/billingEngine.tsnow exposescalculateBillingForExecutionWindow(...), which reuses the canonical prepared-period path without assuming the execution window can be looked up by billing-cycle UUID- comparison-mode drift logging now skips execution windows that have no real billing-cycle bridge instead of trying to replay legacy billing against a typed contract-cadence identity
server/src/test/integration/accounting/invoiceSelection.integration.test.tspassesT251, proving preview selection prefers canonical detail periods over invoice header periods when both existpackages/billing/tests/authoritativeRecurringReaders.servicePeriods.wiring.test.tspassesT272, locking the source-side preference for canonical detail periods over header fallbacksserver/src/test/unit/jobs/generateInvoiceHandler.recurringExecutionIdentity.test.tspassesT271, andserver/src/test/integration/billingInvoiceTiming.integration.test.tspassesT252, proving selector-input contract-cadence execution can run and persist invoices withbilling_cycle_id = nullinstead of being blocked on a billing-cycle-only scheduler assumption- focused validation for this checkpoint used:
cd server && npx tsc --pretty false --noEmit -p ../packages/billing/tsconfig.jsoncd server && npx vitest run ../packages/billing/tests/authoritativeRecurringReaders.servicePeriods.wiring.test.ts src/test/unit/jobs/generateInvoiceHandler.recurringExecutionIdentity.test.ts src/test/unit/billing/recurrenceStorageModel.contract.test.ts --coverage.enabled falsecd server && DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T252" --hookTimeout 600000 --coverage.enabled falsePGPASSWORD=postpass123 psql -h 127.0.0.1 -p 57433 -U postgres -d postgres -c 'DROP DATABASE IF EXISTS test_database;'cd server && DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/integration/accounting/invoiceSelection.integration.test.ts -t "T251" --hookTimeout 600000 --coverage.enabled false
- notable harness gotchas:
- DB-backed suites still require overriding the stale
.env.localtestdatabase port from5438to57433 - re-running
invoiceSelection.integration.test.tsback-to-back can leave a staletest_database; dropping it before the isolated rerun avoids the duplicate-DB bootstrap failure
- DB-backed suites still require overriding the stale
-
(2026-03-17) Partially migrated live recurrence rows now normalize at the remaining client-facing read/write edges, which closes
F219andT249:packages/clients/src/models/clientContract.tsnow normalizescontract_linesrereads withnormalizeLiveRecurringStorage(...), so client-contract authoring flows stop returning raw legacy-nullbilling_timing/cadence_ownervaluespackages/clients/src/actions/clientContractLineActions.tsnow normalizes partially migrated recurring fields on both sides of the client-line seam:getClientContractLine(...)returns normalized defaults before date serialization, andaddClientContractLine(...)now clones fromtemplateRecurringStorageinstead of hand-written?? 'arrears' / 'client'fallbackspackages/client-portal/src/actions/client-portal-actions/client-billing.tsnow normalizes the client-portal contract-line read-model before returning it, which keeps portal contract-line details on the same shared compatibility contract as the billing and clients packagesserver/src/test/unit/billing/recurrenceStorageModel.contract.test.tsnow claimsT249for this plan and locks those source seams explicitly;packages/clients/src/actions/clientContractLineActions.recurringCompatibility.test.tsadds behavior coverage for normalized reads plus clone writes;packages/client-portal/src/actions/client-portal-actions/client-billing.recurringCompatibility.test.tsadds a focused source wiring guard for the portal seam because importing that action directly still trips the unrelated local@alga-psa/jobspackage-resolution issuepackages/billing/tests/renewalsQueueActions.schemaReadiness.integration.test.tshad unrelatedT249/T250/T251/T254/T255labels from another effort; those were renamed toRQ249-style labels so this plan’s checklist IDs stay one-to-one with the billing work- focused validation for this checkpoint used
cd server && npx vitest run src/test/unit/billing/recurrenceStorageModel.contract.test.ts ../packages/clients/src/actions/clientContractLineActions.recurringCompatibility.test.ts ../packages/client-portal/src/actions/client-portal-actions/client-billing.recurringCompatibility.test.ts ../packages/billing/tests/renewalsQueueActions.schemaReadiness.integration.test.ts --coverage.enabled false,npx tsc --pretty false --noEmit -p packages/clients/tsconfig.json, andnpx tsc --pretty false --noEmit -p packages/client-portal/tsconfig.json
-
(2026-03-17) Stale term-storage references are now described as compatibility only instead of authoritative recurrence storage, which closes
F218andT248:packages/billing/src/actions/contractLineAction.tsnow says directly thatcontract_template_lines.billing_timingis authoritative andcontract_template_line_termssurvives only as a legacy shadow read/write during staged rolloutpackages/billing/src/models/contractTemplate.ts,packages/billing/src/repositories/contractLineRepository.ts, andserver/src/lib/repositories/contractLineRepository.tsnow describecontract_template_line_termsjoins as compatibility fallbacks, which removes the remaining model/repository comments that could be read as treating the terms table as the live source of truthserver/src/test/unit/billing/recurrenceStorageModel.contract.test.tsnow includesT248, locking the authoritative-versus-fallback wording againstRECURRENCE_STORAGE_MATRIX.mdand those source comments- focused validation for this checkpoint used
cd server && npx vitest run src/test/unit/billing/recurrenceStorageModel.contract.test.ts --coverage.enabled false,npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json, andnpx tsc --pretty false --noEmit -p server/tsconfig.json; the server compile remains blocked only by the pre-existingpackages/billing/src/actions/creditActions.ts(1208,13)narrowing error
-
(2026-03-17) Stale dropped recurrence-table reads are now explicitly fenced off instead of being left implicit, which closes
F217andT247:- a fresh source inventory across
packages/billing/srcandserver/src/libshows no live reads or joins against the removed recurrence tablescontract_line_terms,contract_line_mappings, orcontract_template_line_mappings server/src/test/unit/billing/recurrenceStorageModel.contract.test.tsnow includesT247, which locks that absence directly while also documenting thatcontract_template_line_terms.billing_timingremains the one intentional template compatibility fallback listed inAUTHORITATIVE_RECURRENCE_STORAGE_MODEL- focused validation for this checkpoint used
cd server && npx vitest run src/test/unit/billing/recurrenceStorageModel.contract.test.ts --coverage.enabled false,npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json, andnpx tsc --pretty false --noEmit -p server/tsconfig.json; the server compile remains blocked only by the pre-existingpackages/billing/src/actions/creditActions.ts(1208,13)narrowing error
- a fresh source inventory across
-
(2026-03-17) Mapping and repository write paths no longer silently drop recurring cadence fields, which closes
F216andT246:packages/billing/src/actions/contractLineMappingActions.tsnow normalizes template snapshots withnormalizeLiveRecurringStorage(...)and carries bothbilling_timingandcadence_ownerthrough template-line updates, so mapping writes stop preserving only cadence owner while dropping timingpackages/billing/src/models/contractLineMapping.tsnow treats billing timing as part of the mapping contract on both live and template rows: reads selectbilling_timing, live updates resolve cadence owner plus timing throughresolveRecurringAuthoringPolicy(...), and template fallbacks return the updated timing field instead of silently omitting itpackages/billing/src/repositories/contractLineRepository.tsandserver/src/lib/repositories/contractLineRepository.tsnow normalize single-line rereads withnormalizeLiveRecurringStorage(...)and preserve existingbilling_timingwhenupdateContractLineRate(...)changes only rate data, removing another pair of repository write seams that previously depended on rawundefinedhandlingserver/src/test/unit/billing/recurrenceStorageModel.contract.test.tsnow includesT246, whilepackages/billing/tests/contractLineCadenceOwnerCompatibility.wiring.test.tsandpackages/billing/tests/contractLineMappingRecurringTiming.wiring.test.tswere refreshed to lock the new helper-based cadence/timing propagation contract directly at the mapping seams- focused validation for this checkpoint used
cd server && npx vitest run src/test/unit/billing/recurrenceStorageModel.contract.test.ts ../packages/billing/tests/contractLineCadenceOwnerCompatibility.wiring.test.ts ../packages/billing/tests/contractLineMappingRecurringTiming.wiring.test.ts --coverage.enabled false,npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json, andnpx tsc --pretty false --noEmit -p server/tsconfig.json; the server compile remains blocked only by the pre-existingpackages/billing/src/actions/creditActions.ts(1208,13)narrowing error
-
(2026-03-17) Legacy
billing_cycle_alignmentcompatibility defaults now run through one shared resolver across the remaining fixed recurring write and reread seams, which closesF215andT245:packages/billing/src/components/billing-dashboard/ContractLineDialog.tsx,packages/billing/src/components/billing-dashboard/contract-lines/FixedContractLinePresetConfiguration.tsx, andpackages/billing/src/components/billing-dashboard/contract-lines/FixedContractLineConfiguration.tsxnow useresolveBillingCycleAlignmentForCompatibility(...)when rehydrating fixed config state and when emitting fixed-config writes, so legacy rows withenable_proration = truebut no stored alignment no longer snap back to'start'in the UIpackages/billing/src/actions/contractLineAction.ts,packages/billing/src/actions/contractWizardActions.ts, andpackages/billing/src/actions/contractLineMappingActions.tsnow use the same helper before writing plan fixed config, client-contract fixed config copies, and template-line fixed snapshots, which removes the last hard-coded'start'fallbacks from live fixed recurring propagation seamspackages/billing/src/repositories/contractLineRepository.tsandserver/src/lib/repositories/contractLineRepository.tsnow normalize alignment on contract-line and template-line rereads using both stored alignment andenable_proration, keeping readback compatibility consistent with the shared write contractserver/src/test/unit/billing/recurrenceStorageModel.contract.test.tsnow includesT245, locking the helper-based normalization across action, wizard, mapping, UI, and repository seams, andpackages/billing/tests/contractLinePresetRecurringAuthoring.wiring.test.tsnow expects the resolver instead of the older inline'start'fallback- focused validation for this checkpoint used
cd server && npx vitest run src/test/unit/billing/recurrenceStorageModel.contract.test.ts src/test/unit/api/contractLineService.billingCycleAlignmentCompatibility.wiring.test.ts ../packages/billing/tests/billingCycleAlignmentCompatibility.model.test.ts ../packages/billing/tests/contractLinePresetRecurringAuthoring.wiring.test.ts --coverage.enabled false,npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json, andnpx tsc --pretty false --noEmit -p server/tsconfig.json; the server compile remains blocked only by the pre-existingpackages/billing/src/actions/creditActions.ts(1208,13)narrowing error
-
(2026-03-17) Billing-timing default normalization now runs through one shared policy across the remaining recurring write seams, which closes
F214andT244:packages/billing/src/actions/contractLineAction.tsnow usesnormalizeLiveRecurringStorage(...)andnormalizeTemplateRecurringStorage(...)for contract-line readback and template-line lookups, eliminating another pocket of hand-writtenbilling_timing ?? 'arrears'logic at the action seampackages/billing/src/repositories/contractLineRepository.tsandserver/src/lib/repositories/contractLineRepository.tsnow normalize billing timing before template snapshot writes and before template-to-contract clone writes, so those repository-level propagation seams no longer bypass the shared recurring timing defaultserver/src/lib/api/services/ContractLineService.tsnow normalizes template-derived recurring cadence fields before writing a fresh live contract line during replacement/renewal assignment flows, which removes the last live service-layer template clone fallback that still hard-codedarrearsserver/src/test/unit/billing/recurrenceStorageModel.contract.test.tsnow includesT244, locking the shared billing-timing default constant plus the fact that wizard, preset, contract-line action, repository, and server assignment write paths all reference the shared normalization helpers instead of inventing local defaults- focused validation for this checkpoint used
cd server && npx vitest run src/test/unit/billing/recurrenceStorageModel.contract.test.ts src/test/unit/billing/templateLineCadenceOwner.persistence.test.ts ../packages/billing/tests/contractLinePresetCadenceOwner.model.test.ts --coverage.enabled false,npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json, andnpx tsc --pretty false --noEmit -p server/tsconfig.json; the server compile remains blocked only by the pre-existingpackages/billing/src/actions/creditActions.ts(1208,13)narrowing error
-
(2026-03-17) The recurrence storage contract is now explicit across live lines, template lines, presets, and shared readers, which closes
F211,F212,F213,F220,T241,T242,T243, andT250:shared/billingClients/recurrenceStorageModel.tsnow defines one authoritative storage model for recurring cadence fields and one set of normalization helpers for live lines, template lines, and presetspackages/billing/src/models/contractTemplate.ts,packages/billing/src/models/contractLinePreset.ts,packages/billing/src/repositories/contractLineRepository.ts,server/src/lib/repositories/contractLineRepository.ts, andshared/billingClients/contractLines.tsnow reuse that shared storage model instead of open-coding separate recurring defaults at each read seamee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRENCE_STORAGE_MATRIX.mdnow names the authoritative table for each recurrence field, the fixed-config compatibility split for partial-period fields, and the shared interface shapes that project those fieldsserver/src/test/unit/billing/recurrenceStorageModel.contract.test.tsnow locks the new shared model plus template/preset cadence-owner migration backfills, whileserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow enforces the new storage matrix artifact andpass-0-source-inventory.jsonwas refreshed so the docs contract stays aligned with live source references- focused validation for this checkpoint used
cd server && npx vitest run src/test/unit/billing/recurrenceStorageModel.contract.test.ts src/test/unit/billing/templateLineCadenceOwner.persistence.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts ../packages/billing/tests/contractLinePresetCadenceOwner.model.test.ts --coverage.enabled false,npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json, andnpx tsc --pretty false --noEmit -p server/tsconfig.json; the server compile remains blocked only by the pre-existingpackages/billing/src/actions/creditActions.ts(1208,13)narrowing error
-
(2026-03-17) Template fixed-line authoring now captures recurring timing and first-invoice semantics explicitly instead of silently defaulting everything to arrears/no-adjustment, which closes
F206andT235:packages/billing/src/components/billing-dashboard/contracts/template-wizard/TemplateWizard.tsxnow carriesbilling_timingandenable_prorationin template wizard state, defaults them toarrears/false, and includes both fields inContractTemplateWizardSubmissionpackages/billing/src/components/billing-dashboard/contracts/template-wizard/steps/TemplateFixedFeeServicesStep.tsxnow lets authors choose fixed-lineBilling Timing, toggleAdjust for Partial Periods, and see first-invoice explainer copy directly in the template authoring step alongside the existing cadence-owner chooserpackages/billing/src/actions/contractWizardActions.tsnow passessubmission.billing_timingintoresolveRecurringAuthoringPolicy(...)when creating template-backed fixed, product, hourly, and usage lines, so template-created recurring defaults stop relying on the action-layer arrears fallback when the wizard meantadvancepackages/billing/src/components/billing-dashboard/contracts/template-wizard/steps/TemplateReviewContractStep.tsxnow echoes cadence owner, billing timing, and partial-period adjustment in the review summary so template authors can verify first-invoice semantics before publishing- focused coverage now lives in
packages/billing/tests/templateWizardBucketOverlay.test.tsandpackages/billing/tests/templateWizardCadenceOwner.wiring.test.ts; the runtime wizard test now proves submission carriesbilling_timing: 'advance'plusenable_proration: true, while the wiring test locks the new template fixed-step and action-layer seams
-
(2026-03-17) Preset authoring/editing now persists the same recurring timing defaults that preset reuse was already replaying, which closes
F205andT234:packages/billing/src/components/billing-dashboard/ContractLineDialog.tsxnow persists explicit preset recurrence defaults instead of dropping them at the modal save seam: fixed presets writebilling_timing, preset creates/updates writecadence_owner: 'client'during the current rollout, and fixed preset config writes now preserve the active partial-period alignment instead of hard-codingstart- the preset create/edit modal now promotes the hidden fixed-config compatibility alignment from
starttoproratedwhenAdjust for Partial Periodsis enabled on a preset that did not already carry a more specific stored alignment, matching the partial-period policy used elsewhere packages/billing/src/components/billing-dashboard/contract-lines/FixedContractLinePresetConfiguration.tsxnow exposesBilling Timingon the main fixed preset edit surface, rehydrates storedbilling_timing, and persists that value plus explicit client cadence metadata when preset basics are saved- focused coverage now lives in
packages/billing/tests/contractLinePresetCadenceOwner.actions.test.tsandpackages/billing/tests/contractLinePresetRecurringAuthoring.wiring.test.ts; the action test now proves preset reuse replays stored advance timing plus partial-period defaults onto the created contract line, while the wiring test locks the create/edit UI payload seams where timing fields had previously been omitted
-
(2026-03-17) Recurring authoring defaults now have one shared policy source instead of per-path fallbacks, which closes
F201,F202,F203,F204,T231,T232, andT233:shared/billingClients/recurringAuthoringPolicy.tsnow defines the authoritative v1 authoring defaults: cadence owner defaults toclient, recurring billing timing defaults toarrears, touched writes preserve stored cadence/timing when omitted, and fixed-line legacy alignment derives through the existing compatibility helper instead of ad hoc inline branchespackages/billing/src/models/contractLine.tsno longer silently dropsbilling_timingon create/update, so contract-wizard-created and custom recurring lines now persist the same timing semantics they author instead of relying on reader-side arrears fallbackspackages/billing/src/actions/contractWizardActions.tsnow resolves one shared recurring authoring policy per submission and uses it across fixed, product, hourly, and usage line creation plus fixed-config alignment writespackages/billing/src/actions/contractLinePresetActions.tsnow uses the same shared policy for preset-to-contract copies and custom recurring-line creation, which fixes the old custom-line mismatch where missingbilling_timingdefaulted toadvanceand fixedenable_prorationstill wrotebilling_cycle_alignment: startpackages/billing/src/repositories/contractLineRepository.ts,server/src/lib/repositories/contractLineRepository.ts,packages/billing/src/actions/contractLineAction.ts, andserver/src/lib/api/services/ContractLineService.tsnow preserve or normalize cadence/timing fields through the same helper on touched writes, reducing live authoring-path drift before the later template/preset cleanup features landpackages/billing/tests/fixedContractLineConfiguration.recurringAuthoring.wiring.test.tsnow locks the live fixed-line edit seam directly: the inline editor must keep cadence owner onupdateContractLine(...), keep billing timing onupsertContractLineTerms(...), and depend on the updated action-layer normalization instead of silently dropping touched timing fieldspackages/billing/src/components/billing-dashboard/contracts/CreateCustomContractLineDialog.tsxnow defaults new custom recurring lines toarrears, matching the rest of the recurring authoring surfaces instead of preselecting a divergent timing mode in the UI
-
(2026-03-17) External tax import and reconciliation consumers now have explicit service-period-first guardrails, which closes
F200andT229:packages/billing/src/services/externalTaxImportService.tsnow states the intended policy inline at both import and reconciliation seams: external tax behavior remains invoice- and charge-tax-driven, while canonical recurring service periods stay explanatory context rather than tax allocation or reconciliation inputspackages/billing/src/services/accountingExportService.tsnow states the complementary post-export rule inline: automatic external-tax import deduplicates by invoice id and must not fan out one invoice into multiple import attempts just because export lines carry multiple canonical recurring periodsserver/src/test/unit/accounting/externalTaxConsumers.servicePeriods.test.tsnow proves both behaviors directly:reconcileTaxDifferences(...)reads only invoice andinvoice_chargestax fields and would fail if it tried to queryinvoice_charge_detailsimportExternalTaxAfterDelivery(...)imports once per unique invoice even when the export context contains multiple canonical recurring detail-backed lines for the same invoice
- validation for this checkpoint used
cd server && npx vitest run src/test/unit/accounting/externalTaxConsumers.servicePeriods.test.ts --coverage.enabled falseplusnpx tsc --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) QuickBooks and Xero export adapters now have explicit recurring-period projection policies, which closes
F198,F199,T227, andT228:packages/billing/src/adapters/accounting/quickBooksOnlineAdapter.tsnow routes line-date selection through an explicit helper: QuickBooks can export only one service date, so canonical recurring ranges pin to the first covered day, end-only fallback lines use the surviving boundary, andfinancial_document_fallbacklines intentionally omitSalesItemLineDetail.ServiceDateserver/src/test/unit/accounting/quickBooksOnlineAdapter.spec.tsnow proves both sides of that QuickBooks contract: canonical recurring ranges export2025-01-01as the service date, while financial-only fallback rows omit it entirelypackages/billing/src/adapters/accounting/xeroAdapter.tsnow routes service-period shaping through an explicit helper: canonical/header-backed lines keep both compatibility summary bounds, whilefinancial_document_fallbacklines stay periodlesspackages/integrations/src/lib/xero/xeroClientService.tsnow flattens Xero line descriptions with explicit date-basis copy instead of raw ISO timestamps: multi-day ranges becomeService period: YYYY-MM-DD to YYYY-MM-DD, same-day coverage becomesService date: YYYY-MM-DD, and null-period lines leave the base description untouched- focused coverage now lives in
server/src/test/unit/accounting/xeroClientService.spec.ts,server/src/test/unit/accounting/xeroAdapter.spec.ts, andpackages/billing/tests/accountingExportAdapters.servicePeriods.wiring.test.ts - validation for this checkpoint used focused adapter/unit runs plus
npx tsc --noEmitonpackages/billingandpackages/integrations; the olderserver/src/test/unit/accounting/xeroAdapter.spec.tsdelivery case still expects a live DB-backed mapping write path duringdeliver(), so the checkpoint relied on the focused transform test rather than claiming the full historical file passes in this local environment
-
(2026-03-17) Reporting and analytics date-basis policy is now explicit across portal, billing, reporting, and finance service seams, which closes
F191,F192,F193,F194,T221,T222,T223, andT230:ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/REPORTING_DATE_BASIS.mdnow defines one policy matrix for billing overview, contract revenue, expiration, reconciliation, financial analytics, and service-metric families, including the mixed-cadence rule that financial-operational readers keep invoice / transaction dates while coverage readers keep canonical recurring service periodspackages/client-portal/src/actions/client-portal-actions/dashboard.tsnow makes the metric split explicit in-source: recent invoice activity may carry recurring coverage summaries, but pending-invoice counts remain invoice-state metrics and must not be silently reinterpreted as service-period totalspackages/billing/src/actions/contractReportActions.tsnow states the intended reporting split directly where revenue and renewal summaries are assembled: revenue pivots to canonical recurring coverage when detail periods exist, while expiration and decision-due summaries stay assignment-date basedpackages/reporting/src/actions/reconciliationReportActions.tsandserver/src/lib/api/services/FinancialService.tsnow document the complementary control rule that reconciliation and financial analytics remain financial-date readers; recurring service periods remain additive explanatory lineage rather than the basis for aging, collections, or discrepancy totalsserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow locks the policy artifact plus those source comments as executable plan contracts, andpass-0-source-inventory.jsonwas refreshed in the same checkpoint so the docs contract continues to match the live persisted-reader inventory
-
(2026-03-17) Financial-artifact fallback handling is now explicit instead of collapsing all missing recurring metadata into the same
financial_document_dateshape, which closesF190andT220:packages/billing/src/actions/creditActions.tsnow distinguishes three additive invoice-context states for credit readers and transaction history:canonical_recurring,financial_document_fallback, andmissing_source_context- the intended staged-coexistence rule is now explicit at the reader seam: historical/manual financial artifacts with no canonical recurring detail stay
financial_document_fallback, while broken lineage references (for example a transferred credit whose source invoice can no longer be loaded) surfacemissing_source_contextwithout inventing recurring periods packages/types/src/interfaces/billing.interfaces.tsandserver/src/interfaces/billing.interfaces.tsnow expose that additiveinvoice_context_statusfield on credit-tracking and transaction payloads so dashboard consumers can distinguish legitimate financial-only fallbacks from repair-needed lineage failurespackages/billing/src/components/billing-dashboard/CreditManagement.tsxnow renders a dedicatedLineage Missingstate with repair guidance instead of silently flattening missing-source cases into the same copy used for intentional financial-only artifacts- focused executable coverage now lives in
server/src/test/unit/billing/creditActions.servicePeriods.test.tsandpackages/billing/tests/creditManagement.financialArtifactContext.wiring.test.ts
-
(2026-03-17) Accounting export readers and stored export lines now make recurring-period provenance explicit, which closes
F195andT224:packages/types/src/interfaces/accountingExport.interfaces.tsnow defines additiveservice_period_sourcemetadata with the three supported export-reader states:canonical_detail_periods,invoice_header_fallback, andfinancial_document_fallbackpackages/billing/src/services/accountingExportInvoiceSelector.tsnow derives that source on preview and batch-selection rows, persists it intoaccounting_export_lines.payload, and normalizes date-only service-period values so preview and stored export lines agree even when the DB driver returns differentDateshapes forDATEcolumnspackages/billing/src/repositories/accountingExportRepository.tsnow normalizes rereadservice_period_start/service_period_endvalues back onto the sharedISO8601Stringcontract instead of leaking raw connection-specificDateobjects to downstream export readerspackages/billing/src/services/accountingExportValidation.ts,packages/billing/src/actions/accountingExportActions.ts,server/src/lib/api/controllers/ApiAccountingExportController.ts, andserver/src/lib/api/controllers/ApiCSVAccountingController.tsnow treatservice_period_sourceas the authoritative projection contract for canonical-detail versus header-fallback versus financial-only export lines- focused coverage now lives in
server/src/test/unit/accounting/accountingExportInvoiceSelector.servicePeriods.test.ts,server/src/test/integration/accounting/invoiceSelection.integration.test.ts, andpackages/billing/tests/accountingExportInvoiceSelector.servicePeriods.wiring.test.ts; the integration seam now explicitly spiesAccountingExportService.createForTenant(...)so selector-created batches share the transaction-scoped repository used by the test harness
-
(2026-03-17) Stored export batches now have an explicit mixed-shape reread contract, which closes
F196andT225:packages/billing/src/services/accountingExportService.tsnow documents the intended storage rule directly atgetBatchWithDetails(...): a stored batch may contain historical/header-fallback lines and canonical-detail-backed recurring lines together, and rereads must preserve each line’s storedservice_period_sourceplus summary/detail-period payload instead of collapsing the batch to one inferred timing basisserver/src/test/integration/accounting/exportDashboard.integration.test.tsnow proves that contract against the real dashboard/service reread seam with one canonical line and one historical fallback line in the same stored batch; both period summaries and per-line payload provenance survivegetBatchWithDetails(...)- the same integration file now aligns its selector re-export path with the live tenant-aware batch creation seam by spying
AccountingExportService.createForTenant(...), keeping the dashboard test harness on the same code path now used bycreateBatchFromFilters(...)
-
(2026-03-17) Export execution now has an explicit replay/reread immutability rule for recurring-period provenance, which closes
F197andT226:packages/billing/src/services/accountingExportService.tsnow states the delivery rule inline at the line-update seam: execute/retry may change transport state such asstatusandexternal_document_ref, but they must not rewrite storedservice_period_sourceor recurring-detail payloadsserver/src/test/unit/accounting/accountingExportService.replay.servicePeriods.test.tsnow proves that contract on the focused execution path: validation promotes the batch toready, delivery moves the line todelivered, a second execute attempt is rejected as invalid state, and the canonical recurring-detail payload survives both the delivery transition and the rejected replay attempt unchanged- an attempted DB-backed version of the same assertion in
batchLifecycle.integration.test.tsran into an unrelated@alga-psa/jobspackage-entry resolution failure before the suite could load, so this checkpoint uses the focused service-level unit seam instead of broadening unrelated harness repair work
-
(2026-03-17)
T100is still blocked after a fresh recheck against the active local Postgres listener, so it remains open:server/src/test/infrastructure/billing/invoices/prepaymentInvoice.test.tsstill spends most of its setup budget replaying older migration-heavy infrastructure bootstrap and then falls over on missing workflow-generator files (scripts/generate-system-email-workflow.cjsandservices/workflow-worker/src/workflows/system-email-processing-workflow.ts) before the targeted recurring assertion becomes trustworthyserver/src/test/infrastructure/billing/invoices/negativeInvoiceCredit.test.tsstill fails during migration bootstrap on202409071803_initial_schema.cjswithROLLBACK - Connection terminated unexpectedly, so the negative-invoice half of the integration guard is still a harness problem rather than an application-behavior regression- keep defending this area with the focused unit suites already added under
creditActions.servicePeriods.test.ts,creditReconciliation.servicePeriods.test.ts, andprepaymentInvoice.periodPolicy.test.tsuntil the older infrastructure harness is repaired enough to run the realT100integration seam
-
(2026-03-17) Dashboard and portal invoice views now label financial-only artifacts explicitly instead of silently omitting recurring context, which closes
F189andT339:packages/client-portal/src/components/billing/InvoiceDetailsDialog.tsxnow rendersFinancial-only line. No recurring service period.for manual or adjustment rows that intentionally lack canonical recurring timing metadatapackages/client-portal/src/components/billing/BillingOverviewTab.tsxnow surfacesFinancial-only invoice...guidance on the next-invoice card when the invoice is manual or credit-affected and no recurring service-period summary is availablepackages/billing/src/components/billing-dashboard/CreditManagement.tsxnow shows aContextcolumn and descriptive copy that distinguish recurring-source credits, transferred recurring credits, and financial-only credits, so dashboard readers can interpret non-service financial artifacts alongside preserved recurring lineage- focused coverage now lives in
packages/client-portal/src/components/billing/InvoiceDetailsDialog.servicePeriods.test.tsx,packages/client-portal/src/components/billing/BillingOverviewTab.servicePeriods.test.tsx, andpackages/billing/tests/creditManagement.financialArtifactContext.wiring.test.ts
-
(2026-03-17) Credit transfer readers and audit metadata now preserve recurring lineage instead of dropping it on the target credit, which closes
F188andT219:packages/billing/src/actions/creditActions.tsnow resolves direct-or-inherited credit invoice lineage, solistClientCredits(...)andgetCreditDetails(...)can keep showing canonical recurring source-invoice context even when a transferred credit’s own transaction has no directinvoice_idtransferCredit(...)now stamps transfer transaction metadata and audit details with the source credit id plus source-invoice date basis and recurring service-period summary, which makes downstream reporting and audit trails explicit for transferred credits that originated from detail-backed recurring invoices- the shared/server billing interfaces now expose additive lineage fields (
source_credit_id,source_invoice_id,lineage_origin) on both transactions and credit rows server/src/test/unit/billing/creditActions.servicePeriods.test.tsnow proves a transferred credit keeps canonical recurring source lineage in both list and detail readers
-
(2026-03-17) Credit expiration and reconciliation now make their date basis explicit, which closes
F187andT218:packages/billing/src/actions/creditReconciliationActions.tsnow stamps reconciliation-report metadata withreconciliation_date_basis: financial_document_date, making the control rule explicit: expiration and remaining-amount reconciliation are driven by transaction/credit metadata such ascreated_atandexpiration_date, not by invoice-header or recurring service-period dates- the same metadata now preserves recurring lineage additively when available: inconsistent-remaining-amount reports carry the source credit invoice’s canonical recurring summary plus each application transaction’s target-invoice date basis and recurring summary fields
server/src/test/unit/billing/creditReconciliation.servicePeriods.test.tsnow proves both halves of that policy: expired negative-invoice credits reconcile on financial dates, and reconciliation reports for detail-backed credits preserve canonical recurring invoice lineage only as explanatory metadata
-
(2026-03-17) Credit application now distinguishes source-invoice versus applied-to-invoice recurring context, which closes
F186andT217:packages/billing/src/actions/creditActions.tsnow preserves the top-level credit summary on the source invoice while separately attaching invoice-number, status, date-basis, and canonical recurring service-period summary fields to each transaction entry returned bygetCreditDetails(...)- the same action now carries target-invoice context onto
CREDIT_NOTE_APPLIEDworkflow payloads, so credit-application audit streams can tell whether the invoice receiving the credit is a canonical recurring-detail-backed invoice or a purely financial-date artifact shared/workflow/streams/domainEventBuilders/creditNoteEventBuilders.ts,shared/workflow/runtime/schemas/billingEventSchemas.ts,packages/event-schemas/src/schemas/domain/billingEventSchemas.ts, and the shared/serverITransactioninterfaces were expanded additively for that target-invoice context- focused executable coverage now lives in
server/src/test/unit/billing/creditActions.servicePeriods.test.ts, which proves the source credit keeps its original recurring summary while the latercredit_applicationtransaction carries the target invoice’s canonical recurring period metadata
-
(2026-03-17) Credit-note issuance metadata now makes source date basis explicit, which closes
F185andT216:shared/workflow/streams/domainEventBuilders/creditNoteEventBuilders.ts,shared/workflow/runtime/schemas/billingEventSchemas.ts, andpackages/event-schemas/src/schemas/domain/billingEventSchemas.tsnow carry additive credit-note source metadata:sourceDocumentKind,sourceInvoiceId,sourceInvoiceNumber,sourceInvoiceStatus,sourceInvoiceDateBasis, and optional source recurring service-period summary fieldspackages/billing/src/actions/creditActions.tsnow tags prepayment-issued credits asfinancial_document_dateartifacts and exposesinvoice_date_basisalongside source recurring service-period summaries inlistClientCredits(...)andgetCreditDetails(...)packages/billing/src/actions/invoiceModification.tsnow recalculates source invoice summary metadata before publishingCREDIT_NOTE_CREATEDfor negative invoices, so detail-backed recurring source invoices emitcanonical_recurring_service_periodwhile flat or non-service sources fall back tofinancial_document_date- focused executable coverage now lives in
server/src/test/unit/billing/creditActions.servicePeriods.test.tsandserver/src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts;packages/billing/src/actions/creditActions.tstype narrowing at the transaction reread seam was also tightened, which unblockednpx tsc --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17)
T100remains open after this checkpoint even though the underlying integration harness moved forward:server/src/test/infrastructure/billing/invoices/prepaymentInvoice.test.tsandserver/src/test/infrastructure/billing/invoices/negativeInvoiceCredit.test.tsnow stop hardcodingDB_PORT=5432and include the auth/secret/db mock exports the current runtime imports require- focused integration assertions for canonical recurring detail preservation (
T100) and negative-invoice source date-basis lineage (T216) were added to those suites, but local execution is still blocked by older migration-heavy infrastructure harness drift inprepaymentInvoice.test.tsand by migration/test-context instability innegativeInvoiceCredit.test.ts
-
(2026-03-17) Negative-invoice offsets now have an explicit source-period policy, which closes
F184andT215:packages/billing/src/actions/creditActions.tsnow documents the offset rule at the source-invoice reader seam: a negative-invoice or prepayment credit keeps its recurring timing context on the source invoice, and later credit-application transactions are financial offsets rather than new service-period definitionsserver/src/test/unit/billing/creditActions.servicePeriods.test.tsnow proves that behavior explicitly for a negative-invoice credit later applied toinvoice-2: the credit reader still reports the source invoice’s canonical recurring period while the transaction history separately records the later financial application
-
(2026-03-17) Prepayment invoices now have an explicit non-service period policy, which closes
F183andT214:packages/billing/src/actions/creditActions.tsnow states the intended boundary where prepayment invoices are created: they remain non-service financial artifacts,billing_period_*on the header tracks the immediate financial issuance window, and canonical recurring service periods stay absentserver/src/test/unit/billing/prepaymentInvoice.periodPolicy.test.tsnow proves the live prepayment creation path creates the financial invoice, transaction, and credit-tracking rows without touchinginvoice_charge_details, which locks the prepayment path away from accidental recurring-period coupling
-
(2026-03-17) Manual-to-recurring provenance is now explicit and advisory-only, which closes
F182andT213:packages/billing/src/services/invoiceService.tsnow states the intended provenance rule inline: a manual adjustment or manual percentage discount may point at an existing invoice charge, including a recurring parent charge, but thatapplies_to_item_idlink is advisory manual provenance rather than canonical recurring timing dataserver/src/test/unit/billing/invoiceService.manualPeriodPolicy.test.tsnow proves a manual discount targeted at an existing recurring parent charge keeps that linkage and recomputes from the recurring row amount, while still remaining periodless itself
-
(2026-03-17) Manual invoice lines now have an explicit periodless policy contract, which closes
F181andT211:packages/billing/src/services/invoiceService.tsnow documents the intended boundary at the persistence seam: manually entered invoice rows remain periodless financial rows and do not create canonicalinvoice_charge_detailsor claim recurring service-period truthserver/src/test/unit/billing/invoiceService.manualPeriodPolicy.test.tsnow proves both a manual line and a manual percentage discount persist withoutservice_period_*orbilling_timingfields while still calculating their net amounts correctly
-
(2026-03-17) Zero-dollar recurring invoice handling now preserves canonical persistence instead of treating every zero-dollar result like an empty billing window, which closes
F180andT210:packages/billing/src/actions/invoiceGeneration.tsnow treatsfinalAmount === 0as a dedicated policy seam: empty no-content windows can still be suppressed, but zero-dollar invoices with real charges/discounts/adjustments are still created so recurring detail-backed invoice content is not silently skipped- the
finalizedzero-dollar setting is now live for that path viafinalizeInvoiceWithKnex(...), so a zero-dollar recurring invoice can still end the run in a finalized state after canonical persistence occurs - focused executable coverage now lives in
server/src/test/unit/billing/invoiceGeneration.zeroDollarFinalization.test.ts, whileserver/src/test/unit/billing/invoiceGeneration.emptyResult.test.tsremains the guard that truly empty no-charge windows still respect the suppress setting
-
(2026-03-17) Percentage-discount recalculation now has an explicit service-period-first contract, which closes
F179andT209:packages/billing/src/services/invoiceService.tsnow recalculates manual percentage-discount rows from the current non-discount invoice subtotal or targeted line amount before tax and final totals are recomputed, so recurring detail-backed charges can change the discount base without losing their own canonical service-period provenance- the policy is now explicit in code rather than accidental: percentage discount rows stay financial-only rows with no service-period truth of their own, while the recurring lines they discount keep the authoritative service-period metadata
- focused executable coverage now lives in
server/src/test/unit/billing/invoiceService.percentageDiscountRecalculation.test.ts, which proves a recurring line plus manual percentage discount rehydrates to the correct discount amount and final invoice totals
-
(2026-03-17) Billed-through enforcement is now explicitly locked to canonical detail periods instead of invoice-header period fields, which closes
F171andT201:packages/billing/src/lib/billing/billingEngine.tswas already reading duplicate-prevention state frominvoice_charge_details.service_period_start,invoice_charge_details.service_period_end, andinvoice_charge_details.billing_timingviahasExistingServicePeriodCharge(...); this checkpoint made that lifecycle-enforcement contract explicit rather than leaving it implicit in the helperserver/src/test/unit/billing/billingEngine.billedThroughReader.test.tsnow proves the reader targetsinvoice_charge_details as iidplus detail-period fields and does not querybilling_period_endfrom invoice headers, while the existingT087runtime regression inserver/src/test/unit/billing/billingEngine.timing.test.tsremains the behavior-level guard that an already-persisted advance service period is skipped even when the enclosing invoice window metadata is later- no production code changed in this checkpoint; the work was to convert the already-migrated billed-through reader into an executable contract before broader recurring lifecycle guards (
F172+) build on it
-
(2026-03-17) Client contract-line mutation guards now treat canonical recurring detail periods as authoritative for edit/remove/replace safety checks, which closes
F172andT202:packages/clients/src/actions/clientContractLineActions.tsnow reads the latest recurring billed-through boundary frominvoice_charge_details.service_period_endjoined throughcontract_line_service_configuration, so contract-line mutation guards follow the same authoritative detail-period source as the recurring billed-through reader- historical flat invoices are still protected during staged coexistence because the new helper falls back to the older invoice-header reader only when no canonical recurring detail periods exist yet
- mutation policy is now explicit in the client action layer: if canonical recurring detail periods already exist, replacing the assigned contract line (
contract_line_idswap) is blocked and end-date/deactivation checks compare against the authoritative billed-through service-period end rather than invoice headers - focused coverage now lives in
server/src/test/unit/billing/clientContractLineMutationGuards.test.ts, proving both the deactivation guard and the replace-after-billing guard
-
(2026-03-17) Renewal and replacement identity is now explicit for superseded recurring lines, which closes
F173andT203:packages/clients/src/actions/clientContractLineActions.tsnow states the replacement policy directly in code: once a billed recurring line is superseded, the follow-on line must be created as a freshcontract_linesrow so historical recurring detail periods stay attached to the old line identityserver/src/lib/api/services/ContractLineService.tsnow carries the same rule in the API assignment path, so renewed or replacement assignments are documented as fresh-line operations instead of in-place mutation of a billed line- executable coverage now lives in
server/src/test/unit/billing/clientContractLineReplacementIdentity.test.ts, which proves the client add-line flow creates a fresh generated line id before cloning template configuration and separately locks the server assign path to the same fresh-identity rule
-
(2026-03-17) Invoice workflow events and audit logs now carry recurring-detail provenance instead of relying implicitly on invoice headers alone, which closes
F170andT199:server/src/lib/api/services/invoiceWorkflowEvents.tsnow definessummarizeInvoiceRecurringProvenance(...), which reduces hydrated invoice charges into one additive provenance summary: whether recurring timing is authoritative from canonical detail rows or only parent charge fields, how many detail-backed charges/periods exist, the recurring summary range, and whether timing is uniform or mixedshared/workflow/runtime/schemas/billingEventSchemas.tsnow allows thatrecurringProvenanceobject on invoice workflow payloads such as sent, status-changed, due-date-changed, overdue, and written-off events, so workflow consumers can distinguish canonical recurring provenance from header-grouping dates without a schema forkserver/src/lib/api/services/InvoiceService.tsnow hydrates that same provenance summary fromInvoiceModel.getInvoiceCharges(...)and attaches it to invoice audit-log details (recurring_provenance) plus the invoice workflow payload builders on invoice update, delete/cancel, finalize, send, payment, credit-application, refund, and bulk-status paths- focused executable coverage now lives in
server/src/test/unit/invoiceWorkflowEvents.test.tsfor the builder/schema side andserver/src/test/unit/api/invoiceWorkflowRecurringProvenance.wiring.test.tsfor the service-layer audit/workflow wiring seam
-
(2026-03-17) Invoice API schemas now make the legacy-flat versus canonical-detail compatibility contract executable instead of only documenting it, which closes
F169andT198:server/src/lib/api/schemas/invoiceSchemas.tsnow accepts both old flat invoice payloads and new detail-backed recurring payloads explicitly: response rows can carry canonicalservice_period_*,billing_timing,recurring_detail_periods, andrecurring_projection, while full invoice responses also accept the deprecatedinvoice_itemsalias alongsideinvoice_charges- the same compatibility rule now exists in
server/src/lib/api/schemas/financialSchemas.ts, so secondary financial API response validation does not reject canonical recurring detail-backed rows while the main invoice API accepts them - the schema contract is intentionally stricter on half-migrated shapes: canonical
recurring_detail_periodsnow requirerecurring_projection, anddetail_period_countmust match the hydrated detail array length server/src/test/unit/api/invoiceResponseSchema.compatibility.test.tsnow proves three states explicitly: legacy flat invoice payloads still parse through the deprecated alias path, canonical detail-backed payloads parse through the new path, and half-migrated payloads are rejected instead of silently drifting through validation
-
(2026-03-17) Recurring invoice parent/detail projection is now explicit instead of being inferred from whatever summary fields happened to be populated, which closes
F161,F162,F163,T191, andT193:packages/types/src/interfaces/invoice.interfaces.tsandserver/src/interfaces/invoice.interfaces.tsnow define additiverecurring_projectionmetadata onIInvoiceCharge, documenting the stable contract for detail-backed recurring reads:recurring_detail_periodsis authoritative, parentservice_period_start/service_period_endis the summary range across those rows, and parentbilling_timingis only populated when every detail row agreespackages/billing/src/models/invoice.tsnow hydrates that projection metadata whenever canonicalinvoice_charge_detailsrows exist, soInvoice.getById(...),Invoice.getFullInvoiceById(...), andgetInvoiceLineItems(...)all expose the same typed parent/detail semantics instead of an implicit summary-only shapeserver/src/lib/api/services/InvoiceService.tsnow delegates invoice-item reads to the same detail-aware invoice model instead of the legacyinvoice_line_itemstable reader, which keeps APIinvoice_chargespayloads aligned with the canonical recurring read-model contract without changing the outer response shapeserver/src/test/unit/billing/invoiceModel.servicePeriods.test.tsnow closesT191with a multi-detail recurring parent charge contract, andserver/src/test/unit/api/invoiceService.recurringDetailProjection.test.tsclosesT193by proving the API service reuses the same hydrated projection
-
(2026-03-17) Historical flat invoice hydration now has an explicit fallback rule instead of an accidental omission, which closes
F164andT192:packages/types/src/interfaces/invoice.interfaces.tsandserver/src/interfaces/invoice.interfaces.tsnow state the fallback plainly: whenrecurring_projectionandrecurring_detail_periodsare absent, readers must preserve any parent-level period fields they already have and must not synthesize canonical detail rows for old flat invoicespackages/billing/src/models/invoice.tsnow carries an inline guard comment on the no-detail branch so the intended behavior is visible at the exact hydration seam: historical flat invoices remain parent-only when no canonicalinvoice_charge_detailsrows existserver/src/test/unit/billing/invoiceModel.servicePeriods.test.tsnow closesT192by proving a historical invoice with no detail rows still hydrates cleanly and intentionally omits synthesized recurring detail metadata
-
(2026-03-17) Preview rows now have an explicit multi-period projection contract instead of only a flattened summary range, which closes
F165andT194:packages/types/src/interfaces/billing.interfaces.tsandserver/src/interfaces/billing.interfaces.tsnow allow runtime billing charges to carry additiverecurringDetailPeriods, giving preview generation one shared shape for charges that summarize one or many canonical recurring periodspackages/types/src/lib/invoice-renderer/types.tsnow documents the preview-row rule explicitly:servicePeriodStart/servicePeriodEndis the compatibility summary range, whilerecurringDetailPeriodsis authoritative when present andbillingTimingonly survives there uniformlypackages/billing/src/actions/invoiceGeneration.tsnow normalizes preview recurring detail periods from either billing-charge or invoice-charge payloads, derives summary fields from that detail list when needed, and carriesrecurringDetailPeriodsthrough the preview payload instead of flattening multi-period charges awayserver/src/test/unit/billing/invoiceGeneration.preview.test.tsnow closesT194by proving one preview charge can summarize two canonical recurring periods while keeping both detail periods visible to renderer-facing consumers
-
(2026-03-17) Rendering adapters now have an explicit flatten-or-expand rule for canonical recurring detail periods, which closes
F166andT195:packages/billing/src/lib/adapters/invoiceAdapters.tsnow documents the adapter rule directly in code: rendered rows keeprecurringDetailPeriodsverbatim when available, flattenservicePeriodStart/servicePeriodEndto a summary range for one-row template consumers, and flatten mixed timing tonullinstead of inventing a winnerpackages/billing/src/lib/adapters/invoiceAdapters.test.tsnow closesT195with a mixed-timing case proving the adapter preserves the canonical detail-period list while exposing a compatibility summary range and a null summary timing
-
(2026-03-17) Client-portal invoice detail projection rules are now explicit for detail-backed, flattened, and omitted recurring period states, which closes
F167andT196:packages/client-portal/src/components/billing/InvoiceDetailsDialog.tsxnow documents the portal policy inline: render the canonical detail-period list when multiple recurring periods exist, flatten to oneService Periodline when only one detail or a parent summary range exists, and omit service-period copy entirely when no recurring period metadata is availablepackages/client-portal/src/components/billing/InvoiceDetailsDialog.servicePeriods.test.tsxnow closesT196by proving the two fallback branches explicitly: a legacy summary-only recurring row renders one flattened service-period label, while a manual adjustment row with no period metadata renders none
-
(2026-03-17) Multi-period recurring ordering and aggregation rules are now explicit instead of relying on upstream input order, which closes
F168andT197:packages/client-portal/src/components/billing/InvoiceDetailsDialog.tsxnow sorts rendered recurring detail periods by canonical start/end before showing them, so client-visible multi-period lists stay deterministic even if an upstream reader returns the periods unsortedpackages/client-portal/src/components/billing/recurringServicePeriodSummary.tsnow sorts and de-duplicates recurring period labels before aggregating them into overview-style summaries, so invoice-level summary copy stays stable across detail-backed and flattened inputspackages/client-portal/src/components/billing/InvoiceDetailsDialog.servicePeriods.test.tsxnow proves the dialog renders unsorted canonical detail periods in chronological order, andpackages/client-portal/src/components/billing/recurringServicePeriodSummary.test.tsclosesT197by proving aggregated invoice summaries remain ordered and de-duplicated across mixed detail-backed and summary-backed rows
-
(2026-03-17) Shared invoice-candidate grouping now carries explicit split reasons for PO and financial/export constraints, which closes
F158,F159,F160,T189, andT190:packages/types/src/interfaces/recurringTiming.interfaces.tsnow lets scoped due selections carry PO scope, currency, tax source, and export-shape keys, while scoped candidate groups now exposesplitReasonswithsingle_contract,purchase_order_scope, andfinancial_constraintshared/billingClients/recurringTiming.tsnow exportsgroupDueServicePeriodsForInvoiceCandidates(...), which applies those split constraints within each due window and annotates the resulting candidate groups with the operator-visible reasons they were splitserver/src/test/unit/billing/recurringTiming.domain.test.tsnow locks all three grouping cases: contract-scope splitting, PO-scope splitting, and financial/export splitting with explainability metadata
-
(2026-03-17) The first explicit invoice-split rule is now codified at the shared-domain layer, which closes
F157andT188:packages/types/src/interfaces/recurringTiming.interfaces.tsnow distinguishes scoped due selections and scoped invoice-candidate groups so grouping logic can carry contract-scope metadata explicitly instead of assuming one invoice window always means one invoiceshared/billingClients/recurringTiming.tsnow exportsgroupDueServicePeriodsByInvoiceWindowAndContract(...), which keeps mixed due selections on the same invoice window together only when they also share the sameclientContractIdserver/src/test/unit/billing/recurringTiming.domain.test.tsnow proves two due selections on the same invoice window still split into separate candidate groups when their contract scopes differ, preserving the existing single-contract invoice invariant before broader PO/tax/export split rules land
-
(2026-03-17) Client-cadence and contract-cadence due-work selection are now both explicit scheduler inputs instead of implicit billing-cycle-only assumptions, which closes
F155,F156,T186, andT187:packages/billing/src/actions/recurringBillingRunActions.tsnow maps persistedgetAvailableBillingPeriods(...)results into deterministic client-cadence recurring-run targets, selector inputs, and execution windows using the storedperiod_start_date/period_end_dateboundaries rather than any current billing-setting anchor mathshared/billingClients/recurringRunExecutionIdentity.tsnow exportsselectContractCadenceRecurringRunTargets(...), which uses the shared contract-cadence service-period generators plusresolveContractCadenceInvoiceWindowForServicePeriod(...)to produce schedulable contract-owned due windows before invoice grouping occursserver/src/test/unit/billing/recurringBillingRunActions.test.tsnow proves client-cadence target selection stays deterministic on historical persisted billing-cycle windows, andserver/src/test/unit/billing/contractCadenceServicePeriods.domain.test.tsnow proves contract-cadence due-work selection stays deterministic across anniversary-window boundary crossings
-
(2026-03-17) Comparison-mode drift traces now carry deterministic recurring execution identity metadata, which closes
F154andT185:packages/billing/src/actions/invoiceGeneration.tsnow routes comparison-mode logging through the selector-input contract and attachesselectionKey,executionIdentityKeys, andexecutionWindowKindsto legacy-vs-canonical drift warnings instead of logging only a billing-cycle placeholder- this keeps comparison-mode safe for staged rollout when recurring selection is no longer defined purely by
billingCycleId: contract-cadence or other future execution windows can now be compared with traceable identity metadata even before full scheduler cutover lands server/src/test/unit/billing/invoiceGeneration.recurringSelection.test.tsnow proves a contract-cadence selector input emits those deterministic trace fields when comparison mode detects drift
-
(2026-03-17) Recurring-run retries now carry deterministic due-work identity instead of relying on billing-cycle list order, which closes
F153andT184:shared/billingClients/recurringRunExecutionIdentity.tsnow derivesselectionKeyandretryKeyfrom the sorted set of execution-window identity keys, so the same due-work selection yields the same retry scope even if callers pass targets in a different orderpackages/billing/src/actions/recurringBillingRunActions.tsnow includes those deterministic keys in the recurring-run result and workflow payload inputs while still keeping event-bus publish idempotency keyed by the per-attemptrunId, which avoids collapsing deliberate retry attempts into the original run recordserver/src/test/unit/billing/recurringBillingRunActions.test.tsnow proves two runs over the same mixed client/contract execution windows produce the sameselectionKeyandretryKeydespite opposite target ordering
-
(2026-03-17) Background recurring selector inputs are now explicit instead of being implicit billing-cycle lookups, which closes
F152andT183:packages/types/src/interfaces/recurringTiming.interfaces.tsnow definesIRecurringDueSelectionInput, andshared/billingClients/recurringRunExecutionIdentity.tsnow providesbuildBillingCycleDueSelectionInput(...)plusbuildContractCadenceDueSelectionInput(...)so background flows can describe the selector window independently from whether invoice persistence still needs abillingCycleIdpackages/billing/src/actions/invoiceGeneration.tsnow exposescalculateBillingForSelectionInput(...), which routes due recurring selection through the new selector-input contract and falls back to the execution-window identity key when no rawbillingCycleIdexistsserver/src/lib/jobs/handlers/generateInvoiceHandler.tsandserver/src/lib/jobs/index.tsnow accept optionalselectorInputpayloads alongside the execution-window metadata, with a guard that prevents mismatched selector and execution identities from silently diverging in background jobsserver/src/test/unit/billing/invoiceGeneration.recurringSelection.test.tsnow locks the new contract by proving a contract-cadence selector input can preselect due recurring service periods without any rawbillingCycleIdin the selector path
-
(2026-03-17) Recurring-run execution identity is now additive and explicit instead of being only a raw
billingCycleId, which closesF151,T181, andT182:packages/types/src/interfaces/recurringTiming.interfaces.tsnow definesIRecurringRunExecutionWindowIdentityplus explicitbilling_cycle_windowandcontract_cadence_windowkinds, so scheduling and workflow metadata can reference a typed recurring execution window instead of a bare stringshared/billingClients/recurringRunExecutionIdentity.tsnow builds stable execution-window identity keys for both current client billing-cycle windows and future contract-cadence windows, andpackages/billing/src/actions/recurringBillingRunActions.tsnow normalizes recurring-run targets through that identity layer before emitting workflow metadataserver/src/lib/jobs/index.tsandserver/src/lib/jobs/handlers/generateInvoiceHandler.tsnow carry optional execution-window metadata on invoice-generation jobs while keeping the live execution bridge onbillingCycleId; contract-cadence execution windows are therefore schedulable as typed payloads before later selector/job work (F152+,F156+) makes them fully executable- focused validation lives in
server/src/test/unit/billing/recurringTiming.domain.test.ts,server/src/test/unit/billing/recurringBillingRunActions.test.ts, andserver/src/test/unit/billing/recurringBillingRunWorkflowEvents.test.ts; the shared-domain builder test file remains outside the repo's default Vitest include, so the server unit suites are the reliable executable guard for this checkpoint
-
(2026-03-17) Feature-to-subsystem traceability is now explicit in the plan artifacts, which closes
F150andT170:ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/FEATURE_SUBSYSTEM_MAP.mdnow groups the feature backlog into subsystem bands spanning architecture/parity, shared timing domain, runtime execution, invoice generation/persistence, storage/API reconciliation, authoring surfaces, downstream readers/exports, contract cadence, and the later materialized-service-period ledger- the map also defines the tracking discipline for future checkpoints: every completed feature should name its primary subsystem, note cross-cut surfaces in
SCRATCHPAD.md, and keep commit scope aligned to a coherent subsystem slice instead of only chronological feature order server/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow enforces the existence of that mapping artifact and the required subsystem coverage asT170
-
(2026-03-17) Operator investigation guidance now has explicit runbook paths for cadence-owner disputes and header-versus-detail service-period mismatches, which closes
F149andT148:ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RUNBOOK.mdnow includes a dedicatedCadence-Owner Dispute Investigationsection that tells operators to compare storedcontract_lines.cadence_owner, the active run window, and the persistedinvoice_charge_detailsrow before deciding whether a line truly followed client cadence or contract cadence- the same runbook now includes a
Service-Period Mismatch Investigationsection that makes the intended interpretation explicit: invoice headers stay invoice-window grouping dates, while canonical recurring detail rows stay the authoritative coverage dates for migrated recurring lines server/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow locks those operator questions, SQL fragments, and escalation rules asT148, so runbook drift will fail fast instead of silently weakening mixed-cadence support guidance
-
(2026-03-17) Time-and-usage unification now has its own executable follow-on boundary contract, which closes
F148andT169:- the time/usage follow-on text was already present in
PRD.mdandPASS0_RECURRING_TIMING_APPENDIX.md, butserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow locks that scope boundary explicitly instead of leaving it as prose that could drift - this checkpoint did not widen recurring v1 scope; it made the existing rule enforceable: time entries and usage records remain event-driven until a separate follow-on plan deliberately reopens canonical period or ledger semantics for those domains
- the time/usage follow-on text was already present in
-
(2026-03-17) Advanced service-period ledger extensions are now explicitly fenced off as follow-on work, which closes
F147andT168:PRD.mdandPASS0_RECURRING_TIMING_APPENDIX.mdnow both carry a dedicatedAdvanced Service-Period Ledger Extensionsboundary that keeps long-range materialization, archival/cold-storage, performance denormalization, and bulk ledger reshaping out of recurring v1 unless the first-cut ledger proves insufficient- the new boundary also names the trigger conditions for reopening that scope later: concrete horizon/regeneration cost, read-performance, or retention/storage failures, plus a requirement to define canonical-versus-derived truth, replay/repair, and rollback posture before any ledger extensions land
server/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow locks those phrases asT168, andpass-0-source-inventory.jsonwas refreshed at the same time so the existing docs inventory contracts continue to match livergoutput
-
(2026-03-17) Post-cutover source-plus-DB validation now proves the migrated live recurring path no longer depends on
billing_cycle_alignmentorresolveServicePeriod, which closesF146,T166, andT167:server/src/test/unit/billing/billingEngine.cleanupSource.test.tsremains the direct source contract: the billing engine file still does not contain the removedresolveServicePeriod,_calculateProrationFactor, orapplyProrationToPlanseamsserver/src/test/integration/billingInvoiceTiming.integration.test.tsnow adds one DB-backed invariance test proving three otherwise-identical partial advance lines withbilling_cycle_alignment = start|end|proratedall persist the same canonicalservice_period_start,service_period_end, and subtotal on the real invoice-generation path- the same integration file now adds a DB-backed arrears partial-period test proving a mid-start monthly recurring line still persists
2025-02-10through2025-02-28on the live invoice-generation path after the helper cleanup, which is the real-data closure for the oldresolveServicePeriodseam - local execution required overriding the stale
.env.localtestDB_PORT=5438setting toDB_PORT=57433, because the active Postgres listener for this worktree is on57433
-
(2026-03-17) End-exclusive recurring boundary behavior now has explicit regression guards across mixed-cadence selection and pricing-schedule overlap, which closes
F145,T149, andT159:server/src/test/unit/billing/billingEngine.timing.test.tsnow proves a contract-owned advance line whose first due window starts exactly at the active client invoice-window end is excluded from the current mixed-cadence selection set, so coexistence keeps[start, end)overlap semantics instead of treating touching windows as overlappingserver/src/test/unit/billing/billingEngine.discountPricingTiming.test.tsnow proves fixed recurring pricing schedules remain end-exclusive on both sides of the canonical service period, excluding schedules that only start at the service-period end or end at the service-period start- no production code changed in this checkpoint; the value was locking the already-correct selection/query boundaries in executable tests before later scheduler and reader work adds more mixed-cadence pressure
-
(2026-03-17) Billing dashboard invoice-generation surfaces now explain service-period-first recurring semantics consistently, which closes
F139and addsT332:packages/billing/src/components/billing-dashboard/Overview.tsxnow frames invoice management as invoice-window generation for recurring service periods plus separate manual/prepayment financial handlingpackages/billing/src/components/billing-dashboard/invoicing/GenerateTab.tsxnow explains the semantic boundary for automatic, manual, and prepayment flows directly under the invoice-type selectorpackages/billing/src/components/billing-dashboard/AutomaticInvoices.tsx,ManualInvoices.tsx, andPrepaymentInvoices.tsxnow each state whether they create recurring service-period coverage, stay periodless, or create credit-only financial artifactspackages/billing/tests/invoicingGenerateTab.recurringCopy.wiring.test.tswas added becauseF139previously had no explicit regression item intests.json; the newT332tracks this copy/UX contract without overloading unrelated recurring-engine tests
-
(2026-03-17) Obsolete recurring-timing terminology is now cleaned out of migrated live copy and implementation comments, which closes
F142andT165:packages/billing/src/components/billing-dashboard/contracts/wizard-steps/FixedFeeServicesStep.tsx,ContractLineDialog.tsx,contracts/CreateCustomContractLineDialog.tsx,contract-lines/FixedContractLineConfiguration.tsx,contract-lines/FixedContractLinePresetConfiguration.tsx, andservice-configurations/FixedServiceConfigPanel.tsxnow useAdjust for Partial Periods/Partial-Period Adjustmentinstead of staleEnable Proration/Prorationlabels on migrated recurring surfaces- the remaining readable legacy alignment selector now shows
Proportional Coverageinstead of the rawProratedlabel where staged compatibility still exposes that choice packages/client-portal/src/components/billing/PaymentSuccessContent.tsxandserver/src/app/msp/licenses/purchase/success/page.tsxno longer describe recurring invoice outcomes withproration-onlyorprorated chargescopy; both now explain coverage in canonical partial-period languagepackages/billing/src/actions/contractLineAction.tsandpackages/billing/src/lib/billing/billingEngine.tsnow describe the remaining compatibility seam as legacy partial-period settings instead of proration-centric commentspackages/billing/tests/recurringTimingTerminology.cleanup.test.tsnow locks those migrated surfaces against stale recurring-timing wording drifting back in
-
(2026-03-17) Runtime rollout protection now blocks partial mixed-timing states before recurring charge-family execution begins, which closes
F143,T150, andT158:packages/billing/src/lib/billing/billingEngine.tsnow always builds the canonical recurring timing selection set for the active invoice window and, when callers providerecurringTimingSelections, rejects missing, extra, or divergent line selections with an explicitRecurring timing rollout guard blocked mixed legacy/canonical timing stateerror- this keeps the service-period-first seam honest during staged rollout: action-layer preselection cannot silently cover only some recurring lines while other fixed/product/license work falls back to a different timing path on the same invoice run
server/src/test/unit/billing/billingEngine.timing.test.tsnow proves both guard modes explicitly: one test covers missing provided selections for one due recurring line in a mixed run, and the other covers a provided selection whose service period diverges from the engine's canonical result for that same line
-
(2026-03-17) Contract wizard draft/resume regression coverage now locks cadence-owner and partial-period defaults through both reread and re-save seams, which closes
F144and addsT337/T338:packages/billing/tests/draftContractForResumeActions.test.tsnow provesgetDraftContractForResume(...)returnscadence_owner,enable_proration, and the fixed recurring base rate together for a resumed recurring draft instead of only partially rehydrating those defaultspackages/billing/tests/contractWizardResume.test.tsxnow drives a resumed fixed-fee wizard back throughSave Draftand proves the emitted draft payload keeps the originalcadence_ownerwhile preserving a toggled partial-period setting- no production code changed in this checkpoint; the draft/resume path was already behaving correctly, but it did not yet have an end-to-end regression guard covering both the action reread and the resumed save-draft emit path
-
(2026-03-17) Template-authored cadence-owner defaults now persist and clone through the real template storage layer, which closes
F138,T119, andT120:server/migrations/20260317213000_add_cadence_owner_to_contract_template_lines.cjsaddscontract_template_lines.cadence_owner, backfills it from matchingcontract_lineswhen possible, and defaults remaining legacy rows to'client'packages/billing/src/repositories/contractLineRepository.tsplusserver/src/lib/repositories/contractLineRepository.tsnow read, snapshot, update, and clone template-linecadence_ownerinstead of dropping it to'client'during template-to-contract instantiationpackages/billing/src/actions/contractWizardActions.tsnow liftscadence_ownerfrom any recurring template/draft line, not only the fixed-fee branch, so hourly-only and usage-only template snapshots and resumed drafts keep their authored cadence defaults- adjacent package/server readers that still hardcoded template cadence to
'client'were aligned as part of the same checkpoint:packages/billing/src/actions/contractLineAction.ts,packages/billing/src/actions/contractLineMappingActions.ts,packages/billing/src/models/contractLineMapping.ts,packages/billing/src/models/contractTemplate.ts, andserver/src/lib/api/services/ContractLineService.ts - focused coverage now lives in
server/src/test/unit/billing/templateLineCadenceOwner.persistence.test.tsandpackages/billing/tests/templateCadenceOwnerRoundTrip.actions.test.ts, while the existing draft/template cadence-owner suites continue to pass on top of the storage change
-
(2026-03-17)
billing_cycle_alignmentis now non-executing on the migrated fixed recurring runtime path, which closesF116:packages/billing/src/lib/billing/billingEngine.tsno longer selects, defaults, or propagatescontract_lines.billing_cycle_alignmentduring recurring fixed-charge execution; coverage settlement is now driven only by canonical service-period timing plusenable_prorationserver/src/test/unit/billing/billingEngine.timing.test.tsclosesT162with an explicit invariance check showingstart,end, andproratedlegacy values all emit the same canonical fixed recurring charge and no longer appear on the generated charge payload- compatibility storage and authoring surfaces intentionally remain in place for later staged-deprecation work (
F137+); this checkpoint only removes live execution dependence, not the legacy column itself
-
(2026-03-17) Client-portal invoice preview now preserves canonical recurring detail metadata end to end, which closes
T088without pretending broader portal-reader cutover is done:packages/billing/src/lib/adapters/invoiceAdapters.tsnow carriesservice_period_start,service_period_end, andbilling_timingfrom DB invoice-charge payloads intoWasmInvoiceViewModel.itemspackages/client-portal/src/components/billing/ClientInvoicePreview.servicePeriods.test.tsxproves the portal preview path receives those canonical recurring fields through the real adapter, so preview/pay-style rendering no longer strips them before template rendering- this is a preview/read-model seam only; broader dashboard/portal invoice-query cleanup still belongs to
F126andF140
-
(2026-03-17) Recurring invoice preselection now has an explicit mixed-invoice regression guard, which closes
T089:server/src/test/unit/billing/invoiceGeneration.recurringSelection.test.tsnow provescalculateBillingForInvoiceWindow(...)still returns non-recurring hourly/usage charges untouched while applying canonicalrecurringTimingSelectionsonly to the recurring timing seam- this keeps the service-period-first preselection contract narrow: it chooses due recurring service periods first, but it does not narrow the eventual invoice payload to recurring charges only
-
(2026-03-17) Obsolete recurring timing helpers are now deleted from the live billing engine, which closes
F117,T163, andT164:packages/billing/src/lib/billing/billingEngine.tsno longer carries the deadresolveServicePeriod,_calculateProrationFactor, orapplyProrationToPlanhelpers, andcalculateBillingInternal(...)no longer runs a no-op fixed-charge proration pass before combining invoice charges- focused timing/product/license tests now assert the old helper is absent rather than spying on it, and
server/src/test/unit/billing/billingEngine.cleanupSource.test.tsadds a direct source contract proving the late-stage helper strings are gone from the runtime file - the old helper-only regression file
server/src/test/unit/billing/billingEngine.prorationExclusive.test.tswas removed because its only purpose was to test a private implementation seam that no longer exists
-
(2026-03-17) Pass-0 implementation artifacts now live in:
ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/PASS0_RECURRING_TIMING_APPENDIX.mdee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/pass-0-source-inventory.jsonserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts
-
(2026-03-17) Shared recurring-timing primitives now live in:
packages/types/src/interfaces/recurringTiming.interfaces.tsshared/billingClients/recurringTiming.tspackages/billing/src/lib/billing/recurringTiming.tsserver/src/test/unit/billing/recurringTiming.domain.test.ts
-
(2026-03-17) Client-cadence service-period generation now lives in:
shared/billingClients/clientCadenceServicePeriods.tsserver/src/test/unit/billing/clientCadenceServicePeriods.domain.test.ts
-
(2026-03-17) The first checkpoint intentionally made the plan executable:
- source-backed file inventories now cover
resolveServicePeriod,billing_cycle_alignment, persisted service-period readers, and downstream recurring timing consumers - a docs contract test now fails if the pass-0 appendix drifts from live grep-backed source references
- source-backed file inventories now cover
-
(2026-03-17) The second checkpoint intentionally kept the new timing domain additive and pure:
- canonical service periods and invoice windows are distinct types with explicit
kindmarkers and shared[start, end)semantics - cadence-owner defaults are centralized at
DEFAULT_CADENCE_OWNER = 'client' - activity-window intersection, coverage calculation, due-window mapping, and invoice-detail timing projection are defined without invoice creation side effects
- canonical service periods and invoice windows are distinct types with explicit
-
(2026-03-17) Client-cadence service-period generation now preserves current anchor behavior while separating two cases explicitly:
- when no historical cycle is authoritative, generation returns the anchored period containing the requested date
- when a historical cycle ends exactly at the next cursor, generation treats that cursor as an authoritative transition boundary and moves forward without reaching back into pre-cutover periods
-
(2026-03-17) Shared recurring timing resolution now has one additive seam for the next cutover:
selectDueServicePeriodsForInvoiceWindowchooses due service periods from a current invoice window without rebuilding separate advance vs arrears period definitionsresolveRecurringSettlementsForInvoiceWindowintersects those due periods with line activity dates, returns covered periods, and computes one coverage ratio for fixed, product, and license families- zero-coverage periods are filtered before any family-specific charge math runs, which is the intended bridge away from bespoke proration and skip branches
-
(2026-03-17) The first fixed-recurring runtime cutover now consumes a shared timing helper instead of calling
resolveServicePerioddirectly:BillingEngine.calculateFixedPriceChargesnow resolves due periods viaresolveFixedRecurringChargeTiming, which builds canonical current/previous service periods and intersects them with the line activity window- an initial bug surfaced immediately in unit tests: subtracting raw day counts broke monthly arrears across month-length changes (
2025-02-01had incorrectly mapped back to2025-01-04); the helper now subtracts billing-cycle calendar units instead - the old arrears-specific skip/clamp branch is gone from the fixed path because empty coverage now returns
nullbefore charge generation - the advance termination credit branch is still live when fixed-proration remains disabled; that is the next incomplete seam (
F044+), not part of this checkpoint
-
(2026-03-17) The next fixed-recurring checkpoint removed the remaining advance-only special case:
BillingEngine.calculateFixedPriceChargesno longer emits separate negative advance termination credit rows throughbuildAdvanceTerminationCredits- fixed custom-rate, pricing-schedule override, and FMV allocation paths now flow through one post-generation settlement step when canonical coverage must scale amounts
- advance final periods without fixed-proration enabled now reuse the canonical coverage ratio with legacy net-of-unused rounding, so the resulting single positive charge matches prior net billing without carrying a synthetic arrears credit row
- this checkpoint also closed two previously hidden seams in the fixed path: custom-rate override charges and pricing-schedule override charges had been bypassing all shared post-processing after an early return
-
(2026-03-17) Fixed tax behavior is now regression-covered on the canonical timing path:
- taxable FMV allocations on a partial advance final period now have explicit unit coverage proving net tax amounts are preserved after coverage settlement
- service-level tax regions remain stable when one service provides an explicit region and another falls back to the client default; both still route through the same fixed recurring settlement path
-
(2026-03-17) Fixed recurring persistence now closes the remaining custom-rate and assignment-metadata seams:
- custom-rate and pricing-schedule override fixed plans now emit detail-backed fixed charges with
config_id,serviceId,client_contract_line_id, andclient_contract_idinstead of collapsing into a parent-only charge that could not write canonicalinvoice_charge_details - the legacy edge path for fixed-config services on non-
Fixedplan types now preserves contract assignment metadata, which keeps PO-scoped grouping and downstream invoice persistence from losingclient_contract_id - existing fixed-detail persistence tests continue to prove canonical
service_period_start,service_period_end, andbilling_timingare written without subtotal drift once charges reachpersistFixedInvoiceCharges
- custom-rate and pricing-schedule override fixed plans now emit detail-backed fixed charges with
-
(2026-03-16)
packages/billing/src/lib/billing/billingEngine.tscurrently mixes several timing models:resolveServicePeriod- advance vs arrears branching
- fixed-fee proration in one path
- product/license proration later in a different path
-
(2026-03-16)
billing_cycle_alignmentis surfaced across schema, repositories, APIs, and UI, but the execution model is not cleanly organized around it. This makes it a strong cleanup target once service periods are explicit. -
(2026-03-16) Current invoice timing tests are effectively asserting service-period behavior already, especially in
server/src/test/integration/billingInvoiceTiming.integration.test.ts. -
(2026-03-16) The recurring-billing mental model shown to users today still leans on “enable proration for mid-month starts,” which is a symptom of implicit rather than canonical service periods.
-
(2026-03-17) The blast radius is substantially wider than the first draft plan represented. Additional impacted surfaces identified during replan:
- invoice generation and recurring billing runs
- invoice detail persistence and billed-through calculations
- credits, prepayment, and negative-invoice flows
- purchase-order and pricing-schedule dependencies
- accounting exports and service-period consumers
- client portal invoice/plan details
- reporting actions/definitions
- repositories/models/schemas/test helpers
-
(2026-03-17) Second-pass agent findings tightened the highest-risk seams:
billingCycleIdis still the true execution identity in recurring runs and job handlers- invoice read models still hydrate mostly parent
invoice_charges, not canonical detail rows - export preview still derives service periods from invoice headers in some paths
- repository and model write paths normalize or silently drop timing fields inconsistently
- template and preset authoring paths are still not guaranteed to propagate future cadence semantics
-
(2026-03-17) Materialization adds a new class of risk the plan must own explicitly:
- generation horizon
- override provenance
- billed-period locking
- regeneration after contract edits
- future-period edit operations and conflict handling
-
(2026-03-17) The plan should explicitly avoid silently dragging time/usage into v1, while still specifying their compatibility boundaries and non-goals.
-
(2026-03-17) Resolve the v1
cadence_ownerpersistence question in favor ofcontract_lines.cadence_owner.- Rationale: live recurring timing is already line-scoped on
contract_lines(billing_frequency,billing_timing), whileclient_contractsonly provides assignment windows and template lines remain a later authoring/defaults surface.
- Rationale: live recurring timing is already line-scoped on
-
(2026-03-17) Fixed recurring metadata preservation now has an explicit persistence regression guard:
packages/billing/src/services/invoiceService.tswas already carryingclient_contract_idon grouped detailed fixed charges, but the consolidated parentinvoice_chargesinsert dropped that assignment field entirelyserver/src/test/unit/billing/invoiceService.fixedPersistence.test.tsnow reproduces the issue without a database and verifies that canonicalinvoice_charge_detailsperiods andinvoice_charge_fixed_details.allocated_amountstay aligned with the parent fixed charge subtotalpackages/billing/src/services/invoiceService.tsnow persistsclient_contract_idon consolidated fixed parent rows, preserving assignment metadata for downstream invoice readers and PO-related invoice flowsserver/src/test/unit/billing/billingEngine.timing.test.tsnow adds explicit fixed-recurring parity coverage for full-period, mid-start, and mid-end monthly client-cadence scenariosserver/src/test/integration/billing/contractPurchaseOrderSupport.integration.test.tsnow includes a PO + fixed-detail persistence regression test, but local execution remains blocked by the current Postgres socket permissions
-
(2026-03-17) Bucket overage timing now maps onto explicit allowance periods instead of invoice windows:
packages/billing/src/lib/billing/billingEngine.tsnow groupsbucket_usagerows byperiod_start+period_endand emits one bucket charge per explicit usage period- bucket charge
servicePeriodStartandservicePeriodEndnow follow the persisted bucket period boundaries, which is the first concrete definition forF051 - tax-date evaluation for bucket overages now uses the allowance period end date instead of the enclosing invoice window end, which keeps the charge aligned with the bucket period it represents
server/src/test/unit/billingEngine.test.tsnow covers multi-period bucket usage inside one invoice window and asserts that the emitted bucket charges preserve the two distinct service periods
-
(2026-03-17) Bucket rollover calculation now follows the same client-cadence boundary source in both writer paths:
packages/billing/src/services/bucketUsageService.tsnow prefersclient_billing_cyclesfor the active allowance period and, when rollover is enabled, uses the previous client billing cycle before falling back to anchor mathpackages/scheduling/src/services/bucketUsageService.tsnow mirrors that same client-cycle-aware rollover logic so time-entry writes and billing writes cannot materialize differentbucket_usageperiods for the same recurring obligationserver/src/test/unit/billing/bucketUsageService.periods.test.tsandpackages/scheduling/tests/bucketUsageService.periods.test.tsnow lock the regression with a monthly client-cadence rollover scenario that would previously have reached back to the contract start anchor instead of the prior billing cycle
-
(2026-03-17) Bucket overage invoice-window attachment is now explicitly covered on the service-period-first path:
packages/billing/src/lib/billing/billingEngine.tscontinues to selectbucket_usagerows by the active billing window, but the focused regression now proves that the emitted charge keeps the persisted allowance period instead of collapsing back to invoice-window datesserver/src/test/unit/billing/billingEngine.bucketTiming.test.tsnow covers both the explicit service-period mapping and the invoice-window filter that keeps February allowance usage on the February invoice window instead of rebilling it elsewhere
-
(2026-03-17) Fixed recurring pricing schedules now evaluate against the canonical due service period, not the enclosing invoice window:
packages/billing/src/lib/billing/billingEngine.tsnow resolves the due service-period[start, end)boundaries first and uses those exclusive boundaries when selectingcontract_pricing_schedulesfor fixed recurring chargesserver/src/test/unit/billing/billingEngine.discountPricingTiming.test.tsproves the intended arrears behavior explicitly: a January schedule still wins for a February invoice window when that invoice is billing the January service period, while a schedule starting exactly on February 1 is excluded from that January service period
-
(2026-03-17) Discount applicability now follows canonical recurring service periods when charge timing has already migrated:
packages/billing/src/lib/billing/billingEngine.tsnow builds per-contract-line discount evaluation windows from emitted chargeservicePeriodStart/servicePeriodEndvalues and uses those windows to filter discount overlap in-memory after a broader candidate query- the query still falls back to invoice-window overlap when no canonical charge service period exists for a contract line, which keeps non-migrated or non-recurring paths on current behavior
server/src/test/unit/billing/billingEngine.discountPricingTiming.test.tsnow proves the intended arrears case explicitly: a January discount applies to the February invoice window when the fixed recurring line is billing the January service period, while February/March discounts do not
-
(2026-03-17) Fixed recurring PO association now has an executable unit guard in addition to the blocked DB integration:
server/src/test/unit/billing/invoiceService.fixedPersistence.test.tsnow proves that detail-backed fixed recurring persistence keepsclient_contract_idon the consolidated parent invoice line, which is the assignment linkage PO-scoped readers and invoice selection rely on- the existing DB-backed regression in
server/src/test/integration/billing/contractPurchaseOrderSupport.integration.test.tsremains valuable, but local execution is still blocked by current Postgres permissions
-
(2026-03-17) Fixed recurring tax-date evaluation now follows the canonical service period instead of the enclosing invoice window:
packages/billing/src/lib/billing/billingEngine.tsnow passesservicePeriodEndinto fixed recurring tax calculations for both the main FMV-allocation path and the legacy fixed-service edge pathserver/src/test/unit/billing/billingEngine.timing.test.tsnow asserts the tax service sees2025-01-20for a partial advance final period instead of the enclosing invoice window end, which closesF056
-
(2026-03-17) Negative recurring fixed totals and credit-generating allocations are now explicit, not implied by an inline comment:
server/src/test/unit/billing/billingEngine.timing.test.tsnow proves a net-negative fixed recurring charge keeps its canonical service period metadata and that a mixed positive/negative FMV allocation can emit one negative detailed charge without breaking the settled period boundaries- no runtime code change was needed for
F057; the checkpoint was to turn previously implicit support into an executable contract before product/license migration reuses the same settlement model
-
(2026-03-17) The out-of-scope boundary for time, usage, and non-recurring bucket readers is now explicit in the plan artifacts:
ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/PASS0_RECURRING_TIMING_APPENDIX.mdnow spells out exactly which time-entry and usage-record behaviors stay event-driven in v1, and which bucket behaviors are in scope versus still deferredserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow enforces those explicit phrases soT060fails if the appendix drifts back toward vague scope language
-
(2026-03-17) A follow-up attempt to close
F058uncovered a local-environment blocker rather than a product behavior decision:- a targeted
prepaymentInvoice.test.tsregression was drafted to prove prepayment credit application does not strip canonical recurring detail rows, but local execution is blocked byECONNREFUSEDto Postgres on127.0.0.1:5432 - that regression was intentionally not left in the tree or marked complete;
F058remains open until the DB-backed path can be executed and verified
- a targeted
-
(2026-03-17) Prepayment-applied recurring invoice rereads now preserve canonical service-period metadata without waiting on the blocked DB path:
packages/billing/src/models/invoice.tsnow reattachesservice_period_start,service_period_end, andbilling_timingfrominvoice_charge_detailswhen invoice charges are reread, so applying prepayment credit no longer collapses migrated recurring timing back to parent-only invoice rowspackages/types/src/interfaces/invoice.interfaces.tsandserver/src/interfaces/invoice.interfaces.tsnow expose those recurring detail fields onIInvoiceCharge, which is the minimum shared contract needed for prepayment-applied invoice readersserver/src/test/unit/billing/invoiceModel.servicePeriods.test.tsclosesT096andT097with source-level reread coverage for one and multiple prepayment-applied recurring cycles- the DB-backed end-to-end prepayment flow is still unavailable locally because Postgres is not listening on
127.0.0.1:5432;T100remains open for the broader integration seam
-
(2026-03-17) Manual invoice create/update flows now reread through the same canonical invoice reader contract used by recurring invoices:
packages/billing/src/actions/manualInvoiceActions.tsnow returnsInvoice.getFullInvoiceById(...)after manual invoice creation and update instead of hand-buildinginvoice_chargesdirectly frominvoice_chargesrows- this keeps manual flows on the same post-persist projection path as recurring invoices while still leaving manual lines periodless because
persistManualInvoiceCharges(...)does not createinvoice_charge_details server/src/test/unit/billing/invoiceModel.servicePeriods.test.tsnow closesT075with a mixed recurring-plus-manual reread contract proving canonical service periods attach only to recurring detail-backed lines and do not leak onto manual adjustments- a targeted
manualInvoice.test.tsintegration slice was attempted for this checkpoint, but the suite currently aborts before execution because its shared@alga-psa/authmock omitswithAuth; the checkpoint is therefore defended by the focused unit reread test plus a package TypeScript compile
-
(2026-03-17) Purchase-order follow-up is still open after recon:
server/src/test/unit/billing/contractPurchaseOrderSupport.ui.test.tsxis stale againstAutomaticInvoices, which now callsgenerateInvoicesAsRecurringBillingRuninstead of the oldergenerateInvoiceaction the test spies on- integration coverage for PO + recurring detail persistence already exists in
server/src/test/integration/billing/contractPurchaseOrderSupport.integration.test.ts, but local execution is still blocked by unavailable Postgres - that leaves
F059implementable, but not as the next low-risk checkpoint; product-path inventory (F061) is cleaner to land first
-
(2026-03-17) Purchase-order recurring safeguards are now regression-covered on the current UI execution path:
server/src/test/unit/billing/contractPurchaseOrderSupport.ui.test.tsxnow importsAutomaticInvoicesdirectly, mocksgenerateInvoicesAsRecurringBillingRun, and proves PO overage confirmations still gate batch and preview generation on the recurring-run path rather than the older per-cyclegenerateInvoiceactionserver/src/test/unit/billing/contractPurchaseOrderSupport.poBanner.ui.test.tsxnow importsPurchaseOrderSummaryBannerdirectly so the banner contract stays executable without pulling unrelated package-entry dependencies into the test graph- no runtime code change was needed for
F059; the recurring PO behavior and PO banner rendering were already correct, but the stale tests were no longer proving it after the recurring-run action cutover T099remains open because the broader credits-plus-PO interaction still depends on DB-backed coverage outside this UI regression seam
-
(2026-03-17) Purchase-order consumption and selection now stay stable when recurring invoices carry applied credits, which closes
F131:packages/billing/src/services/purchaseOrderService.ts#getPurchaseOrderConsumedCents(...)now sums finalized PO consumption astotal_amount - credit_appliedinstead of rawtotal_amount, so recurring invoices that are partially or fully settled by credits no longer overstate PO usage during overage checks- aggregate PO consumption now floors at zero after summing net invoice amounts, which preserves the existing behavior that negative-credit corrections can reduce prior PO consumption but cannot drive the contract below zero consumed cents overall
packages/billing/tests/purchaseOrderService.creditConsumption.test.tsnow proves both sides of that contract: credit-applied recurring invoices reduce consumed PO cents before overage checks, and aggregate consumption cannot go negative when credits outweigh prior billed totals- the existing recurring-run PO UI regressions in
server/src/test/unit/billing/contractPurchaseOrderSupport.ui.test.tsxandserver/src/test/unit/billing/contractPurchaseOrderSupport.poBanner.ui.test.tsxremain green on top of the new service behavior, which is the executable closure forT099
-
(2026-03-17) Tax-source and external-tax consumers are now explicitly guarded against recurring service-period drift, which closes
F132:packages/billing/src/services/invoiceService.tsnow carries explicit comments on theexternalandpending_externaltax branches stating that imported tax remains amount-authoritative even when recurring detail periods are present elsewhere on the invoicepackages/billing/src/actions/taxSourceActions.tsnow makes the complementary rule explicit for finalization: tax gating follows import state (tax_source) rather than recurring service-period metadata- focused regression coverage now lives in
packages/billing/tests/invoiceService.externalTax.servicePeriods.wiring.test.tsandpackages/billing/tests/taxSourceActions.servicePeriods.wiring.test.ts, which lock those tax/import/reconciliation seams away from accidental service-period coupling
-
(2026-03-17) Recurring product timing now has an explicit migration target:
packages/billing/src/lib/billing/billingEngine.tsstill constructs product charges against the enclosing invoice window, stampsservicePeriodStart/servicePeriodEndfrombillingPeriod.startDate/billingPeriod.endDate, and calculates initial tax frombillingPeriod.endDateBillingEngine.calculateBillingstill runsapplyProrationToPlan(...)onproductChargesafter the charges are built whenenable_prorationis truePASS0_RECURRING_TIMING_APPENDIX.md,pass-0-source-inventory.json, andserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow treat that combination as the source-backedF061/T061seam to remove duringF062+
-
(2026-03-17) Recurring product charges now follow canonical service-period timing under client cadence:
packages/billing/src/lib/billing/billingEngine.tsnow resolves product due periods through the same shared recurring timing helper used by fixed recurring charges, so productservicePeriodStart/servicePeriodEndnow reflect the canonical due service period instead of the enclosing invoice window- product coverage settlement now happens inside
calculateProductCharges(...), which removes the late-stageapplyProrationToPlan(...)branch for products fromcalculateBillingwhile preserving the existingenable_prorationgate and rounding behavior - product tax evaluation now uses the canonical due service-period end date, and product charge tests now explicitly cover client-cadence timing (
T062), override precedence (T063), and tax-date behavior (T064)
-
(2026-03-17) Recurring license timing is still a source-backed inventory seam rather than a real runtime migration:
packages/billing/src/lib/billing/billingEngine.tsstill leaves licenses on the late-stageapplyProrationToPlan(...)branch incalculateBillingcalculateLicenseCharges(...)still contains the unresolved license-source TODO and, if populated, would still stampperiod_start,period_end,servicePeriodStart, andservicePeriodEndfrom the enclosing invoice window while evaluating tax frombillingPeriod.endDatePASS0_RECURRING_TIMING_APPENDIX.md,pass-0-source-inventory.json, andserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow treat that as the explicitF065/T065seam to remove beforeF066+
-
(2026-03-17) Recurring license timing now follows canonical service-period timing under client cadence:
packages/billing/src/lib/billing/billingEngine.tsnow routes both product and license recurring charges through one shared quantity-charge helper that starts fromresolveRecurringChargeTiming(...), so licenses no longer depend on the late-stageapplyProrationToPlan(...)branch incalculateBilling- license selection is now explicit and source-backed: recurring products exclude
service_catalog.is_license = true, while recurring licenses requireservice_catalog.item_kind = 'product'plusis_license = true, which closes the earlier placeholder TODO without changing contract-line authoring storage - license coverage settlement now uses
applyQuantityChargeCoverageSettlement(...)inside the canonical timing path, and license tax now evaluates from the canonical due service-period end date instead of the enclosing invoice window server/src/test/unit/billing/billingEngine.licenseTiming.test.tsnow locksT066,T067, andT068, and the pass-0 appendix/inventory contract test was refreshed so the plan artifacts describe the migrated product/license timing status instead of the old placeholder seam
-
(2026-03-17) Product and license persistence now keep canonical detail metadata without forcing fixed-detail tax semantics onto non-fixed charges:
packages/billing/src/services/invoiceService.tsnow inserts oneinvoice_charge_detailsrow for each recurring product or license parent charge, preservingservice_period_start,service_period_end, andbilling_timingfor non-fixed recurring linescalculateAndDistributeTax(...)now treats only parents with matchinginvoice_charge_fixed_detailsrows as consolidated tax carriers, so product/license detail metadata can exist without making tax distribution skip their parent invoice rowsserver/src/test/unit/billing/invoiceService.fixedPersistence.test.tsnow locksT069andT070alongside the existing fixed-detail regression coverage
-
(2026-03-17) The next boundary after
F070is no longer a local billing-engine seam:packages/billing/src/actions/invoiceGeneration.tsstill derives work frombilling_cycle_id, reads one billing cycle window, and callsBillingEngine.calculateBilling(client_id, cycleStart, cycleEnd, billing_cycle_id)directlypackages/billing/src/actions/recurringBillingRunActions.tsstill treats a recurring run as a list of raw billing-cycle IDs, soF071+will need broader action/scheduler identity changes rather than another contained engine-only patch
-
(2026-03-17) Invoice generation now has an explicit due-service-period preselection seam before charge-family execution:
packages/billing/src/lib/billing/billingEngine.tsnow supports an additiverecurringTimingSelectionsoption oncalculateBilling(...)and exposesselectDueRecurringServicePeriodsForBillingWindow(...), which precomputes one canonical timing selection per recurring contract line from the current billing window before fixed, product, and license charge paths runcalculateBillingInternal(...)now builds those selections once per invoice window and threads them into the recurring charge families, so invoice generation no longer relies on each family rediscovering due periods independently inside its own pathpackages/billing/src/actions/invoiceGeneration.tsnow routes preview, PO-overage inspection, and invoice generation throughcalculateBillingForInvoiceWindow(...), making the action-layer contract explicit: select due recurring service periods first, then calculate billing with those selections- direct unit tests that call private recurring helpers still work because the migrated fixed/product/license helpers now fall back to
getBillingCycle(...)when no preselected billing cycle is provided; that preserves old test harness signatures while keeping live execution on the new preselection seam
-
(2026-03-17) Invoice headers and downstream invoice-generation hooks remain anchored to the invoice window after recurring details move onto canonical service periods:
packages/billing/src/actions/invoiceGeneration.tsalready persistedbilling_period_start/billing_period_endfromcycleStart/cycleEnd;server/src/test/unit/billing/invoiceGeneration.headerPeriods.test.tsnow turns that into an executable contract against a recurring charge whose canonicalservicePeriodStart/servicePeriodEndare different- the same unit contract now proves
createInvoiceFromBillingResult(...)still drives the existing downstream draft/finalization path: persisted-charge subtotal calculation, tax distribution, invoice total update, and invoice-generated analytics all continue to run on the service-period-first path without reinterpreting the invoice header dates from recurring detail periods - the test harness surfaced one subtle implementation detail that is now explicit in the regression: invoice header billing-period fields are stored as
Temporal.PlainDatevalues before insert, not raw strings, but they still stringify back to the invoice-window dates exactly
-
(2026-03-17) Empty recurring due-work selection now has an explicit action-layer contract:
server/src/test/unit/billing/invoiceGeneration.emptyResult.test.tsclosesT077by provinggenerateInvoice(...)returnsnullwhenselectDueRecurringServicePeriodsForBillingWindow(...)yields no due recurring work,calculateBilling(...)returns zero charges / zero final amount, and zero-dollar suppression is enabled- the regression also proves the new due-period preselection seam still receives the expected invoice window bounds before the action exits on the empty-result path, which keeps
F071and the future duplicate/billed-through work (F080) grounded in one explicit contract
-
(2026-03-17) Automatic recurring runs now have an executable delegation contract for the service-period-first path:
server/src/test/unit/billing/recurringBillingRunActions.test.tsprovesgenerateInvoicesAsRecurringBillingRun(...)still fans out each selectedbillingCycleIdthroughgenerateInvoice(...), which is now the action path that performs due-service-period preselection before billing calculation- the test also keeps the recurring-run workflow events stable on that migrated path, so automatic invoice generation remains wired through the same started/completed event contract while recurring timing semantics move underneath
generateInvoice(...) - no runtime code change was required for
F074; the new seam landed ingenerateInvoice(...)duringF071, and this checkpoint locks the automatic-run delegation onto that migrated action path
-
(2026-03-17) Preview generation now has an executable service-period-first contract without widening the preview payload shape yet:
server/src/test/unit/billing/invoiceGeneration.preview.test.tsprovespreviewInvoice(...)constructs aBillingEnginepreview throughcalculateBillingForInvoiceWindow(...), so preview generation now preselects due recurring service periods before calculating recurring charges- the preview regression intentionally keeps the current payload surface stable: preview totals and line items remain the same, but the underlying recurring charge source is now canonical service-period selection rather than per-family ad hoc timing math
- the "generate from preview" UI path already rides
generateInvoicesAsRecurringBillingRun(...), soF074plus the existing preview-dialog UI checks are the current guard that single-cycle generation stays on the same migrated execution path
-
(2026-03-17) The pass-0 source inventory needed a maintenance refresh after the last billing-engine/unit-test checkpoints:
pass-0-source-inventory.jsonnow includes the newbilling_cycle_alignmentreference inserver/src/test/unit/billing/billingEngine.discountPricingTiming.test.ts- the persisted-service-period reader inventory now also includes
server/src/test/integration/billing/contractPurchaseOrderSupport.integration.test.ts,server/src/test/unit/billing/billingEngine.bucketTiming.test.ts,server/src/test/unit/billing/billingEngine.discountPricingTiming.test.ts, andserver/src/test/unit/billing/invoiceService.fixedPersistence.test.ts
-
(2026-03-17) Negative-invoice and credit-application recurring safeguards are now locked as executable contracts without needing another runtime patch:
server/src/test/unit/billing/invoiceModel.servicePeriods.test.tsnow closesT091,T094, andT095by proving partial credit-applied recurring invoices, negative recurring invoices, and negative-source-plus-positive-target rereads all keep canonicalinvoice_charge_detailsservice periods attached to recurring linesserver/src/test/unit/billing/billingEngine.timing.test.tsnow closesT078by proving advance duplicate-prevention checks use the canonical service-period end date (2025-01-31) instead of the enclosing invoice-window end (2025-02-01), which is the billed-through seamF080depends on- no production code changed for
F077; the existing runtime behavior already preserved these paths, but the plan had no focused regression contract proving it after the service-period-first cutover - a targeted DB-backed run of
negativeInvoiceCredit.test.tsis still blocked locally because its suite-level@alga-psa/core/secretsmock no longer exportsgetSecret, so this checkpoint uses focused unit coverage instead of widening that unrelated harness repair
-
(2026-03-17) Credit expiration and reconciliation now have focused recurring regression coverage, but
F078is still not fully closed:server/src/test/unit/billing/creditReconciliation.servicePeriods.test.tsnow closesT092andT093by proving two core invariants on the service-period-first path:- a negative-invoice credit that is fully consumed by a later credit application still reconciles to
remaining_amount = 0without any dependency on invoice-header timing - an expired negative-invoice credit still expires from
transactions.expiration_date/credit_tracking.expiration_date, marks the tracking row expired, and creates the same reconciliation-report signal when the client balance has not yet been corrected
- a negative-invoice credit that is fully consumed by a later credit application still reconciles to
- this checkpoint intentionally stops short of flipping
F078because the broader credit-reader (T098) and DB-backed integration (T100) seams are still open
-
(2026-03-17) Contract wizard cadence-owner capture now reaches the live recurring create/resume UI instead of stopping in action-layer types:
packages/billing/src/components/billing-dashboard/contracts/ContractWizard.tsxnow carriescadence_ownerinContractWizardData, defaults new wizards to'client', preserves the value when template snapshots are applied, and includes it inbuildSubmissionData()packages/billing/src/components/billing-dashboard/contracts/wizard-steps/FixedFeeServicesStep.tsxnow renders the same business-language cadence-owner chooser used by the fixed contract-line editor, with the contract-anniversary option visibly staged but disabled during rolloutpackages/billing/tests/contractWizardResume.test.tsxnow proves both sides of the wizard contract: new recurring lines submitcadence_owner: 'client', and resumed draft flows keep a storedcadence_ownervalue across step transitionspackages/billing/tests/contractWizardCadenceOwner.wiring.test.tsnow locks the source-level seams so future edits cannot silently drop cadence owner from wizard defaults, template snapshot application, or fixed-fee UI copy
-
(2026-03-17) Template-authored recurring defaults now carry cadence owner instead of silently dropping it between wizard state and stored template lines:
packages/billing/src/components/billing-dashboard/contracts/template-wizard/TemplateWizard.tsxnow defaults template wizard state tocadence_owner: 'client', keeps that value through reset paths, and submits it inbuildSubmissionData()packages/billing/src/components/billing-dashboard/contracts/template-wizard/steps/TemplateFixedFeeServicesStep.tsxnow renders the staged cadence-owner chooser in business language so template authors see the same client-schedule versus contract-anniversary framing as live contract authorspackages/billing/src/actions/contractWizardActions.tsnow persistscadence_owner: submission.cadence_owner ?? 'client'on all template-authored contract lines created by the wizard, which keeps future template snapshot and clone flows from losing the default at write timepackages/billing/tests/templateWizardBucketOverlay.test.tsandpackages/billing/tests/templateWizardCadenceOwner.wiring.test.tsnow prove the default submits as'client'and that the UI/state/write seams stay source-backed
-
(2026-03-17) Fixed recurring authoring copy now explains partial-period coverage without teaching proration as the primary timing workaround:
packages/billing/src/components/billing-dashboard/contracts/wizard-steps/FixedFeeServicesStep.tsx,packages/billing/src/components/billing-dashboard/contract-lines/FixedContractLineConfiguration.tsx,packages/billing/src/components/billing-dashboard/contract-lines/FixedContractLinePresetConfiguration.tsx,packages/billing/src/components/billing-dashboard/ContractLineDialog.tsx, andpackages/billing/src/components/billing-dashboard/contracts/CreateCustomContractLineDialog.tsxnow describeenable_prorationas scaling a recurring fee to covered service-period time instead of as a mid-cycle workaroundpackages/billing/src/components/billing-dashboard/contracts/wizard-steps/ReviewContractStep.tsxnow reflects that wording back to the user asPartial-Period Adjustmentpackages/billing/src/components/billing-dashboard/service-configurations/FixedServiceConfigPanel.tsxnow explains billing-cycle alignment as a legacy coverage-calculation choice rather than a generic proration togglepackages/billing/src/components/billing-dashboard/Overview.tsx,packages/billing/src/components/billing-dashboard/contracts/QuickStartGuide.tsx, andpackages/billing/src/components/billing-dashboard/BillingCycles.tsxnow describe client billing schedules, invoice windows, and service periods in service-period-first languagepackages/billing/tests/fixedRecurringPartialPeriodCopy.wiring.test.tsandpackages/billing/tests/billingDashboardRecurringCopy.wiring.test.tslock those phrases so the old “enable proration for mid-month starts” guidance does not drift back in unnoticed
-
(2026-03-17) Cadence-owner persistence is now explicit in live code, not only in domain types:
server/migrations/20260317170000_add_cadence_owner_to_contract_lines.cjsaddscontract_lines.cadence_ownerwith default/backfill to'client'plus a check constraint forclient|contractpackages/types/src/interfaces/billing.interfaces.ts,packages/types/src/interfaces/contract.interfaces.ts,server/src/interfaces/billing.interfaces.ts, andserver/src/interfaces/contract.interfaces.tsnow exposecadence_owneron live contract-line and mapping interfacesserver/src/lib/repositories/contractLineRepository.ts,shared/billingClients/contractLines.ts,packages/clients/src/actions/clientContractLineActions.ts,packages/billing/src/actions/contractLineAction.ts, andpackages/billing/src/lib/billing/billingEngine.tsnow read/writecadence_ownerfromcontract_linesand default missing legacy rows to'client'- template lines were intentionally left out of the new schema in this checkpoint; repository reads still default template-backed responses to
'client', but persistence on template authoring remains follow-on work (F093+,F138+,F212+)
-
(2026-03-17) Server API cadence-owner handling now matches the live contract-line storage decision:
server/src/lib/api/schemas/contractLineSchemas.tsandserver/src/lib/api/schemas/financialSchemas.tsnow acceptcadence_owneronly asclient|contracton contract-line and client-contract-line request surfaces and require a valid cadence owner on line response schemasserver/src/lib/api/services/ContractLineService.tsnow defaults create/get/update responses to'client'when legacy rows lack the field, while still persisting explicitcadence_ownervalues on live contract-line writes- template/billing-settings API surfaces were intentionally not widened in this checkpoint because v1 persistence is still line-scoped on
contract_lines; later authoring/default surfaces stay queued behindF086+andF121+
-
(2026-03-17) Billing-package authoring actions now keep live cadence-owner semantics on the remaining contract-line creation paths:
packages/billing/src/actions/contractWizardActions.tsnow threadssubmission.cadence_owner ?? 'client'into all four live recurring line creates insidecreateClientContractFromWizard(...), andgetDraftContractForResume(...)/getContractTemplateSnapshotForClientWizard(...)now surface the fixed-line cadence owner back to the UI while defaulting missing legacy values to'client'packages/billing/src/actions/contractLinePresetActions.tsnow acceptscadence_owneronCreateCustomContractLineInputand preset-copy overrides, so custom recurring lines and preset-instantiated live lines no longer drop cadence ownership at the action layer- template authoring storage remains intentionally out of scope for this checkpoint; template snapshot reads expose cadence owner for compatibility, but no template write path claims persistence until the later template-default features land (
F093+,F138+,F212+) packages/billing/tests/contractLinePresetCadenceOwner.actions.test.ts,packages/billing/tests/contractWizardCadenceOwner.wiring.test.ts, andpackages/billing/tests/draftContractForResumeActions.test.tsnow closeT106with a mix of action execution and focused wiring/readback coverage- the existing draft-resume permission test was also corrected to assert the action's real
permissionError(...)return shape instead of expecting a thrown exception; that was harness drift, not a product behavior change
-
(2026-03-17) Recurring timing fixtures now have one shared cadence-owner-aware test seam, which closes
F087:server/src/test/test-utils/recurringTimingFixtures.tsnow exports shared recurring obligation, service-period, invoice-window, and monthly recurring fixture builders with stable defaults forcadenceOwner,duePosition, and charge familyserver/src/test/unit/billing/recurringTiming.domain.test.tsnow consumes those shared builders and closesT107with an explicit client-vs-contract cadence fixture contract instead of inline one-off setupserver/test-utils/billingTestHelpers.tsnow acceptscadenceOwneroncreateFixedPlanAssignment(...)and persists it tocontract_lines.cadence_owner, so DB-backed invoice and credit fixtures can opt into contract-owned cadence later without rewriting helper internalsserver/src/test/unit/billing/billingTestHelpers.recurringFixtures.test.tslocks that DB-helper seam with a focused source contract so cadence owner cannot silently fall back out of shared fixture setup
-
(2026-03-17) Staged-rollout validation now blocks contract cadence writes before contract-owned runtime support exists, which closes
F088:shared/billingClients/cadenceOwnerRollout.tsnow defines one shared rollout guard message and helper so schema and service layers reject the same unsupported cadence-owner state consistentlyserver/src/lib/api/schemas/contractLineSchemas.tsandserver/src/lib/api/schemas/financialSchemas.tsnow reject authoring payloads that setcadence_owner: 'contract', while still allowing stored/read-model response payloads to exposecadence_owner: 'contract'server/src/lib/api/services/ContractLineService.tsnow enforces the same staged-rollout guard on create/update paths, so API callers cannot bypass schema validation and persist contract cadence early- this keeps the persistence/model plumbing from
F081-F087intact for later rollout phases while making the current rollout posture explicit: client cadence is the only supported live write path until contract-cadence execution exists
-
(2026-03-17) Legacy cadence-owner compatibility is now explicit across package, client-action, and server-service read/write seams, which closes
F089:packages/billing/src/repositories/contractLineRepository.tsnow defaults missing live-linecadence_ownerreads to'client', persists'client'when cloning template lines into live contract lines, and backfills'client'on touched legacy updates instead of leavingcontract_lines.cadence_ownerunsetpackages/billing/src/actions/contractLineMappingActions.tsandpackages/billing/src/models/contractLineMapping.tsnow normalize legacy mapping payloads so template and live contract-line association flows returncadence_owner: 'client'when older rows do not yet carry the column, and touched live association writes preserve/backfill that defaultserver/src/lib/api/services/ContractLineService.tsnow normalizeslist,listWithOptions,getById, andsetPlanActivationresponses through one cadence-owner compatibility helper and persists'client'on touched legacy update paths when callers omit cadence ownerpackages/clients/src/actions/clientContractLineActions.tsnow preserves the existing cadence owner when editing client contract lines and backfills'client'when legacy rows are edited without an explicit cadence owner, instead of letting writes keep the column null
-
(2026-03-17)
billing_cycle_alignmentnow has an explicit staged-deprecation seam instead of remaining an unconditional required write input, which closesF090:shared/billingClients/billingCycleAlignmentCompatibility.tsnow defines one compatibility resolver for legacy alignment values, preserving explicit stored values when present and deriving a readable fallback fromenable_prorationwhen new writes omit the fieldserver/src/lib/api/schemas/contractLineSchemas.tsnow allows fixed-plan config create payloads to omitbilling_cycle_alignment, which stops new API writes from treating the legacy field as mandatory while leaving response payloads readablepackages/billing/src/models/contractLineFixedConfig.tsnow uses the compatibility resolver on reads and on update/upsert writes, so fixed-config writes can omit the field without losing a readable legacy alignment valueserver/src/lib/api/services/ContractLineService.tsand both server/package contract-line repositories now route fixed-config and template-clone alignment handling through the same compatibility resolver rather than hard-coding'start'at each seam
-
(2026-03-17) The first live recurring contract-line configuration surface now presents cadence ownership in business language, which closes
F091:packages/billing/src/components/billing-dashboard/contract-lines/FixedContractLineConfiguration.tsxnow shows a dedicatedCadence Ownersection withInvoice on client billing scheduleandInvoice on contract anniversarybusiness labels instead of leaking internal field names into the UI- the contract-anniversary option is currently rendered but disabled with rollout copy, which keeps the UI aligned with
F088's server-side contract-cadence guard instead of implying that the unsupported path is already available - saves now carry the selected cadence-owner value through
updateContractLine(...), so the fixed recurring line editor remains the first coherent live configuration surface for cadence-owner semantics before wizard/template flows are updated
-
(2026-03-17) Credit-reader invoice context now stays on canonical recurring detail metadata, which closes
F078without pretending the blocked DB integration is done:packages/billing/src/actions/creditActions.tsnow loads source invoices throughInvoice.getById(...)for bothgetCreditDetails(...)and the invoice-summary enrichment insidelistClientCredits(...), instead of rereading rawinvoicesrows that dropped recurringinvoice_charge_details- the credit list path now exposes
invoice_service_period_start/invoice_service_period_endsummary fields derived from hydrated recurring invoice charges, so credit-management screens and support tooling keep stable recurring period context even after credit issuance or application server/src/test/unit/billing/creditActions.servicePeriods.test.tsclosesT098with one focused contract covering both the summary list path and the detailed credit reader against a recurring negative-invoice sourceT100remains open because the broader DB-backed credit/prepayment integration seam is still blocked locally and is shared withF058/F077
-
(2026-03-17) Recurring billing workflow-event metadata is now explicitly locked on the service-period-first path, which closes
F079:server/src/test/unit/billing/recurringBillingRunActions.test.tsnow closesT079by proving the started/completed recurring billing run events keep stablerunId, actor metadata, tenant correlation metadata, and completion counts even after recurring invoice generation moved to due-service-period preselection- no production code change was needed for this checkpoint; the value was converting an already-correct event contract into an executable guard before broader scheduler-identity work (
F151+)
-
(2026-03-17) Duplicate prevention and rerun idempotency now have an explicit action-layer contract, which closes
F080:packages/billing/src/actions/invoiceGeneration.tsnow throws an explicit duplicate error contract (code = DUPLICATE_RECURRING_INVOICE) when a billing cycle already has an invoice, instead of surfacing the misleading legacy"No active contract lines for this period"messagepackages/billing/src/actions/recurringBillingRunActions.tsnow treats that duplicate error as an idempotent skip during reruns, so automatic recurring runs do not mark already-billed cycles as failures while still refusing to create a second invoiceserver/src/test/unit/billing/invoiceGeneration.duplicate.test.tsclosesT086by proving the direct generate action blocks the second invoice for the same due window with the explicit duplicate error payloadserver/src/test/unit/billing/recurringBillingRunActions.test.tsclosesT082by proving reruns skip duplicate cycles without incrementingfailedCountserver/src/test/unit/billing/billingEngine.timing.test.tsclosesT087by proving an already-persisted advance service period suppresses rebilling even when the enclosing invoice-window metadata is later, which keeps billed-through enforcement anchored to canonical service periods
-
(2026-03-17) Client billing schedule UI and preview flows now frame client cadence as one cadence-owner option instead of the only recurring timing source, which closes
F096:packages/billing/src/components/billing-dashboard/BillingCycles.tsxnow explains that the dashboard billing-schedule table previews invoice windows for client-cadence recurring lines only, while contract-anniversary lines can follow their own cadence outside this surfacepackages/clients/src/components/clients/ClientBillingSchedule.tsxnow describes the per-client billing schedule as the client-cadence path, adds preview copy clarifying that the dialog shows client-cadence invoice windows, and explicitly states that contract-anniversary lines are previewed separately at the line levelpackages/billing/tests/billingDashboardRecurringCopy.wiring.test.tsandserver/src/test/unit/billing/ClientBillingSchedule.ui.test.tsxnow closeT117, including a small harness repair so the client-schedule UI test imports the real package component and mocks its actualbillingHelpersdependency
-
(2026-03-17) Client portal invoice and contract-line readers now surface recurring timing metadata instead of flattening it away, which closes
F097:packages/client-portal/src/components/billing/InvoiceDetailsDialog.tsxnow renders canonical recurringservice_period_start/service_period_endranges andbilling_timingbadges directly on invoice line items, so client-visible invoice details keep the recurring coverage window instead of only showing pricing fieldspackages/client-portal/src/actions/client-portal-actions/client-billing.tsnow selectsbilling_timingandcadence_ownerfor the client contract-line reader, andpackages/client-portal/src/components/billing/ContractLineDetailsDialog.tsxnow shows cadence-owner and billing-timing labels with a short recurring service-period explanationpackages/client-portal/src/components/billing/InvoiceDetailsDialog.servicePeriods.test.tsxandpackages/client-portal/src/components/billing/ContractLineDetailsDialog.servicePeriods.test.tsxnow closeT121andT122
-
(2026-03-17) Client-portal billing metrics and recent invoice readers now keep canonical recurring period meaning instead of flattening back to invoice-header-only semantics, which closes
F130:packages/client-portal/src/actions/client-portal-actions/dashboard.tsnow aggregates recent invoice activity throughinvoice_charge_details, so invoice activity descriptions prefer canonical recurringservice_period_start/service_period_endranges when detail rows exist and fall back cleanly for historical invoices- the same dashboard action now formats invoice totals as currency in recent activity descriptions instead of printing raw cents, which keeps the new service-period explanation readable for client users
packages/client-portal/src/actions/client-portal-actions/client-billing.ts#getCurrentUsage(...)now uses explicit[period_start, period_end)bucket-period filtering (period_start <= currentDate < period_end) instead of the old inclusiveBETWEENpredicate, aligning portal billing-overview usage with the canonical recurring boundary modelpackages/client-portal/src/actions/client-portal-actions/client-billing-metrics.ts#getClientBucketUsage(...)was already period-aware;packages/client-portal/src/actions/client-portal-actions/client-billing.bucketPeriods.test.tsnow locks the remaining-units projection so portal bucket reporting stays tied to the explicit allowance period it represents- focused regression coverage now lives in
packages/client-portal/src/actions/client-portal-actions/dashboard.recurringServicePeriods.test.ts,packages/client-portal/src/actions/client-portal-actions/client-billing.bucketPeriods.test.ts, plus the existing detail/preview reader tests inpackages/client-portal/src/components/billing/*.servicePeriods.test.tsx
-
(2026-03-17) Contract reporting now treats recurring YTD revenue as a service-period-based fact instead of an invoice-header-date fact, which closes
F098:packages/billing/src/actions/contractReportActions.tsnow aggregates contract revenue YTD frominvoice_charge_details.service_period_endwhen canonical recurring detail rows exist, while falling back toinvoices.invoice_datefor historical/manual rows that still lack detail periods- the fixed/product/license persistence assumptions are now explicit in reporting code: fixed detail-backed rows sum
invoice_charge_fixed_details.allocated_amount, while current non-fixed recurring parents still rely on the existing one-detail-per-parent write contract packages/reporting/src/lib/reports/definitions/contracts/revenue.tsnow matches that runtime behavior with a raw-SQL metric that uses canonical recurring service-period dates instead of invoice headers when detail rows existpackages/reporting/src/lib/reports/definitions/contracts/expiration.tsandpackages/billing/src/components/billing-dashboard/reports/ContractReports.tsxnow clarify the reporting date basis in business language so contract-expiration reporting stays assignment-based while revenue reporting is explicitly service-period-based- focused regression coverage now lives in
server/src/test/unit/contractReportActions.recurringServicePeriodBasis.test.ts,packages/reporting/src/lib/reports/definitions/contracts/revenue.servicePeriods.test.ts, andpackages/billing/tests/ContractReports.revenueCopy.wiring.test.ts
-
(2026-03-17) Contract reporting summary and adjacent report surfaces now make the clarified date basis visible instead of leaving it implicit, which closes
F129:packages/billing/src/components/billing-dashboard/reports/ContractReports.tsxnow renders the already-computedatRiskDecisionCountas a fourth summary card (Renewal Decisions Due), so the summary surface exposes renewal-decision timing alongside the recurring YTD service-period metricserver/src/test/unit/contractReportActions.summary.servicePeriods.test.tsnow provesgetContractReportSummary()combines canonical recurringservice_period_endYTD totals with decision-due-date risk counts, instead of silently reverting the summary to invoice-header timingserver/src/test/unit/contractReportActions.expiration.servicePeriods.test.tsnow proves contract expiration rows remain assignment-end and renewal-decision based, independent of recurring invoice service-period projectionpackages/reporting/src/lib/reports/definitions/contracts/expiration.servicePeriods.test.ts,packages/reporting/src/actions/reconciliationReportActions.servicePeriods.test.ts, andpackages/billing/tests/ContractReports.summaryCopy.wiring.test.tsnow lock the remaining reporting seams: expiration remains assignment-based, reconciliation remains discrepancy-based, and the dashboard summary copy surfaces the new semantics explicitly
-
(2026-03-17) Accounting export line selection now preserves canonical recurring service periods instead of falling back to invoice header windows, which closes
F099:packages/billing/src/services/accountingExportInvoiceSelector.tsnow joinsinvoice_charge_details, collapses multiple detail rows back to one export preview line per charge, and prefers canonical detail-levelservice_period_start/service_period_endoverinvoices.billing_period_start/billing_period_end- export preview and batch creation still preserve a safe fallback for historical/manual charges that do not have canonical detail rows yet, so export lines remain perioded without forcing detail backfills
- this makes the downstream adapter contract explicit instead of accidental: QuickBooks Online service dates and Xero payload service-period metadata now continue to consume the selector's canonical line periods rather than invoice headers
server/src/test/integration/accounting/invoiceSelection.integration.test.tsnow seedsinvoice_charge_detailsfor the multi-period export scenario so the intended DB-backed regression is in place, but local execution remains blocked byECONNREFUSEDto Postgres on127.0.0.1:5438- executable local coverage for the same behavior now lives in
server/src/test/unit/accounting/accountingExportInvoiceSelector.servicePeriods.test.ts,packages/billing/tests/accountingExportInvoiceSelector.servicePeriods.wiring.test.ts, andpackages/billing/tests/accountingExportAdapters.servicePeriods.wiring.test.ts
-
(2026-03-17) Invoice template preview samples now carry canonical recurring service periods end to end, which closes
F133:packages/billing/src/utils/sampleInvoiceData.tsnow seeds recurring sample charges withservice_period_start,service_period_end,billing_timing, and multi-periodrecurring_detail_periodsinstead of leaving preview/demo invoices periodlesspackages/billing/src/utils/sampleInvoicePreview.tsnow centralizes sample-to-renderer mapping so preview/demo flows preserve canonical recurring period fields rather than dropping them inInvoiceTemplateManagerpackages/billing/src/components/billing-dashboard/InvoiceTemplateManager.tsxnow initializes its sample preview state withuseEffectand the shared mapper, which removes the old render-time state setter and keeps recurring sample metadata intactpackages/billing/src/components/invoice-designer/preview/sampleScenarios.tsnow includes canonicalservicePeriodStart,servicePeriodEnd,billingTiming, andrecurringDetailPeriodson preview demo items so designer previews can render the same recurring metadata shape as live invoice previewspackages/billing/tests/sampleInvoicePreview.test.tsclosesT130with source-backed coverage for both manager-backed sample invoices and designer preview sample scenarios
-
(2026-03-17) Remaining cadence-owner helper seams are now explicit in test utilities and migration fixtures, which closes
F134:server/src/test/test-utils/pricingScheduleHelpers.tsnow exportsbuildRecurringPricingScheduleFixture(...), a shared cadence-owner-aware pricing-schedule fixture builder that composes the canonical monthly recurring fixture and exposes the resulting invoice window, service periods, and pricing-schedule dates in one placeserver/migrations/utils/client_owned_contracts_simplification.cjsnow preserves explicitcadence_ownervalues and backfills legacy-null clone fixtures to'client', so migration clone-plan tests no longer depend on incidental object spreads to keep recurring cadence semanticsserver/src/test/unit/billing/pricingScheduleHelpers.recurringFixtures.test.tsandserver/src/test/unit/migrations/clientOwnedContractsSimplificationMigration.test.tsnow lock those helper seams with client-cadence and contract-cadence fixture coverage
-
(2026-03-17) Cadence-owner backfill verification now has an explicit non-mutation contract, which closes
F135andT110:server/src/test/unit/billing/contractLineCadenceOwner.persistence.test.tsnow asserts the20260317170000_add_cadence_owner_to_contract_lines.cjsmigration never referencesinvoices,invoice_charges, orinvoice_charge_details, so the backfill stays scoped tocontract_lines- the same test still executes the migration against fake legacy
contract_linesrows and proves the only mutation is nullcadence_ownerbackfill to'client', which makes the “safe for untouched tenants” claim explicit instead of only implied by earlier parity checks
-
(2026-03-17) Mixed-cadence rollout validation is now explicit across package actions, API schemas, and UI copy, which closes
F136,T143,T144, andT145:shared/billingClients/cadenceOwnerRollout.tsnow uses one explicit rollout message that names both contract-owned cadence and mixed-cadence billing as staged, so every layer rejects the same unsupported state with the same wordingpackages/billing/src/actions/contractLineAction.ts,packages/billing/src/actions/contractLinePresetActions.ts, andpackages/billing/src/actions/contractWizardActions.tsnow all callassertSupportedCadenceOwnerDuringRollout(...)before persisting live, preset-backed, or wizard-authored contract cadence writesserver/src/test/unit/api/contractLineCadenceOwner.schema.test.tsnow proves both create and update client-line schema writes reject contract cadence during rollout, whileserver/src/test/unit/api/contractLineService.cadenceOwner.test.tscontinues to guard the API service layerpackages/billing/tests/contractLinePresetCadenceOwner.actions.test.tsandpackages/billing/tests/cadenceOwnerRollout.actions.wiring.test.tsnow lock the package action layer, andpackages/billing/tests/fixedContractLineConfiguration.cadenceOwner.ui.test.tsxnow proves the UI explains the staged mixed-cadence block instead of only disabling the option silently
-
(2026-03-17) Fixed-config model/service compatibility now closes the remaining staged-deprecation seam for
billing_cycle_alignment, which closesF137andT161:packages/billing/src/models/contractLineFixedConfig.tsnow routes delete/reset behavior throughresolveBillingCycleAlignmentForCompatibility(...)instead of hard-coding a raw alignment value, so every fixed-config write path uses the same compatibility resolverserver/src/lib/api/services/ContractLineService.tsnow resolves legacy alignment through the compatibility helper when creating fixed-plan configs and when copying fixed config between plans, which removes the last direct?? 'start'fallback from the service layerpackages/billing/tests/billingCycleAlignmentCompatibility.model.test.tsandserver/src/test/unit/api/contractLineService.billingCycleAlignmentCompatibility.wiring.test.tsnow prove fixed-config writes and copies can omitbilling_cycle_alignmentwhile still keeping a readable compatibility value
-
(2026-03-17) Internal recurring-timing docs now describe the live service-period-first model and rollout defaults, which closes
F100:shared/billingClients/recurringTiming.tsnow carries a module-level architecture reference that spells out the current runtime truth chain: cadence owner -> service periods -> invoice windows -> invoice detail persistence, plus the staged default ofclientcadencepackages/reporting/src/actions/report-actions/README.mdnow documents the reporting date-basis policy for the current rollout: recurring report actions should prefer canonical service-period detail fields when present, while historical/manual rows may still fall back to invoice dates- focused docs contract coverage now lives in
packages/billing/tests/recurringTiming.architectureDocs.wiring.test.tsandpackages/reporting/src/actions/report-actions/README.servicePeriods.test.ts
-
(2026-03-17) Contract-owned cadence now has executable anniversary-based boundary definitions for monthly, quarterly, semi-annual, and annual recurring lines, which closes
F101throughF104:shared/billingClients/contractCadenceServicePeriods.tsnow defines calendar-unit contract cadence helpers anchored toanchorDate, which is treated as the assignment-start anniversary rather than a client billing schedule anchor- monthly, quarterly, semi-annual, and annual periods all compute each boundary from the original assignment anchor instead of chaining from the prior constrained date, which preserves anniversary intent across short months and longer calendar cycles
- this checkpoint remains intentionally domain-only: live writes, mixed-cadence selection, first/final invoice rules, and scheduler identity are still deferred to later contract-cadence features (
F106+) - the generator records
timingMetadata.anchorDateplusboundarySource = assignment_start_date, which makes the contract-anchor rule explicit without changing invoice selection behavior yet - focused regression coverage now lives in
server/src/test/unit/billing/contractCadenceServicePeriods.domain.test.ts, including the acceptance-shape8th-anchor scenarios for monthly, quarterly, semi-annual, and annual cycles plus the required-anchor guard
-
(2026-03-17) Contract-cadence lifecycle rules are now explicit for future starts and renewals, which closes
F105:shared/billingClients/contractCadenceServicePeriods.tsnow exportsresolveContractCadenceAnchorDate(...), which defines the rollout rule plainly: future-start assignments and renewed contracts anchor to the new assignment start, while renew-in-place preserves the prior anniversary anchor when one exists- monthly contract-cadence generation already honored the future-start boundary by not emitting pre-anchor service periods; this checkpoint makes that behavior intentional and names the renewal-policy seam instead of leaving it to caller convention
- focused regression coverage for those lifecycle rules now lives in
server/src/test/unit/billing/contractCadenceServicePeriods.domain.test.ts, which proves both the future-start no-prebilling behavior and the split between renew-in-place versus renewed-contract anchor resolution
-
(2026-03-17) Contract-cadence first/final invoice behavior is now explicit before mixed-cadence scheduler work, which closes
F106andF107:shared/billingClients/contractCadenceServicePeriods.tsnow exportsresolveContractCadenceInvoiceWindowForServicePeriod(...), which makes the missing policy explicit: contract cadence owns the due invoice window as well as the service-period boundary- for
advance, the first and final invoices stay on the same anniversary window as the due service period; client billing cycles do not pull those invoices back to the enclosing client cycle - for
arrears, the first and final invoices stay on the next anniversary window after the covered service period ends, including a partial final period caused by termination mid-period server/src/test/unit/billing/contractCadenceServicePeriods.domain.test.tsnow closesT137andT138by proving both due-position variants against a monthly8th-anchor scenario without enabling live contract-cadence execution yet
-
(2026-03-17) Mixed cadence grouping policy is now explicit at the shared-domain layer before scheduler identity changes, which closes
F108andF109:shared/billingClients/recurringTiming.tsnow exportsgroupDueServicePeriodsByInvoiceWindow(...), which groups due work by[start, end)invoice-window identity and carries the set of cadence owners on each group for explainability- the rule is now explicit: cadence owner alone does not force a split when client-owned and contract-owned due work lands on the same invoice window, but differing invoice windows always produce separate invoice candidates
- this checkpoint intentionally stops before
F110: live recurring runs still iterate rawbillingCycleIds, but later scheduler work can now build on a stable grouping contract instead of re-deciding the product rule server/src/test/unit/billing/recurringTiming.domain.test.tsnow closesT141andT142
-
(2026-03-17) Mixed-cadence live execution remains blocked on the current billing-cycle run identity:
packages/billing/src/actions/invoiceGeneration.tsandpackages/billing/src/actions/recurringBillingRunActions.tsstill model one recurring run input as onebillingCycleId/ one client invoice windowpackages/billing/src/lib/billing/billingEngine.tsstill builds live recurring timing selections around the current billing window, so mixed cadence that lands on different windows cannot be executed coherently until the later execution-identity work (F110,F151+)
-
(2026-03-17) Mixed-cadence selection is now deterministic on the current billing-cycle run path, which closes
F110without claiming the later scheduler rewrite:packages/billing/src/lib/billing/billingEngine.tsnow branches recurring timing selection bycadence_owner, using client-cadence periods forclientlines and the shared contract-cadence generator plus contract-owned invoice-window matching forcontractlines- on the current
billingCycleId-driven run path, contract-owned lines are billed only when their contract-owned due window exactly matches the active invoice window; differing contract windows are skipped instead of being pulled into the enclosing client billing cycle - recurring timing selections are now built in stable
client_contract_line_idorder, andserver/src/test/unit/billing/billingEngine.timing.test.tsclosesT146by proving the same mixed-cadence result is produced regardless of input order - broader execution identity, background job payloads, and truly schedulable contract-cadence windows are still deferred to
F151+; this checkpoint only makes the existing run path deterministic when mixed cadence data is present
-
(2026-03-17) The rollout order is now explicit enough to close
F111andT151:PASS0_RECURRING_TIMING_APPENDIX.mdnow carries a dedicatedStaged Rollout Plansection with five concrete stages: additive groundwork, client-cadence parity comparison, client-cadence cutover, contract-cadence enablement, and cleanup/deletion- the rollout rule is now source-backed instead of implied: contract cadence stays blocked on live write paths through Stage 2 and Stage 3, and only becomes tenant-writable after client-cadence parity sign-off plus post-cutover stability checks
server/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow locks that rollout order asT151, so future edits cannot quietly reintroduce contract-cadence enablement ahead of client-cadence parity- while landing that docs checkpoint,
pass-0-source-inventory.jsonwas refreshed to match currentbilling_cycle_alignmentand persisted service-period reader references so the appendix contract suite is green again instead of hiding behind stale inventory drift
-
(2026-03-17) First-cutover guardrails now explicitly keep out-of-scope billing families out of recurring rollout comparison and preselection, which closes
F115:packages/billing/src/lib/billing/billingEngine.tsnow skipscontract_line_type = Hourly|Usagewhen precomputingrecurringTimingSelections, so the service-period-first preselection seam only targets recurring fixed/product/license-style lines instead of every contract linepackages/billing/src/actions/invoiceGeneration.tsnow compares only recurring-backed charges inRECURRING_BILLING_COMPARISON_MODE, filtering to canonical recurring charge families withclient_contract_line_idbefore building drift snapshots- that means time entries, usage records, and client-scoped material charges cannot produce false rollout drift warnings or appear to be part of the recurring hard cutover just because they share an invoice window with recurring work
- manual-only invoice creation was already isolated from recurring selection in
T084; this checkpoint keeps that existing boundary intact while tightening the live recurring engine and comparison seams
-
(2026-03-17) DB-backed client-cadence parity validation now closes
F112,T152, andT153:server/src/test/integration/billingInvoiceTiming.integration.test.tsnow proves both monthly and annual client-cadence recurring invoices keep the same mixed advance/arrears service periods and persisted invoice-window headers under the service-period-first engine- the integration fixture now creates a billing location with an email address and reuses one
client_contract_idfor mixed recurring lines, which keeps the test on the realgenerateInvoice(...)path without tripping unrelated billing-email or multi-contract invoice guards server/test-utils/billingTestHelpers.ts#createFixedPlanAssignment(...)now accepts optional existingcontractId/clientContractIdso DB-backed recurring tests can model multiple lines on one client contract instead of fabricating invalid multi-contract invoice fixtures- local DB validation is now running successfully against the Docker Postgres listener on
127.0.0.1:57433
-
(2026-03-17) Post-cutover DB-backed sanity coverage now reaches all currently migrated recurring families, which closes
F118plusT171-T174:server/src/test/integration/billingInvoiceTiming.integration.test.tsnow adds four focused end-to-end sanity cases on the realgenerateInvoice(...)path: monthly fixed, quarterly fixed, recurring product, and recurring license invoice generation all persist canonical recurring service periods under client cadence- the new product/license fixture helper intentionally uses the same contract-line tables as live recurring execution and marks catalog rows with
service_catalog.item_kind = 'product'plusis_license, so the integration tests exercise the migrated quantity-charge paths rather than a test-only shortcut - those DB-backed tests surfaced one real runtime seam instead of a harness issue: recurring product/license charges were not carrying
config_id, which madeinvoice_charge_details.config_idinserts fail on real persistence even though the unit tests had already proven the timing math packages/billing/src/lib/billing/billingEngine.ts,packages/billing/src/services/invoiceService.ts,packages/types/src/interfaces/billing.interfaces.ts, andserver/src/interfaces/billing.interfaces.tsnow threadconfig_idthrough recurring product/license charges and into detail persistence, which keeps canonical recurring detail rows writable for all migrated charge families
-
(2026-03-17) Operator/developer rollout guidance now has an executable runbook contract, which closes
F119andT160:ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RUNBOOK.mdnow provides concrete parity-check commands, DB-backed sanity validation, comparison-mode guidance, mixed-cadence troubleshooting steps, inspection SQL, and rollback postureserver/src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnow enforces that the runbook keeps those exact sections and command examples instead of drifting into non-actionable prose- refreshing the runbook contract also forced a pass-0 inventory refresh in
pass-0-source-inventory.json, which now matches the live grep-backed source after the recent cleanup and preview-reader checkpoints
-
(2026-03-17) The time-and-usage follow-on boundary is now explicit instead of remaining an unresolved v1 blur, which closes
F120:PRD.mdnow states that time-entry billing and usage-record billing remain event-driven even after recurring materialized service periods land, and that any unification requires a separate follow-on planPASS0_RECURRING_TIMING_APPENDIX.mdnow adds a dedicatedFollow-On Boundary — Full Time And Usage Unificationsection that spells out the trigger conditions for such a follow-on and the implementation questions it must answer before those domains can adopt canonical service periods
-
(2026-03-17) Billing-settings defaults now expose recurring cadence rollout metadata without pretending contract cadence is configurable yet, which closes
F121:packages/billing/src/actions/billingSettingsActions.tsnow returns additive recurring cadence defaults on both tenant and client billing-settings readers:defaultRecurringCadenceOwner = 'client', rollout stateclient_only, and the shared contract-cadence rollout-block message- the same action now rejects any attempted
defaultRecurringCadenceOwner = 'contract'write on update paths, so billing-settings surfaces cannot silently bypass the staged contract-cadence guard even though the settings shape now exposes the rollout posture explicitly packages/types/src/interfaces/billing.interfaces.ts,server/src/interfaces/billing.interfaces.ts, andserver/src/lib/api/schemas/financialSchemas.tsnow carry the same additive default/rollout metadata on billing-settings read models and API schemas, keeping the contract consistent wherever billing defaults are read
-
(2026-03-17) Client billing schedule anchor actions now expose explicit client-cadence scope metadata instead of leaving that meaning as UI-only copy, which closes
F122:shared/billingClients/clientCadenceScheduleContext.tsnow defines one shared client-cadence schedule context with explicit change-scope, schedule, and preview messaging for client-owned invoice windowsshared/billingClients/billingSchedule.tsandpackages/billing/src/actions/billingCycleAnchorActions.tsnow attach that context togetClientBillingCycleAnchor(...)and schedule-preview results, so action consumers can tell that these windows apply only to client-cadence recurring lines and that contract-anniversary lines are configured separatelypackages/clients/src/components/clients/ClientBillingSchedule.tsxnow renders those messages from the returned action contract instead of hardcoding the cadence-owner explanation locally, which keeps the preview flow aligned with explicit cadence ownership at the data-contract layer as well as the UI layer
-
(2026-03-17) Preset-backed recurring cadence defaults now persist on the preset itself instead of being implicit or alignment-adjacent, which closes
F123andT118:server/migrations/20260317193000_add_cadence_owner_to_contract_line_presets.cjsaddscontract_line_presets.cadence_ownerwith safe backfill to'client'plus aclient|contractcheck constraint, giving preset-backed recurring defaults their own storage surface instead of piggybacking onbilling_cycle_alignmentpackages/types/src/interfaces/billing.interfaces.ts,server/src/interfaces/billing.interfaces.ts, andpackages/billing/src/models/contractLinePreset.tsnow expose and normalize presetcadence_owner, defaulting missing legacy rows to'client'on reads and preserving an existing stored cadence owner on updates when callers omit the fieldpackages/billing/src/actions/contractLinePresetActions.tsnow copies preset-backed recurring lines usingpreset.cadence_owner ?? 'client'when no override is provided, so preset reuse reads an explicit cadence-owner default instead of inferring anything from fixed-configbilling_cycle_alignment
-
(2026-03-17) Contract-line mapping/disambiguation helpers now have an executable audit showing they no longer infer recurring timing from legacy fixed-config flags after the cadence-owner cutover, which closes
F124and addsT331:packages/billing/tests/contractLineMappingRecurringTiming.wiring.test.tsnow locks the intended boundary explicitly: after the template-snapshot seam, the mapping and disambiguation helpers must operate on explicitcadence_ownerfields and may not branch onbilling_cycle_alignmentorenable_proration- this checkpoint did not require a runtime patch because the mapping model/action code was already using explicit cadence-owner defaults; the missing work was the focused audit contract proving that the legacy fixed-config flags no longer participate in live mapping/disambiguation decisions
-
(2026-03-17) Full invoice-detail readers now expose canonical recurring period metadata more consistently instead of flattening everything down to one parent-level summary, which closes
F125andT090:packages/types/src/interfaces/invoice.interfaces.tsandserver/src/interfaces/invoice.interfaces.tsnow expose additiverecurring_detail_periodsonIInvoiceCharge, preserving the canonical detail rows behind a recurring parent charge without breaking existing summary fieldspackages/billing/src/models/invoice.tsnow attaches sortedrecurring_detail_periodsarrays to recurring invoice charges, keeps the existing aggregatedservice_period_start/service_period_end/billing_timingsummary for compatibility, and orders invoice-charge reads chronologically by recurring service period before falling back to original charge order for manual or non-perioded rows- that makes both
Invoice.getById(...)and the dashboard-facinggetInvoiceLineItems(...)path more stable: recurring invoice-detail queries can keep their canonical metadata while still presenting predictable line ordering to dialogs and editors
-
(2026-03-17) Renderer-facing invoice read paths now keep canonical recurring detail periods instead of collapsing them back to one flattened range, which closes
F126:packages/types/src/lib/invoice-renderer/types.tsandpackages/billing/src/lib/adapters/invoiceAdapters.tsnow carry additiverecurringDetailPeriodson rendered invoice items, deriving compatibility summary fields from those details only when needed so dashboard preview, client portal preview, PDF generation, and designer preview all keep the canonical detail structure availablepackages/client-portal/src/components/billing/ClientInvoicePreview.servicePeriods.test.tsxandpackages/billing/src/lib/adapters/invoiceAdapters.test.tsnow lock that projection contract explicitly, including the case where a recurring parent line has more than one canonical detail periodpackages/client-portal/src/components/billing/InvoiceDetailsDialog.tsxnow renders multiple recurring detail periods as an explicit list when present instead of always flattening back to one service-period badge, which keeps client-facing invoice reads aligned with the canonical detail hydration added inF125
-
(2026-03-17) Accounting export persistence now carries canonical recurring detail periods directly instead of reducing every export line to one legacy summary window, which closes
F127:packages/types/src/interfaces/accountingExport.interfaces.ts,packages/billing/src/repositories/accountingExportRepository.ts, andpackages/billing/src/actions/accountingExportActions.tsnow expose additiverecurring_detail_periodsmetadata on export preview lines and persistedAccountingExportLine.payload, giving repository and controller code one typed place to carry canonical recurring export detail without changing the existing summaryservice_period_start/service_period_endcolumnspackages/billing/src/services/accountingExportInvoiceSelector.tsnow selectsinvoice_charge_details.billing_timingalongside canonical start/end dates, emits sortedrecurringDetailPeriodson preview rows, and persists those exact periods into export-line payloads during batch creationserver/src/lib/api/controllers/ApiCSVAccountingController.tsnow forwards the same canonical recurring detail payload when CSV export creates batch lines through the shared export service, keeping manual CSV-triggered batches on the same detail-aware contract as the dashboard export flowserver/src/test/unit/accounting/accountingExportInvoiceSelector.servicePeriods.test.tsnow proves both seams: preview selection exposes canonical recurring detail periods, andcreateBatchFromFilters(...)persists them into the export-line payload that downstream adapters and rereads consume
-
(2026-03-17) Recurring billing workflow events now state their service-period-first selection basis explicitly instead of leaving it implicit in builder defaults, which closes
F128:shared/workflow/streams/domainEventBuilders/recurringBillingRunEventBuilders.tsnow defaults recurring billing run payloads toselectionMode = 'due_service_periods'andwindowIdentity = 'billing_cycle_window', making the current run contract explicit about how service-period-first billing still selects work during the billing-cycle-identity phaseshared/workflow/runtime/schemas/billingEventSchemas.tsnow requires those fields on recurring billing run started/completed/failed payloads, so schema consumers and future automation logic cannot treat the selection basis as an unstated conventionpackages/billing/src/actions/recurringBillingRunActions.tsnow passes the same explicit fields at every publish site instead of relying on implicit builder defaults, keeping the emitted workflow events stable if the builders evolve laterserver/src/test/unit/billing/recurringBillingRunWorkflowEvents.test.tsandserver/src/test/unit/billing/recurringBillingRunActions.test.tsnow lock both sides: schema validation and action-layer publish inputs
-
(2026-03-17) Comparison-mode rollout control now closes
F113andT154without changing live invoice persistence:packages/billing/src/actions/invoiceGeneration.tsnow treatsRECURRING_BILLING_COMPARISON_MODE=legacy-vs-canonicalas an additive action-layer gate oncalculateBillingForInvoiceWindow(...)- when enabled, the action runs the canonical preselected recurring path first, then executes one legacy-style billing calculation without
recurringTimingSelectionsand logs a structured drift warning if the comparison snapshot differs - the canonical billing result is always returned and persisted, so comparison mode can be enabled for rollout validation without mutating live invoice outputs
server/src/test/unit/billing/invoiceGeneration.recurringSelection.test.tsnow proves the guard calls both billing modes in order, emits drift logging, and still returns the canonical result object unchanged
-
(2026-03-17) Cadence-owner migration backfill is now explicitly validated, which closes
F114,T155, andT156:server/migrations/20260317170000_add_cadence_owner_to_contract_lines.cjswas already addingcadence_ownerwith a default of'client', but the migration contract had only been source-inspected before this checkpointserver/src/test/unit/billing/contractLineCadenceOwner.persistence.test.tsnow executes the migration against a fake Knex contract and proves the backfill writes'client'only to legacy null rows while preserving existing'client'and'contract'values- together with the DB-backed client-cadence parity validation from
F112, this makes the rollout claim concrete: untouched client-cadence tenants keep client defaults without invoice-output drift
-
(2026-03-17) Invoice reread stability now has an explicit regression guard, which closes
T080:server/src/test/unit/billing/invoiceModel.servicePeriods.test.tsnow proves thatInvoice.getById(...),Invoice.getFullInvoiceById(...), and a secondInvoice.getById(...)reread all preserve the same aggregated canonical service-period metadata for a multi-detail recurring charge- this keeps invoice reload paths from drifting even when one recurring parent charge spans multiple canonical detail periods
-
(2026-03-17) Preview payloads now surface canonical recurring period metadata instead of hiding it behind totals-only preview rows, which closes
T083:packages/billing/src/actions/invoiceGeneration.tsnow threadsservice_period_start,service_period_end, andbilling_timingfrom billing charges into preview invoice items and theWasmInvoiceViewModel.itemspayloadpackages/types/src/lib/invoice-renderer/types.tsnow exposes those preview-item fields as optional renderer inputs, keeping the preview contract additive for existing consumersserver/src/test/unit/billing/invoiceGeneration.preview.test.tsnow proves preview state carries the canonical recurring period range and timing badge data alongside the existing totals
-
(2026-03-17) Manual invoice creation now has an executable isolation guard, which closes
T084:server/src/test/unit/billing/manualInvoiceActions.recurringIsolation.test.tsexecutesgenerateManualInvoice(...)with focused mocks and proves the path persists manual items, returns the hydrated invoice, and never instantiates the recurringBillingEngineor calls due-service-period selection- this keeps the service-period-first recurring rollout from silently leaking into manual invoice creation while invoice readers and preview payloads grow more detail-aware
-
(2026-03-17) Manual invoice edit/view paths now have a focused canonical-period compatibility contract, which closes
T085:server/src/test/unit/billing/manualInvoiceActions.viewing.test.tsexecutesupdateManualInvoice(...), proves the action still deletes/replaces manual items and triggers recalculation, and confirms the returned reread invoice can still carry canonical recurring detail fields on its charge rows- this gives the current manual invoice edit/view seam executable coverage without waiting on the blocked DB-backed manual-invoice suite
-
(2026-03-17) Client-portal overview and payment surfaces now carry canonical recurring service-period summaries, which closes
F140,T333, andT334:packages/billing/src/actions/invoiceQueries.tsnow projects invoice-levelservice_period_start/service_period_endsummaries frominvoice_charge_detailsinto client-facingInvoiceViewModelreads, so portal overview/payment surfaces no longer have to infer recurring timing from invoice headers alonepackages/client-portal/src/actions/clientPaymentActions.tsnow returns the same canonical summary fields during payment verification, which lets the payment-success flow explain what recurring coverage was settled without rereading invoice headers or relying on proration-style copypackages/client-portal/src/components/billing/BillingOverviewTab.tsx,packages/client-portal/src/components/billing/InvoicesTab.tsx, andpackages/client-portal/src/components/billing/PaymentSuccessContent.tsxnow surface those canonical summaries in the overview, invoice-payment, and payment-success surfaces with explicit service-period-first wordingpackages/client-portal/src/components/billing/BillingOverviewTab.servicePeriods.test.tsxandpackages/client-portal/src/components/billing/PaymentSuccessContent.servicePeriods.test.tsxlock the two new portal UI seams, while the existing invoice detail/preview portal tests remain green on top of the new summary surfaces
-
(2026-03-17) Company-sync, accounting-mapping, and export-validation audit is now executable, which closes
F141,T335, andT336:packages/billing/src/services/companySync/companySyncService.tsandpackages/billing/src/services/accountingMappingResolver.tsremain intentionally period-agnostic; the audit now locks that by source contract instead of leaving it implicitpackages/billing/src/services/accountingExportValidation.tsnow makes the boundary explicit in code comments and carries canonicalservice_period_start/service_period_endmetadata through mapping, tax, realm, client-reference, and payment-term validation errors, so export triage keeps the line-level recurring timing context selected earlier in the export pipelinepackages/billing/tests/accountingExportValidation.servicePeriods.wiring.test.tsnow locks both sides of the audit: company sync + mapping stay free of header-period assumptions, and export validation continues to preserve canonical service-period context without falling back to invoice-header billing dates
Commands / Runbooks
-
(2026-03-18) DB-backed contract-cadence monthly and annual validation:
cd server && DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T276|T277" --hookTimeout 600000 --coverage.enabled false
-
(2026-03-18) Report-output client-cadence parity validation:
cd server && npx vitest run src/test/unit/contractReportActions.recurringServicePeriodBasis.test.ts --coverage.enabled false
-
(2026-03-18) Persisted service-period parity comparison validation:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodParity.domain.test.ts src/test/unit/billing/materializeClientCadenceServicePeriods.domain.test.ts src/test/unit/billing/materializeContractCadenceServicePeriods.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) Persisted service-period due-selection contract validation:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodDueSelection.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) Persisted service-period invoice linkage validation:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodInvoiceLinkage.domain.test.ts src/test/unit/migrations/recurringServicePeriodInvoiceLinkageMigration.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts ../packages/types/src/recurringServicePeriodRecord.typecheck.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) Persisted service-period lifecycle validation:
cd server && npx vitest run src/test/unit/billing/recurringServicePeriodLifecycle.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-18) Persisted service-period schema validation:
cd server && npx vitest run src/test/unit/migrations/recurringServicePeriodsMigration.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p server/tsconfig.json- still blocked only by the pre-existing
packages/billing/src/actions/creditActions.ts(1208,13)narrowing error, not by the recurring-service-period migration
- still blocked only by the pre-existing
-
(2026-03-18) Persisted service-period record contract validation:
cd server && npx vitest run src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts ../packages/types/src/recurringServicePeriodRecord.typecheck.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.json
-
(2026-03-17) Billing-timing default normalization validation:
npx vitest run src/test/unit/billing/recurrenceStorageModel.contract.test.ts src/test/unit/billing/templateLineCadenceOwner.persistence.test.ts ../packages/billing/tests/contractLinePresetCadenceOwner.model.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p server/tsconfig.json- still blocked only by the pre-existing
packages/billing/src/actions/creditActions.ts(1208,13)narrowing error, not by the billing-timing normalization changes
- still blocked only by the pre-existing
-
(2026-03-17) Recurrence storage model validation:
npx vitest run src/test/unit/billing/recurrenceStorageModel.contract.test.ts src/test/unit/billing/templateLineCadenceOwner.persistence.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts ../packages/billing/tests/contractLinePresetCadenceOwner.model.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p server/tsconfig.json- still blocked only by the pre-existing
packages/billing/src/actions/creditActions.ts(1208,13)narrowing error, not by the recurrence-storage-model changes
- still blocked only by the pre-existing
-
(2026-03-17) Template recurring authoring validation:
npx vitest run ../packages/billing/tests/templateWizardBucketOverlay.test.ts ../packages/billing/tests/templateWizardCadenceOwner.wiring.test.ts --coverage.enabled false- run from
server/so Vitest uses the workspace alias config for package billing tests
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Preset recurring authoring validation:
npx vitest run ../packages/billing/tests/contractLinePresetCadenceOwner.actions.test.ts ../packages/billing/tests/contractLinePresetRecurringAuthoring.wiring.test.ts --coverage.enabled false- run from
server/so Vitest uses the workspace alias config for package billing tests
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Recurring authoring policy validation:
npx vitest run src/test/unit/billing/recurringAuthoringPolicy.domain.test.ts src/test/unit/api/contractLineService.cadenceOwnerCompatibility.wiring.test.ts --coverage.enabled false- run from
server/
- run from
npx vitest run ../packages/billing/tests/contractWizardCadenceOwner.wiring.test.ts ../packages/billing/tests/contractLinePresetCadenceOwner.actions.test.ts ../packages/billing/tests/contractLineCadenceOwnerCompatibility.repository.test.ts --coverage.enabled false- run from
server/so Vitest uses the workspace alias config for package billing tests
- run from
npx vitest run ../packages/billing/tests/fixedContractLineConfiguration.recurringAuthoring.wiring.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p server/tsconfig.json- still fails only on the pre-existing
packages/billing/src/actions/creditActions.ts(1208,13)narrowing error, not on the recurring authoring policy changes
- still fails only on the pre-existing
-
(2026-03-17) Reporting date-basis policy validation:
npx vitest run src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled false- run from
server/
- run from
npx vitest run src/test/unit/contractReportActions.recurringServicePeriodBasis.test.ts src/test/unit/contractReportActions.summary.servicePeriods.test.ts --coverage.enabled false- run from
server/
- run from
npx vitest run src/actions/reconciliationReportActions.servicePeriods.test.ts --coverage.enabled false- run from
packages/reporting/
- run from
-
(2026-03-17) Financial-artifact fallback validation:
npx vitest run src/test/unit/billing/creditActions.servicePeriods.test.ts --coverage.enabled false- run from
server/
- run from
npx vitest run tests/creditManagement.financialArtifactContext.wiring.test.ts --coverage.enabled false- run from
packages/billing/
- run from
npx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Credit-note source-date-basis validation:
npx vitest run src/test/unit/billing/creditActions.servicePeriods.test.ts src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonDB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/infrastructure/billing/invoices/prepaymentInvoice.test.ts -t "T100" --coverage.enabled false- run from
server/; currently still blocked by migration-heavy harness drift in that infrastructure suite after setup reaches the targeted test
- run from
DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/infrastructure/billing/invoices/negativeInvoiceCredit.test.ts -t "T216" --coverage.enabled false- run from
server/; current local run is still unstable because the older negative-invoice infrastructure suite tears down during migration/test-context setup
- run from
DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/infrastructure/billing/invoices/negativeInvoiceCredit.test.ts -t "T216|T100" --coverage.enabled false- run from
server/; still blocked by202409071803_initial_schema.cjsmigration bootstrap failure (ROLLBACK - Connection terminated unexpectedly)
- run from
-
(2026-03-17) Billing dashboard invoice-generation copy validation:
npx vitest run ../packages/billing/tests/invoicingGenerateTab.recurringCopy.wiring.test.ts ../packages/billing/tests/billingDashboardRecurringCopy.wiring.test.ts --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package billing tests
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Template cadence-owner storage and clone validation:
npx vitest run src/test/unit/billing/templateLineCadenceOwner.persistence.test.ts --coverage.enabled false- run from
server/
- run from
npx vitest run ../packages/billing/tests/templateCadenceOwnerRoundTrip.actions.test.ts ../packages/billing/tests/draftContractForResumeActions.test.ts ../packages/billing/tests/templateWizardCadenceOwner.wiring.test.ts --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package billing tests
- run from
npx vitest run src/test/unit/billing/contractLineCadenceOwner.persistence.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p server/tsconfig.json- still blocked only by the pre-existing
packages/billing/src/actions/creditActions.ts:979narrowing error (IInvoice | nullvsnull), not by the template cadence-owner changes
- still blocked only by the pre-existing
-
(2026-03-17)
billing_cycle_alignmentruntime cleanup and portal preview validation:npx vitest run src/test/unit/billing/invoiceGeneration.recurringSelection.test.ts --coverage.enabled false- run from
server/
- run from
npx vitest run src/test/unit/billing/billingEngine.timing.test.ts -t "T162|T041|T042|T043|T044|T078|T087" --coverage.enabled false- run from
server/
- run from
npx vitest run src/test/unit/billing/billingEngine.cleanupSource.test.ts src/test/unit/billing/billingEngine.timing.test.ts src/test/unit/billing/billingEngine.productTiming.test.ts src/test/unit/billing/billingEngine.licenseTiming.test.ts src/test/unit/billing/invoiceGeneration.recurringSelection.test.ts --coverage.enabled false- run from
server/
- run from
npx vitest run src/test/unit/billingEngine.test.ts -t "T021" --coverage.enabled false- run from
server/
- run from
npx vitest run ../packages/client-portal/src/components/billing/ClientInvoicePreview.servicePeriods.test.tsx --coverage.enabled false- run from
server/
- run from
npx vitest run ../packages/billing/src/lib/adapters/invoiceAdapters.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/client-portal/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx vitest run src/test/unit/billing/billingEngine.timing.test.ts src/test/unit/billingEngine.test.ts --coverage.enabled false- broad run still exposes pre-existing failures in
server/src/test/unit/billingEngine.test.tsunrelated to this checkpoint (this.knex.selectharness drift and missing mocked client lookups in older pricing-schedule tests)
- broad run still exposes pre-existing failures in
-
(2026-03-16) Recon:
rg -n "resolveServicePeriod|applyProrationToPlan|_calculateProrationFactor|billing_cycle_alignment|billing_timing|client_billing_cycles" packages/billing/src/lib/billing server/src/test sharedsed -n '220,470p' packages/billing/src/lib/billing/billingEngine.tssed -n '2284,2595p' packages/billing/src/lib/billing/billingEngine.tssed -n '60,210p' server/src/test/integration/billingInvoiceTiming.integration.test.tssed -n '1,260p' shared/billingClients/createBillingCycles.ts
-
(2026-03-17) Replan breadth inventory:
rg -n "billing_timing|service_period_start|service_period_end|billing_cycle_alignment|client_billing_cycles|generateInvoice|calculateBilling|billing_period_start|billing_period_end|proration" packages server sharedfind server/src/test -maxdepth 3 -type f | rg "billing|invoice|contract|renewal|pricing|credit|report"find packages -maxdepth 5 -type f | rg "billing|invoice|contract|renewal|report|pricing|accounting|BillingCycles|ContractLine|contractWizard|manualInvoice|quickBooks|xero"
-
(2026-03-17) Second-pass critique prompts:
- runtime/dependent billing seams
- invoice/downstream consumer seams
- data model/API/UI/migration seams
-
(2026-03-17) Plan validation:
python3 /Users/roberisaacs/.codex/skills/alga-plan/scripts/validate_plan.py ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership
-
(2026-03-17) Pass-0 appendix validation:
npm --prefix server test -- src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts
-
(2026-03-17) Shared recurring-timing validation:
npm --prefix server test -- src/test/unit/billing/recurringTiming.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.tsnpx vitest run src/interfaces/barrel.test.ts --root packages/types
-
(2026-03-17) Client cadence validation:
npx vitest run src/test/unit/billing/clientCadenceServicePeriods.domain.test.ts src/test/unit/billing/recurringTiming.domain.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled false
-
(2026-03-17) Shared due-period/coverage validation:
npx vitest run src/test/unit/billing/recurringTiming.domain.test.ts --coverage.enabled falsenpx vitest run src/interfaces/barrel.test.ts --root packages/types
-
(2026-03-17) Fixed timing seam validation:
npx vitest run src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled falsenpx vitest run src/test/unit/billing/recurringTiming.domain.test.ts --coverage.enabled falsenpx vitest run src/test/integration/billingInvoiceTiming.integration.test.ts --coverage.enabled false- blocked locally by
ECONNREFUSEDto Postgres on127.0.0.1:5438
- blocked locally by
-
(2026-03-17) Fixed settlement follow-up validation:
npx vitest run src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled falsenpx vitest run src/test/unit/billing/recurringTiming.domain.test.ts --coverage.enabled falsenpx vitest run src/interfaces/barrel.test.ts --root packages/typesnpx vitest run src/test/unit/billingEngine.test.ts --coverage.enabled false- broad legacy unit file currently fails outside this seam because its harness no longer satisfies unrelated
BillingEnginequery expectations (this.knex.selectincalculateMaterialCharges, missing client lookup mocks in older pricing-schedule tests)
- broad legacy unit file currently fails outside this seam because its harness no longer satisfies unrelated
-
(2026-03-17) Fixed tax regression validation:
npx vitest run src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled false
-
(2026-03-17) Fixed detail persistence and metadata validation:
npx vitest run src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled falsenpx vitest run src/test/unit/billing/invoiceService.fixedPersistence.test.ts --coverage.enabled false
-
(2026-03-17) Fixed metadata persistence validation:
npx vitest run src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled falsenpx vitest run src/test/unit/billing/invoiceService.fixedPersistence.test.ts --coverage.enabled falsenpx vitest run src/test/integration/billing/contractPurchaseOrderSupport.integration.test.ts --coverage.enabled false- blocked locally by
EPERMto Postgres on127.0.0.1:5438and127.0.0.1:5432
- blocked locally by
-
(2026-03-17) Bucket period mapping validation:
npx vitest run src/test/unit/billingEngine.test.ts -t "calculate bucket overlay charges correctly|T056" --coverage.enabled false
-
(2026-03-17) Bucket rollover validation:
npx vitest run src/test/unit/billing/billingEngine.bucketTiming.test.ts src/test/unit/billing/bucketUsageService.periods.test.ts --coverage.enabled falsenpm test -- bucketUsageService.periods.test.ts
-
(2026-03-17) Bucket invoice-window validation:
npx vitest run src/test/unit/billing/billingEngine.bucketTiming.test.ts --coverage.enabled false
-
(2026-03-17) Fixed discount and pricing-schedule parity validation:
npx vitest run src/test/unit/billing/billingEngine.discountPricingTiming.test.ts --coverage.enabled false
-
(2026-03-17) Discount applicability and PO-association validation:
npx vitest run src/test/unit/billing/billingEngine.discountPricingTiming.test.ts src/test/unit/billing/invoiceService.fixedPersistence.test.ts --coverage.enabled false
-
(2026-03-17) Fixed tax-date and negative-recurring validation:
npx vitest run src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled false
-
(2026-03-17) Product-path inventory validation:
npx vitest run src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled false
-
(2026-03-17) Product recurring timing validation:
npx vitest run src/test/unit/billing/billingEngine.productTiming.test.ts --coverage.enabled falsenpx vitest run src/test/unit/billing/billingEngine.productTiming.test.ts src/test/unit/billing/billingEngine.timing.test.ts src/test/unit/billing/billingEngine.discountPricingTiming.test.ts --coverage.enabled false
-
(2026-03-17) License-path inventory validation:
npx vitest run src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled false
-
(2026-03-17) License recurring timing validation:
npx vitest run src/test/unit/billing/billingEngine.licenseTiming.test.ts src/test/unit/billing/billingEngine.productTiming.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled falsenpx vitest run src/interfaces/barrel.test.ts --root packages/types
-
(2026-03-17) Product/license detail persistence validation:
npx vitest run src/test/unit/billing/invoiceService.fixedPersistence.test.ts src/test/unit/billing/billingEngine.licenseTiming.test.ts src/test/unit/billing/billingEngine.productTiming.test.ts src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled false
-
(2026-03-17) Explicit out-of-scope boundary validation:
npx vitest run src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled false
-
(2026-03-17) Blocked prepayment follow-up:
npx vitest run src/test/infrastructure/billing/invoices/prepaymentInvoice.test.ts -t "T096" --coverage.enabled false- blocked locally by
ECONNREFUSEDto Postgres on127.0.0.1:5432
- blocked locally by
-
(2026-03-17) Prepayment recurring-detail reread validation:
npx vitest run src/test/unit/billing/invoiceModel.servicePeriods.test.ts src/test/unit/billing/invoiceService.fixedPersistence.test.ts --coverage.enabled falsenpx vitest run src/interfaces/barrel.test.ts --root packages/types
-
(2026-03-17) Manual/recurring reread validation:
npx vitest run src/test/unit/billing/invoiceModel.servicePeriods.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx vitest run src/test/infrastructure/billing/invoices/manualInvoice.test.ts -t "creates a manual invoice with single line item|correctly updates an invoice when new manual items are added" --coverage.enabled false- blocked locally by an existing Vitest module mock error:
@alga-psa/authis mocked withoutwithAuth, so the suite fails during module evaluation before the targeted tests run
- blocked locally by an existing Vitest module mock error:
-
(2026-03-17) Purchase-order UI safeguard validation:
npx vitest run src/test/unit/billing/contractPurchaseOrderSupport.ui.test.tsx src/test/unit/billing/contractPurchaseOrderSupport.poBanner.ui.test.tsx --coverage.enabled false
-
(2026-03-17) Purchase-order credit-consumption validation:
npx vitest run tests/purchaseOrderService.creditConsumption.test.ts --coverage.enabled false- run from
packages/billing/
- run from
npx vitest run src/test/unit/billing/contractPurchaseOrderSupport.ui.test.tsx src/test/unit/billing/contractPurchaseOrderSupport.poBanner.ui.test.tsx --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Tax-source and external-tax service-period validation:
npx vitest run tests/invoiceService.externalTax.servicePeriods.wiring.test.ts tests/taxSourceActions.servicePeriods.wiring.test.ts --coverage.enabled false- run from
packages/billing/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Invoice-generation recurring preselection validation:
npx vitest run src/test/unit/billing/invoiceGeneration.recurringSelection.test.ts --coverage.enabled falsenpx vitest run src/test/unit/billing/billingEngine.timing.test.ts src/test/unit/billing/billingEngine.productTiming.test.ts src/test/unit/billing/billingEngine.licenseTiming.test.ts --coverage.enabled false
-
(2026-03-17) Invoice header/finalization validation:
npx vitest run src/test/unit/billing/invoiceGeneration.headerPeriods.test.ts --coverage.enabled false
-
(2026-03-17) Recurring-run delegation validation:
npx vitest run src/test/unit/billing/recurringBillingRunActions.test.ts --coverage.enabled false
-
(2026-03-17) Preview recurring-timing validation:
npx vitest run src/test/unit/billing/invoiceGeneration.preview.test.ts --coverage.enabled false
-
(2026-03-17) Empty recurring-selection validation:
npx vitest run src/test/unit/billing/invoiceGeneration.emptyResult.test.ts --coverage.enabled false
-
(2026-03-17) Credit-flow and billed-through regression validation:
npx vitest run src/test/unit/billing/invoiceModel.servicePeriods.test.ts src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled false
-
(2026-03-17) Credit reconciliation / expiration validation:
npx vitest run src/test/unit/billing/creditReconciliation.servicePeriods.test.ts --coverage.enabled false
-
(2026-03-17) Contract wizard cadence-owner validation:
npx vitest run ../packages/billing/tests/contractWizardResume.test.tsx ../packages/billing/tests/contractWizardCadenceOwner.wiring.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Template wizard cadence-owner validation:
npx vitest run ../packages/billing/tests/templateWizardBucketOverlay.test.ts ../packages/billing/tests/templateWizardCadenceOwner.wiring.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Fixed recurring copy validation:
npx vitest run ../packages/billing/tests/fixedRecurringPartialPeriodCopy.wiring.test.ts ../packages/billing/tests/fixedContractLineConfiguration.cadenceOwner.ui.test.tsx --coverage.enabled falsenpx vitest run ../packages/billing/tests/billingDashboardRecurringCopy.wiring.test.ts ../packages/billing/tests/fixedRecurringPartialPeriodCopy.wiring.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Cadence-owner persistence validation:
npx vitest run src/test/unit/billing/contractLineCadenceOwner.persistence.test.ts --coverage.enabled falsenpx vitest run src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled falsenpx vitest run src/cadence-owner-contract-lines.typecheck.test.ts src/interfaces/barrel.test.ts --coverage.enabled false- run from
packages/types
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/clients/tsconfig.json
-
(2026-03-17) Cadence-owner API validation:
npx vitest run src/test/unit/api/contractLineCadenceOwner.schema.test.ts src/test/unit/api/contractLineService.cadenceOwner.test.ts src/test/unit/api/contractCreateOwnerClientSchema.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p server/tsconfig.json- blocked by an unrelated existing type error in
packages/billing/src/actions/creditActions.ts(IInvoice | nullnarrowing), not by the cadence-owner API changes
- blocked by an unrelated existing type error in
-
(2026-03-17) Cadence-owner package authoring validation:
npx vitest run ../packages/billing/tests/contractLinePresetCadenceOwner.actions.test.ts ../packages/billing/tests/contractWizardCadenceOwner.wiring.test.ts ../packages/billing/tests/draftContractForResumeActions.test.ts --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package tests
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Recurring fixture-builder validation:
npx vitest run src/test/unit/billing/recurringTiming.domain.test.ts src/test/unit/billing/billingTestHelpers.recurringFixtures.test.ts --coverage.enabled false
-
(2026-03-17) Cadence-owner rollout validation:
npx vitest run src/test/unit/api/contractLineCadenceOwner.schema.test.ts src/test/unit/api/contractLineService.cadenceOwner.test.ts src/test/unit/api/contractCreateOwnerClientSchema.test.ts --coverage.enabled false
-
(2026-03-17) Cadence-owner compatibility validation:
npx vitest run src/test/unit/api/contractLineService.cadenceOwner.test.ts src/test/unit/api/contractLineService.cadenceOwnerCompatibility.wiring.test.ts --coverage.enabled falsenpx vitest run ../packages/billing/tests/contractLineCadenceOwnerCompatibility.repository.test.ts ../packages/billing/tests/contractLineCadenceOwnerCompatibility.wiring.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json && npx tsc --pretty false --noEmit -p packages/clients/tsconfig.json
-
(2026-03-17) Billing-cycle-alignment staged-deprecation validation:
npx vitest run src/test/unit/api/billingCycleAlignmentCompatibility.schema.test.ts src/test/unit/api/billingCycleAlignmentCompatibility.repository.wiring.test.ts src/test/unit/api/contractLineService.billingCycleAlignmentCompatibility.wiring.test.ts --coverage.enabled falsenpx vitest run ../packages/billing/tests/billingCycleAlignmentCompatibility.model.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p server/tsconfig.json- still blocked by an unrelated pre-existing type error in
packages/billing/src/actions/creditActions.ts:979(IInvoice | nullinferred againstnull)
- still blocked by an unrelated pre-existing type error in
-
(2026-03-17) Fixed recurring cadence-owner UI validation:
npx vitest run ../packages/billing/tests/fixedContractLineConfiguration.cadenceOwner.ui.test.tsx --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Credit-reader canonical invoice-context validation:
npx vitest run src/test/unit/billing/creditActions.servicePeriods.test.ts --coverage.enabled falsenpx vitest run src/test/unit/billing/creditReconciliation.servicePeriods.test.ts src/test/unit/billing/invoiceModel.servicePeriods.test.ts src/test/unit/billing/creditActions.servicePeriods.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Recurring workflow-event metadata validation:
npx vitest run src/test/unit/billing/recurringBillingRunActions.test.ts --coverage.enabled falsenpx vitest run src/test/unit/billing/recurringBillingRunActions.test.ts src/test/unit/billing/invoiceGeneration.headerPeriods.test.ts --coverage.enabled false
-
(2026-03-17) Duplicate-prevention and billed-through validation:
npx vitest run src/test/unit/billing/invoiceGeneration.duplicate.test.ts src/test/unit/billing/recurringBillingRunActions.test.ts src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Post-cutover recurring-family sanity validation:
DB_PORT=57433 npx vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T171|T172|T173|T174" --coverage.enabled falseDB_PORT=57433 npx vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T173|T174" --coverage.enabled false- first pass exposed a real persistence seam: product/license detail inserts were failing because runtime charges carried canonical service periods but not
config_id
- first pass exposed a real persistence seam: product/license detail inserts were failing because runtime charges carried canonical service periods but not
npx vitest run src/test/unit/billing/billingEngine.productTiming.test.ts src/test/unit/billing/billingEngine.licenseTiming.test.ts src/test/unit/billing/invoiceService.fixedPersistence.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.json && npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Runbook and docs-inventory validation:
npx vitest run src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled false
-
(2026-03-17) Time/usage follow-on boundary validation:
npx vitest run src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled false
-
(2026-03-17) Billing-settings cadence-default validation:
npx vitest run ../packages/billing/src/actions/billingSettingsActions.cadenceOwnerDefaultsWiring.test.ts ../packages/billing/src/actions/billingSettingsActions.renewalDefaultsWiring.test.ts --coverage.enabled false- run from
server/so the workspace Vitest config can execute the source-level billing action wiring tests
- run from
npx vitest run src/test/unit/api/defaultBillingSettings.cadenceOwner.schema.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p server/tsconfig.json- still blocked by the pre-existing
packages/billing/src/actions/creditActions.ts:979narrowing error, not by the billing-settings cadence-default changes
- still blocked by the pre-existing
-
(2026-03-17) Billing-cycle anchor cadence-context validation:
npx vitest run src/test/unit/billing/previewClientBillingPeriods.test.ts src/test/unit/billing/ClientBillingSchedule.ui.test.tsx --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/clients/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Preset cadence-owner persistence validation:
npx vitest run ../packages/billing/tests/contractLinePresetCadenceOwner.actions.test.ts ../packages/billing/tests/contractLinePresetCadenceOwner.model.test.ts --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package billing tests
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/types/tsconfig.json
-
(2026-03-17) Contract-line mapping recurring-timing audit validation:
npx vitest run ../packages/billing/tests/contractLineMappingRecurringTiming.wiring.test.ts --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package billing tests
- run from
-
(2026-03-17) Invoice-detail recurring-period reader validation:
npx vitest run src/test/unit/billing/invoiceModel.servicePeriods.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/types/tsconfig.json
-
(2026-03-17) Renderer-facing canonical recurring-period validation:
npx vitest run ../packages/billing/src/lib/adapters/invoiceAdapters.test.ts --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package billing tests
- run from
npx vitest run src/components/billing/ClientInvoicePreview.servicePeriods.test.tsx src/components/billing/InvoiceDetailsDialog.servicePeriods.test.tsx --coverage.enabled false- run from
packages/client-portal/
- run from
npx tsc --pretty false --noEmit -p packages/client-portal/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Accounting export canonical-detail payload validation:
npx vitest run src/test/unit/accounting/accountingExportInvoiceSelector.servicePeriods.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p server/tsconfig.json- still blocked by the unrelated pre-existing
packages/billing/src/actions/creditActions.ts:979narrowing error (IInvoice | nullvsnull), not by the accounting-export payload changes
- still blocked by the unrelated pre-existing
-
(2026-03-17) Recurring billing workflow-event schema validation:
npx vitest run src/test/unit/billing/recurringBillingRunActions.test.ts src/test/unit/billing/recurringBillingRunWorkflowEvents.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p server/tsconfig.json- still blocked only by the unrelated pre-existing
packages/billing/src/actions/creditActions.ts:979narrowing error
- still blocked only by the unrelated pre-existing
-
(2026-03-17) Client billing schedule cadence-owner copy validation:
npx vitest run src/test/unit/billing/ClientBillingSchedule.ui.test.tsx ../packages/billing/tests/billingDashboardRecurringCopy.wiring.test.ts --coverage.enabled false- run from
server/so Vitest picks up the existing workspace alias config for both server and package tests
- run from
npx tsc --pretty false --noEmit -p packages/clients/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Client portal recurring timing display validation:
npx vitest run ../packages/client-portal/src/components/billing/InvoiceDetailsDialog.servicePeriods.test.tsx ../packages/client-portal/src/components/billing/ContractLineDetailsDialog.servicePeriods.test.tsx --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for client-portal package tests
- run from
npx tsc --pretty false --noEmit -p packages/client-portal/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/clients/tsconfig.json
-
(2026-03-17) Client-portal billing metrics and recent invoice validation:
npx vitest run src/actions/client-portal-actions/dashboard.recurringServicePeriods.test.ts src/actions/client-portal-actions/client-billing.bucketPeriods.test.ts src/components/billing/InvoiceDetailsDialog.servicePeriods.test.tsx src/components/billing/ContractLineDetailsDialog.servicePeriods.test.tsx src/components/billing/ClientInvoicePreview.servicePeriods.test.tsx --coverage.enabled false- run from
packages/client-portal/
- run from
npx tsc --pretty false --noEmit -p packages/client-portal/tsconfig.json
-
(2026-03-17) Contract reporting service-period basis validation:
npx vitest run src/test/unit/contractReportActions.recurringServicePeriodBasis.test.ts ../packages/billing/tests/ContractReports.revenueCopy.wiring.test.ts ../packages/billing/tests/contractReportActions.summary.wiring.test.ts ../packages/billing/tests/contractReportActions.revenue.assignmentFact.test.ts ../packages/billing/tests/contractReportActions.expiration.wiring.test.ts --coverage.enabled false- run from
server/so Vitest picks up the existing workspace alias config for package billing tests
- run from
npx vitest run src/lib/reports/definitions/contracts/revenue.servicePeriods.test.ts --coverage.enabled false- run from
packages/reporting/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/reporting/tsconfig.json
-
(2026-03-17) Contract reporting summary and expiration validation:
npx vitest run src/test/unit/contractReportActions.recurringServicePeriodBasis.test.ts src/test/unit/contractReportActions.expiration.servicePeriods.test.ts src/test/unit/contractReportActions.summary.servicePeriods.test.ts --coverage.enabled false- run from
server/
- run from
npx vitest run tests/ContractReports.revenueCopy.wiring.test.ts tests/ContractReports.expirationCopy.wiring.test.ts tests/ContractReports.summaryCopy.wiring.test.ts tests/contractReportActions.summary.wiring.test.ts tests/contractReportActions.expiration.wiring.test.ts- run from
packages/billing/
- run from
npx vitest run src/lib/reports/definitions/contracts/revenue.servicePeriods.test.ts src/lib/reports/definitions/contracts/expiration.servicePeriods.test.ts src/actions/reconciliationReportActions.servicePeriods.test.ts- run from
packages/reporting/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Accounting export service-period validation:
npx vitest run src/test/unit/accounting/accountingExportInvoiceSelector.servicePeriods.test.ts ../packages/billing/tests/accountingExportInvoiceSelector.servicePeriods.wiring.test.ts ../packages/billing/tests/accountingExportAdapters.servicePeriods.wiring.test.ts --coverage.enabled false- run from
server/so Vitest picks up the existing workspace alias config for package billing tests
- run from
npx vitest run src/test/integration/accounting/invoiceSelection.integration.test.ts --coverage.enabled false- blocked locally by
ECONNREFUSEDto Postgres on127.0.0.1:5438
- blocked locally by
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Internal recurring-timing docs validation:
npx vitest run ../packages/billing/tests/recurringTiming.architectureDocs.wiring.test.ts --coverage.enabled false- run from
server/so Vitest picks up the existing workspace alias config for package billing tests
- run from
npx vitest run src/actions/report-actions/README.servicePeriods.test.ts --coverage.enabled false- run from
packages/reporting/
- run from
-
(2026-03-17) Contract-cadence boundary validation:
npx vitest run src/test/unit/billing/contractCadenceServicePeriods.domain.test.ts ../packages/billing/tests/recurringTiming.architectureDocs.wiring.test.ts --coverage.enabled false- run from
server/so Vitest picks up the existing workspace alias config for package billing tests
- run from
npx vitest run src/test/unit/billing/contractCadenceServicePeriods.domain.test.ts --coverage.enabled false- run from
server/for the widened quarterly / semi-annual / annual cadence assertions plus future-start / renewal anchor rules
- run from
npx vitest run src/actions/report-actions/README.servicePeriods.test.ts --coverage.enabled false- run from
packages/reporting/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Contract-cadence first/final invoice validation:
npx vitest run src/test/unit/billing/contractCadenceServicePeriods.domain.test.ts --coverage.enabled false
-
(2026-03-17) Mixed-cadence grouping validation:
npx vitest run src/test/unit/billing/recurringTiming.domain.test.ts --coverage.enabled false
-
(2026-03-17) Invoice reread stability validation:
npx vitest run src/test/unit/billing/invoiceModel.servicePeriods.test.ts --coverage.enabled false
-
(2026-03-17) Preview service-period validation:
npx vitest run src/test/unit/billing/invoiceGeneration.preview.test.ts src/test/unit/billing/invoiceModel.servicePeriods.test.ts --coverage.enabled falsenpx vitest run src/interfaces/barrel.test.ts --root packages/types
-
(2026-03-17) Manual-invoice isolation validation:
npx vitest run src/test/unit/billing/manualInvoiceActions.recurringIsolation.test.ts --coverage.enabled false
-
(2026-03-17) Manual-invoice edit/view validation:
npx vitest run src/test/unit/billing/manualInvoiceActions.viewing.test.ts --coverage.enabled false
-
(2026-03-17) Mixed-cadence deterministic selection validation:
npx vitest run src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Staged-rollout plan validation:
npx vitest run src/test/unit/docs/servicePeriodFirstBillingPlan.contract.test.ts --coverage.enabled false
-
(2026-03-17) First-cutover guardrail validation:
npx vitest run src/test/unit/billing/invoiceGeneration.recurringSelection.test.ts --coverage.enabled falsenpx vitest run src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled false
-
(2026-03-17) DB-backed client-cadence parity validation:
DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/integration/billingInvoiceTiming.integration.test.ts --coverage.enabled false- run from
server/
- run from
-
(2026-03-17) Comparison-mode rollout validation:
npx vitest run src/test/unit/billing/invoiceGeneration.recurringSelection.test.ts --coverage.enabled false- run from
server/
- run from
-
(2026-03-17) Cadence-owner backfill validation:
npx vitest run src/test/unit/billing/contractLineCadenceOwner.persistence.test.ts --coverage.enabled false- run from
server/
- run from
-
(2026-03-17) Invoice template preview sample validation:
npx vitest run tests/sampleInvoicePreview.test.ts --coverage.enabled false- run from
packages/billing/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Cadence-owner helper fixture validation:
npx vitest run src/test/unit/billing/pricingScheduleHelpers.recurringFixtures.test.ts src/test/unit/migrations/clientOwnedContractsSimplificationMigration.test.ts --coverage.enabled false- run from
server/
- run from
-
(2026-03-17) Cadence-owner backfill non-mutation validation:
npx vitest run src/test/unit/billing/contractLineCadenceOwner.persistence.test.ts --coverage.enabled false- run from
server/
- run from
-
(2026-03-17) Mixed-cadence rollout validation:
npx vitest run src/test/unit/api/contractLineCadenceOwner.schema.test.ts src/test/unit/api/contractLineService.cadenceOwner.test.ts --coverage.enabled false- run from
server/
- run from
npx vitest run ../packages/billing/tests/contractLinePresetCadenceOwner.actions.test.ts ../packages/billing/tests/cadenceOwnerRollout.actions.wiring.test.ts ../packages/billing/tests/fixedContractLineConfiguration.cadenceOwner.ui.test.tsx --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package tests
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Fixed-config alignment compatibility validation:
npx vitest run src/test/unit/api/contractLineService.billingCycleAlignmentCompatibility.wiring.test.ts --coverage.enabled false- run from
server/
- run from
npx vitest run ../packages/billing/tests/billingCycleAlignmentCompatibility.model.test.ts --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package tests
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Client-portal overview/payment service-period summary validation:
npx vitest run ../packages/client-portal/src/components/billing/BillingOverviewTab.servicePeriods.test.tsx ../packages/client-portal/src/components/billing/PaymentSuccessContent.servicePeriods.test.tsx ../packages/client-portal/src/components/billing/InvoiceDetailsDialog.servicePeriods.test.tsx ../packages/client-portal/src/components/billing/ClientInvoicePreview.servicePeriods.test.tsx --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package client-portal tests
- run from
npx tsc --pretty false --noEmit -p packages/client-portal/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p server/tsconfig.json- currently still fails on the pre-existing
packages/billing/src/actions/creditActions.ts(979,13)type mismatch unrelated toF140
- currently still fails on the pre-existing
-
(2026-03-17) Accounting service-period audit validation:
npx vitest run ../packages/billing/tests/accountingExportValidation.servicePeriods.wiring.test.ts --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package billing tests
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Recurring parent/detail projection validation:
npx vitest run src/test/unit/billing/invoiceModel.servicePeriods.test.ts src/test/unit/api/invoiceService.recurringDetailProjection.test.ts --coverage.enabled false- run from
server/
- run from
npx vitest run src/interfaces/barrel.test.ts --root packages/types --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.jsonnpx tsc --pretty false --noEmit -p server/tsconfig.json- still blocked only by the pre-existing
packages/billing/src/actions/creditActions.ts:979narrowing error, not by the recurring projection changes
- still blocked only by the pre-existing
-
(2026-03-17) Historical flat invoice fallback validation:
npx vitest run src/test/unit/billing/invoiceModel.servicePeriods.test.ts --coverage.enabled false- run from
server/
- run from
npx vitest run src/interfaces/barrel.test.ts --root packages/types --coverage.enabled false
-
(2026-03-17) Preview multi-period projection validation:
npx vitest run src/test/unit/billing/invoiceGeneration.preview.test.ts --coverage.enabled false- run from
server/
- run from
npx vitest run src/interfaces/barrel.test.ts --root packages/types --coverage.enabled falsenpx tsc --pretty false --noEmit -p packages/types/tsconfig.jsonnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Rendering-adapter multi-period projection validation:
npx vitest run ../packages/billing/src/lib/adapters/invoiceAdapters.test.ts --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package source tests
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Client-portal projection policy validation:
npx vitest run ../packages/client-portal/src/components/billing/InvoiceDetailsDialog.servicePeriods.test.tsx --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package client-portal tests
- run from
npx tsc --pretty false --noEmit -p packages/client-portal/tsconfig.json
-
(2026-03-17) Multi-period recurring ordering/aggregation validation:
npx vitest run ../packages/client-portal/src/components/billing/InvoiceDetailsDialog.servicePeriods.test.tsx ../packages/client-portal/src/components/billing/recurringServicePeriodSummary.test.ts --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package client-portal tests
- run from
npx tsc --pretty false --noEmit -p packages/client-portal/tsconfig.json
-
(2026-03-17) Invoice schema compatibility validation:
npx vitest run src/test/unit/api/invoiceResponseSchema.compatibility.test.ts src/test/unit/api/invoiceService.recurringDetailProjection.test.ts src/test/unit/billing/invoiceModel.servicePeriods.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p server/tsconfig.json- still fails only on the pre-existing
packages/billing/src/actions/creditActions.ts:979narrowing error (IInvoice | nullvsnull), not on theF169schema changes
- still fails only on the pre-existing
-
(2026-03-17) Invoice workflow recurring-provenance validation:
npx vitest run src/test/unit/invoiceWorkflowEvents.test.ts src/test/unit/api/invoiceWorkflowRecurringProvenance.wiring.test.ts src/test/unit/api/invoiceResponseSchema.compatibility.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p server/tsconfig.json- still fails only on the pre-existing
packages/billing/src/actions/creditActions.ts:979narrowing error (IInvoice | nullvsnull), not on theF170workflow/audit provenance changes
- still fails only on the pre-existing
-
(2026-03-17) Billed-through detail-reader validation:
npx vitest run src/test/unit/billing/billingEngine.billedThroughReader.test.ts src/test/unit/billing/billingEngine.timing.test.ts -t "T201|T087" --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p server/tsconfig.json- still fails only on the pre-existing
packages/billing/src/actions/creditActions.ts:979narrowing error (IInvoice | nullvsnull), not on theF171billed-through reader contract
- still fails only on the pre-existing
-
(2026-03-17) Client contract-line mutation-guard validation:
npx vitest run src/test/unit/billing/clientContractLineMutationGuards.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/clients/tsconfig.json
-
(2026-03-17) Client contract-line renewal/replacement identity validation:
npx vitest run src/test/unit/billing/clientContractLineReplacementIdentity.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/clients/tsconfig.json
-
(2026-03-17) Client-contract termination/detail-period validation:
npx vitest run src/test/unit/clientContractActions.overlapExclusive.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/clients/tsconfig.json
-
(2026-03-17) Invoice deletion/detail-period safeguard validation:
npx vitest run ../packages/billing/tests/invoiceModification.recurringDeletionGuard.test.ts --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package billing tests
- run from
npx vitest run src/test/unit/api/invoiceService.deleteRecurringDetailGuard.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Invoice recalculation/detail-period preservation validation:
npx vitest run src/test/unit/billing/billingEngine.recalculateInvoice.detailPeriods.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Manual recurring-charge edit guard validation:
npx vitest run ../packages/billing/tests/invoiceModification.manualRecurringGuard.test.ts --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package billing tests
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Persisted-invoice preview refresh validation:
npx vitest run ../packages/billing/src/components/invoice-designer/DesignerVisualWorkspace.test.tsx -t "T208" --coverage.enabled false- run from
server/so Vitest uses the existing workspace alias config for package billing component tests
- run from
npx vitest run ../packages/billing/tests/invoiceQueries.recurringDetailRefresh.wiring.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Percentage-discount recalculation validation:
npx vitest run src/test/unit/billing/invoiceService.percentageDiscountRecalculation.test.ts src/test/unit/billing/billingEngine.recalculateInvoice.detailPeriods.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Zero-dollar recurring finalization validation:
npx vitest run src/test/unit/billing/invoiceGeneration.emptyResult.test.ts src/test/unit/billing/invoiceGeneration.zeroDollarFinalization.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Manual invoice period-policy validation:
npx vitest run src/test/unit/billing/invoiceService.manualPeriodPolicy.test.ts src/test/unit/billing/invoiceService.percentageDiscountRecalculation.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Manual-to-recurring provenance validation:
npx vitest run src/test/unit/billing/invoiceService.manualPeriodPolicy.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Prepayment financial-artifact validation:
npx vitest run src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Negative-invoice offset validation:
npx vitest run src/test/unit/billing/creditActions.servicePeriods.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Credit application recurring-context validation:
npx vitest run src/test/unit/billing/creditActions.servicePeriods.test.ts src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Credit reconciliation date-basis validation:
npx vitest run src/test/unit/billing/creditReconciliation.servicePeriods.test.ts src/test/unit/billing/creditActions.servicePeriods.test.ts src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Credit transfer lineage validation:
npx vitest run src/test/unit/billing/creditActions.servicePeriods.test.ts src/test/unit/billing/creditReconciliation.servicePeriods.test.ts src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts --coverage.enabled false- run from
server/
- run from
npx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Financial-artifact display policy validation:
npx vitest run src/components/billing/InvoiceDetailsDialog.servicePeriods.test.tsx src/components/billing/BillingOverviewTab.servicePeriods.test.tsx --coverage.enabled false- run from
packages/client-portal/
- run from
npx vitest run tests/creditManagement.financialArtifactContext.wiring.test.ts --coverage.enabled false- run from
packages/billing/
- run from
npx tsc --pretty false --noEmit -p packages/client-portal/tsconfig.json
-
(2026-03-17) Client contract assignment-date validation now distinguishes canonical recurring coverage from historical invoice-window fallback, which closes
F174andT204:packages/clients/src/actions/clientContractActions.tsnow queries canonicalinvoice_charge_detailscoverage for the currentclient_contract_idbefore using legacyclient_billing_cyclesoverlap logic, so end-date shortening and mid-cycle termination are enforced against billed recurring service periods rather than against the entire enclosing invoice window whenever authoritative detail rows exist- the new rule is explicit and end-exclusive: the earliest billed recurring
service_period_startstill blocks moving the contract start later into already billed coverage, while the latest billedservice_period_endis treated as exclusive and converted into the last already-billed service day when validating a shorterend_date - historical flat invoices still keep the old fallback behavior because
updateClientContract(...)only consultsclient_billing_cycleswhen no canonical recurring detail periods exist for the contract server/src/test/unit/clientContractActions.overlapExclusive.test.tsnow proves all three boundary cases together: touching historical invoice-window boundaries still pass under[start, end)semantics, canonical partial service periods allow termination exactly on the last billed day, and shortening earlier than that day is rejected with a detail-period-specific error
-
(2026-03-17) Invoice deletion now preserves authoritative recurring detail periods instead of treating detail-backed invoices like disposable draft rows, which closes
F175andT205:packages/billing/src/actions/invoiceModification.tsnow blockshardDeleteInvoice(...)as soon as canonicalinvoice_charge_detailsrows exist for the invoice, with an explicit error instructing callers to cancel instead of deletingserver/src/lib/api/services/InvoiceService.tsnow computes recurring provenance before delete-path branching and treats canonical-detail-backed invoices as soft-cancel candidates even when they have no payments yet, so the API delete path preserves the invoice plus its authoritative recurring detail history- the safeguard intentionally distinguishes detail-backed recurring invoices from historical/manual invoices: invoices without canonical recurring detail periods still follow the existing delete behavior, while draft or finalized recurring invoices with canonical periods are preserved through cancellation semantics
- focused executable coverage now lives in
packages/billing/tests/invoiceModification.recurringDeletionGuard.test.tsfor the explicit hard-delete action andserver/src/test/unit/api/invoiceService.deleteRecurringDetailGuard.test.tsfor the API soft-cancel branch
-
(2026-03-17) Invoice recalculation is now explicitly defined as a financial-only pass that preserves canonical recurring detail periods, which closes
F176andT206:packages/billing/src/lib/billing/billingEngine.tsnow documents the intended contract inline at the recalculation seam: once an invoice exists,invoice_charge_detailsremains the authoritative recurring timing record and recalculation should only recompute tax distribution and invoice totalsserver/src/test/unit/billing/billingEngine.recalculateInvoice.detailPeriods.test.tsproves that contract directly by exercisingBillingEngine.recalculateInvoice(...)with a fake invoice/client store, asserting it delegates only tocalculateAndDistributeTax(...)andupdateInvoiceTotalsAndRecordTransaction(...), and failing if the recalculation path starts queryinginvoice_charge_details
-
(2026-03-17) Manual invoice edit flows now make recurring provenance policy explicit instead of silently ignoring edits against recurring parent charges, which closes
F177andT207:packages/billing/src/actions/invoiceModification.tsnow checks targeted update/remove item IDs before manual edits proceed and rejects any non-manual invoice charge targets up front- the recurring-specific rule is stricter and explicit: if a targeted parent charge already has canonical
invoice_charge_detailsrows, the action throwsCannot manually edit recurring invoice charges once canonical detail periods exist...and tells the caller to add a manual adjustment or cancel/regenerate instead of mutating the recurring line in place packages/billing/tests/invoiceModification.manualRecurringGuard.test.tsproves that recurring-detail-backed invoice charges trip that guard and that the action does not fall through into invoice recalculation after the rejected edit attempt
-
(2026-03-17) Post-persist rerender and preview-refresh flows are now explicitly split between summary-range readers and full detail-aware readers, which closes
F178andT208:packages/billing/src/actions/invoiceQueries.tsnow documents the intended split directly in code: paginated/list summary surfaces flatten canonical recurring detail rows to one summary range, whilegetInvoiceForRendering(...)must keep usingInvoice.getFullInvoiceById(...)so persisted recurring detail periods survive rerender and preview refreshpackages/billing/src/components/invoice-designer/DesignerVisualWorkspace.test.tsxnow proves the persisted existing-invoice preview path forwardsrecurring_detail_periodsintomapDbInvoiceToWasmViewModel(...)instead of stripping them before preview renderingpackages/billing/tests/invoiceQueries.recurringDetailRefresh.wiring.test.tsnow locks the reader split at the action layer so future refactors cannot collapse rerender flows back onto the summary-only invoice list projection
-
(2026-03-17) Template-to-contract cloning now propagates explicit recurring timing semantics instead of dropping them at the client wizard seam, which closes
F207andT236:packages/billing/src/actions/contractWizardActions.tsnow treatsbilling_timingas part of the client-wizard recurrence contract everywhere the template clone path crosses a boundary:ClientTemplateSnapshot,ClientContractWizardSubmission, andDraftContractWizardDataall carry it explicitly;getContractTemplateSnapshotForClientWizard(...)andgetDraftContractForResume(...)now reread it from detailed template/live lines; andcreateClientContractFromWizard(...)now passessubmission.billing_timingintoresolveRecurringAuthoringPolicy(...)before creating fixed, product, hourly, and usage linespackages/billing/src/components/billing-dashboard/contracts/ContractWizard.tsxnow preserves that timing field through wizard state instead of flattening every template-derived clone back to the arrears default: default wizard state carriesbilling_timing: 'arrears', template snapshot application copiessnapshot.billing_timing, and save/create submission payloads emitwizardData.billing_timing ?? 'arrears'packages/billing/tests/templateCadenceOwnerRoundTrip.actions.test.tsnow proves template snapshots and resumed drafts preserve bothcadence_ownerandbilling_timingeven when the authored recurring line is hourly/usage instead of fixed, which keeps the clone path from relying on incidental fixed-line-only behaviorpackages/billing/tests/contractWizardResume.test.tsxnow proves the live client wizard clone seam carries template-authoredbilling_timing: 'advance'andcadence_owner: 'contract'into the eventualcreateClientContractFromWizard(...)save-draft payload after template selection, and the resumed-draft fixture coverage now keepsbilling_timingstable across resavepackages/billing/tests/contractWizardCadenceOwner.wiring.test.tsnow locks the additive source contract so template snapshots and wizard submissions cannot silently stop threadingbilling_timingalongsidecadence_owner- validation for this checkpoint used
cd server && npx vitest run ../packages/billing/tests/templateCadenceOwnerRoundTrip.actions.test.ts ../packages/billing/tests/contractWizardResume.test.tsx ../packages/billing/tests/contractWizardCadenceOwner.wiring.test.ts --coverage.enabled falseplusnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Template-derived live contract lines now have explicit post-clone independence semantics, which closes
F208andT237:packages/billing/src/repositories/contractLineRepository.tsandserver/src/lib/repositories/contractLineRepository.tsnow state the intended clone rule inline atcloneTemplateLineToContract(...): cadence owner and billing timing are copied onto the new livecontract_linesrow at clone time, and later template edits are provenance only rather than retroactive live-line mutationsserver/src/lib/api/services/ContractLineService.tsnow states the same service-layer rule where template lines are assigned to client contracts:template_contract_line_idremains lineage/provenance, but recurring cadence semantics on the live line stop inheriting from the template once the clone is createdpackages/billing/src/actions/contractWizardActions.tsnow makesclient_contracts.template_contract_idintent explicit for template-derived contracts: it is retained for draft/review provenance, while live recurring cadence semantics remain copied onto the contract lines themselvesserver/src/test/unit/billing/templateLineCadenceOwner.persistence.test.tsnow addsT237, proving a cloned live contract line keepscadence_owner: 'contract'andbilling_timing: 'advance'even after the source template row is later mutated toclient/arrears- validation for this checkpoint used
cd server && npx vitest run src/test/unit/billing/templateLineCadenceOwner.persistence.test.ts --coverage.enabled falseplusnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json npx tsc --pretty false --noEmit -p server/tsconfig.jsonis still blocked by an unrelated pre-existing type error atpackages/billing/src/actions/creditActions.ts:1208(Type 'IInvoice | null' is not assignable to type 'null'), so the checkpoint relies on the focused server unit seam rather than claiming a clean full-server compile
-
(2026-03-17) Pre-save recurring authoring previews now use one explicit copy contract for cadence-owner, timing, first-invoice, and partial-period outcomes, which closes
F209,T238, andT239:packages/billing/src/components/billing-dashboard/contracts/recurringAuthoringPreview.tsnow defines the authoritative preview vocabulary for recurring authoring: cadence-owner label/summary, billing-timing label/summary, first-invoice behavior, and partial-period outcomepackages/billing/src/components/billing-dashboard/contracts/wizard-steps/FixedFeeServicesStep.tsxnow renders aRecurring Preview Before Saveblock for live contract authoring, so billing staff can see client-schedule versus contract-anniversary outcomes plus advance/arrears and partial-period consequences before savingpackages/billing/src/components/billing-dashboard/contracts/wizard-steps/ReviewContractStep.tsx,packages/billing/src/components/billing-dashboard/contracts/template-wizard/steps/TemplateFixedFeeServicesStep.tsx, andpackages/billing/src/components/billing-dashboard/contracts/template-wizard/steps/TemplateReviewContractStep.tsxnow reuse the same preview helper instead of drifting into per-surface wording, which keeps template authoring and contract authoring alignedpackages/billing/tests/recurringAuthoringPreview.test.tsnow proves both first-invoice families directly: client-cadence advance/arrears copy and contract-cadence advance/arrears copypackages/billing/tests/contractWizardCadenceOwner.wiring.test.tsandpackages/billing/tests/templateWizardCadenceOwner.wiring.test.tsnow lock the UI wiring so both authoring surfaces continue to render the shared preview contract- validation for this checkpoint used
cd server && npx vitest run ../packages/billing/tests/recurringAuthoringPreview.test.ts ../packages/billing/tests/contractWizardCadenceOwner.wiring.test.ts ../packages/billing/tests/templateWizardCadenceOwner.wiring.test.ts --coverage.enabled falseplusnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
-
(2026-03-17) Unsupported recurring authoring combinations now have one explicit early-validation boundary in the wizards, which closes
F210andT240:shared/billingClients/recurringAuthoringValidation.tsnow defines the shared unsupported-combination message contract across line type, cadence owner, billing timing, and billing frequency; during the current rollout it names the exact blocked combination instead of only saying contract cadence is generically unavailablepackages/billing/src/components/billing-dashboard/contracts/ContractWizard.tsxnow checks every present recurring family before finish/save-draft and surfaces the first unsupported combination message locally, so template-derived or resumed contract-cadence drafts fail before the action layerpackages/billing/src/components/billing-dashboard/contracts/template-wizard/TemplateWizard.tsxnow applies that same boundary on the review/publish step, so template authoring surfaces reject unsupported contract-cadence combinations with the same detailed message contract used by the client wizardpackages/billing/tests/recurringAuthoringValidation.wiring.test.tsnow proves both the detailed shared message shape (lineType,cadenceOwner,billingTiming,billingFrequency) and the fact that both wizard flows import and invoke the shared validator before persistence- validation for this checkpoint used
cd server && npx vitest run ../packages/billing/tests/recurringAuthoringValidation.wiring.test.ts --coverage.enabled falseplusnpx tsc --pretty false --noEmit -p packages/billing/tsconfig.json
Links / References
- Related plans:
ee/docs/plans/2026-03-16-client-owned-contracts-simplification/ee/docs/plans/2026-03-16-contract-template-normalization/
- Pass-0 artifacts:
ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/PASS0_RECURRING_TIMING_APPENDIX.mdee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/pass-0-source-inventory.json
- Materialized-ledger artifacts:
ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/PERSISTED_SERVICE_PERIOD_RECORD.mdee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_DUE_SELECTION.mdee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_INVOICE_LINKAGE.mdee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_PARITY_COMPARISON.mdee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/RECURRING_SERVICE_PERIOD_LIFECYCLE.md
- Key runtime files:
packages/types/src/interfaces/recurringTiming.interfaces.tsserver/migrations/20260318120000_create_recurring_service_periods.cjsserver/migrations/20260318143000_add_invoice_linkage_to_recurring_service_periods.cjsshared/billingClients/recurringServicePeriodDueSelection.tsshared/billingClients/recurringServicePeriodKeys.tsshared/billingClients/recurringServicePeriodLifecycle.tsshared/billingClients/recurringServicePeriodInvoiceLinkage.tsshared/billingClients/recurringServicePeriodParity.tspackages/billing/src/lib/billing/billingEngine.tspackages/billing/src/lib/billing/recurringTiming.tsshared/billingClients/createBillingCycles.tsshared/billingClients/clientCadenceServicePeriods.tsshared/billingClients/contractCadenceServicePeriods.tsshared/billingClients/recurrenceStorageModel.tsshared/billingClients/recurringTiming.tspackages/billing/src/actions/invoiceGeneration.tspackages/billing/src/actions/recurringBillingRunActions.tspackages/billing/src/repositories/accountingExportRepository.ts
- Key UI/config files:
packages/billing/src/components/billing-dashboard/ContractLineDialog.tsxpackages/billing/src/components/billing-dashboard/contracts/ContractWizard.tsxpackages/billing/src/components/billing-dashboard/contracts/QuickStartGuide.tsxpackages/client-portal/src/actions/client-portal-actions/client-billing.ts
- Key tests:
server/src/test/integration/billingInvoiceTiming.integration.test.tsserver/src/test/infrastructure/billing/invoices/*server/src/test/infrastructure/billing/credits/*server/src/test/unit/billing/recurringServicePeriodDueSelection.domain.test.tsserver/src/test/unit/billing/recurringServicePeriodInvoiceLinkage.domain.test.tsserver/src/test/unit/billing/recurringServicePeriodLifecycle.domain.test.tsserver/src/test/unit/billing/recurringServicePeriodParity.domain.test.tsserver/src/test/unit/migrations/recurringServicePeriodInvoiceLinkageMigration.test.tsserver/src/test/unit/migrations/recurringServicePeriodsMigration.test.tsserver/src/test/unit/billingEngine.test.tsserver/src/test/unit/billing/billingEngine.timing.test.ts
Open Questions
-
Which bucket/allowance behaviors must join v1 versus a follow-on?
-
Which exact service-period edit operations belong in v1 versus a follow-on?
-
(2026-03-18) Mixed-cadence portal/export coverage and header-vs-detail drift validation are now explicit, which closes
T147andT270:packages/client-portal/src/components/billing/InvoiceDetailsDialog.servicePeriods.test.tsxnow covers a mixed invoice that contains both client-cadence and contract-cadence canonical recurring rows, so the portal detail dialog proves both shapes remain readable on one invoice instead of only exercising a single cadence pathserver/src/test/integration/accounting/invoiceSelection.integration.test.tsnow carriesT147, proving export preview persistence remains stable when one invoice contains both a client-cadence recurring detail row and a contract-cadence recurring detail row; both persisted export lines keepservice_period_source = canonical_detail_periodswith their distinct canonical date ranges intactserver/src/test/unit/accounting/accountingExportValidation.servicePeriodProjection.test.tsnow carriesT270on a focused validation seam instead of a migration-heavy integration harness: it provesAccountingExportValidation.ensureMappingsForBatch(...)marks the batchneeds_attentionand recordsservice_period_projection_mismatchwhen stored export-line summary dates drift away from canonicalinvoice_charge_detailspass-0-source-inventory.jsonwas refreshed in the same checkpoint becauseT003inventory coverage must follow the live persisted-reader set; deleting the abandoned integration seam and adding the focused unit seam changed that list- the first DB-backed
T270attempt was intentionally discarded because it added migration churn without improving the contract under test; the focused unit seam exercises the validator directly and keeps the actual mismatch rule explicit instead of burying it under unrelated bootstrap noise - focused validation for this checkpoint used:
cd server && npx vitest run src/test/unit/accounting/accountingExportValidation.servicePeriodProjection.test.ts --coverage.enabled falsecd server && npx vitest run ../packages/client-portal/src/components/billing/InvoiceDetailsDialog.servicePeriods.test.tsx --coverage.enabled falsecd server && DB_HOST=127.0.0.1 DB_PORT=57433 DB_USER_ADMIN=postgres DB_PASSWORD_ADMIN=postpass123 DB_USER_SERVER=app_user DB_PASSWORD_SERVER=postpass123 npx vitest run src/test/integration/accounting/invoiceSelection.integration.test.ts -t "T147" --hookTimeout 600000 --coverage.enabled false