diff --git a/.specs/coding-plans.md b/.specs/coding-plans.md new file mode 100644 index 0000000000..40bcfb817f --- /dev/null +++ b/.specs/coding-plans.md @@ -0,0 +1,149 @@ +# Coding Plans + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC 2119] [RFC 8174] when, and only when, they appear in capitalized form. + +--- + +## Definitions + +**Coding Plan** - A recurring subscription product that grants a user access through Kilo to an upstream provider plan by installing its issued credential in the user's existing personal BYOK provider slot. A user's later BYOK changes do not alter Coding Plan billing. + +**Plan Catalog** - The set of Coding Plan offerings enabled by Kilo. The catalog is controlled by trusted application configuration and may contain one or more offerings. + +**Plan ID** - A stable identifier for a purchasable Coding Plan offering. A Plan ID is distinct from the upstream provider or routing identifier used to execute API traffic. + +**Upstream Plan ID** - The MiniMax-issued identifier paired with a Managed Plan Credential and used by support to deprovision that provider plan. It is operational metadata, not the Kilo Plan ID. + +**Managed Plan Credential** - An upstream API key acquired or provisioned by Kilo for a Coding Plan. Kilo manages its assignment and revocation. It is paired with an Upstream Plan ID and is not exposed to the subscriber after it is installed in BYOK. + +**Installed BYOK Configuration** - A normal personal BYOK entry that Kilo initially populates with a Managed Plan Credential. While unchanged, it identifies Token Plan Plus as its origin and Kilo may delete it at Effective Cancellation. A subscriber may test, enable, disable, update, or delete it using normal BYOK operations. Replacing its credential transfers cleanup ownership to the subscriber. + +**Availability Notification Intent** - A user's plan-scoped request to be notified when a sold-out Coding Plan has capacity again. It is not a reservation, purchase, subscription, or entitlement. + +**Manual Revocation Work Item** - Durable inventory remediation state requiring authorized support staff to deprovision an issued MiniMax plan using its stored Upstream Plan ID through the provider admin process and record its outcome in Kilo. The initial pilot represents this work on the inventory row and does not require a separate remediation audit-event history. MiniMax does not provide an automated revocation integration for the initial release. + +**Kilo Credits** - The unit of account used for Coding Plan billing. The pricing layer manages conversion to internal microdollar accounting; user-facing surfaces display `Credits` as the payment source and charged amounts in USD. + +**Upstream Provider** — An external API vendor whose plan access is offered through Kilo. The initial planned offering uses MiniMax. + +**Obfuscated Identity** — An irreversible, per-provider cryptographic hash of a user's internal identifier, used when Kilo must identify a subscriber to an upstream provider without sending personally identifiable information. + +**Effective Cancellation** — The time when a Coding Plan ceases to provide access. For a user-requested cancellation, this is the end of the already-paid billing period. For account deletion or an immediate administrative termination, this is immediate. + +--- + +## 1. Plan catalog + +1.1. The system **MUST** present the configured Plan Catalog within app.kilo.ai. The catalog **MAY** contain a single offering. + +1.2. Each catalog entry **MUST** have a stable Plan ID and **MUST** display its provider name, plan name, recurring USD price, billing period, and payment source. Coding Plans paid from a user's credit balance **MUST** identify `Credits` as their payment source. + +1.3. Kilo's backend **MUST** be the source of truth for the set of available Coding Plans and their pricing. A change to enabled offerings or price **MUST** be deployed through controlled application configuration or code review. + +1.4. A Plan ID **MUST NOT** be treated as the upstream provider identity. Multiple future plans from one upstream provider **MAY** coexist without extending or replacing one another unless their product rules explicitly state otherwise. + +1.5. The initial implementation is intended to configure one offering: MiniMax Token Plan Plus. This initial offering does not impose a requirement that future catalogs contain MiniMax or any minimum number of offerings. + +1.6. When no assignable Managed Plan Credential exists for an offering, customer-facing catalog responses **MUST** identify the offering as sold out without exposing credential counts or credential metadata. + +## 2. Subscription and billing + +2.1. Users **MUST** be able to purchase a Coding Plan using Kilo Credits through Kilo. The system **MUST NOT** redirect a user to an upstream provider to subscribe. + +2.2. The system **MUST** allow at most one non-terminal subscription for a given user and Plan ID. A terminal subscription **MUST NOT** prevent a later new subscription to the same Plan ID. + +2.3. Each purchase request **MUST** include an idempotency key scoped to the user and Plan ID. Retrying a successfully processed request with the same idempotency key **MUST** return the original outcome and **MUST NOT** create an additional billing period, charge, or credential assignment. + +2.4. The initial release **MUST NOT** sell an additional prepaid period for a non-terminal subscription. A successful idempotent retry **MUST** return the existing purchase result; all other purchase attempts while a subscription is `active` or `past_due` **MUST** be rejected. Later-period billing occurs only through recurring renewal in the initial release. + +2.5. The system **MUST** atomically perform initial activation: verify that the user's personal provider slot is unoccupied, debit sufficient Kilo Credits using a guarded balance operation, claim an available Managed Plan Credential, create the Installed BYOK Configuration, record the charged term, and create the active subscription. If any step fails, none of those effects **MUST** commit. + +2.6. A failed initial activation **MUST NOT** commit a debit that later needs a compensating refund. The user **MUST** receive an error that indicates whether the failure was insufficient credits or unavailable plan capacity without exposing credential material. + +2.7. Each charged term **MUST** snapshot the plan price and billing period applied to it. A later catalog price change **MUST NOT** retroactively alter a paid term. + +## 3. Customer relationship and managed access + +3.1. Kilo **MUST** manage the subscriber's account relationship, subscription status, and Kilo Credit billing. Kilo **MUST NOT** send a subscriber's email address, name, password, or other personally identifiable account data to an upstream provider. + +3.2. When an upstream operation needs subscriber attribution, Kilo **MUST** use only an Obfuscated Identity unless another non-PII identifier is required by an approved provider contract. + +3.3. A Managed Plan Credential remains controlled by Kilo for inventory and revocation. A subscriber **MUST NOT** receive its raw value through application UI or API responses in the initial release. + +3.4. On activation, Kilo **MUST** configure an Installed BYOK Configuration in the ordinary personal provider slot so eligible traffic can use the plan through the Kilo Gateway. Activation **MUST** fail without charge or assignment if that provider slot is occupied, including by a disabled key. + +3.5. While an Installed BYOK Configuration still contains Kilo's issued credential, user-facing BYOK surfaces **MUST** identify its Coding Plan origin. Ordinary BYOK test, enable/disable, update, and delete operations **MUST** remain available. Before updating, disabling, or deleting that configuration, Kilo **MUST** warn that the operation changes routing but does not cancel subscription billing and **MUST** direct cancellation to the Subscription Center. Updating the credential **MUST** mark the entry as user-managed and detach it from later Coding Plan cleanup; deleting it **MUST NOT** cancel or pause the subscription. Testing or re-enabling the key does not require this warning. + +## 4. Credential provisioning and inventory + +4.1. Kilo **MUST** acquire or provision Managed Plan Credentials before accepting a purchase that depends on them. For an offering initially provisioned by operator upload, only authorized administrative tooling **MAY** insert credentials into inventory. + +4.2. Available and assigned credentials **MUST** be encrypted at rest. Raw credentials **MUST NOT** appear in logs, analytics, error messages, customer responses, ordinary BYOK responses, or administrative inventory and remediation responses. Authorized administrative remediation surfaces **MAY** display the stored Upstream Plan ID needed to revoke issued MiniMax access. + +4.3. Inventory **MUST** distinguish at least these credential lifecycle states: available, assigned, revocation pending, revoked, and revocation failed. + +4.4. An available credential **MUST** be assigned at most once. Once assigned, it **MUST NOT** return to available inventory, including after cancellation, failed revocation, user deletion, or a later re-subscription. + +4.5. A new subscription **MUST** be confirmed active only after an available credential is claimed and its Installed BYOK Configuration has been created within Kilo. + +4.6. When no available credential exists for the requested plan, activation **MUST** fail without debiting credits or creating a subscription. + +4.7. Kilo **MUST** retain the Upstream Plan ID and non-secret assignment and revocation disposition evidence on inventory records for the required operational and compliance retention period. When an issued credential enters manual revocation remediation, Kilo **MUST** remove retained encrypted credential material because support deprovisions it using the Upstream Plan ID. After the applicable retention period, terminal credential records **MAY** be deleted without deleting billing history. + +4.8. Administrative upload tooling **MUST** accept each MiniMax issued credential with its Upstream Plan ID, using the `::` input format or an equivalent structured input, and **MUST** persist the identifier on the inventory record without treating it as the Kilo Plan ID. + +4.9. Administrative upload tooling **MUST** prevent accidental duplicate credential assignment without exposing raw credential values in list responses, for example through a secure, non-reversible fingerprint comparison. + +4.10. Before a MiniMax credential becomes `available` inventory, administrative upload tooling **MUST** validate that it can use the approved ordinary MiniMax routing and model behavior for Token Plan Plus. An invalid or incompatible credential **MUST NOT** become assignable inventory. + +## 5. Subscription lifecycle + +5.1. A Coding Plan with successful activation enters `active` state and remains billable until its paid period ends or it is terminated immediately under this section. A subscriber's BYOK updates, disablement, or deletion **MUST NOT** change that billing lifecycle. + +5.2. When a user requests cancellation, the subscription **MUST** stop renewing and **MUST** remain paid through the end of its current period. On the first billing lifecycle sweep processing the subscription at or after Effective Cancellation, Kilo **MUST** delete its Installed BYOK Configuration only if that configuration is still linked and Kilo-installed, and **MUST** create a Manual Revocation Work Item for the originally issued credential. Kilo **MUST NOT** delete a replacement or later user-created provider key. + +5.3. An uninterrupted successful renewal **MUST** extend paid access using the existing assigned credential. The system **MUST** debit the snapshotted renewal price atomically with recording the new charged term. + +5.4. If a subscription reaches renewal without sufficient Kilo Credits and the user has not enabled applicable auto-top-up, the next billing lifecycle sweep **MUST** terminate it, delete only a still-linked Installed BYOK Configuration, and create a Manual Revocation Work Item for the issued credential. + +5.5. If a subscription reaches renewal without sufficient Kilo Credits and the user has enabled applicable auto-top-up, the system **MUST** trigger no more than one auto-top-up attempt for that due term, move the subscription to `past_due`, and allow payment recovery for a grace period calculated as no more than 24 hours from the due time. + +5.6. During the `past_due` recovery period, arrival of sufficient credits before the stored grace deadline **MUST** allow one atomic renewal debit and restore `active` status. If renewal cannot be funded by that deadline, the next billing lifecycle sweep **MUST** terminate the subscription, delete only a still-linked Installed BYOK Configuration, and create a Manual Revocation Work Item for the issued credential. + +5.7. A user-requested cancellation **MUST NOT** trigger auto-top-up. Updating, disabling, or deleting an Installed BYOK Configuration **MUST NOT** trigger cancellation or affect renewal billing. + +5.8. When a user account is deleted, Kilo **MUST** immediately terminate any Coding Plan subscription, delete the user's BYOK configurations and Availability Notification Intents under the general deletion policy, create a Manual Revocation Work Item for each issued credential, and anonymize subscriber linkage in retained credential disposition records. Account deletion **MUST NOT** wait until the end of a prepaid period. Subscription and charged-term history **MAY** remain associated with the platform's anonymized user record when required for financial or compliance retention. + +5.9. Manual upstream revocation **MUST** be completed by authorized support through the MiniMax admin process using the stored Upstream Plan ID, and its outcome **MUST** be recorded on the inventory item in Kilo. Pending and failed work **MUST** remain visible in the admin console for remediation. Kilo **MUST** keep the Coding Plan terminated while revocation is pending or failed. An issued credential awaiting or failing revocation **MUST NOT** be reassigned; a separate user-managed provider key **MUST NOT** be removed because of revocation work. + +5.10. The initial pilot **MAY** leave an unchanged Kilo-installed BYOK configuration routable between its paid-period or grace deadline and the next scheduled billing lifecycle sweep. Once that sweep processes termination, local Kilo-installed access **MUST** be deleted regardless of whether manual upstream revocation is complete. + +## 6. Traffic routing + +6.1. Initial Token Plan Plus setup **MUST** route through the Kilo Gateway using the existing ordinary personal MiniMax BYOK provider identity. The initial release **MUST NOT** expose saved raw credential values through Kilo UI or API responses. + +6.2. The system **MUST NOT** add a Token Plan Plus-specific provider or model-routing namespace. The Kilo-installed MiniMax key and any later subscriber replacement **MUST** use ordinary MiniMax BYOK routing and model availability. + +6.3. Purchase **MUST** reject an occupied personal MiniMax BYOK slot before a charge or issued credential assignment commits. Once subscribed, a user's ordinary MiniMax BYOK actions affect routing configuration only; Coding Plan billing and revocation of Kilo's originally issued credential remain independent. + +## 7. User-facing behavior + +7.1. Users **MUST** be able to view catalog offerings, purchase a Coding Plan, view their subscription status and paid-period dates, and request cancellation from Kilo surfaces. + +7.2. Coding Plan surfaces **MUST** display recurring prices and charged-term amounts in USD regardless of payment source. Kilo Credits are valued one-to-one with USD for display. Surfaces **MUST** identify `Credits` as the payment source for credit-funded subscriptions and **MUST NOT** expose internal microdollars. + +7.3. While an Installed BYOK Configuration is unchanged, the BYOK surface **MUST** identify it as configured by Token Plan Plus. Before updating, disabling, or deleting it, the surface **MUST** warn that routing changes do not cancel subscription billing and direct the user to Subscription Center to cancel. Saved raw-key view or copy controls **MUST NOT** be added for customer BYOK surfaces. + +7.4. Purchase messaging **MUST** state that Kilo configures MiniMax in BYOK and **MUST** tell users with an existing MiniMax key to delete it before subscribing. Cancellation messaging **MUST** state when billing ends, that Kilo deletes only its unchanged installed configuration, and that Kilo revokes its issued credential when plan access ends. + +7.5. A `past_due` subscription **MUST** communicate its grace deadline with date and local time, the consequence of unsuccessful payment recovery, and that a replacement or user-created MiniMax BYOK key is not deleted by Coding Plan termination. + +7.6. A sold-out offering **MUST** display its unavailable state and **MUST** offer an authenticated user a way to record an Availability Notification Intent. Recording the same intent again **MUST** be idempotent, **MUST NOT** reserve capacity or initiate billing, and **MUST** show the saved intent state. A successful activation **MUST** clear the activated user's intent for that Plan ID. + +## 8. Security and observability + +8.1. Logs and monitoring **MUST NOT** contain raw Managed Plan Credentials, credential-bearing authorization headers, provider-management secrets, or unfiltered provider/SDK key-test error content. + +8.2. General administrative credential inventory responses **MUST** return non-secret status and remediation metadata only. For a `revocation_pending` or `revocation_failed` item, the manual-revocation admin console **MAY** display its Upstream Plan ID to authorized staff. Raw credential values **MUST NOT** be returned by queue, list, or remediation APIs or appear on customer surfaces. + +8.3. The initial pilot does not require a Coding Plans audit-log history for admin inventory upload or manual revocation actions. Inventory lifecycle state, Upstream Plan ID, request/completion timestamps, attempt count, and sanitized failure information **MUST** record current disposition without retaining raw credentials after remediation starts. diff --git a/.specs/subscription-center.md b/.specs/subscription-center.md index a651281879..cca8eff460 100644 --- a/.specs/subscription-center.md +++ b/.specs/subscription-center.md @@ -14,6 +14,16 @@ choices belong in plan documents and code, not here. Draft -- created 2026-03-31. Updated 2026-05-12 -- KiloClaw price-version display behavior. +Updated 2026-05-26 -- Coding Plans managed-credential and catalog behavior. +Updated 2026-05-27 -- Coding Plans ordinary MiniMax BYOK setup and billing separation. +Updated 2026-05-27 -- Coding Plans manual MiniMax revocation handling. +Updated 2026-05-27 -- Token Plan Plus pilot operations and UI behavior. +Updated 2026-05-28 -- Personal product navigation and return context. +Updated 2026-05-28 -- USD price display independent of payment source. +Updated 2026-05-28 -- Coding Plans sold-out availability notification intent. +Updated 2026-05-28 -- Credit-funded payment source label. +Updated 2026-05-28 -- Coding Plans API key configuration summary. +Updated 2026-05-28 -- Coding Plans billing history USD amount display. ## Conventions @@ -28,9 +38,9 @@ capitals, as shown here. - **Subscription Center**: A unified page where users view and manage all of their subscriptions in one place. It exists at both a personal and organizational level. -- **Subscription Group**: A category of subscriptions displayed as a - visual section on the page. Current groups are Kilo Pass, KiloClaw, - Coding Plans, and Teams/Enterprise Seats. +- **Subscription Group**: A product category within the Subscription + Center. Current groups are Kilo Pass, KiloClaw, Coding Plans, and + Teams/Enterprise Seats. - **Subscription Card**: A summary element within a group that represents a single subscription instance and its current state. - **Available Product Card**: A card shown within a subscription group @@ -56,18 +66,21 @@ capitals, as shown here. current period; (c) it is trialing and the trial end date is approaching. The exact threshold for "approaching" is an implementation choice that MAY vary by subscription type. -- **Price**: All prices are denominated in USD and MUST be displayed - with a dollar sign and two decimal places (e.g. "$9.99/mo"). +- **Price**: Prices MUST be displayed in USD regardless of payment + source. Price labels MUST use a dollar sign and billing cadence (e.g. + "$20 /month"). Credit-funded products MUST display "Credits" as their + payment source separately from their USD price. ## Overview -The Subscription Center is a centralized page where users can see -every subscription they hold, grouped by product type: Kilo Pass, -KiloClaw, Coding Plans, and Teams/Enterprise Seats. Each group contains individual -subscription cards showing status, plan, pricing, and billing date at -a glance. Clicking a subscription card navigates to a detail page with -full management capabilities including plan changes, cancellation, -usage history, and invoice viewing. +The Subscription Center is a centralized page where users manage +subscriptions by product type: Kilo Pass, KiloClaw, Coding Plans, and +Teams/Enterprise Seats. The personal route provides access to each +personal product while preserving product context when users return from +its detail page. Each visible group contains subscription cards showing +status, plan, pricing, and billing date at a glance. Clicking a +subscription card navigates to a detail page with management capabilities +including plan changes, cancellation, usage history, and invoice viewing. The personal Subscription Center lives at `/subscriptions` and is accessible from the sidebar. Organization subscriptions live at @@ -79,8 +92,8 @@ only org-owned subscriptions. Subscription management UI may continue to appear in other parts of the app (e.g. Kilo Pass cards on the profile page, billing controls within the KiloClaw dashboard). The Subscription Center does not -replace those surfaces but serves as the canonical hub where all -subscriptions are consolidated. +replace those surfaces but is the canonical hub for moving between and +managing each subscription product. These routes form a stable URL contract. If any path changes in the future, the system MUST redirect from the old path to the new one. @@ -109,34 +122,36 @@ future, the system MUST redirect from the old path to the new one. ### Subscription Groups -6. The page MUST display subscriptions organized into groups by product - type. The initial groups are: +6. The page MUST organize subscriptions into groups by product type. The + initial groups are: - **Kilo Pass** (personal route only) - **KiloClaw** (personal route only) - **Coding Plans** (personal route only) - **Teams/Enterprise Seats** (org route only) -7. A group MUST always appear on its respective route regardless of - whether the user has a subscription of that type. +7. The personal route MUST provide access to each personal Subscription + Group regardless of whether the user has a subscription of that type. + The route MAY show only one group's content at a time. Returning from a + personal subscription detail page MUST restore the user's product + context. -8. When a group contains no subscriptions in a non-terminal state, the - system MUST display an Available Product Card with a call-to-action - to subscribe. +8. When a user views a group with no subscriptions in a non-terminal + state, the system MUST display an Available Product Card with a + call-to-action to subscribe. -9. When a group contains one or more subscriptions in a non-terminal - state, the system MUST display a Subscription Card for each - non-terminal subscription. +9. When a user views a group with one or more subscriptions in a + non-terminal state, the system MUST display a Subscription Card for + each non-terminal subscription. 10. Subscriptions in a terminal state (Kilo Pass: `canceled`, `incomplete_expired`; KiloClaw: `canceled`; Coding Plans: `canceled`; Teams/Enterprise: `ended`) MUST be hidden by default. - The page MUST provide a single page-level toggle that reveals or - hides all terminal subscriptions across all groups simultaneously. + Users MUST be able to reveal terminal subscriptions for each + applicable product. -11. When revealed, terminal subscription cards MUST use a visually - muted treatment — reduced opacity or desaturated color — and MUST - display a status label indicating the terminal state (e.g. - "Cancelled", "Ended"). +11. When revealed, terminal subscriptions MUST be clearly distinguished + from non-terminal subscriptions and MUST display their terminal + status (e.g. "Cancelled", "Ended"). ### Subscription Cards @@ -147,20 +162,17 @@ future, the system MUST redirect from the old path to the new one. - Price per billing period - Payment method summary (e.g. "Visa ending 4242" or "Credits") -13. Cards for subscriptions in a warning state MUST use a colored left - border or background tint that differs from the default card style, - using the application's existing warning/destructive color tokens. - The system MUST NOT use badges or notification indicators in the - navigation. +13. Cards for subscriptions in a warning state MUST communicate + prominently and accessibly that the subscription requires attention. 14. Each Subscription Card MUST be clickable and navigate to that subscription's detail page, regardless of subscription status. 15. Each subscription group MUST load independently. A failure or delay - in loading one group MUST NOT prevent other groups from rendering. + in one group MUST NOT prevent the user from viewing another group. -16. While a group's data is loading, the system MUST display a - placeholder skeleton for that group's cards. +16. While the requested group's data is loading, the system MUST provide + visible loading feedback for that group. ### Kilo Pass Subscriptions (Personal Route) @@ -231,57 +243,70 @@ future, the system MUST redirect from the old path to the new one. - Link to the Stripe customer portal for payment method management (if Stripe-funded) -KiloClaw Subscription Card price display MUST use the subscription -row's price version and renewal amount, when available, so live legacy -lineages show legacy pricing and current lineages show current pricing. -KiloClaw detail price and plan-switch displays MUST use the -subscription row's price version for both the current plan and any -scheduled or requested target plan. +KiloClaw summary and detail views MUST display the pricing applicable +to the subscription, including subscriptions enrolled under earlier +pricing. Scheduled or requested plan changes MUST display pricing that +will apply if the change completes. -The KiloClaw Available Product Card MUST use the fresh current-price -enrollment preview when the group has no non-terminal KiloClaw -subscription. Canceled KiloClaw history MUST NOT cause legacy pricing -or legacy entitlement to appear on subscribe surfaces. Stripe-funded -KiloClaw checkout pending invoice settlement MUST be displayed as -pending settlement rather than fully active. +When the user has no non-terminal KiloClaw subscription, the enrollment +view MUST display the currently available offer and price. Canceled +KiloClaw history MUST NOT cause an earlier price or entitlement to +appear as the available offer. Stripe-funded enrollment awaiting invoice +settlement MUST be presented as pending rather than active. ### Coding Plans Subscriptions (Personal Route) 27. A user MAY have multiple Coding Plans subscriptions — one per - upstream provider. The Coding Plans group MUST display one - Subscription Card for each active coding plan subscription. + configured Plan ID. The Coding Plans group MUST display one + Subscription Card for each non-terminal coding plan subscription, + including a `past_due` subscription in its warning state. 28. The Coding Plans detail page MUST be served at `/subscriptions/coding-plans/[subscriptionId]`. 29. Each Coding Plans detail page MUST support the following management actions: - - View subscription status (active, cancelled) - - Cancel subscription + - View subscription status (`active`, `past_due`, or `canceled`) + - Cancel an active subscription at the end of its paid period 30. Each Coding Plans detail page MUST display: - - Provider name and status - - Billing period and next renewal date - - Cost in Kilo Credits per billing period - - Payment source (Kilo Credits) - - Traffic routing information (Kilo Gateway or direct) - - The user's assigned API key with view and copy controls (see - Coding Plans spec, rule 4.2.1) - - Inline billing history showing credit transactions (see Billing - History rules) - -31. When a coding plan is cancelled or the user's credit balance is - insufficient to renew, the system MUST remove the API key from the - user's BYOK configuration within Kilo. The system MUST NOT revoke - the key with the upstream provider — the key belongs to the user - (see Coding Plans spec, rule 5.1). The cancel confirmation dialog - MUST communicate this to the user. - -32. When a group contains no active coding plans, the system MUST - display the available provider catalog inline as Available Product - Cards — one per upstream provider — showing the provider name, - recurring cost in Kilo Credits, and billing period. Each card MUST - have a subscribe action that initiates subscription creation. + - Provider name, plan name, and status + - Billing period and next renewal date, paid-through date, or grace + deadline as appropriate for its status; a grace deadline MUST include + local date and time + - Price in USD per billing period + - Payment source (Credits) + - API Key Configuration summary identifying configuration in BYOK and + linking to `/byok` when a managed key is installed + - Traffic routing information (Kilo Gateway through the ordinary + MiniMax BYOK provider setup) + - Inline billing history showing credit transactions with amounts in USD + (see Billing History rules) + + Before update, disable, or delete, `/byok` MUST warn that routing changes + do not cancel or pause Token Plan Plus billing and cancellation is managed + in Subscription Center; customer surfaces MUST NOT include saved raw-key + view or copy controls. + +31. Coding Plan cancellation, installed MiniMax configuration cleanup, + and issued-credential revocation MUST follow `.specs/coding-plans.md`. + Cancellation messaging MUST communicate the paid-through date, that + only Kilo's unchanged installed configuration is removed, and that + Kilo revokes its issued credential when plan access ends. + +32. When the user views Coding Plans with no non-terminal subscription, + the system MUST show each configured offering with provider name, + plan name, recurring USD price, billing period, and payment source. + An offering with assignable credential capacity MUST show a subscribe + action. For MiniMax Token Plan Plus, purchase messaging MUST explain + automatic MiniMax BYOK setup and purchase MUST be blocked when any + personal MiniMax BYOK key exists, including a disabled key. In that + state, the system MUST direct the user to delete the existing key in + `/byok` first. An offering without assignable credential capacity MUST + display a sold-out state and a `Notify me when available` action. The + action MUST persist one notification intent per user and Plan ID without + charging credits or reserving inventory, and the surface MUST indicate + once that intent has been saved. ### Teams/Enterprise Seats Subscriptions (Org Route) @@ -341,9 +366,9 @@ pending settlement rather than fully active. group's displayed state MUST NOT change. An abandoned or failed checkout MUST NOT alter what the page displays. -45. When the user has no subscriptions of any type, the personal - Subscription Center MUST display Available Product Cards for every - group — it MUST NOT show an empty page. +45. When the user has no subscriptions of any type, each personal + product MUST remain accessible and MUST present its subscribe + opportunity when viewed; the visible product view MUST NOT be empty. ### Billing History @@ -356,16 +381,16 @@ pending settlement rather than fully active. 48. For subscriptions funded entirely by credits (no Stripe billing), the billing history MUST display credit transaction history in - place of invoices — showing date, amount, and description for each - credit deduction. + place of invoices, showing date, USD-denominated amount, and description + for each credit deduction. 49. The billing history MUST be scoped to the individual subscription being viewed — the system MUST NOT display entries from other subscriptions. 50. The billing history MUST be ordered by date descending (newest - first). When there are more than 25 entries, the system MUST - paginate or provide a "show more" mechanism. + first). Users MUST be able to access additional entries when the + complete history is not shown initially. ### Payment Method Management @@ -379,8 +404,8 @@ pending settlement rather than fully active. ### Responsiveness 53. The Subscription Center MUST be fully functional on mobile - viewports. Subscription Cards MUST stack vertically on narrow - screens. + viewports without hiding subscription information or management + actions required by this spec. 54. All management actions on detail pages MUST be accessible and usable on mobile viewports. @@ -412,8 +437,7 @@ The following rules use SHOULD and reflect intended behavior that is not yet enforced in the current codebase: 1. The system SHOULD support additional subscription types beyond the - initial four. The group-based layout SHOULD accommodate new product - types without structural changes to the page. + initial four without disrupting access to existing products. 2. The system SHOULD surface upcoming renewals or billing events on the landing page (e.g. "renews in 3 days") to help users @@ -425,6 +449,27 @@ not yet enforced in the current codebase: ## Changelog +### 2026-05-28 -- Personal product navigation and return context + +- Defined access to each personal product and restoration of product context after detail-page navigation. +- Kept independent loading, terminal history, and available-product behavior within each product view. + +### 2026-05-27 -- Token Plan Plus pilot operations and UI behavior + +- Accepted billing-sweep local cleanup timing for the pilot and defined admin-console manual credential revocation. +- Added admin-only explicit key reveal, validate-on-upload inventory, mutation-time BYOK warnings, no prepaid extensions, and local-time grace display. +- Dropped Coding Plans admin-action audit history for the initial pilot while retaining secret-handling restrictions and inventory disposition state. + +### 2026-05-27 -- Coding Plans manual MiniMax revocation handling + +- Recorded manual provider revocation as the initial MiniMax operational workflow. +- Kept local cleanup separate from upstream revocation processing by authorized support. + +### 2026-05-27 -- Coding Plans ordinary MiniMax BYOK setup + +- Replaced read-only managed-key behavior with automatic ordinary MiniMax BYOK setup and normal key management. +- Defined occupied-MiniMax purchase blocking, billing separation, and conditional installed-key cleanup. + ### 2026-05-12 -- KiloClaw price-version display behavior - Added KiloClaw Subscription Card, detail, plan-switch, and Available diff --git a/apps/web/public/logos/minimax.svg b/apps/web/public/logos/minimax.svg new file mode 100644 index 0000000000..cca722983a --- /dev/null +++ b/apps/web/public/logos/minimax.svg @@ -0,0 +1 @@ +MiniMax \ No newline at end of file diff --git a/apps/web/src/app/(app)/components/PersonalAppSidebar.tsx b/apps/web/src/app/(app)/components/PersonalAppSidebar.tsx index 8d266db392..6cafe5b52e 100644 --- a/apps/web/src/app/(app)/components/PersonalAppSidebar.tsx +++ b/apps/web/src/app/(app)/components/PersonalAppSidebar.tsx @@ -3,7 +3,7 @@ import { Sidebar, SidebarContent, SidebarHeader } from '@/components/ui/sidebar'; import { useUser } from '@/hooks/useUser'; import { useKiloClawStatus } from '@/hooks/useKiloClaw'; -import { useEffect, useMemo, useState } from 'react'; +import { useState } from 'react'; import { Code, Coins, @@ -197,6 +197,7 @@ export default function PersonalAppSidebar(props: React.ComponentProps = [ { @@ -262,13 +263,16 @@ export default function PersonalAppSidebar(props: React.ComponentProps( - isKiloClawPath && hasKiloClawInstance ? 'kiloClaw' : 'main' - ); - - useEffect(() => { - setSidebarMenu(isKiloClawPath && hasKiloClawInstance ? 'kiloClaw' : 'main'); - }, [hasKiloClawInstance, isKiloClawPath]); + const [sidebarMenuOverride, setSidebarMenuOverride] = useState<{ + pathname: string; + menu: 'main' | 'kiloClaw'; + } | null>(null); + const sidebarMenu = + hasKiloClawInstance && sidebarMenuOverride?.pathname === pathname + ? sidebarMenuOverride.menu + : isKiloClawPath && hasKiloClawInstance + ? 'kiloClaw' + : 'main'; const kiloClawEntryItems: Array<{ title: string; @@ -282,7 +286,7 @@ export default function PersonalAppSidebar(props: React.ComponentProps setSidebarMenu('kiloClaw'), + onClick: () => setSidebarMenuOverride({ pathname, menu: 'kiloClaw' }), isActive: isKiloClawPath, suffixIcon: ChevronRight, }, @@ -304,22 +308,18 @@ export default function PersonalAppSidebar(props: React.ComponentProps setSidebarMenu('main'), + onClick: () => setSidebarMenuOverride({ pathname, menu: 'main' }), }, ]; - const allUrls = useMemo( - () => - [ - kiloClawBaseUrl, - ...dashboardItems, - ...kiloClawItems, - ...cloudItems, - ...accountItems, - ...startItems, - ].map(i => (typeof i === 'string' ? i : i.url)), - [kiloClawBaseUrl, dashboardItems, kiloClawItems, cloudItems, accountItems, startItems] - ); + const allUrls = [ + kiloClawBaseUrl, + ...dashboardItems, + ...kiloClawItems, + ...cloudItems, + ...accountItems, + ...startItems, + ].map(i => (typeof i === 'string' ? i : i.url)); return ( diff --git a/apps/web/src/app/(app)/subscriptions/coding-plans/[subscriptionId]/page.tsx b/apps/web/src/app/(app)/subscriptions/coding-plans/[subscriptionId]/page.tsx index 4b9c89c272..83bd228d90 100644 --- a/apps/web/src/app/(app)/subscriptions/coding-plans/[subscriptionId]/page.tsx +++ b/apps/web/src/app/(app)/subscriptions/coding-plans/[subscriptionId]/page.tsx @@ -1,10 +1,16 @@ import { PageContainer } from '@/components/layouts/PageContainer'; import { CodingPlanDetail } from '@/components/subscriptions/coding-plans/CodingPlanDetail'; -export default function CodingPlanSubscriptionPage() { +export default async function CodingPlanSubscriptionPage({ + params, +}: { + params: Promise<{ subscriptionId: string }>; +}) { + const { subscriptionId } = await params; + return ( - + ); } diff --git a/apps/web/src/app/(app)/subscriptions/page.tsx b/apps/web/src/app/(app)/subscriptions/page.tsx index 0b39b5f38b..dcf21ee045 100644 --- a/apps/web/src/app/(app)/subscriptions/page.tsx +++ b/apps/web/src/app/(app)/subscriptions/page.tsx @@ -1,5 +1,6 @@ import { PersonalSubscriptions } from '@/components/subscriptions/PersonalSubscriptions'; +import { CODING_PLANS_PURCHASE_ENABLED } from '@/lib/config.server'; export default function SubscriptionsPage() { - return ; + return ; } diff --git a/apps/web/src/app/admin/coding-plans/CodingPlansOperationsContent.tsx b/apps/web/src/app/admin/coding-plans/CodingPlansOperationsContent.tsx new file mode 100644 index 0000000000..87867a54c5 --- /dev/null +++ b/apps/web/src/app/admin/coding-plans/CodingPlansOperationsContent.tsx @@ -0,0 +1,532 @@ +'use client'; + +import { useReducer } from 'react'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import { ExternalLink, RefreshCw, ShieldAlert, Upload } from 'lucide-react'; +import { toast } from 'sonner'; + +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Label } from '@/components/ui/label'; +import { Skeleton } from '@/components/ui/skeleton'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Textarea } from '@/components/ui/textarea'; +import { useTRPC } from '@/lib/trpc/utils'; + +const PLAN_ID = 'minimax-token-plan-plus'; + +type OperationsState = { + entriesText: string; + completeInventoryId: string | null; + failureInventoryId: string | null; + failureReason: string; +}; + +const INITIAL_OPERATIONS_STATE: OperationsState = { + entriesText: '', + completeInventoryId: null, + failureInventoryId: null, + failureReason: '', +}; + +function updateOperationsState(state: OperationsState, update: Partial) { + return { ...state, ...update }; +} + +export function CodingPlansOperationsContent() { + const trpc = useTRPC(); + const [state, updateState] = useReducer(updateOperationsState, INITIAL_OPERATIONS_STATE); + const { entriesText, completeInventoryId, failureInventoryId, failureReason } = state; + const setEntriesText = (entriesText: string) => updateState({ entriesText }); + const setCompleteInventoryId = (completeInventoryId: string | null) => + updateState({ completeInventoryId }); + const setFailureInventoryId = (failureInventoryId: string | null) => + updateState({ failureInventoryId }); + const setFailureReason = (failureReason: string) => updateState({ failureReason }); + + const countsQuery = useQuery(trpc.codingPlans.adminKeyInventory.queryOptions({})); + const queueQuery = useQuery(trpc.codingPlans.adminRevocationQueue.queryOptions({})); + + const refreshOperations = async () => { + await Promise.all([countsQuery.refetch(), queueQuery.refetch()]); + }; + + const uploadMutation = useMutation( + trpc.codingPlans.adminUploadKeys.mutationOptions({ + onSuccess: async result => { + setEntriesText(''); + toast.success( + `${result.inserted} validated credential${result.inserted === 1 ? '' : 's'} added to inventory.` + ); + await refreshOperations(); + }, + onError: error => toast.error(error.message || 'Credential validation or upload failed.'), + }) + ); + const completeMutation = useMutation( + trpc.codingPlans.adminMarkRevocationComplete.mutationOptions({ + onSuccess: async () => { + setCompleteInventoryId(null); + toast.success('MiniMax plan marked revoked.'); + await refreshOperations(); + }, + onError: error => toast.error(error.message || 'Unable to mark credential revoked.'), + }) + ); + const failureMutation = useMutation( + trpc.codingPlans.adminMarkRevocationFailed.mutationOptions({ + onSuccess: async () => { + setFailureInventoryId(null); + setFailureReason(''); + toast.success('Revocation failure recorded for retry.'); + await refreshOperations(); + }, + onError: error => toast.error(error.message || 'Unable to record revocation failure.'), + }) + ); + const requeueMutation = useMutation( + trpc.codingPlans.adminRequeueRevocation.mutationOptions({ + onSuccess: async () => { + toast.success('Credential requeued for manual revocation.'); + await refreshOperations(); + }, + onError: error => toast.error(error.message || 'Unable to requeue credential.'), + }) + ); + + const submittedEntries = entriesText + .split('\n') + .map(entry => entry.trim()) + .filter(entry => entry.length > 0); + const workItems = queueQuery.data ?? []; + const inventoryCounts = countsQuery.data ?? []; + const totalCredentialCount = inventoryCounts.reduce((total, item) => total + item.count, 0); + const countCredentialsByStatus = (status: string) => + inventoryCounts.reduce((total, item) => total + (item.status === status ? item.count : 0), 0); + const inventorySummary = [ + { + label: 'Total credentials in system', + count: totalCredentialCount, + detail: 'All inventory states', + }, + { + label: 'Available credentials in system', + count: countCredentialsByStatus('available'), + detail: 'Ready for assignment', + }, + { + label: 'Revoked credentials', + count: countCredentialsByStatus('revoked'), + detail: 'Confirmed complete', + }, + { + label: 'Pending revocation credentials', + count: countCredentialsByStatus('revocation_pending'), + detail: 'Awaiting manual action', + }, + ]; + + return ( +
+
+
+

Coding plans operations

+

+ Manage validated Token Plan Plus inventory and manual MiniMax credential revocation. +

+
+ +
+ + + + void refreshOperations()} + onComplete={setCompleteInventoryId} + onFailure={setFailureInventoryId} + onRequeue={inventoryKeyId => requeueMutation.mutate({ inventoryKeyId })} + onEntriesTextChange={setEntriesText} + onUpload={() => uploadMutation.mutate({ planId: PLAN_ID, entries: submittedEntries })} + /> + + setCompleteInventoryId(null)} + onComplete={inventoryKeyId => completeMutation.mutate({ inventoryKeyId })} + onCloseFailure={() => setFailureInventoryId(null)} + onFailureReasonChange={setFailureReason} + onFailure={(inventoryKeyId, reason) => failureMutation.mutate({ inventoryKeyId, reason })} + /> +
+ ); +} + +type InventorySummaryItem = { + label: string; + count: number; + detail: string; +}; + +type RevocationWorkItem = { + inventoryKeyId: string; + planId: string; + upstreamPlanId: string; + status: string; + revocationRequestedAt: string | null; + revocationAttemptCount: number; + lastRevocationError: string | null; +}; + +function InventorySummaryCards({ + items, + isLoading, + isError, +}: { + items: InventorySummaryItem[]; + isLoading: boolean; + isError: boolean; +}) { + return ( +
+ {items.map(summary => ( + + +

{summary.label}

+ {isLoading ? ( +
+
+ ))} +
+ ); +} + +function OperationsTabs({ + workItems, + queueLoading, + queueError, + entriesText, + submittedEntries, + uploadPending, + requeuePending, + onRefresh, + onComplete, + onFailure, + onRequeue, + onEntriesTextChange, + onUpload, +}: { + workItems: RevocationWorkItem[]; + queueLoading: boolean; + queueError: boolean; + entriesText: string; + submittedEntries: string[]; + uploadPending: boolean; + requeuePending: boolean; + onRefresh: () => void; + onComplete: (inventoryKeyId: string) => void; + onFailure: (inventoryKeyId: string) => void; + onRequeue: (inventoryKeyId: string) => void; + onEntriesTextChange: (entriesText: string) => void; + onUpload: () => void; +}) { + return ( + + + Manual revocation queue + Upload validated inventory + + + + + +
+ Manual revocation queue + + Pending and failed issued credentials requiring action in MiniMax admin tooling. + +
+ +
+ +
+ + + + Inventory item + MiniMax plan ID + Status + Requested + Attempts + Latest failure + Actions + + + + {queueError ? ( + + + Unable to load manual revocation work. Refresh to retry. + + + ) : workItems.length === 0 ? ( + + + {queueLoading ? 'Loading manual work...' : 'No revocation work pending.'} + + + ) : ( + workItems.map(item => ( + + +
{item.inventoryKeyId}
+
{item.planId}
+
+ + {item.upstreamPlanId} + + + + {formatStatus(item.status)} + + + + {formatTimestamp(item.revocationRequestedAt)} + + + {item.revocationAttemptCount} + + + {item.lastRevocationError ?? 'None'} + + +
+ + + {item.status === 'revocation_failed' ? ( + + ) : null} +
+
+
+ )) + )} +
+
+
+
+
+
+ + + + + Upload validated inventory + + Enter one MiniMax credential and plan ID pair per line. Each credential is tested + through ordinary MiniMax routing before encrypted storage as available inventory. + + + +
+ +