Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
13 KiB
PRD — Client Contract Line Post-Drop Cutover
- Slug:
client-contract-line-post-drop-cutover - Date:
2026-03-19 - Status: Draft
Summary
Finish the post-client_contract_lines cutover so fully migrated environments run billing, recurring invoicing, contract authoring, and related read models entirely through the client-owned contract structure:
contractsas the client-owned contract headercontract_linesas the canonical line/runtime obligationclient_contractsas the client assignment and lifecycle window
This plan covers both:
- restoring runtime correctness in environments where the legacy
client_contract_*line tables have already been dropped - cleaning up stale code, tests, and plan/runbook references that still encode
client_contract_linesas a live dependency
The target end state is that no live billing or contract path requires the dropped client_contract_lines table, and recurring client-cadence obligations use a canonical post-drop identity consistent with the client-owned contract model.
Problem
The branch currently carries two contradictory models at once:
- the March 16 client-owned contracts work says non-template contracts are client-owned,
contract_linesare the canonical line records, andclient_contractsis the assignment/lifecycle layer - a later migration drops
client_contract_linesand related per-client line tables as redundant - some newer runtime code already follows that post-drop model
- several recurring, invoicing, contract-authoring, credit, bucket, report, and API paths still query
client_contract_linesdirectly or preserveclient_contract_lineas a first-class runtime identity - server-side contract-line APIs, billing overview reporting, credit flows, and test/runbook fixtures still encode the dropped table as the live assignment model
In a fully migrated environment this causes real breakage, not just cleanup debt:
AutomaticInvoicescan 500 while loading recurring due work- recurring preview/generate can fail when resolving selector-input windows
- service-period repair and regeneration paths can fail
- linkage, bucket, and contract authoring paths can drift between old and new structures
- tests can still pass because they mock or assert the removed table as expected live behavior
The current state is therefore unsafe:
- migrations say the old structure is gone
- runtime code still depends on it
- tests and docs partially protect the old behavior instead of the intended end state
Goals
- Make fully migrated environments work without
client_contract_linesor related dropped client-contract line tables. - Standardize live contract/billing runtime around:
contractscontract_linesclient_contracts
- Define one canonical post-drop identity for recurring client-cadence obligations.
- Remove live recurring/invoicing dependence on
client_contract_line_idandobligation_type = 'client_contract_line'where those concepts only preserve the removed table. - Update contract authoring and mutation paths so they no longer write dropped client-contract line tables.
- Update dependent read models, reports, and linkage flows so they no longer query dropped tables.
- Update server-side contract-line service paths so deprecated client-line assumptions do not leak back into live runtime behavior.
- Remove or rewrite stale tests/static guards that preserve the removed structure as expected behavior.
- Update plan/runbook/docs language so future work does not regress back to the dropped structure.
Non-goals
- Reintroducing
client_contract_linesor any other dropped client-contract line table. - Reversing the client-owned contract model established in the March 16 simplification work.
- Redesigning contract templates, invoicing UI, or recurring cadence ownership beyond what is necessary to align them to the post-drop structure.
- Rewriting historical invoice financial data unless linkage/read-side migration is required for correctness.
- Broad billing-domain cleanup outside the specific dropped-table and recurring-obligation identity mismatch.
Users and Primary Flows
-
Billing admin
- Creates a client-owned contract and contract lines.
- Generates recurring invoices from
AutomaticInvoices. - Previews, generates, reverses, or deletes recurring invoices without hidden dependence on removed tables.
-
Finance / operations
- Reviews due recurring work in fully migrated environments.
- Uses service-period inspection and regeneration without schema mismatch failures.
- Trusts recurring history, linkage repair, and bucket-derived calculations after migration.
-
Engineers / support
- Can reason about one contract-line runtime model.
- Do not have to special-case “post-drop environments” versus “legacy environments” in normal billing code.
- Can rely on tests to protect the intended client-owned post-drop structure.
UX / UI Notes
- This plan is primarily structural/runtime cleanup, but several user-facing surfaces must stop breaking or implying old lineage:
AutomaticInvoices- recurring preview / generate
- recurring service-period management
- contract authoring/wizard flows
- contract line editing/configuration
- recurring history / reverse / delete affordances
- No new major UI is required.
- Existing UI copy that refers to service periods or client-owned contracts should remain, but hidden legacy dependencies must be removed so the UI actually works in migrated tenants.
- Any operator-facing error about missing service periods should be a canonical repair action, not a missing-table or dropped-structure crash.
Requirements
Functional Requirements
-
Canonical post-drop contract structure
- Live billing and recurring paths must treat
contract_linesas the canonical recurring/service obligation record. - Client ownership must resolve through
contracts.owner_client_id. - Assignment lifecycle windows must resolve through
client_contracts.
- Live billing and recurring paths must treat
-
Recurring obligation identity
- The implementation must define one canonical post-drop identity for client-cadence recurring obligations.
- Selector-input due work, materialization, regeneration, invoice linkage, reverse/delete repair, and duplicate prevention must all use that same identity.
- Legacy
client_contract_linerecurring identity may remain only as passive historical compatibility metadata if required, not as a live table dependency.
-
Due-work and invoicing
getAvailableRecurringDueWork()must work in environments whereclient_contract_linesdoes not exist.- Recurring due-work selection, preview, and generation must not join dropped tables.
AutomaticInvoicesmust load, preview, and generate recurring rows in a fully migrated environment.
-
Service-period lifecycle
- Recurring service-period inspection, regeneration, and repair actions must resolve obligation metadata without dropped tables.
- Client-cadence regeneration must load obligations from the surviving structure and persist records keyed to the canonical post-drop identity.
-
Contract authoring and mutation
- Contract wizard and related create/update flows must stop inserting or updating dropped client-contract line tables.
- Contract line mutation paths must use the surviving structures only.
-
Linkage and dependent billing flows
- Invoice/service-period linkage must resolve recurring obligation candidates without
client_contract_lines. - Bucket period resolution and related recurring helpers must use the surviving structure.
- Credit or other dependent billing flows that still look up client-contract line rows must be rewritten or removed.
- Invoice/service-period linkage must resolve recurring obligation candidates without
-
Reports / read models / API
- Read models and report definitions must stop querying dropped client-contract line tables as live fact sources.
- Server/package contract-line services that still use dropped client-line tables for unassign/deactivate, usage analytics, in-use checks, overlap validation, or overview counts must be rewritten or removed from live paths.
- API/services that remain intentionally legacy must be clearly isolated and must not be called by live billing/runtime paths.
-
Tests and docs cleanup
- Static tests and integration tests that currently assert
client_contract_linesusage as live expected behavior must be updated or removed. - Plans/runbooks/docs that still describe
client_contract_linesas an expected live runtime table must be corrected or explicitly marked historical. - Cleanup-only references in teardowns, fixtures, tracked backup files, and stale runbooks should be removed so new work does not keep cargo-culting the dropped structure.
- Static tests and integration tests that currently assert
Non-functional Requirements
- No live runtime path should crash simply because a fully migrated database no longer contains
client_contract_lines. - The cutover should reduce structural ambiguity, not add more compatibility layers.
- Tests must fail if a live billing/runtime path reintroduces dropped-table dependence.
- The plan should prefer one clear canonical model over mixed old/new identity shims.
Data / API / Integrations
-
Runtime data shape
contracts.owner_client_idremains the client ownership anchor.client_contractsremains the assignment/lifecycle source.contract_linesremains the canonical recurring/service obligation row.
-
Recurring identity cleanup
- The current use of
obligation_type = 'client_contract_line'andclient_contract_line_idin recurring service periods must be reviewed and either:- migrated to canonical
contract_lineidentity, or - formally retained as a logical compatibility identity that no longer depends on a dropped table.
- migrated to canonical
- The chosen approach must be applied consistently across:
- materialization
- due-work selection
- preview/generate
- linkage
- regeneration
- reverse/delete repair
- The current use of
-
API/services
- Server and package services that still expose or derive live behavior from
client_contract_linesmust be rewritten or clearly deprecated away from runtime codepaths. - Report definitions and billing overview readers must use surviving contract/assignment structures.
- Server and package services that still expose or derive live behavior from
Security / Permissions
- No new permissions are required.
- Existing billing, contract, invoice, and recurring-service-period permissions remain the guardrails.
- Cleanup must not bypass any existing authorization checks while rewriting dropped-table lookups.
Observability
- Errors that currently manifest as missing-relation failures should become explicit domain failures only where a real business precondition is missing.
- The plan does not require new observability infrastructure, but runtime failures should become diagnosable from the canonical structure instead of surfacing raw dropped-table errors.
Rollout / Migration
- This work assumes the migration dropping
client_contract_linesand related tables has already run in target environments. - Runtime code must therefore treat those tables as unavailable, not optional.
- The implementation sequence should be:
- unblock recurring due-work /
AutomaticInvoices - unblock recurring preview/generate/linkage/repair
- fix regeneration and service-period management
- remove dropped-table writes from contract authoring and mutation paths
- clean up dependent read models, contract-line services, credits, buckets, and reports
- rewrite stale tests/docs/runbooks that preserve the old structure
- remove tracked backup artifacts and low-value cleanup references that keep the removed table visible in active development flows
- unblock recurring due-work /
- If any historical data or compatibility read path still requires legacy recurring identity fields, keep them as passive metadata only and document the boundary clearly.
Open Questions
- Should recurring client-cadence obligations migrate fully to
obligation_type = 'contract_line', or should the code retain a logicalclient_contract_lineidentity that is no longer backed by a physical table? - If any historical invoices or recurring records still encode legacy line identity, what minimal read-side compatibility is required to keep them explainable without preserving old runtime joins?
- Which deprecated server/package services should be cleaned up in this plan versus explicitly deferred once live billing/runtime paths are fixed?
- Which stale historical plan/design docs should be left untouched but explicitly treated as historical, versus corrected because they are still used as operational guidance?
Acceptance Criteria (Definition of Done)
AutomaticInvoicesloads successfully in a fully migrated environment with noclient_contract_linestable.- Recurring due-work, preview, generate, reverse/delete repair, and service-period management no longer query dropped client-contract line tables.
- Contract wizard and related contract-authoring flows do not write dropped client-contract line tables.
- Invoice linkage, bucket timing, and dependent recurring/billing helpers work through the surviving contract-owned structure.
- Live report/read-model and contract-line service paths no longer depend on
client_contract_lines. - Tests and static guards no longer preserve dropped-table dependence as expected live behavior.
- Documentation and runbooks describe the post-drop client-owned structure accurately, and tracked backup artifacts or stale cleanup fixtures no longer point engineers back at the removed table.