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

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:

  • 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.