diff --git a/CONTEXT.md b/CONTEXT.md index cfc3a07189..eda5988528 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -2,7 +2,7 @@ ## Scope -Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contract defines Code Reviewer, Security Agent, and Cost Insights language plus ownership boundaries used across review execution, analytics, sync, web, email, remediation, billing alerts, tests, and product documentation. +Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contract defines Code Reviewer, Security Agent, Cost Insights, and Kilo Pass for Organizations language plus ownership boundaries used across review execution, analytics, sync, web, email, remediation, billing alerts, tests, and product documentation. ## Contexts @@ -14,6 +14,7 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr | **Security Agent Email Delivery** | Dispatch-time revalidation, email rendering, owner-aware links, and Mailgun delivery | `apps/web/src/app/api/internal/security-agent/`, `apps/web/src/lib/email.ts`, `apps/web/src/emails/` | Accepts notification identity only and loads current data before sending | | **Shared Security Notification Policy** | Canonical config parsing, defaults, severity thresholds, and pure event eligibility rules | `packages/worker-utils/src/security-notification-policy.ts` | Web and Worker must use same policy contract | | **Cost Insights** | Spend evidence dashboard, Spend Alerts policy, alert history, and owner-scoped spend alerting | Billing, usage ingestion, usage analytics, and subscription-management surfaces | Applies to both personal users and organizations | +| **Kilo Pass for Organizations** | Organization-owned Kilo Pass agreements, term versions, purchased pass capacity, sub-org allocation, and pooled bonus unlock behavior | Organization billing, seats, credits, sub-orgs, and Kilo Pass org administration surfaces | Separate source of truth from personal Kilo Pass subscriptions | ## Canonical Terms @@ -49,6 +50,31 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr | **Spend threshold** | Optional configured rolling 24-hour, rolling 7-day, or rolling 30-day owner Credit-spend amount for Spend Threshold Alerts | Referring to any supported threshold window | Hard limit, budget cap, daily quota | | **Alert acknowledgment** | Authorized owner action that marks the current alert episode as reviewed | Referring to review without changing settings | Email open, page view, passive acknowledge | | **Cost Insight Event** | Durable owner-scoped record of Spend Alert notifications, reviews, configuration changes, and disablement | Referring to 90-day Cost Insights history | Raw usage row, provider log | +| **Kilo Pass for Organizations** | Organization-owned Kilo Pass product that buys pass capacity for parent-org paid seats and allocates pooled credit capacity into parent or direct child containers | Referring to org Kilo Pass agreements, purchases, allocations, and admin workflows | Personal Kilo Pass, user Kilo Pass subscription | +| **Kilo Pass org agreement** | Durable org-owned source of truth for one organization's Kilo Pass terms, version, purchase channel, and lifecycle | Preserving self-serve and manual org Kilo Pass terms | Personal subscription row, seat purchase row | +| **Kilo Pass term version** | Immutable versioned commercial and credit-grant rules attached to a Kilo Pass org agreement; standard and dedicated custom versions are allowed | Referring to legacy upfront-bonus terms, default bonus-after-base terms, or future bonus changes | Promo flag, mutable agreement fields, plan metadata | +| **Kilo Pass term transition** | Scheduled change from one immutable term version to another at agreement's commercial renewal boundary | Referring to future terms for an existing agreement | Mid-term mutation, retroactive version change | +| **Kilo Pass seat add-on item** | Recurring Stripe subscription item on parent org's seat subscription, with quantity and cadence synchronized to seat item | Referring to self-serve provider billing for Kilo Pass for Organizations | Separate Kilo Pass Stripe subscription, entitlement source of truth | +| **Purchased pass capacity** | Count of Kilo Passes the parent org has bought for eligible seats | Referring to total passes resolved across direct-child allocations and parent allocation | Assigned users, personal wallets | +| **Required pass capacity** | Purchased pass capacity required when a parent org opts into Kilo Pass for Organizations; exactly equals parent org paid seat count | Enforcing all-seat coverage for org Kilo Pass purchase or renewal | Current member count, occupied seats, independently selected quantity | +| **Kilo Pass allocation** | Integer pass capacity resolved for one Kilo Pass allocation container; direct-child allocations are explicit and parent allocation is the purchased-capacity remainder | Referring to parent or child allocation state | User assignment, unassigned capacity, personal credit balance | +| **Sub-org allocation** | A Kilo Pass allocation whose container is a direct child sub-org | Referring specifically to child allocation state | Parent allocation, descendant allocation, user assignment | +| **Parent default allocation** | Purchased pass capacity remaining after direct-child allocations, automatically allocated to parent organization | Referring to parent capacity derived from purchased capacity rather than explicitly assigned to a child | Unassigned pass capacity, unused passes, pending capacity | +| **Kilo Pass allocation plan** | Versioned set of direct-child allocations and derived parent default allocation applied at one issuance boundary; initial plan may govern first issuance and later plans are future-window effective | Referring to initial purchase distribution or pending allocation edits and their effective issuance boundary | Current-window issuance snapshot, unassigned capacity | +| **Kilo Pass org issuance snapshot** | Immutable record for one allocation container and one issuance window, including regular, bridge, or supplemental entitlement, allocated count, concrete amounts, and term version | Auditing or evaluating grants and bonus-after-base unlocks | Live allocation, live bonus terms | +| **Kilo Pass allocation container** | Parent org or direct child sub-org that can receive Kilo Pass-funded pooled credits and own a period's bonus unlock threshold | Referring to where purchased pass capacity is allocated | Billing entity, descendant org, personal wallet | +| **Overallocated Kilo Pass agreement** | Active org agreement whose future direct-child allocation total exceeds purchased pass capacity after paid seat count decreases; parent default allocation is zero until reconciled | Referring to allocation state requiring owner or billing-manager reconciliation | Invalid current-period issuance, automatic child-allocation reduction | +| **Kilo Pass pooled spend** | Cumulative actual product Credit consumption charged to one allocation container from scheduled issuance-window start, regardless of issuance record creation time or other credit sources in balance | Evaluating a snapshotted bonus-after-base threshold | Individual-user usage, Kilo Pass credit-lot depletion, balance movement | +| **Kilo Pass issuance anchor** | Agreement-specific monthly issuance schedule anchor; self-serve agreements derive it from parent seat-subscription billing anchor, while manual contracts may define another anchor such as calendar-month start | Determining issuance boundaries and allocation effective dates | Global first-of-month schedule | +| **Kilo Pass issuance window** | Agreement-relative monthly interval during which one issuance can accumulate pooled spend and unlock its snapshotted bonus | Referring to bonus eligibility lifetime | Calendar month by default, indefinite threshold, rolling multi-period threshold | +| **Kilo Pass paid-through interval** | Half-open `[paid_from, paid_until)` interval through which org agreement has paid or contractually approved entitlement | Determining scheduled issuance eligibility for self-serve and manual agreements | Inclusive end date, raw provider status alone, payment grace period | +| **Kilo Pass supplement tranche** | Immutable prorated current-window entitlement created only for paid capacity added above capacity already issued in that window | Referring to mid-window seat-increase grants and bonus progress | Rewritten original issuance, raw seat-count increase | +| **Kilo Pass org tier** | One of existing `tier_19`, `tier_49`, or `tier_199` price points selected once per org agreement and applied uniformly to every paid seat | Referring to org-wide per-seat Kilo Pass level | Per-member tier, org-only tier catalog | +| **Kilo Pass org term rules** | Dedicated immutable org rules defining concrete per-pass billing price, base-credit benefit, bonus benefit, unlock spend, and bonus mode | Referring to org agreement benefits | Personal Kilo Pass streak, welcome promo, fingerprint, referral logic, live tier config, formula document | +| **Manual legacy processing** | Agreement mode for an existing manually administered customer whose current-term grants remain operator-controlled while agreement and terms are recorded | Preserving existing current-term arrangements during migration | Missing agreement record, default automated processing | +| **Kilo Pass agreement state** | Commercial lifecycle state: `pending_payment`, `active`, `cancel_at_period_end`, or `ended` | Referring to entitlement lifecycle | Manual, blocked, overallocated | +| **Kilo Pass processing condition** | Operational condition such as manual, blocked, overallocated, or failed that does not replace commercial agreement state | Referring to processing eligibility or remediation | Subscription status, agreement state | +| **Kilo Pass processing run** | Durable attempt to process one agreement and issuance window, with state `pending`, `running`, `succeeded`, `blocked`, or `failed` | Referring to retries, replay, metrics, or blocked-window notifications | Agreement state, provider event | ## Relationships @@ -65,6 +91,73 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr - A **Security Remediation** belongs to one **Security Finding** and can have one or more **Security Remediation Attempts**. - A **Security Finding Activity Event** belongs to one Security Agent owner and one Security Finding, including after that finding is deleted. - **Spend Alerts** and **Cost Suggestions** belong to exactly one **Spend owner**: one personal user or one organization. +- **Kilo Pass for Organizations** uses a new org-owned source of truth; personal Kilo Pass subscription rows are not authoritative for org agreements. +- A **Kilo Pass org agreement** belongs to a parent organization and records a **Kilo Pass term version**. +- A parent organization has at most one non-ended **Kilo Pass org agreement**. +- **Kilo Pass agreement state** is separate from **Kilo Pass processing condition** so operational blocks do not rewrite commercial lifecycle. +- A **Kilo Pass org agreement** selects one **Kilo Pass org tier** from existing personal tier catalog; same tier applies to every paid seat. +- A **Kilo Pass term version** is immutable. Changing standard or custom commercial terms creates a new version rather than mutating an existing version. +- Each **Kilo Pass term version** contains dedicated **Kilo Pass org term rules** and does not inherit personal Kilo Pass streak, welcome-promo, fingerprint, or referral behavior. +- **Kilo Pass org term rules** snapshot per-pass billing price and base-credit benefit. Issuance does not derive benefit from live tier configuration or actual invoice totals. +- **Kilo Pass org term rules** store concrete base-credit, bonus-credit, and unlock-spend microdollars per pass. An issuance multiplies each by its snapshotted allocation count. +- Automated `upfront` bonus mode grants bonus alongside each monthly base issuance. Annual-all-upfront legacy contracts remain in manual processing for current term. +- A **Kilo Pass term transition** takes effect at commercial renewal: next monthly renewal for monthly agreements, next annual renewal for annual agreements, or explicit contractual renewal for manual agreements. +- A **Kilo Pass org tier** change takes effect only at commercial renewal boundary; current paid period and issuance snapshots retain existing tier. +- Self-serve cancellation takes effect at commercial renewal boundary. Entitlement and scheduled monthly issuances continue through paid period, granted credits remain, and no new issuance occurs after agreement ends. +- Scheduled monthly issuance requires its window to be covered by **Kilo Pass paid-through interval**. Pending cancellation remains eligible through `paid_until`; failed or unpaid renewal blocks later issuance until payment restores entitlement. +- Recognized paid invoices, including legitimate zero-due invoices, advance self-serve paid-through entitlement. Refund, dispute, or chargeback suspends future entitlement for manual review without automatic pooled-credit clawback. +- **Purchased pass capacity** belongs to parent organization; capacity allocated to direct child sub-orgs goes to those children and all remaining capacity becomes the **Parent default allocation**. +- **Required pass capacity** equals the parent organization's paid seat count, not active non-billing-manager membership count. +- A parent organization that opts into Kilo Pass for Organizations has exactly one pass per paid seat; Kilo Pass quantity is not independently selected. +- Successful paid-seat quantity changes for an organization with active Kilo Pass for Organizations automatically synchronize **Purchased pass capacity** to the new paid seat count. +- A mid-window paid-seat increase increases the **Parent default allocation** and creates a **Kilo Pass supplement tranche** for the parent only for capacity above amount already issued in current window; moving that capacity to a child remains future-window effective. +- A supplement uses authoritative remaining-service-time ratio, applies same ratio to base, bonus, and unlock spend, rounds each to nearest microdollar with round-half-up, and snapshots ratio inputs and results rather than deriving benefit from invoice total. +- For `after_base`, supplement grants prorated base and opens independent prorated threshold counting spend after supplement creation. For automated `upfront`, supplement co-grants prorated bonus. Prior spend does not unlock newly added benefit. +- Self-serve Kilo Pass for Organizations is billed through a **Kilo Pass seat add-on item** on parent org's existing seat subscription. +- **Kilo Pass seat add-on item** quantity and cadence match seat subscription item; internal **Kilo Pass org agreement** remains entitlement source of truth. +- Self-serve Kilo Pass charges and issuance schedule are co-termed with parent seat subscription's existing billing anchor. +- Confirmed paid Stripe invoice is authoritative for self-serve agreement activation and first issuance. Webhook processing is idempotent; browser return only displays or polls state. +- Initial self-serve purchase allows a parent organization owner or billing manager to record direct-child allocations before first issuance. Confirmed paid activation resolves the remaining purchased capacity to the parent organization and creates immediate issuance for the resulting parent and child allocations matching the paid service interval; absent child allocations, all capacity defaults to parent. +- Paid activation revalidates initial direct-child relationships and allocation totals against current purchased capacity. Invalid hierarchy or direct-child allocations above current capacity block first issuance for owner or billing-manager correction; the system does not silently reduce a selected child allocation. +- Parent organization members with role `owner` or `billing_manager` may purchase, cancel, view, and allocate Kilo Pass for Organizations. Regular members cannot access Kilo Pass management mechanics. +- Kilo Pass purchase, agreement status, and allocation management live on organization subscription page beside seat controls; personal Subscription Center does not manage org Kilo Pass. +- Authorized parent organization owners and billing managers see Kilo Pass terminology in management surfaces. Regular members see only generic organization Credit balances and transaction language, without Kilo Pass labels or mechanics. +- Only parent organization `owner` and `billing_manager` roles manage Kilo Pass allocations. Child sub-org owners may see resulting usable credits through existing balance surfaces but cannot access parent Kilo Pass agreement terms or allocation controls. +- A child sub-org with nonzero initial or future Kilo Pass allocation cannot detach, reparent, archive, or delete. A parent organization owner or billing manager must set future allocation to zero first; already granted pooled credits remain with child. +- A seat reduction first reduces the derived **Parent default allocation**. If direct-child allocations then exceed purchased pass capacity, the agreement becomes an **Overallocated Kilo Pass agreement**; the seat reduction remains allowed and the system does not choose which direct-child allocation loses future capacity. +- A **Kilo Pass allocation** creates pooled credit capacity for its allocation container; it is not a personal wallet and does not limit usage to specific users. +- The parent organization may act as a **Kilo Pass allocation container** when the customer wants one shared org pool; child sub-org allocations remain optional. +- Phase one allocation containers are parent org and direct child sub-orgs only; agreement owner must be top-level parent. +- **Kilo Pass allocation plan** stores integer allocations for direct child sub-orgs, derives **Parent default allocation** as purchased capacity minus their sum, rejects stale concurrent edits transactionally, and cannot newly set direct-child allocations above purchased capacity. +- Monthly base-credit processing grants Kilo Pass-funded credits only for current allocation counts in each **Kilo Pass allocation container**. +- Self-serve processing uses parent seat subscription's billing anchor as its **Kilo Pass issuance anchor**. Manual enterprise agreements may use calendar-month processing only when contract explicitly defines that anchor. +- Use actual provider period boundaries when available. Internal monthly boundaries derive from original **Kilo Pass issuance anchor** plus month index, clamped to target month end; they never advance from previously clamped boundary. +- New/default annual Kilo Pass org agreements charge annually but create monthly base-credit issuance snapshots; each monthly issuance has its own pooled bonus-after-base unlock. Current-term annual-all-upfront legacy behavior remains manual. +- Joint seat and Kilo Pass purchase creates immediate full first issuance. Mid-period enablement on monthly seats bridges to existing seat renewal. Mid-period enablement on annual seats bridges to next internal monthly anniversary. Full windows then follow agreement's **Kilo Pass issuance anchor**. +- Annual self-serve agreements create monthly issuances on subscription-date anniversaries and no more than 12 issuance windows within one 12-month paid term. Mid-term annual activation bridges only to next internal monthly anniversary, not annual renewal. +- Purchased pass capacity not allocated to direct child sub-orgs becomes **Parent default allocation** and creates spendable parent-org Kilo Pass-funded credits; no paid-but-unassigned state exists. +- The initial self-serve **Kilo Pass allocation plan** applies to first issuance. After first issuance snapshot creation, allocation-plan changes affect future scheduled processing only and do not prorate or recalculate current-period base grants or bonus unlock thresholds, except reconciliation for a blocked window without a snapshot. +- An **Overallocated Kilo Pass agreement** does not alter current-period issuance snapshots; an authorized parent organization owner or billing manager must reconcile future-cycle allocations before next scheduled processing. +- Scheduled issuance processing skips an **Overallocated Kilo Pass agreement** entirely, records a durable blocked result, notifies authorized parent organization owners and billing managers, and retries after allocation reconciliation. +- Delayed or repaired processing creates original scheduled **Kilo Pass issuance window** rather than shifting schedule or skipping paid entitlement. It counts qualifying pooled spend from scheduled window start and keeps original window end. +- Reconciled allocation may supply snapshot retroactively only for blocked, not-yet-created window. Once issuance snapshot exists, later allocation edits remain future-effective. +- Bonus-after-base evaluation uses the relevant **Kilo Pass org issuance snapshot**, not live allocation state or live bonus terms. +- Every **Kilo Pass org issuance snapshot** records the resolved terms from its agreement's immutable **Kilo Pass term version**. +- Existing legacy customers are backfilled with a **Kilo Pass org agreement**, dedicated immutable terms where needed, contractual paid-through interval, external contract identity, processing mode, and `manually_issued_through`; migration does not synthesize historical issuances or credits. +- **Manual legacy processing** does not automatically issue current-term grants; transition to automated processing requires explicit commercial renewal transition. +- Newly sold manual or enterprise agreements use automated allocation, monthly issuance, and pooled bonus processing by default; manual processing requires explicit legacy or exceptional contract designation. +- Bonus-after-base unlock compares **Kilo Pass pooled spend** with issuance's snapshotted unlock-spend threshold; default terms set threshold equal to base, but custom terms may differ. +- **Kilo Pass pooled spend** includes chargeable model, API, hosting, and other product Credit consumption. It excludes transfers, expirations, grants, refunds, reversals, and administrative adjustments. +- Canonical allocation-container spend recording atomically advances **Kilo Pass pooled spend** and grants an issuance bonus exactly once when threshold is crossed; idempotent sweep repairs missed evaluations. +- Exact organization ledger debited by request owns spend attribution. Parent membership or allocation in another container never transfers spend between containers. +- Each issuance counts **Kilo Pass pooled spend** only within its agreement-relative **Kilo Pass issuance window**. A still-locked bonus expires when next window begins; unused granted base credits may remain in the allocation-container balance. +- An unlocked org bonus grant expires at next agreement-relative issuance boundary. Expiration removes only unused bonus value; unused base credits remain in allocation-container balance. +- If repair occurs after issuance window ended, system backfills base and historical outcome but does not silently create already-expired spendable bonus. It records missed bonus for audited operator compensation and processes windows chronologically. +- One **Kilo Pass processing run** is all-or-nothing across agreement allocation containers; one container failure rolls back whole agreement/window issuance. +- Stable idempotency identities exist for provider event, activation, agreement/container/window issuance, supplement, bonus tranche, credit grant, expiry, and blocked result. +- **Kilo Pass processing runs** use leased idempotent retries, metrics, and manual replay. Blocked window shows persistent subscription-page status and sends one deduplicated email to current parent organization owners and billing managers. +- Platform admins alone create custom/manual agreements, set paid-through intervals, designate manual processing, schedule commercial transitions, issue audited compensation, or manually retry; each mutation records actor, reason, before/after values, and timestamp. +- Organization subscription page supports initial direct-child distribution before first issuance, then separates current immutable issuance snapshot from next **Kilo Pass allocation plan** and effective date; it shows purchased, parent-default, direct-child allocated, paid-through, pending-cancellation, blocked, and overallocated state. - All Credit spend charged to a **Spend owner** counts toward that owner's Spend Alerts and Cost Suggestion evaluation. - **Cost Suggestions** are enabled by default, independent from Spend Alerts, and recommend an eligible Coding Plan or Kilo Pass when observed Credit spend indicates potential cost-efficiency benefit. - Cost Suggestions are advisory. They do not guarantee savings, automatically purchase or change subscriptions, or alter spend behavior. @@ -184,6 +277,50 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr - Treat "all activity" in a **Security Agent Audit Report** as all material actions and outcomes recorded by Kilo, not every internal processing step or an attestation that legacy history is exhaustive. Exclude reads, unchanged sync observations, queue claims, heartbeats, and retries with no new finding-level outcome. - A rollout baseline event records current state at actual capture time for an existing Security Finding; it is not a synthetic creation event and must not be backdated. - Use **Cost Insights** for the user-facing surface, **Spend Alerts** for the alerting capability, and **Cost Suggestion** for optional cost-efficiency recommendations. Do not call this feature Spend Protection or Cost Controls. +- Use **Kilo Pass for Organizations** for the org-owned product. Do not describe org agreements as personal Kilo Pass subscriptions. +- Use **Kilo Pass org agreement** for the durable org-owned source of truth. Do not use seat purchase rows as the Kilo Pass source of truth. +- Use existing `tier_19`, `tier_49`, and `tier_199` identifiers for **Kilo Pass org tier**. Do not create per-member tiers or separate org tier identifiers without a later product decision. +- Use immutable **Kilo Pass term versions** for both reusable standard terms and one-off legacy/custom terms. Do not store mutable bonus policy only on the agreement. +- Keep **Kilo Pass org term rules** independent from personal Kilo Pass bonus and promotion code. Similar commercial values may be configured explicitly without sharing personal lifecycle logic. +- Record legacy agreements even when grants remain manual. Do not infer automated current-term grants or leave legacy entitlement represented only by operator notes and bonus-credit transactions. +- Treat manual purchase channel separately from processing mode. Do not make new sales-assisted agreements manually administered by default. +- Use term-version base-credit benefit for issuance. Do not let discounts, taxes, prorations, or later tier-config changes alter purchased credit entitlement. +- Use concrete microdollar amounts for org base, bonus, and unlock threshold. Do not introduce percentage rounding or flexible formula evaluation for org term versions. +- Interpret automated `upfront` as monthly base-and-bonus co-grant. Do not add annual-upfront automation solely for legacy current-term agreements. +- Use a **Kilo Pass term transition** for future version changes. Do not change an active agreement's terms during its paid commercial period. +- Schedule org-wide tier changes for commercial renewal. Do not create mid-period supplemental grants, prorations, or bonus-threshold rewrites for tier changes. +- Use **Required pass capacity** when referring to the all-seat quantity. It exactly equals paid parent-org seats, not occupied seats or an independently selected quantity. +- Treat paid-seat and purchased-pass quantities as one synchronized entitlement for active Kilo Pass org agreements. Do not permit a successful seat change to leave pass capacity below paid seat count. +- Match confirmed paid seat-increase billing with an immediate prorated supplement to the **Parent default allocation**. Grant only capacity above current-window issued capacity; do not move the supplement to a child mid-window or double-grant after decrease/reincrease. +- Use **Kilo Pass seat add-on item** for self-serve Stripe billing. Do not model self-serve org Kilo Pass as separate Stripe subscription or make Stripe item source of agreement terms and allocations. +- Do not activate self-serve agreement or grant first issuance from subscription-item presence or browser return. Require paid invoice evidence. +- Use parent seat subscription billing anchor as self-serve **Kilo Pass issuance anchor**. Do not create a global first-of-month entitlement boundary for self-serve agreements. +- Derive internal issuance boundaries from original anchor plus month index. Do not repeatedly add a month to prior boundary because short-month clamping causes permanent schedule drift. +- When a seat decrease creates overallocation, preserve current-period issuance and require owner or billing-manager reconciliation for future direct-child allocations. Do not automatically choose a child allocation to reduce. +- Do not partially issue or overgrant an **Overallocated Kilo Pass agreement**. Block entire agreement's scheduled processing until allocations are reconciled. +- Retry blocked or failed issuance against original scheduled boundaries. Do not shift agreement anchor or discard paid issuance because processing completed late. +- Use **Purchased pass capacity** and **Kilo Pass allocation** for org pass counts. Use **Sub-org allocation** only when container is direct child. Do not call these user assignments or personal wallets. +- Use **Kilo Pass allocation container** when behavior applies to either the parent org default pool or a child sub-org pool. +- Before initial self-serve issuance, allow a parent organization owner or billing manager to allocate capacity across direct child sub-orgs. Resolve all remaining purchased capacity to parent organization and use that distribution for first issuance; if no child allocation is provided, allocate all capacity to parent. +- Use existing parent organization `owner` and `billing_manager` roles for Kilo Pass management. Do not introduce a Kilo Pass-specific organization admin role. +- Place org Kilo Pass management with organization seat subscription workflow, not personal Subscription Center or general member dashboard. +- Keep parent-funded Kilo Pass allocation authority at parent organization boundary. Do not infer allocation authority from child sub-org ownership. +- Do not detach, reparent, archive, or delete child with active future allocation, and do not attempt source-specific credit clawback. Require explicit zero allocation and preserve already granted pooled credits. +- Keep Kilo Pass terminology on authorized management and audit surfaces. Do not expose pass counts, tiers, bonus rules, or Kilo Pass-funded transaction labels to regular members. +- Treat self-serve Kilo Pass for Organizations monthly processing as agreement-relative and provider-anchored. Use calendar-month processing only for manual contracts that explicitly define it. +- Treat annual cadence as payment cadence for new/default agreements, not annual upfront credit cadence. Preserve current-term annual-all-upfront legacy behavior through manual processing. +- Treat self-serve cancellation as non-renewal, not immediate entitlement removal or credit clawback. Annual agreements continue monthly issuances through prepaid annual term. +- Use **Kilo Pass paid-through interval**, not raw Stripe status alone, to determine scheduled issuance eligibility across self-serve and manual agreements. +- Match initial issuance to paid service interval: full for joint seat/Kilo Pass start, prorated bridge for mid-period enablement on existing seat subscription. +- Do not issue again at next calendar-month boundary. Next self-serve issuance occurs at next agreement-relative boundary defined by **Kilo Pass issuance anchor**. +- Use **Parent default allocation** for purchased capacity not allocated to direct child sub-orgs. Do not model or display paid capacity as unassigned, unused, or pending allocation. +- Apply initial self-serve **Kilo Pass allocation plan** to first issuance. Treat every later allocation-plan change as a next-window input except blocked-window reconciliation before snapshot; ordinary mid-window edits do not create immediate grants, prorations, reversals, or threshold rewrites. +- Use **Kilo Pass org issuance snapshot** when describing period-specific base grants and bonus unlock thresholds. Do not compute current-period bonus unlock from live allocation or live term-version changes. +- Use **Kilo Pass pooled spend** for container-level threshold progress. Do not model bonus unlock as individual usage or explicit depletion of a Kilo Pass credit lot. +- Do not advance **Kilo Pass pooled spend** for ledger movement that is not actual product consumption. +- Keep normal bonus unlock transactional with canonical spend recording and issuance-level idempotency. Do not rely on asynchronous or scheduled evaluation as primary unlock path. +- Treat bonus eligibility as period-bounded by agreement-relative **Kilo Pass issuance window**. Do not carry a locked bonus threshold into later windows or count later spend toward multiple issuance bonuses. +- Apply bonus-credit expiry at next agreement-relative issuance boundary, matching personal Kilo Pass period behavior. Do not apply same period expiry to base credits. - Do not describe a Cost Suggestion as an alert, warning, guaranteed savings, automatic optimization, or required action. - Use **Spend Threshold Alert** and **Spend threshold** for the independent rolling 24-hour, rolling 7-day, and rolling 30-day threshold windows. Do not introduce warning or critical threshold tiers. - Keep Spend Alerts alert-only. Do not describe them as spend blocks, hard limits, pauses, throttles, or request admission controls. @@ -199,6 +336,9 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr | new finding | Can mean newly created at source, first observed, inserted, updated, or reopened | For notification policy, it means first insertion into Kilo for owner | | owner | Can mean Security Agent policy owner or organization membership role | Use "Security Agent owner" for user/organization boundary and "organization owner" for role | | SLA reminder | Does not distinguish warning before deadline from breach at/after deadline | Use **SLA Warning Notification** or **SLA Breach Notification** | +| Kilo Pass subscription | Can mean current personal Kilo Pass subscription or future org Kilo Pass purchase | Use **Kilo Pass org agreement** for org-owned terms and personal Kilo Pass subscription for the existing user product | +| assigned pass | Can imply a user receives a personal credit wallet | Use **Kilo Pass allocation** for org pass distribution and **Purchased pass capacity** for bought capacity | +| admin | Can mean customer organization role or internal Kilo operator | Use "organization owner" or "billing manager" for customer roles; use "platform admin" for internal manual-commercial operations | ## Context Boundaries @@ -213,6 +353,8 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr - **Shared Security Notification Policy** defines common parsing and pure eligibility behavior; it does not perform persistence or recipient lookup. - Cross-context dispatch sends only stable notification ID from **Security Sync** to authenticated **Security Agent Email Delivery** boundary. - **Spend Alerts** evaluate personal and organization Credit spend at the owner boundary, not per product by default. +- **Kilo Pass for Organizations** owns org agreements, term versions, purchased pass capacity, allocation plans, issuance snapshots, and pooled bonus-after-base behavior. Personal Kilo Pass owns existing user subscriptions and user-global threshold behavior. +- Org seat purchases may determine eligible seat counts for Kilo Pass for Organizations, but seat purchase rows are not the Kilo Pass agreement source of truth. - Do not assume Spend Alerts apply to owners who have not opted in. - Do not make Spend Alerts block, pause, throttle, suppress auto-top-up, reject paid requests, or emit Spend Alerts-specific HTTP 402 responses. - Do not hide v1 Cost Insights behind a release-toggle gate unless a later product decision supersedes public opt-in. @@ -223,6 +365,8 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr ## Decision References - `.specs/cost-insights.md` defines Cost Insights and Spend Alerts business rules. +- `.specs/kilo-pass.md` defines current personal Kilo Pass behavior. +- `docs/adr/0003-org-kilo-pass-source-of-truth.md` records org-owned agreement, provider-anchored issuance, allocation, bonus, migration, and operational decisions. - `.plans/code-review-analytics.md` defines prospective Review Analytics collection, taxonomy, persistence, and metric semantics. - `.specs/security-agent.md` defines Security Agent Auto Remediation and notification guarantees. - `.plans/security-agent-notifications.md` records notification implementation and rollout design. diff --git a/apps/storybook/.storybook/preview.ts b/apps/storybook/.storybook/preview.ts index 8b28d391bb..5fbd379c51 100644 --- a/apps/storybook/.storybook/preview.ts +++ b/apps/storybook/.storybook/preview.ts @@ -49,6 +49,18 @@ const withProductionFonts: Decorator = Story => { return Story(); }; +const withStorybookViewMode: Decorator = (Story, context) => { + if (typeof document !== 'undefined') { + if (context.viewMode === 'docs') { + document.body.dataset.storybookViewMode = 'docs'; + } else { + delete document.body.dataset.storybookViewMode; + } + } + + return Story(); +}; + const preview: Preview = { parameters: { layout: 'fullscreen', @@ -93,7 +105,13 @@ const preview: Preview = { }, }, }, - decorators: [withProductionFonts, withTRPC, withQueryClient, withSessionProvider], + decorators: [ + withStorybookViewMode, + withProductionFonts, + withTRPC, + withQueryClient, + withSessionProvider, + ], }; export default preview; diff --git a/apps/storybook/.storybook/storybook.css b/apps/storybook/.storybook/storybook.css index 513d598eae..088ff13714 100644 --- a/apps/storybook/.storybook/storybook.css +++ b/apps/storybook/.storybook/storybook.css @@ -32,6 +32,20 @@ body, color: var(--foreground); } +body[data-storybook-view-mode='docs'] .storybook-org-app-shell { + position: relative; + min-height: 37rem; +} + +body[data-storybook-view-mode='docs'] .storybook-org-app-shell-content { + min-height: 0; +} + +body[data-storybook-view-mode='docs'] .storybook-org-app-shell [data-slot='sidebar-container'] { + position: absolute; + height: auto; +} + #storybook-docs .sbdocs.sbdocs-wrapper, #storybook-docs .sbdocs.sbdocs-content { background: var(--surface-background) !important; @@ -64,8 +78,23 @@ body, color: var(--foreground) !important; } +#storybook-docs .sbdocs table:not(.docblock-argstable table) { + width: 100%; + table-layout: fixed; +} + +#storybook-docs .sbdocs table:not(.docblock-argstable table) th { + width: 33.333%; + vertical-align: bottom; + color: var(--foreground) !important; +} + +#storybook-docs .sbdocs table:not(.docblock-argstable table) td { + vertical-align: top; + color: var(--muted-foreground) !important; +} + #storybook-docs .sbdocs a, -#storybook-docs .sbdocs button:not([role='switch']), #storybook-docs .sbdocs label { color: var(--muted-foreground) !important; } diff --git a/apps/storybook/stories/organizations/subscriptions/OrgKiloPass.stories.tsx b/apps/storybook/stories/organizations/subscriptions/OrgKiloPass.stories.tsx new file mode 100644 index 0000000000..6e0e922f6a --- /dev/null +++ b/apps/storybook/stories/organizations/subscriptions/OrgKiloPass.stories.tsx @@ -0,0 +1,1088 @@ +import { useState } from 'react'; +import type { Meta, StoryObj } from '@storybook/nextjs'; +import { expect, fireEvent, userEvent, within } from 'storybook/test'; +import { + OrgKiloPassActivationView, + OrgKiloPassCheckoutReviewView, + OrgKiloPassDetailView, + OrgKiloPassGroupStateView, + OrgKiloPassSetupView, + OrgKiloPassSubscriptionCenterView, + type OrgKiloPassAllocation, + type OrgKiloPassTier, +} from '@/components/subscriptions/OrgKiloPassViews'; +import { + currentAllocations, + customTerms, + nextAllocations, + OrganizationAppPreview, + organizationId, + organizationName, + overallocatedAllocations, + standardTerms, + storyContext, + StoryPage, + supplementedAllocations, + upfrontAllocations, + updateChildAllocation, +} from './OrgKiloPassStoryFixtures'; + +const meta = { + title: 'Kilo Pass for Orgs/Customer Journey', + tags: ['autodocs'], + parameters: { + layout: 'fullscreen', + nextjs: { + appDirectory: true, + navigation: { + pathname: `/organizations/${organizationId}/subscriptions`, + query: {}, + }, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +function SetupStory({ + initial = nextAllocations, + validationMessage, + cadence = 'annual', +}: { + initial?: OrgKiloPassAllocation[]; + validationMessage?: string; + cadence?: 'monthly' | 'annual'; +}) { + const [selectedTier, setSelectedTier] = useState('tier_49'); + const [allocations, setAllocations] = useState(initial); + const quote = + cadence === 'annual' + ? { + recurringTotal: '$70,560/year', + firstCharge: '$4,417.32', + firstServiceInterval: 'Jul 10 – Aug 1, 2026', + firstIssuance: '$3,682.19', + } + : { + recurringTotal: '$5,880/month', + firstCharge: '$1,470.00', + firstServiceInterval: 'Jul 10 – Aug 1, 2026', + firstIssuance: '$1,470.00', + }; + + return ( + + + + setAllocations(current => updateChildAllocation(current, id, passCount)) + } + onReviewPurchase={() => undefined} + /> + + + ); +} + +function DistributionEditorStory({ + initial, + condition, + stalePlanMessage, +}: { + initial: OrgKiloPassAllocation[]; + condition?: Parameters[0]['condition']; + stalePlanMessage?: string; +}) { + const [allocations, setAllocations] = useState(initial); + + return ( + + + + setAllocations(current => updateChildAllocation(current, id, passCount)) + } + onSaveDistribution={() => undefined} + /> + + + ); +} + +export const AvailableInSubscriptionCenter: Story = { + parameters: storyContext({ + seeing: 'Kilo Pass for Organizations is available as an add-on for all 120 paid seats.', + how: 'Northstar Labs has an active seat subscription but has not added Kilo Pass yet.', + next: 'Select Add Kilo Pass to choose a tier and decide where the passes go.', + }), + render: () => ( + + + + ), + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + await expect(canvas.getByText('Available add-on')).toBeVisible(); + await expect(canvas.getByText('Kilo Pass for Organizations')).toBeVisible(); + await expect(canvas.getByText('From')).toBeVisible(); + await expect(canvas.getByText('$19')).toBeVisible(); + await expect(canvas.getByText('per paid seat/month, billed with seats')).toBeVisible(); + await expect(canvas.getByText('Get monthly Credits for all 120 paid seats')).toBeVisible(); + await expect( + canvas.getByText('Unlock bonus Credits as your organization uses Kilo') + ).toBeVisible(); + await expect(canvas.getByRole('link', { name: /add kilo pass/i })).toBeVisible(); + }, +}; + +export const ActiveInSubscriptionCenter: Story = { + parameters: storyContext({ + seeing: 'Northstar Labs has an active Pro add-on covering all 120 paid seats.', + how: 'Payment was confirmed and Kilo Pass for Organizations is active through Jul 1, 2027.', + next: 'Open Kilo Pass to view Credits or manage future pass assignments.', + }), + render: () => ( + + + + ), +}; + +export const CancellationScheduledInSubscriptionCenter: Story = { + parameters: storyContext({ + seeing: 'The Pro add-on is scheduled to end on Jul 1, 2027.', + how: 'A billing manager canceled the subscription at the end of its current billing period.', + next: 'Monthly Credits continue until the end date; existing Credits remain available.', + }), + render: () => ( + + + + ), +}; + +export const BlockedProcessingInSubscriptionCenter: Story = { + parameters: storyContext({ + seeing: "Next month's Credits are paused because too many passes are assigned.", + how: 'The paid-seat total fell below the number of passes assigned to child organizations.', + next: 'Reduce child organization pass assignments so monthly Credits can resume.', + }), + render: () => ( + + + + ), +}; + +export const FailedProcessingInSubscriptionCenter: Story = { + parameters: storyContext({ + seeing: 'Monthly Credits are delayed after Kilo could not add them.', + how: 'A temporary system error stopped the monthly Credit update before any Credits were added.', + next: 'Kilo retries automatically; no customer action is needed.', + }), + render: () => ( + + + + ), +}; + +export const AwaitingPaymentInSubscriptionCenter: Story = { + parameters: storyContext({ + seeing: 'The Pro add-on is waiting for payment.', + how: 'Checkout finished, but the invoice has not been paid or confirmed yet.', + next: 'Complete payment to start Kilo Pass and receive the first Credits.', + }), + render: () => ( + + + + ), +}; + +export const EndedAgreementInSubscriptionCenter: Story = { + parameters: storyContext({ + seeing: 'The Pro add-on ended on Jul 1, 2026.', + how: 'The subscription reached its scheduled end date.', + next: 'No new monthly Credits will be added; add Kilo Pass again to restart service.', + }), + render: () => ( + + + + ), +}; + +export const SubscriptionGroupLoading: Story = { + parameters: storyContext({ + seeing: 'The Kilo Pass section is loading.', + how: 'Storybook is showing the period while subscription details are being retrieved.', + next: 'The section is replaced with subscription details when loading finishes.', + }), + render: () => ( + + + + ), +}; + +export const SubscriptionGroupError: Story = { + parameters: storyContext({ + seeing: 'Kilo Pass subscription details could not be loaded.', + how: 'The request for subscription details failed.', + next: 'Retry loading the page.', + }), + render: () => ( + + + + ), +}; + +export const ChildOrganizationIsIneligible: Story = { + parameters: storyContext({ + seeing: 'A child organization can see Kilo Pass but cannot manage it.', + how: 'Northstar Labs owns the seat subscription and controls pass assignments for its children.', + next: 'Switch to Northstar Labs to manage Kilo Pass for Organizations.', + }), + render: () => ( + + + + ), +}; + +export const InitialSetupAndDistribution: Story = { + parameters: storyContext({ + seeing: 'Northstar Labs is setting up an annual Pro add-on for 120 paid seats.', + how: 'The organization selected Kilo Pass from its available subscription add-ons.', + next: 'Choose a tier, assign passes, and review the purchase.', + }), + render: () => , +}; + +export const MonthlySetup: Story = { + parameters: storyContext({ + seeing: 'Northstar Labs is setting up a monthly Pro add-on for 120 paid seats.', + how: 'The seat subscription uses monthly billing and Kilo Pass follows the same schedule.', + next: 'Choose a tier, assign passes, and review the purchase.', + }), + render: () => , +}; + +export const SetupUpdatesTierAndParentRemainder: Story = { + parameters: storyContext({ + seeing: 'Changing tier and child assignments updates the setup before purchase.', + how: 'Passes not assigned to child organizations automatically stay with Northstar Labs.', + next: 'Review the recalculated total and remaining passes before purchase.', + }), + render: () => , + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + await userEvent.click(canvas.getByRole('button', { name: /expert/i })); + await expect(canvas.getByRole('button', { name: /expert/i })).toHaveAttribute( + 'aria-pressed', + 'true' + ); + + const productAllocation = canvas.getByRole('spinbutton', { + name: 'Product & Engineering passes', + }); + fireEvent.change(productAllocation, { target: { value: '45' } }); + + await expect(canvas.getByLabelText(`${organizationName} passes`)).toHaveValue('55'); + }, +}; + +export const SetupWithoutChildOrganizations: Story = { + parameters: storyContext({ + seeing: 'All 120 passes stay with Northstar Labs.', + how: 'This organization has no child organizations eligible to receive passes.', + next: 'Review the selected tier and purchase details.', + }), + render: () => ( + + ), +}; + +export const SetupValidationError: Story = { + parameters: storyContext({ + seeing: 'Child organizations have been assigned more than the 120 available passes.', + how: 'Pass assignments were entered that exceed the paid-seat total.', + next: 'Remove excess passes before reviewing the purchase.', + }), + render: () => ( + ({ + ...allocation, + passCount: index === 1 ? 100 : allocation.passCount, + }))} + /> + ), +}; + +export const SetupReconcilesOverallocationBeforeReview: Story = { + parameters: storyContext({ + seeing: 'Purchase review stays disabled until pass assignments fit the available total.', + how: 'The setup currently assigns more passes to child organizations than paid seats allow.', + next: 'Reduce a child assignment to enable purchase review.', + }), + render: () => ( + ({ + ...allocation, + passCount: index === 1 ? 100 : allocation.passCount, + }))} + /> + ), + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const reviewPurchase = canvas.getByRole('button', { name: 'Review purchase' }); + + await expect(reviewPurchase).toBeDisabled(); + const productAllocation = canvas.getByRole('spinbutton', { + name: 'Product & Engineering passes', + }); + fireEvent.change(productAllocation, { target: { value: '80' } }); + await expect(reviewPurchase).toBeEnabled(); + }, +}; + +export const SetupHierarchyChanged: Story = { + parameters: storyContext({ + seeing: 'A saved pass assignment points to an organization that is no longer a child.', + how: 'The organization hierarchy changed while setup was in progress.', + next: 'Remove the old assignment, then review the purchase again.', + }), + render: () => ( + + ), +}; + +export const PurchaseReviewWithAnnualBridge: Story = { + parameters: storyContext({ + seeing: 'Northstar Labs is reviewing an annual Pro purchase and its first partial charge.', + how: 'Kilo Pass is being added between annual seat billing dates, so the first charge covers only Jul 10 through Aug 1.', + next: 'Confirm the purchase to continue to payment.', + }), + render: () => ( + + + + + + ), +}; + +export const PurchaseReviewWithMonthlyCadence: Story = { + parameters: storyContext({ + seeing: 'Northstar Labs is reviewing a monthly Pro purchase.', + how: 'Kilo Pass follows the monthly seat billing schedule beginning Aug 1.', + next: 'Confirm the purchase to continue to payment.', + }), + render: () => ( + + + + + + ), +}; + +export const PurchaseReviewLoading: Story = { + parameters: storyContext({ + seeing: 'The purchase is being submitted.', + how: 'A billing manager confirmed the order and Kilo is creating checkout.', + next: 'Wait for checkout to finish; the submit action remains disabled meanwhile.', + }), + render: () => ( + + + + + + ), +}; + +export const AwaitingPaidInvoice: Story = { + parameters: storyContext({ + seeing: 'Checkout is complete and Kilo is waiting for payment confirmation.', + how: 'An invoice was created but payment has not been confirmed.', + next: 'Kilo Pass starts after payment is received.', + }), + render: () => ( + + ), +}; + +export const CreatingFirstIssuance: Story = { + parameters: storyContext({ + seeing: 'Payment is confirmed and the first Credits are being added.', + how: 'Kilo is using the pass assignments chosen during checkout.', + next: 'Wait for activation to finish.', + }), + render: () => ( + + ), +}; + +export const PaymentRequiresAction: Story = { + parameters: storyContext({ + seeing: 'Kilo could not complete the payment.', + how: 'The payment provider requires the billing manager to confirm the payment method.', + next: 'Return to billing and complete the requested payment step.', + }), + render: () => ( + + ), +}; + +export const FirstIssuanceBlocked: Story = { + parameters: storyContext({ + seeing: 'Payment succeeded, but the first Credits cannot be added yet.', + how: 'Paid seats or child organizations changed during checkout, making the saved assignments invalid.', + next: 'Update pass assignments before Kilo tries again.', + }), + render: () => ( + + ), +}; + +export const ActivationSucceeded: Story = { + parameters: storyContext({ + seeing: 'Kilo Pass for Organizations is active and the first Credits were added.', + how: 'Payment was confirmed and every saved pass assignment was valid.', + next: 'Open Kilo Pass to view Credits and future assignments.', + }), + render: () => ( + + ), +}; + +export const ActiveAgreement: Story = { + parameters: storyContext({ + seeing: 'An active annual Pro subscription with current Credits and future pass assignments.', + how: 'Northstar Labs completed payment and assigned passes across itself and two child organizations.', + next: 'Review current Credit progress or edit assignments for next month.', + }), + render: () => ( + + + + + + ), +}; + +export const PendingPaymentAgreement: Story = { + parameters: storyContext({ + seeing: 'The subscription exists but its first payment is still pending.', + how: 'Checkout created the annual Pro subscription and invoice, but payment is not confirmed.', + next: 'Pay the invoice to start the first monthly Credit period.', + }), + render: () => ( + + + + + + ), +}; + +export const EndedAgreement: Story = { + parameters: storyContext({ + seeing: 'The annual Pro subscription has ended.', + how: 'It reached its Jul 1, 2026 end date after the final monthly Credit period.', + next: 'No new Credits are scheduled; start a new subscription to resume them.', + }), + render: () => ( + + + + + + ), +}; + +export const ParentOnlyDistribution: Story = { + parameters: storyContext({ + seeing: 'Northstar Labs keeps all 120 passes and monthly Credits.', + how: 'No passes are assigned to child organizations for the current or next month.', + next: 'Edit future assignments if child organizations should receive passes.', + }), + render: () => ( + + + + + + ), +}; + +export const CustomUpfrontTerms: Story = { + parameters: storyContext({ + seeing: 'A custom Expert subscription is active with manually added Credits.', + how: 'Northstar Labs has older custom terms that remain manual until its next renewal.', + next: 'Automatic monthly Credits begin when the subscription renews.', + }), + render: () => ( + + + + + + ), +}; + +export const ParentSupplementAfterSeatIncrease: Story = { + parameters: storyContext({ + seeing: 'Five new passes and their Credits were added to Northstar Labs this month.', + how: 'The paid-seat count increased from 120 to 125 after the monthly Credits were first added.', + next: 'Update next month’s child assignments if the new passes should move elsewhere.', + }), + render: () => ( + + + + + + ), +}; + +export const ExpiredAndMissedBonusOutcomes: Story = { + parameters: storyContext({ + seeing: 'Child organizations ended the month with different bonus Credit results.', + how: 'Each organization used a different amount of its monthly Credits before the period ended.', + next: 'Review usage and adjust future pass assignments if needed.', + }), + render: () => ( + + + ({ + ...allocation, + bonusState: index === 0 ? 'expired' : index === 1 ? 'missed' : 'unlocked', + }))} + nextWindowStarts="Jul 1, 2026" + nextAllocations={nextAllocations} + /> + + + ), +}; + +export const EditNextDistribution: Story = { + parameters: storyContext({ + seeing: 'A billing manager is editing pass assignments for next month.', + how: 'The active subscription already has Credits for this month, so changes apply only from Aug 1.', + next: 'Save the new assignments.', + }), + render: () => , +}; + +export const SeatDecreaseNeedsReconciliation: Story = { + parameters: storyContext({ + seeing: 'Child organizations have 110 assigned passes, but only 100 will be available.', + how: 'The paid-seat count decreased after next month’s assignments were saved.', + next: 'Remove 10 child organization passes before Aug 1.', + }), + render: () => ( + + ), +}; + +export const StaleDistributionEdit: Story = { + parameters: storyContext({ + seeing: 'The pass assignments on screen are no longer current.', + how: 'Another billing manager or process changed the assignments while this page was open.', + next: 'Refresh the latest assignments before making and saving changes.', + }), + render: () => ( + + ), +}; + +export const CancellationScheduled: Story = { + parameters: storyContext({ + seeing: 'The annual Pro subscription is scheduled to end on Jul 1, 2027.', + how: 'A billing manager scheduled cancellation at the end of the paid billing period.', + next: 'Monthly Credits continue through the end date; existing Credits remain available.', + }), + render: () => ( + + + + + + ), +}; + +export const TierChangeScheduled: Story = { + parameters: storyContext({ + seeing: 'The active Pro tier is scheduled to change to Expert on Jul 1, 2027.', + how: 'A billing manager selected a different tier for the next renewal.', + next: 'Pro terms remain active until the change date.', + }), + render: () => ( + + + + + + ), +}; + +export const PaymentUnderReview: Story = { + parameters: storyContext({ + seeing: 'New monthly Credits are paused while a payment is reviewed.', + how: 'A previously confirmed payment was reversed.', + next: 'Check billing; Credits already added remain available during the review.', + }), + render: () => ( + + + + + + ), +}; + +export const FailedProcessing: Story = { + parameters: storyContext({ + seeing: 'Monthly Credits are delayed after a system error.', + how: 'The update stopped before any Credits were added.', + next: 'Kilo retries automatically; no customer action is needed.', + }), + render: () => ( + + + + + + ), +}; + +export const BlockedIssuance: Story = { + parameters: storyContext({ + seeing: 'No monthly Credits were added because assignments exceed the 100 available passes.', + how: 'A seat decrease left child organizations with more assigned passes than the new total.', + next: 'Reduce child organization assignments, then Kilo will try again.', + }), + render: () => ( + + + + + + ), +}; + +export const MobileSetup: Story = { + parameters: storyContext({ + seeing: 'The annual Pro setup flow at a mobile viewport.', + how: 'This is the same 120-seat setup shown on a narrow screen to verify responsive behavior.', + next: 'Choose a tier, assign passes, and review the purchase.', + }), + render: () => , + globals: { viewport: { value: 'mobile2', isRotated: false } }, +}; + +export const MobileActiveAgreement: Story = { + ...ActiveAgreement, + parameters: storyContext({ + seeing: 'The active annual Pro subscription at a mobile viewport.', + how: 'This is the same active subscription shown on a narrow screen to verify responsive behavior.', + next: 'Review current Credits or edit future pass assignments.', + }), + globals: { viewport: { value: 'mobile2', isRotated: false } }, +}; diff --git a/apps/storybook/stories/organizations/subscriptions/OrgKiloPassOperations.stories.tsx b/apps/storybook/stories/organizations/subscriptions/OrgKiloPassOperations.stories.tsx new file mode 100644 index 0000000000..e0aaaeaea7 --- /dev/null +++ b/apps/storybook/stories/organizations/subscriptions/OrgKiloPassOperations.stories.tsx @@ -0,0 +1,554 @@ +import type { Meta, StoryObj } from '@storybook/nextjs'; +import { + OrgKiloPassAdminView, + OrgKiloPassBillingView, + OrgKiloPassHierarchyGuardView, + OrgKiloPassMemberCreditView, + OrgKiloPassSeatChangeView, + OrgKiloPassStatusReferenceView, +} from '@/components/subscriptions/OrgKiloPassViews'; +import { + customTerms, + OrganizationAppPreview, + organizationId, + standardTerms, + storyContext, + StoryPage, +} from './OrgKiloPassStoryFixtures'; + +const meta = { + title: 'Kilo Pass for Orgs/Operations and Access', + tags: ['autodocs'], + parameters: { + layout: 'fullscreen', + nextjs: { + appDirectory: true, + navigation: { + pathname: `/organizations/${organizationId}/subscriptions/kilo-pass`, + query: {}, + }, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const SeatIncrease: Story = { + parameters: storyContext({ + seeing: 'The seat count is increasing from 120 to 125, adding five Kilo Passes.', + how: 'A billing manager added five paid seats during the current monthly Credit period.', + next: 'Confirm the increase; child organization assignment changes become available Aug 1.', + }), + render: () => ( + + + + + + ), +}; + +export const SeatDecreaseWithoutConflict: Story = { + parameters: storyContext({ + seeing: 'The seat count is decreasing from 120 to 115 without an assignment conflict.', + how: 'Child organizations use only 50 passes, so their assignments fit the new total.', + next: 'Confirm the decrease; the new pass total applies next month.', + }), + render: () => ( + + + + + + ), +}; + +export const SeatDecreaseWithReconciliation: Story = { + parameters: storyContext({ + seeing: 'The seat count is decreasing to 100 while child organizations still have 110 passes.', + how: 'The saved child assignments exceed the new paid-seat total by 10.', + next: 'Remove 10 child organization passes before Aug 1.', + }), + render: () => ( + + + + + + ), +}; + +export const BillingCadenceChange: Story = { + parameters: storyContext({ + seeing: 'Seat and Kilo Pass billing are changing from monthly to annual.', + how: 'A billing manager scheduled a new billing schedule for Aug 1 without changing seat count.', + next: 'Confirm the change; monthly Credits continue on the same schedule.', + }), + render: () => ( + + + + + + ), +}; + +export const CancellationImpact: Story = { + parameters: storyContext({ + seeing: 'The seat subscription and Kilo Pass are being canceled for Jul 1, 2027.', + how: 'A billing manager chose to end the shared subscription after the paid billing period.', + next: 'Confirm cancellation; monthly Credits continue until the end date.', + }), + render: () => ( + + + + + + ), +}; + +export const MixedSeatAndKiloPassInvoices: Story = { + parameters: storyContext({ + seeing: + 'Billing history contains combined seat charges, a first Kilo Pass charge, and a refund.', + how: 'Kilo Pass was added to an existing seat subscription and a prior payment was refunded.', + next: 'Open an invoice when full billing details are needed.', + }), + render: () => ( + + + + + + ), +}; + +export const EmptyBillingHistory: Story = { + parameters: storyContext({ + seeing: 'The organization has no Kilo Pass invoices yet.', + how: 'Billing has not started or no invoice has been created for this subscription.', + next: 'Charges appear here after billing begins.', + }), + render: () => ( + + + + + + ), +}; + +export const MemberSeesGenericCredits: Story = { + parameters: storyContext({ + seeing: 'An organization member sees the shared Credit balance and recent activity.', + how: 'The member can use Credits but does not have access to Kilo Pass subscription details.', + next: 'Use Credits for model activity or review recent transactions.', + }), + render: () => ( + + + + + + ), +}; + +export const MemberSeesEmptyCreditActivity: Story = { + parameters: storyContext({ + seeing: 'An organization member sees a zero balance and no Credit activity.', + how: 'No Credits have been added to or used by this organization yet.', + next: 'Activity appears after Credits are added or used.', + }), + render: () => ( + + + + + + ), +}; + +export const ChildDetachBlocked: Story = { + parameters: storyContext({ + seeing: 'Product & Engineering cannot be removed from the organization group yet.', + how: 'It still has 30 passes assigned for next month.', + next: 'Remove its pass assignment, then remove the child organization.', + }), + render: () => ( + + + + + + ), +}; + +export const ChildReparentBlocked: Story = { + parameters: storyContext({ + seeing: 'Product & Engineering cannot move to another parent organization yet.', + how: 'It still has 30 passes assigned by Northstar Labs for next month.', + next: 'Remove its pass assignment, then move the child organization.', + }), + render: () => ( + + + + + + ), +}; + +export const ChildArchiveBlocked: Story = { + parameters: storyContext({ + seeing: 'Customer Operations cannot be archived yet.', + how: 'It still has 20 passes assigned for next month.', + next: 'Remove its pass assignment, then archive the child organization.', + }), + render: () => ( + + + + + + ), +}; + +export const ChildDeleteBlocked: Story = { + parameters: storyContext({ + seeing: 'Customer Operations cannot be deleted yet.', + how: 'It still has 20 passes assigned for next month.', + next: 'Remove its pass assignment, then delete the child organization.', + }), + render: () => ( + + + + + + ), +}; + +export const AdminAutomatedAgreement: Story = { + parameters: storyContext({ + seeing: 'An active agreement using automatic monthly Credit processing.', + how: 'Standard terms are active and the latest Jul 1 processing run succeeded.', + next: 'Monitor the next scheduled monthly run.', + }), + render: () => ( + + + + ), +}; + +export const AdminPendingPaymentAgreement: Story = { + parameters: storyContext({ + seeing: 'An agreement waiting for its first confirmed payment.', + how: 'Automatic processing is configured, but no paid service period has started.', + next: 'Resolve payment before the first Credit run can begin.', + }), + render: () => ( + + + + ), +}; + +export const AdminCancellationScheduledAgreement: Story = { + parameters: storyContext({ + seeing: 'An active agreement scheduled to end after its current billing period.', + how: 'Cancellation was scheduled after a successful Jul 1 monthly run.', + next: 'Allow remaining paid-period runs, then confirm the agreement ends.', + }), + render: () => ( + + + + ), +}; + +export const AdminEndedAgreement: Story = { + parameters: storyContext({ + seeing: 'An ended agreement with a successful final processing run.', + how: 'The agreement ended after the Jun 1 through Jun 30 service period.', + next: 'Keep the record for audit; create a new agreement if service resumes.', + }), + render: () => ( + + + + ), +}; + +export const AdminBlockedRun: Story = { + parameters: storyContext({ + seeing: 'The Aug 1 Credit run is blocked before any Credits are added.', + how: 'Direct-child pass assignments exceed purchased capacity, and customer contacts were notified.', + next: 'Correct the assignments, then retry the blocked run.', + }), + render: () => ( + + + + ), +}; + +export const AdminPendingProcessingRun: Story = { + parameters: storyContext({ + seeing: 'The Aug 1 monthly processing run is queued but has not started.', + how: 'The run was created for the next service period and remains pending.', + next: 'Wait for processing or investigate if it remains pending too long.', + }), + render: () => ( + + + + ), +}; + +export const AdminRunningProcessingRun: Story = { + parameters: storyContext({ + seeing: 'The Aug 1 monthly processing run is in progress.', + how: 'A worker started adding Credits for the service period.', + next: 'Wait for completion, then verify the final run status.', + }), + render: () => ( + + + + ), +}; + +export const AdminFailedProcessingRun: Story = { + parameters: storyContext({ + seeing: 'The Aug 1 monthly processing run failed without adding Credits.', + how: 'The run rolled back after an error, preserving the original service period for retry.', + next: 'Fix the failure cause, then retry the run.', + }), + render: () => ( + + + + ), +}; + +export const AdminPaymentReview: Story = { + parameters: storyContext({ + seeing: 'Future Credit processing is paused while a payment is reviewed.', + how: 'A confirmed payment was reversed; Credits already added were left in place.', + next: 'Resolve the payment review before resuming processing.', + }), + render: () => ( + + + + ), +}; + +export const AdminCustomManualAgreement: Story = { + parameters: storyContext({ + seeing: 'An active custom agreement still using manual Credit processing.', + how: 'Older contract terms require operators to add Credits manually through Dec 31, 2026.', + next: 'Continue manual processing or move the agreement to automation at renewal.', + }), + render: () => ( + + + + ), +}; + +export const StatusReference: Story = { + parameters: storyContext({ + seeing: 'A reference for agreement, processing, and Credit result statuses.', + how: 'The table documents every state used by Kilo Pass for Organizations operations.', + next: 'Use it when diagnosing an agreement or monthly processing run.', + }), + render: () => ( + + + + ), +}; diff --git a/apps/storybook/stories/organizations/subscriptions/OrgKiloPassStoryFixtures.tsx b/apps/storybook/stories/organizations/subscriptions/OrgKiloPassStoryFixtures.tsx new file mode 100644 index 0000000000..fdf5262b45 --- /dev/null +++ b/apps/storybook/stories/organizations/subscriptions/OrgKiloPassStoryFixtures.tsx @@ -0,0 +1,298 @@ +import type { ReactNode } from 'react'; +import { + Activity, + Bot, + Building, + ChartColumnIncreasing, + Cloud, + CreditCard, + Key, + Layers, + List, + Shield, + Sliders, + Webhook, +} from 'lucide-react'; +import HeaderLogo from '@/components/HeaderLogo'; +import { AppShellSkipLink } from '@/components/AppShellSkipLink'; +import { SetPageTitle } from '@/components/SetPageTitle'; +import { PageTitleProvider } from '@/contexts/PageTitleContext'; +import { AppTopbar } from '@/app/(app)/components/AppTopbar'; +import { OrganizationSwitcherView } from '@/app/(app)/components/OrganizationSwitcher'; +import SidebarMenuList from '@/app/(app)/components/SidebarMenuList'; +import SidebarUserFooter from '@/app/(app)/components/SidebarUserFooter'; +import { + Sidebar, + SidebarContent, + SidebarHeader, + SidebarInset, + SidebarProvider, + SidebarRail, +} from '@/components/ui/sidebar'; +import type { + OrgKiloPassAllocation, + OrgKiloPassTerms, +} from '@/components/subscriptions/OrgKiloPassViews'; + +export const organizationId = 'org-northstar'; +export const organizationName = 'Northstar Labs'; + +export function storyContext({ seeing, how, next }: { seeing: string; how: string; next: string }) { + return { + docs: { + description: { + story: `| What you are seeing | How this happened | What happens next | +| --- | --- | --- | +| ${seeing} | ${how} | ${next} |`, + }, + }, + }; +} + +export const standardTerms: OrgKiloPassTerms = { + tier: 'tier_49', + tierName: 'Pro', + pricePerPassUsd: 49, + baseCreditsPerPassUsd: 49, + bonusCreditsPerPassUsd: 12, + unlockSpendPerPassUsd: 49, + bonusMode: 'after_base', +}; + +export const customTerms: OrgKiloPassTerms = { + tier: 'tier_199', + tierName: 'Expert', + pricePerPassUsd: 165, + baseCreditsPerPassUsd: 185, + bonusCreditsPerPassUsd: 55, + unlockSpendPerPassUsd: 170, + bonusMode: 'upfront', + isCustom: true, +}; + +export const nextAllocations: OrgKiloPassAllocation[] = [ + { + organizationId, + organizationName, + kind: 'parent', + passCount: 70, + }, + { + organizationId: 'org-product', + organizationName: 'Product & Engineering', + kind: 'child', + passCount: 30, + }, + { + organizationId: 'org-customer', + organizationName: 'Customer Operations', + kind: 'child', + passCount: 20, + }, +]; + +export const currentAllocations: OrgKiloPassAllocation[] = [ + { + organizationId, + organizationName, + kind: 'parent', + passCount: 70, + baseCreditsUsd: 3430, + qualifyingSpendUsd: 3430, + unlockTargetUsd: 3430, + bonusCreditsUsd: 840, + bonusState: 'unlocked', + }, + { + organizationId: 'org-product', + organizationName: 'Product & Engineering', + kind: 'child', + passCount: 30, + baseCreditsUsd: 1470, + qualifyingSpendUsd: 1127, + unlockTargetUsd: 1470, + bonusCreditsUsd: 360, + bonusState: 'locked', + }, + { + organizationId: 'org-customer', + organizationName: 'Customer Operations', + kind: 'child', + passCount: 20, + baseCreditsUsd: 980, + qualifyingSpendUsd: 412, + unlockTargetUsd: 980, + bonusCreditsUsd: 240, + bonusState: 'locked', + }, +]; + +export const supplementedAllocations: OrgKiloPassAllocation[] = [ + { + ...currentAllocations[0], + passCount: 75, + supplementCreditsUsd: 122.5, + unlockTargetUsd: 3552.5, + qualifyingSpendUsd: 3476, + }, + currentAllocations[1], + currentAllocations[2], +]; + +export const upfrontAllocations: OrgKiloPassAllocation[] = currentAllocations.map(allocation => ({ + ...allocation, + baseCreditsUsd: allocation.passCount * customTerms.baseCreditsPerPassUsd, + bonusCreditsUsd: allocation.passCount * customTerms.bonusCreditsPerPassUsd, + qualifyingSpendUsd: 0, + unlockTargetUsd: 0, + bonusState: 'upfront_granted', +})); + +export const overallocatedAllocations: OrgKiloPassAllocation[] = [ + { + organizationId, + organizationName, + kind: 'parent', + passCount: 0, + }, + { + organizationId: 'org-product', + organizationName: 'Product & Engineering', + kind: 'child', + passCount: 70, + }, + { + organizationId: 'org-customer', + organizationName: 'Customer Operations', + kind: 'child', + passCount: 40, + }, +]; + +export function updateChildAllocation( + allocations: OrgKiloPassAllocation[], + organizationIdToUpdate: string, + passCount: number +) { + return allocations.map(allocation => + allocation.organizationId === organizationIdToUpdate ? { ...allocation, passCount } : allocation + ); +} + +export function StoryPage({ children }: { children: ReactNode }) { + return
{children}
; +} + +export function OrganizationAppPreview({ + pageTitle, + children, + organizationRole = 'owner', +}: { + pageTitle: string; + children: ReactNode; + organizationRole?: 'owner' | 'billing_manager' | 'member'; +}) { + const dashboardItems = [ + { title: 'Organization', icon: Building, url: `/organizations/${organizationId}` }, + { + title: 'Usage', + icon: ChartColumnIncreasing, + url: `/organizations/${organizationId}/usage-details`, + }, + ]; + const cloudItems = [ + { title: 'Cloud Agent', icon: Cloud, url: `/organizations/${organizationId}/cloud` }, + { title: 'Sessions', icon: List, url: `/organizations/${organizationId}/cloud/sessions` }, + { + title: 'Webhooks / Triggers', + icon: Webhook, + url: `/organizations/${organizationId}/cloud/triggers`, + }, + { title: 'Code Reviewer', icon: Bot, url: `/organizations/${organizationId}/code-reviews` }, + { + title: 'Security Agent', + icon: Shield, + url: `/organizations/${organizationId}/security-agent`, + }, + ]; + const accountItems = [ + ...(organizationRole === 'member' + ? [] + : [ + { + title: 'Subscriptions', + icon: CreditCard, + url: `/organizations/${organizationId}/subscriptions`, + }, + ]), + { + title: 'Model Access', + icon: Layers, + url: `/organizations/${organizationId}/providers-and-models`, + }, + { + title: 'Custom Modes', + icon: Sliders, + url: `/organizations/${organizationId}/custom-modes`, + }, + { + title: 'Audit Logs', + icon: Activity, + url: `/organizations/${organizationId}/audit-logs`, + }, + { + title: 'Invoices', + icon: CreditCard, + url: `/organizations/${organizationId}/payment-details`, + }, + { + title: 'Bring Your Own Key (BYOK)', + icon: Key, + url: `/organizations/${organizationId}/byok`, + }, + ]; + const allUrls = [...dashboardItems, ...cloudItems, ...accountItems].map(item => item.url); + + return ( + + + +
+ + +
+ + undefined} + /> +
+
+ + + + + + + +
+ + + +
+ {children} +
+
+
+
+
+ ); +} diff --git a/apps/web/src/app/(app)/components/OrganizationSwitcher.tsx b/apps/web/src/app/(app)/components/OrganizationSwitcher.tsx index ca3150b6b3..8da4c204e1 100644 --- a/apps/web/src/app/(app)/components/OrganizationSwitcher.tsx +++ b/apps/web/src/app/(app)/components/OrganizationSwitcher.tsx @@ -92,6 +92,8 @@ export function OrganizationSwitcherView({ switch (role) { case 'owner': return 'Owner'; + case 'billing_manager': + return 'Billing Manager'; case 'member': return 'Member'; default: diff --git a/apps/web/src/components/organizations/UpgradeTrialDialog.tsx b/apps/web/src/components/organizations/UpgradeTrialDialog.tsx index 1fa96f4ad5..ab1a538f31 100644 --- a/apps/web/src/components/organizations/UpgradeTrialDialog.tsx +++ b/apps/web/src/components/organizations/UpgradeTrialDialog.tsx @@ -239,19 +239,20 @@ export function UpgradeTrialDialog({ {/* Credit Options */}

- Credits are not included with your subscription. Use what works best for your team no - matter what plan you choose. + Credits are not included with seat subscriptions. Choose the credit setup that fits + your team.

{/* Kilo Pass */}
-

Kilo Pass

+

Kilo Pass for Organizations

- Credit subscription with bonus credits.{' '} + Monthly Credits and bonus Credits for every paid seat.{' '} Learn more @@ -268,6 +269,7 @@ export function UpgradeTrialDialog({ href="https://app.kilo.ai/credits" target="_blank" rel="noopener noreferrer" + aria-label="Learn more about pay-as-you-go Credits" className="text-blue-400 hover:underline" > Learn more @@ -284,6 +286,7 @@ export function UpgradeTrialDialog({ href="https://kilo.ai/docs/getting-started/byok" target="_blank" rel="noopener noreferrer" + aria-label="Learn more about Bring Your Own Key" className="text-blue-400 hover:underline" > Learn more diff --git a/apps/web/src/components/subscriptions/AvailableProductCard.tsx b/apps/web/src/components/subscriptions/AvailableProductCard.tsx index f5e7791509..96b3da9f42 100644 --- a/apps/web/src/components/subscriptions/AvailableProductCard.tsx +++ b/apps/web/src/components/subscriptions/AvailableProductCard.tsx @@ -19,7 +19,7 @@ export function AvailableProductCard({ }: { icon: ReactNode; title: string; - price: { amount: string; cadenceLabel: string }; + price: { amount: string; cadenceLabel: string; qualifier?: string }; status?: string; features?: readonly string[]; featureLayout?: 'responsive' | 'single'; @@ -71,7 +71,10 @@ export function AvailableProductCard({ ) : null}

-
+
+ {price.qualifier ? ( + {price.qualifier} + ) : null} {price.amount} {price.cadenceLabel}
diff --git a/apps/web/src/components/subscriptions/OrgKiloPassViews.tsx b/apps/web/src/components/subscriptions/OrgKiloPassViews.tsx new file mode 100644 index 0000000000..d12419391b --- /dev/null +++ b/apps/web/src/components/subscriptions/OrgKiloPassViews.tsx @@ -0,0 +1,1483 @@ +'use client'; + +import { + AlertCircle, + AlertTriangle, + ArrowRight, + Building2, + CalendarDays, + Check, + CheckCircle2, + Coins, + FileClock, + GitBranch, + History, + Loader2, + LockKeyhole, + Play, + ReceiptText, + RefreshCw, + Settings2, + ShieldAlert, + Users, +} from 'lucide-react'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Progress } from '@/components/ui/progress'; +import { Label } from '@/components/ui/label'; +import { PageLayout } from '@/components/PageLayout'; +import { AvailableProductCard } from '@/components/subscriptions/AvailableProductCard'; +import { DetailPageHeader } from '@/components/subscriptions/DetailPageHeader'; +import { SubscriptionCard } from '@/components/subscriptions/SubscriptionCard'; +import { SubscriptionGroup } from '@/components/subscriptions/SubscriptionGroup'; +import { SubscriptionStatusBadge } from '@/components/subscriptions/SubscriptionStatusBadge'; +import { cn } from '@/lib/utils'; + +export type OrgKiloPassTier = 'tier_19' | 'tier_49' | 'tier_199'; +export type OrgKiloPassCadence = 'monthly' | 'annual'; +export type OrgKiloPassCommercialState = + | 'pending_payment' + | 'active' + | 'cancel_at_period_end' + | 'ended'; +export type OrgKiloPassBonusState = + | 'locked' + | 'unlocked' + | 'upfront_granted' + | 'expired' + | 'missed'; + +export type OrgKiloPassTerms = { + tier: OrgKiloPassTier; + tierName: string; + pricePerPassUsd: number; + baseCreditsPerPassUsd: number; + bonusCreditsPerPassUsd: number; + unlockSpendPerPassUsd: number; + bonusMode: 'after_base' | 'upfront'; + isCustom?: boolean; +}; + +export type OrgKiloPassAllocation = { + organizationId: string; + organizationName: string; + kind: 'parent' | 'child'; + passCount: number; + baseCreditsUsd?: number; + supplementCreditsUsd?: number; + qualifyingSpendUsd?: number; + unlockTargetUsd?: number; + bonusCreditsUsd?: number; + bonusState?: OrgKiloPassBonusState; +}; + +export type OrgKiloPassCondition = { + kind: 'manual' | 'blocked' | 'overallocated' | 'failed' | 'payment_review'; + title: string; + description: string; + actionLabel?: string; +}; + +export type OrgKiloPassInvoice = { + id: string; + date: string; + description: string; + amount: string; + status: 'Paid' | 'Open' | 'Refunded'; +}; + +const STANDARD_TERMS: Record = { + tier_19: { + tier: 'tier_19', + tierName: 'Starter', + pricePerPassUsd: 19, + baseCreditsPerPassUsd: 19, + bonusCreditsPerPassUsd: 4, + unlockSpendPerPassUsd: 19, + bonusMode: 'after_base', + }, + tier_49: { + tier: 'tier_49', + tierName: 'Pro', + pricePerPassUsd: 49, + baseCreditsPerPassUsd: 49, + bonusCreditsPerPassUsd: 12, + unlockSpendPerPassUsd: 49, + bonusMode: 'after_base', + }, + tier_199: { + tier: 'tier_199', + tierName: 'Expert', + pricePerPassUsd: 199, + baseCreditsPerPassUsd: 199, + bonusCreditsPerPassUsd: 50, + unlockSpendPerPassUsd: 199, + bonusMode: 'after_base', + }, +}; + +function formatUsd(value: number): string { + return value.toLocaleString('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 0, + maximumFractionDigits: 2, + }); +} + +function getDirectChildTotal(allocations: OrgKiloPassAllocation[]): number { + return allocations.reduce( + (sum, allocation) => sum + (allocation.kind === 'child' ? allocation.passCount : 0), + 0 + ); +} + +function statusLabel(status: OrgKiloPassCommercialState): string { + switch (status) { + case 'pending_payment': + return 'Awaiting payment'; + case 'cancel_at_period_end': + return 'Cancellation scheduled'; + case 'ended': + return 'Ended'; + case 'active': + return 'Active'; + } +} + +function billingDateLabel(status: OrgKiloPassCommercialState): string { + switch (status) { + case 'active': + return 'Covered through'; + case 'pending_payment': + return 'Payment'; + case 'cancel_at_period_end': + return 'Ends'; + case 'ended': + return 'Ended'; + } +} + +function conditionVariant(condition: OrgKiloPassCondition) { + return condition.kind === 'failed' || condition.kind === 'payment_review' + ? ('destructive' as const) + : ('warning' as const); +} + +export function OrgKiloPassSubscriptionCenterView({ + organizationId, + organizationName, + seatsUsed, + paidSeats, + seatPrice, + renewalDate, + agreement, + eligibilityMessage, +}: { + organizationId: string; + organizationName: string; + seatsUsed: number; + paidSeats: number; + seatPrice: string; + renewalDate: string; + agreement?: { + status: OrgKiloPassCommercialState; + tierName: string; + price: string; + paidThrough: string; + condition?: OrgKiloPassCondition; + }; + eligibilityMessage?: string; +}) { + return ( + + } + > +
+ } + title="Teams / Enterprise Seats" + subtitle={`${seatsUsed} of ${paidSeats} seats in use`} + status="active" + price={seatPrice} + billingDate={renewalDate} + paymentMethod="Stripe" + href={`/organizations/${organizationId}/subscriptions/seats`} + /> + +
+
+ +

+ {agreement || eligibilityMessage ? 'Add-on' : 'Available add-on'} +

+
+ {eligibilityMessage ? ( + + + Switch organizations to manage Kilo Pass for Organizations + {eligibilityMessage} + + ) : agreement ? ( +
+ {agreement.condition ? : null} + } + title="Kilo Pass for Organizations" + subtitle={`${agreement.tierName} add-on · ${paidSeats} paid seats covered`} + status={agreement.status} + price={agreement.price} + billingDate={agreement.paidThrough} + billingDateLabel={billingDateLabel(agreement.status)} + paymentMethod="Stripe" + href={`/organizations/${organizationId}/subscriptions/kilo-pass`} + isTerminal={agreement.status === 'ended'} + warningTone={ + agreement.status === 'pending_payment' || + agreement.status === 'cancel_at_period_end' || + agreement.condition + ? 'warning' + : undefined + } + /> +
+ ) : ( + } + title="Kilo Pass for Organizations" + price={{ + qualifier: 'From', + amount: '$19', + cadenceLabel: 'per paid seat/month, billed with seats', + }} + status="Available" + features={[ + `Get monthly Credits for all ${paidSeats} paid seats`, + 'Choose the monthly Credit amount that fits your usage', + 'Unlock bonus Credits as your organization uses Kilo', + 'See seat and Kilo Pass charges on one invoice', + ]} + cta={{ + label: 'Add Kilo Pass', + href: `/organizations/${organizationId}/subscriptions/kilo-pass/setup`, + }} + /> + )} +
+
+
+
+ ); +} + +export function OrgKiloPassSetupView({ + organizationId, + organizationName, + paidSeats, + cadence, + renewalDate, + selectedTier, + terms = Object.values(STANDARD_TERMS), + allocations, + quote, + validationMessage, + onTierChange, + onChildAllocationChange, + onReviewPurchase, +}: { + organizationId: string; + organizationName: string; + paidSeats: number; + cadence: OrgKiloPassCadence; + renewalDate: string; + selectedTier: OrgKiloPassTier; + terms?: OrgKiloPassTerms[]; + allocations: OrgKiloPassAllocation[]; + quote: { + recurringTotal: string; + firstCharge: string; + firstServiceInterval: string; + firstIssuance: string; + }; + validationMessage?: string; + onTierChange: (tier: OrgKiloPassTier) => void; + onChildAllocationChange: (organizationId: string, passCount: number) => void; + onReviewPurchase: () => void; +}) { + const selectedTerms = terms.find(option => option.tier === selectedTier) ?? terms[0]; + const directChildTotal = getDirectChildTotal(allocations); + const parentPasses = Math.max(0, paidSeats - directChildTotal); + const isInvalid = directChildTotal > paidSeats || Boolean(validationMessage); + + return ( +
+ } + /> + + + + Coverage + + +
+ } /> + } /> + } + /> +
+

+ Kilo Pass for Organizations includes one pass for every paid seat in {organizationName}. + It is billed on the same schedule as your seat subscription. +

+
+
+ + + + Choose one tier for all paid seats + + + {terms.map(option => { + const selected = option.tier === selectedTier; + return ( + + ); + })} + + + + + +
+
+ Choose where your first Credits go +

+ Assign passes to child organizations. Unassigned passes stay with {organizationName} + . +

+
+ + {paidSeats} passes total + +
+
+ + + + {isInvalid ? ( + + + Review pass assignments + + {validationMessage ?? + `Remove ${directChildTotal - paidSeats} passes from child organizations.`} + + + ) : null} + +
+ + + + +
+ +
+ +
+
+
+
+ ); +} + +export function OrgKiloPassCheckoutReviewView({ + organizationId, + organizationName, + terms, + paidSeats, + cadence, + allocations, + quote, + bridgeExplanation, + isSubmitting = false, +}: { + organizationId: string; + organizationName: string; + terms: OrgKiloPassTerms; + paidSeats: number; + cadence: OrgKiloPassCadence; + allocations: OrgKiloPassAllocation[]; + quote: { + firstCharge: string; + recurringTotal: string; + renewsOn: string; + }; + bridgeExplanation: string; + isSubmitting?: boolean; +}) { + const directChildTotal = getDirectChildTotal(allocations); + const parentPasses = paidSeats - directChildTotal; + + return ( +
+ } + /> +
+
+ + + Plan details + + + + + + + + + + + Where your first Credits go + + + + + + + + Your first Credits follow your seat billing date + {bridgeExplanation} + +
+ + + Order summary + + +
+ + +

Renews {quote.renewsOn}

+
+

+ After payment is confirmed, we check these pass assignments and add your first + Credits. +

+ +
+
+
+
+ ); +} + +export function OrgKiloPassActivationView({ + state, + title, + description, + actionLabel, +}: { + state: 'awaiting_payment' | 'requires_action' | 'activating' | 'blocked' | 'succeeded'; + title: string; + description: string; + actionLabel?: string; +}) { + const isLoading = state === 'awaiting_payment' || state === 'activating'; + const isSuccess = state === 'succeeded'; + const isBlocked = state === 'blocked' || state === 'requires_action'; + const Icon = isLoading ? Loader2 : isSuccess ? CheckCircle2 : AlertTriangle; + + return ( +
+ + + + + +

{title}

+

{description}

+ {isLoading ? ( +

+ This usually completes within 30 seconds. You can leave this page safely. +

+ ) : null} + {actionLabel ? : null} +
+
+
+ ); +} + +export function OrgKiloPassDetailView({ + organizationId, + organizationName, + commercialState, + condition, + terms, + totalPasses, + cadence, + paidThrough, + currentWindow, + currentAllocations, + nextWindowStarts, + nextAllocations, + pendingTransition, + cancellationEffectiveAt, + isEditing = false, + stalePlanMessage, + onChildAllocationChange, + onSaveDistribution, +}: { + organizationId: string; + organizationName: string; + commercialState: OrgKiloPassCommercialState; + condition?: OrgKiloPassCondition; + terms: OrgKiloPassTerms; + totalPasses: number; + cadence: OrgKiloPassCadence; + paidThrough: string; + currentWindow: string; + currentAllocations: OrgKiloPassAllocation[]; + nextWindowStarts: string; + nextAllocations: OrgKiloPassAllocation[]; + pendingTransition?: { tierName: string; effectiveAt: string }; + cancellationEffectiveAt?: string; + isEditing?: boolean; + stalePlanMessage?: string; + onChildAllocationChange?: (organizationId: string, passCount: number) => void; + onSaveDistribution?: () => void; +}) { + const directChildTotal = getDirectChildTotal(nextAllocations); + const parentPasses = Math.max(0, totalPasses - directChildTotal); + const reductionRequired = Math.max(0, directChildTotal - totalPasses); + const isOverallocated = reductionRequired > 0 || condition?.kind === 'overallocated'; + const hasCurrentIssuance = commercialState !== 'pending_payment'; + const hasFutureDistribution = commercialState !== 'ended'; + + return ( +
+ } + actions={} + /> + + {condition ? : null} + {cancellationEffectiveAt ? ( + + + Cancellation scheduled + + Kilo Pass for Organizations stays active through {cancellationEffectiveAt}. Monthly + Credits continue until then, and Credits already added remain available. + + + ) : null} + {pendingTransition ? ( + + + Tier change scheduled + + {pendingTransition.tierName} starts when your subscription renews on{' '} + {pendingTransition.effectiveAt}. Your current Credits and bonus rules stay the same + until then. + + + ) : null} + + + + } + /> + } /> + } + /> + } /> + + + + {hasCurrentIssuance ? ( + + +
+
+ Current monthly Credit period +

{currentWindow}

+
+ Final for this period +
+
+ + +

+ Credits already added for this monthly period stay with the organizations that + received them. +

+
+
+ ) : ( + + + Payment pending + + +

+ Kilo will add your first Credits after payment is confirmed. +

+
+
+ )} + + {hasFutureDistribution ? ( + + +
+
+ Next pass assignments +

Starts: {nextWindowStarts}

+
+ + {directChildTotal} for child organizations · {parentPasses} for {organizationName} + +
+
+ + {isEditing && onChildAllocationChange ? ( + + ) : ( + + )} + + {stalePlanMessage ? ( + + + Pass assignments were changed elsewhere + {stalePlanMessage} + + ) : null} + +
+

+ Changes apply next month. Current Credits and usage stay the same. +

+ {isEditing && onSaveDistribution ? ( + + ) : ( + + )} +
+
+
+ ) : null} +
+ ); +} + +export function OrgKiloPassSeatChangeView({ + kind, + currentSeats, + newSeats, + details, + requiresReconciliation = false, +}: { + kind: 'increase' | 'decrease' | 'cadence' | 'cancel'; + currentSeats: number; + newSeats: number; + details: Array<{ label: string; value: string }>; + requiresReconciliation?: boolean; +}) { + const copy = { + increase: { + title: 'Increase paid seats', + description: + 'Kilo Pass for Organizations adds one pass for each new paid seat. Credits for those passes are added for the rest of this monthly period.', + action: 'Confirm seat increase', + }, + decrease: { + title: 'Decrease paid seats', + description: + 'Kilo Pass for Organizations removes one pass for each removed paid seat. Credits already added stay the same, and next month uses the new pass total.', + action: 'Confirm seat decrease', + }, + cadence: { + title: 'Change billing schedule', + description: 'Your seats and Kilo Pass for Organizations will use the same billing schedule.', + action: 'Schedule billing change', + }, + cancel: { + title: 'Cancel seat subscription', + description: + 'Your seats and Kilo Pass for Organizations will end after your current billing period.', + action: 'Schedule cancellation', + }, + }[kind]; + + return ( + + + {copy.title} +

{copy.description}

+
+ + {kind === 'increase' || kind === 'decrease' ? ( +
+ + + +
+ ) : null} + {requiresReconciliation ? ( + + + Update pass assignments before next month's Credits + + Pass assignments to child organizations exceed the new total. The seat decrease will + still complete, but monthly Credits will pause until you update the pass assignments. + + + ) : null} +
+ {details.map(detail => ( + + ))} +
+
+ + +
+
+
+ ); +} + +export function OrgKiloPassBillingView({ invoices }: { invoices: OrgKiloPassInvoice[] }) { + return ( + + + + + Billing history + + + + {invoices.length === 0 ? ( +

+ No invoices yet. Seat and Kilo Pass for Organizations charges will appear here after + billing begins. +

+ ) : ( +
+ {invoices.map(invoice => ( +
+ {invoice.date} +
+

{invoice.description}

+

{invoice.id}

+
+ + {invoice.amount} + + + {invoice.status} + +
+ ))} +
+ )} +

+ Seat and Kilo Pass for Organizations charges appear on the same invoice. +

+
+
+ ); +} + +export function OrgKiloPassHierarchyGuardView({ + organizationName, + allocatedPasses, + action, +}: { + organizationName: string; + allocatedPasses: number; + action: 'detach' | 'reparent' | 'archive' | 'delete'; +}) { + const actionCopy = { + detach: { + description: 'remove it from this organization group', + label: 'Remove from organization group', + }, + reparent: { + description: 'move it to another parent organization', + label: 'Move to another parent', + }, + archive: { description: 'archive it', label: 'Archive organization' }, + delete: { description: 'delete it', label: 'Delete organization' }, + }[action]; + + return ( + + + + + Child organization + + + +
+
+

{organizationName}

+

+ {allocatedPasses} passes assigned for next month's Credits +

+
+ + Pass assignment must be removed first + +
+ + + + Remove this organization's pass assignment before you {actionCopy.description} + + + Remove its pass assignment first. Credits already added to this organization remain + available. + + +
+ + +
+
+
+ ); +} + +export function OrgKiloPassMemberCreditView({ + organizationName, + balance, + transactions, +}: { + organizationName: string; + balance: string; + transactions: Array<{ date: string; description: string; amount: string }>; +}) { + return ( +
+ + + Credit balance + + +

{balance}

+

Available to {organizationName}

+
+
+ + + Recent Credit activity + + + {transactions.length === 0 ? ( +

+ No Credit activity yet. +

+ ) : ( +
+ {transactions.map(transaction => ( +
+ {transaction.date} + {transaction.description} + + {transaction.amount} + +
+ ))} +
+ )} +
+
+
+ ); +} + +export function OrgKiloPassAdminView({ + terms, + state, + condition, + processingMode, + latestRun, +}: { + terms: OrgKiloPassTerms; + state: OrgKiloPassCommercialState; + condition?: OrgKiloPassCondition; + processingMode: 'automated' | 'manual_legacy'; + latestRun: { window: string; status: 'pending' | 'running' | 'succeeded' | 'blocked' | 'failed' }; +}) { + return ( +
+ + +
+
+ Kilo Pass agreement +

Northstar Labs · org-northstar

+
+
+ + {processingMode.replace('_', ' ')} +
+
+
+ + {condition ? : null} +
+ + + + +
+
+
+
+

Latest processing run

+

{latestRun.window}

+
+
+ + {latestRun.status} + + +
+
+
+
+ + + + +
+
+
+ + + Required audit details + + +
+ + +
+ + +
+
+
+ ); +} + +export function OrgKiloPassStatusReferenceView() { + const commercialStates: OrgKiloPassCommercialState[] = [ + 'pending_payment', + 'active', + 'cancel_at_period_end', + 'ended', + ]; + const runStates = ['pending', 'running', 'succeeded', 'blocked', 'failed']; + + return ( +
+ + + Commercial states + + + {commercialStates.map(state => ( +
+ {statusLabel(state)} + +
+ ))} +
+
+ + + Processing states + + + {runStates.map(state => ( +
+ {state} + + {state} + +
+ ))} +
+
+
+ ); +} + +export function OrgKiloPassGroupStateView({ state }: { state: 'loading' | 'error' }) { + return ( + + } + isLoading={state === 'loading'} + isError={state === 'error'} + error={ + state === 'error' + ? new Error("Kilo Pass for Organizations status couldn't be loaded. Try again.") + : undefined + } + onRetry={() => undefined} + > +
+ + + ); +} + +function ConditionAlert({ condition }: { condition: OrgKiloPassCondition }) { + return ( + + {condition.kind === 'failed' || condition.kind === 'payment_review' ? ( + + ) : ( + + )} + {condition.title} + +

{condition.description}

+ {condition.actionLabel ? ( + + ) : null} +
+
+ ); +} + +function SummaryValue({ + label, + value, + detail, + icon, +}: { + label: string; + value: string; + detail?: string; + icon: React.ReactNode; +}) { + return ( +
+
+ {icon} +
+
+

{label}

+

{value}

+ {detail ?

{detail}

: null} +
+
+ ); +} + +function QuoteValue({ label, value }: { label: string; value: string }) { + return ( +
+

{label}

+

{value}

+
+ ); +} + +function DetailValue({ label, value }: { label: string; value: string }) { + return ( +
+

{label}

+

{value}

+
+ ); +} + +function PriceRow({ + label, + value, + emphasized = false, +}: { + label: string; + value: string; + emphasized?: boolean; +}) { + return ( +
+ + {label} + + {value} +
+ ); +} + +function AllocationEditor({ + parentName, + parentPasses, + allocations, + isInvalid = false, + onChildAllocationChange, +}: { + parentName: string; + parentPasses: number; + allocations: OrgKiloPassAllocation[]; + isInvalid?: boolean; + onChildAllocationChange: (organizationId: string, passCount: number) => void; +}) { + const childAllocations = allocations.filter(allocation => allocation.kind === 'child'); + + return ( +
+
+ Organization + Passes +
+ } + name={parentName} + detail={`${parentName} gets unassigned passes`} + > + + + {childAllocations.map(allocation => ( + } + name={allocation.organizationName} + detail="Child organization" + > + { + const nextValue = Number(event.target.value); + onChildAllocationChange( + allocation.organizationId, + Number.isInteger(nextValue) && nextValue >= 0 ? nextValue : 0 + ); + }} + aria-invalid={isInvalid} + className="w-24 text-right tabular-nums" + /> + + ))} +
+ ); +} + +function AllocationSummary({ + parentName, + parentPasses, + allocations, +}: { + parentName: string; + parentPasses: number; + allocations: OrgKiloPassAllocation[]; +}) { + const childAllocations = allocations.filter(allocation => allocation.kind === 'child'); + + return ( +
+
+ Organization + Passes +
+ } + name={parentName} + detail={`${parentName} gets unassigned passes`} + > + {parentPasses} + + {childAllocations.map(allocation => ( + } + name={allocation.organizationName} + detail="Child organization" + > + {allocation.passCount} + + ))} +
+ ); +} + +function AllocationIdentityRow({ + icon, + name, + detail, + children, +}: { + icon: React.ReactNode; + name: string; + detail: string; + children: React.ReactNode; +}) { + return ( +
+
+ + {icon} + + + {name} + {detail} + +
+
{children}
+
+ ); +} + +function CurrentAllocationTable({ allocations }: { allocations: OrgKiloPassAllocation[] }) { + return ( +
+ {allocations.map(allocation => { + const spend = allocation.qualifyingSpendUsd ?? 0; + const unlockTarget = allocation.unlockTargetUsd ?? 0; + const progress = + unlockTarget > 0 ? Math.min(100, Math.round((spend / unlockTarget) * 100)) : 100; + const state = allocation.bonusState ?? 'locked'; + const stateCopy = { + locked: `${formatUsd(spend)} of ${formatUsd(unlockTarget)} spent`, + unlocked: 'Bonus unlocked', + upfront_granted: 'Bonus already added', + expired: 'Bonus can no longer be earned', + missed: "We're checking your bonus", + }[state]; + const isPositive = state === 'unlocked' || state === 'upfront_granted'; + + return ( +
+
+
+ + {allocation.kind === 'parent' ? ( + + ) : ( + + )} + +
+

{allocation.organizationName}

+

+ {allocation.passCount} passes · {formatUsd(allocation.baseCreditsUsd ?? 0)}{' '} + monthly + {' Credits'} + {allocation.supplementCreditsUsd + ? ` + ${formatUsd(allocation.supplementCreditsUsd)} additional Credits` + : ''} +

+
+
+
+
+ + {stateCopy} + + + {formatUsd(allocation.bonusCreditsUsd ?? 0)} bonus Credits + +
+ +
+
+
+ ); + })} +
+ ); +} diff --git a/apps/web/src/components/subscriptions/SubscriptionStatusBadge.tsx b/apps/web/src/components/subscriptions/SubscriptionStatusBadge.tsx index 0debe0d2f1..80df3cd683 100644 --- a/apps/web/src/components/subscriptions/SubscriptionStatusBadge.tsx +++ b/apps/web/src/components/subscriptions/SubscriptionStatusBadge.tsx @@ -3,6 +3,7 @@ import { cn } from '@/lib/utils'; const statusStyles: Record = { active: 'bg-green-500/10 text-green-300 border-green-500/20', + pending_payment: 'bg-amber-500/10 text-amber-300 border-amber-500/20', pending_settlement: 'bg-blue-500/10 text-blue-300 border-blue-500/20', trialing: 'bg-blue-500/10 text-blue-300 border-blue-500/20', past_due: 'bg-amber-500/10 text-amber-300 border-amber-500/20', @@ -10,6 +11,7 @@ const statusStyles: Record = { paused: 'bg-amber-500/10 text-amber-300 border-amber-500/20', suspended: 'bg-red-500/10 text-red-300 border-red-500/20', pending_cancellation: 'bg-amber-500/10 text-amber-300 border-amber-500/20', + cancel_at_period_end: 'bg-amber-500/10 text-amber-300 border-amber-500/20', canceled: 'bg-muted text-muted-foreground border-border', cancelled: 'bg-muted text-muted-foreground border-border', ended: 'bg-muted text-muted-foreground border-border', @@ -19,6 +21,8 @@ const statusStyles: Record = { function formatStatusLabel(status: string): string { if (status === 'pending_cancellation') return 'Cancellation Scheduled'; + if (status === 'cancel_at_period_end') return 'Cancellation Scheduled'; + if (status === 'pending_payment') return 'Awaiting Payment'; return status.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase()); } diff --git a/apps/web/src/components/subscriptions/seats/SeatsSubscribeCard.tsx b/apps/web/src/components/subscriptions/seats/SeatsSubscribeCard.tsx index f0a5d9b227..be0da67237 100644 --- a/apps/web/src/components/subscriptions/seats/SeatsSubscribeCard.tsx +++ b/apps/web/src/components/subscriptions/seats/SeatsSubscribeCard.tsx @@ -261,13 +261,14 @@ export function SeatsSubscribeCard({
-

Kilo Pass

+

Kilo Pass for Organizations

- Credit subscription with bonus credits.{' '} + Monthly Credits and bonus Credits for every paid seat.{' '} Learn more @@ -281,6 +282,7 @@ export function SeatsSubscribeCard({ Purchase credits as needed. Only pay for what you use with no commitments.{' '} Learn more @@ -292,7 +294,11 @@ export function SeatsSubscribeCard({

Bring Your Own Key

Use API keys from your existing AI provider accounts.{' '} - + Learn more

diff --git a/docs/adr/0003-org-kilo-pass-source-of-truth.md b/docs/adr/0003-org-kilo-pass-source-of-truth.md new file mode 100644 index 0000000000..08ce4b2e34 --- /dev/null +++ b/docs/adr/0003-org-kilo-pass-source-of-truth.md @@ -0,0 +1,205 @@ +# ADR 0003: Use Org-Owned Agreements for Kilo Pass for Organizations + +## Status + +Accepted + +## Context + +Kilo Pass currently exists as a personal-user product. Current subscription, issuance, bonus threshold, scheduled-change, and audit records are keyed to `kilo_user_id` and personal subscription state. + +Kilo Pass for Organizations has different requirements: parent organization billing, coverage for all eligible paid seats, allocation into direct child sub-orgs with all remaining purchased capacity defaulting to parent organization, pooled org or sub-org usage for bonus unlocks, manual enterprise agreements, and term-version preservation for legacy upfront-bonus deals. + +The existing organization billing model already has seat purchase history and organization credit ledgers, but those rows do not preserve Kilo Pass-specific bonus terms, allocation state, or pooled unlock state. + +## Decision + +Kilo Pass for Organizations will use org-owned agreement records as its source of truth. + +Personal Kilo Pass subscription rows remain the source of truth for the existing user-owned product only. Seat purchase rows may inform eligible seat counts and billing integration, but they are not the Kilo Pass for Organizations agreement source of truth. + +Each org agreement references an immutable Kilo Pass term version. Standard term versions may be reused; manual legacy or custom deals may receive dedicated immutable versions. Commercial changes create new versions instead of mutating existing terms, and each issuance snapshots its resolved version terms. + +Org term versions explicitly define concrete per-pass base benefit, bonus amount, unlock spend, and bonus mode. They do not inherit personal Kilo Pass streak, welcome-promo, payment-fingerprint, referral logic, or percentage formula evaluation. Similar commercial values may be configured independently. + +Each term version snapshots per-pass billing price and base-credit benefit. Standard versions may match tier price; dedicated custom versions may differ. Issuance does not derive entitlement from live tier configuration, discounts, taxes, provider invoice proration amount, or actual invoice total. + +Term versions store concrete `base_credit_microdollars_per_pass`, `bonus_credit_microdollars_per_pass`, and `unlock_spend_microdollars_per_pass` values. Each issuance multiplies these values by snapshotted allocation count. Default bonus-after-base terms set unlock spend equal to base benefit; future or custom versions may use another explicit amount. + +Automated `upfront` bonus mode grants bonus alongside each monthly base issuance. Annual-all-upfront legacy contracts remain manual for current term rather than adding a separate annual bonus scheduler. + +Each org agreement selects one existing Kilo Pass tier: `tier_19`, `tier_49`, or `tier_199`. The selected tier applies uniformly to every paid seat. Org agreements do not introduce separate org-only tier identifiers. + +Existing manually administered customers are backfilled into org agreement records with purchase channel, contractual paid-through interval, and dedicated immutable custom terms where needed. Their current term uses manual legacy processing, so automated issuance does not duplicate operator grants. Automated processing begins only through an explicit renewal transition. + +Migration records contractual half-open paid-through interval, external contract identity, processing mode, and `manually_issued_through`. It does not synthesize historical issuances or credits. Automation requires explicit next allocation plan and renewal transition. + +New manual or enterprise agreements use the same automated allocation, monthly issuance, and pooled bonus engine as self-serve agreements by default. Manual processing requires explicit legacy or exceptional contract designation; purchase channel does not determine processing mode. + +An agreement moves to a newer term version at its commercial renewal boundary: next monthly renewal for monthly agreements, next annual renewal for annual agreements, or explicit contractual renewal for manual agreements. Existing terms remain effective through the paid period. + +Org-wide tier upgrades and downgrades also take effect only at commercial renewal boundary. Current paid period and issuance snapshots retain existing tier; no supplemental mid-period grant or threshold rewrite occurs. + +Self-serve cancellation takes effect at commercial renewal boundary. Monthly agreements retain current paid-period entitlement; annual agreements continue scheduled monthly issuances through prepaid annual term. Granted credits remain, current issuance bonus may unlock through its issuance window, and no new issuance occurs after agreement ends. + +Each agreement records half-open `[paid_from, paid_until)` entitlement intervals. Scheduled issuance occurs only while covered by interval; partial coverage creates explicit bridge window. Pending cancellation remains eligible through `paid_until`; failed or unpaid renewal blocks later issuance until payment restores entitlement. Manual agreements use contractual dates. Late successful payment backfills original covered windows. + +Recognized `invoice.paid`, including legitimate zero-due invoice, extends self-serve paid-through entitlement. Refund, dispute, or chargeback suspends future entitlement and enters manual review without automatic pooled-credit clawback. + +When a parent organization opts into Kilo Pass for Organizations, purchased pass capacity exactly equals the parent organization's paid seat count. Kilo Pass is a seat add-on, not an independently sized product. The count is not based on current active non-billing-manager members or occupied seats. + +For an organization with an active Kilo Pass org agreement, every successful paid-seat quantity change automatically synchronizes purchased pass capacity to the new paid seat count. Kilo Pass capacity remains exactly equal to paid seat count. + +A paid mid-window seat increase adds matching capacity to derived parent default allocation and creates a parent supplement only for `max(0, new purchased capacity - capacity already issued for current window)`. Moving added capacity to a child remains future-window effective. This prevents decrease/reincrease from granting twice. Seat decreases do not claw back or rewrite current-window issuance. + +Supplement proration uses authoritative remaining-service-time ratio. Same ratio applies to base, bonus, and unlock spend; each result rounds to nearest microdollar with round-half-up. Snapshot records numerator, denominator, and resolved amounts. Benefit does not derive from invoice total. + +In `after_base` mode, supplement is separate tranche that grants prorated base and opens independent prorated threshold using spend after supplement creation. In automated `upfront` mode, it co-grants prorated bonus. Spend before supplement does not unlock newly added benefit. + +Self-serve Kilo Pass for Organizations is billed as recurring add-on item on parent organization's existing Stripe seat subscription. Add-on quantity and cadence remain synchronized with seat item. Internal org agreement, not Stripe item, remains source of entitlement terms, allocation, and issuance state. + +Self-serve Kilo Pass charges and credit issuance schedule are co-termed with parent seat subscription on its existing billing anchor. Joint seat and Kilo Pass purchase records initial direct-child allocations before payment completion and creates immediate full first issuance from resolved child allocations and parent remainder. Mid-period enablement on monthly seats bridges to existing seat renewal. Mid-period enablement on annual seats bridges to next internal monthly anniversary. Full windows then follow seat-subscription anchor. + +Confirmed paid Stripe invoice is authoritative for self-serve agreement activation and first issuance. Before payment completion, server persists initial direct-child allocation selection, if any. Webhook processing idempotently creates or activates agreement, resolves all remaining capacity to parent, and creates first issuance from that resolved allocation. Browser return may poll and display state but does not authorize entitlement or allocation. + +Initial self-serve purchase allows a parent organization owner or billing manager to allocate integer pass capacity across direct child sub-orgs before first issuance. On paid activation, all purchased capacity not assigned to a direct child defaults to parent organization, and immediate issuance matching the paid service interval uses that resolved distribution. Later allocation changes apply only to future issuance windows. + +Paid activation revalidates initial direct-child relationships and allocation totals against current purchased capacity. If a selected child is no longer a direct child or direct-child allocations exceed current purchased capacity, first issuance blocks for owner or billing-manager correction. The system does not silently reduce an explicitly selected child allocation. + +At most one non-ended agreement may exist for a parent organization. Commercial states are `pending_payment`, `active`, `cancel_at_period_end`, and `ended`. Manual, blocked, overallocated, and failed are processing conditions rather than commercial states. + +Existing parent organization owners and billing managers may purchase, cancel, view, and allocate Kilo Pass for Organizations. Regular members cannot access Kilo Pass purchasing, agreement, or allocation mechanics. + +Kilo Pass purchase, status, and allocation controls live on existing organization subscription page beside seat controls. Personal Subscription Center does not manage org Kilo Pass. + +Authorized parent organization owners and billing managers see Kilo Pass terminology in management and audit surfaces. Regular members see generic organization Credit balance and transaction language without Kilo Pass labels, counts, tiers, or bonus mechanics. + +Child sub-org owners do not gain Kilo Pass allocation authority from child ownership. They may see resulting usable credits through existing balance surfaces, while parent owners and billing managers retain agreement and allocation control. + +A child sub-org cannot detach, reparent, archive, or delete while it has nonzero initial or future Kilo Pass allocation. A parent organization owner or billing manager must first set allocation to zero. Already granted pooled credits remain with child because shared balance cannot safely identify source-specific remainder for clawback. + +A seat decrease first reduces future parent default allocation. If direct-child allocations then exceed purchased pass capacity, the seat and pass count decrease still takes effect, parent default allocation becomes zero, and current-period issuance snapshots remain unchanged. The agreement enters an overallocated state that an authorized parent organization owner or billing manager must reconcile before next scheduled processing; the system does not choose which direct-child allocation loses capacity. + +If the agreement remains overallocated at scheduled issuance processing, processing skips the entire agreement. It records a durable blocked result, notifies authorized parent organization owners and billing managers, and retries after allocations are reconciled. It does not partially issue by choosing containers or grant above paid capacity. + +When blocked or failed processing later succeeds, it creates the original scheduled issuance window. Grant, pooled-spend threshold, and bonus expiry retain original boundaries; qualifying spend since scheduled window start counts. Processing delay does not shift agreement anchor or discard paid entitlement. + +Allocation reconciliation may supply snapshot retroactively only for blocked, not-yet-created window. Once issuance snapshot exists, later allocation changes remain future-effective. + +Monthly Kilo Pass for Organizations processing grants base credits to every allocation container with resolved pass capacity. Direct-child allocations are explicit integers, and parent allocation is derived as purchased capacity minus direct-child allocation sum. Every purchased pass therefore belongs to a child or parent allocation container, and no paid-but-unassigned state exists. Allocation plans are versioned by effective issuance boundary and reject stale concurrent writes transactionally. + +Self-serve monthly processing uses parent seat subscription's billing anchor. Manual enterprise agreements may use a calendar-month anchor only when contract explicitly defines it. Reporting may group issuances by calendar month without making calendar month entitlement boundary. + +Actual provider period boundaries are authoritative when available. Internal monthly boundaries derive from original billing anchor plus month index and clamp to target month end. They do not advance from previously clamped boundary. A January 31 anchor therefore yields February month-end, then March 31, not March 28. + +New/default annual agreements charge annually but create monthly base-credit issuance snapshots. Each monthly issuance has its own pooled bonus-after-base unlock. Current-term annual-all-upfront legacy behavior remains manual. + +When joint self-serve seat and Kilo Pass purchase completes, paid activation creates immediate full first issuance from pre-issuance direct-child allocation selection and derived parent remainder. Mid-period activation on monthly seats creates a prorated bridge through next seat renewal. Mid-period activation on annual seats creates a prorated bridge through next internal monthly anniversary. + +Annual self-serve agreements create monthly issuances on subscription-date anniversaries and no more than 12 issuance windows during one 12-month paid term. Mid-term annual activation bridges only to next internal monthly anniversary, then full monthly windows continue through annual renewal. + +The parent organization may be an allocation container for Kilo Pass-funded pooled credits when customer wants one shared org pool. Child allocations support direct children only in phase one; agreement owner must be top-level parent. + +Initial allocation selection applies to first issuance. Every allocation-plan change after first issuance affects future scheduled processing only. Current-period base grants and bonus unlock thresholds are not prorated, recalculated, or reversed for ordinary mid-window edits. Blocked-window reconciliation may supply allocation only when no snapshot exists. + +Bonus-after-base unlocks use immutable issuance snapshot created for allocation container. Snapshot records allocation count, concrete amounts, term version, and unlock threshold for window. Unlock logic does not read live allocation or live bonus terms after snapshot exists. + +Bonus progress uses cumulative Credit spend charged to allocation container from scheduled window start, regardless of issuance record creation time or other credit sources in balance. Unlock compares spend with snapshotted unlock-spend threshold and does not require credit-lot ordering or proof that a specific Kilo Pass grant funded each deduction. + +Qualifying spend is actual product Credit consumption, including chargeable model, API, hosting, and other product usage. Parent-child transfers, expirations, grants, refunds, reversals, and administrative adjustments do not advance bonus progress. + +Exact organization ledger debited by request owns spend attribution. Parent membership or allocation in another container never transfers spend between containers. + +Canonical allocation-container spend recording atomically advances issuance pooled spend and grants bonus exactly once when threshold is crossed. Issuance-level idempotency handles concurrent spend and retries. An idempotent sweep repairs missed evaluations but is not the normal unlock path. + +Each issuance counts spend only during its agreement-relative issuance window. A still-locked bonus expires when next window begins. Unused granted base credits may remain in allocation-container balance, but old bonus opportunity does not carry forward. + +When bonus unlocks, its credit grant expires at next agreement-relative issuance boundary. Expiration removes only unused bonus value. Base credits do not receive this period expiry and may remain in allocation-container balance. + +If repair occurs after window ended, system backfills base and historical outcome but does not silently create already-expired spendable bonus. It records missed bonus for audited operator compensation. Windows process chronologically so later window cannot bypass unresolved earlier window. + +One agreement/window processing attempt is an all-or-nothing database transaction. One allocation-container failure rolls back whole agreement issuance. Stable unique identities cover provider event, agreement activation, agreement/container/window issuance, invoice-line supplement, bonus tranche, credit grant, expiry, and blocked result. + +Canonical qualifying debit ledger remains queryable by allocation container, timestamp, and stable transaction ID. Blocked run records original window immediately; repair reconstructs pooled spend from immutable debit records. + +Platform admins alone may create custom/manual agreements, set paid-through intervals, designate manual processing, schedule commercial transitions, issue audited compensation, or manually retry processing. Every mutation records actor, reason, before/after values, and timestamp. + +Issuance operations persist `pending`, `running`, `succeeded`, `blocked`, or `failed`, use leased idempotent retries, expose manual replay and metrics, and show persistent subscription-page status. Blocked window sends one deduplicated email to current parent owners and billing managers. + +Subscription page supports initial direct-child distribution before first issuance, then separates immutable current issuance snapshot from next allocation plan and effective date. It also shows purchased capacity, derived parent allocation, direct-child allocations, paid-through, pending-cancellation, blocked, and overallocated state. + +## Alternatives + +- Extend personal Kilo Pass subscriptions with organization fields and branch logic. +- Store Kilo Pass for Organizations terms only on organization seat purchase rows. +- Use global calendar-month issuance instead of provider-anchored agreement windows. +- Bill Kilo Pass through separate Stripe subscription rather than seat add-on item. +- Mutate live agreement terms or allocations instead of immutable versions and snapshots. + +## Consequences + +Kilo Pass for Organizations can model agreement versions, manual and self-serve purchase channels, purchased pass capacity, sub-org allocation, and pooled bonus-after-base unlocks without overloading personal Kilo Pass lifecycle rules. + +Immutable term versions preserve the meaning of purchased agreements and allow support to identify which standard or custom rules produced each issuance. + +Dedicated org term rules keep user-specific promotion and anti-abuse behavior out of parent-owned pooled agreements. + +Recording legacy agreements without automating current-term grants preserves purchased behavior and creates one entitlement inventory without requiring unreliable historical issuance reconstruction. + +Automating new sales-assisted agreements prevents manual operations from remaining the default enterprise lifecycle while preserving contractual exceptions. + +Snapshotted price and benefit preserve purchased agreement meaning when product prices or provider invoices change. + +Concrete microdollar values make grants and thresholds exact and auditable without percentage rounding or a general rule-expression engine. + +Renewal-bound transitions prevent mid-term changes to prepaid benefits while avoiding permanent accidental grandfathering. + +Renewal-bound tier changes keep provider price, per-seat base amount, and bonus terms on same effective date without mid-period issuance adjustment. + +Period-end cancellation preserves purchased benefits and avoids pooled-credit attribution and clawback requirements. + +Explicit paid-through entitlement separates credit eligibility from asynchronous provider status and gives self-serve and manual agreements one issuance gate. + +Exact equality with paid seat count enforces seat add-on model, while parent-default allocation ensures every purchased pass produces value in either parent or direct-child container. + +Automatic quantity synchronization enforces all-seat coverage as a system invariant rather than relying on a separate administrative step or later reconciliation. + +Prorated seat-increase supplements make paid added capacity immediately useful without granting full-window value for partial-window payment. + +Using one Stripe subscription gives seat and Kilo Pass items same invoice, renewal, cadence, and quantity lifecycle without cross-subscription synchronization. + +Paid-invoice authority prevents unpaid or incomplete subscription updates from granting credits and makes provider retries safe. + +Provider-anchored issuance keeps paid periods, credit windows, and cancellation boundaries consistent without changing existing seat billing terms. + +Optional pre-issuance direct-child distribution lets an owner or billing manager direct first-window value immediately, while parent-default allocation ensures purchase still provides immediate credits without requiring any child allocation. + +Seat reductions automatically consume parent default allocation first. Explicit reconciliation is required only when direct-child allocations exceed reduced purchased capacity, avoiding silent reduction of a business-critical child's allocation while allowing seat administration and provider-originated quantity changes to complete. + +Blocking the entire overallocated agreement fails closed without granting unpaid capacity or encoding arbitrary team priority into billing logic. + +Retrying original windows preserves provider-period alignment and paid entitlement while keeping reporting and later issuance dates stable. + +Granting credits from direct-child allocations and derived parent default allocation keeps every purchased pass tied to one usage container without retroactive allocation changes or paid-but-unused capacity. + +Agreement-relative processing prevents one payment from producing extra issuance windows at calendar boundaries. Calendar-month reporting remains available independently. + +Original-anchor boundary derivation prevents cumulative short-month drift and supports exactly 12 monthly windows per annual paid term. + +Separating annual payment cadence from monthly credit cadence keeps default monthly and annual agreements on one grant and bonus model while preserving versioned legacy exceptions. + +Immediate full or prorated first issuance makes paid credits usable immediately while matching benefit to provider-billed service interval. + +Allowing the parent organization as the default allocation container avoids forcing sub-org setup for organizations that want one shared pool and matches existing organization credit ledger behavior. + +Future-cycle allocation changes apply at next agreement-relative issuance and avoid duplicate grants, clawbacks, or threshold rewrites caused by mid-period allocation edits. + +Issuance snapshots make bonus unlock behavior auditable and keep later allocation or agreement-version changes from mutating current-period entitlements. + +Container-level spend counters preserve pooled behavior and avoid introducing a separate wallet-bucket consumption model into existing organization credit balances. + +Transactional unlock makes bonus available immediately after base-threshold consumption and avoids an eventual-consistency window where funded usage can be blocked. + +Period-bounded eligibility prevents one later spend event from satisfying multiple monthly thresholds and avoids maintaining a queue of old locked bonuses. + +Implementation adds new schema and routing rather than reusing existing personal Kilo Pass tables directly. Integration code must bridge org agreements to seat billing, credit grants, usage attribution, and admin UI. diff --git a/docs/assets/kilo-pass-orgs/active-management.png b/docs/assets/kilo-pass-orgs/active-management.png new file mode 100644 index 0000000000..76a3c007e7 Binary files /dev/null and b/docs/assets/kilo-pass-orgs/active-management.png differ diff --git a/docs/assets/kilo-pass-orgs/initial-setup.png b/docs/assets/kilo-pass-orgs/initial-setup.png new file mode 100644 index 0000000000..204abb0064 Binary files /dev/null and b/docs/assets/kilo-pass-orgs/initial-setup.png differ diff --git a/docs/assets/kilo-pass-orgs/seat-reconciliation.png b/docs/assets/kilo-pass-orgs/seat-reconciliation.png new file mode 100644 index 0000000000..a4315dd0d1 Binary files /dev/null and b/docs/assets/kilo-pass-orgs/seat-reconciliation.png differ diff --git a/docs/assets/kilo-pass-orgs/subscription-center-entry.png b/docs/assets/kilo-pass-orgs/subscription-center-entry.png new file mode 100644 index 0000000000..e274bae384 Binary files /dev/null and b/docs/assets/kilo-pass-orgs/subscription-center-entry.png differ diff --git a/docs/kilo-pass-orgs-executive-overview.html b/docs/kilo-pass-orgs-executive-overview.html new file mode 100644 index 0000000000..d861197766 --- /dev/null +++ b/docs/kilo-pass-orgs-executive-overview.html @@ -0,0 +1,3799 @@ + + + + + + + Kilo Pass for Organizations | Executive overview + + + +
+
+
+
+ + + Kilo + + Internal enablement + + 01 / 17 +
+
+
+ +

Kilo Pass for Organizations

+

+ Fund shared organization Credit balances from one Kilo Pass per paid seat. + Organization owners and billing managers choose how passes are distributed. +

+

Internal enablement | July 2026

+
+ +
+
+
+ +
+
+
+ Kilo + Kilo Pass for Organizations02 / 17 +
+
+
+

How the product works

+

+ Four rules define who is covered, where credits go, when value is issued, and how + bonus behavior works. +

+
+
+
+ 01 +
+

One tier covers every paid seat

+

+ Each paid seat in the parent organization requires one Kilo Pass. One pricing + tier applies to every pass. +

+
+
+
+ 02 +
+

Owners and billing managers choose where credits go

+

+ Kilo Passes can fund the parent org, direct child sub-orgs, or both. They are + never assigned to individual users. +

+
+
+
+ 03 +
+

Automated agreements deliver monthly value

+

+ Automated agreements add credits monthly on each customer's own schedule, even + when billing is annual. +

+
+
+
+ 04 +
+

Bonus timing follows the agreement terms

+

+ Standard plans unlock bonus through organization usage. Upfront plans include + bonus with monthly base credits. Some older annual agreements remain manual. +

+
+
+
+
+
+
+ +
+
+
+ Kilo + Owner / billing manager journey03 / 17 +
+
+
+
+ +

Find Kilo Pass beside seat billing

+
+

+ Owners and billing managers start from the existing organization Subscriptions page. + Kilo Pass shows the paid-seat requirement before setup begins. +

+
+ +
+
+
+ +
+
+
+ Kilo + Kilo Pass for Organizations04 / 17 +
+
+
+

One pass for every paid seat

+

+ A parent organization with 120 paid seats purchases 120 Kilo Passes. One selected + tier applies to all 120. +

+
+ +
+
    +
  • $19 per pass / month
  • +
  • $49 per pass / month
  • +
  • $199 per pass / month
  • +
+
+ Important: these are standard self-serve list prices per pass. + Approved custom agreements may use different fixed per-pass pricing and benefits. + Kilo Pass quantity still equals paid-seat count; quantity cannot be reduced to cover + only selected users. +
+
+
+
+
+ +
+
+
+ Kilo + Kilo Pass for Organizations05 / 17 +
+
+
+

Self-serve billing stays with seat billing

+

+ Self-serve customers add Kilo Pass to the parent organization's seat subscription, + either during initial purchase or as an add-on later. It stays on the same invoice + and renewal schedule as seats. +

+
+
    +
  1. + 01Select a tierA parent-organization owner or billing manager chooses one tier +
  2. +
  3. + 02Choose distributionChoose each direct child's pass count; the remainder stays with parent +
  4. +
  5. + 03Add to seat billingQuantity, cadence, and renewal stay aligned with paid seats +
  6. +
  7. + 04Confirm paid invoiceA valid paid invoice, including one with $0 due, activates Kilo Pass +
  8. +
  9. + 05Issue the first creditsInitial distribution applies immediately to the first issuance +
  10. +
+
+
+

Customer experience

+

Same invoice and renewal date

+

No second subscription to reconcile.

+
+
+

Aligned billing

+

Same cadence and seat count

+

Kilo Pass quantity stays synchronized with paid seats.

+
+
+

Activation

+

First window depends on purchase timing

+

+ Buying Kilo Pass when the seat subscription starts creates a full window. Adding + it during an existing seat period reduces the first charge and credits to the + remaining time in that period. +

+
+
+
+
+
+ +
+
+
+ Kilo + Owner / billing manager journey06 / 17 +
+
+
+
+ +

Choose one tier, then direct the first Credits

+
+

+ Paid seats determine pass quantity. The customer assigns direct children and sees + the parent remainder before reviewing the purchase. +

+
+ +
+
+
+ +
+
+
+ Kilo + Kilo Pass for Organizations07 / 17 +
+
+
+

Three common allocation scenarios

+

+ Owners and billing managers choose each direct child's Kilo Pass count. Any + remaining passes automatically fund the parent org. +

+
+
+
+

Scenario 1 | No sub-orgs

+

50 seats, one shared balance

+

+ 50 paid seats create 50 passes. All 50 are allocated to the parent org, so members + use one shared organization Credit balance. Usage charged there can help unlock + bonus credits when the agreement requires usage before bonus. +

+
+
+

Scenario 2 | Parent + direct children

+

120 seats, value split by organization

+

+ Assign 30 passes to child A and 20 to child B. The remaining 70 automatically go + to the parent org. Each organization receives credits in its own Credit balance. +

+
+
+

Scenario 3 | All value to children

+

80 seats, parent allocation is zero

+

+ Assign 50 passes to child A and 30 to child B. No passes remain for the parent + org, but all 80 purchased passes still create spendable credits for the two + children. +

+
+
+
+ Timing: initial distribution is selected before first issuance and + applies immediately. Later changes start with the next credit issuance cycle and never + move existing spend or credits between organizations. +
+
+
+
+ +
+
+
+ Kilo + Owner / billing manager journey08 / 17 +
+
+
+
+ +

+ Current value stays fixed; next distribution can change +

+
+

+ The management screen separates issued Credits and bonus progress from the plan that + will apply at the next agreement-relative window. +

+
+ +
+
+
+ +
+
+
+ Kilo + Kilo Pass for Organizations09 / 17 +
+
+
+

Monthly usage can unlock more credits

+

+ In standard automated plans, each allocated organization gets monthly base credits + and may unlock extra bonus credits through usage. +

+
+
    +
  1. + +
    + Window opens +

    Base credits arrive

    +

    Each allocated organization receives its approved monthly base amount.

    +
    +
  2. +
  3. + +
    + During the window +

    Usage adds progress

    +

    + Usage charged to that organization's Credit balance counts toward its bonus. +

    +
    +
  4. +
  5. + +
    + Threshold reached +

    Bonus unlocks once

    +

    Reaching the approved threshold grants that organization's monthly bonus.

    +
    +
  6. +
  7. + +
    + Next boundary +

    A new window begins

    +

    + Unused base may remain. Bonus not yet unlocked and unused bonus credits expire. +

    +
    +
  8. +
+
+
+ Counts toward bonus unlock +

Eligible model, API, hosting, and other paid product Credit consumption.

+
+
+ Does not count +

+ Transfers, grants, expirations, refunds, reversals, and administrative + adjustments. +

+
+
+
+ Important: standard after-base terms set the unlock threshold equal + to the monthly base-credit benefit. Other approved plans may use a different target or + include bonus automatically with monthly credits. Personal streaks, welcome + promotions, and referral benefits do not apply. +
+
+
+
+ +
+
+
+ Kilo + Kilo Pass for Organizations10 / 17 +
+
+
+

Automated annual billing delivers monthly credits

+

+ Monthly credits follow each agreement's start date, not calendar months. +

+
+
+
+
+ MonthlyInvoice monthly +
+ +

+ Each paid monthly interval creates one full issuance window. +

+
+
+
+ AnnualInvoice annually +
+ +

+ One paid annual term includes up to 12 monthly credit issuances. +

+
+
+
+
+

Adding to existing seats

+

Only the first window may be prorated

+

+ Buying Kilo Pass when the seat subscription starts creates a full window. When + added later, Kilo Pass does not start a separate billing period; first charge and + credits cover only time until the next aligned boundary. +

+
+
+

Self-serve cancellation

+

Ends at commercial renewal

+

+ Scheduled monthly issuance continues through the self-serve paid period. Granted + credits are not clawed back; normal bonus expiry still applies. +

+
+
+

Term or tier change

+

Changes wait for renewal

+

+ Term and tier changes take effect at commercial renewal, not mid-period. +

+
+
+
+ Important: only designated older annual agreements still give all + value up front and require manual handling. New automated upfront plans deliver base + and bonus together each month. +
+
+
+
+ +
+
+
+ Kilo + Kilo Pass for Organizations11 / 17 +
+
+
+

Seat count and Kilo Pass quantity stay in sync

+

+ When paid-seat count changes, Kilo Pass quantity changes with it. Credits already + issued for the current monthly cycle are not recalculated or taken back. +

+
+
+
+
+

Paid mid-window seat increase

+ + Added seats +
+ +
+
+
What changes
+
The added Kilo Passes automatically go to the parent org.
+
+
+
This window
+
+ Only newly added passes receive partial value for the remaining time in the + current cycle. +
+
+
+
Next window
+
An owner or billing manager may redistribute those passes to children.
+
+
+
+
+
+

Seat decrease

+ − Removed seats +
+ +
+
+
What changes
+
Kilo first removes passes from the parent org's future share.
+
+
+
This window
+
Credits already issued remain unchanged and are not clawed back.
+
+
+
Next window
+
+ If child allocations exceed the new total, an owner or billing manager must + reduce them before issuance can continue. +
+
+
+
+
+
+ Why customer action may be required: when a seat decrease is larger + than the parent org's future share, Kilo cannot know which child should lose passes. + An owner or billing manager makes that choice before the next credit cycle. Until + then, no organization under that agreement receives new credits. +
+
+
+
+ +
+
+
+ Kilo + Owner / billing manager journey12 / 17 +
+
+
+
+ +

Show exactly what must be reconciled

+
+

+ Current Credits remain untouched. A persistent warning names the excess and + deadline, while the next distribution stays blocked until child assignments fit paid + seats. +

+
+ +
+
+
+ +
+
+
+ Kilo + Kilo Pass for Organizations13 / 17 +
+
+
+

Owners and billing managers control Kilo Pass

+

+ These existing parent-organization roles manage Kilo Pass. Most users only spend + from their organization's shared Credit balance. +

+
+
+
+

Parent organization owner or billing manager

+

Controls the agreement

+
    +
  • Purchase and cancel Kilo Pass for Organizations
  • +
  • Select the pricing tier
  • +
  • Set direct-child allocations; the parent org receives the remainder
  • +
  • Resolve child allocations that exceed purchased Kilo Passes
  • +
+
+
+

Regular member or direct-child organization owner

+

Uses shared credits

+
    +
  • Consumes Credits from the organization's Credit balance
  • +
  • Sees generic Credit balance and transactions
  • +
  • + Does not see tier, pass count, or bonus rules without parent billing authority +
  • +
  • Cannot change Kilo Pass allocations
  • +
+
+
+

Kilo platform admin

+

Handles exceptions

+
    +
  • Create approved custom or manual agreements
  • +
  • Set contractual service dates and schedule renewal changes
  • +
  • Restore eligible value and record any manual adjustments
  • +
  • Maintain an audit record for administrative changes
  • +
+
+
+
+ Hierarchy protection: before a child sub-org with an initial or + future allocation can be detached, reparented, archived, or deleted, a + parent-organization owner or billing manager must set that allocation to zero. Credits + already granted remain with the child sub-org. +
+
+
+
+ +
+
+
+ Kilo + Kilo Pass for Organizations14 / 17 +
+
+
+

How agreement types are processed

+

+ Purchase channel does not determine processing mode. Self-serve and new enterprise + agreements automate monthly issuance by default. Manual processing requires an + explicit legacy or exceptional designation. +

+
+
+
+ Self-serve standard +

+ Existing tiers, a synchronized seat add-on item, paid-invoice activation, and + automated monthly issuance. +

+ Automated +
+
+ New enterprise +

+ Uses a reusable standard term version or a dedicated custom term version, plus + contract dates. Allocation, monthly issuance, and bonus processing automate by + default. +

+ Automated by default +
+
+ Existing legacy +

+ Agreement identity and term version are recorded, but current-term grants remain + in Manual legacy processing to avoid duplicate value. +

+ Manual legacy processing +
+
+
+
+

Agreement terms

+

A term version fixes the rules

+

+ It records per-pass price, base credits, bonus, and unlock threshold for the paid + commercial period. +

+
+
+

Changes

+

New terms start at renewal

+

+ Tier or term changes do not rewrite the current paid period. +

+
+
+

Manual processing

+

Explicit exception only

+

+ Existing legacy current-term grants remain manual to avoid duplication. Automation + requires an explicit renewal transition. +

+
+
+
+
+
+ +
+
+
+ Kilo + Kilo Pass for Organizations15 / 17 +
+
+
+

Exceptions have clear owners and next actions

+

+ Kilo pauses new credits when payment or allocation is not valid. Some issues require + customer action; Kilo handles others. +

+
+
+
+

Typical self-serve lifecycle

+ +
+
+

Exception ownership

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExceptionOwnerNext action
Child allocations exceed purchased passes + Owner / billing manager + Choose which child allocation to reduce.
Paid invoice is missing or reversed + Billing team / admin + Review payment before future issuance.
Automated processing failsKilo system + Retry automatically; admin can rerun if needed. +
Manual legacy agreement + Platform admin + Follow recorded agreement terms.
+
+
+
+
+
+

Consistent issuance

+

All allocated organizations or none

+

+ No organization receives that window's credits unless the entire agreement is + processed successfully. +

+
+
+

Delayed processing

+

Original window is retained

+

+ Kilo can still issue eligible base credits for the original cycle. If bonus has + expired, an admin reviews whether a separate make-good is needed. +

+
+
+

Payment reversal

+

Future issuance pauses for review

+

+ Refunds, disputes, and chargebacks pause future issuance. Credits already granted + are not automatically clawed back. +

+
+
+
+
+
+ +
+
+
+ Kilo + Kilo Pass for Organizations16 / 17 +
+
+
+

What this means for customers and operations

+

+ The model centralizes funding and control at the parent organization while keeping + everyday Credit use simple for members. +

+
+
+
+

Customer experience

+
    +
  • Self-serve uses one Stripe subscription and invoice with seat billing
  • +
  • Credits appear in allocated organization Credit balances
  • +
  • Automated monthly base credits continue during paid annual terms
  • +
  • Current-window grants stay unchanged when future allocations change
  • +
  • Parent-organization owners and billing managers control allocations
  • +
+
+
+

Product constraints

+
    +
  • All paid seats must be covered
  • +
  • Every purchased pass funds either parent or direct-child credits
  • +
  • Sub-org allocation is limited to direct child sub-orgs
  • +
  • Child ownership alone does not grant allocation authority
  • +
  • Child allocations above the reduced Kilo Pass total block issuance
  • +
+
+
+

Operational model

+
    +
  • Org agreements remain separate from personal Kilo Pass subscriptions
  • +
  • Purchased terms stay fixed through the paid commercial period
  • +
  • Credits issue only for paid service periods or approved contract dates
  • +
  • Allocation changes affect future windows, not current-window benefits
  • +
  • Blocked or failed issuance keeps its original schedule and can be retried
  • +
+
+
+
+ Model boundary: Kilo Pass for Organizations is centralized funding + for shared product usage. It is not a per-member allowance, a budget cap, or a + calendar-month coupon. +
+
+
+
+ +
+
+
+ Kilo + Kilo Pass for Organizations17 / 17 +
+
+
+ +

How the full model fits together

+

+ One tier covers every paid seat. Owners and billing managers choose how many passes + each direct child receives; all remaining passes stay with the parent org. +

+
+
+
+ 01 +
+

Coverage and billing

+

+ Kilo Pass quantity equals paid-seat count. Self-serve shares seat billing + quantity, cadence, and renewal. +

+
+
+
+ 02 +
+

Distribution

+

+ Choose each direct child's pass count. All remaining passes automatically fund + the parent org. +

+
+
+
+ 03 +
+

Monthly benefit

+

+ Automated agreements add base credits monthly. Bonus unlocks through usage or + arrives automatically, depending on agreement type. +

+
+
+
+ 04 +
+

Operations

+

+ Most new agreements run automatically. Only legacy agreements or approved + exceptions require manual handling. +

+
+
+
+
+
+
+
+ + + + + +
+
+

Slide overview

+

Select a slide to present.

+
+ +
+
+
+ +

+ + + +