Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
82 KiB
Scratchpad — Recurring Invoicing Hard Cutover
- Plan slug:
recurring-invoicing-hard-cutover - Created:
2026-03-18
What This Is
Working notes for the hard-cutover plan that removes recurring invoice bridge assumptions after service-period-driven recurring execution already exists.
Decisions
- (2026-03-18) Treat
client_billing_cyclesas a legitimate client cadence concept, not as recurring invoice execution identity. - (2026-03-18) Treat
billing_cycle_idas historical/optional metadata only in the target state; it should not remain a first-class recurring contract. - (2026-03-18) Do not support mixed recurring schema states in steady-state application code.
- (2026-03-18) Prefer explicit hard failures and repair actions over fallback compatibility rows when recurring service-period materialization is missing.
- (2026-03-18) This plan is a hard-cutover follow-on to the broader service-period-first plan and the softer service-driven invoicing cutover plan.
- (2026-03-18) Keep the hard-cutover architecture notes separate from the softer service-driven runbook so the steady-state model is explicit and testable.
- (2026-03-18) Recurring invoice linkage should derive candidate obligations from canonical persisted detail metadata (
config_id, service-period window, due position, invoice window) plus contract-line or assignment identity, never from invoice-headerbilling_cycle_id. - (2026-03-18) Prepayment classification should use explicit invoice-kind state (
is_prepayment) rather than the absence of a recurring bridge field; bridge-less recurring invoices and prepayments are separate concepts. - (2026-03-18) Persisted recurring execution windows are already canonical recurring truth, so billing-engine validation for selector-input recurring runs must not round-trip back through
client_billing_cyclesor auto-create cycle rows just to validate the window. - (2026-03-18) Direct
selector_inputpreview/generate requests must normalize and validate against persistedrecurring_service_periods; treating the caller-provided window as trusted input leaves a gap where mutated windows can bypass canonical service-period authority. - (2026-03-18) Client billing schedule edits should regenerate future client-cadence
recurring_service_periodsafter the last billed boundary while leavingclient_billing_cyclesavailable only for schedule administration and historical context. - (2026-03-18) Bucket allowance periods should resolve from canonical
recurring_service_periodsfor both client cadence and contract cadence;client_billing_cyclesshould not determine bucket rollover windows once recurring service periods exist. - (2026-03-18) Hourly and usage recurring charge queries should always use canonical service-period bounds when a persisted recurring selection is present, even if the invoice window is later or shifted metadata.
- (2026-03-18) Live accounting export lines should emit canonical recurring detail periods when present and otherwise stay periodless financial documents; invoice-header billing periods remain historical read-side metadata only, not live recurring provenance.
- (2026-03-18) Ready recurring work should use canonical selection actions only; the dashboard must not keep bridge-only row menus such as “Delete Cycle” that disappear when
billingCycleIdis null.
Agent Findings
-
Documentation slice completed:
- Added ARCHITECTURE.md to define the hard-cutover invariant, the limited role of
client_billing_cycles, the required-schema posture, and theinvoices.billing_cycle_iddeprecation posture. - Added RUNBOOK.md to describe the final recurring mental model, missing-materialization diagnosis, and canonical reverse/delete repair expectations.
- Added recurringInvoicingHardCutover.runbook.test.ts to lock T072/T073/T074 against regressions.
- Added ARCHITECTURE.md to define the hard-cutover invariant, the limited role of
-
Due-work cutover slice completed:
- Updated billingAndTax.ts so ready recurring work comes only from persisted
recurring_service_periods, with client-cadence materialization gaps surfaced separately as repair records instead of compatibility due rows. - Updated recurringRunExecutionIdentity.ts to add canonical
client_cadence_windowidentity keyed by schedule/period/window instead ofbillingCycleId. - Updated recurringDueWork.ts so client-cadence due-work rows can be built bridge-free while still carrying optional
billingCycleIdmetadata for display. - Updated billingEngine.ts to stop treating missing recurring-service-period relations as an acceptable mixed-schema fallback.
- Added/updated focused tests:
- Updated billingAndTax.ts so ready recurring work comes only from persisted
-
Recurring run-orchestration slice completed:
- Updated recurringBillingRunActions.shared.ts so recurring run targets always carry canonical
selectorInputplus execution-window identity; client-cadence target mapping now starts from canonical due-work rows instead of billing periods. - Updated recurringBillingRunActions.ts so recurring batch selection reads only
getAvailableRecurringDueWork(...), recurring runs no longer accept rawbillingCycleIds, and execution always delegates throughgenerateInvoiceForSelectionInput(...). - Updated AutomaticInvoices.tsx so ready-table, preview, and PO-overage generate flows submit canonical recurring targets for both bridged client rows and bridge-free contract rows.
- Updated recurring workflow metadata in recurringBillingRunEventBuilders.ts and billingEventSchemas.ts so run payloads use
client_cadence_window,contract_cadence_window, ormixed_execution_windows, rather than treating billing-cycle windows as the default live recurring mode. - Added/updated focused tests:
- Updated recurringBillingRunActions.shared.ts so recurring run targets always carry canonical
-
Billing-calculation and duplicate-detection slice completed:
- Updated invoiceGeneration.ts so selector-input billing always executes through
calculateBillingForExecutionWindow(...), no longer runs rollout-era legacy-vs-canonical comparison mode, and checks client-cadence duplicates via canonicalrecurring_service_periodsschedule/period linkage before any legacy billing-cycle fallback. - Updated billingEngine.ts to remove the unused billing-cycle parameter from persisted recurring due-selection loading.
- Added/updated focused tests:
- Updated invoiceGeneration.ts so selector-input billing always executes through
-
Billing-engine recurring execution slice completed:
- Updated billingEngine.ts so selector-input recurring due-work loading and execution-window billing stay on canonical persisted service-period selections, bypass
validateBillingPeriod(...)forrecurringTimingSelectionSource = "persisted", and never callgetClientContractLinesAndCycle(...)orgetBillingCycle(...)on the live recurring execution path. - This keeps client cadence source rules relevant only before execution, when recurring service periods are materialized; the runtime billing engine now treats the persisted service-period window as the recurring source of truth.
- Added/updated focused tests:
- Updated billingEngine.ts so selector-input recurring due-work loading and execution-window billing stay on canonical persisted service-period selections, bypass
-
Selector-input window-validation slice completed:
- Updated invoiceGeneration.ts so
normalizeRecurringSelectorInput(...)canonicalizes client-cadence and contract-cadence selector input by looking up persistedrecurring_service_periods, rejects windows that do not match materialized service periods, and wraps generation-side normalization failures with execution-identity diagnostics. - Added focused preview/generate regressions:
- Updated invoiceGeneration.ts so
-
Accounting export service-period slice completed:
- Updated accountingExportInvoiceSelector.ts so live export lines only emit canonical recurring detail periods when invoice charge detail rows exist and otherwise fall back to periodless
financial_document_fallbackoutput instead of preserving invoice-header recurring provenance. - Kept
invoice_header_fallbackconfined to passive historical payload/read-side compatibility; live export selection no longer emits mixed canonical-vs-header recurring provenance. - Added/updated focused tests:
- Updated accountingExportInvoiceSelector.ts so live export lines only emit canonical recurring detail periods when invoice charge detail rows exist and otherwise fall back to periodless
-
Recurring dashboard copy/actions slice completed:
- Updated AutomaticInvoices.tsx so ready recurring rows no longer render a bridge-only delete-cycle menu, unbridged rows share the same service-period-backed badge and selection actions as bridged rows, and reverse/delete dialog copy now describes client-cycle data only as optional bridge metadata instead of the primary recurring object.
- Extended automaticInvoices.recurringDueWork.ui.test.tsx with a regression proving ready service-period rows no longer expose bridge-only row menus while history row actions and service-period-backed copy remain intact.
-
Billing UI/actions sweep:
AutomaticInvoices, due-work selection, recurring run target selection, history, reversal/delete, accounting export, and some authoring/storage helpers still preserve bridge-first recurring behavior.- Notable files:
-
API/contracts sweep:
- recurring preview/generate request contracts, invoice DTOs, recurring shared types, and invoice list/read classification still preserve
billing_cycle_idas a recurring concept. - Notable files:
- recurring preview/generate request contracts, invoice DTOs, recurring shared types, and invoice list/read classification still preserve
-
Runtime/history sweep:
- billing engine, invoice generation, invoice linkage, bucket usage, schedule changes, jobs, and migrations still preserve bridge-era assumptions.
- Notable files:
-
Recurring invoice-linkage slice completed:
- invoiceService.ts now builds recurring linkage candidates from canonical config and invoice-window data, then matches
recurring_service_periodsacross explicitcontract_lineandclient_contract_lineobligations without anyinvoice.billing_cycle_idbranch. - The linkage helper no longer suppresses missing-relation errors as a rollout-era fallback; required recurring linkage relations are now assumed to exist in steady state.
- Added persistence coverage in invoiceService.fixedPersistence.test.ts for both client-cadence and contract-cadence rows with null
billing_cycle_id. - Added static guards in recurringInvoiceLinkage.static.test.ts to lock out
billing_cycle_id-driven widening and mixed-schema fallback logic.
- invoiceService.ts now builds recurring linkage candidates from canonical config and invoice-window data, then matches
-
Invoice-kind classification slice completed:
- invoiceModification.ts now classifies prepayment handling from explicit invoice kind (
is_prepayment) and negative totals, rather than treatingbilling_cycle_id = nullas a proxy. - creditActions.ts now persists
is_prepayment: truewhen creating prepayment invoices so finalization and later reads use the same explicit kind signal. - Added behavior and static coverage in invoiceFinalization.kindClassification.test.ts, plus a prepayment persistence assertion in prepaymentInvoice.periodPolicy.test.ts.
- invoiceModification.ts now classifies prepayment handling from explicit invoice kind (
-
Client schedule-regeneration slice completed:
- Added clientCadenceScheduleRegeneration.ts to derive the last billed client boundary, load client-cadence obligations, and regenerate future
recurring_service_periodswith canonicalbilling_schedule_changed/backfill_materializationprovenance instead of mutating futureclient_billing_cycles. - Updated billingScheduleActions.ts and billingCycleAnchorActions.ts so both schedule-edit entrypoints call the new regeneration helper after updating client schedule settings.
- Added focused unit coverage in updateClientBillingSchedule.test.ts and rewrote the DB-backed schedule expectations in clientBillingCycleAnchors.test.ts to assert recurring-service-period regeneration and preserved schedule-administration cycles instead of future-cycle deactivation.
- Added clientCadenceScheduleRegeneration.ts to derive the last billed client boundary, load client-cadence obligations, and regenerate future
-
Bucket recurring-period slice completed:
- Updated bucketUsageService.ts so current and previous bucket allowance periods resolve from canonical
recurring_service_periodsby obligation and schedule key, with no liveclient_billing_cycleslookup in the recurring bucket path. - Replaced the old client-cycle-based unit coverage in bucketUsageService.periods.test.ts with client-cadence and contract-cadence tests that prove rollover uses canonical recurring service periods and never touches
client_billing_cycles.
- Updated bucketUsageService.ts so current and previous bucket allowance periods resolve from canonical
-
Hourly/usage service-window verification slice completed:
- Confirmed billingEngine.ts already uses
servicePeriodStartExclusive/servicePeriodEndExclusiveto querytime_entriesandusage_trackingfor persisted recurring selections; no product-code change was required for the hard-cutover behavior. - Extended billingEngine.endExclusiveQueries.test.ts with persisted-recurring hourly and usage regressions proving canonical service windows still drive the queries when they differ from the invoice window.
- Confirmed billingEngine.ts already uses
-
Cadence-authoring cleanup slice completed:
- Updated recurringAuthoringPolicy.ts so shared normalization no longer hides a client-cadence default; callers must provide an explicit authoring default or reuse stored cadence.
- Updated billingSettingsActions.ts, packages/types billing.interfaces.ts, and server billing.interfaces.ts so billing settings expose explicit client defaults and
mixed_enabledmetadata without blocking contract cadence updates behind rollout-era throws. - Removed the dead rollout validator wrappers from contractLineSchemas.ts and financialSchemas.ts, leaving cadence-owner defaults only at explicit UI/action/service boundaries.
- Aligned contractLineRepository.ts with the package-side repository by removing live
contract_template_line_termsreads from template detail and template-clone paths; only the cleanup delete in contractTemplate.ts still references that table. - Extended focused wiring/static coverage:
-
Service-period troubleshooting slice completed:
- Added recurringServicePeriodActions.ts to load persisted recurring service-period management views and preview operator regeneration/history-repair flows behind dedicated recurring-service-period permissions.
- Added RecurringServicePeriodsTab.tsx, and wired it into BillingDashboard.tsx plus billingTabsConfig.ts, so operators can inspect generated, edited, billed, and corrective service-period state by schedule key.
- Updated AutomaticInvoices.tsx to render
materializationGapsas explicit repair-required panels with deep links into the Service Periods tab, instead of silently dropping the gap or pretending it is ready invoice work. - Added 20260318194500_add_recurring_service_period_permissions.cjs so admins/managers receive the recurring service-period
view,manage_future,regenerate, andcorrect_historypermissions required by the new operator surface. - Added/updated focused tests:
-
Required-schema and wording cleanup slice completed:
- Updated recurringInvoicingHardCutover.runbook.test.ts to lock the hard-cutover architecture’s required-schema posture, including the expectation that recurring code never catches missing table/column errors to recreate bridge behavior.
- Renamed the client-cadence materialization-gap reader in billingAndTax.ts away from
compatibilityPeriods, and removed the deadmergeRecurringDueWorkRows(...)helper from recurringDueWork.ts, so the steady-state due-work path no longer carries bridge-era “compatibility” terminology or helpers.
-
Workflow/event diagnostics audit slice completed:
- Added recurringBillingRunEventDiagnostics.static.test.ts to lock out
billingCycleId,billing_cycle_id,billing_cycle_window, and similar bridge-era fields from recurring workflow/job diagnostic contracts. - Updated generateInvoiceHandler.recurringExecutionIdentity.test.ts so the recurring invoice job payload contract is explicitly selector-input plus canonical execution-window identity, with no raw
billingCycleIdrequirement. - Updated recurringBillingRunWorkflowEvents.test.ts to prove recurring run failure events identify runs with
selectionKey,retryKey,selectionMode,windowIdentity, andexecutionWindowKinds, and do not emit bridge-specific fields.
- Added recurringBillingRunEventDiagnostics.static.test.ts to lock out
-
Invoice-query summary cleanup slice completed:
- Updated invoiceQueries.ts so invoice list, client, and contract summary readers derive
service_period_start/service_period_endthroughinvoice_charges -> invoice_charge_detailsjoins, rather than stale direct-detail assumptions or bridge-era summary naming. - Updated invoiceQueries.recurringDetailRead.test.ts with a static guard that the summary queries use canonical charge-detail joins and no longer describe their service-period projection as a compatibility range.
- Updated invoiceQueries.ts so invoice list, client, and contract summary readers derive
-
API schema audit slice completed:
- Updated financialSchemas.ts so financial invoice write requests no longer expose
billing_cycle_id, the deadbillingCycleInvoiceRequestSchemais removed, andcalculateBillingSchemanow rejects raw cycle-ID bridge input while preserving billing-cycle fields only on schedule-administration schemas and invoice read metadata. - Updated ApiFinancialController.ts and FinancialService.ts so
/api/v1/financial/billing/calculateexecutes through canonical execution-window billing instead of forwarding abilling_cycle_idhandle into the legacy cycle-based engine path. - Added financialRecurringSchemaAudit.static.test.ts to lock T070 against non-schedule API schemas reintroducing billing-cycle recurring request contracts.
- Updated financialSchemas.ts so financial invoice write requests no longer expose
-
Shared interface separation slice completed:
- Updated recurringTiming.interfaces.ts so canonical
IRecurringInvoiceWindowno longer carries a deadbillingCycleIdbridge field. - Updated packages/types billing.interfaces.ts and server billing.interfaces.ts to remove the unused
IBillingCycleInvoiceRequestinterface now that cycle-ID invoice request schemas are no longer part of the hard-cutover recurring contract surface. - Added recurringInterfaceSeparation.static.test.ts to lock T071 against recurring execution models or shared/server billing interface files growing cycle-bridge request models back in.
- Updated recurringTiming.interfaces.ts so canonical
-
Historical read-side strategy slice completed:
- Updated ARCHITECTURE.md with a dedicated historical read-side strategy section defining
canonical_recurring,financial_document_fallback, andmissing_source_contextas read-only migration states, not live recurring execution modes. - Updated RUNBOOK.md with operator guidance for historically incomplete linkage so old invoices remain readable without synthesizing due work or rebuilding execution identity from
billing_cycle_id. - Extended recurringInvoicingHardCutover.runbook.test.ts to lock that fallback posture against regressions, alongside the existing read-side unit coverage in invoiceModel.servicePeriods.test.ts and creditActions.servicePeriods.test.ts.
- Updated ARCHITECTURE.md with a dedicated historical read-side strategy section defining
-
Legacy recurring test cleanup slice completed:
- Updated billingInvoiceTiming.integration.test.ts so the remaining client-cadence happy-path/history/reversal assertions (
T051,T087,T088,T090) use canonical selector input andclient_cadence_windowexpectations instead of rawbillingCycleIdgeneration orbilling_cycle_windowcompatibility assertions. - Added legacyRecurringIntegrationTests.static.test.ts to lock T081 against bridge-era compatibility titles and
billing_cycle_windowassertions creeping back into the recurring timing integration harness.
- Updated billingInvoiceTiming.integration.test.ts so the remaining client-cadence happy-path/history/reversal assertions (
-
Canonical recurring regression slice completed:
- Updated billingInvoiceTiming.integration.test.ts so the contract-cadence and client-cadence happy-path integration cases are now explicit checklist regressions
T077andT076, both exercising due-work selection, canonical preview, selector-input generation, and recurring history withcontract_cadence_window/client_cadence_windowidentity rather than raw billing-cycle execution.
- Updated billingInvoiceTiming.integration.test.ts so the contract-cadence and client-cadence happy-path integration cases are now explicit checklist regressions
-
Invoice-kind regression slice completed:
- Updated invoiceFinalization.kindClassification.test.ts so the explicit-kind regression cases are aligned to checklist items
T046andT047, covering both bridge-less recurring finalization and true prepayment credit behavior after the explicitis_prepaymentcutover. - Revalidated prepaymentInvoice.periodPolicy.test.ts alongside that suite to keep the prepayment financial-document policy anchored to the same explicit kind model.
- Updated invoiceFinalization.kindClassification.test.ts so the explicit-kind regression cases are aligned to checklist items
-
Preview/generate request rejection tests completed:
- Revalidated invoiceRecurringSelectorInput.schema.test.ts and invoiceService.recurringSelectorInput.test.ts, which already lock checklist items
T014andT015at both schema and service-routing layers for recurring preview/generate rejection of rawbilling_cycle_idselectors.
- Revalidated invoiceRecurringSelectorInput.schema.test.ts and invoiceService.recurringSelectorInput.test.ts, which already lock checklist items
-
Canonical preview/generate success tests completed:
- Updated billingInvoiceTiming.integration.test.ts so the canonical happy-path integrations now explicitly cover
T016/T018/T076for client cadence andT017/T019/T077for contract cadence, proving both preview and selector-input generation succeed without requiring a live billing-cycle bridge.
- Updated billingInvoiceTiming.integration.test.ts so the canonical happy-path integrations now explicitly cover
Discoveries / Constraints
- (2026-03-18) The repo already had an in-progress service-driven runbook/test update in the worktree; the hard-cutover docs were added in separate files to avoid mixing coexistence guidance with the post-bridge model.
- (2026-03-18) The due-work reader still needs later UI/run-action follow-up because some surfaces continue to use
billingCycleIdfor row actions even though due-work identity is now canonical. - (2026-03-18)
invoiceModification.tsnow classifies prepayment behavior fromis_prepaymentrather than null/non-nullbilling_cycle_id; historical rows that predate the explicit flag may still need later read-side or backfill consideration. - (2026-03-18)
billingAndTax.tsnow sources ready recurring work only fromrecurring_service_periods, but the separate repair-gap surface still needs downstream UI handling so operators can act on missing materialization without relying on compatibility rows. - (2026-03-18) recurring run selection,
AutomaticInvoices, and recurring preview/generate API requests now use canonical selector-input targets, but shared type unions and some read-side/history surfaces still carry legacybilling_cycle_idorbilling_cycle_windowsemantics. - (2026-03-18)
generateInvoice(...),previewInvoice(...), and PO-overage selection now normalize legacy billing-cycle entrypoints onto canonical client-cadence selector input before duplicate detection or billing calculation.billingCycleIdremains only as optional metadata on the normalized selector while request-shape cleanup is still pending. - (2026-03-18) recurring preview/generate API schemas now reject top-level
billing_cycle_idandbilling_cycle_windowselector inputs. The remaining bridge-only fields live in shared type unions and read-side/history surfaces, not in recurring preview/generate request handling. - (2026-03-18) Legitimate client billing schedule administration still has separate schedule-specific schemas and actions (
financialSchemas.ts,billingScheduleActions.ts,billingCycleAnchorActions.ts) after recurring preview/generate stopped acceptingbilling_cycle_id. - (2026-03-18) Live recurring history now loads through recurring-invoice-first readers and action labels. A deprecated billing-cycle alias remains only to keep the already-dirty
billingInvoiceTiming.integration.test.tsharness compiling until that separate worktree change is cleaned up. - (2026-03-18)
InvoiceService.tslist/detail classification now uses canonical recurring summary data.billing_cycle_idremains only as optional historical metadata or explicit include-side context. - (2026-03-19)
InvoiceService.tsstill had one stale read-side bridge seam in its historical billing-cycle include path: it was joiningclient_billing_cycleson legacycycle_id/period_start/period_endcolumn names. The hard-cutover read-side tests now lock the currentbilling_cycle_id/period_start_date/period_end_datemetadata path instead. - (2026-03-19) Recurring reverse/delete actions still contained one bridge-era cleanup branch that deactivated or deleted
client_billing_cyclesafter invoice deletion. That mutation is now removed so client billing schedule rows stay cadence-administration/history records rather than recurring runtime objects. - (2026-03-18) one API service path still appears to reference old cycle column names (
cycle_id,period_start,period_end) and should be rechecked during cleanup. - (2026-03-18) Bucket recurring period resolution now derives from canonical
recurring_service_periodswindows and only falls back to plan-anchor arithmetic when no current service period exists. - (2026-03-18) Live accounting export selection no longer emits invoice-header recurring provenance. Canonical recurring detail rows remain authoritative, while invoices without canonical detail export as periodless financial documents; historical stored payloads may still carry older fallback metadata.
- (2026-03-18) Shared recurring identity types now drop bridge-only
billingCycleIdandhasBillingCycleBridgefields, but read-side invoice history types still legitimately exposebilling_cycle_windowand bridge metadata for historical context. - (2026-03-18)
generateInvoiceHandlerandscheduleRecurringWindowInvoiceGenerationare now selector-input-only. The oldscheduleInvoiceGeneration(...)helper is left as an explicit hard failure if something still tries to schedule recurring work from a rawbillingCycleId. - (2026-03-18) DB-backed verification for
billingInvoiceTiming.integration.test.tscould not run locally because PostgreSQL was unavailable on127.0.0.1:5438/::1:5438. The targeted tests were skipped before any test body executed. - (2026-03-18) Recurring preview/generation diagnostics now emit only
executionIdentityKeyfor live recurring failures.billingCycleIdstill exists as passive persistence or read-side metadata, but it is no longer part of the recurring preview/generate error contract. - (2026-03-18) Invoice API list/detail contracts now distinguish client recurring invoices as
client_cadence_window, notbilling_cycle_window.billing_cycle_idremains available on invoice DTOs only as optional historical metadata or explicit include-side context, not as the recurring classifier. - (2026-03-18)
recurring_projectionwas dead compatibility metadata in the clean runtime paths. Canonical recurring reads now key directly offrecurring_detail_periods; only the already-dirtybillingInvoiceTiming.integration.test.tsstill references the removed field and was intentionally left untouched until that file is clean. - (2026-03-18) Live recurring history now loads through
getRecurringInvoiceHistoryPaginated(...)and labels the surface as recurring invoice history, not invoiced billing cycles. A deprecatedgetInvoicedBillingCyclesPaginated(...)alias remains only so the already-dirtybillingInvoiceTiming.integration.test.tsharness can be cleaned up separately without mixing in its unrelated edits. - (2026-03-18)
invoiceService.tsno longer usesinvoice.billing_cycle_idto choose recurring linkage candidates. The next remaining bridge-like misclassification seam is still in invoiceModification.ts, where null/non-nullbilling_cycle_idis still used for prepayment logic (F046/F047). - (2026-03-18)
invoiceModification.tsnow classifies prepayment behavior fromis_prepayment, andcreditActions.tsnow persists that flag for new prepayment invoices. Historical rows that predate the explicit flag may still need later read-side or backfill consideration if they relied on the old null-bridge proxy. - (2026-03-18) The schedule-edit actions still read
client_billing_cyclesonly to determine the last invoiced historical boundary. That boundary is now passive historical input to recurring-service-period regeneration; future schedule changes no longer deactivate or redefine recurring work by mutating future cycle rows. - (2026-03-18) The DB-backed schedule-management suite now imports action files directly instead of
@alga-psa/billing/actions, because the broader action index pulls optional jobs infrastructure that is not resolvable in this isolated test harness. - (2026-03-18) DB-backed verification for
clientBillingCycleAnchors.test.tsis currently blocked locally because PostgreSQL is unavailable on127.0.0.1:5438/::1:5438. The rewritten tests load and compile, but the shared test context aborts before any test body executes. - (2026-03-18)
bucketUsageService.tsno longer queriesclient_billing_cycleson the recurring bucket path. Bucket period resolution now derives current and rollover windows fromrecurring_service_periodsand falls back to plan-anchor arithmetic only when no canonical service period exists. - (2026-03-18)
billingEngine.tsalready had the correct service-window behavior for hourly and usage recurring execution.F056was completed by locking that behavior with explicit persisted-selection query regressions instead of changing runtime logic. - (2026-03-18) DB-backed verification for
invoiceSelection.integration.test.tsis also currently blocked locally because PostgreSQL is unavailable on127.0.0.1:5438/::1:5438. The updated coexistence assertions compile, but the shared test context aborts before any integration test body executes. - (2026-03-19) Shared recurring authoring defaults are now explicit at product boundaries only. The remaining non-test
cadence_owner ?? 'client'callsites are all UI defaults (ContractWizard, template wizard, and contract-line configuration components), while shared/action/repository code now requires eitherdefaultCadenceOwneror stored cadence reuse. - (2026-03-19) The last live
contract_template_line_termsruntime reads were in server/src/lib/repositories/contractLineRepository.ts; they are now removed. The only remaining non-test reference is the cleanup delete in contractTemplate.ts, which is intentionally retained for template cleanup. - (2026-03-19)
getAvailableRecurringDueWork(...)was already returningmaterializationGaps, but AutomaticInvoices.tsx was discarding them. The hard-cutover operator story now depends on surfacing those gaps directly and linking to the Service Periods tab by schedule key. - (2026-03-19) The new Service Periods tab uses schedule-key lookup rather than client-cycle identity. That keeps troubleshooting aligned with persisted recurring-service-period authority and avoids reintroducing
billing_cycle_idas the operator handle for recurring repair. - (2026-03-19) The remaining steady-state “compatibility” wording in recurring due-work code had collapsed to an internal gap-reader name plus an unused merge helper. Removing both let the hard-cutover docs and runtime code tell the same story: missing service periods are repair work, not a coexistence mode.
- (2026-03-19) The recurring run builders, workflow event schemas, and job handler contracts were already canonical; the remaining gap was regression coverage. The new tests now lock that those diagnostics stay keyed by canonical execution-window identity and never grow bridge-only payload fields back.
- (2026-03-19)
invoiceQueries.tsstill had one remaining bridge-era seam in its summary readers: they projected service-period ranges with staleinvoice_charge_detailsassumptions and a “compatibility summary range” comment. That is now cleaned up and locked to canonical charge-detail joins. - (2026-03-19) The remaining API-schema bridge seam was the legacy financial billing-calculation route.
ApiInvoiceControllerwas already selector-input-only for recurring preview/generate, butApiFinancialControllerstill acceptedbilling_cycle_idand routed throughBillingEngine.calculateBilling(...); that path now uses canonical execution-window dates only. - (2026-03-19) Financial invoice write schemas now treat
billing_cycle_idas read-side metadata only. The hard-cutover API audit does not remove historical invoice list filters or response metadata, but it does remove writable/request-side cycle bridge fields outside legitimate client billing schedule administration schemas. - (2026-03-19) The remaining package/shared interface leak was dead bridge data on canonical recurring invoice-window types plus an unused
IBillingCycleInvoiceRequestinterface duplicated in package/server billing interfaces. Those are now removed; passive cycle metadata still exists on read/display models such as recurring due-work rows where F009 explicitly kept it for optional context. - (2026-03-19) The code already had the historical-read fallback states needed for incomplete recurring linkage (
financial_document_fallbackandmissing_source_context), but the hard-cutover architecture/runbook did not explain that posture.F075is now documented as an explicit read-side strategy: keep old invoices readable without inventing new recurring work or rebuilding live identity from bridge metadata. - (2026-03-19) The highest-signal leftover bridge-era tests were in the already-dirty billingInvoiceTiming.integration.test.ts harness. They were rewritten surgically in place to avoid disturbing the unrelated worktree edits, and local DB-backed execution is still blocked by the missing PostgreSQL instance on
127.0.0.1:5438/::1:5438. - (2026-03-19) The same timing integration harness now carries the canonical end-to-end regression names for client and contract recurring happy paths (
T076/T077). They collect successfully under Vitest, but local execution remains blocked before test bodies run because PostgreSQL is unavailable on127.0.0.1:5438/::1:5438. - (2026-03-19) The remaining explicit-kind coverage already existed in focused unit tests;
F079was completed by aligning those regressions to checklist itemsT046andT047and rerunning them. No additional product-code change was required for this final feature slice.
Commands / Runbooks
rg -n "billing_cycle_id|client_billing_cycles|getAvailableBillingPeriods|missing_service_period_materialization|isMissingRecurringDueWorkRelation" packages/billing/src server/src/lib/api shared -g '!**/*.test.*'rg -n "prepayment|billing_cycle_id" packages/billing/src/actions packages/billing/src/services -g '!**/*.test.*'rg -n "recurring_projection|hasBillingCycleBridge|billingCycleId" packages/types/src server/src/interfaces server/src/lib/api -g '!**/*.test.*'rg -n "billing cycle" packages/billing/src/components packages/billing/src/actions -g '!**/*.test.*'cd server && pnpm exec vitest run src/test/unit/docs/recurringInvoicingHardCutover.runbook.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/recurringDueWork.domain.test.ts src/test/unit/billing/recurringDueWorkReader.integration.test.ts src/test/unit/billing/recurringDueWorkReader.static.test.ts src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/recurringServicePeriodDueSelection.domain.test.ts src/test/unit/billing/recurringTiming.domain.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/recurringBillingRunActions.test.ts src/test/unit/billing/recurringBillingRunActions.static.test.ts src/test/unit/billing/recurringBillingRunWorkflowEvents.test.ts src/test/unit/billing/recurringBillingRunWindowIdentity.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx src/test/unit/billing/contractPurchaseOrderSupport.ui.test.tsx --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/invoiceGeneration.recurringSelection.test.ts src/test/unit/billing/invoiceGeneration.selectorInputGenerate.test.ts src/test/unit/billing/invoiceGeneration.preview.test.ts src/test/unit/billing/invoiceGeneration.emptyResult.test.ts src/test/unit/billing/invoiceGeneration.duplicate.test.ts src/test/unit/billing/invoiceGeneration.duplicate.static.test.ts src/test/unit/billing/invoiceGeneration.zeroDollarFinalization.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/recurringIdentityTypes.static.test.ts src/test/unit/billing/recurringTiming.domain.test.ts src/test/unit/billing/recurringServicePeriodDueSelection.domain.test.ts src/test/unit/billing/recurringDueWork.domain.test.ts src/test/unit/billing/recurringDueWorkReader.integration.test.ts src/test/unit/billing/contractPurchaseOrderSupport.ui.test.tsx src/test/unit/jobs/generateInvoiceHandler.recurringExecutionIdentity.test.ts src/test/unit/billing/invoiceGeneration.recurringSelection.test.ts src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx --coverage.enabled=falsecd server && pnpm exec vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T321|T322/T328" --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/invoiceGeneration.selectorInputGenerate.test.ts src/test/unit/billing/invoiceGeneration.preview.test.ts src/test/unit/billing/invoiceGeneration.duplicate.test.ts src/test/unit/billing/invoiceGeneration.duplicate.static.test.ts src/test/unit/billing/invoiceGeneration.zeroDollarFinalization.test.ts src/test/unit/billing/invoiceGeneration.emptyResult.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/api/invoiceRecurringList.contract.test.ts src/test/unit/api/invoiceRecurringSelectorInput.schema.test.ts src/test/unit/api/invoiceService.recurringSelectorInput.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/api/invoiceService.recurringDetailProjection.test.ts src/test/unit/api/invoiceResponseSchema.compatibility.test.ts src/test/unit/billing/invoiceModel.servicePeriods.test.ts src/test/unit/billing/invoiceQueries.recurringDetailRead.test.ts src/test/unit/billing/manualInvoiceActions.viewing.test.ts src/test/unit/api/invoiceService.deleteRecurringDetailGuard.test.ts src/test/unit/invoiceWorkflowEvents.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx --coverage.enabled=falsecd server && pnpm exec vitest run src/test/integration/api/invoiceService.recurringCoexistence.integration.test.ts src/test/integration/accounting/invoiceSelection.integration.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx src/test/unit/billing/contractPurchaseOrderSupport.ui.test.tsx src/test/unit/billing/recurringInvoiceHistory.static.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/invoiceService.fixedPersistence.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/recurringInvoiceLinkage.static.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/invoiceFinalization.kindClassification.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/billingEngine.recurringExecution.static.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/invoiceGeneration.preview.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/invoiceGeneration.selectorInputGenerate.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/updateClientBillingSchedule.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/infrastructure/billing/invoices/clientBillingCycleAnchors.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/bucketUsageService.periods.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/billingEngine.endExclusiveQueries.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/accounting/accountingExportInvoiceSelector.servicePeriods.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run ../packages/billing/tests/accountingExportInvoiceSelector.servicePeriods.wiring.test.ts ../packages/billing/tests/authoritativeRecurringReaders.servicePeriods.wiring.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/integration/accounting/invoiceSelection.integration.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/recurringInvoiceHistory.static.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/recurringAuthoringPolicy.domain.test.ts src/test/unit/billing/recurrenceStorageModel.contract.test.ts src/test/unit/api/contractLineCadenceOwner.schema.test.ts src/test/unit/api/contractLineService.cadenceOwner.test.ts src/test/unit/api/defaultBillingSettings.cadenceOwner.schema.test.ts ../packages/billing/tests/cadenceOwnerRollout.actions.wiring.test.ts ../packages/billing/tests/contractWizardCadenceOwner.wiring.test.ts ../packages/billing/tests/templateWizardCadenceOwner.wiring.test.ts ../packages/billing/tests/contractLinePresetRecurringAuthoring.wiring.test.ts ../packages/billing/tests/recurringAuthoringValidation.wiring.test.ts ../packages/billing/tests/fixedContractLineConfiguration.cadenceOwner.ui.test.tsx ../packages/billing/tests/contractLinePresetCadenceOwner.actions.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run ../packages/billing/src/actions/billingSettingsActions.cadenceOwnerDefaultsWiring.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/recurringServicePeriodActions.test.ts src/test/unit/billing/recurringServicePeriodsTab.ui.test.tsx src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/docs/recurringInvoicingHardCutover.runbook.test.ts src/test/unit/billing/recurringDueWorkReader.static.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/jobs/generateInvoiceHandler.recurringExecutionIdentity.test.ts src/test/unit/billing/recurringBillingRunWorkflowEvents.test.ts src/test/unit/billing/recurringBillingRunEventDiagnostics.static.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/invoiceQueries.recurringDetailRead.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/api/financialRecurringSchemaAudit.static.test.ts src/test/unit/api/invoiceRecurringSelectorInput.schema.test.ts --coverage.enabled=falsecd server && pnpm exec tsc -p tsconfig.json --noEmit(started for this slice; full run was still in progress during checklist updates)cd server && pnpm exec vitest run src/test/unit/billing/recurringIdentityTypes.static.test.ts src/test/unit/billing/recurringInterfaceSeparation.static.test.ts src/test/unit/api/financialRecurringSchemaAudit.static.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/docs/recurringInvoicingHardCutover.runbook.test.ts src/test/unit/billing/invoiceModel.servicePeriods.test.ts src/test/unit/billing/creditActions.servicePeriods.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/legacyRecurringIntegrationTests.static.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T051|T087|T088|T090" --coverage.enabled=falsecd server && pnpm exec vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T051|T076|T077|T088|T090" --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/billing/invoiceFinalization.kindClassification.test.ts src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/api/invoiceRecurringSelectorInput.schema.test.ts src/test/unit/api/invoiceService.recurringSelectorInput.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T016|T017|T018|T019|T076|T077" --coverage.enabled=falsecd server && pnpm exec vitest run src/test/unit/api/invoiceService.billingCycleMetadata.static.test.ts src/test/unit/api/invoiceRecurringList.contract.test.ts src/test/unit/api/invoiceService.recurringDetailProjection.test.ts src/test/unit/billing/invoiceGeneration.preview.test.ts src/test/unit/billing/recurringInvoiceReversal.static.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/integration/api/invoiceService.recurringCoexistence.integration.test.ts --coverage.enabled=falsecd server && pnpm exec vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T029|T030|T033|T034|T049|T050|T078|T079|T080|T082|T084|T085|T087" --coverage.enabled=false
Completed Items
- (2026-03-18) F001 implemented by defining canonical service-period or execution-window identity as the only recurring execution identity in
ARCHITECTURE.md. - (2026-03-18) F002 implemented by documenting
client_billing_cyclesas cadence/source-rule infrastructure and optional historical context only. - (2026-03-18) F074 implemented by documenting
invoices.billing_cycle_idas passive historical metadata only in recurring code until later physical removal. - (2026-03-18) F080 implemented by adding hard-cutover architecture and operator runbook guidance for the final recurring model.
- (2026-03-18) T072/T073/T074 implemented with a focused documentation regression test for the final model, client-cycle role, and
billing_cycle_iddeprecation posture. - (2026-03-18) F003/F004/F005/F006 implemented by removing recurring due-work compatibility merges, removing recurring mixed-schema fallback guards from the reader/engine, and sourcing ready recurring work only from persisted service-period rows.
- (2026-03-18) F007/F008/F009 implemented by introducing canonical client-cadence execution identity keyed by schedule/period/window and treating
billingCycleIdonly as optional read-side metadata on due-work rows. - (2026-03-18) T001/T002/T003/T004/T005/T006 implemented with static, integration, and unit coverage for canonical due-work sourcing and bridge-free client-cadence row identity.
- (2026-03-18) F010/F011/F012/F013 implemented by removing raw-cycle recurring run entrypoints, selecting recurring runs from canonical due-work rows only, and making the recurring run executor invoke selector-input invoice generation for all cadence owners.
- (2026-03-18) F014/F015 implemented at the run-target boundary by removing required
billingCycleIdfrom recurring run target contracts and making client-cadence run identity schedule/period/window keyed end to end. - (2026-03-18) T007/T008/T023/T024/T026/T027/T028 implemented with unit, integration, static, and UI coverage for bridge-free execution identity keys, canonical recurring run selection, and selector-input-only recurring run execution.
- (2026-03-18) F016 implemented by removing the billing-cycle-vs-execution-window branch from selector-input billing calculation helpers and using execution-window-first billing for recurring selector inputs and compatibility wrappers alike.
- (2026-03-18) F017 implemented by deleting the rollout-era recurring comparison-mode branch from
invoiceGeneration.ts. - (2026-03-18) F018/F019/F020 implemented by normalizing legacy billing-cycle preview/generate entrypoints onto canonical client-cadence selector identity, deleting the remaining
invoices.billing_cycle_idduplicate fallback, and keeping rerun or retry behavior keyed off canonical execution identity even whenbilling_cycle_idis retained as passive metadata. - (2026-03-18) F021/F022 implemented by making recurring preview/generate API schemas require canonical
selector_inputand rejectbilling_cycle_idrequest shapes orbilling_cycle_windowselector inputs. - (2026-03-18) F023/F024 implemented by proving the
AutomaticInvoicespreview/generate actions submit canonical selector input for both client-cadence and contract-cadence due rows, even when passive billing-cycle metadata is still displayed on the row. - (2026-03-18) F025/F026 implemented by removing API service/controller routing through
generateInvoice(...)orpreviewInvoice(...), renaming the generate controller path away from billing-cycle framing, and making recurring API routes depend only on selector-input actions. - (2026-03-18) F027 confirmed by keeping billing-cycle-specific schedule administration surfaces separate from recurring execution APIs; no recurring preview/generate contract still depends on those schedule-only endpoints.
- (2026-03-18) T020/T021/T022 implemented with client-cadence, contract-cadence, and static source coverage showing duplicate detection uses canonical recurring linkage before any legacy billing-cycle fallback.
- (2026-03-18) F028/F029 implemented by removing bridge-only
billingCycleIdandhasBillingCycleBridgefields from shared recurring identity interfaces, dropping sharedbilling_cycle_windowbuilders, converting shared client due-work builders to canonical schedule/period selector input, and updating recurring jobs to require canonical selector-input payloads. - (2026-03-18) T009 implemented with a static contract test for the shared recurring type file plus domain/UI/job coverage proving client-cadence and contract-cadence shared builders still work after the bridge fields were removed.
- (2026-03-18) F030 implemented by removing
billingCycleIdfrom recurring preview/generation error payloads and duplicate errors so live recurring diagnostics key only on canonicalexecutionIdentityKey. - (2026-03-18) F031/F032/F033/F034 implemented by renaming client recurring invoice DTO execution-window kind to
client_cadence_window, keepingbilling_cycle_idonly as optional metadata, and cuttingInvoiceServicelist/detail projection plus filter classification over to canonical recurring summary data without anyinvoices.billing_cycle_idfallback. - (2026-03-18) F035/F036 implemented by removing
recurring_projectionfrom invoice charge types, schema contracts, invoice model hydration, and workflow-event provenance so canonical recurring detail reads depend only onrecurring_detail_periodsplus summary parent period fields. - (2026-03-18) T010 implemented with a UI regression proving
AutomaticInvoicescan render, preview, and generate a client-cadence row whosebillingCycleIdmetadata is null while still sending canonical selector-input targets. - (2026-03-18) F037/F038 implemented by renaming the live recurring history reader to
getRecurringInvoiceHistoryPaginated(...), removing the remaining billing-cycle cadence fallback from the history query, and updatingAutomaticInvoicescopy from billing-cycle framing to recurring-invoice-history framing. - (2026-03-18) T011/T035/T036/T037 implemented with ready-row UI coverage for an unbridged contract-cadence row, recurring-history UI coverage for row actions and service-period/execution-window copy, and a static guard that the live history surface no longer imports or labels itself as invoiced billing cycles.
- (2026-03-18) F039/F040/F041 implemented by making recurring reverse/delete wrappers repair recurring service periods through
hardDeleteInvoice(...)andreleaseRecurringServicePeriodInvoiceLinkageForInvoice(...)without mutatingclient_billing_cyclesas the recurring primary object. - (2026-03-18) T012/T013 implemented by extending the existing client-cadence
AutomaticInvoicesUI regression so the same proof covers both preview and generate actions submitting canonical selector input with nobilling_cycle_id. - (2026-03-18) F042/F043/F044/F045 implemented by making
invoiceService.tsderive recurring linkage candidates from canonical config/window identity, matching bothcontract_lineandclient_contract_lineobligations without consultinginvoice.billing_cycle_id, and deleting the mixed-schema missing-relation fallback guard from recurring linkage persistence. - (2026-03-18) T031/T032 implemented with static source guards proving recurring invoice linkage no longer widens or narrows from
invoice.billing_cycle_idand no longer suppresses missing-relation fallback errors. - (2026-03-18) F046/F047/F048 implemented by classifying invoice finalization behavior from explicit
is_prepaymentplus negative totals, persistingis_prepayment: trueon prepayment creation, and proving bridge-less recurring invoices with nullbilling_cycle_idno longer get routed through prepayment credit logic. - (2026-03-18) T048 implemented with a static source guard proving invoice finalization no longer keys recurring or prepayment classification from null/non-null
billing_cycle_id. - (2026-03-18) F049/F050/F051 implemented by making persisted recurring execution-window billing bypass cycle validation, loading contract lines directly for the execution window, and preventing selector-input recurring runs from auto-loading or auto-creating
client_billing_cyclesduring live execution. - (2026-03-18) T051 implemented with static and unit coverage proving the billing engine’s persisted recurring path no longer routes through
getClientContractLinesAndCycle(...),validateBillingPeriod(...), orgetBillingCycle(...)to execute recurring work. - (2026-03-18) Added T089 because the original checklist lacked a focused validation test for direct selector-input windows drifting away from persisted recurring service periods; preview and generate both now reject that mismatch explicitly.
- (2026-03-18) F052 implemented by normalizing client-cadence and contract-cadence selector input against persisted
recurring_service_periods, so recurring preview/generate validate canonical service-period windows instead of trusting caller-supplied windows or legacy cycle semantics. - (2026-03-18) T089 implemented with preview/generate regressions proving selector-input recurring actions reject execution windows that do not match materialized recurring service periods and still surface canonical execution-identity diagnostics.
- (2026-03-18) F053/F054 implemented by routing both client billing schedule edit actions through canonical client-cadence service-period regeneration, keeping
client_billing_cyclesavailable for schedule administration while removing the old future-cycle invalidation behavior from recurring execution. - (2026-03-18) T052/T053 implemented with focused unit coverage for schedule-driven
recurring_service_periodsregeneration plus a DB-backed schedule suite rewrite that now asserts preserved client-cycle administration and canonical recurring regeneration semantics, although the DB-backed run is currently blocked locally by the missing PostgreSQL test instance. - (2026-03-18) F055 implemented by deriving bucket allowance periods and rollover lookups from canonical
recurring_service_periodsschedule windows instead of preferringclient_billing_cycles. - (2026-03-18) T054/T055 implemented with client-cadence and contract-cadence bucket-service unit coverage proving recurring bucket resolution uses canonical service periods and never hits
client_billing_cycles. - (2026-03-18) F056 confirmed by locking the existing billing-engine behavior that filters hourly
time_entriesand usageusage_trackingagainst canonical persisted service windows rather than invoice-window or client-cycle boundaries. - (2026-03-18) T056/T057 implemented with focused billing-engine regressions proving persisted recurring hourly and usage execution still query inside the canonical service window when it differs from the invoice window.
- (2026-03-18) F057/F058/F059 implemented by cutting live accounting export selection over to canonical recurring detail periods only, removing invoice-header recurring provenance emission from the live selector, and treating invoices without canonical recurring detail as periodless financial documents.
- (2026-03-18) T058/T059/T060/T061 implemented with integration, unit, and static coverage for client-cadence and contract-cadence canonical export periods plus the legacy no-detail case that now exports without live recurring fallback provenance.
- (2026-03-18) F060/F061 implemented by removing the ready-table’s bridge-only delete-cycle menu, making bridge-free rows use the same service-period-backed badge and canonical selection flow as other recurring rows, and updating recurring reverse/delete copy to talk about optional bridge metadata instead of treating billing cycles as the primary object.
- (2026-03-19) F062/F063/F064 implemented by moving client-cadence defaults to explicit UI/action/service boundaries, removing rollout-era cadence-owner schema wrappers and hidden shared defaults, and deleting the last live
contract_template_line_termsfallback reads from the server-side contract-line repository. - (2026-03-19) T062/T063/T064 implemented with focused wiring/schema/storage-model coverage proving authoring defaults stay explicit at UX boundaries, shared/runtime helpers no longer hide client fallback semantics, and normal read/write paths no longer use old template-term fallback tables.
- (2026-03-19) F065/F066 implemented by keeping the Service Periods tab wired into the billing dashboard as the canonical troubleshooting surface, introducing dedicated recurring-service-period permissions and management actions, and surfacing missing-materialization gaps in
AutomaticInvoicesas explicit repair links instead of compatibility due rows. - (2026-03-19) T065/T066 implemented with UI/action coverage proving the service-period management surface remains reachable from the billing dashboard and that missing materialization renders as a repair-required panel linked to canonical service-period review rather than as ready-to-generate invoice work.
- (2026-03-19) F067/F068 implemented by explicitly locking the hard-cutover required-schema posture in docs coverage and removing the last bridge-era “compatibility” naming/helper from the recurring due-work repair path.
- (2026-03-19) F069/F070 implemented by locking recurring job/workflow diagnostics to canonical execution-window identity and proving static schema/builder contracts no longer emit bridge-only recurring payload fields.
- (2026-03-19) T025/T067/T068 implemented with job-contract, workflow-payload, and static-schema coverage for selector-input-only recurring job payloads and bridge-free recurring run diagnostics.
- (2026-03-19) F071/T069 implemented by moving invoice summary service-period projections onto canonical
invoice_charges+invoice_charge_detailsjoins and adding a static guard against bridge-era summary assumptions. - (2026-03-19) F072/T070 implemented by removing
billing_cycle_idfrom non-schedule financial API write/calculation schemas, routing financial billing calculation through canonical execution-window billing, deleting the dead billing-cycle invoice request schema, and adding a static audit that only client billing schedule schemas still expose billing-cycle request contracts. - (2026-03-19) F073/T071 implemented by removing dead cycle-bridge fields from canonical recurring invoice-window interfaces, deleting the unused shared/server
IBillingCycleInvoiceRequestmodel, and adding a static guard that recurring execution interfaces stay separate from client billing cycle request models. - (2026-03-19) F075 implemented by documenting the hard-cutover migration/read-side strategy for historically incomplete recurring linkage, explicitly constraining fallback to
financial_document_fallback/missing_source_contextread models while keeping live recurring execution bridge-free. - (2026-03-19) F076/T081 implemented by rewriting the remaining client-cadence timing integration assertions away from raw cycle-ID generation and
billing_cycle_windowexpectations, then adding a static guard that the legacy compatibility framing stays removed. - (2026-03-19) F077/F078 and T076/T077 implemented by aligning the DB-backed client- and contract-cadence happy-path timing integrations with the checklist regressions, so both end-to-end recurring flows now validate canonical selector-input preview/generate/history behavior under
client_cadence_windowandcontract_cadence_window. - (2026-03-19) F079 and T046/T047 implemented by aligning the explicit invoice-kind regression suite to the checklist and rerunning it, proving bridge-less recurring invoices are not misclassified as prepayments while true prepayment behavior still issues credit correctly.
- (2026-03-19) T038/T039/T040/T041/T042/T043/T044/T045/T075/T088 implemented by fixing
InvoiceServicehistorical billing-cycle metadata reads to currentclient_billing_cyclescolumns, adding read-side classification/detail/static coverage for passive bridge metadata versus canonical recurring summary data, and tightening recurring preview/generation diagnostic tests sobillingCycleIdnever reappears as the primary recurring error key. - (2026-03-19) T029/T030/T033/T034/T049/T050/T078/T079/T080/T082/T084/T085/T087 implemented by aligning the DB-backed recurring timing suite to canonical selector-input linkage, mandatory service-period materialization, bridge-free engine queries, null-bridge client-cadence persistence, and history/reverse/reissue behavior after the last billing-cycle cleanup branches were removed. Local execution of those DB-backed tests remains blocked until PostgreSQL is available on
127.0.0.1:5438. - (2026-03-19) T083/T086 implemented by relabeling the DB-backed client billing schedule regeneration suite around the hard-cutover acceptance criteria: client billing schedule records remain administrable, future recurring service periods regenerate canonically, and historical invoice context remains intact. Local execution remains blocked until PostgreSQL is available on
127.0.0.1:5438.
Links / References
Open Questions
- Should there be a later physical schema removal plan for
invoices.billing_cycle_id, or is passive historical retention enough? - How much read-side fallback for historically incomplete recurring linkage is acceptable after live recurring compatibility branches are removed?