feat: account-wide pooled module overage (migration 030)#47
Closed
I-am-nothing wants to merge 2 commits into
Closed
feat: account-wide pooled module overage (migration 030)#47I-am-nothing wants to merge 2 commits into
I-am-nothing wants to merge 2 commits into
Conversation
Move module overage from PER-APP to a single ACCOUNT-WIDE POOL of 5 included modules, per the owner spec 2026-07-05 (a deliberate reversal of the base-fee v1 per-app tier). - Schema (030): accounts.overage_since (one grace timer per account) + account_overage_snapshots (per-(account, period) charge ledger, the double-charge guard mirroring app_base_snapshots). - Pricing: per-app base is now FLAT $20; overage = $3 x max(0, SUM(live-app module_count) - 5) at the account level (usage.AccountOverageMicros / ProratedOverageMicros). - Timer: RegisterApp / SyncAppModules recompute the pool after every module_count write and arm/clear accounts.overage_since (first-cross-wins / idempotent clear, no refund on drop). - Mid-period grace charge: a new cmd/billing-cycle sweep charges the pooled overage prorated from grace-end to the period end once the 3-day grace window elapses (a deliberate mid-period charge; base-fee mid-period behavior is unchanged). Deterministic per-(account, period) Stripe idem keys + the snapshot ledger make it money-safe. - Boundary advance leg: each app now contributes only its flat base; a SEPARATE pooled-overage term bills the closing period ONCE, skipped when the sweep already billed it (snapshot guard), source='advance'. - Display: GetAccountBillResponse gains AccountOverageMicros (additive, snapshot-first else live pooled estimate); per-app base display is flat. Tests cover: pool crossing 5 arms the timer, dropping under clears it, grace holds for exactly 3 days, the mid-period charge fires once and is excluded from the boundary (no double-charge), interleaved installs sum to the account pool, and uninstall never refunds. mirrorstack-docs/db/ms_billing/ needs a companion update for the new account_overage_snapshots table + accounts.overage_since column. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… findings) PR #47 adversarial review found the grace sweep and the boundary leg could both charge the same period's pooled overage if a crash landed between a successful Stripe call and the account_overage_snapshots write (disjoint Idempotency-Key namespaces, so Stripe never deduped it). Adds a status column ('pending'/'charged') to account_overage_snapshots: the claim is now written BEFORE either leg calls Stripe, so a crash anywhere leaves durable evidence the other leg must respect. Also fixes the boundary reclaim livelock (a reclaimed run recomputed the overage to 0 instead of reusing the frozen amount, colliding with its own stable Idempotency-Key) and threads the genuine Stripe invoice item id back from the boundary's combined charge instead of storing the idempotency-key string. Judgment call (no product decision available): pooled-overage growth after a period's grace charge now gets an incremental top-up, conservatively prorated from the sweep's own instant forward (never retroactive) — flagged in cycle/overage.go's topUpGraceOverage doc for owner review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
Superseded by #48 — the overage concept (5 included modules, $3/module beyond) carries forward, but the mechanism is fully replaced: single account-wide timer → one independent 3-day timer per module instance, anchored to that module's own install date with live FIFO re-evaluation. Branch |
I-am-nothing
added a commit
that referenced
this pull request
Jul 6, 2026
Replaces the account-wide pooled overage model with per-module-instance overage timers (migration 033), merges in the app-creation grace period and the auto-collect disclosure flag, and unifies base fee + module overage into one charge concept. Supersedes #45, #46, #47. Key mechanics: 3-day creation grace (deleted WITHIN grace = never charged; after = still owes); 5 included modules account-wide with $3/module beyond, one install-anchored timer per module instance with live FIFO re-evaluation (over->included only); coverage contract — every grace charge covers install/creation day through the END of the period its grace elapses into, boundary legs count only entities whose (immutable) install + grace-expiry predate the new period; all Stripe charges via draft->pinned-items->finalize with ms_charge_ref metadata; migration 035/036 markers + FindInvoiceByRef recovery for retries past Stripe's idempotency-key window (gates bypassed only when a finalized invoice actually exists); per-app advisory-locked timer synthesis and deletion. Migrations 029-036. Squash of the 32-commit PR branch, including two adversarial-review fix waves (23 + 12 confirmed findings fixed; full record on PR #48 and in docs-temp/pr48-review/findings.md). go build/vet/gofmt clean; full unit + integration suites green against real Postgres (migrations 001->036). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> 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.
What
Moves module overage from per-app to a single account-wide pool of 5 included modules, per the owner spec 2026-07-05 (confirmed reversal of the base-fee v1 per-app tier).
module_countacross all live apps;$3/monthfor each module beyond the pooled 5, charged once at the account level.$3/moduleoverage math moved.How
Schema — migration
030(030was free in this worktree;028was the prior latest):accounts.overage_since TIMESTAMPTZ NULL— the one grace-timer anchor.account_overage_snapshots— per-(account, period_start)charge ledger; the double-charge guard, mirroringapp_base_snapshots.Pricing (
usage/basefee.go): removed the per-appAppBaseFeeMicrosoverage term; addedAccountOverageMicros(pooledCount)+ProratedOverageMicros. Per-app base is now flat.Timer:
RegisterApp/SyncAppModulesrecompute the pool after everymodule_countwrite and arm/clearoverage_since(first-cross-wins; idempotent clear; no refund on drop, D1e).Mid-period grace charge: a new sweep in
cmd/billing-cyclecharges the pooled overage prorated from grace-end → period-end once the 3-day window elapses — a deliberate mid-period charge (the D1b "no mid-period charges" rule now excludes the overage leg; base-fee behavior is unchanged). Deterministic per-(account, period)Stripe idem keys + the snapshot ledger make it money-safe.Boundary advance leg (
charge.go): each app contributes only its flat base; a separate pooled-overage term bills the closing period once, skipped when the sweep already billed it (source='advance').Display:
GetAccountBillResponsegainsAccountOverageMicros(additive — no field removed/renamed; included inTotalMicros), snapshot-first else a live pooled estimate. Per-app displayed base is now flat.Tests
Pool crossing 5 arms the timer; dropping under clears it; grace holds for exactly 3 days; the mid-period charge fires once and is excluded from the next boundary (no double-charge — the key test); interleaved installs sum to the account pool; uninstall never refunds.
make test/go vet/go build ./...all clean.Follow-up / notes
mirrorstack-docs/db/ms_billing/needs a companion update for the newaccount_overage_snapshotstable +accounts.overage_sincecolumn — flagging, not doing it here (someone will consolidate docs across the three related billing PRs).AccountOverageMicroswire field is additive so existing consumers are unaffected; the web line is a separate PR.🤖 Generated with Claude Code