Separate self-managed plans from billing subscriptions #412

Closed
caution-bot wants to merge 1 commit from feat/admin-unlimited-enclaves into main
caution-bot commented 2026-08-08 21:23:45 +00:00 (Migrated from codeberg.org)

Summary

  • introduce organization-scoped self_managed_plans as the sole operational authority for BYOC/self-managed enclave capacity while retaining subscription rows as billing/provider history
  • model NULL as unlimited capacity, 0 as disabled, and positive values as exact finite quotas; self-managed reservations are atomic and are not overridden by the generic fully managed resource cap
  • serialize Paddle, legacy-maintenance, rolling-reconciliation, and operator transitions per organization with tenant/authority fencing and subscription-before-plan lock ordering
  • support dry-run-first audited manual contracts, safe Paddle detachment, legacy-ledger closure, and explicit dead-letter retry/resolution operations
  • terminate only BYOC resources through the canonical resource lifecycle using durable leased jobs, bounded retries, dead-letter recovery, redirect-safe internal authentication, and credential deletion protection
  • reconcile stale, delayed, ambiguous, or provider-confirmed Paddle tier/cancellation outcomes without allowing detached provider history to regain authority; exact retry payloads and idempotency keys are persisted across catalog changes, and cancellation remains fail-closed after historical prices leave the catalog

Capacity semantics

  • enclave_limit IS NULL: unlimited
  • enclave_limit = 0: disabled
  • enclave_limit > 0: exact finite quota

Test plan

  • cargo +1.96.1 check -p api -p metering
  • cargo +1.96.1 test -p api --no-fail-fast — 199 passed
  • PADDLE_API_KEY= PADDLE_API_URL= cargo +1.96.1 test -p metering --no-fail-fast — 44 passed
  • POSTGRES_CONTAINER=postgres-test ./tests/e2e/test_admin_self_managed_plan.sh — passed on a fresh migrated database
  • DB_CONTAINER=postgres-test ./tests/e2e/test_paddle_subscriptions.sh — passed, including stale/equal-time events, payment-failure ordering, replacement authority, rolling reconciliation, and canonical BYOC teardown
  • npm test in frontend/ — 56 passed
  • Node 22 Vite production build with --configLoader runner --outDir /tmp/caution-frontend-build — passed
  • production and e2e-feature API container builds — passed
  • shell syntax checks and git diff --check origin/main — passed

Operational notes

  • The admin transition command remains dry-run-only unless --confirm is supplied and requires explicit operator identity, reason, capacity, and expiration policy.
  • Provider detachment requires --detach-paddle; active provider/legacy billing projections are closed before authority is transferred.
  • Migration 053 fails closed if an older deployment still has an unreconciled live Paddle tier intent whose exact provider payload cannot be reconstructed.
  • Dead-letter termination jobs require explicit audited retry or terminal-state-fenced resolve actions.
  • A live Paddle sandbox credential was not available for this checkout; signed local Paddle lifecycle E2E coverage uses the repository pricing fixture and real API/metering containers.
## Summary - introduce organization-scoped `self_managed_plans` as the sole operational authority for BYOC/self-managed enclave capacity while retaining subscription rows as billing/provider history - model `NULL` as unlimited capacity, `0` as disabled, and positive values as exact finite quotas; self-managed reservations are atomic and are not overridden by the generic fully managed resource cap - serialize Paddle, legacy-maintenance, rolling-reconciliation, and operator transitions per organization with tenant/authority fencing and subscription-before-plan lock ordering - support dry-run-first audited manual contracts, safe Paddle detachment, legacy-ledger closure, and explicit dead-letter retry/resolution operations - terminate only BYOC resources through the canonical resource lifecycle using durable leased jobs, bounded retries, dead-letter recovery, redirect-safe internal authentication, and credential deletion protection - reconcile stale, delayed, ambiguous, or provider-confirmed Paddle tier/cancellation outcomes without allowing detached provider history to regain authority; exact retry payloads and idempotency keys are persisted across catalog changes, and cancellation remains fail-closed after historical prices leave the catalog ## Capacity semantics - `enclave_limit IS NULL`: unlimited - `enclave_limit = 0`: disabled - `enclave_limit > 0`: exact finite quota ## Test plan - `cargo +1.96.1 check -p api -p metering` - `cargo +1.96.1 test -p api --no-fail-fast` — 199 passed - `PADDLE_API_KEY= PADDLE_API_URL= cargo +1.96.1 test -p metering --no-fail-fast` — 44 passed - `POSTGRES_CONTAINER=postgres-test ./tests/e2e/test_admin_self_managed_plan.sh` — passed on a fresh migrated database - `DB_CONTAINER=postgres-test ./tests/e2e/test_paddle_subscriptions.sh` — passed, including stale/equal-time events, payment-failure ordering, replacement authority, rolling reconciliation, and canonical BYOC teardown - `npm test` in `frontend/` — 56 passed - Node 22 Vite production build with `--configLoader runner --outDir /tmp/caution-frontend-build` — passed - production and e2e-feature API container builds — passed - shell syntax checks and `git diff --check origin/main` — passed ## Operational notes - The admin transition command remains dry-run-only unless `--confirm` is supplied and requires explicit operator identity, reason, capacity, and expiration policy. - Provider detachment requires `--detach-paddle`; active provider/legacy billing projections are closed before authority is transferred. - Migration `053` fails closed if an older deployment still has an unreconciled live Paddle tier intent whose exact provider payload cannot be reconstructed. - Dead-letter termination jobs require explicit audited `retry` or terminal-state-fenced `resolve` actions. - A live Paddle sandbox credential was not available for this checkout; signed local Paddle lifecycle E2E coverage uses the repository pricing fixture and real API/metering containers.
caution-bot commented 2026-08-08 21:23:47 +00:00 (Migrated from codeberg.org)

Operator usage after deployment

  1. Find and verify the organization UUID:
    ./utils/admin list-orgs
    ./utils/admin show-org <organization-uuid>
    
  2. Run a dry-run with the contract's expiration policy:
    ./utils/admin set-enterprise-entitlement <organization-uuid> \
      --unlimited --never-expires
    
    Or replace --never-expires with --expires-at <finite-RFC3339-timestamp>.
  3. Check the printed organization, current subscription, and expiry.
  4. Apply the exact same command with --confirm appended.
  5. Verify the final table reports enterprise_unlimited, enterprise, active, and unlimited.

The command is deliberately fail-closed. It will not convert a legacy/Paddle subscription or operate when it finds Paddle identifiers, pending provider changes, or metered subscription history. Reconcile/cancel those states first, then repeat the dry-run. This avoids detaching provider billing or allowing stale billing writers/webhooks to overwrite the enterprise contract.

Implementation note: the existing integer capacity gate is preserved by storing PostgreSQL INTEGER max (2147483647) as the practical-unlimited sentinel. The API recognizes “unlimited” only when the row is an enterprise enterprise_unlimited entitlement with that stored sentinel. Expired contracts are returned/displayed as expired and remain blocked by the deployment gate.

## Operator usage after deployment 1. Find and verify the organization UUID: ```bash ./utils/admin list-orgs ./utils/admin show-org <organization-uuid> ``` 2. Run a dry-run with the contract's expiration policy: ```bash ./utils/admin set-enterprise-entitlement <organization-uuid> \ --unlimited --never-expires ``` Or replace `--never-expires` with `--expires-at <finite-RFC3339-timestamp>`. 3. Check the printed organization, current subscription, and expiry. 4. Apply the exact same command with `--confirm` appended. 5. Verify the final table reports `enterprise_unlimited`, `enterprise`, `active`, and `unlimited`. The command is deliberately fail-closed. It will not convert a legacy/Paddle subscription or operate when it finds Paddle identifiers, pending provider changes, or metered subscription history. Reconcile/cancel those states first, then repeat the dry-run. This avoids detaching provider billing or allowing stale billing writers/webhooks to overwrite the enterprise contract. Implementation note: the existing integer capacity gate is preserved by storing PostgreSQL `INTEGER` max (`2147483647`) as the practical-unlimited sentinel. The API recognizes “unlimited” only when the row is an enterprise `enterprise_unlimited` entitlement with that stored sentinel. Expired contracts are returned/displayed as expired and remain blocked by the deployment gate.
ryansquared commented 2026-09-08 01:56:08 +00:00 (Migrated from codeberg.org)

Closing in favor of #444

Closing in favor of #444
ryansquared commented 2026-09-08 01:56:25 +00:00 (Migrated from codeberg.org)

Billing happens through the subscriptions_ledger

Billing happens through the subscriptions_ledger

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
public/platform!412
No description provided.