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
4.0 KiB
4.0 KiB
Recurring Invoicing Hard Cutover Architecture Notes
Final Invariant
Recurring invoice execution identity is canonical service-period or execution-window identity only.
In steady state:
- cadence ownership and source rules generate persisted
recurring_service_periods - ready recurring work is read only from persisted
recurring_service_periods - preview, generate, run, history, reverse, and delete flows identify recurring work by canonical execution-window or service-period identity
billing_cycle_idis 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_periodsis 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_periodsand 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_idmay 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