Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

82 KiB
Raw Permalink Blame History

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_cycles as a legitimate client cadence concept, not as recurring invoice execution identity.
  • (2026-03-18) Treat billing_cycle_id as 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-header billing_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_cycles or auto-create cycle rows just to validate the window.
  • (2026-03-18) Direct selector_input preview/generate requests must normalize and validate against persisted recurring_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_periods after the last billed boundary while leaving client_billing_cycles available only for schedule administration and historical context.
  • (2026-03-18) Bucket allowance periods should resolve from canonical recurring_service_periods for both client cadence and contract cadence; client_billing_cycles should 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 billingCycleId is null.

Agent Findings

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 billingCycleId for row actions even though due-work identity is now canonical.
  • (2026-03-18) invoiceModification.ts now classifies prepayment behavior from is_prepayment rather than null/non-null billing_cycle_id; historical rows that predate the explicit flag may still need later read-side or backfill consideration.
  • (2026-03-18) billingAndTax.ts now sources ready recurring work only from recurring_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 legacy billing_cycle_id or billing_cycle_window semantics.
  • (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. billingCycleId remains 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_id and billing_cycle_window selector 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 accepting billing_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.ts harness compiling until that separate worktree change is cleaned up.
  • (2026-03-18) InvoiceService.ts list/detail classification now uses canonical recurring summary data. billing_cycle_id remains only as optional historical metadata or explicit include-side context.
  • (2026-03-19) InvoiceService.ts still had one stale read-side bridge seam in its historical billing-cycle include path: it was joining client_billing_cycles on legacy cycle_id / period_start / period_end column names. The hard-cutover read-side tests now lock the current billing_cycle_id / period_start_date / period_end_date metadata path instead.
  • (2026-03-19) Recurring reverse/delete actions still contained one bridge-era cleanup branch that deactivated or deleted client_billing_cycles after 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_periods windows 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 billingCycleId and hasBillingCycleBridge fields, but read-side invoice history types still legitimately expose billing_cycle_window and bridge metadata for historical context.
  • (2026-03-18) generateInvoiceHandler and scheduleRecurringWindowInvoiceGeneration are now selector-input-only. The old scheduleInvoiceGeneration(...) helper is left as an explicit hard failure if something still tries to schedule recurring work from a raw billingCycleId.
  • (2026-03-18) DB-backed verification for billingInvoiceTiming.integration.test.ts could not run locally because PostgreSQL was unavailable on 127.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 executionIdentityKey for live recurring failures. billingCycleId still 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, not billing_cycle_window. billing_cycle_id remains available on invoice DTOs only as optional historical metadata or explicit include-side context, not as the recurring classifier.
  • (2026-03-18) recurring_projection was dead compatibility metadata in the clean runtime paths. Canonical recurring reads now key directly off recurring_detail_periods; only the already-dirty billingInvoiceTiming.integration.test.ts still 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 deprecated getInvoicedBillingCyclesPaginated(...) alias remains only so the already-dirty billingInvoiceTiming.integration.test.ts harness can be cleaned up separately without mixing in its unrelated edits.
  • (2026-03-18) invoiceService.ts no longer uses invoice.billing_cycle_id to choose recurring linkage candidates. The next remaining bridge-like misclassification seam is still in invoiceModification.ts, where null/non-null billing_cycle_id is still used for prepayment logic (F046/F047).
  • (2026-03-18) invoiceModification.ts now classifies prepayment behavior from is_prepayment, and creditActions.ts now 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_cycles only 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.ts is currently blocked locally because PostgreSQL is unavailable on 127.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.ts no longer queries client_billing_cycles on the recurring bucket path. Bucket period resolution now derives current and rollover windows from recurring_service_periods and falls back to plan-anchor arithmetic only when no canonical service period exists.
  • (2026-03-18) billingEngine.ts already had the correct service-window behavior for hourly and usage recurring execution. F056 was 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.ts is also currently blocked locally because PostgreSQL is unavailable on 127.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 either defaultCadenceOwner or stored cadence reuse.
  • (2026-03-19) The last live contract_template_line_terms runtime 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 returning materializationGaps, 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_id as 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.ts still had one remaining bridge-era seam in its summary readers: they projected service-period ranges with stale invoice_charge_details assumptions 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. ApiInvoiceController was already selector-input-only for recurring preview/generate, but ApiFinancialController still accepted billing_cycle_id and routed through BillingEngine.calculateBilling(...); that path now uses canonical execution-window dates only.
  • (2026-03-19) Financial invoice write schemas now treat billing_cycle_id as 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 IBillingCycleInvoiceRequest interface 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_fallback and missing_source_context), but the hard-cutover architecture/runbook did not explain that posture. F075 is 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 on 127.0.0.1:5438 / ::1:5438.
  • (2026-03-19) The remaining explicit-kind coverage already existed in focused unit tests; F079 was completed by aligning those regressions to checklist items T046 and T047 and 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=false
  • cd 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=false
  • cd server && pnpm exec vitest run src/test/unit/billing/recurringServicePeriodDueSelection.domain.test.ts src/test/unit/billing/recurringTiming.domain.test.ts --coverage.enabled=false
  • cd 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=false
  • cd server && pnpm exec vitest run src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx src/test/unit/billing/contractPurchaseOrderSupport.ui.test.tsx --coverage.enabled=false
  • cd 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=false
  • cd 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=false
  • cd server && pnpm exec vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T321|T322/T328" --coverage.enabled=false
  • cd 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=false
  • cd 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=false
  • cd 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=false
  • cd server && pnpm exec vitest run src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/integration/api/invoiceService.recurringCoexistence.integration.test.ts src/test/integration/accounting/invoiceSelection.integration.test.ts --coverage.enabled=false
  • cd 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=false
  • cd server && pnpm exec vitest run src/test/unit/billing/invoiceService.fixedPersistence.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/billing/recurringInvoiceLinkage.static.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/billing/invoiceFinalization.kindClassification.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/billing/billingEngine.timing.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/billing/billingEngine.recurringExecution.static.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/billing/invoiceGeneration.preview.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/billing/invoiceGeneration.selectorInputGenerate.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/billing/updateClientBillingSchedule.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/infrastructure/billing/invoices/clientBillingCycleAnchors.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/billing/bucketUsageService.periods.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/billing/billingEngine.endExclusiveQueries.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/accounting/accountingExportInvoiceSelector.servicePeriods.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run ../packages/billing/tests/accountingExportInvoiceSelector.servicePeriods.wiring.test.ts ../packages/billing/tests/authoritativeRecurringReaders.servicePeriods.wiring.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/integration/accounting/invoiceSelection.integration.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/billing/recurringInvoiceHistory.static.test.ts --coverage.enabled=false
  • cd 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=false
  • cd server && pnpm exec vitest run ../packages/billing/src/actions/billingSettingsActions.cadenceOwnerDefaultsWiring.test.ts --coverage.enabled=false
  • cd 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=false
  • cd server && pnpm exec vitest run src/test/unit/docs/recurringInvoicingHardCutover.runbook.test.ts src/test/unit/billing/recurringDueWorkReader.static.test.ts --coverage.enabled=false
  • cd 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=false
  • cd server && pnpm exec vitest run src/test/unit/billing/invoiceQueries.recurringDetailRead.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/api/financialRecurringSchemaAudit.static.test.ts src/test/unit/api/invoiceRecurringSelectorInput.schema.test.ts --coverage.enabled=false
  • cd 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=false
  • cd 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=false
  • cd server && pnpm exec vitest run src/test/unit/billing/legacyRecurringIntegrationTests.static.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T051|T087|T088|T090" --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T051|T076|T077|T088|T090" --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/billing/invoiceFinalization.kindClassification.test.ts src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/unit/api/invoiceRecurringSelectorInput.schema.test.ts src/test/unit/api/invoiceService.recurringSelectorInput.test.ts --coverage.enabled=false
  • cd server && pnpm exec vitest run src/test/integration/billingInvoiceTiming.integration.test.ts -t "T016|T017|T018|T019|T076|T077" --coverage.enabled=false
  • cd 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=false
  • cd server && pnpm exec vitest run src/test/integration/api/invoiceService.recurringCoexistence.integration.test.ts --coverage.enabled=false
  • cd 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_cycles as cadence/source-rule infrastructure and optional historical context only.
  • (2026-03-18) F074 implemented by documenting invoices.billing_cycle_id as 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_id deprecation 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 billingCycleId only 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 billingCycleId from 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_id duplicate fallback, and keeping rerun or retry behavior keyed off canonical execution identity even when billing_cycle_id is retained as passive metadata.
  • (2026-03-18) F021/F022 implemented by making recurring preview/generate API schemas require canonical selector_input and reject billing_cycle_id request shapes or billing_cycle_window selector inputs.
  • (2026-03-18) F023/F024 implemented by proving the AutomaticInvoices preview/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(...) or previewInvoice(...), 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 billingCycleId and hasBillingCycleBridge fields from shared recurring identity interfaces, dropping shared billing_cycle_window builders, 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 billingCycleId from recurring preview/generation error payloads and duplicate errors so live recurring diagnostics key only on canonical executionIdentityKey.
  • (2026-03-18) F031/F032/F033/F034 implemented by renaming client recurring invoice DTO execution-window kind to client_cadence_window, keeping billing_cycle_id only as optional metadata, and cutting InvoiceService list/detail projection plus filter classification over to canonical recurring summary data without any invoices.billing_cycle_id fallback.
  • (2026-03-18) F035/F036 implemented by removing recurring_projection from invoice charge types, schema contracts, invoice model hydration, and workflow-event provenance so canonical recurring detail reads depend only on recurring_detail_periods plus summary parent period fields.
  • (2026-03-18) T010 implemented with a UI regression proving AutomaticInvoices can render, preview, and generate a client-cadence row whose billingCycleId metadata 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 updating AutomaticInvoices copy 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(...) and releaseRecurringServicePeriodInvoiceLinkageForInvoice(...) without mutating client_billing_cycles as the recurring primary object.
  • (2026-03-18) T012/T013 implemented by extending the existing client-cadence AutomaticInvoices UI regression so the same proof covers both preview and generate actions submitting canonical selector input with no billing_cycle_id.
  • (2026-03-18) F042/F043/F044/F045 implemented by making invoiceService.ts derive recurring linkage candidates from canonical config/window identity, matching both contract_line and client_contract_line obligations without consulting invoice.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_id and no longer suppresses missing-relation fallback errors.
  • (2026-03-18) F046/F047/F048 implemented by classifying invoice finalization behavior from explicit is_prepayment plus negative totals, persisting is_prepayment: true on prepayment creation, and proving bridge-less recurring invoices with null billing_cycle_id no 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_cycles during live execution.
  • (2026-03-18) T051 implemented with static and unit coverage proving the billing engines persisted recurring path no longer routes through getClientContractLinesAndCycle(...), validateBillingPeriod(...), or getBillingCycle(...) 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_cycles available 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_periods regeneration 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_periods schedule windows instead of preferring client_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_entries and usage usage_tracking against 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-tables 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_terms fallback 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 AutomaticInvoices as 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_details joins and adding a static guard against bridge-era summary assumptions.
  • (2026-03-19) F072/T070 implemented by removing billing_cycle_id from 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 IBillingCycleInvoiceRequest model, 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_context read 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_window expectations, 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_window and contract_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 InvoiceService historical billing-cycle metadata reads to current client_billing_cycles columns, adding read-side classification/detail/static coverage for passive bridge metadata versus canonical recurring summary data, and tightening recurring preview/generation diagnostic tests so billingCycleId never 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.

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?