30 lines
3.3 KiB
Markdown
30 lines
3.3 KiB
Markdown
# Decisions
|
|
|
|
Append-only log of architectural and design decisions.
|
|
|
|
## 2026-04-29 — Project lives in `autumn/.context/`, not the outer worktree
|
|
The work targets first-class billing actions in `autumn/server/src/internal/billing/v2/actions/` and ships with tests in `autumn/`. Context is colocated with the code it describes. The outer `.context/migration-interface/` holds only a redirect README.
|
|
|
|
## 2026-04-29 — Phase 1 is cataloging, not designing
|
|
The user explicitly asked to survey existing migration & import scripts before committing to interface shape. Risk of premature lock-in is high — there are at least three classes of patterns (DB-only mutations, DB+Stripe mutations, imports with balance carryover) and each could break the proposed shape differently. Output of Phase 1 is a verdict-per-script catalog.
|
|
|
|
## 2026-04-29 — Build as core product feature, not internal tooling
|
|
Will eventually surface to users on the dashboard. Implications: lives in `autumn/server/src/internal/operations/` (peer to `billing/v2/`), has integration tests, no `as any`, follows full billing-action conventions for the heavyweight executors (`setupBillingContext` → compute Autumn plan → `evaluateStripeBillingPlan` → execute).
|
|
|
|
## 2026-04-29 — Planner + Executor model, not single billing action
|
|
Migrations and imports differ in HOW they produce a plan but converge in HOW the plan executes. A single `migrate` action would conflate heterogeneous mutation surfaces (catalog, customer product graph, balance, Stripe repair) into one sprawling compute function. Splitting produces:
|
|
- Durable, inspectable, persistable `OperationPlan` artifact
|
|
- Dry-run identical to live (only `apply()` differs)
|
|
- Pluggable planners (MigrationDefinition, ImportDefinition) emit same operation graph
|
|
- Specialized executors per mutation surface, each with own idempotency/guards
|
|
- Reuses billing v2 pipeline INSIDE the CustomerProductGraphOperation executor — no replacement, just orchestration above
|
|
|
|
## 2026-04-29 — Operations typed by mutation surface, not lifecycle intent
|
|
Initial design used `PlanOp = expire | insert | swap | patch` (lifecycle-flavored). Better: `Operation = CatalogOperation | CustomerProductGraphOperation | CustomerEntitlementOperation | BalanceOperation | StripeRepairOperation | CacheInvalidationOperation`. Each surface has different idempotency, transaction boundaries, and concurrency rules — surface-typed ops let executors specialize cleanly.
|
|
|
|
## 2026-04-29 — `is_custom` decoupled from "uses shared catalog rows"
|
|
Today `setupCustomFullProduct` always mints fresh per-customer Price/Entitlement rows when customize is provided. For 10k-customer migrations this creates 10k Stripe prices unnecessarily. New model: `ProductItemPatch.add_existing { entitlement_id, price_id }` references shared catalog rows by id; `is_custom` becomes purely an ownership flag (one-off vs catalog), not "this row is per-customer." Existing custom rows untouched; new code uses new model.
|
|
|
|
## 2026-04-29 — Ownership is first-class
|
|
Today repair logic is bespoke per-script because we can't distinguish imported from user-created customer_products. New model: `Ownership { origin: "imported"|"user_created"|"migrated", source?, imported_at? }` on every TargetAssignment. Likely stored as new column on `customer_products`. Unblocks safe re-runnable imports and convergence/repair semantics.
|