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

369 lines
82 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
- Documentation slice completed:
- Added [ARCHITECTURE.md](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/ee/docs/plans/2026-03-18-recurring-invoicing-hard-cutover/ARCHITECTURE.md) to define the hard-cutover invariant, the limited role of `client_billing_cycles`, the required-schema posture, and the `invoices.billing_cycle_id` deprecation posture.
- Added [RUNBOOK.md](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/ee/docs/plans/2026-03-18-recurring-invoicing-hard-cutover/RUNBOOK.md) to describe the final recurring mental model, missing-materialization diagnosis, and canonical reverse/delete repair expectations.
- Added [recurringInvoicingHardCutover.runbook.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/docs/recurringInvoicingHardCutover.runbook.test.ts) to lock T072/T073/T074 against regressions.
- Due-work cutover slice completed:
- Updated [billingAndTax.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/shared/billingClients/recurringRunExecutionIdentity.ts) to add canonical `client_cadence_window` identity keyed by schedule/period/window instead of `billingCycleId`.
- Updated [recurringDueWork.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/shared/billingClients/recurringDueWork.ts) so client-cadence due-work rows can be built bridge-free while still carrying optional `billingCycleId` metadata for display.
- Updated [billingEngine.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/lib/billing/billingEngine.ts) to stop treating missing recurring-service-period relations as an acceptable mixed-schema fallback.
- Added/updated focused tests:
- [recurringDueWork.domain.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringDueWork.domain.test.ts)
- [recurringDueWorkReader.integration.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringDueWorkReader.integration.test.ts)
- [recurringDueWorkReader.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringDueWorkReader.static.test.ts)
- Recurring run-orchestration slice completed:
- Updated [recurringBillingRunActions.shared.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/recurringBillingRunActions.shared.ts) so recurring run targets always carry canonical `selectorInput` plus execution-window identity; client-cadence target mapping now starts from canonical due-work rows instead of billing periods.
- Updated [recurringBillingRunActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/recurringBillingRunActions.ts) so recurring batch selection reads only `getAvailableRecurringDueWork(...)`, recurring runs no longer accept raw `billingCycleIds`, and execution always delegates through `generateInvoiceForSelectionInput(...)`.
- Updated [AutomaticInvoices.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/shared/workflow/streams/domainEventBuilders/recurringBillingRunEventBuilders.ts) and [billingEventSchemas.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/shared/workflow/runtime/schemas/billingEventSchemas.ts) so run payloads use `client_cadence_window`, `contract_cadence_window`, or `mixed_execution_windows`, rather than treating billing-cycle windows as the default live recurring mode.
- Added/updated focused tests:
- [recurringBillingRunActions.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringBillingRunActions.test.ts)
- [recurringBillingRunActions.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringBillingRunActions.static.test.ts)
- [recurringBillingRunWorkflowEvents.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringBillingRunWorkflowEvents.test.ts)
- [recurringBillingRunWindowIdentity.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringBillingRunWindowIdentity.test.ts)
- [automaticInvoices.recurringDueWork.ui.test.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx)
- [contractPurchaseOrderSupport.ui.test.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/contractPurchaseOrderSupport.ui.test.tsx)
- Billing-calculation and duplicate-detection slice completed:
- Updated [invoiceGeneration.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/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 canonical `recurring_service_periods` schedule/period linkage before any legacy billing-cycle fallback.
- Updated [billingEngine.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/lib/billing/billingEngine.ts) to remove the unused billing-cycle parameter from persisted recurring due-selection loading.
- Added/updated focused tests:
- [invoiceGeneration.recurringSelection.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.recurringSelection.test.ts)
- [invoiceGeneration.duplicate.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.duplicate.test.ts)
- [invoiceGeneration.duplicate.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.duplicate.static.test.ts)
- [invoiceGeneration.selectorInputGenerate.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.selectorInputGenerate.test.ts)
- [invoiceGeneration.preview.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.preview.test.ts)
- [invoiceGeneration.emptyResult.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.emptyResult.test.ts)
- [invoiceGeneration.zeroDollarFinalization.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.zeroDollarFinalization.test.ts)
- Billing-engine recurring execution slice completed:
- Updated [billingEngine.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/lib/billing/billingEngine.ts) so selector-input recurring due-work loading and execution-window billing stay on canonical persisted service-period selections, bypass `validateBillingPeriod(...)` for `recurringTimingSelectionSource = "persisted"`, and never call `getClientContractLinesAndCycle(...)` or `getBillingCycle(...)` 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:
- [billingEngine.timing.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/billingEngine.timing.test.ts)
- [billingEngine.recurringExecution.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/billingEngine.recurringExecution.static.test.ts)
- Selector-input window-validation slice completed:
- Updated [invoiceGeneration.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/invoiceGeneration.ts) so `normalizeRecurringSelectorInput(...)` canonicalizes client-cadence and contract-cadence selector input by looking up persisted `recurring_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:
- [invoiceGeneration.preview.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.preview.test.ts)
- [invoiceGeneration.selectorInputGenerate.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceGeneration.selectorInputGenerate.test.ts)
- Accounting export service-period slice completed:
- Updated [accountingExportInvoiceSelector.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/services/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_fallback` output instead of preserving invoice-header recurring provenance.
- Kept `invoice_header_fallback` confined to passive historical payload/read-side compatibility; live export selection no longer emits mixed canonical-vs-header recurring provenance.
- Added/updated focused tests:
- [accountingExportInvoiceSelector.servicePeriods.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/accounting/accountingExportInvoiceSelector.servicePeriods.test.ts)
- [accountingExportInvoiceSelector.servicePeriods.wiring.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/tests/accountingExportInvoiceSelector.servicePeriods.wiring.test.ts)
- [authoritativeRecurringReaders.servicePeriods.wiring.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/tests/authoritativeRecurringReaders.servicePeriods.wiring.test.ts)
- [invoiceSelection.integration.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/integration/accounting/invoiceSelection.integration.test.ts)
- Recurring dashboard copy/actions slice completed:
- Updated [AutomaticInvoices.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/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:
- [AutomaticInvoices.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/AutomaticInvoices.tsx)
- [billingAndTax.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/billingAndTax.ts)
- [recurringBillingRunActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/recurringBillingRunActions.ts)
- [recurringBillingRunActions.shared.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/recurringBillingRunActions.shared.ts)
- [billingCycleActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/billingCycleActions.ts)
- [accountingExportInvoiceSelector.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/services/accountingExportInvoiceSelector.ts)
- API/contracts sweep:
- recurring preview/generate request contracts, invoice DTOs, recurring shared types, and invoice list/read classification still preserve `billing_cycle_id` as a recurring concept.
- Notable files:
- [ApiInvoiceController.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/controllers/ApiInvoiceController.ts)
- [invoiceSchemas.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/schemas/invoiceSchemas.ts)
- [InvoiceService.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/services/InvoiceService.ts)
- [recurringTiming.interfaces.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/types/src/interfaces/recurringTiming.interfaces.ts)
- [invoice.interfaces.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/types/src/interfaces/invoice.interfaces.ts)
- Runtime/history sweep:
- billing engine, invoice generation, invoice linkage, bucket usage, schedule changes, jobs, and migrations still preserve bridge-era assumptions.
- Notable files:
- [billingEngine.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/lib/billing/billingEngine.ts)
- [invoiceGeneration.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/invoiceGeneration.ts)
- [invoiceService.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/services/invoiceService.ts)
- [bucketUsageService.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/services/bucketUsageService.ts)
- [billingScheduleActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/billingScheduleActions.ts)
- [20241130164200_add_billing_cycle_id_to_invoices.cjs](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/migrations/20241130164200_add_billing_cycle_id_to_invoices.cjs)
- Recurring invoice-linkage slice completed:
- [invoiceService.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/services/invoiceService.ts) now builds recurring linkage candidates from canonical config and invoice-window data, then matches `recurring_service_periods` across explicit `contract_line` and `client_contract_line` obligations without any `invoice.billing_cycle_id` branch.
- 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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringInvoiceLinkage.static.test.ts) to lock out `billing_cycle_id`-driven widening and mixed-schema fallback logic.
- Invoice-kind classification slice completed:
- [invoiceModification.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/invoiceModification.ts) now classifies prepayment handling from explicit invoice kind (`is_prepayment`) and negative totals, rather than treating `billing_cycle_id = null` as a proxy.
- [creditActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/creditActions.ts) now persists `is_prepayment: true` when creating prepayment invoices so finalization and later reads use the same explicit kind signal.
- Added behavior and static coverage in [invoiceFinalization.kindClassification.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceFinalization.kindClassification.test.ts), plus a prepayment persistence assertion in [prepaymentInvoice.periodPolicy.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts).
- Client schedule-regeneration slice completed:
- Added [clientCadenceScheduleRegeneration.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/clientCadenceScheduleRegeneration.ts) to derive the last billed client boundary, load client-cadence obligations, and regenerate future `recurring_service_periods` with canonical `billing_schedule_changed` / `backfill_materialization` provenance instead of mutating future `client_billing_cycles`.
- Updated [billingScheduleActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/billingScheduleActions.ts) and [billingCycleAnchorActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/updateClientBillingSchedule.test.ts) and rewrote the DB-backed schedule expectations in [clientBillingCycleAnchors.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/infrastructure/billing/invoices/clientBillingCycleAnchors.test.ts) to assert recurring-service-period regeneration and preserved schedule-administration cycles instead of future-cycle deactivation.
- Bucket recurring-period slice completed:
- Updated [bucketUsageService.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/services/bucketUsageService.ts) so current and previous bucket allowance periods resolve from canonical `recurring_service_periods` by obligation and schedule key, with no live `client_billing_cycles` lookup in the recurring bucket path.
- Replaced the old client-cycle-based unit coverage in [bucketUsageService.periods.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/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`.
- Hourly/usage service-window verification slice completed:
- Confirmed [billingEngine.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/lib/billing/billingEngine.ts) already uses `servicePeriodStartExclusive` / `servicePeriodEndExclusive` to query `time_entries` and `usage_tracking` for persisted recurring selections; no product-code change was required for the hard-cutover behavior.
- Extended [billingEngine.endExclusiveQueries.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/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.
- Cadence-authoring cleanup slice completed:
- Updated [recurringAuthoringPolicy.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/shared/billingClients/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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/billingSettingsActions.ts), [packages/types billing.interfaces.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/types/src/interfaces/billing.interfaces.ts), and [server billing.interfaces.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/interfaces/billing.interfaces.ts) so billing settings expose explicit client defaults and `mixed_enabled` metadata without blocking contract cadence updates behind rollout-era throws.
- Removed the dead rollout validator wrappers from [contractLineSchemas.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/schemas/contractLineSchemas.ts) and [financialSchemas.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/schemas/financialSchemas.ts), leaving cadence-owner defaults only at explicit UI/action/service boundaries.
- Aligned [contractLineRepository.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/repositories/contractLineRepository.ts) with the package-side repository by removing live `contract_template_line_terms` reads from template detail and template-clone paths; only the cleanup delete in [contractTemplate.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/models/contractTemplate.ts) still references that table.
- Extended focused wiring/static coverage:
- [cadenceOwnerRollout.actions.wiring.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/tests/cadenceOwnerRollout.actions.wiring.test.ts)
- [contractWizardCadenceOwner.wiring.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/tests/contractWizardCadenceOwner.wiring.test.ts)
- [recurrenceStorageModel.contract.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurrenceStorageModel.contract.test.ts)
- Service-period troubleshooting slice completed:
- Added [recurringServicePeriodActions.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/RecurringServicePeriodsTab.tsx), and wired it into [BillingDashboard.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/BillingDashboard.tsx) plus [billingTabsConfig.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/billingTabsConfig.ts), so operators can inspect generated, edited, billed, and corrective service-period state by schedule key.
- Updated [AutomaticInvoices.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/AutomaticInvoices.tsx) to render `materializationGaps` as 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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/migrations/20260318194500_add_recurring_service_period_permissions.cjs) so admins/managers receive the recurring service-period `view`, `manage_future`, `regenerate`, and `correct_history` permissions required by the new operator surface.
- Added/updated focused tests:
- [recurringServicePeriodActions.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringServicePeriodActions.test.ts)
- [recurringServicePeriodsTab.ui.test.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringServicePeriodsTab.ui.test.tsx)
- [automaticInvoices.recurringDueWork.ui.test.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/automaticInvoices.recurringDueWork.ui.test.tsx)
- Required-schema and wording cleanup slice completed:
- Updated [recurringInvoicingHardCutover.runbook.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/docs/recurringInvoicingHardCutover.runbook.test.ts) to lock the hard-cutover architectures 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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/billingAndTax.ts) away from `compatibilityPeriods`, and removed the dead `mergeRecurringDueWorkRows(...)` helper from [recurringDueWork.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/shared/billingClients/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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/jobs/generateInvoiceHandler.recurringExecutionIdentity.test.ts) so the recurring invoice job payload contract is explicitly selector-input plus canonical execution-window identity, with no raw `billingCycleId` requirement.
- Updated [recurringBillingRunWorkflowEvents.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringBillingRunWorkflowEvents.test.ts) to prove recurring run failure events identify runs with `selectionKey`, `retryKey`, `selectionMode`, `windowIdentity`, and `executionWindowKinds`, and do not emit bridge-specific fields.
- Invoice-query summary cleanup slice completed:
- Updated [invoiceQueries.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/invoiceQueries.ts) so invoice list, client, and contract summary readers derive `service_period_start` / `service_period_end` through `invoice_charges -> invoice_charge_details` joins, rather than stale direct-detail assumptions or bridge-era summary naming.
- Updated [invoiceQueries.recurringDetailRead.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/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.
- API schema audit slice completed:
- Updated [financialSchemas.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/schemas/financialSchemas.ts) so financial invoice write requests no longer expose `billing_cycle_id`, the dead `billingCycleInvoiceRequestSchema` is removed, and `calculateBillingSchema` now rejects raw cycle-ID bridge input while preserving billing-cycle fields only on schedule-administration schemas and invoice read metadata.
- Updated [ApiFinancialController.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/controllers/ApiFinancialController.ts) and [FinancialService.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/api/services/FinancialService.ts) so `/api/v1/financial/billing/calculate` executes through canonical execution-window billing instead of forwarding a `billing_cycle_id` handle into the legacy cycle-based engine path.
- Added [financialRecurringSchemaAudit.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/api/financialRecurringSchemaAudit.static.test.ts) to lock T070 against non-schedule API schemas reintroducing billing-cycle recurring request contracts.
- Shared interface separation slice completed:
- Updated [recurringTiming.interfaces.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/types/src/interfaces/recurringTiming.interfaces.ts) so canonical `IRecurringInvoiceWindow` no longer carries a dead `billingCycleId` bridge field.
- Updated [packages/types billing.interfaces.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/types/src/interfaces/billing.interfaces.ts) and [server billing.interfaces.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/interfaces/billing.interfaces.ts) to remove the unused `IBillingCycleInvoiceRequest` interface now that cycle-ID invoice request schemas are no longer part of the hard-cutover recurring contract surface.
- Added [recurringInterfaceSeparation.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/recurringInterfaceSeparation.static.test.ts) to lock T071 against recurring execution models or shared/server billing interface files growing cycle-bridge request models back in.
- Historical read-side strategy slice completed:
- Updated [ARCHITECTURE.md](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/ee/docs/plans/2026-03-18-recurring-invoicing-hard-cutover/ARCHITECTURE.md) with a dedicated historical read-side strategy section defining `canonical_recurring`, `financial_document_fallback`, and `missing_source_context` as read-only migration states, not live recurring execution modes.
- Updated [RUNBOOK.md](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/ee/docs/plans/2026-03-18-recurring-invoicing-hard-cutover/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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/docs/recurringInvoicingHardCutover.runbook.test.ts) to lock that fallback posture against regressions, alongside the existing read-side unit coverage in [invoiceModel.servicePeriods.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceModel.servicePeriods.test.ts) and [creditActions.servicePeriods.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/creditActions.servicePeriods.test.ts).
- Legacy recurring test cleanup slice completed:
- Updated [billingInvoiceTiming.integration.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/integration/billingInvoiceTiming.integration.test.ts) so the remaining client-cadence happy-path/history/reversal assertions (`T051`, `T087`, `T088`, `T090`) use canonical selector input and `client_cadence_window` expectations instead of raw `billingCycleId` generation or `billing_cycle_window` compatibility assertions.
- Added [legacyRecurringIntegrationTests.static.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/legacyRecurringIntegrationTests.static.test.ts) to lock T081 against bridge-era compatibility titles and `billing_cycle_window` assertions creeping back into the recurring timing integration harness.
- Canonical recurring regression slice completed:
- Updated [billingInvoiceTiming.integration.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/integration/billingInvoiceTiming.integration.test.ts) so the contract-cadence and client-cadence happy-path integration cases are now explicit checklist regressions `T077` and `T076`, both exercising due-work selection, canonical preview, selector-input generation, and recurring history with `contract_cadence_window` / `client_cadence_window` identity rather than raw billing-cycle execution.
- Invoice-kind regression slice completed:
- Updated [invoiceFinalization.kindClassification.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/invoiceFinalization.kindClassification.test.ts) so the explicit-kind regression cases are aligned to checklist items `T046` and `T047`, covering both bridge-less recurring finalization and true prepayment credit behavior after the explicit `is_prepayment` cutover.
- Revalidated [prepaymentInvoice.periodPolicy.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/billing/prepaymentInvoice.periodPolicy.test.ts) alongside that suite to keep the prepayment financial-document policy anchored to the same explicit kind model.
- Preview/generate request rejection tests completed:
- Revalidated [invoiceRecurringSelectorInput.schema.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/api/invoiceRecurringSelectorInput.schema.test.ts) and [invoiceService.recurringSelectorInput.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/unit/api/invoiceService.recurringSelectorInput.test.ts), which already lock checklist items `T014` and `T015` at both schema and service-routing layers for recurring preview/generate rejection of raw `billing_cycle_id` selectors.
- Canonical preview/generate success tests completed:
- Updated [billingInvoiceTiming.integration.test.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/integration/billingInvoiceTiming.integration.test.ts) so the canonical happy-path integrations now explicitly cover `T016/T018/T076` for client cadence and `T017/T019/T077` for contract cadence, proving both preview and selector-input generation succeed without requiring a live billing-cycle bridge.
## 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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/actions/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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/lib/repositories/contractLineRepository.ts); they are now removed. The only remaining non-test reference is the cleanup delete in [contractTemplate.ts](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/models/contractTemplate.ts), which is intentionally retained for template cleanup.
- (2026-03-19) `getAvailableRecurringDueWork(...)` was already returning `materializationGaps`, but [AutomaticInvoices.tsx](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/packages/billing/src/components/billing-dashboard/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](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/server/src/test/integration/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`.
## Links / References
- Broad architecture:
- [PRD.md](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/ee/docs/plans/2026-03-16-service-period-first-billing-and-cadence-ownership/PRD.md)
- Softer cutover plan:
- [PRD.md](/Users/roberisaacs/alga-psa.worktrees/feature/client-owned-contracts-simplification/ee/docs/plans/2026-03-18-service-driven-invoicing-cutover/PRD.md)
## 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?