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
168 lines
12 KiB
Markdown
168 lines
12 KiB
Markdown
# 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.ts` still resolves template source using `clientContract.template_contract_id ?? clientContract.contract_id ?? null` in `applyContractToClient(...)`.
|
||
- `packages/clients/src/actions/clientContractLineActions.ts` still selects `template_contract_id` alongside `contract_id` and still resolves template source with `clientContract.template_contract_id ?? clientContract.contract_id`.
|
||
- `server/src/lib/api/services/ContractLineService.ts` still contains template-source fallback logic in client-owned mutation/setup paths.
|
||
- `server/scripts/contract-template-decoupling.ts` still backfills or depends on `template_contract_id` fallback semantics.
|
||
- `packages/billing/src/models/contract.ts` and 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:
|
||
- `F083` remove template fallback joins from live billing computations
|
||
- `F084` split instantiated contract detail/assignment loading from template detail loading
|
||
- `F085` stop runtime backfill writes to `client_contracts.template_contract_id`
|
||
- `F086` extend static guards for post-drop table usage
|
||
- This new plan should pick up only what remains after those changes.
|
||
|
||
## Working Decision
|
||
|
||
- Treat `template_contract_id` as 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_id` as 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 server`
|
||
- `pnpm exec vitest run --coverage.enabled=false <targeted tests>`
|
||
- `jq empty ee/docs/plans/2026-03-20-template-runtime-normalization-completion/features.json`
|
||
- `jq 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.ts`
|
||
- `applyContractToClient(...)` uses `clientContract.template_contract_id ?? clientContract.contract_id ?? null` before cloning services/config.
|
||
- `packages/clients/src/actions/clientContractLineActions.ts`
|
||
- `addClientContractLine(...)` loads both `template_contract_id` and `contract_id`, then falls back with `clientContract.template_contract_id ?? clientContract.contract_id`.
|
||
- `server/src/lib/api/services/ContractLineService.ts`
|
||
- `assignPlanToClient(...)` loads both IDs and falls back with `clientContract.template_contract_id ?? clientContract.contract_id ?? null`.
|
||
- `server/scripts/contract-template-decoupling.ts`
|
||
- Backfills `client_contracts.template_contract_id = contract_id` when missing.
|
||
- Uses `clientContract?.template_contract_id ?? clientContract?.contract_id ?? null` when cloning.
|
||
|
||
### 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.
|
||
|
||
### Allowed provenance-only references (not runtime fallback)
|
||
|
||
- `packages/billing/src/models/contract.ts`
|
||
- `getAllWithClients(...)` left-joins `contract_templates` by `cc.template_contract_id` only to expose template name metadata for list views.
|
||
- `packages/billing/src/actions/contractActions.ts`
|
||
- `getDraftContracts(...)` joins `contract_templates` and selects `cc.template_contract_id` as draft metadata.
|
||
- `packages/billing/src/actions/renewalsQueueActions.ts`
|
||
- Optional-column guard + copy-through of `template_contract_id` into draft assignment metadata (no fallback derivation).
|
||
- `packages/billing/src/actions/contractWizardActions.ts`
|
||
- Writes `template_contract_id: submission.template_id ?? null` and reads back `template_id: clientContract.template_contract_id ?? undefined` for draft resume UX.
|
||
- `packages/billing/src/lib/billing/billingEngine.ts`
|
||
- Selects `cc.template_contract_id` as provenance field while live joins remain anchored on `cc.contract_id -> contract_lines`.
|
||
- `packages/clients/src/actions/clientContractLineActions.ts`
|
||
- `getClientContractLine(...)` selects `cc.template_contract_id` in response payload.
|
||
- Update/edit destructuring drops inbound `template_contract_id` from mutable update fields.
|
||
- `packages/clients/src/models/clientContractLine.ts`
|
||
- Selects `cc.template_contract_id` in DTO shape and strips it from mutation payload updates.
|
||
- `packages/clients/src/actions/clientContractActions.ts` and `packages/clients/src/models/clientContract.ts`
|
||
- New client contract inserts set `template_contract_id: null` explicitly (no fallback behavior).
|
||
|
||
### Notes / gotchas
|
||
|
||
- There are additional `template_contract_id` mentions 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_id` remains 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_id` and cloned `contract_lines`.
|
||
- Current retained provenance uses:
|
||
- draft/resume and renewal flows (`contractWizardActions`, `renewalsQueueActions`)
|
||
- operator-facing metadata surfaces (`getAllWithClients`, draft list views, selected DTO fields)
|
||
- 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_id` with 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_id` provenance explicitly and errors when absent.
|
||
- Retired hybrid decoupling script semantics:
|
||
- `server/scripts/contract-template-decoupling.ts`
|
||
- rewritten to audit-only reporting; no `template_contract_id` backfill writes; no template clone fallback behavior.
|
||
|
||
### Runtime model/type provenance alignment
|
||
|
||
- Clarified provenance-only semantics in shared interfaces:
|
||
- `packages/types/src/interfaces/billing.interfaces.ts`
|
||
- `packages/types/src/interfaces/contract.interfaces.ts`
|
||
- Added explicit provenance metadata handling/comments in runtime client contract-line surfaces:
|
||
- `packages/clients/src/models/clientContractLine.ts`
|
||
- `packages/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.ts` now asserts fail-closed on missing provenance and success when provenance exists.
|
||
- `server/src/test/unit/billing/clientContractLineReplacementIdentity.test.ts` now asserts fail-closed add behavior when provenance is missing.
|
||
- `packages/clients/src/actions/clientContractLineActions.recurringCompatibility.test.ts` now asserts `template_contract_id` is 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`, SQL `coalesce(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:5438` and `ECONNREFUSED 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.
|