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

212 lines
13 KiB
Markdown

# 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:
- `contracts` as the client-owned contract header
- `contract_lines` as the canonical line/runtime obligation
- `client_contracts` as 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_lines` as 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_lines` are the canonical line records, and `client_contracts` is the assignment/lifecycle layer
- a later migration drops `client_contract_lines` and 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_lines` directly or preserve `client_contract_line` as 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:
- `AutomaticInvoices` can 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_lines` or related dropped client-contract line tables.
- Standardize live contract/billing runtime around:
- `contracts`
- `contract_lines`
- `client_contracts`
- Define one canonical post-drop identity for recurring client-cadence obligations.
- Remove live recurring/invoicing dependence on `client_contract_line_id` and `obligation_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_lines` or 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
1. Creates a client-owned contract and contract lines.
2. Generates recurring invoices from `AutomaticInvoices`.
3. Previews, generates, reverses, or deletes recurring invoices without hidden dependence on removed tables.
- Finance / operations
1. Reviews due recurring work in fully migrated environments.
2. Uses service-period inspection and regeneration without schema mismatch failures.
3. Trusts recurring history, linkage repair, and bucket-derived calculations after migration.
- Engineers / support
1. Can reason about one contract-line runtime model.
2. Do not have to special-case “post-drop environments” versus “legacy environments” in normal billing code.
3. 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_lines` as the canonical recurring/service obligation record.
- Client ownership must resolve through `contracts.owner_client_id`.
- Assignment lifecycle windows must resolve through `client_contracts`.
- 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_line` recurring 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 where `client_contract_lines` does not exist.
- Recurring due-work selection, preview, and generation must not join dropped tables.
- `AutomaticInvoices` must 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.
- 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_lines` usage as live expected behavior must be updated or removed.
- Plans/runbooks/docs that still describe `client_contract_lines` as 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.
### 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_id` remains the client ownership anchor.
- `client_contracts` remains the assignment/lifecycle source.
- `contract_lines` remains the canonical recurring/service obligation row.
- Recurring identity cleanup
- The current use of `obligation_type = 'client_contract_line'` and `client_contract_line_id` in recurring service periods must be reviewed and either:
- migrated to canonical `contract_line` identity, or
- formally retained as a logical compatibility identity that no longer depends on a dropped table.
- The chosen approach must be applied consistently across:
- materialization
- due-work selection
- preview/generate
- linkage
- regeneration
- reverse/delete repair
- API/services
- Server and package services that still expose or derive live behavior from `client_contract_lines` must be rewritten or clearly deprecated away from runtime codepaths.
- Report definitions and billing overview readers must use surviving contract/assignment structures.
## 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_lines` and related tables has already run in target environments.
- Runtime code must therefore treat those tables as unavailable, not optional.
- The implementation sequence should be:
1. unblock recurring due-work / `AutomaticInvoices`
2. unblock recurring preview/generate/linkage/repair
3. fix regeneration and service-period management
4. remove dropped-table writes from contract authoring and mutation paths
5. clean up dependent read models, contract-line services, credits, buckets, and reports
6. rewrite stale tests/docs/runbooks that preserve the old structure
7. remove tracked backup artifacts and low-value cleanup references that keep the removed table visible in active development flows
- 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 logical `client_contract_line` identity 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)
- `AutomaticInvoices` loads successfully in a fully migrated environment with no `client_contract_lines` table.
- 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.