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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
12 KiB
12 KiB
Scratchpad — Template Runtime Normalization Completion
- Plan slug:
template-runtime-normalization-completion - Date opened:
2026-03-20
Scope Notes
- This is the cleanup remainder after the service-driven invoicing cutover, not a reopening of that broader billing work.
- The focus is the remaining template/runtime ambiguity around
template_contract_id, contract application, line cloning/setup, service-layer helpers, and legacy decoupling scripts. - Core billing fallback removal already landed elsewhere and should be treated as prerequisite context, not repeated scope.
Discoveries
packages/clients/src/actions/clientContractActions.tsstill resolves template source usingclientContract.template_contract_id ?? clientContract.contract_id ?? nullinapplyContractToClient(...).packages/clients/src/actions/clientContractLineActions.tsstill selectstemplate_contract_idalongsidecontract_idand still resolves template source withclientContract.template_contract_id ?? clientContract.contract_id.server/src/lib/api/services/ContractLineService.tsstill contains template-source fallback logic in client-owned mutation/setup paths.server/scripts/contract-template-decoupling.tsstill backfills or depends ontemplate_contract_idfallback semantics.packages/billing/src/models/contract.tsand related runtime readers still join template metadata for display/provenance. These joins need classification as allowed metadata reads versus behaviorally dangerous runtime widening.
Prior Plan Context
- The March 18 service-driven invoicing cutover already tracked and completed several adjacent cleanup items:
F083remove template fallback joins from live billing computationsF084split instantiated contract detail/assignment loading from template detail loadingF085stop runtime backfill writes toclient_contracts.template_contract_idF086extend static guards for post-drop table usage
- This new plan should pick up only what remains after those changes.
Working Decision
- Treat
template_contract_idas suspicious by default:- allowed only when explicitly serving provenance metadata
- forbidden when used as fallback runtime identity
- If a flow still needs authoring source data, make that dependency explicit and fail closed when the source cannot be determined.
Open Questions
- Keep
client_contracts.template_contract_idas provenance-only metadata for renewals/draft resume/debugging, or drive toward making it removable in a later migration? - Which remaining template joins in runtime loaders are genuinely operator-useful provenance reads versus accidental mixed-model leakage?
Candidate Commands
rg -n "template_contract_id|coalesce\\(cc\\.template_contract_id|template_contract_id \\?\\?|contract_templates as template" packages/billing packages/clients serverpnpm exec vitest run --coverage.enabled=false <targeted tests>jq empty ee/docs/plans/2026-03-20-template-runtime-normalization-completion/features.jsonjq empty ee/docs/plans/2026-03-20-template-runtime-normalization-completion/tests.json
2026-03-20 F001/T001 Inventory Classification
- Completed preflight runtime inventory with:
rg -n "template_contract_id|template_contract_id \\?\\?|coalesce\\([^\\n]*template_contract_id|contract_templates as template" packages/clients/src packages/billing/src server/src/lib/api/services server/scripts
Forbidden runtime fallback (must remove)
packages/clients/src/actions/clientContractActions.tsapplyContractToClient(...)usesclientContract.template_contract_id ?? clientContract.contract_id ?? nullbefore cloning services/config.
packages/clients/src/actions/clientContractLineActions.tsaddClientContractLine(...)loads bothtemplate_contract_idandcontract_id, then falls back withclientContract.template_contract_id ?? clientContract.contract_id.
server/src/lib/api/services/ContractLineService.tsassignPlanToClient(...)loads both IDs and falls back withclientContract.template_contract_id ?? clientContract.contract_id ?? null.
server/scripts/contract-template-decoupling.ts- Backfills
client_contracts.template_contract_id = contract_idwhen missing. - Uses
clientContract?.template_contract_id ?? clientContract?.contract_id ?? nullwhen cloning.
- Backfills
Legacy cleanup target
server/scripts/contract-template-decoupling.ts- Script behavior is coupled to hybrid model (
client_contract_lines+ fallback backfill) and should be retired or rewritten to provenance-only semantics.
- Script behavior is coupled to hybrid model (
Allowed provenance-only references (not runtime fallback)
packages/billing/src/models/contract.tsgetAllWithClients(...)left-joinscontract_templatesbycc.template_contract_idonly to expose template name metadata for list views.
packages/billing/src/actions/contractActions.tsgetDraftContracts(...)joinscontract_templatesand selectscc.template_contract_idas draft metadata.
packages/billing/src/actions/renewalsQueueActions.ts- Optional-column guard + copy-through of
template_contract_idinto draft assignment metadata (no fallback derivation).
- Optional-column guard + copy-through of
packages/billing/src/actions/contractWizardActions.ts- Writes
template_contract_id: submission.template_id ?? nulland reads backtemplate_id: clientContract.template_contract_id ?? undefinedfor draft resume UX.
- Writes
packages/billing/src/lib/billing/billingEngine.ts- Selects
cc.template_contract_idas provenance field while live joins remain anchored oncc.contract_id -> contract_lines.
- Selects
packages/clients/src/actions/clientContractLineActions.tsgetClientContractLine(...)selectscc.template_contract_idin response payload.- Update/edit destructuring drops inbound
template_contract_idfrom mutable update fields.
packages/clients/src/models/clientContractLine.ts- Selects
cc.template_contract_idin DTO shape and strips it from mutation payload updates.
- Selects
packages/clients/src/actions/clientContractActions.tsandpackages/clients/src/models/clientContract.ts- New client contract inserts set
template_contract_id: nullexplicitly (no fallback behavior).
- New client contract inserts set
Notes / gotchas
- There are additional
template_contract_idmentions in tests; they are fixture/static assertions and not part of runtime inventory for F001. - Immediate implementation focus after F001: remove the four forbidden fallback sites and convert missing-provenance cases to explicit fail-closed errors.
2026-03-20 F002 Semantics Decision (authoritative)
client_contracts.template_contract_idremains in the schema for now as provenance-only metadata.- It is not a valid runtime identifier and must never be used as fallback identity for live contract operations.
- Runtime ownership and live reads remain anchored on
client_contracts.contract_idand clonedcontract_lines. - Current retained provenance uses:
- draft/resume and renewal flows (
contractWizardActions,renewalsQueueActions) - operator-facing metadata surfaces (
getAllWithClients, draft list views, selected DTO fields)
- draft/resume and renewal flows (
- Removal readiness: not ready for immediate drop migration in this plan because retained draft/renewal metadata reads still exist; this plan’s objective is to remove behavioral dependence and leave only explicit provenance reads.
2026-03-20 Implementation Log (F003-F014)
Runtime behavior changes
- Removed forbidden fallback resolution in client contract apply flow:
packages/clients/src/actions/clientContractActions.ts- replaced
template_contract_id ?? contract_idwith explicit provenance read and fail-closed error when missing.
- Removed forbidden fallback resolution in client contract line add flow:
packages/clients/src/actions/clientContractLineActions.ts- replaced fallback with explicit provenance read and fail-closed error when missing.
- Removed forbidden fallback resolution in API service contract-line assignment:
server/src/lib/api/services/ContractLineService.ts- assignment now requires
template_contract_idprovenance explicitly and errors when absent.
- Retired hybrid decoupling script semantics:
server/scripts/contract-template-decoupling.ts- rewritten to audit-only reporting; no
template_contract_idbackfill writes; no template clone fallback behavior.
Runtime model/type provenance alignment
- Clarified provenance-only semantics in shared interfaces:
packages/types/src/interfaces/billing.interfaces.tspackages/types/src/interfaces/contract.interfaces.ts
- Added explicit provenance metadata handling/comments in runtime client contract-line surfaces:
packages/clients/src/models/clientContractLine.tspackages/clients/src/actions/clientContractLineActions.ts
Legacy tests/fixtures normalization updates
- Updated tests that previously encoded fallback behavior:
server/src/test/unit/api/contractLineService.clientOwnedMutation.test.tsnow asserts fail-closed on missing provenance and success when provenance exists.server/src/test/unit/billing/clientContractLineReplacementIdentity.test.tsnow asserts fail-closed add behavior when provenance is missing.packages/clients/src/actions/clientContractLineActions.recurringCompatibility.test.tsnow assertstemplate_contract_idis nullable provenance metadata.
New tests added
- Apply flow provenance/fail-closed unit coverage:
server/src/test/unit/billing/clientContractApplyProvenance.test.ts
- Runtime fallback static guard extensions:
server/src/test/unit/billing/clientContractLineRuntimeSourceGuards.static.test.ts- now guards
template_contract_id ?? contract_id, SQLcoalesce(template_contract_id, contract_id), and mixed-ID join patterns in targeted runtime files/scripts.
- Script contract test for decoupling script retirement:
server/src/test/unit/scripts/contractTemplateDecoupling.script.test.ts
- Shared type/schema provenance contract test:
server/src/test/unit/billing/templateContractIdProvenance.types.test.ts
- Runtime contract loader provenance static test:
server/src/test/unit/billing/contractRuntimeLoaderTemplateProvenance.static.test.ts
- DB-backed integration coverage for apply + line-add happy/failure:
server/src/test/integration/templateRuntimeNormalization.clientActions.integration.test.ts
Commands / runbook
- Inventory command:
rg -n "template_contract_id|template_contract_id \\?\\?|coalesce\\([^\\n]*template_contract_id|contract_templates as template" packages/clients/src packages/billing/src server/src/lib/api/services server/scripts
- Unit/static test command (passing):
pnpm exec vitest run src/test/unit/api/contractLineService.clientOwnedMutation.test.ts src/test/unit/billing/contractRuntimeLoaderTemplateProvenance.static.test.ts src/test/unit/billing/clientContractLineRuntimeSourceGuards.static.test.ts src/test/unit/scripts/contractTemplateDecoupling.script.test.ts src/test/unit/billing/templateContractIdProvenance.types.test.ts src/test/unit/billing/clientContractApplyProvenance.test.ts src/test/unit/billing/clientContractLineReplacementIdentity.test.ts ../packages/clients/src/actions/clientContractLineActions.recurringCompatibility.test.ts --coverage.enabled=false
- Integration command (test authored; local environment blocked):
pnpm exec vitest run src/test/integration/templateRuntimeNormalization.clientActions.integration.test.ts --coverage.enabled=false
Gotchas / blockers
- Local DB integration execution is blocked in this environment by:
ECONNREFUSED ::1:5438andECONNREFUSED 127.0.0.1:5438
- Integration tests are implemented and ready, but could not be executed locally until Postgres test DB is reachable on configured port.