feat: org billing accounts + funding designation (migration 041, W0 substrate)#51
Open
I-am-nothing wants to merge 2 commits into
Open
feat: org billing accounts + funding designation (migration 041, W0 substrate)#51I-am-nothing wants to merge 2 commits into
I-am-nothing wants to merge 2 commits into
Conversation
…ubstrate) Org-billing W0 (docs-temp/org-billing/design.md D1) — retires the D6 user-only rule: - One org = one ms_billing.accounts row (owner_kind='org'), created by EnsureOrgAccount (advisory-locked get-or-create, org namespace 'lbto') lazily at the org's first funding designation. - The designation picks only the FUNDING INSTRUMENT: sponsor mode finalizes invoices against the sponsor's Stripe customer/default PM (ChargeFundingAccount hop in resolveChargeableCustomer, resolved at charge time); attribution never moves. Sponsor = the acting user's own account, validated server-side; only the current sponsor may self-revoke. - Resolution (AccountByOwner / Ensure / RegisterApp) is designation- + activation-gated; an undesignated org registers UNBILLED roster rows (apps.account_id NULL + owner_org_id stamp) and lazy NULL-account events. - RepointOrgUsage attach sweep: backfills roster account_id, folds NULL-account events into the account's first open window (recorded_at clamp + repointed_from audit — the rollup windows by recorded_at, so older events would otherwise never bill), synthesizes fresh overage timers anchored at designation (prospective billing). - PM RPC family (Ensure/Prepare/Start/Finish/Get/Detach/SetDefault) widened to user-XOR-org owners; Ensure's payment_method capability checks the funding account. - Migration slot 041: 038-040 are claimed by the open service-block PR #50. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n decode Post-review fixes on the W0 substrate (4-lens /simplify pass): - recoveryCustomer: all four crash-recovery sites (boundary hasFrozen, boundary moneyMayHaveMoved, creation proration, module overage) now resolve the SAME ChargeFundingAccount hop the fresh-charge path uses — a sponsor-funded org's crashed attempt was previously looked up under the org's own (usually absent) Stripe customer: a loud permanent error at best, a missed reconcile at worst. Regression test pins the sponsor customer on the recovery lookup. The funding-switch-during-crash race is backstopped by the deterministic ~24h idem keys (same envelope as the documented Search-lag note); recorded-instrument hardening travels with the W2 transfer wave. - Ensure's funding hop hoists inside the RequirePaymentMethod branch and skips the provably-identity query for user principals. - billing.AccountByOrgFunded renamed ResolveOrgFundedAccount — THE org resolution now greps as one name across all three stores. - uuidRowFound decode helper (per package, matching the local-helper idiom) collapses six copies of the ErrNoRows/parse ceremony. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Org-billing W0 substrate (design: workspace
docs-temp/org-billing/design.md, D1 + W0 row — user-approved v2.1). Retires the account-billing-read D6 user-only rule.What
ms_billing.accountsrow (owner_kind='org', schema-ready since 001; first writer path). Created byEnsureOrgAccount— the advisory-locked get-or-create twin of the user leg (org lock namespace'lbto').org_billing_designations, migration 041): picks ONLY the funding instrument for the org account's invoices.sponsor= the acting org owner/admin's own card (validated server-side: the request carries no sponsor field, so pointing at another member's wallet is structurally impossible);org= the org's own Stripe customer/card. Attribution (periods, aggregates, roster, pool, budgets, collection, invoice mirror) always stays on the org account — funding switches re-route only future charges.resolveChargeableCustomerresolvesChargeFundingAccountonce per charge; all three charge legs (boundary, creation proration, module overage) inherit it. A revoked sponsorship degrades to the ordinary transientskipped_no_pm.ResolveOrgFundedAccount): sponsor designation activates immediately (anchor = designation day, ADR 0006 amendment);funding='org'activates at card bind — the pointer never flips to an unfunded account.RegisterAppaccepts org owners; an undesignated org registersapps.account_id NULL+owner_org_idstamp (no base fee, no timers, excluded from every sweep —AppsPendingProrationgainsaccount_id IS NOT NULL; direct-path guards added in proration + timer reconcile).RepointOrgUsageattach sweep: backfills rosteraccount_id, folds NULL-account events into the account's first open window (recorded_atclamp +repointed_fromaudit column — the rollup windows byrecorded_at, so older events would otherwise never enter any window), synthesizes fresh overage timers anchored at designation (prospective billing; no retroactive grace).Get/SetOrgDesignation(acting-user validation + server-computeddisclosed_backlog_microsfor the pre-confirm disclosure),RevokeSponsorship(self-revoke, authorized by being the sponsor),RepointOrgUsage— all four wired into the dispatcher.Ensure/Prepare/Start/Finish/GetPaymentMethods/Detach/SetDefault): W1's org billing page has no billing-engine leg, so this lands here.Ensure'spayment_methodcapability checks the FUNDING account (a sponsor-funded org account owns no PM rows itself).Migration 041 (038–040 reserved by #50)
New table
org_billing_designations(+ funding-shape CHECK, sponsor FKON DELETE CASCADE+ covering partial index, updated_at trigger);apps.account_id DROP NOT NULL+apps.owner_org_id+apps_unbilled_only_org_check+ partial index;usage_events.repointed_from. Down migration mirrors everything (unbilled roster rows are dropped — they carry no billed state).Lock impact:
ALTER TABLE apps(DROP NOT NULL / ADD COLUMN / ADD CONSTRAINT) takes a brief ACCESS EXCLUSIVE onms_billing.apps; the CHECK validates existing rows in the same lock (small table).usage_events ADD COLUMN … NULLis metadata-only. Grants: covered by 024'sALTER DEFAULT PRIVILEGES.Verification
go build ./...,go vet ./...(incl.-tags integration), fullgo test ./...— all green.RunBillingCycle(sponsor customer charged, run + mirror keyed to the org account; revoke → transient no_pm), billing owner widening (funded gate beats row existence, org PM targets, validation).Merge order
feat/org-owner-stamp(owner stamps + mirror org leg) → 3. runbilling-backfill(its new org leg registers pre-existing org apps as unbilled roster rows). mirrorstack-docsdocs/org-billing-w0documents 037+041 and can merge any time after this. W1 (org billing page + designation UI) follows.Out of scope, per design: the
CreateApp/transfer 402 designation gate (turns on with W1's UI),ai_meterorg attribution (conversations carry no app context until app-attributed AI), egress-sync (already owner-less by design for all apps; the repoint sweep scoops org events by app id).🤖 Generated with Claude Code