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

78 lines
4.0 KiB
Markdown

# Recurring Invoicing Hard Cutover Architecture Notes
## Final Invariant
Recurring invoice execution identity is canonical service-period or execution-window identity only.
In steady state:
1. cadence ownership and source rules generate persisted `recurring_service_periods`
2. ready recurring work is read only from persisted `recurring_service_periods`
3. preview, generate, run, history, reverse, and delete flows identify recurring work by canonical execution-window or service-period identity
4. `billing_cycle_id` is never required to discover recurring work, prevent duplicates, explain recurring history, or repair recurring invoice linkage
## Canonical Runtime Model
The hard cutover keeps a single recurring model in application code:
- `recurring_service_periods` is the recurring ledger and the only ready-work substrate
- recurring due rows, run targets, and retry identity are derived from canonical schedule or service-period identity
- missing materialization is an explicit repairable failure, not a compatibility invoice row
- history and reversal operate on invoice-linked recurring service-period records, not billing-cycle handles
## Retained Role Of `client_billing_cycles`
`client_billing_cycles` remain valid product data, but only in these roles:
- client cadence administration and anchor management
- source-rule input when `cadence_owner = client`
- optional historical or read-side context when an older invoice still carries bridge metadata
`client_billing_cycles` do not remain valid in these roles:
- recurring due-work substrate
- recurring execution identity
- duplicate-prevention key for recurring invoices
- primary object for recurring history, reverse, delete, preview, or generate operations
## Required Schema Posture
The hard cutover treats recurring-service-period storage as required schema, not rollout-era optional schema.
- recurring invoice paths must assume `recurring_service_periods` and related linkage structures exist
- missing service-period rows are diagnosed as data repair work
- code must not catch missing table or missing column errors to rebuild recurring work from `client_billing_cycles`
## `invoices.billing_cycle_id` Deprecation Posture
The current deprecation posture is:
- `invoices.billing_cycle_id` may remain physically present for now
- it is passive historical or client-context metadata only
- no live recurring path may use it to decide what recurring work exists, whether it is duplicate, how it is previewed or generated, or how recurring linkage is repaired
- later physical removal can happen after historical read-side cleanup is complete, but live recurring code must already behave as though the bridge is gone
## Historical Read-Side Strategy
Historical invoices may still lack complete canonical recurring linkage. The hard cutover handles that only on the read side:
- when canonical recurring detail periods exist, treat the invoice as `canonical_recurring`
- when canonical detail is absent but the invoice must remain readable, treat it as `financial_document_fallback`
- when related source invoice context cannot be resolved at all, surface `missing_source_context`
This is a migration/read model strategy, not a live recurring execution mode:
- do not synthesize new recurring due work from `client_billing_cycles`
- do not rebuild live recurring identity from invoice-header `billing_cycle_id`
- do not backfill fake recurring service periods just to make historical invoices look canonical
- use fallback states only to keep historical reads, exports, and lineage views diagnosable while cleanup/backfill work proceeds separately
## Anti-Regression Rules
Future recurring changes should preserve these boundaries:
- shared recurring execution contracts do not require `billingCycleId`
- recurring UI copy talks about service periods or execution windows, not invoiced billing cycles
- exports and invoice reads derive recurring semantics from canonical recurring detail and service-period data
- tests should prove client-cadence and contract-cadence recurring work remain operable without a billing-cycle bridge