diff --git a/.impeccable/design.json b/.impeccable/design.json
new file mode 100644
index 0000000000..44047cf588
--- /dev/null
+++ b/.impeccable/design.json
@@ -0,0 +1,305 @@
+{
+ "schemaVersion": 2,
+ "generatedAt": "2026-05-06T00:00:00.000Z",
+ "title": "Design System: Kilo Cloud",
+ "extensions": {
+ "colorMeta": {
+ "background": {
+ "role": "neutral",
+ "displayName": "App Canvas",
+ "canonical": "#121212",
+ "tonalRamp": [
+ "oklch(0.12 0 0)",
+ "oklch(0.18 0 0)",
+ "oklch(0.24 0 0)",
+ "oklch(0.32 0 0)",
+ "oklch(0.45 0 0)",
+ "oklch(0.62 0 0)",
+ "oklch(0.78 0 0)",
+ "oklch(0.94 0 0)"
+ ]
+ },
+ "surface": {
+ "role": "neutral",
+ "displayName": "Dashboard Surface",
+ "canonical": "#2B2B2B",
+ "tonalRamp": [
+ "oklch(0.14 0 0)",
+ "oklch(0.20 0 0)",
+ "oklch(0.26 0 0)",
+ "oklch(0.34 0 0)",
+ "oklch(0.46 0 0)",
+ "oklch(0.62 0 0)",
+ "oklch(0.78 0 0)",
+ "oklch(0.94 0 0)"
+ ]
+ },
+ "primary": {
+ "role": "primary",
+ "displayName": "Kilo Action Yellow-Green",
+ "canonical": "#EDFF00",
+ "tonalRamp": [
+ "oklch(0.18 0.06 108)",
+ "oklch(0.30 0.09 108)",
+ "oklch(0.42 0.11 108)",
+ "oklch(0.55 0.13 108)",
+ "oklch(0.68 0.14 108)",
+ "oklch(0.80 0.15 108)",
+ "oklch(0.90 0.15 108)",
+ "oklch(0.95 0.15 108)"
+ ]
+ },
+ "secondary": {
+ "role": "secondary",
+ "displayName": "Workhorse Gray",
+ "canonical": "#3D3D3D",
+ "tonalRamp": [
+ "oklch(0.14 0 0)",
+ "oklch(0.20 0 0)",
+ "oklch(0.27 0 0)",
+ "oklch(0.34 0 0)",
+ "oklch(0.44 0 0)",
+ "oklch(0.58 0 0)",
+ "oklch(0.74 0 0)",
+ "oklch(0.90 0 0)"
+ ]
+ },
+ "link": {
+ "role": "secondary",
+ "displayName": "Legacy Link Blue",
+ "canonical": "#3B82F6",
+ "tonalRamp": [
+ "oklch(0.18 0.08 264)",
+ "oklch(0.30 0.12 264)",
+ "oklch(0.42 0.16 264)",
+ "oklch(0.54 0.20 264)",
+ "oklch(0.64 0.22 264)",
+ "oklch(0.74 0.18 264)",
+ "oklch(0.84 0.12 264)",
+ "oklch(0.94 0.06 264)"
+ ]
+ },
+ "red-500": {
+ "role": "tertiary",
+ "displayName": "Destructive Red",
+ "canonical": "#EF4444",
+ "tonalRamp": [
+ "oklch(0.18 0.06 25)",
+ "oklch(0.30 0.10 25)",
+ "oklch(0.42 0.14 25)",
+ "oklch(0.54 0.18 25)",
+ "oklch(0.64 0.20 25)",
+ "oklch(0.74 0.16 25)",
+ "oklch(0.84 0.10 25)",
+ "oklch(0.94 0.05 25)"
+ ]
+ }
+ },
+ "typographyMeta": {
+ "display": {
+ "displayName": "Display",
+ "purpose": "Empty-state hero moments and onboarding only."
+ },
+ "headline": {
+ "displayName": "Headline",
+ "purpose": "Page titles and major decision screens."
+ },
+ "title": { "displayName": "Title", "purpose": "Card groups, dialogs, and page sections." },
+ "body": { "displayName": "Body", "purpose": "Default product UI text." },
+ "code": {
+ "displayName": "Code",
+ "purpose": "Commands, terminal output, costs, timestamps, and dense numeric columns."
+ }
+ },
+ "shadows": [
+ {
+ "name": "input-recess",
+ "value": "0 1px 0 0 rgb(0 0 0 / 0.2)",
+ "purpose": "Inputs and small recessed controls."
+ },
+ {
+ "name": "floating-chrome",
+ "value": "0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.4)",
+ "purpose": "Popovers, tooltips, menus, and dropdowns."
+ },
+ {
+ "name": "dialog-lift",
+ "value": "0 10px 15px -3px rgb(0 0 0 / 0.5), 0 4px 6px -4px rgb(0 0 0 / 0.4)",
+ "purpose": "Dialogs over a dark overlay."
+ },
+ {
+ "name": "agent-glow",
+ "value": "0 0 24px #EDFF0059",
+ "purpose": "Focused or streaming agent surfaces only."
+ }
+ ],
+ "motion": [
+ {
+ "name": "ease-out-strong",
+ "value": "cubic-bezier(0.23, 1, 0.32, 1)",
+ "purpose": "Purposeful state transitions and entrances."
+ },
+ {
+ "name": "sidebar-width",
+ "value": "200ms linear",
+ "purpose": "Sidebar expand and collapse width transition."
+ }
+ ],
+ "breakpoints": [
+ { "name": "md", "value": "768px" },
+ { "name": "sidebar-mobile", "value": "md:hidden sheet behavior" }
+ ]
+ },
+ "components": [
+ {
+ "name": "Primary Button",
+ "kind": "button",
+ "refersTo": "button-primary",
+ "description": "One per surface. The action the user came to perform.",
+ "html": "",
+ "css": ".ds-btn-primary { display:inline-flex; align-items:center; justify-content:center; gap:8px; height:36px; padding:0 14px; border:0; border-radius:6px; background:#EDFF00; color:#1F1F1F; font-family:Inter, ui-sans-serif, system-ui, sans-serif; font-size:14px; font-weight:500; cursor:pointer; transition:background-color 160ms cubic-bezier(0.23,1,0.32,1), box-shadow 160ms cubic-bezier(0.23,1,0.32,1); } .ds-btn-primary:hover { background:#D6E600; } .ds-btn-primary:focus-visible { outline:none; box-shadow:0 0 0 3px #EDFF0059; } .ds-btn-primary:disabled { opacity:.5; pointer-events:none; }"
+ },
+ {
+ "name": "Secondary Button",
+ "kind": "button",
+ "refersTo": "button-secondary",
+ "description": "Default non-primary action for dense product surfaces.",
+ "html": "",
+ "css": ".ds-btn-secondary { display:inline-flex; align-items:center; justify-content:center; gap:8px; height:36px; padding:0 14px; border:1px solid #FFFFFF1A; border-radius:6px; background:#3D3D3D; color:#FAFAFA; font-family:Inter, ui-sans-serif, system-ui, sans-serif; font-size:14px; font-weight:500; cursor:pointer; transition:background-color 160ms cubic-bezier(0.23,1,0.32,1), box-shadow 160ms cubic-bezier(0.23,1,0.32,1); } .ds-btn-secondary:hover { background:#4D4D4D; } .ds-btn-secondary:focus-visible { outline:none; box-shadow:0 0 0 3px #EDFF0059; }"
+ },
+ {
+ "name": "Ghost Button",
+ "kind": "button",
+ "refersTo": "button-ghost",
+ "description": "Quiet inline or row-level action. No chrome at rest.",
+ "html": "",
+ "css": ".ds-btn-ghost { display:inline-flex; align-items:center; justify-content:center; height:36px; padding:0 4px; border:0; border-radius:6px; background:transparent; color:#FAFAFA; font-family:Inter, ui-sans-serif, system-ui, sans-serif; font-size:14px; font-weight:500; text-decoration:underline; text-decoration-color:rgba(255,255,255,.35); text-underline-offset:4px; cursor:pointer; transition:color 160ms cubic-bezier(0.23,1,0.32,1), text-decoration-color 160ms cubic-bezier(0.23,1,0.32,1); } .ds-btn-ghost:hover { text-decoration-color:rgba(255,255,255,1); } .ds-btn-ghost:focus-visible { outline:none; box-shadow:0 0 0 3px #EDFF0059; }"
+ },
+ {
+ "name": "Text Input",
+ "kind": "input",
+ "refersTo": "input",
+ "description": "Compact recessed field aligned to button height.",
+ "html": "",
+ "css": ".ds-input { display:flex; width:100%; min-width:0; height:36px; padding:0 12px; border:1px solid #FFFFFF2E; border-radius:6px; background:#FFFFFF0A; color:#FAFAFA; font-family:Inter, ui-sans-serif, system-ui, sans-serif; font-size:14px; box-shadow:0 1px 0 0 rgb(0 0 0 / .2); transition:box-shadow 160ms cubic-bezier(0.23,1,0.32,1), border-color 160ms cubic-bezier(0.23,1,0.32,1); } .ds-input::placeholder { color:#A3A3A3; } .ds-input:focus-visible { outline:none; border-color:#EDFF00; box-shadow:0 0 0 3px #EDFF0059; } .ds-input[aria-invalid=\"true\"] { border-color:#EF4444; box-shadow:0 0 0 3px rgba(239,68,68,.28); }"
+ },
+ {
+ "name": "Dashboard Card",
+ "kind": "card",
+ "refersTo": "card",
+ "description": "Default product container. Lift comes from value step plus border.",
+ "html": " Kilo Credits, model usage, and active seats stay visible without decoration.
$ kilo run --headless\nsession started 3 minutes ago", + "css": ".ds-terminal { margin:0; padding:16px; border:1px solid #FFFFFF1A; border-radius:10px; background:#121212; color:#FAFAFA; font-family:'Roboto Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-size:13px; line-height:1.5; white-space:pre-wrap; } .ds-terminal-prompt { color:#EDFF00; } .ds-terminal-muted { color:#A3A3A3; } .ds-terminal:focus-within { box-shadow:0 0 24px #EDFF0059; }" + } + ], + "narrative": { + "northStar": "The Operator Console", + "overview": "Kilo Cloud is a trustworthy infrastructure tool for developers, organization admins, and internal operators. It should feel like a calm console for real work: dense, legible, fast to scan, and specific about state, cost, access, and next action.\n\nThe system is dark-first because the product sits next to editors, terminals, and long-running agent sessions. The physical scene is a developer or admin checking active sessions, usage, billing, or security findings during focused work on a laptop or large monitor. Low glare, compact density, and predictable contrast matter more than decorative atmosphere.\n\nThe surface rejects generic SaaS dashboards, neon AI hype, decorative glass or gradient-heavy interfaces, playful consumer app UI, corporate enterprise portals, and vague marketing pages. The single atmospheric exception is the agent surface, where terminal-styled mono and a thin yellow-green glow can signal live work. Everywhere else, utility wins.", + "keyCharacteristics": [ + "Dark-first near-black canvas with stepped charcoal surfaces.", + "Borders carry structure: white at low alpha, not solid gray.", + "Kilo yellow-green is scarce and action-oriented.", + "Compact dashboard rhythm: 36px controls, 48px table rows, 24px card padding.", + "Developer-to-developer clarity: concrete nouns, precise costs, states, timestamps, and links.", + "Existing implementation drift exists in a few legacy components: some button variants still hardcode blue, and the app-level `--primary` token is not yet the Kilo yellow-green. Treat those as migration targets, not design precedent." + ], + "rules": [ + { + "name": "The Yellow Acts Rule", + "body": "Yellow-green means the primary action or live agent focus. If two yellow buttons appear on one surface, one of them is wrong.", + "section": "colors" + }, + { + "name": "The Border Carries Structure Rule", + "body": "Use low-alpha white borders to separate surfaces. Do not tint borders to create hierarchy.", + "section": "colors" + }, + { + "name": "The Blue Is Inline Rule", + "body": "Blue is a legacy inline link role only. It is prohibited as a primary button, badge accent outside its status role, or marketing flourish.", + "section": "colors" + }, + { + "name": "The Mono Earns Its Place Rule", + "body": "Use mono only where alignment or code semantics matter. Do not use mono as decoration or inline emphasis in prose.", + "section": "typography" + }, + { + "name": "The Sentence Case Rule", + "body": "User-facing product chrome uses sentence case. Title Case is wrong for buttons, nav, section titles, badges, dialogs, and empty states.", + "section": "typography" + }, + { + "name": "The Dense Data Rule", + "body": "Numbers that must be compared across rows use Roboto Mono with tabular alignment.", + "section": "typography" + }, + { + "name": "The Value Step Rule", + "body": "Lift comes from background value plus border. A card should still read correctly if every shadow is removed.", + "section": "elevation" + }, + { + "name": "The Glass Is Sticky Chrome Rule", + "body": "Backdrop blur belongs on topbars or persistent overlays only. Glass cards are prohibited.", + "section": "elevation" + }, + { + "name": "The Shadow Must Float Rule", + "body": "If an element is part of normal page flow, do not add a shadow to make it look important.", + "section": "elevation" + } + ], + "dos": [ + "Do use `background` under `surface` under `surface-raised` to build hierarchy.", + "Do use Kilo yellow-green for exactly one primary action per surface.", + "Do use low-alpha white borders: `border` for default structure, `border-strong` for inputs and focused chrome.", + "Do keep product surfaces compact: 36px controls, 48px rows, 24px card padding, and 24px vertical card gaps.", + "Do use Roboto Mono for costs, latencies, token counts, timestamps, commands, and terminal output.", + "Do use concrete labels, costs, states, timestamps, links, and next actions.", + "Do preserve sentence case and the Kilo naming rules from PRODUCT.md.", + "Do use Lucide icons with restrained sizing and consistent stroke.", + "Do treat current blue primary buttons and near-white app `--primary` as legacy drift to migrate away from." + ], + "donts": [ + "Don't create generic SaaS dashboards.", + "Don't create neon AI hype.", + "Don't use decorative glass or gradient-heavy interfaces.", + "Don't make the product feel like a playful consumer app UI.", + "Don't make the product feel like a corporate enterprise portal.", + "Don't create vague marketing pages inside product chrome.", + "Don't use blue as a primary button background. Blue is inline link/accent only.", + "Don't put more than one yellow button on a screen.", + "Don't introduce new status hues beyond the documented status palette.", + "Don't use pure black or pure white in product surfaces.", + "Don't add per-element drop shadows to default cards.", + "Don't use glass cards, side-stripe borders, gradient text, hero-metric templates, or repeated identical icon-card grids.", + "Don't use ambiguous billing language. Kilo Credits are purchased usage credit; tokens are model input and output volume.", + "Don't use old names such as \"Kilo For Teams,\" \"Kilo for Enterprise,\" \"Kilo for Organizations,\" \"Kilo Code Deploy,\" \"Kilo Managed Indexing,\" or \"Kilo Cloud Agents.\"" + ] + } +} diff --git a/.plans/kiloclaw-org-billing.md b/.plans/kiloclaw-org-billing.md new file mode 100644 index 0000000000..805fce71bd --- /dev/null +++ b/.plans/kiloclaw-org-billing.md @@ -0,0 +1,903 @@ +# Organization KiloClaw Billing — Unified Implementation Plan + +## Status + +Plan only. Supersedes and merges: + +- `.plans/org-kiloclaw-billing-backend.md` +- `.plans/org-kiloclaw-billing-ui.md` +- `.plans/org-kiloclaw-billing-admin.md` + +Business rules remain governed by: + +- `.specs/kiloclaw-billing.md` +- `.specs/kiloclaw-datamodel.md` +- `.specs/team-enterprise-seat-billing.md` + +## Goals + +Implement organization KiloClaw billing end-to-end: + +- Org KiloClaw is pure-credit, month-to-month, per-instance, org-funded. +- Org KiloClaw remains subordinate to parent org seat/trial entitlement. +- Enterprise-only opt-out blocks org KiloClaw only while the org is Enterprise. +- Any org member may provision/manage their own org KiloClaw instance when allowed. +- Owners and billing managers may view/manage org KiloClaw billing details. +- Non-billing-admin associated users see only operational access state/contact-admin prompts. +- Existing org KiloClaw instances receive one 30-day billing-launch trial at launch. +- Renewal, past-due, suspension, destruction, notifications, auto top-up, access gates, admin support, audit, and GDPR behavior satisfy the specs. + +## Non-goals + +- No direct Stripe hosting subscription for org KiloClaw. +- No org seat-subscription add-on line items for org KiloClaw. +- No org KiloClaw commit plan or plan-switching UI/API. +- No customer-facing cancel-without-destroy or reactivation in MVP. +- No DB uniqueness constraint for one active org KC instance per user/org; enforce in UI/router/service logic. + +## Resolved contract decisions + +### Launch date and pre-launch behavior + +`KILOCLAW_ORG_BILLING_LAUNCH_DATE` is the enforcement switch. + +Before launch date is set/reached: + +- Existing org KiloClaw behavior remains non-charging and non-blocking on org KC credits. +- New org KC provisioning may create bootstrap rows, but these rows are treated as pre-launch/unbilled org KC state. +- Org KC billing UI may render read-only/pre-launch copy, but MUST NOT imply active paid org KC billing. +- Renewal charging, org credit insufficiency blocking, launch trials, and billing-launch notification flows stay inert. + +At/after launch: + +- Active org-owned KC instances are reconciled into canonical org KC subscription rows. +- Existing active org instances receive one 30-day launch trial ending `launchDate + 30 days`. +- Launch backfill never creates a second subscription row for an instance. +- Launch backfill updates an existing pre-launch row when present; creates only when missing. +- Destroyed org instances are ignored by launch backfill unless a row is needed only for historical remediation. + +### Trial eligibility + +A user receives at most one free org KC trial per organization. + +Eligibility for a new 7-day org KC trial is false when any historical org KC instance/subscription record exists for the `(associatedUserId, organizationId)` pair, including: + +- active rows, +- destroyed rows, +- canceled rows, +- 7-day trial rows, +- 30-day launch-backfill rows. + +The 30-day launch trial is migration-granted access and blocks future reusable 7-day org KC trials. Destroy/recreate after launch backfill requires sufficient org credits immediately. + +### Parent entitlement precedence + +Parent organization entitlement is the highest-level org KC gate because org KC is subordinate to org seat/trial entitlement. + +Shared helpers MUST expose explicit state instead of relying on ad hoc UI ordering: + +- `deriveParentEntitlementState(org, latestSeatPurchase, now)` +- `deriveOrgKiloclawProvisionState(input)` +- `deriveOrgKiloclawAccessState(input)` +- `deriveOrgKiloclawDisplayState(input)` + +Provision-state priority: + +1. `blocked_parent_entitlement` +2. `blocked_opt_out` +3. `blocked_existing_instance` +4. `blocked_insufficient_credits` +5. `allowed` + +Access-state priority: + +1. instance missing/destroyed/quarantined => denied +2. parent entitlement blocks => denied +3. Enterprise opt-out enforced => denied +4. local subscription grants access => allowed +5. otherwise denied + +Display-state MUST return both: + +- `blocker: null | blocked_parent_entitlement | blocked_opt_out | quarantined` +- `lifecycle: not_provisioned | active | trialing | past_due_grace | past_due_suspended | canceling_at_period_end | canceled | destroyed` + +UI badges may derive a single label from `(blocker, lifecycle)`, but APIs MUST return the richer shape so admin, member, and provisioning surfaces do not contradict each other. + +Parent entitlement outcomes: + +- `allowed`: require-seats disabled, active subscription purchase, or organization trial not hard-expired. +- `org_trial_hard_expired`: blocks org KC access/provisioning, does not immediately cancel org KC rows. +- `org_subscription_ended`: blocks access/provisioning and immediately cancels all live org KC subscriptions. +- `no_org_subscription`: blocks only when the org has no valid trial/entitlement path under existing seat-billing rules. + +### Enterprise opt-out + +- Stored in organization settings as `kiloclaw_opt_out?: boolean`. +- Configurable only for Enterprise orgs by owners/billing managers. +- Persisted while Teams but hidden/non-configurable and not enforced while Teams. +- Enforced again if the org returns to Enterprise. +- When enforced: block provisioning, block access, prevent future org KC renewals. +- Opt-out does not by itself cancel subscriptions. +- Renewal while opt-out is enforced: skip charging, leave `credit_renewal_at` due, set a non-terminal paused/blocked display state through the blocker, and re-evaluate immediately after opt-out is disabled. Do not catch up multiple missed periods in one sweep. + +### Role visibility + +Customer API payloads are role-discriminated. + +Admin role (`owner`, `billing_manager`; site admin projected consistently with existing org middleware) MAY receive: + +- subscription ID, +- instance ID, +- plan/payment source, +- status, +- current period dates, +- credit renewal date, +- trial start/end, +- launch-backfill flag, +- renewal cost, +- org credit balance, +- auto top-up status, +- associated user basics, +- parent entitlement summary, +- opt-out state. + +Member role MUST NOT receive: + +- subscription IDs, +- credit balance, +- invoices, +- price/cost/shortfall, +- billing period dates, +- renewal dates, +- exact trial end dates, +- provider IDs. + +Member role MAY receive: + +- operational access state, +- whether action is needed from a billing admin, +- generic trialing/available/blocked/past-due/canceling labels, +- contact-admin copy. + +Internal `/admin` surfaces may show billing details to Kilo staff, subject to internal admin authorization and audit rules. + +### Credit and ledger rules + +Org KC paid rows are pure credit rows: + +- `payment_source = 'credits'` +- provider subscription ID is null +- plan is internally `standard` +- no Stripe hosting subscription +- no seat-subscription add-on line item + +Cost is `9_000_000` microdollars per month until a later pricing rule changes it. + +Org credit balance is computed as: + +```text +total_microdollars_acquired - microdollars_used +``` + +Deductions: + +- insert `credit_transactions` with `organization_id`, associated `kilo_user_id`, negative amount, deterministic period/category key, and category-uniqueness protection, +- increment `organizations.microdollars_used`, +- keep deprecated `organizations.microdollars_balance` synchronized while the column exists, +- do not decrement `total_microdollars_acquired`, +- write subscription change log in the same transaction when subscription state mutates. + +### Lifecycle semantics + +Creation: + +- Requires sufficient org credits for the first paid period even when a 7-day org KC trial is granted. +- Exception: launch backfill does not require or deduct credits at creation. +- If trial eligible, create trialing row with trial end `max(now + 7 days, orgTrialEndAt when org trial extends longer)`. +- If not trial eligible, deduct first month and create active row. + +Renewal: + +- Select due pure-credit org rows, including trialing/active/past-due rows. +- Hybrid/Stripe paths do not apply to org KC. +- Advance exactly one period per successful sweep run; no multi-period catch-up in one pass. +- At trial end, successful deduction transitions `trialing -> active`. +- Insufficient credits triggers org auto top-up once per period when available. +- If auto top-up cannot/does not resolve, set/preserve `past_due_since`, send role-aware notifications, then existing past-due lifecycle applies. + +Past due: + +- Non-suspended past-due continues to grant access during the 14-day grace period. +- After 14 days, stop instance, set `suspended_at`, set destruction deadline 7 days out, send notifications. +- On recovery, deduct due period, transition active, attempt auto-resume, clear suspension fields only after successful start/no-instance case. + +User destroy: + +- Tear down infrastructure immediately. +- Mark instance destroyed. +- Set subscription `cancel_at_period_end = true`. +- No refund/proration. +- Period-boundary cancellation sweep includes destroyed canceling rows and sets status `canceled` without charging. +- Destroyed canceling rows MUST NOT be skipped before the cancel branch runs. + +Parent entitlement ended: + +- Immediately cancel all live org KC rows for the org. +- Clear no-renew fields so rows do not renew. +- Preserve instance records; do not destroy instances unless the spec changes. +- Record durable cancellation reason (`parent_entitlement_ended`) or equivalent indexed status reason, not only a change-log note. + +Parent trial hard-expired: + +- Block access/provisioning. +- Do not immediately cancel org KC rows. +- Do not let org KC state recover or extend parent entitlement. + +Opt-out enforced: + +- Block access/provisioning. +- Do not deduct renewals while enforced. +- Do not cancel solely due to opt-out. +- On disable, next renewal sweep processes at most one due period. + +### Data model, audit, and change log + +Before org KC subscription mutations ship, implement `kiloclaw_subscription_change_log` per `.specs/kiloclaw-datamodel.md`: + +- append-only, +- one entry per business mutation, +- DB-server timestamp, +- actor type/id (`user` or `system`), +- consistent action labels, +- before/after state detail, +- optional safe context/reason, +- no tokens/secrets/card data, +- GDPR anonymization support. + +Every subscription mutation in this plan MUST write a change-log entry: + +- bootstrap creation, +- launch backfill create/update, +- trial -> active, +- active renewal, +- past-due transition, +- suspension/destruction scheduling, +- auto-resume recovery, +- user destroy/cancel-at-period-end, +- period-end cancellation, +- parent-ended cancellation, +- admin override, +- quarantine/remediation mutation. + +Action labels must be documented before use. + +### Bootstrap and orphan handling + +Target creation order follows `.specs/kiloclaw-datamodel.md`: + +1. infrastructure exists, +2. `kiloclaw_instance` row exists, +3. billing bootstrap creates corresponding subscription row in the same provisioning request. + +If bootstrap fails after instance creation: + +- provisioning service retries or runs fallback bootstrap before returning, +- request MUST NOT complete successfully with an unpaired instance, +- if all bootstrap paths fail, mark the instance explicitly quarantined and return failure, +- onboarding completion/ding waits for both instance and subscription persistence. + +Existing-active-instance checks during bootstrap MUST exclude the target instance that was just created. + +## Unified user/admin surfaces + +### Customer-facing org surfaces + +- `/organizations/[id]/claw/subscription`: role-aware subscription/status page. +- `/organizations/[id]/claw/*`: ambient banner + lock dialog mounted in org claw layout. +- `/organizations/[id]/claw/new`: preflight-gated provisioning wizard. +- `/organizations/[id]/settings`: settings shell with tabs. +- `/organizations/[id]/settings?tab=kiloclaw`: Enterprise-only opt-out control. +- Existing destroy flow: org-aware copy, launch-trial forfeiture copy, cancel-at-period-end side effect. + +### Customer-facing owner/billing-manager follow-ups + +- `/organizations/[id]/subscriptions`: org KC group next to seats group. +- `/organizations/[id]/subscriptions/kiloclaw/[instanceId]`: org KC detail/history page. +- Org dashboard alert tile for org-wide KC past-due/suspended/parent-canceled counts. +- Associated-user dashboard banner for the viewer's own org KC state. +- Associated-user chip in claw settings header. + +### Internal `/admin` surfaces + +- `/admin/kiloclaw`: org-aware filters/search/stats/readiness. +- `/admin/kiloclaw/[id]`: billing support card, operational reason, links, raw IDs, change logs. +- `/admin/users/[id]?tab=kiloclaw`: separate personal/org rows; personal-only actions disabled on org rows unless explicit org-safe override exists. +- `/admin/organizations/[id]`: org KC support section with aggregate counts and instance/subscription list. +- Admin readiness/health card for launch date, launch backfill, orphan/quarantine rows, due renewals, past-due/suspended counts. +- Saved incident filters for org past-due, suspended, launch trials ending, canceling, parent-blocked, opt-out-blocked, orphan/quarantined. + +## API/procedure contracts + +### Customer org procedures + +`organizations.kiloclaw.getBillingStatus({ organizationId })` + +- Server derives role. +- Returns redacted member payload or admin detail payload. +- Includes `blocker`, `lifecycle`, and `access` objects. +- Member payload contains no prohibited billing fields. +- Loads destroyed/canceling rows when needed to explain lifecycle. + +`organizations.kiloclaw.getProvisionPreflight({ organizationId })` + +- Server derives role. +- Uses `deriveOrgKiloclawProvisionState`. +- Admin blocked-insufficient-credit payload includes balance/cost/shortfall. +- Member blocked-insufficient-credit payload contains no balance/cost/shortfall. +- Allowed admin payload includes first paid cost and estimated trial end if relevant. +- Allowed member payload includes only `trialEligible` and generic copy inputs. + +`organizations.kiloclaw.provision(...)` + +- Runs preflight inside the existing org provision lock. +- Recomputes server-side; does not trust client preflight. +- Throws structured `PRECONDITION_FAILED` on blockers. +- Calls worker/billing bootstrap; success requires paired instance + subscription. + +`organizations.kiloclaw.destroy(...)` + +- Calls shared org KC destroy helper. +- Infrastructure teardown and subscription cancel-at-period-end are one logical operation with compensation/rollback on failure. +- Writes change log. + +`organizationSettings.setKiloClawOptOut({ organizationId, optOut })` + +- Owner/billing-manager only. +- Enterprise-only mutation. +- JSONB merge preserving other settings. +- Writes org settings audit log. +- Teams preserves value but cannot configure/enforce. + +### Backend worker/service APIs + +`services/kiloclaw-billing`: + +- launch backfill/reconciliation sweep, +- authoritative org bootstrap, +- org credit renewal, +- org lifecycle sweeps, +- parent-ended cancellation reconciliation, +- org auto top-up side-effect call, +- change-log writes. + +`services/kiloclaw`: + +- direct proxy/user-route/access-code gates for org instances, +- blocks bookmarked/direct access when parent/opt-out/subscription state denies access, +- preserves personal behavior. + +### Internal admin procedures + +`admin.kiloclawInstances.list/get` additions: + +- scope filters, +- org search, +- operational filters, +- trial-kind filters, +- org/associated-user metadata, +- billing support payload, +- readiness/health aggregates. + +`admin.kiloclawInstances.getSubscriptionChangeLogs({ instanceId | subscriptionId })` + +- Paginated/support-safe change log access. + +`organizations.admin.getOrgKiloclawHealthSummary({ organizationId })` + +- Admin org page aggregate counts. + +Customer owner/billing-manager procedures for follow-up surfaces: + +- `listSubscriptions` +- `getSubscriptionDetail` +- `getBillingHistory` +- `getOrgKiloclawHealthSummary` + +## Vertical slice implementation sequence + +Each slice should be independently reviewable and tested. Slices 0–8 are launch-critical and must deploy before ops sets `KILOCLAW_ORG_BILLING_LAUNCH_DATE`. Slices 9–11 may follow if support accepts launch without those convenience surfaces; internal launch readiness in Slice 2 is still launch-critical. + +### Slice 0 — Foundation: schema, config, contracts, change log + +**Backend** + +- Add `kiloclaw_subscriptions.is_launch_backfill boolean NOT NULL DEFAULT false`. +- Add org subscription cancellation/status reason field if needed for parent-ended/admin counts. +- Add `OrganizationSettingsSchema.kiloclaw_opt_out?: boolean`. +- Add `kiloclaw_subscription_change_log` schema and documented action labels. +- Add shared mutation/change-log helper. +- Add launch-date config parsing in Next.js and `services/kiloclaw-billing`. +- Add shared helpers for cost, org credit balance, parent entitlement, opt-out enforcement, provision/access/display state. + +**UI** + +- Add shared TypeScript types for role-redacted status/preflight contracts. +- No customer-visible behavior change. + +**Admin** + +- No visible behavior change. + +**Tests** + +- Schema/type tests where applicable. +- Fixture tests for parent entitlement, opt-out enforcement, launch switch, provision/access/display state, role redaction. +- Change-log helper tests, including transaction rollback behavior. + +**Exit criteria** + +- Contracts compile and are fixture-tested. +- No subscription mutation path added after this may bypass the change-log helper. + +### Slice 1 — Launch reconciliation and readiness + +**Backend** + +- Implement launch backfill/reconciliation sweep/script. +- Update existing pre-launch org KC rows in place; create only missing rows. +- Never create duplicate subscription rows for one instance. +- Set launch rows: `standard`, `trialing`, `credits`, provider ID null, trial/current period/credit renewal ending `launchDate + 30 days`, `is_launch_backfill = true`, no deduction. +- Treat launch rows as historical org KC records for future 7-day trial eligibility. +- Ignore destroyed instances except remediation reporting. + +**UI** + +- Optional pre-launch subscription page copy behind read-only status contract. + +**Admin** + +- Add `/admin/kiloclaw` readiness card: + - launch date configured/unset, + - active org instances without subscription rows, + - existing rows needing launch reconciliation, + - launch trial row count/common end date, + - orphan/quarantined rows, + - due-renewal/past-due/suspended counts. + +**Tests** + +- Idempotency. +- Existing-row update vs missing-row create. +- No duplicate subscription per instance. +- No credit deduction. +- Destroyed ignored. +- Launch row blocks future 7-day trial. +- Readiness aggregate links/filter counts. + +**Exit criteria** + +- Ops can dry-run/review launch impact before setting launch date. + +### Slice 2 — Provision preflight and authoritative bootstrap + +**Backend** + +- Implement `getProvisionPreflight`. +- Gate `provision` mutation with server-side preflight inside provision lock. +- Update billing-worker bootstrap to recompute and enforce: + - parent entitlement, + - Enterprise opt-out, + - existing active instance excluding target instance, + - trial eligibility, + - org credit sufficiency. +- Implement transactional bootstrap: + - trialing row if eligible, + - active paid row + first-period org credit deduction if not eligible, + - change log, + - idempotent duplicate handling, + - fallback/quarantine on bootstrap failure. + +**UI** + +- Refactor `/organizations/[id]/claw/new` to preflight dispatcher. +- Add blocks for parent entitlement, opt-out, existing instance, insufficient credits. +- Admin copy can show cost/balance/shortfall. +- Member copy must not show cost/balance/shortfall. +- Allowed wizard copy handles org-trial-extended trial end for admins; members receive generic role-safe copy. + +**Admin** + +- `/admin/kiloclaw` readiness card links to orphan/quarantine rows. +- Admin instance list can show `quarantined`/bootstrap-failed state when present. + +**Tests** + +- Preflight branch matrix by role. +- Member redaction assertions. +- Bootstrap trial, paid, insufficient credits, parent block, opt-out block, duplicate, target-instance exclusion, quarantine failure. +- Onboarding success waits for instance + subscription. + +**Exit criteria** + +- New provisioning cannot create a live unpaired org instance and cannot bypass billing rules. + +### Slice 3 — Role-aware billing status and customer read surface + +**Backend** + +- Implement `getBillingStatus` with `blocker`, `lifecycle`, `access`. +- Include admin-only subscription/org credit/parent/opt-out details. +- Include member-safe operational state only. +- Load current viewer's org KC row, including destroyed/canceling rows when needed. +- Soft-deleted associated users return anonymized values. + +**UI** + +- Add `/organizations/[id]/claw/subscription`. +- Add owner/billing-manager admin card. +- Add member state card. +- Add shared status badges/shell components reused by ambient surfaces. +- Sidebar adds org KC Subscription link. + +**Admin** + +- Extend `/admin/kiloclaw` list with org/customer row metadata enough to cross-link customer subscription page. + +**Tests** + +- Status matrix for blocker/lifecycle/access combinations. +- Member payload contains no IDs, costs, balances, period dates, renewal dates, exact trial ends, provider IDs. +- Admin payload includes expected details. +- Page rendering matrix. + +**Exit criteria** + +- Users and billing admins can understand current org KC state without mutation side effects. + +### Slice 4 — Access gates and ambient locks + +**Backend** + +- Add org KC access middleware to Next.js org KC routes operating on existing instances. +- Add direct worker gate in `services/kiloclaw` for: + - `/i/:instanceId/*`, + - active-instance cookie routing, + - user-facing APIs accepting `instanceId`, + - access-code/open-instance flows. +- Allow status/preflight/service-degraded/latest-version queries where needed. +- Past-due grace grants access; suspended past-due blocks. +- Parent block and opt-out block promptly. + +**UI** + +- Mount `OrgBillingWrapper` in org claw layout. +- Add org billing banner and access-locked dialog. +- Lock fires for parent block, opt-out, and suspended past-due. +- Banners show role-safe admin/member CTAs. + +**Admin** + +- `/admin/kiloclaw/[id]` billing support card can show access-denial reason. + +**Tests** + +- tRPC gates reject blocked states and allow grace-period past-due. +- Worker proxy/access-code/direct routes reject blocked org instances. +- Personal routes unchanged. +- Banner/lock state tests by role. + +**Exit criteria** + +- UI locks are not the only enforcement; direct worker access is blocked. + +### Slice 5 — Renewal, lifecycle, auto top-up, notifications + +**Backend** + +- Implement org pure-credit renewal path. +- Include `trialing`, `active`, `past_due`; exclude hybrid/Stripe. +- Process cancel-at-period-end before deduction, including destroyed rows. +- Enforce one-period-per-sweep advancement. +- Implement org auto top-up fire-and-skip with durable one-attempt-per-period marker. +- Implement insufficient-credit past-due transition. +- Implement 14-day suspension, 7-day destruction deadline, destruction warning, destruction, and interrupted auto-resume for org rows. +- Add role-aware notification recipient/idempotency model: + - owners/billing managers receive top-up/admin CTA, + - associated non-admin receives contact-admin copy, + - each recipient/lifecycle event is idempotent. +- Add Next internal side effect `trigger_organization_auto_top_up` calling existing org auto top-up logic. + +**UI** + +- Billing status and banners render past-due grace/suspended/admin-action states from status API. +- Admin users get top-up CTAs; members get contact-admin copy. + +**Admin** + +- `/admin/kiloclaw` filters/stats include due soon, past due, suspended, destruction deadline. +- Detail support card shows auto-top-up marker/status when available. + +**Tests** + +- Trial conversion to paid active. +- Active renewal. +- Past-due recovery. +- Suspended recovery + auto-resume. +- Insufficient credits. +- Auto top-up fire-and-skip. +- Auto top-up already-attempted => past due. +- Cancel-at-period-end no charge. +- Opt-out skip. +- One-period-per-sweep overdue row. +- Notifications by recipient/role/idempotency. + +**Exit criteria** + +- Org KC can renew, fail, suspend, recover, and notify according to spec. + +### Slice 6 — Destroy cancellation end-to-end + +**Backend** + +- Implement shared org destroy helper. +- Destroy infra immediately; mark instance destroyed. +- Set subscription `cancel_at_period_end = true`. +- No refund/proration. +- Write change log. +- Compensate/rollback subscription flag if worker destroy fails and instance destruction is rolled back. +- Period-boundary cancellation sweep transitions destroyed canceling rows to `canceled` without charging. + +**UI** + +- Update destroy confirmation copy. +- Include launch-backfill forfeiture copy when applicable. +- No cancel-without-destroy or reactivate controls. + +**Admin** + +- `admin.kiloclawInstances.destroy` calls same helper or proves equivalent side effects. +- Disable unsafe personal-only cancel/trial-reset actions for org rows. +- `/admin/users/[id]?tab=kiloclaw` separates personal/org rows and active-instance links. + +**Tests** + +- Customer destroy and admin destroy parity. +- Destroy marks instance destroyed + subscription canceling. +- Worker failure leaves subscription unchanged or compensates. +- Period-boundary cancellation sees destroyed canceling row and does not deduct. +- Launch-backfill destroy copy. + +**Exit criteria** + +- Destroy is the only customer termination path and matches spec. + +### Slice 7 — Enterprise opt-out settings end-to-end + +**Backend** + +- Implement `setKiloClawOptOut`. +- Add active-instance count query. +- Enforce Enterprise-only configuration. +- Persist Teams value without enforcement. +- Audit org settings change. +- Ensure provision/access/renewal/status helpers consume opt-out enforcement consistently. + +**UI** + +- Restructure org settings page into tabs. +- Redirect `/providers-and-models` to `/settings?tab=providers-and-models`. +- Add `kiloclaw` tab. +- Teams copy: org KC is available; the opt-out control is Enterprise-only. +- Enterprise toggle: + - enabling requires confirmation with active-instance count and consequences, + - disabling is one click. + +**Admin** + +- Show opt-out state on admin detail/list/org support surfaces. +- Add admin opt-out mutation only if support explicitly needs it; otherwise read-only. + +**Tests** + +- Enterprise gate. +- Role gate. +- Teams hidden/non-enforced semantics. +- Persisted setting re-enforced after Enterprise transition. +- Audit entry. +- Active-instance count excludes destroyed. +- UI tab/copy tests. + +**Exit criteria** + +- Enterprise admins can disable org KC, and all gates respect it. + +### Slice 8 — Parent-entitlement-end cancellation and reconciliation + +**Backend** + +- Add event-driven cancellation from seat subscription lifecycle when parent entitlement becomes ended/non-recoverable. +- Add sweep reconciliation as safety net. +- Cancel all live org KC rows for org immediately. +- Record cancellation reason `parent_entitlement_ended`. +- Do not mutate parent subscription. +- Do not destroy instances unless spec changes. +- Hard-expired org trial blocks access but does not cancel. + +**UI** + +- Billing status/subscription page renders parent-blocked reason and admin CTA to org subscription page. +- Member copy says contact owner/billing manager. + +**Admin** + +- List/detail/org pages expose parent-ended cancellation reason and counts. + +**Tests** + +- Ended parent cancels live org KC rows immediately. +- Hard-expired org trial blocks but does not cancel. +- Recoverable parent states do not cancel. +- Parent-canceled rows do not renew. +- Admin counts derive from durable reason. + +**Exit criteria** + +- Parent entitlement cannot be extended/recovered by org KC state, and ended parent stops all org KC renewal/access. + +### Slice 9 — Internal admin launch/support readiness + +**Backend** + +- Expand `admin.kiloclawInstances.list/get` with org billing fields, filters, aggregates, and support payloads. +- Add change-log query by instance/subscription. +- Add cheap admin dashboard aggregate procedures. + +**UI/Admin** + +- `/admin/kiloclaw`: + - scope filter personal/org, + - org ID/name search, + - subscription status filter, + - operational/blocker/lifecycle filters, + - trial-kind filter, + - split stats personal vs org, + - org billing health counts. +- `/admin/kiloclaw/[id]`: + - billing support card, + - reason callouts, + - links to user/org/customer pages/change logs, + - collapsed raw IDs. +- `/admin/users/[id]?tab=kiloclaw`: + - separate personal/org sections, + - no personal-only actions on org rows. +- `/admin/organizations/[id]`: + - org KC support card/table, + - credit balance, + - renewal-cost aggregate, + - blocked/past-due/suspended/canceling counts. + +**Tests** + +- Admin filter/search/count tests. +- Support card state tests. +- Mutation guard tests. +- Change-log rendering/query tests. + +**Exit criteria** + +- Support can triage org KC billing at launch without ad hoc DB queries. + +### Slice 10 — Customer owner/billing-manager subscription overview follow-up + +**Backend** + +- Add owner/billing-manager procedures: + - `listSubscriptions`, + - `getSubscriptionDetail`, + - `getBillingHistory`, + - `getOrgKiloclawHealthSummary`. +- Define org KC credit transaction category convention before history query ships. +- Cursor paginate lists/history. + +**UI** + +- Add org KC group on `/organizations/[id]/subscriptions`. +- Add org KC detail page under `/subscriptions/kiloclaw/[instanceId]`. +- Add org dashboard alert tile for past-due/suspended/parent-canceled counts. + +**Admin** + +- Reuse procedure shapes where useful; no new internal requirements. + +**Tests** + +- Admin-gate enforcement for customer owner/billing-manager procedures. +- Pagination. +- Terminal filter. +- Aggregate counts. +- Detail/history rendering. + +**Exit criteria** + +- Org billing admins have customer-facing fleet overview and history. + +### Slice 11 — Associated-user polish follow-up + +**Backend** + +- Extend org `getStatus` with `associatedUser` basics. +- Personal `getStatus` returns `associatedUser: null`. +- Use soft-deleted/anonymized user values. + +**UI** + +- Add associated-user dashboard banner for current user's own org KC state. +- Add associated-user chip in claw settings header. +- Keep `/profile` personal-only; no org trial copy there. + +**Admin** + +- No new admin requirement. + +**Tests** + +- Banner variant derivation by role/state/trial threshold. +- Chip render/null/self tests. +- Soft-deleted user PII safety. +- Personal status null associated user. + +**Exit criteria** + +- Associated ownership is visible without changing billing behavior. + +## Launch checklist + +Before ops sets `KILOCLAW_ORG_BILLING_LAUNCH_DATE`: + +1. Slices 0–8 deployed. +2. Admin readiness card shows no unremediated active org instances without canonical subscriptions, except explicit quarantines. +3. Launch backfill dry-run/reconciliation count reviewed. +4. Worker direct access gate deployed. +5. Billing worker renewal/lifecycle deployed. +6. Parent-ended cancellation event + sweep deployed. +7. Opt-out settings deployed and enforced only for Enterprise. +8. Role-redaction tests prove member payloads contain no prohibited billing fields. +9. Change-log coverage exists for every org KC subscription mutation. +10. GDPR audit complete for associated-user/admin PII exposure and change logs. +11. Alerting/log dashboards cover bootstrap failures, launch backfill failures, renewal failures, auto top-up failures, parent cancellation, access denials, orphan/quarantine counts. +12. Targeted tests and `scripts/typecheck-all.sh --changes-only` pass; run full `pnpm typecheck` only if needed per repo guidance. +13. `pnpm format` run before commit/merge. + +## Verification targets + +Run Postgres first when needed: + +```sh +docker compose -f dev/docker-compose.yml ps postgres +pnpm test:db +``` + +Targeted checks: + +```sh +pnpm test -- apps/web/src/lib/kiloclaw/org-billing/*.test.ts +pnpm test -- apps/web/src/routers/organizations/organization-kiloclaw-router.test.ts +pnpm test -- apps/web/src/routers/organizations/organization-settings-router.test.ts +pnpm test -- apps/web/src/routers/admin-kiloclaw-instances-router.test.ts +pnpm test -- apps/web/src/routers/admin-router.test.ts +pnpm --filter kiloclaw-billing test +pnpm --filter kiloclaw test +scripts/typecheck-all.sh --changes-only +pnpm format +``` + +## Open confirmations before Slice 0 + +Resolve before coding starts: + +1. Exact parent-entitlement helper inputs/outputs from existing seat-billing code. +2. Durable field name and enum values for org KC cancellation/status reason. +3. Quarantine marker location on `kiloclaw_instance` or related operational table. +4. Final org auto top-up API payload for `trigger_organization_auto_top_up`. +5. Whether support needs an internal admin opt-out override in MVP or read-only is sufficient. diff --git a/.specs/kiloclaw-billing.md b/.specs/kiloclaw-billing.md index 400338a073..5c38f80f3f 100644 --- a/.specs/kiloclaw-billing.md +++ b/.specs/kiloclaw-billing.md @@ -2,14 +2,7 @@ ## Role of This Document -This spec defines the business rules and invariants for KiloClaw -billing. It is the source of truth for _what_ the system must -guarantee — valid states, ownership boundaries, correctness -properties, and user-facing behavior. It deliberately does not -prescribe _how_ to implement those guarantees: handler names, column -layouts, conflict-resolution strategies, null-safety patterns, and -other implementation choices belong in plan documents and code, not -here. +This spec defines KiloClaw billing business rules and invariants. It is the source of truth for _what_ the system must guarantee: valid states, ownership boundaries, correctness properties, and user-facing behavior. It deliberately does not prescribe _how_ to implement those guarantees; handler names, column layouts, conflict-resolution strategies, null-safety patterns, and other implementation choices belong in plan documents and code, not here. ## Status @@ -23,137 +16,57 @@ Updated 2026-04-16 -- successor subscription rows on personal reprovision. ## Conventions -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", -"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and -"OPTIONAL" in this document are to be interpreted as described in -BCP 14 [RFC 2119] [RFC 8174] when, and only when, they appear in all -capitals, as shown here. +The BCP 14 keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" are interpreted as described in BCP 14 [RFC 2119] [RFC 8174] only when they appear in all capitals, as shown here. ## Definitions -- **Legacy Stripe subscription**: A subscription with payment source - `stripe` and a non-null payment provider subscription ID. The - payment provider owns all state. -- **Hybrid subscription**: A subscription with payment source - `credits` and a non-null payment provider subscription ID. The - payment provider collects payment; the local billing engine tracks - the period via credits. -- **Pure credit subscription**: A subscription with payment source - `credits` and a null payment provider subscription ID. The local - credit renewal sweep owns all state. -- **Stripe-funded subscription**: Any subscription with a non-null - payment provider subscription ID (legacy Stripe or hybrid). Used - throughout this spec to mean "has Stripe billing infrastructure" - regardless of payment source. -- **Invoice settlement**: The process triggered by a paid KiloClaw - invoice from the payment provider that converts the payment into - balanced credit ledger entries and advances the subscription - period. Defined in Stripe-Funded Credit Settlement. -- **Dunning state**: A non-active payment failure status reported by - the payment provider (past-due, unpaid, or defensive terminal - fallback). -- **Credit balance**: The user's available credit balance, computed as - `total_microdollars_acquired - microdollars_used`. Credits enter the - system by incrementing the acquired counter (purchases, grants, - bonuses). Credits leave the system by incrementing the used counter - (inference usage, pure-credit hosting deductions). The balance MUST - NOT change as a result of Stripe-funded settlement (see - Stripe-Funded Credit Settlement rule 3), which achieves - balance-neutrality by incrementing and then decrementing the - acquired counter. -- **Credit spend**: Any operation that increments the used counter. - Both inference usage and pure-credit KiloClaw hosting deductions are - credit spend. Stripe-funded settlement deductions are NOT credit - spend; they are balance-neutral bookkeeping entries. Credit spend - counts toward the Kilo Pass bonus unlock threshold. +- **Legacy Stripe subscription**: Subscription with payment source `stripe` and non-null provider subscription ID. The payment provider owns all state. +- **Hybrid subscription**: Subscription with payment source `credits` and non-null provider subscription ID. The payment provider collects payment; the local billing engine tracks the period via credits. +- **Pure credit subscription**: Subscription with payment source `credits` and null provider subscription ID. The local credit renewal sweep owns all state. +- **Stripe-funded subscription**: Any subscription with non-null provider subscription ID (legacy Stripe or hybrid). In this spec, means "has Stripe billing infrastructure" regardless of payment source. +- **Invoice settlement**: Process triggered by a paid KiloClaw invoice from the payment provider that converts payment into balanced credit ledger entries and advances the subscription period. Defined in Stripe-Funded Credit Settlement. +- **Dunning state**: Non-active payment failure status reported by the payment provider: past-due, unpaid, or defensive terminal fallback. +- **Credit balance**: User's available credit balance: `total_microdollars_acquired - microdollars_used`. Credits enter by incrementing acquired (purchases, grants, bonuses) and leave by incrementing used (inference usage, pure-credit hosting deductions). Stripe-funded settlement MUST NOT change the balance (see Stripe-Funded Credit Settlement rule 3); it remains balance-neutral by incrementing then decrementing acquired. +- **Credit spend**: Any operation that increments the used counter. Inference usage and pure-credit KiloClaw hosting deductions are credit spend. Stripe-funded settlement deductions are NOT credit spend; they are balance-neutral bookkeeping entries. Credit spend counts toward the Kilo Pass bonus unlock threshold. +- **Organization KiloClaw subscription**: KiloClaw subscription tied to an organizational instance. Associated with a user, owned by an organization, and funded only by the organization credit balance. +- **Associated user**: Organization member whose org KiloClaw instance is provisioned for their use. The associated user is not necessarily authorized to view billing details. +- **Parent organization entitlement**: Organization-level subscription or trial state that controls whether organization features are available. Organization KiloClaw access is subordinate to this state. +- **Organization KiloClaw opt-out**: Enterprise-only organization admin setting that disables organization KiloClaw access and provisioning while enforced. +- **Organization KiloClaw billing launch date**: Date from which existing organization KiloClaw instances receive their one-time 30-day free billing-launch trial. ## Overview -KiloClaw Billing manages the subscription lifecycle for KiloClaw hosted -instances. Every KiloClaw subscription is funded by credits: a -subscription is a recurring credit deduction tied to a specific -instance. Users access the service through one of two hosting plans: a -discounted six-month commit plan or a month-to-month standard plan. - -The recommended checkout path is Kilo Pass, which adds credits to the -user's balance via a Stripe subscription. Those credits fund both -hosting and inference. Users who only want hosting (using free inference -models) can subscribe to a standalone hosting plan via Stripe; the -system routes each Stripe payment through the credit ledger as a -balanced deposit-and-deduction, so all hosting transactions appear in -the credit system regardless of funding source. - -Stripe-funded subscriptions are lazily converted to a hybrid state on -their first settled invoice: the system records the payment source as -`credits` while preserving the payment provider subscription ID, -allowing Stripe to continue collecting payment while the local billing -engine tracks the period via credits. The commit plan auto-renews for -successive six-month periods at the same price; users may switch -between plans at any time. - -Each subscription is scoped to a specific instance. A user MAY have -multiple instances, each with its own subscription and renewal cycle. -All subscriptions deduct from the same user credit balance. - -New users who provision an instance without subscribing first -automatically receive a 7-day free trial. Legacy -`kiloclaw_earlybird_purchases` rows without canonical subscription rows -MUST NOT mint fresh trial access and instead require manual -remediation. Canonical earlybird subscription rows continue to grant -access until their recorded expiry. -A periodic background job enforces expiry, credit renewal, suspension, -and eventual instance destruction when access lapses, with email -notifications at each stage. +KiloClaw Billing manages the subscription lifecycle for KiloClaw hosted instances. Every KiloClaw subscription is funded by credits: a recurring credit deduction tied to a specific instance. Personal users access the service through one of two hosting plans: a discounted six-month commit plan or a month-to-month standard plan. + +The recommended checkout path is Kilo Pass, which adds credits to the user's balance via a Stripe subscription. Those credits fund hosting and inference. Users who only want hosting (using free inference models) can subscribe to a standalone Stripe hosting plan; the system routes each Stripe payment through the credit ledger as a balanced deposit-and-deduction, so all hosting transactions appear in the credit system regardless of funding source. + +Stripe-funded subscriptions lazily convert to hybrid state on their first settled invoice: the system records payment source as `credits` while preserving the provider subscription ID, allowing Stripe to collect payment while the local billing engine tracks the period via credits. The commit plan auto-renews for successive six-month periods at the same price; users may switch between plans at any time. + +Each subscription is scoped to a specific instance. A user MAY have multiple instances, each with its own subscription and renewal cycle. Personal subscriptions deduct from the same user credit balance. Organization subscriptions deduct from the owning organization's credit balance, are associated with a user, and remain subordinate to the organization's parent entitlement. + +New users who provision an instance without subscribing first automatically receive a 7-day free trial. Legacy `kiloclaw_earlybird_purchases` rows without canonical subscription rows MUST NOT mint fresh trial access and instead require manual remediation. Canonical earlybird subscription rows continue to grant access until their recorded expiry. +A periodic background job enforces expiry, credit renewal, suspension, and eventual instance destruction when access lapses, with email notifications at each stage. ## Rules ### Plans -1. The system MUST support exactly two user-facing subscription plans: - commit and standard. A trial plan exists internally but is created - automatically at provisioning time, not selected by the user. -2. A trial plan MUST last 7 calendar days from the moment it is created. +1. For personal subscriptions, system MUST support exactly two user-facing subscription plans: commit and standard. A trial plan exists internally but is created automatically at provisioning time, not user-selected. +2. A personal bootstrap trial plan MUST last 7 calendar days from creation. Organization KiloClaw trial duration is governed by Organization Trials. 3. A commit plan MUST cover a six-calendar-month billing period. -4. A standard plan MUST bill on a monthly recurring cycle. -5. The system MUST enforce at most one subscription record per - instance. Each subscription MUST reference the instance it funds. - A user MAY have multiple instances, each with its own subscription. -6. The base user-visible price for each plan MUST be identical - regardless of payment source. Payment-provider-native promotions, - coupons, or other checkout-side adjustments are excluded from this - parity rule and are governed by the payment-source-specific rules - below. -7. Stripe-funded billing MUST use configured payment-provider price - identifiers. Credit-funded billing MUST use internal microdollar - amounts that correspond to the same plan prices. -8. The system MUST fail with an error if required billing - configuration for the selected plan is missing. For Stripe-funded - billing this includes the payment-provider price identifier. -9. Each plan MUST support two payment sources: payment-provider - (Stripe) and credits. Base plan pricing, built-in first-period - pricing defined by this spec, access rules, failure handling, and - suspension/destruction timelines MUST be identical regardless of - payment source. Payment-provider-native promotions, coupons, and - checkout-side adjustments MAY differ by payment source. The payment - mechanism and the internal implementation of plan switching and - cancellation differ by payment source (see Plan Switching and - Cancellation and Reactivation). +4. A standard plan MUST bill monthly. +5. System MUST enforce at most one subscription record per instance. Each subscription MUST reference the instance it funds. A user MAY have multiple instances, each with its own subscription. +6. The base user-visible price for each plan MUST be identical regardless of payment source. Payment-provider-native promotions, coupons, or other checkout-side adjustments are excluded from this parity rule and governed by the payment-source-specific rules below. +7. Stripe-funded billing MUST use configured payment-provider price identifiers. Credit-funded billing MUST use internal microdollar amounts corresponding to the same plan prices. +8. System MUST error if required billing configuration for the selected plan is missing. For Stripe-funded billing, this includes the payment-provider price identifier. +9. Each plan MUST support two payment sources: payment-provider (Stripe) and credits. Base plan pricing, built-in first-period pricing defined by this spec, access rules, failure handling, and suspension/destruction timelines MUST be identical regardless of payment source. Payment-provider-native promotions, coupons, and checkout-side adjustments MAY differ by payment source. The payment mechanism and internal plan switching and cancellation implementation differ by payment source (see Plan Switching and Cancellation and Reactivation). ### Payment Sources -The rules in this section govern paid self-service KiloClaw -subscription rows. Trial rows are temporary bootstrap rows and are -exempt from the paid funding invariants in rules 2 and 3. Current -organizational bootstrap rows that grant temporary `managed-active` -access before org billing launches are also outside these funding -invariants; they remain a temporary carveout until org billing -integration ships. +This section governs paid self-service KiloClaw subscription rows. Trial rows are temporary bootstrap rows and exempt from the paid funding invariants in rules 2 and 3. Organization KiloClaw paid rows MUST be pure credit rows funded by organization credits and MUST NOT have a provider subscription ID. -1. The system MUST record a payment source for each subscription. The - value MUST be either `stripe` or `credits`. -2. For paid self-service rows, the system MUST enforce exactly three - valid combinations of payment source and payment provider - subscription ID: +1. System MUST record a payment source for each subscription. Value MUST be either `stripe` or `credits`. +2. For paid self-service rows, system MUST enforce exactly three valid payment source/provider subscription ID combinations: | State | payment_source | provider subscription ID | | ------------- | -------------- | ------------------------ | @@ -161,976 +74,413 @@ integration ships. | Hybrid | `credits` | non-null | | Pure credit | `credits` | null | - A subscription with payment source `stripe` MUST have a non-null - payment provider subscription ID. A subscription with payment source - `credits` MAY have a non-null payment provider subscription ID - (hybrid) or a null one (pure credit). No other combination is - valid. - -3. A paid self-service subscription with payment source `credits` - MUST record a credit renewal timestamp indicating when the next - credit deduction is due. -4. At most one subscription record per instance is allowed regardless - of payment source (see Plans rule 5). -5. User-initiated switching between payment sources is not supported - for in-place mutation. Users MUST NOT be able to manually change a - subscription's payment source while the subscription remains - active; they MUST cancel and re-enroll to change funding method. - System-initiated conversion from legacy Stripe to hybrid (`stripe` - to `credits` with the provider subscription ID preserved) occurs - automatically when a KiloClaw invoice is settled (see - Stripe-Funded Credit Settlement). This is a one-way lazy - migration, not a user action. A separate user-prompted conversion - path exists for users who subscribe to Kilo Pass while holding a - standalone Stripe hosting subscription (see Standalone-to-Credit - Conversion). + A subscription with payment source `stripe` MUST have a non-null provider subscription ID. A subscription with payment source `credits` MAY have a non-null provider subscription ID (hybrid) or a null one (pure credit). No other combination is valid. + +3. A paid self-service subscription with payment source `credits` MUST record a credit renewal timestamp indicating when the next credit deduction is due. +4. At most one subscription record per instance is allowed regardless of payment source (see Plans rule 5). +5. User-initiated switching between payment sources is unsupported for in-place mutation. Users MUST NOT manually change a subscription's payment source while it remains active; they MUST cancel and re-enroll to change funding method. System-initiated conversion from legacy Stripe to hybrid (`stripe` to `credits` with provider subscription ID preserved) occurs automatically when a KiloClaw invoice is settled (see Stripe-Funded Credit Settlement). This is a one-way lazy migration, not a user action. A separate user-prompted conversion path exists for users who subscribe to Kilo Pass while holding a standalone Stripe hosting subscription (see Standalone-to-Credit Conversion). ### Hybrid Subscription Ownership -When a subscription is in the hybrid state, multiple events may -attempt to mutate the same subscription. The following ownership -rules resolve conflicts. - -1. Invoice settlement MUST be the sole authority for hybrid-row - successful payment: advancing the billing period, mutating the - plan, updating the credit renewal timestamp, updating the - commitment end date, and recovering the subscription to active - status. No other event or background process MAY perform these - operations on a hybrid row. -2. Subscription status-change events from the payment provider for - hybrid rows MUST be limited to propagating cancel intent and - dunning states. They MUST NOT overwrite the payment source, - plan, billing period, credit renewal timestamp, or commitment end - date. They MUST NOT recover hybrid rows to active status, clear - suspension state, or trigger auto-resume. -3. Subscription creation events from the payment provider MUST NOT - revert an already-hybrid row's converted state. The hybrid row's - payment source, plan, billing period, credit renewal timestamp, - and commitment end date MUST be preserved. Payment provider - metadata (subscription ID, cancel intent) MUST still be updated. -4. Schedule lifecycle events (completion, release) for hybrid rows - MUST clear schedule tracking state but MUST NOT mutate the plan - or commitment end date. Plan mutation is owned by invoice - settlement (rule 1). Schedule events and settled invoices may - arrive in either order; the system MUST tolerate both orderings. -5. The credit renewal sweep MUST NOT select hybrid rows (see Credit - Renewal rule 1). Hybrid-row renewal is owned entirely by invoice - settlement. -6. The interrupted auto-resume retry in the billing lifecycle - background job MUST include hybrid rows. A hybrid row can need - retry if auto-resume was interrupted after invoice settlement - recovered it to active (see Billing Lifecycle Background Job - rule 5). -7. For non-hybrid rows (legacy Stripe or pure credit), all existing - event-handling and sweep behaviors MUST remain unchanged. The - ownership rules in this section apply ONLY to hybrid rows. - -### Trial Eligibility and Creation - -1. A trial MUST only be created automatically when a user provisions an - instance for the first time. There is no user-facing "start trial" - action; the trial is bootstrapped during provisioning. -2. The system MUST create a trial only if the user has no existing - subscription record. The instance-record check is not needed at - provisioning time because provisioning itself creates the instance, - but the billing status endpoint includes the instance check as - defense in depth. -3. When a trial is created, the system MUST record the trial start - timestamp and an end timestamp exactly 7 days later. -4. The system MUST NOT require a credit card to start a trial. -5. When a user provisions a new instance and the user's existing - subscription references a destroyed instance, the system MUST - create a successor subscription row on the newly provisioned - instance, provided the current personal subscription row still - grants access (active, non-suspended past-due, or trialing with a - future end date). The predecessor row on the destroyed instance - MUST remain as historical record and MUST be marked non-live via - `transferred_to_subscription_id`. The successor row MUST inherit - the remaining entitlement and any live payment-provider ownership. - This preserves the user's remaining subscription time when they - destroy and re-create an instance while keeping one subscription - row per instance. +When a subscription is hybrid, multiple events may attempt to mutate it. These ownership rules resolve conflicts. + +1. Invoice settlement MUST be the sole authority for hybrid-row successful payment: advancing the billing period, mutating the plan, updating the credit renewal timestamp, updating the commitment end date, and recovering the subscription to active status. No other event or background process MAY perform these operations on a hybrid row. +2. Subscription status-change events from the payment provider for hybrid rows MUST be limited to propagating cancel intent and dunning states. They MUST NOT overwrite the payment source, plan, billing period, credit renewal timestamp, or commitment end date. They MUST NOT recover hybrid rows to active status, clear suspension state, or trigger auto-resume. +3. Subscription creation events from the payment provider MUST NOT revert an already-hybrid row's converted state. The hybrid row's payment source, plan, billing period, credit renewal timestamp, and commitment end date MUST be preserved. Payment provider metadata (subscription ID, cancel intent) MUST still be updated. +4. Schedule lifecycle events (completion, release) for hybrid rows MUST clear schedule tracking state but MUST NOT mutate the plan or commitment end date. Plan mutation is owned by invoice settlement (rule 1). Schedule events and settled invoices may arrive in either order; system MUST tolerate both. +5. Credit renewal sweep MUST NOT select hybrid rows (see Credit Renewal rule 1). Hybrid-row renewal is owned entirely by invoice settlement. +6. The interrupted auto-resume retry in the billing lifecycle background job MUST include hybrid rows. A hybrid row can need retry if auto-resume was interrupted after invoice settlement recovered it to active (see Billing Lifecycle Background Job rule 5). +7. For non-hybrid rows (legacy Stripe or pure credit), all existing event-handling and sweep behaviors MUST remain unchanged. These ownership rules apply ONLY to hybrid rows. + +### Personal Trial Eligibility and Creation + +1. A personal trial MUST only be created automatically when a user provisions a personal instance for the first time. There is no user-facing "start trial" action; the trial is bootstrapped during provisioning. +2. System MUST create a personal trial only if the user has no existing personal subscription record. The instance-record check is unnecessary at provisioning because provisioning itself creates the instance, but the billing status endpoint includes the instance check as defense in depth. +3. When a trial is created, system MUST record the trial start timestamp and an end timestamp exactly 7 days later. +4. System MUST NOT require a credit card to start a trial. +5. When a user provisions a new instance and the user's existing subscription references a destroyed instance, system MUST create a successor subscription row on the newly provisioned instance, provided the current personal subscription row still grants access (active, non-suspended past-due, or trialing with a future end date). The predecessor row on the destroyed instance MUST remain historical and be marked non-live via `transferred_to_subscription_id`. The successor row MUST inherit the remaining entitlement and any live payment-provider ownership. This preserves the user's remaining subscription time when they destroy and re-create an instance while keeping one subscription row per instance. ### Personal Reprovision Transfer -1. In personal context, the current subscription row is the personal - subscription row whose `transferred_to_subscription_id` is null. -2. Live personal runtime MUST have at most one current subscription - row per user personal context. If more than one exists, runtime - MUST fail closed and quarantine/manual-review the user rather than - choose heuristically. -3. Transferred-out predecessor rows MUST NOT participate in live - access checks, checkout duplicate guards, credit enrollment, - Stripe webhook mutation, invoice settlement, renewal, dunning, - lifecycle sweeps, or email warnings. -4. Webhook and settlement routing MUST first resolve by Stripe - subscription ID. If resolved row has - `transferred_to_subscription_id`, runtime MUST follow predecessor - to successor until current row is reached. If Stripe ownership or - lineage resolution is ambiguous, missing, cyclic, or crosses the - personal/organization boundary, runtime MUST quarantine rather than - mutate a row. -5. Personal paid flows MUST always carry an instance billing anchor. - The system MUST NOT create new detached personal subscription rows. +1. In personal context, the current subscription row is the personal subscription row whose `transferred_to_subscription_id` is null. +2. Live personal runtime MUST have at most one current subscription row per user personal context. If more than one exists, runtime MUST fail closed and quarantine/manual-review the user rather than choose heuristically. +3. Transferred-out predecessor rows MUST NOT participate in live access checks, checkout duplicate guards, credit enrollment, Stripe webhook mutation, invoice settlement, renewal, dunning, lifecycle sweeps, or email warnings. +4. Webhook and settlement routing MUST first resolve by Stripe subscription ID. If resolved row has `transferred_to_subscription_id`, runtime MUST follow predecessor to successor until current row is reached. If Stripe ownership or lineage resolution is ambiguous, missing, cyclic, or crosses the personal/organization boundary, runtime MUST quarantine rather than mutate a row. +5. Personal paid flows MUST always carry an instance billing anchor. System MUST NOT create new detached personal subscription rows. + +### Organization KiloClaw Billing + +#### Organization Funding and Plans + +1. Organization KiloClaw subscriptions MUST be pure credit subscriptions funded by organization credits. +2. Organization KiloClaw subscriptions MUST NOT use direct Stripe hosting subscriptions. +3. Organization KiloClaw subscriptions MUST NOT be represented as Stripe add-on line items on the organization seat subscription. +4. Each organization KiloClaw instance MUST have its own canonical KiloClaw subscription row. +5. Organization KiloClaw subscriptions MUST be scoped to an organization-owned instance and associated with the user for whom the instance was created. +6. Organization KiloClaw purchases MUST be month-to-month. +7. Organization KiloClaw pricing MUST match the individual standard month-to-month KiloClaw price until changed by a later pricing rule. +8. Organization KiloClaw MUST NOT expose a user-visible commit plan or plan-switching flow. +9. Internal plan fields MAY remain future-compatible, but org KiloClaw UI/API behavior MUST expose only the month-to-month organization plan. +10. Creating an organization KiloClaw subscription, including a trialing subscription, MUST require sufficient organization credits for the first paid billing period, except for existing-instance launch backfill. +11. If organization credits are insufficient at creation, system MUST NOT create or activate the subscription. +12. When creation fails for insufficient credits, organization owners and billing managers MUST be prompted to top up organization credits, and non-billing-admin users MUST be prompted to contact a billing admin. +13. Organization KiloClaw renewals MUST use the existing pure-credit renewal lifecycle, with deductions from organization credits instead of user credits. +14. If organization auto top-up is enabled and organization credits are insufficient at renewal, the renewal flow SHOULD trigger organization auto top-up using the existing one-attempt-per-period semantics. +15. If auto top-up is unavailable, disabled, declined, or already attempted for the renewal period, the subscription MUST enter the existing past-due grace, suspension, and destruction lifecycle. +16. Renewal failure prompts and notifications MUST be role-aware: billing admins and owners receive top-up actions, while non-billing-admin associated users receive contact-admin messaging. + +#### Organization Parent Entitlement + +1. Organization KiloClaw access MUST be subordinate to parent organization entitlement. +2. KiloClaw subscription state MUST NOT keep an org KiloClaw accessible when the parent organization subscription or trial state blocks organization access, including hard-expired organization trial states. +3. A parent organization entitlement that blocks access but can be recovered without creating a new organization subscription, such as a hard-expired organization trial, MUST block organization KiloClaw access but MUST NOT by itself immediately cancel organization KiloClaw subscriptions. +4. If the parent organization entitlement ends because the organization subscription is canceled, ended, or otherwise no longer recoverable as the same entitlement, all organization KiloClaw subscriptions for that organization MUST be immediately canceled and MUST NOT renew. +5. Parent entitlement checks MUST remain enforced independently of the local KiloClaw subscription status. +6. Organization KiloClaw state MUST NOT affect, extend, or recover the parent organization subscription state. + +#### Organization Trials + +1. A user receives at most one 7-day organization KiloClaw trial per organization. +2. Trial eligibility MUST be based on historical organization KiloClaw records for that user/org pair; destroyed or canceled prior instances still count. +3. Destroying and recreating an org KiloClaw instance MUST NOT reset 7-day trial eligibility. +4. If an organization KiloClaw is created while the organization is trialing, the KiloClaw trial end MUST be the later of the organization trial end and the associated user's 7-day org KiloClaw trial end. +5. Organization-trial time MAY consume the user's 7-day KiloClaw trial. +6. If the organization becomes active after the user's 7-day KiloClaw trial has elapsed, KiloClaw billing MAY begin immediately, subject to credit sufficiency and parent entitlement. +7. Existing organization KiloClaw instances at billing launch MUST be backfilled as trialing subscriptions ending 30 days after the organization KiloClaw billing launch date. +8. Existing organization KiloClaw launch-trial backfill MUST NOT deduct organization credits at creation. +9. The 30-day billing-launch trial is migration-granted access and SHOULD NOT create reusable trial eligibility for future destroy/recreate cycles. + +#### Enterprise KiloClaw Opt-Out + +1. Enterprise organizations MUST have an admin setting that disables organization KiloClaw access. +2. The opt-out setting MUST be configurable and enforced only while the organization is on Enterprise. +3. When enforced, the opt-out setting MUST block new organization KiloClaw provisioning, block access to existing organization KiloClaw instances, and prevent future organization KiloClaw renewals. +4. When an Enterprise org with opt-out enabled transitions to Teams, the setting MAY persist in storage but MUST NOT be enforced while the organization is Teams. +5. If the organization later transitions back to Enterprise, the persisted opt-out setting MUST become enforceable again unless changed by an authorized admin. + +#### Organization Availability + +1. Organization KiloClaw MUST be available to both Teams and Enterprise organizations. +2. Teams organizations MUST NOT be blocked from organization KiloClaw solely because the Enterprise-only opt-out setting exists. +3. Access remains subject to parent organization entitlement, credits, permissions, and any enforced Enterprise opt-out setting. + +#### Organization Permissions and Visibility + +1. Any organization member MAY create their own organization KiloClaw instance when organization KiloClaw is enabled, parent organization entitlement allows access, the per-user-per-org instance limit allows provisioning, and trial or credit rules allow subscription creation. +2. The associated user MAY manage their own organization KiloClaw lifecycle. +3. Organization owners and billing managers MAY manage any organization KiloClaw instance in the organization. +4. Members who are neither the associated user nor an owner or billing manager MUST NOT manage another user's organization KiloClaw instance. +5. Non-billing-admin associated users MUST NOT see subscription details, including price, organization credit balance, invoices, billing period dates, renewal dates, or subscription identifiers. +6. Non-billing-admin associated users MAY see operational access state, including available, trialing, blocked, past-due, and needs billing admin action. +7. Owners and billing managers MAY see full organization KiloClaw billing details and organization credit actions. + +#### Organization Instance Destruction and Cancellation + +1. User-initiated destruction of an organization KiloClaw instance MUST tear down infrastructure immediately. +2. The instance record MUST be marked destroyed. +3. The subscription MUST be set to cancel at the end of the current billing period. +4. System MUST NOT issue prorated credits or refunds for user-initiated destruction. +5. At the period boundary, the subscription MUST transition to canceled and MUST NOT renew. +6. Parent organization entitlement loss remains separate: when the organization subscription is canceled, ended, or otherwise no longer recoverable as the same entitlement, organization KiloClaw subscriptions are canceled immediately rather than at KiloClaw period end. + +#### Organization UI Limit + +1. The product MUST continue to limit organization KiloClaw provisioning to one active instance per user per organization in the UI/router layer. This MUST NOT be enforced as a database constraint. ### Access Control -1. The system MUST grant access when the subscription status is active. -2. The system MUST grant access when the subscription status is past-due - and the subscription has not been suspended. -3. The system MUST grant access when the subscription status is trialing - and the trial end date is in the future. -4. The system MUST grant access when a canonical earlybird subscription - row remains in an access-granting state. -5. When earlybird access expires, the system MUST NOT automatically - transition the user to a trial or any other plan; the user MUST - manually subscribe to regain access. -6. The system MUST deny access and return a forbidden error when none of - the above conditions are met. -7. All instance lifecycle operations (start, stop, destroy, provision, - configuration changes) MUST be gated behind the access check, except - for provisioning which uses the trial-bootstrap flow. +1. System MUST grant access when the subscription status is active. +2. System MUST grant access when the subscription status is past-due and the subscription has not been suspended. +3. System MUST grant access when the subscription status is trialing and the trial end date is in the future. +4. System MUST grant access when a canonical earlybird subscription row remains in an access-granting state. +5. When earlybird access expires, system MUST NOT automatically transition the user to a trial or any other plan; the user MUST manually subscribe to regain access. +6. System MUST deny access and return a forbidden error when none of the above conditions are met. +7. All instance lifecycle operations (start, stop, destroy, provision, configuration changes) MUST be gated behind the access check, except for provisioning which uses the applicable subscription-bootstrap flow. ### Subscription Checkout (Stripe) -1. The system MUST reject a checkout request if the user already has a - subscription in active, past-due, or unpaid status. -2. The system MUST allow checkout when the existing subscription status - is trialing or canceled. -3. The system MUST verify with the payment provider that no subscription - in active or trialing (delayed-billing) status already exists for the - customer before creating a new checkout session, to guard against - concurrent checkouts. This check does not cover provider-side - subscriptions in past-due status. -4. The system MUST allow payment-provider promotional codes for either - plan. These promotions are payment-provider-native checkout - adjustments and do not require an equivalent user-entered mechanism - in the credit-enrollment flow. -5. The system MUST apply the configured first-month discount when - creating a standard plan checkout session without consuming the - promotional-code input. The implementation MAY use a dedicated - intro price or another provider-supported mechanism that keeps - user-entered promotional codes available. -6. When a configurable billing start date is set and is in the future, - the system MUST create the subscription with a delayed billing period - that begins on that date. -7. When the billing start date is unset or is in the past, the system - MUST start billing immediately with no delayed period. -8. The system SHOULD include referral tracking data in checkout sessions - when a referral cookie is present. -9. The system SHOULD attempt to expire open checkout sessions tagged as - KiloClaw before creating a new checkout session, so users who - abandoned a previous checkout can start fresh. Expiration is - best-effort: errors from the payment provider (e.g. the session was - already expired or completed) MUST be swallowed. Duplicate open - sessions from concurrent requests are tolerable because each requires - independent user action to complete, and rule 3 prevents duplicate - subscriptions. -10. After a Stripe checkout completes, the subscription MUST NOT be - reported as fully activated until invoice settlement has completed - (see Stripe-Funded Credit Settlement). Subscription creation from - the payment provider is an intermediate state; the system MUST - treat a subscription as fully activated only after settlement has - converted it to the hybrid state. +1. System MUST reject a checkout request if the user already has a subscription in active, past-due, or unpaid status. +2. System MUST allow checkout when the existing subscription status is trialing or canceled. +3. System MUST verify with the payment provider that no subscription in active or trialing (delayed-billing) status already exists for the customer before creating a new checkout session, to guard against concurrent checkouts. This check does not cover provider-side subscriptions in past-due status. +4. System MUST allow payment-provider promotional codes for either plan. These promotions are payment-provider-native checkout adjustments and do not require an equivalent user-entered mechanism in the credit-enrollment flow. +5. System MUST apply the configured first-month discount when creating a standard plan checkout session without consuming the promotional-code input. The implementation MAY use a dedicated intro price or another provider-supported mechanism that keeps user-entered promotional codes available. +6. When a configurable billing start date is set and is in the future, system MUST create the subscription with a delayed billing period that begins on that date. +7. When the billing start date is unset or is in the past, system MUST start billing immediately with no delayed period. +8. System SHOULD include referral tracking data in checkout sessions when a referral cookie is present. +9. System SHOULD attempt to expire open checkout sessions tagged as KiloClaw before creating a new checkout session, so users who abandoned a previous checkout can start fresh. Expiration is best-effort: errors from the payment provider (e.g. the session was already expired or completed) MUST be swallowed. Duplicate open sessions from concurrent requests are tolerable because each requires independent user action to complete, and rule 3 prevents duplicate subscriptions. +10. After a Stripe checkout completes, the subscription MUST NOT be reported as fully activated until invoice settlement has completed (see Stripe-Funded Credit Settlement). Subscription creation from the payment provider is an intermediate state; system MUST treat a subscription as fully activated only after settlement has converted it to the hybrid state. ### Credit Enrollment -1. The system MUST reject a credit enrollment request if the user - already has a subscription in active, past-due, or unpaid status. - This is the same guard as Subscription Checkout rule 1. -2. The system MUST allow credit enrollment when the existing - subscription status is trialing or canceled. -3. The system MUST apply a first-month discounted price when enrolling - in the standard plan via credits, identical to the built-in Stripe - first-month discount defined in Subscription Checkout rule 5. This - rule does not attempt to mirror user-entered payment-provider promo - codes. The discounted cost is 4,000,000 microdollars. A user - qualifies for the discount when no prior paid subscription exists; a canceled trial - subscription (plan = 'trial') MUST NOT count as a prior paid - subscription. When the user has a canceled non-trial subscription, - the system MUST charge the regular standard price of 9,000,000 - microdollars. The commit plan has no first-month discount. -4. The system MUST verify that the user's effective credit balance is - sufficient to cover the first billing period before proceeding: - the applicable standard plan cost per rule 3 (4,000,000 or - 9,000,000 microdollars) or 48,000,000 microdollars for the commit - plan (six months paid upfront). The effective balance MUST be - computed as the current - credit balance plus the projected bonus credits the user would earn - from the deduction. The projected bonus MUST be obtained by querying - the Kilo Pass entitlement system for the bonus that would result - from the deduction amount, without committing any credit award. - When the user has no Kilo Pass, the effective balance equals the - current credit balance. When the enrollment is triggered by a Kilo - Pass upsell checkout flow (see Kilo Pass Upsell Checkout), the - system MUST account for the credits that will be added by the - concurrent Kilo Pass purchase when evaluating sufficiency. -5. The system MUST check whether the user was previously suspended - (has a non-null suspension timestamp) before mutating the - subscription row. -6. The credit deduction and subscription upsert MUST be performed in - a single database transaction so that a crash cannot - leave the user with deducted credits and no active subscription. - Within this transaction the system MUST: - a. Insert a negative credit transaction for the first period's cost. - The insertion MUST use a period-encoded idempotency key (see - Credit Renewal rule 2) with conflict-safe semantics. The key - MUST distinguish the instance, plan, and billing period, for - example `kiloclaw-subscription:{instance_id}:YYYY-MM` for - standard or `kiloclaw-subscription-commit:{instance_id}:YYYY-MM` - for commit. If the insertion detects a duplicate, the system MUST - abort the enrollment as a duplicate attempt. - b. Atomically record the deduction as credit spend (see - Definitions) by incrementing the user's used counter by the - deducted amount. This ensures the deduction counts toward the - Kilo Pass bonus unlock threshold. - c. Create or upsert the subscription record with payment source set - to `credits`, status set to active, the billing period set from - the current time, the credit renewal timestamp set to the period - end, the payment provider subscription ID set to null, and the - instance reference set to the target instance. - d. The subscription upsert MUST clear the past-due-since timestamp - and set the status to active, but MUST NOT clear the suspension - timestamp or destruction deadline at this step. If the user was - previously suspended, those columns are needed as a signal for - the auto-resume procedure in rule 8. - If the transaction is interrupted, the database MUST roll back all - operations so that a retry can re-attempt without the idempotency - key blocking it. -7. After the enrollment transaction commits (rule 6), the system MUST - trigger a bonus credit evaluation. This step determines whether the - user's cumulative credit spend (see Definitions) — including the - hosting deduction just committed — now qualifies for additional - bonus credits under their Kilo Pass entitlement and, if so, awards - them. The user's credit balance MAY be temporarily negative between - the deduction in rule 6b and the bonus award; other - balance-observing systems (monitoring, display, renewal sweeps) - MUST tolerate transient negative balances from this flow. When the - user has no Kilo Pass, this step is a no-op. If the bonus - evaluation fails or times out, the system MUST log the failure but - MUST NOT roll back the enrollment. The missed bonus SHOULD be - recovered by a subsequent reconciliation process; this spec does - not define that process. -8. If the user was previously suspended (per rule 5), the system MUST - call the auto-resume procedure after the transaction commits to - restart the instance, clear suspension-cycle email log entries, and - clear the suspension timestamp and destruction deadline. This MUST - happen after the subscription row is in active state. If the - process crashes before auto-resume completes, the non-null - suspension timestamp on an active subscription signals that - resume is still required; the next background job run MUST - detect this state and retry the auto-resume. -9. For the commit plan, the system MUST record a commit-period end - date six calendar months from enrollment, consistent with Commit - Plan Lifecycle rule 2. +1. System MUST reject a credit enrollment request if the user already has a subscription in active, past-due, or unpaid status. This is the same guard as Subscription Checkout rule 1. +2. System MUST allow credit enrollment when the existing subscription status is trialing or canceled. +3. System MUST apply a first-month discounted price when enrolling in the standard plan via credits, identical to the built-in Stripe first-month discount defined in Subscription Checkout rule 5. This rule does not attempt to mirror user-entered payment-provider promo codes. The discounted cost is 4,000,000 microdollars. A user qualifies for the discount when no prior paid subscription exists; a canceled trial subscription (plan = 'trial') MUST NOT count as a prior paid subscription. When the user has a canceled non-trial subscription, system MUST charge the regular standard price of 9,000,000 microdollars. The commit plan has no first-month discount. +4. System MUST verify that the user's effective credit balance is sufficient to cover the first billing period before proceeding: the applicable standard plan cost per rule 3 (4,000,000 or 9,000,000 microdollars) or 48,000,000 microdollars for the commit plan (six months paid upfront). The effective balance MUST be computed as the current credit balance plus the projected bonus credits the user would earn from the deduction. The projected bonus MUST be obtained by querying the Kilo Pass entitlement system for the bonus that would result from the deduction amount, without committing any credit award. When the user has no Kilo Pass, the effective balance equals the current credit balance. When enrollment is triggered by a Kilo Pass upsell checkout flow (see Kilo Pass Upsell Checkout), system MUST account for credits added by the concurrent Kilo Pass purchase when evaluating sufficiency. +5. System MUST check whether the user was previously suspended (has a non-null suspension timestamp) before mutating the subscription row. +6. The credit deduction and subscription upsert MUST be performed in a single database transaction so a crash cannot leave the user with deducted credits and no active subscription. Within this transaction system MUST: + a. Insert a negative credit transaction for the first period's cost. The insertion MUST use a period-encoded idempotency key (see Credit Renewal rule 2) with conflict-safe semantics. The key MUST distinguish the instance, plan, and billing period, for example `kiloclaw-subscription:{instance_id}:YYYY-MM` for standard or `kiloclaw-subscription-commit:{instance_id}:YYYY-MM` for commit. If insertion detects a duplicate, system MUST abort enrollment as a duplicate attempt. + b. Atomically record the deduction as credit spend (see Definitions) by incrementing the user's used counter by the deducted amount. This ensures the deduction counts toward the Kilo Pass bonus unlock threshold. + c. Create or upsert the subscription record with payment source set to `credits`, status set to active, the billing period set from the current time, the credit renewal timestamp set to the period end, the provider subscription ID set to null, and the instance reference set to the target instance. + d. The subscription upsert MUST clear the past-due-since timestamp and set the status to active, but MUST NOT clear the suspension timestamp or destruction deadline at this step. If the user was previously suspended, those columns are needed as a signal for the auto-resume procedure in rule 8. + If the transaction is interrupted, the database MUST roll back all operations so a retry can re-attempt without the idempotency key blocking it. +7. After the enrollment transaction commits (rule 6), system MUST trigger a bonus credit evaluation. This step determines whether the user's cumulative credit spend (see Definitions) — including the hosting deduction just committed — now qualifies for additional bonus credits under their Kilo Pass entitlement and, if so, awards them. The user's credit balance MAY be temporarily negative between the deduction in rule 6b and the bonus award; other balance-observing systems (monitoring, display, renewal sweeps) MUST tolerate transient negative balances from this flow. When the user has no Kilo Pass, this step is a no-op. If the bonus evaluation fails or times out, system MUST log the failure but MUST NOT roll back the enrollment. The missed bonus SHOULD be recovered by a subsequent reconciliation process; this spec does not define that process. +8. If the user was previously suspended (per rule 5), system MUST call the auto-resume procedure after the transaction commits to restart the instance, clear suspension-cycle email log entries, and clear the suspension timestamp and destruction deadline. This MUST happen after the subscription row is in active state. If the process crashes before auto-resume completes, the non-null suspension timestamp on an active subscription signals that resume is still required; the next background job run MUST detect this state and retry the auto-resume. +9. For the commit plan, system MUST record a commit-period end date six calendar months from enrollment, consistent with Commit Plan Lifecycle rule 2. ### Kilo Pass Upsell Checkout -Kilo Pass is the RECOMMENDED checkout path for KiloClaw hosting. The -system SHOULD present Kilo Pass tiers as the primary option when a -user activates hosting, with standalone hosting plans as a secondary -alternative. - -1. When a user selects a Kilo Pass tier from the KiloClaw checkout - flow, the system MUST redirect to the Kilo Pass checkout with a - callback parameter indicating that KiloClaw auto-activation is - pending. The callback MUST include the selected hosting plan - (standard or commit) and the target instance identifier. -2. After the Kilo Pass checkout completes and the payment provider's - invoice has been settled (credits have been added to the user's - balance), the system MUST automatically enroll the target instance - in the selected hosting plan via the credit enrollment path (see - Credit Enrollment). The user MUST NOT be required to take a - separate activation action. -3. The auto-enrollment MUST wait for the Kilo Pass invoice settlement - to complete before attempting the credit deduction. The system - MUST poll or wait until the user's credit balance reflects the - Kilo Pass payment before calling credit enrollment, to handle - the race between the browser redirect and the payment provider - webhook. -4. The commit plan MUST be offered to users selecting a Kilo Pass - tier only when the tier provides sufficient credits to cover the - first commit period (48,000,000 microdollars). This includes - monthly tiers of 49 dollars or above and all annual tiers. The - standard plan MUST be available with all Kilo Pass tiers. -5. All credit enrollment rules (balance check, idempotency, - transaction atomicity, bonus evaluation, auto-resume) apply - to Kilo Pass upsell enrollments. The upsell checkout is a - convenience flow that ends in the same credit enrollment path. +Kilo Pass is the RECOMMENDED checkout path for KiloClaw hosting. System SHOULD present Kilo Pass tiers as the primary option when a user activates hosting, with standalone hosting plans as a secondary alternative. + +1. When a user selects a Kilo Pass tier from the KiloClaw checkout flow, system MUST redirect to the Kilo Pass checkout with a callback parameter indicating that KiloClaw auto-activation is pending. The callback MUST include the selected hosting plan (standard or commit) and the target instance identifier. +2. After the Kilo Pass checkout completes and the payment provider's invoice has been settled (credits have been added to the user's balance), system MUST automatically enroll the target instance in the selected hosting plan via the credit enrollment path (see Credit Enrollment). The user MUST NOT be required to take a separate activation action. +3. The auto-enrollment MUST wait for the Kilo Pass invoice settlement to complete before attempting the credit deduction. System MUST poll or wait until the user's credit balance reflects the Kilo Pass payment before calling credit enrollment, to handle the race between the browser redirect and the payment provider webhook. +4. The commit plan MUST be offered to users selecting a Kilo Pass tier only when the tier provides sufficient credits to cover the first commit period (48,000,000 microdollars). This includes monthly tiers of 49 dollars or above and all annual tiers. The standard plan MUST be available with all Kilo Pass tiers. +5. All credit enrollment rules (balance check, idempotency, transaction atomicity, bonus evaluation, auto-resume) apply to Kilo Pass upsell enrollments. The upsell checkout is a convenience flow that ends in the same credit enrollment path. ### Standalone-to-Credit Conversion -When a user with a Stripe-funded hosting subscription subscribes to -Kilo Pass, the system SHOULD prompt the user to transition hosting to -credit-funded billing. This section applies to legacy Stripe and -hybrid subscriptions. Hybrid subscriptions already route payments -through the credit ledger but still incur a separate Stripe charge; -conversion eliminates that charge by transitioning to pure credit. - -1. The system MUST detect when a user has both a Kilo Pass - subscription and a Stripe-funded KiloClaw hosting subscription - (non-null payment provider subscription ID). -2. When this condition is detected, the system SHOULD present a - prompt offering to switch hosting to credit-funded billing. The - conversion MUST NOT be automatic; it MUST require user - confirmation. -3. If the user accepts, the system MUST set cancel-at-period-end on - the Stripe-funded hosting subscription (both in the payment - provider and locally). The current billing period continues as - already paid by Stripe. -4. When the Stripe subscription reaches its canceled state at period - end, the system MUST clear the payment provider subscription ID - from the local subscription row, converting it to a pure credit - subscription. If the row was hybrid, the payment source remains - `credits`; if it was legacy Stripe, the payment source MUST be - set to `credits`. The credit renewal timestamp MUST be set to - the existing current-period-end so that the credit renewal sweep - picks up the next renewal. This transition MUST happen - atomically when the payment provider reports the subscription as - canceled. -5. After the transition in rule 4, the credit renewal sweep handles - subsequent renewals as a pure credit subscription, deducting - from the user's Kilo Pass-funded credit balance. -6. If the user declines or ignores the prompt, the Stripe-funded - hosting subscription MUST continue unchanged. The system MAY - re-present the prompt at a later time. +When a user with a Stripe-funded hosting subscription subscribes to Kilo Pass, system SHOULD prompt the user to transition hosting to credit-funded billing. This section applies to legacy Stripe and hybrid subscriptions. Hybrid subscriptions already route payments through the credit ledger but still incur a separate Stripe charge; conversion eliminates that charge by transitioning to pure credit. + +1. System MUST detect when a user has both a Kilo Pass subscription and a Stripe-funded KiloClaw hosting subscription (non-null provider subscription ID). +2. When this condition is detected, system SHOULD present a prompt offering to switch hosting to credit-funded billing. The conversion MUST NOT be automatic; it MUST require user confirmation. +3. If the user accepts, system MUST set cancel-at-period-end on the Stripe-funded hosting subscription (both in the payment provider and locally). The current billing period continues as already paid by Stripe. +4. When the Stripe subscription reaches its canceled state at period end, system MUST clear the provider subscription ID from the local subscription row, converting it to a pure credit subscription. If the row was hybrid, the payment source remains `credits`; if it was legacy Stripe, the payment source MUST be set to `credits`. The credit renewal timestamp MUST be set to the existing current-period-end so the credit renewal sweep picks up the next renewal. This transition MUST happen atomically when the payment provider reports the subscription as canceled. +5. After the transition in rule 4, the credit renewal sweep handles subsequent renewals as a pure credit subscription, deducting from the user's Kilo Pass-funded credit balance. +6. If the user declines or ignores the prompt, the Stripe-funded hosting subscription MUST continue unchanged. System MAY re-present the prompt later. ### Stripe-Funded Credit Settlement -When the payment provider reports a paid invoice for a KiloClaw -subscription, the system converts the payment into credit-accounted -settlement. This is the mechanism by which legacy Stripe rows become -hybrid rows (see Payment Sources rule 2) and by which existing hybrid -rows renew. - -1. The system MUST identify KiloClaw invoices by matching a line - item's price against the configured KiloClaw price identifiers. - Invoices with no matching line item MUST NOT be processed by - this flow. If required invoice data (subscription identifier, - matching line item, or period boundaries) is absent, the system - MUST log a warning and skip the invoice. A charge identifier is - optional because the payment provider can emit fully paid `$0` - invoices without a charge object. -2. The settled plan and billing period boundaries MUST be derived - from the invoice, not from local subscription state or - wall-clock time. The invoice is authoritative because local - schedule tracking may have been cleared before the invoice - arrives (see Hybrid Subscription Ownership rule 4). -3. Settlement MUST be balance-neutral: the system MUST record a - positive credit entry and a matching negative credit deduction - in a single atomic operation. The user's visible credit balance - MUST NOT change as a result. -4. The deduction amount MUST equal the settled invoice amount. The - system MUST NOT substitute locally defined plan cost constants. - Payment-provider-side adjustments (first-month discounts, - promotional codes, coupons, prorations) flow through as-is. -5. Settlement MUST be idempotent. Processing the same invoice twice - MUST NOT produce duplicate credits or duplicate deductions. When a - charge identifier is present, the system SHOULD use it as the - external payment identifier for settlement. When the invoice has no - charge identifier, the system MUST fall back to the invoice - identifier so `$0` KiloClaw invoices still settle exactly once. -6. On successful settlement the system MUST: - a. Set payment source to `credits`, preserving the payment - provider subscription ID (converting a legacy Stripe row to - hybrid, or no-op for an already-hybrid row). +When the payment provider reports a paid invoice for a KiloClaw subscription, the system converts the payment into credit-accounted settlement. This is how legacy Stripe rows become hybrid rows (see Payment Sources rule 2) and existing hybrid rows renew. + +1. System MUST identify KiloClaw invoices by matching a line item's price against the configured KiloClaw price identifiers. Invoices with no matching line item MUST NOT be processed by this flow. If required invoice data (subscription identifier, matching line item, or period boundaries) is absent, system MUST log a warning and skip the invoice. A charge identifier is optional because the payment provider can emit fully paid `$0` invoices without a charge object. +2. The settled plan and billing period boundaries MUST be derived from the invoice, not from local subscription state or wall-clock time. The invoice is authoritative because local schedule tracking may have been cleared before the invoice arrives (see Hybrid Subscription Ownership rule 4). +3. Settlement MUST be balance-neutral: system MUST record a positive credit entry and a matching negative credit deduction in a single atomic operation. The user's visible credit balance MUST NOT change as a result. +4. The deduction amount MUST equal the settled invoice amount. System MUST NOT substitute locally defined plan cost constants. Payment-provider-side adjustments (first-month discounts, promotional codes, coupons, prorations) flow through as-is. +5. Settlement MUST be idempotent. Processing the same invoice twice MUST NOT produce duplicate credits or duplicate deductions. When a charge identifier is present, system SHOULD use it as the external payment identifier for settlement. When the invoice has no charge identifier, system MUST fall back to the invoice identifier so `$0` KiloClaw invoices still settle exactly once. +6. On successful settlement system MUST: + a. Set payment source to `credits`, preserving the provider subscription ID (converting a legacy Stripe row to hybrid, or no-op for an already-hybrid row). b. Set subscription status to active. - c. Advance the billing period and credit renewal timestamp to - the invoice-derived boundaries. - d. For commit plans, update the commitment end date to the - invoice's period end. For standard plans, clear it. - e. Clear past-due state and any auto-top-up marker for the - prior period. -7. If a scheduled plan change matches the settled invoice's plan, - the system MUST clear the schedule tracking state atomically - with settlement. If the invoice plan differs from the current - plan and there is no matching scheduled change, the system MUST - treat the settled invoice as authoritative and log a warning. -8. If the subscription was past-due or suspended before settlement, - the system MUST trigger the auto-resume procedure after the - settlement transaction commits (see Auto-Resume on Payment - Recovery). -9. After the settlement transaction commits, the system MUST - trigger a bonus credit evaluation as described in Credit - Enrollment rule 6. -10. `$0` KiloClaw invoices MUST still run the settlement path so - Stripe-created subscriptions can transition out of the - intermediate Stripe-funded state into the hybrid activated state. - Revenue side effects that require a paid amount, such as revenue - analytics or affiliate sale events, MUST apply their own - `amount_paid > 0` guard and MUST NOT block settlement. + c. Advance the billing period and credit renewal timestamp to the invoice-derived boundaries. + d. For commit plans, update the commitment end date to the invoice's period end. For standard plans, clear it. + e. Clear past-due state and any auto-top-up marker for the prior period. +7. If a scheduled plan change matches the settled invoice's plan, system MUST clear the schedule tracking state atomically with settlement. If the invoice plan differs from the current plan and there is no matching scheduled change, system MUST treat the settled invoice as authoritative and log a warning. +8. If the subscription was past-due or suspended before settlement, system MUST trigger the auto-resume procedure after the settlement transaction commits (see Auto-Resume on Payment Recovery). +9. After the settlement transaction commits, system MUST trigger a bonus credit evaluation as described in Credit Enrollment rule 6. +10. `$0` KiloClaw invoices MUST still run the settlement path so Stripe-created subscriptions can transition out of the intermediate Stripe-funded state into the hybrid activated state. Revenue side effects that require a paid amount, such as revenue analytics or affiliate sale events, MUST apply their own `amount_paid > 0` guard and MUST NOT block settlement. ### Commit Plan Lifecycle -1. A commit subscription MUST remain on the commit price in the payment - provider; the system MUST NOT create a schedule to auto-transition - the subscription to the standard plan. -2. When a commit subscription is created, the system MUST record a - commit-period end date six calendar months from the billing start. - When a delayed-billing period is configured, the six months MUST - start from the delayed-billing end date, not from subscription - creation. -3. For legacy Stripe rows, when a subscription update is received and - the commit-period end date is in the past, the system MUST extend - it by six calendar months from the previous boundary, keeping the - subscription on the commit plan. For hybrid rows, commit-period - extension is handled by invoice settlement (see Stripe-Funded - Credit Settlement rule 6d); subscription status-change events - MUST NOT extend the commit-period end date (see Hybrid - Subscription Ownership rule 2). -4. When a user-initiated plan-switch schedule completes or is - released/canceled, the system MUST apply or clear the schedule - tracking fields as appropriate (see Plan Switching). +1. A commit subscription MUST remain on the commit price in the payment provider; system MUST NOT create a schedule to auto-transition the subscription to the standard plan. +2. When a commit subscription is created, system MUST record a commit-period end date six calendar months from the billing start. When a delayed-billing period is configured, the six months MUST start from the delayed-billing end date, not from subscription creation. +3. For legacy Stripe rows, when a subscription update is received and the commit-period end date is in the past, system MUST extend it by six calendar months from the previous boundary, keeping the subscription on the commit plan. For hybrid rows, commit-period extension is handled by invoice settlement (see Stripe-Funded Credit Settlement rule 6d); subscription status-change events MUST NOT extend the commit-period end date (see Hybrid Subscription Ownership rule 2). +4. When a user-initiated plan-switch schedule completes or is released/canceled, system MUST apply or clear the schedule tracking fields as appropriate (see Plan Switching). ### Plan Switching -1. The system MUST allow switching between commit and standard plans only - for active subscriptions. -2. The system MUST reject a switch if the user is already on the - requested plan. -3. For Stripe-funded subscriptions, a switch from standard to commit - MUST create a payment-provider schedule with two phases: current - plan until period end, then commit (open-ended). -4. For Stripe-funded subscriptions, a switch from commit to standard - MUST create a payment-provider schedule with two phases: current - plan until period end, then standard. -5. For a standard-to-commit switch, the recorded scheduled-plan MUST - be commit. -6. When a plan-switch schedule reaches a terminal status (completed or - released) and the local schedule tracking state still references - the schedule: for legacy Stripe rows the system MUST apply the - scheduled plan and update the commit-period end date accordingly; - for hybrid rows the system MUST clear the schedule tracking state - but MUST NOT mutate the plan or commitment end date (see Hybrid - Subscription Ownership rule 4). Plan mutation for hybrid rows - occurs when the corresponding invoice is settled (see - Stripe-Funded Credit Settlement rule 7). Intentional releases - (cancellation or cancel-plan-switch) clear the local schedule - reference before the event fires, so the schedule event MUST NOT - match those rows. -7. When a standard-to-commit switch takes effect, the system MUST set - the commit-period end date to six calendar months from the - transition date. -8. The system MUST allow cancellation of user-initiated plan switches. -9. For pure credit subscriptions, a plan switch MUST NOT create a - payment-provider schedule. The system MUST record the scheduled - plan locally and apply it at the next period boundary during the - credit renewal sweep. -10. For pure credit subscriptions, canceling a plan switch MUST clear - the locally recorded scheduled plan. No payment-provider API call - is needed. -11. User-initiated cross-payment-source switching (credits to Stripe or - vice versa) is NOT RECOMMENDED. Users who wish to change payment - source MUST cancel their current subscription and re-enroll after - the billing period ends. System-initiated conversion from legacy - Stripe to hybrid via invoice settlement (see Payment Sources - rule 5 and Stripe-Funded Credit Settlement) is not governed by - this rule. +1. System MUST allow switching between commit and standard plans only for active subscriptions. +2. System MUST reject a switch if the user is already on the requested plan. +3. For Stripe-funded subscriptions, a switch from standard to commit MUST create a payment-provider schedule with two phases: current plan until period end, then commit (open-ended). +4. For Stripe-funded subscriptions, a switch from commit to standard MUST create a payment-provider schedule with two phases: current plan until period end, then standard. +5. For a standard-to-commit switch, the recorded scheduled-plan MUST be commit. +6. When a plan-switch schedule reaches a terminal status (completed or released) and the local schedule tracking state still references the schedule: for legacy Stripe rows system MUST apply the scheduled plan and update the commit-period end date accordingly; for hybrid rows system MUST clear the schedule tracking state but MUST NOT mutate the plan or commitment end date (see Hybrid Subscription Ownership rule 4). Plan mutation for hybrid rows occurs when the corresponding invoice is settled (see Stripe-Funded Credit Settlement rule 7). Intentional releases (cancellation or cancel-plan-switch) clear the local schedule reference before the event fires, so the schedule event MUST NOT match those rows. +7. When a standard-to-commit switch takes effect, system MUST set the commit-period end date to six calendar months from the transition date. +8. System MUST allow cancellation of user-initiated plan switches. +9. For pure credit subscriptions, a plan switch MUST NOT create a payment-provider schedule. System MUST record the scheduled plan locally and apply it at the next period boundary during the credit renewal sweep. +10. For pure credit subscriptions, canceling a plan switch MUST clear the locally recorded scheduled plan. No payment-provider API call is needed. +11. User-initiated cross-payment-source switching (credits to Stripe or vice versa) is NOT RECOMMENDED. Users who wish to change payment source MUST cancel their current subscription and re-enroll after the billing period ends. System-initiated conversion from legacy Stripe to hybrid via invoice settlement (see Payment Sources rule 5 and Stripe-Funded Credit Settlement) is not governed by this rule. ### Cancellation and Reactivation -1. The system MUST reject a cancellation request if no active - subscription exists. For Stripe-funded subscriptions, the provider - subscription ID MUST be present. For pure credit subscriptions, - the payment source MUST be `credits` and status MUST be active. -2. The system MUST reject a cancellation request if cancellation is - already pending. -3. When canceling a Stripe-funded subscription that has a pending - schedule, the system MUST release the schedule before setting the - cancel-at-period-end flag. -4. Cancellation MUST NOT terminate access immediately; access MUST - continue until the current billing period ends. -5. For Stripe-funded subscriptions, the system MUST set the - cancel-at-period-end flag on both the payment provider and in the - local database. -6. For pure credit subscriptions, the system MUST set the - cancel-at-period-end flag in the local database only. No payment - provider API call is needed. The credit renewal sweep handles the - period-end transition (see Credit Renewal rule 5). -7. The system MUST allow reactivation of a subscription that is pending - cancellation. -8. On reactivation of a Stripe-funded subscription, the system MUST - clear the cancel-at-period-end flag on both the payment provider - and in the local database. -9. On reactivation of a pure credit subscription, the system MUST - clear the cancel-at-period-end flag in the local database only. +1. System MUST reject a cancellation request if no active subscription exists. For Stripe-funded subscriptions, the provider subscription ID MUST be present. For pure credit subscriptions, the payment source MUST be `credits` and status MUST be active. +2. System MUST reject a cancellation request if cancellation is already pending. +3. When canceling a Stripe-funded subscription that has a pending schedule, system MUST release the schedule before setting the cancel-at-period-end flag. +4. Cancellation MUST NOT terminate access immediately; access MUST continue until the current billing period ends. +5. For Stripe-funded subscriptions, system MUST set the cancel-at-period-end flag on both the payment provider and in the local database. +6. For pure credit subscriptions, system MUST set the cancel-at-period-end flag in the local database only. No payment provider API call is needed. The credit renewal sweep handles the period-end transition (see Credit Renewal rule 5). +7. System MUST allow reactivation of a subscription that is pending cancellation. +8. On reactivation of a Stripe-funded subscription, system MUST clear the cancel-at-period-end flag on both the payment provider and in the local database. +9. On reactivation of a pure credit subscription, system MUST clear the cancel-at-period-end flag in the local database only. ### Billing Lifecycle Background Job -1. The background job MUST be protected by an authorization secret; - requests without valid authorization MUST receive an unauthorized - response. -2. Each sweep in the background job MUST process users independently; - a failure for one user MUST NOT prevent processing of other users. +1. The background job MUST be protected by an authorization secret; requests without valid authorization MUST receive an unauthorized response. +2. Each sweep in the background job MUST process users independently; a failure for one user MUST NOT prevent processing of other users. 3. All errors during sweep processing MUST be captured for monitoring. -4. The credit renewal sweep MUST run before all other sweeps so that - pure credit subscriptions are renewed (or marked past-due, or - canceled) before the existing sweeps evaluate expiry and suspension. - Hybrid rows are excluded from the credit renewal sweep (see Credit - Renewal rule 1); their renewal is handled by invoice settlement. -5. The background job MUST detect subscriptions with payment source - `credits` (both hybrid and pure credit) in active status that - still have a non-null suspension timestamp (indicating a prior - auto-resume was interrupted) and retry the auto-resume procedure - for those subscriptions. This MUST include hybrid rows; a hybrid - row can need retry if auto-resume was interrupted after invoice - settlement recovered it to active. -6. The system MAY run additional background jobs that are not part of - the hourly lifecycle sweep order when those jobs have different - cadence or operational isolation requirements. Such jobs MUST still - follow rules 1–3. +4. Credit renewal sweep MUST run before all other sweeps so pure credit subscriptions are renewed (or marked past-due, or canceled) before existing sweeps evaluate expiry and suspension. Hybrid rows are excluded from the credit renewal sweep (see Credit Renewal rule 1); their renewal is handled by invoice settlement. +5. The background job MUST detect subscriptions with payment source `credits` (both hybrid and pure credit) in active status that still have a non-null suspension timestamp (indicating a prior auto-resume was interrupted) and retry the auto-resume procedure for those subscriptions. This MUST include hybrid rows; a hybrid row can need retry if auto-resume was interrupted after invoice settlement recovered it to active. +6. System MAY run additional background jobs that are not part of the hourly lifecycle sweep order when those jobs have different cadence or operational isolation requirements. Such jobs MUST still follow rules 1–3. ### Trial Inactivity Stop -1. The system MUST evaluate personal trial inactivity at most once per - day, not as part of the hourly lifecycle sweep order. -2. The inactivity job MUST consider only the current personal - subscription row whose plan is `trial`, whose status is `trialing`, - and whose associated instance is active, personal, and older than 48 - hours. -3. The activity check MUST use qualifying KiloClaw usage from the last - 2 days using the product-approved Snowflake semantics. If the - activity source is unavailable or ambiguous for a user, the system - MUST fail open for that user. -4. When a qualifying personal trial row has no qualifying usage in the - last 2 days, the system MUST stop the instance. -5. The system MUST NOT change the subscription status, trial dates, - suspension timestamp, destruction deadline, or other billing - entitlement fields when applying a trial inactivity stop. -6. The operational inactivity marker MAY be cleared when the instance - is explicitly restarted or when the current personal subscription - row is no longer both plan `trial` and status `trialing`. -7. The system MUST NOT send an email for a trial inactivity stop. -8. Restart after a trial inactivity stop MUST require an explicit user - or admin start action; trialing access remains governed by the - normal access rules. -9. The operational inactivity marker is only meaningful while the - current personal subscription row remains a live personal trial. - When that row leaves the `plan = trial` / `status = trialing` - state — including trial expiry or paid activation — the marker - MUST be cleared. +1. System MUST evaluate personal trial inactivity at most once per day, not as part of the hourly lifecycle sweep order. +2. The inactivity job MUST consider only the current personal subscription row whose plan is `trial`, whose status is `trialing`, and whose associated instance is active, personal, and older than 48 hours. +3. The activity check MUST use qualifying KiloClaw usage from the last 2 days using the product-approved Snowflake semantics. If the activity source is unavailable or ambiguous for a user, system MUST fail open for that user. +4. When a qualifying personal trial row has no qualifying usage in the last 2 days, system MUST stop the instance. +5. System MUST NOT change the subscription status, trial dates, suspension timestamp, destruction deadline, or other billing entitlement fields when applying a trial inactivity stop. +6. The operational inactivity marker MAY be cleared when the instance is explicitly restarted or when the current personal subscription row is no longer both plan `trial` and status `trialing`. +7. System MUST NOT send an email for a trial inactivity stop. +8. Restart after a trial inactivity stop MUST require an explicit user or admin start action; trialing access remains governed by the normal access rules. +9. The operational inactivity marker is only meaningful while the current personal subscription row remains a live personal trial. When that row leaves the `plan = trial` / `status = trialing` state — including trial expiry or paid activation — the marker MUST be cleared. ### Credit Renewal -1. The credit renewal sweep MUST select only pure credit subscriptions - where status is active or past-due and the credit renewal timestamp - is at or before the current time. Hybrid subscriptions MUST NOT be - selected; their renewal is owned by invoice settlement (see - Stripe-Funded Credit Settlement). The payment provider's dunning - process handles payment failure for hybrid subscriptions; - status-change events propagate past-due state to the local row. -2. Each credit deduction MUST use a period-encoded category key - with a uniqueness constraint. The key MUST be derived from the - subscription's credit renewal timestamp (the period boundary being - charged for), not from the current wall-clock time. The format - MUST distinguish the instance, renewal cadence, and plan, for - example `kiloclaw-subscription:{instance_id}:2026-04` for a - standard renewal or - `kiloclaw-subscription-commit:{instance_id}:2026-04` for a - commit renewal. - The insertion MUST use conflict-safe semantics so that a duplicate - key is silently ignored rather than causing an error. - The sweep MUST advance the subscription by exactly one billing - period per successful deduction. If the subscription has fallen - behind by multiple periods (e.g., the sweep was delayed), the - sweep MUST NOT attempt to catch up multiple periods in a single - run. Instead, each successive sweep run advances by one period - until the credit renewal timestamp is in the future. This ensures - each period produces a distinct idempotency key. -3. The credit deduction insert and subscription period advancement - MUST be performed in a single database transaction. If the - transaction is interrupted, the database MUST roll back both - operations so that a retry can re-attempt the deduction without - the idempotency key blocking it. -4. If the deduction insert returns zero affected rows (duplicate key - from a prior committed transaction), the subscription update - within the same transaction is a no-op (same values). The system - MUST skip further processing for that row. -5. If the subscription has cancel-at-period-end set, the sweep MUST - skip the deduction, set the subscription status to canceled, and - clear the cancel-at-period-end flag. The billing period MUST NOT - be advanced; current-period-end retains its existing value. - Subscription Period Expiry Enforcement rule 1 handles suspension - once current-period-end has passed. -6. When the effective balance (as defined in Credit Enrollment rule 4) - is sufficient and the deduction succeeds (one affected row), the - system MUST atomically record the deduction as credit spend (see - Definitions) and advance the subscription's billing period - (current-period-start, current-period-end, credit-renewal-timestamp) - within the same transaction. After the transaction commits, the - system MUST trigger a bonus credit evaluation as described in Credit - Enrollment rule 6. The user's credit balance MAY be temporarily - negative between the deduction and the bonus award. If the bonus evaluation - fails or times out, the system MUST log the failure and continue - processing the row; the missed bonus SHOULD be recovered by a - subsequent reconciliation process. -7. When a commit-plan renewal succeeds and the commit-period end date - has been reached, the system MUST extend the commit-period end date - by six calendar months from the previous boundary. -8. When the deduction succeeds and the subscription was previously - past-due, the system MUST clear the past-due-since timestamp and - set the status to active. -9. When the deduction succeeds, the subscription was past-due, and - the suspension timestamp is null (grace-period recovery), the - system MUST delete the credit-renewal-failed email log entry for - the user so that future failures can re-trigger the notification. -10. When the deduction succeeds, the subscription was past-due, and - the suspension timestamp is non-null (suspended recovery), the - system MUST call the auto-resume procedure to restart the instance, - clear the suspension-cycle email log entries (including the - credit-renewal-failed entry), and clear the suspension columns. -11. When the effective balance (as defined in Credit Enrollment - rule 4) is insufficient, the system MUST first check whether - the user has auto top-up enabled and whether a top-up has - already been triggered for the current renewal period. If auto - top-up is available and has NOT yet been triggered for this - period, the system MUST persist the durable marker (the credit - renewal timestamp of the period being charged) on the - subscription row BEFORE triggering the auto top-up call. This - ensures that if the process crashes after the payment-provider - invoice is created but before the marker write would otherwise - have committed, the marker already exists and prevents a - duplicate top-up on the next sweep. The auto top-up call MUST - include a deterministic idempotency key derived from the user ID - and the credit renewal timestamp of the period being charged, so - that the payment provider de-duplicates repeated requests for the - same renewal period. After the marker is persisted and the - top-up triggered, the system MUST skip the row without changing - any other state (fire-and-skip). The next sweep run MUST - re-evaluate the row after the top-up webhook has credited the - balance. The marker MUST be cleared when the billing period - advances (successful deduction) or when the subscription is - canceled. -12. When the effective balance is still insufficient (per rule 11) - and auto top-up is not available, has been disabled due to a - prior card decline, or was already triggered for the current - period (marker present), the system MUST set the subscription - status to past-due and record a past-due-since timestamp - (preserving any existing value). Past-Due Payment Enforcement - rule 1 handles suspension after 14 days. -13. When the effective balance is insufficient and the system enters the past-due - path (rule 12), the system MUST send a credit-renewal-failed - notification, subject to the standard email idempotency rules. - The notification MUST NOT be sent when the system takes the - fire-and-skip path (rule 11). -14. The credit renewal sweep MUST handle three distinct recovery paths - in a single pass: active renewal (status active, renewal due), - grace-period recovery (status past-due, not suspended), and - suspended recovery (status past-due, suspended). Separate sweeps - are not needed. -15. When a pure credit subscription has a scheduled plan change and - the current period has ended, the renewal sweep MUST determine - the effective plan and cost before the deduction, but MUST apply - the plan mutation inside the same database transaction as the - credit deduction and period advancement (rule 3). This ensures - that a crash between the plan switch and the charge cannot leave - the subscription on the new plan without a corresponding - deduction. Applying the plan change MUST: +1. Credit renewal sweep MUST select only pure credit subscriptions where status is active or past-due and the credit renewal timestamp is at or before the current time. Hybrid subscriptions MUST NOT be selected; their renewal is owned by invoice settlement (see Stripe-Funded Credit Settlement). The payment provider's dunning process handles payment failure for hybrid subscriptions; status-change events propagate past-due state to the local row. +2. Each credit deduction MUST use a period-encoded category key with a uniqueness constraint. The key MUST be derived from the subscription's credit renewal timestamp (the period boundary being charged for), not from the current wall-clock time. The format MUST distinguish the instance, renewal cadence, and plan, for example `kiloclaw-subscription:{instance_id}:2026-04` for a standard renewal or `kiloclaw-subscription-commit:{instance_id}:2026-04` for a commit renewal. + The insertion MUST use conflict-safe semantics so a duplicate key is silently ignored rather than causing an error. + The sweep MUST advance the subscription by exactly one billing period per successful deduction. If the subscription has fallen behind by multiple periods (e.g., the sweep was delayed), the sweep MUST NOT attempt to catch up multiple periods in a single run. Instead, each successive sweep run advances by one period until the credit renewal timestamp is in the future. This ensures each period produces a distinct idempotency key. +3. The credit deduction insert and subscription period advancement MUST be performed in a single database transaction. If the transaction is interrupted, the database MUST roll back both operations so a retry can re-attempt the deduction without the idempotency key blocking it. +4. If the deduction insert returns zero affected rows (duplicate key from a prior committed transaction), the subscription update within the same transaction is a no-op (same values). System MUST skip further processing for that row. +5. If the subscription has cancel-at-period-end set, the sweep MUST skip the deduction, set the subscription status to canceled, and clear the cancel-at-period-end flag. The billing period MUST NOT be advanced; current-period-end retains its existing value. Subscription Period Expiry Enforcement rule 1 handles suspension once current-period-end has passed. +6. When the effective balance (as defined in Credit Enrollment rule 4) is sufficient and the deduction succeeds (one affected row), system MUST atomically record the deduction as credit spend (see Definitions) and advance the subscription's billing period (current-period-start, current-period-end, credit-renewal-timestamp) within the same transaction. After the transaction commits, system MUST trigger a bonus credit evaluation as described in Credit Enrollment rule 6. The user's credit balance MAY be temporarily negative between the deduction and the bonus award. If the bonus evaluation fails or times out, system MUST log the failure and continue processing the row; the missed bonus SHOULD be recovered by a subsequent reconciliation process. +7. When a commit-plan renewal succeeds and the commit-period end date has been reached, system MUST extend the commit-period end date by six calendar months from the previous boundary. +8. When the deduction succeeds and the subscription was previously past-due, system MUST clear the past-due-since timestamp and set the status to active. +9. When the deduction succeeds, the subscription was past-due, and the suspension timestamp is null (grace-period recovery), system MUST delete the credit-renewal-failed email log entry for the user so future failures can re-trigger the notification. +10. When the deduction succeeds, the subscription was past-due, and the suspension timestamp is non-null (suspended recovery), system MUST call the auto-resume procedure to restart the instance, clear the suspension-cycle email log entries (including the credit-renewal-failed entry), and clear the suspension columns. +11. When the effective balance (as defined in Credit Enrollment rule 4) is insufficient, system MUST first check whether the user has auto top-up enabled and whether a top-up has already been triggered for the current renewal period. If auto top-up is available and has NOT yet been triggered for this period, system MUST persist the durable marker (the credit renewal timestamp of the period being charged) on the subscription row BEFORE triggering the auto top-up call. This ensures that if the process crashes after the payment-provider invoice is created but before the marker write would otherwise have committed, the marker already exists and prevents a duplicate top-up on the next sweep. The auto top-up call MUST include a deterministic idempotency key derived from the user ID and the credit renewal timestamp of the period being charged, so the payment provider de-duplicates repeated requests for the same renewal period. After the marker is persisted and the top-up triggered, system MUST skip the row without changing any other state (fire-and-skip). The next sweep run MUST re-evaluate the row after the top-up webhook has credited the balance. The marker MUST be cleared when the billing period advances (successful deduction) or when the subscription is canceled. +12. When the effective balance is still insufficient (per rule 11) and auto top-up is not available, has been disabled due to a prior card decline, or was already triggered for the current period (marker present), system MUST set the subscription status to past-due and record a past-due-since timestamp (preserving any existing value). Past-Due Payment Enforcement rule 1 handles suspension after 14 days. +13. When the effective balance is insufficient and the system enters the past-due path (rule 12), system MUST send a credit-renewal-failed notification, subject to the standard email idempotency rules. The notification MUST NOT be sent when the system takes the fire-and-skip path (rule 11). +14. Credit renewal sweep MUST handle three distinct recovery paths in a single pass: active renewal (status active, renewal due), grace-period recovery (status past-due, not suspended), and suspended recovery (status past-due, suspended). Separate sweeps are not needed. +15. When a pure credit subscription has a scheduled plan change and the current period has ended, the renewal sweep MUST determine the effective plan and cost before the deduction, but MUST apply the plan mutation inside the same database transaction as the credit deduction and period advancement (rule 3). This ensures that a crash between the plan switch and the charge cannot leave the subscription on the new plan without a corresponding deduction. Applying the plan change MUST: - Update the subscription's plan to the scheduled plan value. - Clear the scheduled-plan and scheduled-by fields. - - If switching to commit: set the commit-period end date to six - calendar months from the transition date, consistent with Plan - Switching rule 7. + - If switching to commit: set the commit-period end date to six calendar months from the transition date, consistent with Plan Switching rule 7. - If switching to standard: clear the commit-period end date. - After the plan change is applied, subsequent sweeps MUST NOT - reapply it (the cleared scheduled-plan field prevents this). - This rule does not apply to hybrid rows; hybrid plan switching - is handled by Stripe-Funded Credit Settlement rule 10. + After the plan change is applied, subsequent sweeps MUST NOT reapply it (the cleared scheduled-plan field prevents this). This rule does not apply to hybrid rows; hybrid plan switching is handled by Stripe-Funded Credit Settlement rule 10. ### Auto Top-Up Integration with Credit Renewal -1. The auto top-up flow is asynchronous: triggering auto top-up - creates and pays a payment-provider invoice, but credits are only - applied when the invoice-paid webhook fires. The credit renewal - sweep MUST NOT wait for the top-up to complete. -2. When the sweep triggers auto top-up for a row, the sweep MUST skip - that row entirely without setting past-due status, sending failure - notifications, or advancing the billing period. -3. On the next sweep run, if the auto top-up succeeded and the - effective balance (as defined in Credit Enrollment rule 4) is now - sufficient, the sweep MUST proceed with the normal deduction. If - the effective balance is still insufficient, the sweep MUST enter - the insufficient-balance path (Credit Renewal rule 11). -4. The system MUST enter the insufficient-balance path (not fire-and- - skip) when auto top-up is not enabled, has been disabled due to a - prior card decline, or was already triggered for the current - renewal period (as indicated by the durable marker described in - Credit Renewal rule 11) and the effective balance remains - insufficient. +1. The auto top-up flow is asynchronous: triggering auto top-up creates and pays a payment-provider invoice, but credits are only applied when the invoice-paid webhook fires. Credit renewal sweep MUST NOT wait for the top-up to complete. +2. When the sweep triggers auto top-up for a row, the sweep MUST skip that row entirely without setting past-due status, sending failure notifications, or advancing the billing period. +3. On the next sweep run, if the auto top-up succeeded and the effective balance (as defined in Credit Enrollment rule 4) is now sufficient, the sweep MUST proceed with the normal deduction. If the effective balance is still insufficient, the sweep MUST enter the insufficient-balance path (Credit Renewal rule 11). +4. System MUST enter the insufficient-balance path (not fire-and-skip) when auto top-up is not enabled, has been disabled due to a prior card decline, or was already triggered for the current renewal period (as indicated by the durable marker described in Credit Renewal rule 11) and the effective balance remains insufficient. ### Trial Expiry Warnings -1. When a trial has 2 or fewer days remaining and has not been - suspended, the system MUST send a trial-ending-soon notification. -2. When a trial has 1 or fewer days remaining, the system MUST send a - more urgent trial-expires-tomorrow notification instead of the - 2-day notification. +1. When a trial has 2 or fewer days remaining and has not been suspended, system MUST send a trial-ending-soon notification. +2. When a trial has 1 or fewer days remaining, system MUST send a more urgent trial-expires-tomorrow notification instead of the 2-day notification. ### Earlybird Expiry Warnings -1. When a canonical earlybird subscription row's `trial_ends_at` is 14 - or fewer days away and the user does not have another active or - trialing subscription, the system MUST send a warning notification. -2. When the row's `trial_ends_at` is 1 or fewer days away, the system - MUST send a more urgent expires-tomorrow notification instead of - the 14-day notification. -3. The notification's expiry date and days-remaining MUST be derived - from the row's `trial_ends_at`, not from a globally configured - earlybird expiry constant. +1. When a canonical earlybird subscription row's `trial_ends_at` is 14 or fewer days away and the user does not have another active or trialing subscription, system MUST send a warning notification. +2. When the row's `trial_ends_at` is 1 or fewer days away, system MUST send a more urgent expires-tomorrow notification instead of the 14-day notification. +3. The notification's expiry date and days-remaining MUST be derived from the row's `trial_ends_at`, not from a globally configured earlybird expiry constant. ### Trial Expiry Enforcement -1. When a trial's end date has passed and the subscription is still in - trialing status (not yet suspended), the system MUST stop the - subscription's associated instance. -2. The system MUST transition the subscription to canceled status. -3. The system MUST set a suspension timestamp and a destruction deadline - 7 days in the future. -4. The system MUST send a trial-suspended notification. -5. If the instance stop operation fails (e.g., no instance exists), the - system MUST still proceed with the status transition. +1. When a trial's end date has passed and the subscription is still in trialing status (not yet suspended), system MUST stop the subscription's associated instance. +2. System MUST transition the subscription to canceled status. +3. System MUST set a suspension timestamp and a destruction deadline 7 days in the future. +4. System MUST send a trial-suspended notification. +5. If the instance stop operation fails (e.g., no instance exists), system MUST still proceed with the status transition. ### Subscription Period Expiry Enforcement -1. When a canceled subscription's billing period has ended and the - subscription has not been suspended, the system MUST stop the - subscription's associated instance. -2. The system MUST set a suspension timestamp and a destruction deadline - 7 days in the future. -3. The system MUST send a subscription-suspended notification. +1. When a canceled subscription's billing period has ended and the subscription has not been suspended, system MUST stop the subscription's associated instance. +2. System MUST set a suspension timestamp and a destruction deadline 7 days in the future. +3. System MUST send a subscription-suspended notification. ### Destruction Warning -1. When a suspended subscription's destruction deadline is 2 or fewer - days away, the system MUST send a destruction-warning notification. +1. When a suspended subscription's destruction deadline is 2 or fewer days away, system MUST send a destruction-warning notification. ### Instance Destruction -1. When a suspended subscription's destruction deadline has passed, the - system MUST destroy the subscription's associated instance. -2. The system MUST mark the instance record as destroyed. -3. The system MUST clear the destruction deadline after destruction. -4. The system MUST send an instance-destroyed notification. -5. If the destroy operation fails (e.g., no instance exists), the system - MUST still proceed with the state transition. +1. When a suspended subscription's destruction deadline has passed, system MUST destroy the subscription's associated instance. +2. System MUST mark the instance record as destroyed. +3. System MUST clear the destruction deadline after destruction. +4. System MUST send an instance-destroyed notification. +5. If the destroy operation fails (e.g., no instance exists), system MUST still proceed with the state transition. ### Past-Due Payment Enforcement -1. When a subscription has been in past-due status for more than 14 days - and has not been suspended, the system MUST stop the subscription's - associated instance. This applies equally to Stripe-funded and - credit-funded subscriptions. -2. The system MUST set a suspension timestamp and a destruction deadline - 7 days in the future. -3. The system MUST send a payment-suspended notification. -4. The 14-day threshold MUST be measured from the time the subscription - first entered past-due status, not from the last database update. - For pure credit subscriptions, past-due status is set by the credit - renewal sweep. For legacy Stripe subscriptions, it is set by the - payment provider status-change event. For hybrid subscriptions, it - is set by the payment provider's dunning state propagation (see - Hybrid Subscription Ownership rule 2). +1. When a subscription has been in past-due status for more than 14 days and has not been suspended, system MUST stop the subscription's associated instance. This applies equally to Stripe-funded and credit-funded subscriptions. +2. System MUST set a suspension timestamp and a destruction deadline 7 days in the future. +3. System MUST send a payment-suspended notification. +4. The 14-day threshold MUST be measured from the time the subscription first entered past-due status, not from the last database update. For pure credit subscriptions, past-due status is set by the credit renewal sweep. For legacy Stripe subscriptions, it is set by the provider status-change event. For hybrid subscriptions, it is set by the payment provider's dunning state propagation (see Hybrid Subscription Ownership rule 2). ### Email Notifications -1. Each notification type MUST be sent at most once per user per - lifecycle event. -2. If a notification send fails, the system MUST allow the notification - to be retried on the next background job run. -3. The system MUST prevent concurrent duplicate sends of the same - notification to the same user. -4. The system MUST support a credit-renewal-failed notification type - for credit-funded subscriptions. This notification MUST be sent - when the credit renewal sweep enters the insufficient-balance path - and MUST be subject to the same idempotency rules as other - notification types. +1. Each notification type MUST be sent at most once per user per lifecycle event. +2. If a notification send fails, system MUST allow the notification to be retried on the next background job run. +3. System MUST prevent concurrent duplicate sends of the same notification to the same user. +4. System MUST support a credit-renewal-failed notification type for credit-funded subscriptions. This notification MUST be sent when the credit renewal sweep enters the insufficient-balance path and MUST be subject to the same idempotency rules as other notification types. ### Auto-Resume on Payment Recovery -1. When a subscription transitions to active while the subscription's - instance is suspended, the system MUST attempt to start the - subscription's associated instance. - For legacy Stripe subscriptions, this transition is detected by a - payment provider status-change event. For pure credit - subscriptions, this transition is detected by the credit renewal - sweep when a past-due subscription with a non-null suspension - timestamp is successfully renewed. For hybrid subscriptions, this - transition is detected by the invoice settlement path (see - Stripe-Funded Credit Settlement rule 8); payment provider - status-change events MUST NOT trigger auto-resume for hybrid rows - (see Hybrid Subscription Ownership rule 2). -2. If the instance start attempt fails, the system MUST log the failure - and MUST NOT clear the suspension timestamp or destruction deadline. - Leaving these fields intact allows the background job (Billing - Lifecycle Background Job rule 5) to detect the incomplete - auto-resume and retry on the next sweep. -3. The system MUST clear the suspension timestamp and destruction - deadline only after a successful instance start (or when no instance - exists to restart). -4. The system MUST clear email log entries for suspension, destruction, - and credit-renewal-failed notifications so they can fire again in a - future suspension cycle. -5. The system MUST NOT clear email log entries for trial or earlybird - warning notifications, as those are one-time events. +1. When a subscription transitions to active while the subscription's instance is suspended, system MUST attempt to start the subscription's associated instance. + For legacy Stripe subscriptions, this transition is detected by a provider status-change event. For pure credit subscriptions, this transition is detected by the credit renewal sweep when a past-due subscription with a non-null suspension timestamp is successfully renewed. For hybrid subscriptions, this transition is detected by the invoice settlement path (see Stripe-Funded Credit Settlement rule 8); provider status-change events MUST NOT trigger auto-resume for hybrid rows (see Hybrid Subscription Ownership rule 2). +2. If the instance start attempt fails, system MUST log the failure and MUST NOT clear the suspension timestamp or destruction deadline. Leaving these fields intact allows the background job (Billing Lifecycle Background Job rule 5) to detect the incomplete auto-resume and retry on the next sweep. +3. System MUST clear the suspension timestamp and destruction deadline only after a successful instance start (or when no instance exists to restart). +4. System MUST clear email log entries for suspension, destruction, and credit-renewal-failed notifications so they can fire again in a future suspension cycle. +5. System MUST NOT clear email log entries for trial or earlybird warning notifications, as those are one-time events. ### Payment Provider Status Mapping -1. When the payment provider reports a subscription as "trialing" - (delayed billing), the system MUST map this to active status - internally, since delayed billing is not a product-level trial. -2. When the payment provider reports "incomplete" or "paused" status, - the system MUST map these to terminal statuses (unpaid or canceled - respectively). -3. Pure credit subscriptions have no payment provider status. Their - status MUST be managed entirely by the credit renewal sweep and - the billing lifecycle sweeps. Payment provider status mapping - rules MUST NOT apply to pure credit subscriptions. -4. Hybrid subscriptions receive limited payment provider status - mapping. Only dunning states MUST be propagated from payment - provider status changes. Recovery to active status, plan changes, - period advancement, and clearing of suspension state MUST NOT - be applied from status-change events for hybrid subscriptions; - these are owned by invoice settlement (see Hybrid Subscription - Ownership rules 1-2). +1. When the payment provider reports a subscription as "trialing" (delayed billing), system MUST map this to active status internally, since delayed billing is not a product-level trial. +2. When the payment provider reports "incomplete" or "paused" status, system MUST map these to terminal statuses (unpaid or canceled respectively). +3. Pure credit subscriptions have no provider status. Their status MUST be managed entirely by the credit renewal sweep and the billing lifecycle sweeps. Payment provider status mapping rules MUST NOT apply to pure credit subscriptions. +4. Hybrid subscriptions receive limited provider status mapping. Only dunning states MUST be propagated from provider status changes. Recovery to active status, plan changes, period advancement, and clearing of suspension state MUST NOT be applied from status-change events for hybrid subscriptions; these are owned by invoice settlement (see Hybrid Subscription Ownership rules 1-2). ### Billing Status Reporting -1. The billing status response MUST include whether the user currently - has access and the reason for that access (trial, subscription, or - earlybird). -2. The system MUST report trial eligibility as true only when the user - has no instance records at all (including destroyed instances), no - subscription record. -3. The billing status MUST include trial data (start, end, days - remaining, expired flag) when a trial exists or existed. -4. The billing status MUST include subscription data (plan, status, - cancel-at-period-end, period end, commit end, scheduled plan, - payment source) when a paid subscription exists. When a user has - multiple instances, the billing status MUST include subscription - data for each instance. Subscription data MUST be included when - either a payment provider subscription ID is present or the - payment source is `credits`; it MUST NOT be suppressed solely - because a payment provider subscription ID is absent. -5. When the payment source is `credits`, the billing status MUST also - include the credit renewal timestamp and the renewal cost for the - next billing period so the frontend can display the next renewal - date and amount due. For hybrid subscriptions, the renewal cost is - Stripe-determined; the system MUST report a plan-based - approximation or indicate that renewal is billed via Stripe. -6. The billing status MUST include a Stripe-funding indicator that is - true for Stripe-funded subscriptions and false for pure credit - subscriptions. The frontend MUST use this indicator — not payment - source alone — to determine whether to show Stripe portal access, - payment method management, or credit-specific UI such as the - top-up flow. -7. When the user has a Stripe-funded KiloClaw subscription and also - has a Kilo Pass subscription, the billing status MUST include an - indicator signaling that the standalone-to-credit conversion - prompt should be shown (see Standalone-to-Credit Conversion). -8. The billing status MUST include earlybird data (expiry date, days - remaining) only when a canonical earlybird subscription row exists. -9. The billing status MUST include instance data (whether an - undestroyed instance exists, suspension timestamp, destruction - deadline, and destroyed flag) when any instance record exists. +1. The billing status response MUST include whether the user currently has access and the reason for that access (trial, subscription, or earlybird). +2. System MUST report trial eligibility as true only when the user has no instance records at all (including destroyed instances), no subscription record. +3. Billing status MUST include trial data (start, end, days remaining, expired flag) when a trial exists or existed. +4. Billing status MUST include subscription data (plan, status, cancel-at-period-end, period end, commit end, scheduled plan, payment source) when a paid subscription exists. When a user has multiple instances, billing status MUST include subscription data for each instance. Subscription data MUST be included when either a provider subscription ID is present or the payment source is `credits`; it MUST NOT be suppressed solely because a provider subscription ID is absent. +5. When the payment source is `credits`, billing status MUST also include the credit renewal timestamp and the renewal cost for the next billing period so the frontend can display the next renewal date and amount due. For hybrid subscriptions, the renewal cost is Stripe-determined; system MUST report a plan-based approximation or indicate that renewal is billed via Stripe. +6. Billing status MUST include a Stripe-funding indicator that is true for Stripe-funded subscriptions and false for pure credit subscriptions. Frontend MUST use this indicator — not payment source alone — to determine whether to show Stripe portal access, payment method management, or credit-specific UI such as the top-up flow. +7. When the user has a Stripe-funded KiloClaw subscription and also has a Kilo Pass subscription, billing status MUST include an indicator signaling that the standalone-to-credit conversion prompt should be shown (see Standalone-to-Credit Conversion). +8. Billing status MUST include earlybird data (expiry date, days remaining) only when a canonical earlybird subscription row exists. +9. Billing status MUST include instance data (whether an undestroyed instance exists, suspension timestamp, destruction deadline, and destroyed flag) when any instance record exists. +10. For organization KiloClaw, billing status reporting MUST enforce role-based visibility. Non-billing-admin associated users MAY see operational access state and contact-admin prompts, but MUST NOT receive price, organization credit balance, invoices, billing period dates, renewal dates, or subscription identifiers. Owners and billing managers MAY receive full organization KiloClaw billing details and organization credit actions. ### Billing Portal -1. The system MUST allow users with Stripe-funded subscriptions to - access the payment provider's billing portal to manage their payment - methods. -2. The billing portal session MUST redirect the user back to the - dashboard upon completion. -3. The billing portal MUST NOT be offered for pure credit - subscriptions. The frontend MUST use the Stripe-funding indicator - (see Billing Status Reporting rule 6), not payment source alone, - to determine portal eligibility. Hybrid subscriptions MUST have - portal access for Stripe payment method management. Pure credit - users MUST be directed to the credit top-up flow instead. +1. System MUST allow users with Stripe-funded subscriptions to access the payment provider's billing portal to manage their payment methods. +2. The billing portal session MUST redirect the user back to the dashboard upon completion. +3. The billing portal MUST NOT be offered for pure credit subscriptions. Frontend MUST use the Stripe-funding indicator (see Billing Status Reporting rule 6), not payment source alone, to determine portal eligibility. Hybrid subscriptions MUST have portal access for Stripe payment method management. Pure credit users MUST be directed to the credit top-up flow instead. ### User Data Deletion -1. When a user is soft-deleted, the system MUST retain - `kiloclaw_instance` and `kiloclaw_subscription` rows for that - user. Ownership references and directly identifying user fields - MUST be anonymized rather than deleted. -2. When a user is soft-deleted, the system MUST retain subscription - change-log rows as canonical audit history. Any directly - identifying actor or ownership fields in those rows MUST be - anonymized while preserving the audit trail's meaning. -3. When a user is soft-deleted, the system MUST delete auxiliary - KiloClaw billing records whose purpose is operational rather than - canonical state, such as email notification log entries. -4. Credit transaction records created by subscription deductions are - managed by the credit system's own data deletion rules, not by - KiloClaw billing. This spec does not impose additional deletion - requirements on credit transaction records. +1. When a user is soft-deleted, system MUST retain `kiloclaw_instance` and `kiloclaw_subscription` rows for that user. Ownership references and directly identifying user fields MUST be anonymized rather than deleted. +2. When a user is soft-deleted, system MUST retain subscription change-log rows as canonical audit history. Any directly identifying actor or ownership fields in those rows MUST be anonymized while preserving the audit trail's meaning. +3. When a user is soft-deleted, system MUST delete auxiliary KiloClaw billing records whose purpose is operational rather than canonical state, such as email notification log entries. +4. Credit transaction records created by subscription deductions are managed by the credit system's own data deletion rules, not by KiloClaw billing. This spec does not impose additional deletion requirements on credit transaction records. ### Changelog +#### 2026-05-06 -- Organization KiloClaw billing + +- Added organization KiloClaw subscription definitions and rules. +- Defined org-funded pure-credit billing, parent entitlement gating, Enterprise opt-out, org trial behavior, launch backfill, role-aware visibility, lifecycle permissions, organization auto top-up, and period-end cancellation on user destruction. +- Clarified that personal plan and trial rules apply to personal subscriptions, while org KiloClaw exposes month-to-month billing only. + #### 2026-03-27 -- Credit spend model, subscription reassignment -- Added definitions for credit balance, credit spend, and the - distinction between pure-credit deductions (which increment the used - counter and count toward the Kilo Pass bonus threshold) and - Stripe-funded settlement deductions (which are balance-neutral - bookkeeping and do not count as spend). -- Updated Credit Enrollment rule 5b and Credit Renewal rule 6 to use - "record as credit spend" instead of "decrement acquired credit - balance," aligning the spec with the intent that hosting deductions - count toward the Kilo Pass bonus unlock threshold. -- Clarified Credit Enrollment rule 6: "cumulative credit spend" - explicitly includes the hosting deduction just committed. -- Added Trial Eligibility and Creation rule 5: when a user provisions - a new instance and the existing subscription references a destroyed - instance, the system reassigns the subscription to the new instance. - This fixes a bug where destroying and re-creating an instance left - the subscription orphaned on the old destroyed instance. +- Added definitions for credit balance, credit spend, and the distinction between pure-credit deductions (which increment the used counter and count toward the Kilo Pass bonus threshold) and Stripe-funded settlement deductions (which are balance-neutral bookkeeping and do not count as spend). +- Updated Credit Enrollment rule 5b and Credit Renewal rule 6 to use "record as credit spend" instead of "decrement acquired credit balance," aligning the spec with the intent that hosting deductions count toward the Kilo Pass bonus unlock threshold. +- Clarified Credit Enrollment rule 6: "cumulative credit spend" explicitly includes the hosting deduction just committed. +- Added Personal Trial Eligibility and Creation rule 5: when a user provisions a new instance and the existing subscription references a destroyed instance, the system reassigns the subscription to the new instance. This fixes a bug where destroying and re-creating an instance left the subscription orphaned on the old destroyed instance. #### 2026-03-24 -- Credits-first billing, per-instance subscriptions, Kilo Pass upsell -- Reframed the overview to reflect credits-first billing direction: - every KiloClaw subscription is a credit deduction, regardless of - funding source. Kilo Pass is the recommended checkout path. -- Changed subscription scope from per-user to per-instance. Plans - rule 5 now enforces at most one subscription per instance. A user - may have multiple instances, each with its own subscription. -- Idempotency keys for credit deductions (enrollment and renewal) - now include the instance identifier to support per-instance - subscriptions. -- Added Kilo Pass Upsell Checkout section defining the recommended - checkout flow where users subscribe to Kilo Pass and hosting - auto-activates via credit enrollment. -- Added Standalone-to-Credit Conversion section defining the - user-prompted flow for transitioning Stripe-funded hosting - (legacy Stripe or hybrid) to pure credit when the user subscribes - to Kilo Pass. Includes the state transition that clears the - payment provider subscription ID at period end. -- Credit Enrollment rule 3 now accounts for credits from a concurrent - Kilo Pass purchase when evaluating balance sufficiency. -- Billing Status Reporting now includes per-instance subscription - data and a conversion-prompt indicator for users with both - Stripe-funded hosting and Kilo Pass. -- Lifecycle enforcement sections (trial expiry, subscription expiry, - destruction, past-due, auto-resume) updated to reference the - subscription's associated instance rather than "the user's - instance." +- Reframed the overview to reflect credits-first billing direction: every KiloClaw subscription is a credit deduction, regardless of funding source. Kilo Pass is the recommended checkout path. +- Changed subscription scope from per-user to per-instance. Plans rule 5 now enforces at most one subscription per instance. A user may have multiple instances, each with its own subscription. +- Idempotency keys for credit deductions (enrollment and renewal) now include the instance identifier to support per-instance subscriptions. +- Added Kilo Pass Upsell Checkout section defining the recommended checkout flow where users subscribe to Kilo Pass and hosting auto-activates via credit enrollment. +- Added Standalone-to-Credit Conversion section defining the user-prompted flow for transitioning Stripe-funded hosting (legacy Stripe or hybrid) to pure credit when the user subscribes to Kilo Pass. Includes the state transition that clears the provider subscription ID at period end. +- Credit Enrollment rule 3 now accounts for credits from a concurrent Kilo Pass purchase when evaluating balance sufficiency. +- Billing Status Reporting now includes per-instance subscription data and a conversion-prompt indicator for users with both Stripe-funded hosting and Kilo Pass. +- Lifecycle enforcement sections (trial expiry, subscription expiry, destruction, past-due, auto-resume) updated to reference the subscription's associated instance rather than "the user's instance." #### 2026-03-20 -- Stripe-to-credits hybrid billing model -- Introduced the hybrid subscription state: `payment_source='credits'` - with a non-null payment provider subscription ID. Legacy Stripe rows - lazily convert to hybrid on their next settled invoice. -- Added Stripe-Funded Credit Settlement section defining the invoice - settlement path. -- Added Hybrid Subscription Ownership section defining which events - own which mutations for hybrid rows. -- Changed discriminants throughout the spec from payment source to - payment provider subscription ID presence for: plan switching, - cancellation, reactivation, billing portal, and renewal sweep scope. -- Credit renewal sweep now excludes hybrid rows; hybrid renewal is - owned by invoice settlement. -- Payment provider status mapping now includes a limited hybrid path: - non-active dunning states only. +- Introduced the hybrid subscription state: `payment_source='credits'` with a non-null provider subscription ID. Legacy Stripe rows lazily convert to hybrid on their next settled invoice. +- Added Stripe-Funded Credit Settlement section defining the invoice settlement path. +- Added Hybrid Subscription Ownership section defining which events own which mutations for hybrid rows. +- Changed discriminants throughout the spec from payment source to provider subscription ID presence for: plan switching, cancellation, reactivation, billing portal, and renewal sweep scope. +- Credit renewal sweep now excludes hybrid rows; hybrid renewal is owned by invoice settlement. +- Payment provider status mapping now includes a limited hybrid path: non-active dunning states only. - Billing status now includes a Stripe-funding indicator. - Checkout success activation now requires invoice settlement. @@ -1146,23 +496,14 @@ Previous values: New values: - Trial duration: 7 days (existing trials keep their original end date) -- Standard plan: $9/month with $4 first month while still allowing - promotional codes +- Standard plan: $9/month with $4 first month while still allowing promotional codes - Commit plan: $48/6 months - Trial expiry warning: 2 days before expiry - 14 existing subscribers migrated to new pricing at next billing cycle ## Error Handling -1. When a background job sweep encounters an error for a specific user, - the system MUST log the error and continue processing remaining - users. -2. When an instance stop or destroy operation fails during a lifecycle - sweep, the system MUST log the failure and proceed with the - subscription state transition regardless. -3. When a schedule release fails during cancellation with an error - indicating the schedule is already released or canceled, the system - MUST treat this as success and proceed with clearing local state. -4. When a schedule release fails during cancellation for any other - reason (e.g., transient API error), the system MUST abort the - cancellation and return an error to the user. +1. When a background job sweep encounters an error for a specific user, system MUST log the error and continue processing remaining users. +2. When an instance stop or destroy operation fails during a lifecycle sweep, system MUST log the failure and proceed with the subscription state transition regardless. +3. When a schedule release fails during cancellation with an error indicating the schedule is already released or canceled, system MUST treat this as success and proceed with clearing local state. +4. When a schedule release fails during cancellation for any other reason (e.g., transient API error), system MUST abort the cancellation and return an error to the user. diff --git a/.specs/kiloclaw-datamodel.md b/.specs/kiloclaw-datamodel.md index 90fba4b173..baa58d0aa6 100644 --- a/.specs/kiloclaw-datamodel.md +++ b/.specs/kiloclaw-datamodel.md @@ -2,20 +2,11 @@ ## Role of This Document -This spec defines the business rules and invariants for the KiloClaw -data model — specifically the `kiloclaw_instance` and -`kiloclaw_subscription` tables and the relationships between them. It -is the source of truth for _what_ the system is required to guarantee -about record existence, immutability, lookup patterns, and creation -order. -It deliberately does not prescribe _how_ to implement those -guarantees: column layouts, migration strategies, backfill scripts, -and other implementation choices belong in plan documents and code, -not here. - -Multiple services and apps operate on this data model (the web app, -the kiloclaw CF worker service, the kiloclaw-billing service, and -background jobs). All consumers MUST comply with the rules below. +This spec defines KiloClaw data-model business rules and invariants, specifically the `kiloclaw_instance` and `kiloclaw_subscription` tables and their relationship. It is the source of truth for system guarantees about record existence, immutability, lookup patterns, and creation order. + +It does not prescribe implementation. Column layouts, migration strategies, backfill scripts, and other implementation choices belong in plans and code. + +Multiple services and apps operate on this model: the web app, kiloclaw CF worker service, kiloclaw-billing service, and background jobs. All consumers MUST comply with these rules. ## Status @@ -23,302 +14,119 @@ Draft — created 2026-04-15. ## Conventions -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", -"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and -"OPTIONAL" in this document are to be interpreted as described in -BCP 14 [RFC 2119] [RFC 8174] when, and only when, they appear in all -capitals, as shown here. +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" are BCP 14 [RFC 2119] [RFC 8174] terms only when capitalized as shown here. ## Definitions -- **Instance record**: A row in `kiloclaw_instance` representing a - KiloClaw instance, whether or not the underlying infrastructure - (CF worker Durable Object and infra provider resources) still exists. -- **Subscription record**: A row in `kiloclaw_subscription` - representing a billing subscription tied to a specific instance. -- **Destroyed instance**: An instance record whose underlying - infrastructure has been torn down. The record persists with a - destroyed marker. -- **Early-bird subscriber**: A user who purchased early-bird access - before the subscription billing system was available. These users - have instance records but may lack subscription records until - backfill is complete. -- **Subscription change log entry**: A row in the subscription audit - log that captures a single mutation to a `kiloclaw_subscription` - record — what changed, when, and who or what caused it. -- **Actor**: The entity responsible for a subscription mutation. - An actor is either a user (identified by user ID) or the system - (identified by a service or process name). -- **Context**: The ownership scope of an instance — either - _personal_ (not associated with any organization) or - _organizational_ (associated with a specific organization). A user - has one personal context and one organizational context per - organization they belong to. -- **Active instance**: An instance record that has not been marked - as destroyed. -- **Mutation**: Any database write (INSERT or UPDATE) to a - `kiloclaw_subscription` row that changes one or more of its - business-relevant fields (status, plan, billing period, payment - source, cancellation flags, suspension state, etc.). Automated - timestamp updates (e.g., `updated_at`) that occur without any - other field change are not mutations for change log purposes. -- **Infra Provider**: The backing service provider where we provision compute and storage and onto which we actually deploy OpenClaw. For example fly.io, docker-local, Northflank. -- **Infra Provider Base Resource**: Some infra providers have base top-level organizational resource that must exist. For example fly.io has a app concept, Northflank has projects. +- **Instance record**: A `kiloclaw_instance` row representing a KiloClaw instance, whether or not the underlying infrastructure (CF worker Durable Object and infra provider resources) still exists. +- **Subscription record**: A `kiloclaw_subscription` row representing a billing subscription tied to a specific instance. +- **Destroyed instance**: An instance record whose underlying infrastructure has been torn down. The record persists with a destroyed marker. +- **Early-bird subscriber**: A user who purchased early-bird access before subscription billing existed. These users have instance records but may lack subscription records until backfill completes. +- **Subscription change log entry**: A subscription audit-log row capturing one `kiloclaw_subscription` mutation: what changed, when, and who or what caused it. +- **Actor**: Entity responsible for a subscription mutation: either a user (user ID) or the system (service or process name). +- **Context**: Instance ownership scope: _personal_ (not associated with any organization) or _organizational_ (associated with a specific organization). A user has one personal context and one organizational context per organization they belong to. +- **Associated user**: For an organizational instance, the organization member whose KiloClaw instance is provisioned for their use. The associated user is the user-facing operational owner; the organization is the billing owner. +- **Active instance**: Instance record not marked as destroyed. +- **Mutation**: Any `kiloclaw_subscription` INSERT or UPDATE that changes one or more business-relevant fields (status, plan, billing period, payment source, cancellation flags, suspension state, etc.). Automated timestamp updates (e.g., `updated_at`) without other field changes are not mutations for change log purposes. +- **Infra Provider**: Backing service provider where we provision compute and storage and deploy OpenClaw, e.g. fly.io, docker-local, Northflank. +- **Infra Provider Base Resource**: Some infra providers have a base top-level organizational resource that must exist, e.g. a fly.io app or Northflank project. ## Overview -The KiloClaw data model centers on two core entities: instances and -subscriptions. An instance record tracks the existence and state of a -KiloClaw hosted environment. A subscription record tracks the billing -relationship that funds that instance. Together they form the -foundation that the web app, CF worker services, billing service, and -background jobs all rely on. - -The data model supports multiple instances per user or organization, -though the system currently limits provisioning to one active instance -per user per context (personal, and each organization the user belongs -to) via UI and router constraints. These constraints are enforced at -the application layer, not the data layer, so removing them in the -future requires no schema changes. - -A subscription change log provides a complete audit trail of every -mutation to a subscription record. Because subscriptions are never -deleted and are mutated by multiple services (web app, kiloclaw CF -worker, kiloclaw-billing service, background jobs, and payment -provider webhooks), the change log gives operators and support a -reliable history of what happened, when, and why — without relying -on application logs that may be rotated or incomplete. +The KiloClaw data model centers on instances and subscriptions. An instance record tracks a KiloClaw hosted environment's existence and state. A subscription record tracks the billing relationship funding that instance. Together, they are the foundation for the web app, CF worker services, billing service, and background jobs. + +The model supports multiple instances per user or organization, though the system currently limits provisioning to one active instance per user per context (personal, and each organization the user belongs to) via UI and router constraints. These constraints are application-layer, not data-layer, so removing them later requires no schema changes. + +A subscription change log provides a complete audit trail for every subscription-record mutation. Subscriptions are never deleted and are mutated by multiple services (web app, kiloclaw CF worker, kiloclaw-billing service, background jobs, and payment provider webhooks), so the change log gives operators and support reliable history of what happened, when, and why without relying on rotated or incomplete logs. ## Rules ### Record Immutability -1. An instance record MUST NOT be deleted from `kiloclaw_instance`, - even after the underlying infrastructure (CF worker Durable Object - and infra provider resources) is destroyed. Destroyed instances MUST be marked as - destroyed rather than removed. -2. A subscription record MUST NOT be deleted from - `kiloclaw_subscription`. Subscription lifecycle transitions - (cancellation, expiry, etc.) MUST be represented as status changes - on the existing record, never as row deletion. -3. When a user account is deleted (e.g., GDPR right-to-erasure), - instance and subscription records MUST be retained. Ownership - references MUST be anonymized rather than cascaded or removed. - Subscription change log rows MUST also be retained as canonical - audit history. Any directly identifying fields in those rows MUST - be anonymized under the GDPR exception in Subscription Change Log - rule 14. - Foreign key constraints on these tables MUST NOT cascade deletes - from parent tables. +1. An instance record MUST NOT be deleted from `kiloclaw_instance`, even after the underlying infrastructure (CF worker Durable Object and infra provider resources) is destroyed. Destroyed instances MUST be marked as destroyed, not removed. +2. A subscription record MUST NOT be deleted from `kiloclaw_subscription`. Subscription lifecycle transitions (cancellation, expiry, etc.) MUST be represented as status changes on the existing record, never row deletion. Historical organization KiloClaw instance and subscription records MUST be retained and usable to determine whether a user has already consumed their one 7-day org KiloClaw trial in that organization. +3. When a user account is deleted (e.g., GDPR right-to-erasure), instance and subscription records MUST be retained. Ownership references, including associated-user references on organizational instances, MUST be anonymized, not cascaded or removed. Organization ownership references MAY be retained when they are not directly identifying user data. Subscription change log rows MUST also be retained as canonical audit history. Any directly identifying fields in those rows MUST be anonymized under the GDPR exception in Subscription Change Log rule 14. Foreign key constraints on these tables MUST NOT cascade deletes from parent tables. ### Instance–Subscription Relationship -4. Every instance record MUST have a corresponding subscription - record. This is an eventually-consistent invariant: during the - creation sequence (rules 19–23), a brief window exists between - the instance INSERT and the subscription INSERT where the - instance has no subscription. Outside that bounded creation - window, there MUST NOT exist an instance record without a - subscription record, except an instance explicitly quarantined - for bootstrap remediation after both primary and fallback - subscription-bootstrap paths failed (rule 22). That exception - MUST be rare, MUST cause the provisioning request to fail, and - MUST NOT be treated as a live provisioned instance for user - access or onboarding completion. This invariant is enforced at the - application layer; the creation-order rules define the sequence - that satisfies it. -5. Each subscription record MUST reference exactly one instance. The - relationship is one-to-one: at most one subscription per instance - (see kiloclaw-billing.md, Plans rule 5). -6. Early-bird subscribers who have instance records without - subscription records are a known violation of rule 4. These - MUST be resolved by backfilling canonical subscription records - for those instances. Runtime code MUST NOT continue granting - access from purchase-table fallback once migration cleanup is - complete; users without canonical rows are treated as exceptions - requiring manual remediation. +4. Every instance record MUST have a corresponding subscription record. This invariant is eventually consistent: during the creation sequence (rules 19–23), a brief window exists between the instance INSERT and subscription INSERT where the instance has no subscription. Outside that bounded creation window, an instance record without a subscription record MUST NOT exist, except an instance explicitly quarantined for bootstrap remediation after both primary and fallback subscription-bootstrap paths failed (rule 22). That exception MUST be rare, MUST cause the provisioning request to fail, and MUST NOT be treated as a live provisioned instance for user access or onboarding completion. This invariant is application-layer enforced; the creation-order rules define the satisfying sequence. +5. Each subscription record MUST reference exactly one instance. The relationship is one-to-one: at most one subscription per instance (see kiloclaw-billing.md, Plans rule 5). +6. Early-bird subscribers with instance records but no subscription records are a known rule 4 violation. These MUST be resolved by backfilling canonical subscription records for those instances. Runtime code MUST NOT continue granting access from purchase-table fallback once migration cleanup is complete; users without canonical rows are treated as exceptions requiring manual remediation. ### Multi-Instance Support -7. The data model MUST accommodate multiple instances per user or - organization. No schema-level constraint SHALL restrict a user or - organization to a single instance. -8. The system MUST limit provisioning to one active instance per - user per context. A user MAY have one active instance in their - personal context and one active instance in each organization - they belong to, simultaneously. The limit is per context, not - per user globally. This limit MUST be enforced at the UI and - router layer, not at the database layer. -9. When the single-instance limit is relaxed in the future, no - schema migration SHALL be required. +7. The model MUST accommodate multiple instances per user or organization. No schema-level constraint SHALL restrict a user or organization to a single instance. Organizational instance records MUST identify the owning organization, associated user, and organizational context. The associated user is the user-facing owner for operational workflows; the organization is the billing owner. +8. The system MUST limit provisioning to one active instance per user per context. A user MAY simultaneously have one active instance in their personal context and one in each organization they belong to. The limit is per context, not per user globally. This limit MUST be enforced at the UI and router layer, not the database layer. Runtime and UI rules MUST limit active organization KiloClaw provisioning to one active instance per user per organization until this product limit is explicitly relaxed, but no schema-level constraint SHALL enforce only one organization KiloClaw instance per organization. +9. When the single-instance limit is relaxed in the future, no schema migration SHALL be required. ### Operational Instance Markers -Instance records MAY store operational lifecycle markers that do not -by themselves grant or revoke billing entitlement. These markers are -runtime metadata on the instance record, not a substitute for -subscription status, suspension, or destruction fields. -Markers MAY be cleared when the lifecycle condition they represent no -longer applies. +Instance records MAY store operational lifecycle markers that alone do not grant or revoke billing entitlement. These markers are runtime metadata on the instance record, not substitutes for subscription status, suspension, or destruction fields. Markers MAY be cleared when the lifecycle condition they represent no longer applies. ### Record Lookup -10. Fetching a single record from `kiloclaw_instance` or - `kiloclaw_subscription` SHOULD use the table's primary key; - non-primary-key lookups are acceptable only when the caller does - not yet know the primary key (e.g., initial resolution from an - external identifier). Queries MUST NOT rely on fuzzy matching, - partial string comparison, or heuristic selection to locate a - specific record. -11. When a query requires filtering by user, organization, or other - non-primary-key attributes (e.g., listing all instances for a - user), the query MUST use exact equality on indexed columns. +10. Fetching a single record from `kiloclaw_instance` or `kiloclaw_subscription` SHOULD use the table's primary key. Non-primary-key lookups are acceptable only when the caller does not yet know the primary key (e.g., initial resolution from an external identifier). Queries MUST NOT rely on fuzzy matching, partial string comparison, or heuristic selection to locate a specific record. +11. Queries filtering by user, organization, or other non-primary-key attributes (e.g., listing all instances for a user) MUST use exact equality on indexed columns. ### Subscription Change Log -Every mutation to a `kiloclaw_subscription` record MUST be -accompanied by a change log entry. The change log is append-only -and serves as the authoritative audit trail for subscription state. - -12. Each service or process that mutates a subscription record MUST - write the corresponding change log entry. This includes - creation, status transitions, plan changes, billing period - advancement, payment source changes, cancellation, reactivation, - suspension, destruction scheduling, and any other mutation. -13. Each change log entry MUST capture the following information: - a. The subscription identifier (foreign key to the subscription - record). - b. A timestamp of when the change occurred. The timestamp MUST - be the database server's current time at the moment of - insertion, not the application's wall clock or an external - event timestamp. +Every `kiloclaw_subscription` mutation MUST be accompanied by a change log entry. The change log is append-only and the authoritative audit trail for subscription state. + +12. Each service or process that mutates a subscription record MUST write the corresponding change log entry. This includes creation, status transitions, plan changes, billing period advancement, payment source changes, cancellation, reactivation, suspension, destruction scheduling, and any other mutation. +13. Each entry MUST capture: + a. The subscription identifier (foreign key to the subscription record). + b. The change timestamp. It MUST be the database server's current time at insertion, not the application's wall clock or an external event timestamp. c. The actor type: `user` or `system`. - d. The actor identifier: for user actors, the user ID; for - system actors, a service or process name (e.g., - `kiloclaw-billing`, `kiloclaw-worker`, `billing-lifecycle-job`, - `stripe-webhook`, `credit-renewal-sweep`). - e. The action performed, as a descriptive label (e.g., - `created`, `status_changed`, `plan_switched`, - `period_advanced`, `canceled`, `reactivated`, `suspended`, - `destruction_scheduled`, `reassigned`). All services MUST use - consistent action labels. New labels MUST be documented before - use. - f. Sufficient detail to reconstruct the state of the - subscription before and after the mutation. For the initial - creation entry, the prior state MUST be recorded as absent. - g. An optional context or reason string providing additional - detail (e.g., `stripe_invoice:inv_xxx`, `insufficient_credits`, - `user_requested`, `trial_expired`). -14. Change log entries MUST NOT be updated or deleted during normal - operation. The log is strictly append-only. GDPR-required - anonymization of directly identifying fields is the sole - exception. That anonymization MUST preserve the event's audit - meaning, timestamps, action labels, and non-identifying context. -15. When the change log entry is written in the same database - transaction as the mutation, a change log failure that aborts - the transaction is acceptable — the entire operation will be - retried. When no enclosing transaction exists, a change log - failure MUST NOT prevent the mutation from succeeding; the - system MUST log the failure and proceed. The system MUST - retry the failed change log write or run a reconciliation - process that detects and backfills missing entries. Missing - entries MUST be resolved within a bounded time (defined by - the implementing service's SLA) so the audit trail remains - complete. -16. When a subscription mutation occurs within a database - transaction, the change log entry SHOULD be written within the - same transaction so that the log is consistent with the - subscription state. Out-of-transaction writes are acceptable - only when the mutation itself is not transactional (e.g., a - single atomic UPDATE). -17. The change log MUST be queryable by subscription identifier and - by time range to support debugging and support investigations. -18. Change log entries MUST NOT contain sensitive data such as - payment tokens, card numbers, or credentials. Payment provider - identifiers (e.g., Stripe subscription ID, invoice ID) MAY be - included as context. + d. The actor identifier: for user actors, the user ID; for system actors, a service or process name (e.g., `kiloclaw-billing`, `kiloclaw-worker`, `billing-lifecycle-job`, `stripe-webhook`, `credit-renewal-sweep`). + e. The action performed, as a descriptive label (e.g., `created`, `status_changed`, `plan_switched`, `period_advanced`, `canceled`, `reactivated`, `suspended`, `destruction_scheduled`, `reassigned`). All services MUST use consistent action labels. New labels MUST be documented before use. + f. Enough detail to reconstruct subscription state before and after the mutation. For initial creation, prior state MUST be recorded as absent. + g. Optional context or reason string with additional detail (e.g., `stripe_invoice:inv_xxx`, `insufficient_credits`, `user_requested`, `trial_expired`). +14. Change log entries MUST NOT be updated or deleted during normal operation. The log is strictly append-only. GDPR-required anonymization of directly identifying fields is the sole exception. That anonymization MUST preserve the event's audit meaning, timestamps, action labels, and non-identifying context. +15. When the change log entry is written in the same database transaction as the mutation, a change log failure that aborts the transaction is acceptable: the entire operation will be retried. Without an enclosing transaction, a change log failure MUST NOT prevent the mutation from succeeding; the system MUST log the failure and proceed. The system MUST retry the failed change log write or run reconciliation to detect and backfill missing entries. Missing entries MUST be resolved within a bounded time (defined by the implementing service's SLA) so the audit trail remains complete. +16. When a subscription mutation occurs within a database transaction, the change log entry SHOULD be written within the same transaction so the log is consistent with subscription state. Out-of-transaction writes are acceptable only when the mutation itself is not transactional (e.g., a single atomic UPDATE). +17. The change log MUST be queryable by subscription identifier and time range for debugging and support investigations. +18. Change log entries MUST NOT contain sensitive data such as payment tokens, card numbers, or credentials. Payment provider identifiers (e.g., Stripe subscription ID, invoice ID) MAY be included as context. ### Record Creation Order -The creation order below reflects the target lifecycle. This order -MUST be enforced only after the existing data model has been brought -into the desired state (rules 1–6 satisfied, early-bird backfill -complete). - -19. A Cloudflare Worker Durable Object and a infra provider base resource MUST both exist - before an instance record is created in `kiloclaw_instance`. - Infrastructure MUST be provisioned first; the record is a - reflection of existing infrastructure, not a reservation. -20. If either infrastructure component fails to provision, the system - MUST NOT create an instance record. Cleanup of any partially - provisioned infrastructure is the responsibility of the - provisioning service. -21. The kiloclaw CF worker service MUST be the sole creator of - `kiloclaw_instance` records. No other service or application - MAY insert rows into this table. -22. After the instance record has been committed to the database, - the kiloclaw CF worker service MUST call the kiloclaw-billing - service to create the corresponding `kiloclaw_subscription` - record. Subscription creation MUST NOT be attempted before the - instance record is persisted. This call MUST occur as part of - the same provisioning request — the window between instance - commit and subscription creation (see rule 4) MUST be bounded - to the duration of that request. If the primary subscription - bootstrap path fails after the instance row is persisted, the - provisioning service MUST retry or run a fallback path that - still creates canonical subscription state before the request - exits. The request MUST NOT complete successfully while leaving - a silently unpaired instance row. If both primary and fallback - bootstrap fail, the provisioning request MUST fail and the - instance MUST be explicitly quarantined for remediation rather - than left as an unnoticed orphan. This quarantine state is the - sole temporary exception to rule 4 and MUST NOT be surfaced as a - successful provisioned instance. -23. The onboarding flow MUST NOT be considered complete (and MUST NOT - play the completion "ding" sound) until both the instance record - and the subscription record have been persisted to the database. +The creation order below reflects the target lifecycle. This order MUST be enforced only after the existing data model reaches the desired state (rules 1–6 satisfied, early-bird backfill complete). + +19. A Cloudflare Worker Durable Object and an infra provider base resource MUST both exist before an instance record is created in `kiloclaw_instance`. Infrastructure MUST be provisioned first; the record is a reflection of existing infrastructure, not a reservation. +20. If either infrastructure component fails to provision, the system MUST NOT create an instance record. Cleanup of any partially provisioned infrastructure is the provisioning service's responsibility. +21. The kiloclaw CF worker service MUST be the sole creator of `kiloclaw_instance` records. No other service or application MAY insert rows into this table. +22. After the instance record is committed, the kiloclaw CF worker service MUST call the kiloclaw-billing service to create the corresponding `kiloclaw_subscription` record. For organizational-context provisioning, this bootstrap MUST create the corresponding organization-funded subscription row. Subscription creation MUST NOT be attempted before the instance record is persisted. This call MUST occur as part of the same provisioning request: the window between instance commit and subscription creation (see rule 4) MUST be bounded to that request's duration. If the primary subscription bootstrap path fails after the instance row is persisted, the provisioning service MUST retry or run a fallback path that creates canonical subscription state before the request exits. The request MUST NOT complete successfully while leaving a silently unpaired instance row. If both primary and fallback bootstrap fail, the provisioning request MUST fail and the instance MUST be explicitly quarantined for remediation, not left as an unnoticed orphan. This quarantine state is the sole temporary exception to rule 4 and MUST NOT be surfaced as a successful provisioned instance. +23. The onboarding flow MUST NOT be considered complete and MUST NOT play the completion "ding" sound until both the instance record and subscription record have been persisted to the database. ## Migration Path -The creation-order rules (19–23) represent the target state. They -MUST NOT be enforced until the following prerequisites are met: +The creation-order rules (19–23) represent the target state. They MUST NOT be enforced until these prerequisites are met: -1. All existing instance records satisfy rules 1–6 (no orphaned - instances without subscriptions). +1. All existing instance records satisfy rules 1–6 (no orphaned instances without subscriptions). 2. Early-bird subscription backfill is complete (rule 6). -3. Any existing code paths that create records in a different order - have been updated. +3. Any existing code paths that create records in a different order have been updated. -Until these prerequisites are met, the existing creation order -remains in effect and the system MUST tolerate records created under -the prior ordering. +Until these prerequisites are met, the existing creation order remains in effect and the system MUST tolerate records created under the prior ordering. ## Not Yet Implemented -The following rules use SHOULD and reflect intended behavior that is -not yet enforced in the current codebase: +The following rules use SHOULD and reflect intended behavior not yet enforced: -1. Early-bird subscription backfill SHOULD be completed before - enforcing the creation-order rules. (Currently, early-bird users - may have instance records without subscription records.) -2. The onboarding flow SHOULD gate completion on both records - existing. (Currently, the onboarding flow may complete before - subscription creation.) -3. The subscription change log (rules 12–18) SHOULD be implemented - across all services that mutate subscription records. (Currently, - no change log exists; subscription history can only be - reconstructed from application logs.) +1. Early-bird subscription backfill SHOULD be completed before enforcing the creation-order rules. (Currently, early-bird users may have instance records without subscription records.) +2. The onboarding flow SHOULD gate completion on both records existing. (Currently, the onboarding flow may complete before subscription creation.) +3. The subscription change log (rules 12–18) SHOULD be implemented across all services that mutate subscription records. (Currently, no change log exists; subscription history requires reconstruction from application logs.) ## Changelog +### 2026-05-06 -- Organization KiloClaw ownership + +- Added associated-user terminology for organizational instances. +- Clarified organizational instance ownership, billing ownership, per-user-per-org active-instance limits, organization-funded subscription bootstrap, and associated-user GDPR handling. + ### 2026-04-15 -- Initial spec - Record immutability (rules 1–3), including GDPR anonymization. -- Instance–subscription pairing invariant (rules 4–6) with - early-bird backfill requirement. -- Multi-instance support with per-context single-instance limit - (rules 7–9). +- Instance–subscription pairing invariant (rules 4–6) and early-bird backfill requirement. +- Multi-instance support with per-context single-instance limit (rules 7–9). - Primary-key-based record lookup rules (rules 10–11). -- Subscription change log with actor tracking, action labels, - before/after state, and transaction semantics (rules 12–18). +- Subscription change log with actor tracking, action labels, before/after state, and transaction semantics (rules 12–18). - Record creation order and partial-failure handling (rules 19–23). diff --git a/.specs/team-enterprise-seat-billing.md b/.specs/team-enterprise-seat-billing.md index 7d957912d7..feef8bf213 100644 --- a/.specs/team-enterprise-seat-billing.md +++ b/.specs/team-enterprise-seat-billing.md @@ -2,119 +2,78 @@ ## Role of This Document -This spec defines the business rules and invariants for Team and -Enterprise seat billing. It is the source of truth for _what_ the -system must guarantee — valid states, pricing, seat counting, -subscription lifecycle, and user-facing behavior. It deliberately -does not prescribe _how_ to implement those guarantees: handler -names, column layouts, Stripe API call patterns, and other -implementation choices belong in plan documents and code, not here. +This spec defines business rules and invariants for Team and Enterprise seat billing: required states, pricing, seat +counting, subscription lifecycle, and user-facing behavior. It is the source of truth for _what_ the system must +guarantee, not _how_ to implement it. Handler names, column layouts, Stripe API call patterns, and other implementation +choices belong in plans and code. ## Status Active. -- Reverse-engineered from existing code on 2026-03-26. -- Updated 2026-03-26 to add monthly billing cycle option. -- Updated 2026-03-28 to document billing cycle change hardening. -- Updated 2026-03-28/29 with spec audit fixes, definitions, and billing compliance implementation. +- 2026-03-26: reverse-engineered from existing code. +- 2026-03-26: added monthly billing cycle option. +- 2026-03-28: documented billing cycle change hardening. +- 2026-03-28/29: added spec audit fixes, definitions, and billing compliance implementation. ## Conventions -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", -"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and -"OPTIONAL" in this document are to be interpreted as described in -BCP 14 [RFC 2119] [RFC 8174] when, and only when, they appear in all -capitals, as shown here. +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT +RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC 2119] [RFC 8174] +when, and only when, they appear in all capitals, as shown here. -All monetary amounts in this spec are in USD. The system currently -supports only USD for seat billing. +All monetary amounts in this spec are USD; seat billing currently supports only USD. ## Definitions -- **Active member**: A user who has a current organization - membership record and is not a bot user. This is the set of - users counted toward seat usage (after excluding billing - managers). -- **Seat**: A unit of access purchased by an organization. Each - active member (except billing managers) and each pending invitation - (except for the billing manager role) consumes one seat. -- **Billing cycle**: The recurrence interval for a subscription — - either monthly or annual. -- **Billing period**: A single interval within a billing cycle (one - month for monthly, one year for annual). Renewal and proration - boundaries align to billing period edges. -- **Purchase record**: A persistent record of a subscription event, - containing: subscription ID, organization ID, seat count, amount, - start date, expiration date, idempotency key, and status. -- **Billing manager**: An organization role that grants access to - billing operations (checkout, cancellation, seat changes, portal) - without consuming a seat. -- **Seat usage**: The count of active members plus qualifying pending - invitations, excluding billing managers and expired/accepted +- **Active member**: User with a current organization membership record who is not a bot user. Counted toward seat usage + after excluding billing managers. +- **Seat**: Access unit purchased by an organization. Each active member (except billing managers) and each pending + invitation (except billing manager role) consumes one seat. +- **Billing cycle**: Subscription recurrence interval: monthly or annual. +- **Billing period**: One interval within a billing cycle (one month for monthly, one year for annual). Renewal and + proration boundaries align to billing period edges. +- **Purchase record**: Persistent subscription-event record containing: subscription ID, organization ID, seat count, + amount, start date, expiration date, idempotency key, and status. +- **Billing manager**: Organization role granting access to billing operations (checkout, cancellation, seat changes, + portal) without consuming a seat. +- **Seat usage**: Active members plus qualifying pending invitations, excluding billing managers and expired/accepted invitations. -- **Non-ended subscription**: A subscription for which the payment - processor has not set a termination timestamp. All subscription - statuses other than canceled and incomplete-expired are non-ended. -- **Active subscription**: A subscription whose payment processor - status is specifically `active`. Distinguished from "non-ended - subscription," which includes additional statuses such as - past-due or incomplete. -- **Active subscription purchase**: A purchase record whose - `subscription_status` is not `ended`. Used for access checks and - trial enforcement — an organization with at least one active - subscription purchase is considered subscribed. -- **Known paid seat prices**: The set of payment processor price - identifiers corresponding to the four seat pricing tiers (Teams - monthly, Teams annual, Enterprise monthly, Enterprise annual). - This set is maintained in configuration and MUST be updated - whenever pricing tiers are added or removed. -- **Seat line item**: A subscription line item whose associated - payment processor price product is the Teams or Enterprise seat - product. Paid seat line items use known paid seat prices. - Free-seat line items use another price under a seat product. -- **Free-seat line item**: A seat line item whose price is not one - of the known paid seat prices. These represent promotional or - complimentary seats and are excluded when determining the - resubscribe quantity. -- **Seat metadata**: Payment processor metadata used to classify - subscriptions or invoices as seat-related. Subscription metadata - `type` field value `seats` identifies seat subscriptions; known - paid seat price IDs can also identify seat invoice lines. -- **Self-service creation flow**: The user-initiated flow for - creating a new organization through the standard web UI, as - opposed to administrative creation (admin panel, scripts, or - API). -- **Require-seats flag**: A per-organization boolean that, when - false, bypasses all trial and subscription enforcement — used for - design partners, internal testing, and enterprise contracts. -- **Subscription event**: A notification about a subscription state - change, originating either from the payment processor (via - webhook) or recorded locally after a direct API call (see - Subscription Lifecycle rules 7-8). Both sources produce the same - processing flow. -- **Subscription metadata user**: The user identified by the - `kiloUserId` field in the payment processor subscription's - metadata object. This is typically the user who initiated the - checkout. -- **Subscription metadata type**: A string field in the payment - processor subscription metadata that identifies the subscription - category (e.g., `seats`). Used to dispatch subscription events - to the appropriate handler. -- **Bot user**: A system or service account (flagged `is_bot` in - the user record) that is excluded from seat counting and +- **Non-ended subscription**: Subscription for which the payment processor has not set a termination timestamp. All + subscription statuses except canceled and incomplete-expired are non-ended. +- **Active subscription**: Subscription whose payment processor status is specifically `active`. Distinguished from + "non-ended subscription," which also includes statuses such as past-due or incomplete. +- **Active subscription purchase**: Purchase record whose `subscription_status` is not `ended`. Used for access checks + and trial enforcement — an organization with at least one active subscription purchase is considered subscribed. +- **Known paid seat prices**: Payment processor price identifiers for the four seat pricing tiers (Teams monthly, Teams + annual, Enterprise monthly, Enterprise annual). This configuration set MUST be updated whenever pricing tiers are + added or removed. +- **Seat line item**: Subscription line item whose associated payment processor price product is the Teams or Enterprise + seat product. Paid seat line items use known paid seat prices. Free-seat line items use another price under a seat + product. +- **Free-seat line item**: Seat line item whose price is not one of the known paid seat prices. These represent + promotional or complimentary seats and are excluded when determining resubscribe quantity. +- **Seat metadata**: Payment processor metadata used to classify subscriptions or invoices as seat-related. Subscription + metadata `type` value `seats` identifies seat subscriptions; known paid seat price IDs can also identify seat invoice + lines. +- **Self-service creation flow**: User-initiated flow for creating a new organization through the standard web UI, not + administrative creation (admin panel, scripts, or API). +- **Require-seats flag**: Per-organization boolean that, when false, bypasses all trial and subscription enforcement — + used for design partners, internal testing, and enterprise contracts. +- **Subscription event**: Subscription state-change notification from the payment processor (via webhook) or recorded + locally after a direct API call (see Subscription Lifecycle rules 7-8). Both sources produce the same processing flow. +- **Subscription metadata user**: User identified by `kiloUserId` in the payment processor subscription metadata object. + Typically the checkout initiator. +- **Subscription metadata type**: String field in payment processor subscription metadata identifying the subscription + category (e.g., `seats`). Used to dispatch subscription events to the appropriate handler. +- **Bot user**: System or service account (flagged `is_bot` in the user record) excluded from seat counting and organization member lists. -- **OSS sponsorship program**: An organization-level enrollment - (indicated by a non-null `oss_sponsorship_tier` setting) managed - through an administrative workflow. Participating organizations - are exempt from trial expiration. -- **Suppressed trial messaging**: An organization setting - (`suppress_trial_messaging`) that, when enabled, treats the - organization as if it has an active subscription for trial - enforcement purposes. -- **Trial stages**: Progressive classifications of an - organization's trial status based on the floored `daysRemaining` - value (see Free Trial rule 3): +- **OSS sponsorship program**: Organization-level enrollment (indicated by a non-null `oss_sponsorship_tier` setting) + managed through an administrative workflow. Participating organizations are exempt from trial expiration. +- **Suppressed trial messaging**: Organization setting (`suppress_trial_messaging`) that, when enabled, treats the + organization as if it has an active subscription for trial enforcement purposes. +- **Trial stages**: Progressive classifications of an organization's trial status based on floored `daysRemaining` (see + Free Trial rule 3): - **Active**: `daysRemaining >= 8` - **Ending soon**: `daysRemaining` 4 through 7 (inclusive) - **Ending very soon**: `daysRemaining` 1 through 3 (inclusive) @@ -124,501 +83,348 @@ supports only USD for seat billing. ## Overview -Organizations purchase seats through recurring subscriptions to grant -their members access to the platform. Two plan tiers exist -- Teams -and Enterprise -- each with distinct per-seat pricing. Both monthly -and annual billing cycles are available for each tier, with annual -billing discounted to 10 months' worth of the monthly rate (12 months -for the price of 10). All self-service signups begin on the Enterprise -plan with a 14-day free trial; users choose their plan tier (Teams or -Enterprise) and billing cycle (monthly or annual) when converting to a -paid subscription. Seat counts are tracked per organization and -enforced against active members and pending invitations. Organizations -without an active subscription operate under a time-limited free trial -with escalating restrictions: informational banners, then a -dismissible read-only lock, then a non-dismissible hard lock that -blocks all server-side mutations. A "billing manager" role exists that -grants billing access without consuming a seat. +Organizations buy recurring seats to give members platform access. Two plan tiers exist -- Teams and Enterprise -- each +with distinct per-seat pricing. Each tier supports monthly and annual billing; annual billing costs 10 months of the +monthly rate (12 months for the price of 10). All self-service signups start on Enterprise with a 14-day free trial. +Users choose plan tier (Teams or Enterprise) and billing cycle (monthly or annual) only when converting to paid. Seat +counts are tracked per organization and enforced against active members and pending invitations. Organizations without +an active subscription use a time-limited free trial with escalating restrictions: informational banners, a dismissible +read-only lock, then a non-dismissible hard lock blocking all server-side mutations. A billing manager role grants +billing access without consuming a seat. ## Rules ### Organization Plans -1. The system MUST support exactly two plan types: Teams and - Enterprise. -2. The system MUST default the organization plan to Teams at the - data layer when no plan is explicitly provided by the creation - flow. (Self-service creation always provides Enterprise; see - rule 3.) -3. The self-service creation flow MUST create all new organizations - on the Enterprise plan with a 14-day free trial. -4. The user MUST NOT be offered a choice of plan or billing cycle - during trial creation; plan tier and billing cycle selection - occurs only when converting to a paid subscription. -5. When processing any subscription event (whether from a webhook - or a local API call), the system MUST update the organization's - plan type if the subscription metadata includes a valid plan - type value. -6. The system MUST NOT change the organization's plan type when the - subscription metadata omits the plan type field. -7. The system MUST silently ignore (log and discard) invalid plan - type values in subscription metadata. -8. When an organization transitions from Enterprise to Teams, the - system MUST persist Enterprise-only settings (e.g., model deny - lists, provider deny lists) in storage, MUST exclude them from - enforcement logic, and MUST NOT expose them in the organization - settings UI while the plan is Teams. -9. If the organization later transitions back to Enterprise, the - system MUST reactivate the previously stored Enterprise-only - settings without requiring reconfiguration. +1. System MUST support exactly two plan types: Teams and Enterprise. +2. System MUST default the organization plan to Teams at the data layer when no plan is explicitly provided by the + creation flow. (Self-service creation always provides Enterprise; see rule 3.) +3. The self-service creation flow MUST create all new organizations on the Enterprise plan with a 14-day free trial. +4. The user MUST NOT be offered a choice of plan or billing cycle during trial creation; plan tier and billing cycle + selection occurs only when converting to a paid subscription. +5. When processing any subscription event (whether from a webhook or a local API call), system MUST update the + organization's plan type if the subscription metadata includes a valid plan type value. +6. System MUST NOT change the organization's plan type when the subscription metadata omits the plan type field. +7. System MUST silently ignore (log and discard) invalid plan type values in subscription metadata. +8. When an organization transitions from Enterprise to Teams, the system MUST persist Enterprise-only settings (e.g., + model deny lists, provider deny lists, KiloClaw opt-out) in storage, MUST exclude them from enforcement logic, and + MUST NOT expose them in the organization settings UI while the plan is Teams. +9. If the organization later transitions back to Enterprise, the system MUST reactivate the previously stored + Enterprise-only settings without requiring reconfiguration. ### Seat Pricing -1. The system MUST price Teams seats at $18 per seat per month on - the monthly billing cycle. -2. The system MUST price Teams seats at $180 per seat per year on - the annual billing cycle (equivalent to $15 per seat per month). -3. The system MUST price Enterprise seats at $72 per seat per month - on the monthly billing cycle. -4. The system MUST price Enterprise seats at $720 per seat per year - on the annual billing cycle (equivalent to $60 per seat per +1. System MUST price Teams seats at $18 per seat per month on the monthly billing cycle. +2. System MUST price Teams seats at $180 per seat per year on the annual billing cycle (equivalent to $15 per seat per month). -5. Annual pricing MUST equal 10 times the monthly rate for each - plan tier (12 months for the price of 10). -6. Both monthly and annual billing cycles MUST be available for - both plan tiers. +3. System MUST price Enterprise seats at $72 per seat per month on the monthly billing cycle. +4. System MUST price Enterprise seats at $720 per seat per year on the annual billing cycle (equivalent to $60 per seat + per month). +5. Annual pricing MUST equal 10 times the monthly rate for each plan tier (12 months for the price of 10). +6. Both monthly and annual billing cycles MUST be available for both plan tiers. ### Seat Purchase and Checkout -1. The system MUST allow purchasing between 1 and 100 seats - (inclusive) per checkout. -2. The system MUST require the user to select a billing cycle - (monthly or annual) during checkout. -3. The system MUST use the billing-cycle-specific price for the - selected plan tier when creating the checkout session. -4. The system MUST require a payment processor customer record for - the organization before creating a checkout session. -5. The system MUST lazily create the payment processor customer - record when one does not yet exist. -6. The system MUST NOT allow creating a second subscription if the - organization already has a non-ended subscription. Seat changes - on an existing subscription MUST use the mid-subscription - modification flow defined in the Seat Count Modification section. -7. The system MUST record each subscription event as a seat purchase - record with: subscription ID, organization ID, seat count, amount - in USD, start date, expiration date, idempotency key, and - subscription status. The amount MUST reflect the actual - cycle-specific price charged. -8. When resubscribing after an ended subscription, the system MUST - use only the paid seat quantity from the most recently ended - subscription (by termination timestamp), excluding free-seat - line items, as the checkout quantity. -9. The system MUST require billing address collection during - checkout. +1. System MUST allow purchasing between 1 and 100 seats (inclusive) per checkout. +2. System MUST require the user to select a billing cycle (monthly or annual) during checkout. +3. System MUST use the billing-cycle-specific price for the selected plan tier when creating the checkout session. +4. System MUST require a payment processor customer record for the organization before creating a checkout session. +5. System MUST lazily create the payment processor customer record when one does not yet exist. +6. System MUST NOT allow creating a second subscription if the organization already has a non-ended subscription. Seat + changes on an existing subscription MUST use the mid-subscription modification flow defined in the Seat Count + Modification section. +7. System MUST record each subscription event as a seat purchase record with: subscription ID, organization ID, seat + count, amount in USD, start date, expiration date, idempotency key, and subscription status. The amount MUST reflect + the actual cycle-specific price charged. +8. When resubscribing after an ended subscription, system MUST use only the paid seat quantity from the most recently + ended subscription (by termination timestamp), excluding free-seat line items, as the checkout quantity. +9. System MUST require billing address collection during checkout. ### Seat Usage Counting -1. The system MUST count each active organization member toward - seat usage, except members with the billing manager role. -2. The system MUST count each pending invitation toward seat usage, - except invitations for the billing manager role. -3. The system MUST NOT count expired invitations toward seat usage. -4. The system MUST NOT count accepted invitations toward seat usage - (accepted invitees are counted as active members instead). -5. The system MUST NOT count bot users (see Definitions) toward - seat usage. -6. The system MUST report seat usage as a pair: seats used (members - plus qualifying pending invitations) and total seats purchased. -7. The system MUST allow seat usage to exceed total purchased seats - (no hard block on over-usage at the counting layer). -8. For Teams-plan organizations, the system MUST disable the - invitation UI when seat usage equals or exceeds the purchased +1. System MUST count each active organization member toward seat usage, except members with the billing manager role. +2. System MUST count each pending invitation toward seat usage, except invitations for the billing manager role. +3. System MUST NOT count expired invitations toward seat usage. +4. System MUST NOT count accepted invitations toward seat usage (accepted invitees are counted as active members + instead). +5. System MUST NOT count bot users (see Definitions) toward seat usage. +6. System MUST report seat usage as a pair: seats used (members plus qualifying pending invitations) and total seats + purchased. +7. System MUST allow seat usage to exceed total purchased seats (no hard block on over-usage at the counting layer). +8. For Teams-plan organizations, system MUST disable the invitation UI when seat usage equals or exceeds the purchased seat count. -9. For Enterprise-plan organizations, the system MUST NOT restrict - invitations based on seat usage. -10. The server MUST NOT enforce seat limits when processing - invitations or when members accept invitations; seat-limit +9. For Enterprise-plan organizations, system MUST NOT restrict invitations based on seat usage. +10. Server MUST NOT enforce seat limits when processing invitations or when members accept invitations; seat-limit enforcement on invitations is a UI-layer-only control. -11. When a billing period begins with a lower seat count than - current usage (e.g., an end-of-period downgrade takes effect), - the system MUST NOT remove existing members. The over-usage - state persists until resolved by the organization (by removing - members or purchasing additional seats). The system SHOULD - display a warning to organization owners indicating over-usage. +11. When a billing period begins with a lower seat count than current usage (e.g., an end-of-period downgrade takes + effect), system MUST NOT remove existing members. The over-usage state persists until resolved by the organization + (by removing members or purchasing additional seats). System SHOULD display a warning to organization owners + indicating over-usage. ### Seat Count Updates from Subscription Events -1. The system MUST update the organization's seat count when - processing an active subscription event. -2. The organization's effective seat count MUST equal the highest - seat count among all purchase records that share the most recent - billing-period start date for that organization. -3. The system MUST apply seat upgrades (higher count with a more - recent start date) within the same transaction that records +1. System MUST update the organization's seat count when processing an active subscription event. +2. The organization's effective seat count MUST equal the highest seat count among all purchase records that share the + most recent billing-period start date for that organization. +3. System MUST apply seat upgrades (higher count with a more recent start date) within the same transaction that records the purchase. -4. The system MUST NOT apply seat downgrades within the same billing - period; the current higher seat count MUST be retained until a - new billing period begins. -5. The system MUST apply seat downgrades when a subscription event - arrives with a more recent start date (new billing period). -6. The system MUST handle out-of-order subscription events correctly - by always resolving to the seat count from the most recent start - date, regardless of processing order. -7. The system MUST set the organization's seat count to zero when - the subscription has ended. -8. The system MUST NOT update the organization's seat count for - subscriptions in non-active statuses (e.g., incomplete, past +4. System MUST NOT apply seat downgrades within the same billing period; the current higher seat count MUST be retained + until a new billing period begins. +5. System MUST apply seat downgrades when a subscription event arrives with a more recent start date (new billing + period). +6. System MUST handle out-of-order subscription events correctly by always resolving to the seat count from the most + recent start date, regardless of processing order. +7. System MUST set the organization's seat count to zero when the subscription has ended. +8. System MUST NOT update the organization's seat count for subscriptions in non-active statuses (e.g., incomplete, past due); the purchase record MUST still be created. -9. The system MUST sum quantities across all seat line items in a - subscription to compute the total seat count (to support - subscriptions with multiple seat price tiers), and MUST exclude - non-seat line items. -10. The system MUST record the seat subscription amount as the gross - total (list-price unit amount times quantity) across all seat - line items. This value does not reflect discounts, promotion - codes, coupons, or non-seat line items. -11. The system SHOULD record the net amount actually charged (after - discounts) rather than the gross list price. This is SHOULD - rather than MUST because the current payment processor API does - not expose a reliable net amount at event time; once available, - this SHOULD be upgraded to MUST. (Not yet implemented.) -12. When a subscription event's recurring interval is not recognized - as monthly or annual, the system MUST default the billing cycle - to monthly and log a warning. +9. System MUST sum quantities across all seat line items in a subscription to compute the total seat count (to support + subscriptions with multiple seat price tiers), and MUST exclude non-seat line items. +10. System MUST record the seat subscription amount as the gross total (list-price unit amount times quantity) across + all seat line items. This value does not reflect discounts, promotion codes, coupons, or non-seat line items. +11. System SHOULD record the net amount actually charged (after discounts) rather than the gross list price. This is + SHOULD rather than MUST because the current payment processor API does not expose a reliable net amount at event + time; once available, this SHOULD be upgraded to MUST. (Not yet implemented.) +12. When a subscription event's recurring interval is not recognized as monthly or annual, system MUST default the + billing cycle to monthly and log a warning. ### Idempotency -1. The system MUST use an idempotency key per subscription event to - prevent duplicate processing. -2. The system MUST auto-generate an idempotency key when one is not - provided. -3. The system MUST silently skip subscription events whose - idempotency key already exists. -4. The system MUST produce exactly one purchase record even when - multiple concurrent calls use the same idempotency key. +1. System MUST use an idempotency key per subscription event to prevent duplicate processing. +2. System MUST auto-generate an idempotency key when one is not provided. +3. System MUST silently skip subscription events whose idempotency key already exists. +4. System MUST produce exactly one purchase record even when multiple concurrent calls use the same idempotency key. ### Subscription Lifecycle -1. The system MUST ensure the subscription metadata user is a - member of the organization when processing any subscription - event, subject to the removal check in rule 2. If the user has - never been a member of the organization, the system MUST add - them as owner. If the user already has a membership in any role, - the system MUST preserve their current role. If the metadata - user ID does not resolve to a valid user record, the membership - step silently fails (no membership is created) but the - subscription event continues to be processed normally — the - purchase record is still created and seat counts are still - updated. The membership-ensure step MAY execute outside the - purchase-recording transaction; if it succeeds but the purchase - recording subsequently fails, the membership MUST NOT be rolled - back — the user retains their membership. -2. The system MUST NOT re-add a subscription metadata user who was - previously removed from the organization. This rule takes - precedence over rule 1: a removed user is not treated as "never - been a member." The system MUST be able to distinguish between a - user who has never been a member and a user who was removed. - (Not yet implemented — currently, if the metadata user was - removed from the organization and a subsequent webhook fires, - they are re-added as owner.) -3. The system MUST cancel subscriptions at the end of the current - billing period (not immediately). -4. When cancelling a subscription that has a pending billing cycle - change schedule, the system MUST release the schedule before or - as part of the cancellation to prevent orphaned schedules. -5. The system MUST allow a pending cancellation to be reversed - (stop cancellation), restoring the subscription to active. -6. When resubscribing after an ended subscription, the system MUST - preserve the previous billing cycle (monthly or annual) as the - default for the new checkout session. -7. The system MUST record subscription changes to the system's - persistent state synchronously — before returning a response to - the caller — after making payment processor API calls. The - system MUST NOT depend solely on asynchronous webhook delivery - for state consistency. -8. The system MUST also process incoming webhook events for - subscription creation, update, and deletion. -9. The system MUST dispatch the subscription event to the correct - handler based on the subscription metadata type field (see - Definitions). Events with unrecognized type values MUST be - logged and discarded. -10. The system MUST NOT allow deletion of an organization while a - non-ended subscription exists. The subscription MUST be - cancelled (and reach ended state) before organization deletion - can proceed. +1. System MUST ensure the subscription metadata user is a member of the organization when processing any subscription + event, subject to the removal check in rule 2. If the user has never been a member of the organization, system MUST + add them as owner. If the user already has a membership in any role, system MUST preserve their current role. If the + metadata user ID does not resolve to a valid user record, the membership step silently fails (no membership is + created) but the subscription event continues to be processed normally — the purchase record is still created and + seat counts are still updated. The membership-ensure step MAY execute outside the purchase-recording transaction; if + it succeeds but the purchase recording subsequently fails, the membership MUST NOT be rolled back — the user retains + their membership. +2. System MUST NOT re-add a subscription metadata user who was previously removed from the organization. This rule takes + precedence over rule 1: a removed user is not treated as "never been a member." System MUST be able to distinguish + between a user who has never been a member and a user who was removed. (Not yet implemented — currently, if the + metadata user was removed from the organization and a subsequent webhook fires, they are re-added as owner.) +3. System MUST cancel subscriptions at the end of the current billing period (not immediately). +4. When cancelling a subscription that has a pending billing cycle change schedule, system MUST release the schedule + before or as part of the cancellation to prevent orphaned schedules. +5. System MUST allow a pending cancellation to be reversed (stop cancellation), restoring the subscription to active. +6. When resubscribing after an ended subscription, system MUST preserve the previous billing cycle (monthly or annual) + as the default for the new checkout session. +7. System MUST record subscription changes to the system's persistent state synchronously — before returning a response + to the caller — after making payment processor API calls. The system MUST NOT depend solely on asynchronous webhook + delivery for state consistency. +8. System MUST also process incoming webhook events for subscription creation, update, and deletion. +9. System MUST dispatch the subscription event to the correct handler based on the subscription metadata type field (see + Definitions). Events with unrecognized type values MUST be logged and discarded. +10. System MUST NOT allow deletion of an organization while a non-ended subscription exists. The subscription MUST be + cancelled (and reach ended state) before organization deletion can proceed. ### Seat Count Modification (Mid-Subscription) -1. The system MUST allow only organization owners and billing - managers to modify the seat count on an active subscription. -2. The system MUST reject seat downgrades when the requested count - is less than the number of seats currently in use. -3. The system MUST apply prorated billing (immediate invoice) when - increasing seats. -4. The system MUST NOT prorate when decreasing seats; the decrease - takes effect at the end of the billing cycle. This applies to - both monthly and yearly billing cycles. -5. The system MUST support payment authentication challenges (e.g., - 3D Secure) for seat increases that require additional +1. System MUST allow only organization owners and billing managers to modify the seat count on an active subscription. +2. System MUST reject seat downgrades when the requested count is less than the number of seats currently in use. +3. System MUST apply prorated billing (immediate invoice) when increasing seats. +4. System MUST NOT prorate when decreasing seats; the decrease takes effect at the end of the billing cycle. This + applies to both monthly and yearly billing cycles. +5. System MUST support payment authentication challenges (e.g., 3D Secure) for seat increases that require additional verification, returning a client secret for frontend handling. -6. The system MUST validate that the new seat count is a positive - integer. -7. The system MUST NOT impose an upper limit on seat count for - mid-subscription modifications. The 100-seat limit in Seat +6. System MUST validate that the new seat count is a positive integer. +7. System MUST NOT impose an upper limit on seat count for mid-subscription modifications. The 100-seat limit in Seat Purchase rule 1 applies only to the initial checkout flow. -8. The system MUST serialize concurrent seat count modifications - for the same subscription to prevent conflicting payment +8. System MUST serialize concurrent seat count modifications for the same subscription to prevent conflicting payment processor updates. ### Billing Cycle Changes -1. The system MUST allow an organization to request a billing cycle - change (monthly to annual, or annual to monthly) on an active - subscription. If two concurrent requests both pass the - existing-schedule check, the second MUST fail rather than create - a duplicate schedule. -2. A billing cycle change MUST take effect at the next renewal date; - it MUST NOT be applied immediately. -3. The system MUST NOT prorate or issue immediate charges or credits - for a billing cycle change request. -4. The system MUST restrict billing cycle change requests to - organization owners and billing managers. -5. When a billing cycle change takes effect at renewal, the system - MUST use the new cycle's price for the next billing period. -6. The system MUST allow cancellation of a pending billing cycle - change, restoring the subscription to its original cycle. -7. The system MUST preserve all subscription line items when - scheduling a billing cycle change; only the paid seat price - MUST change, other items (e.g., free seats) MUST be carried - forward unchanged. -8. The system MUST preserve subscription-level discounts (promotion - codes, coupons) when scheduling a billing cycle change. Both the - current-period phase and the new-cycle phase MUST retain the - active discounts. -9. If the schedule phase update fails after the schedule has been - created, the system MUST release the orphaned schedule so the - subscription is not permanently blocked from future cycle - changes. -10. The system MUST determine the subscription's plan tier from the - live payment processor price, not from the organization's - database plan field, to prevent billing the wrong tier when - the two diverge. -11. The system MUST verify that a schedule was created by the - billing cycle change flow before allowing cancellation, to +1. System MUST allow an organization to request a billing cycle change (monthly to annual, or annual to monthly) on an + active subscription. If two concurrent requests both pass the existing-schedule check, the second MUST fail rather + than create a duplicate schedule. +2. A billing cycle change MUST take effect at the next renewal date; it MUST NOT be applied immediately. +3. System MUST NOT prorate or issue immediate charges or credits for a billing cycle change request. +4. System MUST restrict billing cycle change requests to organization owners and billing managers. +5. When a billing cycle change takes effect at renewal, the system MUST use the new cycle's price for the next billing + period. +6. System MUST allow cancellation of a pending billing cycle change, restoring the subscription to its original cycle. +7. System MUST preserve all subscription line items when scheduling a billing cycle change; only the paid seat price + MUST change, other items (e.g., free seats) MUST be carried forward unchanged. +8. System MUST preserve subscription-level discounts (promotion codes, coupons) when scheduling a billing cycle change. + Both the current-period phase and the new-cycle phase MUST retain the active discounts. +9. If the schedule phase update fails after the schedule has been created, system MUST release the orphaned schedule so + the subscription is not permanently blocked from future cycle changes. +10. System MUST determine the subscription's plan tier from the live payment processor price, not from the + organization's database plan field, to prevent billing the wrong tier when the two diverge. +11. System MUST verify that a schedule was created by the billing cycle change flow before allowing cancellation, to avoid releasing unrelated schedules. ### Subscription Access Control -1. The system MUST restrict subscription creation, cancellation, - stop-cancellation, seat count changes, and billing portal access - to organization owners and billing managers. -2. The system MUST allow any organization member to view the current - subscription status and seat usage. -3. The system MUST reject requests from non-members with an access - denied error. -4. The system MUST reject requests from members who are neither - owners nor billing managers with a role-based authorization +1. System MUST restrict subscription creation, cancellation, stop-cancellation, seat count changes, and billing portal + access to organization owners and billing managers. +2. System MUST allow any organization member to view the current subscription status and seat usage. +3. System MUST reject requests from non-members with an access denied error. +4. System MUST reject requests from members who are neither owners nor billing managers with a role-based authorization error. ### Require-Seats Flag and Subscription Enforcement -1. The system MUST treat organizations with the require-seats flag - set to false as having an active subscription for all access - checks (bypassing trial and subscription requirements). -2. The system MUST set the require-seats flag to true for all new - organization signups, including enterprise trials. -3. The system MUST allow platform administrators (users with the - site-wide admin role) to manually set the require-seats flag to - false for any organization at the administrator's discretion, - including design partners, internal testing, and enterprise - contracts. -4. The system MUST classify an organization's status as "active" - when it either has require-seats disabled OR has an active +1. System MUST treat organizations with the require-seats flag set to false as having an active subscription for all + access checks (bypassing trial and subscription requirements). +2. System MUST set the require-seats flag to true for all new organization signups, including enterprise trials. +3. System MUST allow platform administrators (users with the site-wide admin role) to manually set the require-seats + flag to false for any organization at the administrator's discretion, including design partners, internal testing, + and enterprise contracts. +4. System MUST classify an organization's status as "active" when it either has require-seats disabled OR has an active subscription purchase. -5. The system MUST classify an organization's status as "incomplete" - when it has require-seats enabled AND has no active subscription. +5. System MUST classify an organization's status as "incomplete" when it has require-seats enabled AND has no active + subscription. + +### Organization KiloClaw Add-On + +1. KiloClaw is an organization add-on subordinate to the organization subscription or trial entitlement. +2. Organization KiloClaw MUST NOT create, modify, or extend the organization seat subscription. +3. Organization KiloClaw MUST NOT appear as a direct Stripe add-on line item on the organization seat subscription. +4. When the organization subscription entitlement ends because the organization subscription is canceled, ended, or + otherwise no longer recoverable as the same entitlement, the KiloClaw billing system MUST cancel organization + KiloClaw subscriptions. +5. Organization KiloClaw availability MUST follow the organization's active, trial, and hard-expired access state. + Active or trialing organization states MAY allow KiloClaw, subject to KiloClaw-specific rules. Hard-expired + organization trial states MUST block KiloClaw access but MUST NOT by themselves immediately cancel organization + KiloClaw subscriptions. Ended organization entitlement MUST block KiloClaw access. +6. Enterprise organizations MUST support an admin-controlled KiloClaw opt-out setting. +7. This setting MUST be persisted like other Enterprise-only settings. +8. The setting MUST be hidden or non-configurable while the organization is Teams. +9. The setting MUST NOT be enforced while the organization is Teams. +10. If the organization returns to Enterprise, the persisted setting MUST again be enforceable. +11. When enforced, the setting MUST block organization KiloClaw provisioning and access. ### Free Trial -1. The system MUST place organizations without an active - subscription into a free trial period. -2. The system MUST compute trial expiration from an explicit end - date when set, or fall back to the organization creation date - plus a configurable number of days (default: 14). -3. The system MUST compute days remaining by flooring the - fractional difference between the expiration timestamp and the - current time (i.e., rounding toward negative infinity, using - `floor` semantics — not truncation toward zero). The system - MUST classify trial status into the progressive trial stages - defined in the Definitions section. -4. During active, ending-soon, ending-very-soon, and expires-today - stages, the system MUST allow full functionality and MUST display - an informational banner. The banner MUST use informational - styling during the active stage, warning styling during the - ending-soon stage, and critical styling during the - ending-very-soon and expires-today stages. -5. During the soft-expired stage, the system MUST display a - dismissible blocking dialog. If the user dismisses it, the - system MUST present the interface in a read-only state with - interactive controls disabled. -6. The soft-expired read-only restriction MUST be enforced at the - UI layer only; the server MUST NOT block mutations during the - soft-expired stage. -7. During the hard-expired stage, the system MUST display a - non-dismissible blocking dialog. The user's only options MUST be +1. System MUST place organizations without an active subscription into a free trial period. +2. System MUST compute trial expiration from an explicit end date when set, or fall back to the organization creation + date plus a configurable number of days (default: 14). +3. System MUST compute days remaining by flooring the fractional difference between the expiration timestamp and the + current time (i.e., rounding toward negative infinity, using `floor` semantics — not truncation toward zero). The + system MUST classify trial status into the progressive trial stages defined in the Definitions section. +4. During active, ending-soon, ending-very-soon, and expires-today stages, system MUST allow full functionality and MUST + display an informational banner. The banner MUST use informational styling during the active stage, warning styling + during the ending-soon stage, and critical styling during the ending-very-soon and expires-today stages. +5. During the soft-expired stage, system MUST display a dismissible blocking dialog. If the user dismisses it, the + system MUST present the interface in a read-only state with interactive controls disabled. +6. The soft-expired read-only restriction MUST be enforced at the UI layer only; the server MUST NOT block mutations + during the soft-expired stage. +7. During the hard-expired stage, system MUST display a non-dismissible blocking dialog. The user's only options MUST be to upgrade or switch to a personal profile. -8. The system MUST evaluate trial expiration status on every - mutation request and MUST block server-side mutations with a - forbidden error when the trial is currently hard-expired and no - active subscription exists. -9. The system MUST exempt organizations participating in the OSS - sponsorship program (see Definitions) from trial expiration +8. System MUST evaluate trial expiration status on every mutation request and MUST block server-side mutations with a + forbidden error when the trial is currently hard-expired and no active subscription exists. +9. System MUST exempt organizations participating in the OSS sponsorship program (see Definitions) from trial expiration (never hard-locked). -10. The system MUST exempt organizations with suppressed trial - messaging (see Definitions) from trial expiration (treated as +10. System MUST exempt organizations with suppressed trial messaging (see Definitions) from trial expiration (treated as subscribed). ### Payment Processor Customer Management -1. The system MUST create at most one payment processor customer - per organization. -2. The system MUST reuse an existing payment processor customer ID - when one is already stored. -3. The system MUST handle race conditions during customer creation: - if another process sets the customer ID between the initial check - and the update, the creation MUST fail rather than overwrite. -4. The system MUST store the organization ID in the payment - processor customer's metadata. +1. System MUST create at most one payment processor customer per organization. +2. System MUST reuse an existing payment processor customer ID when one is already stored. +3. System MUST handle race conditions during customer creation: if another process sets the customer ID between the + initial check and the update, the creation MUST fail rather than overwrite. +4. System MUST store the organization ID in the payment processor customer's metadata. ### Invoices -1. The system MUST classify organization invoices as "seats" when - any line item contains seat metadata, and as "topup" otherwise. - This is a seat-detection heuristic; non-seat subscription types - that appear under an organization customer would be misclassified - as "topup". The system SHOULD alert operators when invoices are - classified as "topup" for an organization that has an active seat - subscription, to detect potential misclassification. -2. The system MUST return invoice data including: ID, number, - status, amount due, currency, creation date, hosted URL, PDF URL, - type, and description. +1. System MUST classify organization invoices as "seats" when any line item contains seat metadata, and as "topup" + otherwise. This is a seat-detection heuristic; non-seat subscription types that appear under an organization customer + would be misclassified as "topup". System SHOULD alert operators when invoices are classified as "topup" for an + organization that has an active seat subscription, to detect potential misclassification. +2. System MUST return invoice data including: ID, number, status, amount due, currency, creation date, hosted URL, PDF + URL, type, and description. ### Email Notifications -1. The system MUST send a subscription confirmation email to the - purchasing user upon initial subscription creation. -2. The system MUST send a renewal notification email to all - organization owners when the subscription renews (first event in - a new billing period). For monthly subscriptions this occurs - every month; for annual subscriptions, once per year. -3. The system MUST send a cancellation notification email to all - organization owners when the subscription ends. -4. The system MUST NOT send renewal emails for seat count changes - within the same billing period. -5. Email delivery failures MUST be logged and reported to error - tracking but MUST NOT block or roll back the subscription - operation. +1. System MUST send a subscription confirmation email to the purchasing user upon initial subscription creation. +2. System MUST send a renewal notification email to all organization owners when the subscription renews (first event in + a new billing period). For monthly subscriptions this occurs every month; for annual subscriptions, once per year. +3. System MUST send a cancellation notification email to all organization owners when the subscription ends. +4. System MUST NOT send renewal emails for seat count changes within the same billing period. +5. Email delivery failures MUST be logged and reported to error tracking but MUST NOT block or roll back the + subscription operation. ## Error Handling -1. When a subscription event has no seat line items or no seat line - item has a period end date, the system MUST reject the event with - an error. -2. When subscription metadata is missing or has invalid required - fields (type, user ID, organization ID, or non-numeric seat - value), the system MUST reject the event with a validation error. -3. When a seat count update is requested but no subscription exists, - the system MUST return a not-found error. -4. When a cancellation or stop-cancellation is requested but the - organization's trial has hard-expired (and no subscription - exists), the system MUST return a forbidden error. -5. When a new subscription checkout is attempted but the - organization already has a non-ended subscription, the system +1. When a subscription event has no seat line items or no seat line item has a period end date, system MUST reject the + event with an error. +2. When subscription metadata is missing or has invalid required fields (type, user ID, organization ID, or non-numeric + seat value), system MUST reject the event with a validation error. +3. When a seat count update is requested but no subscription exists, system MUST return a not-found error. +4. When a cancellation or stop-cancellation is requested but the organization's trial has hard-expired (and no + subscription exists), system MUST return a forbidden error. +5. When a new subscription checkout is attempted but the organization already has a non-ended subscription, the system MUST return a bad-request error. -6. When a payment for seat increase fails (e.g., card declined, - insufficient funds), the system MUST propagate the failure rather - than silently accepting the seat change. -7. When the payment processor customer creation fails, the system - MUST propagate the error without persisting a partial customer - record. -8. When a downgrade is attempted to a count lower than current seat - usage, the system MUST return an error that includes both the - current seat usage count and the requested seat count, so the - caller can understand why the downgrade was rejected. -9. When a subscription event references an organization that has - been deleted, the system MUST reject the event with an error - and log a warning. +6. When a payment for seat increase fails (e.g., card declined, insufficient funds), system MUST propagate the failure + rather than silently accepting the seat change. +7. When the payment processor customer creation fails, the system MUST propagate the error without persisting a partial + customer record. +8. When a downgrade is attempted to a count lower than current seat usage, system MUST return an error that includes + both the current seat usage count and the requested seat count, so the caller can understand why the downgrade was + rejected. +9. When a subscription event references an organization that has been deleted, system MUST reject the event with an + error and log a warning. ## Not Yet Implemented -The following rules reflect intended behavior that is not yet enforced -in the current codebase: +These intended rules are not yet enforced in the current codebase: -1. The system SHOULD record the net amount actually charged (after - discounts) rather than the gross list price for subscription - purchase records. (Currently records gross only.) +1. System SHOULD record the net amount actually charged (after discounts) rather than the gross list price for + subscription purchase records. (Currently records gross only.) ## Changelog +### 2026-05-06 -- Organization KiloClaw add-on + +- Added organization KiloClaw as a credits-funded add-on subordinate to organization entitlement, not a Stripe + seat-subscription line item. +- Added Enterprise-only KiloClaw opt-out persistence and enforcement rules aligned with other Enterprise-only settings. + ### 2026-05-05 -- Mixed subscription line-item filtering -- Updated seat line item definitions and Seat Count Updates rules 9-10 - to require filtering subscription events to seat product line items - only, excluding non-seat add-ons from seat counts and recorded seat - subscription amount. -- Updated Error Handling rule 1 to validate seat line item periods - rather than the first unfiltered line item. +- Updated seat line item definitions and Seat Count Updates rules 9-10 to require filtering subscription events to seat + product line items only, excluding non-seat add-ons from seat counts and recorded seat subscription amount. +- Updated Error Handling rule 1 to validate seat line item periods rather than the first unfiltered line item. ### 2026-03-28/29 -- Spec audit and billing compliance -- Added Definitions: non-ended subscription, active subscription, - active subscription purchase, free-seat line item, active member, - self-service creation flow, known paid seat prices, seat metadata, - subscription event, subscription metadata user, subscription - metadata type, bot user, OSS sponsorship program, suppressed trial - messaging, trial stages (with numeric boundaries). -- Fixed CRITICAL: Free Trial rule 3 contradicted itself — said - "flooring" and "(i.e., truncating toward zero)" which differ for - negative values. Clarified to "floor semantics (rounding toward - negative infinity)." Rewrote trial stage definitions to use - `daysRemaining` integer values instead of ambiguous "N days past - expiration" prose. -- Fixed Subscription Lifecycle rules 1-2: merged with explicit - precedence so rule 2 (don't re-add removed users) clearly +- Added Definitions: non-ended subscription, active subscription, active subscription purchase, free-seat line item, + active member, self-service creation flow, known paid seat prices, seat metadata, subscription event, subscription + metadata user, subscription metadata type, bot user, OSS sponsorship program, suppressed trial messaging, trial stages + (with numeric boundaries). +- Fixed CRITICAL: Free Trial rule 3 contradicted itself — said "flooring" and "(i.e., truncating toward zero)" which + differ for negative values. Clarified to "floor semantics (rounding toward negative infinity)." Rewrote trial stage + definitions to use `daysRemaining` integer values instead of ambiguous "N days past expiration" prose. +- Fixed Subscription Lifecycle rules 1-2: merged with explicit precedence so rule 2 (don't re-add removed users) clearly overrides rule 1 (add as owner if no membership). -- Documented metadata user behavior: if user ID doesn't resolve, - membership step silently fails but purchase proceeds (Lifecycle - rule 1). Documented that membership-ensure step MAY execute - outside the purchase-recording transaction. -- Upgraded Subscription Lifecycle rule 2 from SHOULD to MUST and - clarified rule 1 to resolve the conflict between ensuring user - ownership and not re-adding removed users. -- Added Subscription Lifecycle rule 4: release pending billing cycle - change schedule before cancellation. -- Added Subscription Lifecycle rule 10: block organization deletion - while a non-ended subscription exists. +- Documented metadata user behavior: if user ID doesn't resolve, membership step silently fails but purchase proceeds + (Lifecycle rule 1). Documented that membership-ensure step MAY execute outside the purchase-recording transaction. +- Upgraded Subscription Lifecycle rule 2 from SHOULD to MUST and clarified rule 1 to resolve the conflict between + ensuring user ownership and not re-adding removed users. +- Added Subscription Lifecycle rule 4: release pending billing cycle change schedule before cancellation. +- Added Subscription Lifecycle rule 10: block organization deletion while a non-ended subscription exists. - Added Seat Usage Counting rule 5: bot user exclusion. -- Added Seat Usage Counting rule 11: over-quota members after a - period-boundary downgrade MUST NOT be removed; system SHOULD warn - owners. -- Added Seat Count Modification rule 8: concurrent modification - serialization. -- Added Seat Count Updates rule 12: unrecognized billing cycle - interval defaults to monthly. -- Added Email Notifications rule 5: email failures must not block - subscription operations. +- Added Seat Usage Counting rule 11: over-quota members after a period-boundary downgrade MUST NOT be removed; system + SHOULD warn owners. +- Added Seat Count Modification rule 8: concurrent modification serialization. +- Added Seat Count Updates rule 12: unrecognized billing cycle interval defaults to monthly. +- Added Email Notifications rule 5: email failures must not block subscription operations. - Added Error Handling rule 9: reject events for deleted orgs. - Added Invoices rule 1 SHOULD for misclassification alerting. - Added Billing Cycle Changes rule 1 concurrent-request guard. -- Clarified Organization Plans rules 2, 5, 8; Seat Purchase rules 6, 8; - Seat Count Updates rules 3, 9, 11; Subscription Lifecycle rules 2, 7, 9; - Billing Cycle Changes rule 11; Free Trial rules 2-4, 8-10; - Require-Seats rule 3; Seat Count Modification rule 7; - Error Handling rule 8. -- Rewrote Seat Count Updates rule 2 as a business invariant instead - of an algorithm description. -- Made Free Trial rule 4 styling requirements explicit (replaced - "e.g." with MUST for each trial stage styling level). +- Clarified Organization Plans rules 2, 5, 8; Seat Purchase rules 6, 8; Seat Count Updates rules 3, 9, 11; Subscription + Lifecycle rules 2, 7, 9; Billing Cycle Changes rule 11; Free Trial rules 2-4, 8-10; Require-Seats rule 3; Seat Count + Modification rule 7; Error Handling rule 8. +- Rewrote Seat Count Updates rule 2 as a business invariant instead of an algorithm description. +- Made Free Trial rule 4 styling requirements explicit (replaced "e.g." with MUST for each trial stage styling level). - Added USD-only currency convention. - Updated purchase record definition: removed "database row" wording. @@ -627,27 +433,20 @@ in the current codebase: ### 2026-03-28 -- Billing cycle change hardening -- Expanded Billing Cycle Changes with rules 6-11: cancellation of - pending changes, preservation of all line items and discounts, - orphan schedule recovery, plan tier derived from Stripe price, - and schedule structure verification. -- Added Subscription Lifecycle rule 5: resubscribe preserves the - previous billing cycle. -- Added Seat Purchase and Checkout rule 8: resubscribe uses only - paid seat quantity. +- Expanded Billing Cycle Changes with rules 6-11: cancellation of pending changes, preservation of all line items and + discounts, orphan schedule recovery, plan tier derived from Stripe price, and schedule structure verification. +- Added Subscription Lifecycle rule 5: resubscribe preserves the previous billing cycle. +- Added Seat Purchase and Checkout rule 8: resubscribe uses only paid seat quantity. ### 2026-03-26 -- Monthly billing cycle option -- Rewrote Seat Pricing to separate monthly and annual prices per - plan tier. Monthly rates: Teams $18/seat, Enterprise $72/seat. - Annual rates unchanged ($180/$720). Annual equals 10 monthly - payments (12 months for the price of 10). +- Rewrote Seat Pricing to separate monthly and annual prices per plan tier. Monthly rates: Teams $18/seat, Enterprise + $72/seat. Annual rates unchanged ($180/$720). Annual equals 10 monthly payments (12 months for the price of 10). - Added billing cycle selection to Seat Purchase and Checkout. -- Added Billing Cycle Changes section: cycle switches take effect - at renewal, no proration, restricted to owners and billing - managers. -- Updated Overview and Organization Plans rule 4 to reflect that - users choose both plan tier and billing cycle at conversion. +- Added Billing Cycle Changes section: cycle switches take effect at renewal, no proration, restricted to owners and + billing managers. +- Updated Overview and Organization Plans rule 4 to reflect that users choose both plan tier and billing cycle at + conversion. - Clarified Email Notifications renewal frequency by billing cycle. ### 2026-03-26 -- Initial spec diff --git a/.specs/template.md b/.specs/template.md index 3903f567ef..c63a3942a3 100644 --- a/.specs/template.md +++ b/.specs/template.md @@ -2,13 +2,9 @@ ## Role of This Document -This spec defines the business rules and invariants for [feature]. -It is the source of truth for _what_ the system must guarantee — -valid states, ownership boundaries, correctness properties, and -user-facing behavior. It deliberately does not prescribe _how_ to -implement those guarantees: handler names, column layouts, -conflict-resolution strategies, and other implementation choices -belong in plan documents and code, not here. +This spec defines [feature] business rules and invariants: valid states, ownership boundaries, correctness properties, +and user-facing behavior. It is the source of truth for _what_ the system must guarantee, not _how_ to implement it. +Handler names, column layouts, conflict-resolution strategies, and other implementation choices belong in plans and code. ## Status @@ -16,40 +12,35 @@ Draft -- created YYYY-MM-DD. ## Conventions -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", -"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and -"OPTIONAL" in this document are to be interpreted as described in -BCP 14 [RFC 2119] [RFC 8174] when, and only when, they appear in all -capitals, as shown here. +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT +RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC 2119] [RFC 8174] +when, and only when, they appear in all capitals, as shown here. ## Definitions -- **Term**: Definition of a domain-specific term used throughout - this spec. +- **Term**: Domain-specific term definition used throughout this spec. ## Overview -A concise narrative (1-2 paragraphs) describing the feature from a -user and system perspective. Cover what the feature does, who it -serves, and the high-level lifecycle. Avoid implementation details. +Concise narrative (1-2 paragraphs) describing the feature from user and system perspectives. Cover what the feature +does, audience, and high-level lifecycle. Avoid implementation details. ## Rules ### [Section Name] -1. The system MUST ... -2. The system MUST NOT ... +1. System MUST ... +2. System MUST NOT ... ## Error Handling -1. When [error condition], the system MUST [behavior]. +1. When [error condition], system MUST [behavior]. ## Not Yet Implemented -The following rules use SHOULD and reflect intended behavior that is -not yet enforced in the current codebase: +Intended SHOULD rules not yet enforced in the current codebase: -1. The system SHOULD ... (Currently ...) +1. System SHOULD ... (Currently ...) ## Changelog diff --git a/PRODUCT.md b/PRODUCT.md new file mode 100644 index 0000000000..1dd9aae3f1 --- /dev/null +++ b/PRODUCT.md @@ -0,0 +1,57 @@ +# Product + +## Register + +product + +## Users + +Kilo Cloud serves developers, team and organization admins, and internal operators who use or manage Kilo Code across editor, command line, and cloud workflows. Users are technical, time-constrained, and often switching between code, billing, integrations, security, deployment, and agent-session management. + +Their job is to keep Kilo usage understandable and operational: manage Kilo Organizations, seats, credits, plans, model access, billing, integrations, Cloud Agents, Kilo Deploy, Code Reviews, Managed Indexing, Security Agent, Auto Triage, Kilo Autofix, App Builder, and KiloClaw infrastructure. + +## Product Purpose + +Kilo Cloud is the operational surface around Kilo Code, the open-source AI coding agent. It gives users one place to manage AI usage, model inference, Kilo Credits, paid plans, organizations, integrations, cloud-based agents, deployment automation, code review automation, security workflows, indexing, and infrastructure lifecycle tasks. + +Success means users can quickly understand what is running, what it costs, who has access, what needs attention, and what action to take next. The interface should reduce uncertainty, not decorate it. + +## Brand Personality + +Expert, direct, utilitarian, transparent, and human. Write conversationally, like talking to a fellow developer. Use first-person and second-person naturally where helpful. Be enthusiastic about what Kilo is building, but avoid hype. + +The product voice should feel like a technical teammate who has done the work: precise, candid, useful, and willing to explain complexity without talking down to the user. + +Avoid robotic, generic, or AI-generated prose. Do not start messages with filler such as “Great,” “Certainly,” “Okay,” or “Sure.” Avoid marketing jargon and hype words such as “revolutionary,” “game-changing,” and “synergy.” + +## Anti-references + +Do not look or sound like a generic SaaS dashboard, neon AI hype product, decorative glass or gradient-heavy interface, playful consumer app, corporate enterprise portal, or vague marketing site. + +Avoid these product and content patterns: + +1. Blue-primary SaaS chrome when Kilo yellow-green should carry the primary action. +2. Decorative gradients, glassmorphism, and AI-themed glow effects outside the rare agent-surface glow described in DESIGN.md. +3. Repeated same-sized card grids with icon, heading, and text. +4. Big-number hero metric sections that feel like template SaaS marketing. +5. Buzzword copy that claims value instead of showing the concrete workflow impact. +6. Ambiguous billing language. Use Kilo Credits for purchased usage credit. Reserve “token” for model input and output volume only. +7. Old naming patterns such as “Kilo For Teams,” “Kilo for Enterprise,” “Kilo for Organizations,” “Kilo Code Deploy,” “Kilo Managed Indexing,” or “Kilo Cloud Agents.” + +## Design Principles + +1. **Utility before atmosphere.** Every screen should help the user decide what is happening, what matters, and what to do next. Visual style supports operational clarity. +2. **Show the work.** Prefer concrete costs, states, timestamps, links, examples, and next actions over abstract reassurance. +3. **Treat developers as peers.** Assume intelligence, explain context when needed, define niche concepts when introduced, and avoid performative simplicity. +4. **Keep the brand action scarce.** Kilo yellow-green is the primary action and brand signal. Use it with intent so it retains meaning. +5. **Be precise with naming and billing language.** Use product names consistently: Kilo, Kilo CLI, Kilo Plans, Kilo Teams, Kilo Enterprise, Kilo Deploy, Cloud Agents, Parallel Agents, Code Reviews, App Builder, Managed Indexing, Security Agent, Auto Triage, Kilo Autofix, and iOS App. Use paid plans for Teams and Enterprise collectively. Use Kilo Organization for the place where seats and credits are managed. + +## Accessibility & Inclusion + +Target WCAG AA. Prioritize keyboard navigation, visible focus states, readable dense data, sufficient contrast, reduced-motion-safe animations, and clear error recovery. + +Use sentence case for user-visible product chrome. Avoid all caps except intentional eyebrow labels and rare role badges already defined by the design system. Avoid acronyms when possible; when acronyms are needed, expand them at least once in a conversation or document. + +For timestamps and deadlines, account for distributed teams. Prefer exact dates, relative times, and timezone references over ambiguous phrases such as “end of day.” For internal content, use ISO dates in `yyyy-mm-dd` format and months in `yyyy-mm` format. + +Use `open source` as a noun and `open-source` as an adjective before a noun. Refer to Kilo Code people as Kilo Code team members, not employees. Use “non-US” instead of “international,” “offshore,” or “overseas” when referring to team members outside the US. diff --git a/apps/web/src/app/prototype/org-kc-billing/components.tsx b/apps/web/src/app/prototype/org-kc-billing/components.tsx new file mode 100644 index 0000000000..fad45d60bf --- /dev/null +++ b/apps/web/src/app/prototype/org-kc-billing/components.tsx @@ -0,0 +1,2986 @@ +/** + * Presentational variants of every component proposed in + * `.plans/org-kiloclaw-billing-ui.md`. No tRPC, no hooks beyond local state. + * + * Each component takes a fully-resolved status / preflight / row prop so the + * prototype page can render every variant flat for design review. + */ + +'use client'; + +import { useState } from 'react'; +import Link from 'next/link'; +import { + AlertCircle, + AlertTriangle, + Ban, + ChevronLeft, + ChevronRight, + Clock, + Cpu, + CreditCard, + ExternalLink, + KeyRound, + LayoutDashboard, + Loader2, + MessageSquare, + Receipt, + Settings, + ShieldOff, + Sparkles, + Trash2, + TriangleAlert, + User, + Users, +} from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { Banner } from '@/components/shared/Banner'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Switch } from '@/components/ui/switch'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import KiloCrabIcon from '@/components/KiloCrabIcon'; +import { + type AdminBillingStatus, + type AssociatedUser, + type CreditBillingEntry, + type KiloAdminChangeLogEntry, + type KiloAdminKiloclawInstanceRow, + type KiloAdminKiloclawReadiness, + type KiloAdminKiloclawStats, + type KiloAdminOrgSupportSummary, + type KiloAdminUserSupportSummary, + type KiloClawOrgSubscriptionRow, + type MemberBillingStatus, + type OrgBillingOperationalState, + type OrgBillingStatus, + type OrgKiloclawHealthSummary, + type OrgProvisionPreflight, + formatDate, + formatMicrodollars, +} from './mock-data'; + +function pluralizeDays(n: number): string { + return n === 1 ? '1 day' : `${n} days`; +} + +// --------------------------------------------------------------------------- +// Shared chrome +// --------------------------------------------------------------------------- + +export function StatusBadge({ kind }: { kind: OrgBillingOperationalState['kind'] | 'canceled' }) { + // Status palette follows DESIGN.md and the in-repo shadcn `beta`/`new` badge + // pattern: `bg-{c}-500/10 text-{c}-400 ring-1 ring-{c}-500/20 border-transparent`. + const styles: Record
{label}
+{copy}
+Acme Inc.
+No KiloClaw subscription yet
++ Anyone in your organization can provision an instance. +
+{statusCopy}
} + + {operational.kind === 'past_due' && ( +{memberCopy.title}
+{memberCopy.body}
++ Lock dialog (modal preview, inlined) +
+{content.description}
++ Destroy confirm dialog (preview) +
++ This tears down the running instance immediately and stops the subscription at the end of + the current period ({formatDate(currentPeriodEnd)}). +
++ {preflight.trialEligible + ? `Free for 7 days, then ${formatMicrodollars(preflight.firstPeriodCostMicrodollars)} from organization credits.` + : `${formatMicrodollars(preflight.firstPeriodCostMicrodollars)} from organization credits at provisioning.`} +
+ ) : ( ++ {preflight.trialEligible + ? 'Free for 7 days, then renews from organization credits.' + : 'Renews from organization credits at provisioning.'} +
+ )} ++ Org credit balance +
++ {formatMicrodollars(preflight.orgCreditBalanceMicrodollars)} +
+{description}
+{memberHint}
} +Checking provisioning eligibility…
+Could not check eligibility
++ Try refreshing. If the problem persists, contact support. +
++ (Existing OrganizationProvidersAndModelsPage content renders here, unchanged.) +
++ KiloClaw access controls are available on Enterprise plans. Contact sales to upgrade. +
+Disable KiloClaw for this organization
++ Blocks all members from accessing or provisioning org KiloClaw instances. Existing + subscriptions remain in their current period and stop at period end. +
+| Date | +Description | +Amount | +
|---|---|---|
| {formatDate(entry.date)} | +{entry.description} | ++ {entry.amountMicrodollars === 0 + ? '—' + : formatMicrodollars(entry.amountMicrodollars)} + | +
+ Hosting subscriptions for organization KiloClaw instances. +
++ No KiloClaw subscriptions yet. Members can provision an organization KiloClaw + instance. +
+ +
+
+ Manage power state and gateway lifecycle. +
++ Org dashboard · banner above tile (Wave B + Wave A) +
++ (Existing members card rendered here, unchanged.) +
++ {label} +
+{value}
+ {hint &&{hint}
} +{title}
+{item.label}
+{item.detail}
+{row.associatedUserEmail}
++ {row.organizationPlan} · balance{' '} + {formatMicrodollars(row.orgCreditBalanceMicrodollars ?? 0)} · auto top-up{' '} + {row.orgAutoTopUpEnabled ? 'on' : 'off'} +
+ > + ) : ( + Personal context + )} +Personal KiloClaw
++ {summary.personal.activeInstanceId + ? `${summary.personal.instanceName} · ${summary.personal.subscriptionStatus}` + : 'No active personal KiloClaw instance'} +
+Organization KiloClaw
++ Org rows remain visible but personal trial/cancel controls are replaced with + org-specific support context. +
+URL: {url}
+ {description &&{description}
} +{label}
+ {caption &&{caption}
} +View as
+ ++ Design preview +
+
+ Preview every UI change planned for organization KiloClaw billing before any code
+ ships. Use the role switcher on the left to compare customer-facing{' '}
+ Admin and{' '}
+ Member surfaces, or switch to{' '}
+ Kilo Admin for the internal
+ support and launch-readiness sections from{' '}
+ .plans/org-kiloclaw-billing-admin.md.
+