Skip to content
Docs Get started Mirrored upstream
hosted-allowances.md
Docs source v0.164.2 13d15fec6a14 View this file at the pinned commit ↗

Hosted capacity allowances

Organization entitlements are enabled only when a plan catalog or legacy plan metadata is configured. By default, local and self-hosted Hubs, including Hubs using --hosted-config for hosted identity and the web app, do not create plan assignments or apply plan allowances. Omitted or empty entitlements leaves plans disabled, just like a bearer-only Hub. Runner, host, policy and provider safety limits still apply. With the Cloud capacity catalog configured, no Stripe account, subscription, or card is necessary for the permanent Free base plan. Model-provider charges remain separate from hosting.

Capacity catalog

Plan Version USD per organization/month Concurrent work
Free 1 0 2
Starter 1 49 8
Team 2 99 20
Growth 2 249 50
Scale 2 Contact us Custom

The Cloud capacity catalog applies only when explicitly configured in entitlements; its base assignment is free version 1. team uses version 2 because version 1 is a legacy paid mapping. Growth and Scale moved to version 2 with the new prices; their version-1 records keep their stored prices for existing subscriptions and resolve with Growth and Scale concurrent work. Scale has no self-serve price; operators grant it, or a custom plan, as complimentary access. Free signup needs no card, Stripe customer, or subscription.

Concurrent work counts the organization's live claims: every running attempt, whatever its stage. A claim at the limit is refused with plan_capacity; the runner treats it like full capacity, the item stays in its lane, and it becomes claimable when a slot frees. Nothing is refused at filing time. Every tier, Free included, has native execution; paid tiers add hosted artifacts. No tier limits projects, members, repositories or runners.

Issues keep an abuse guard of 10,000 open issues across the organization, reported as unarchived_issues. Issues in a terminal lane (Done, Cancelled) and archived issues do not count. Issue creation, imports, restore and moves out of a terminal lane use the existing atomic mutation transaction. Luna chat and hosted AI are gated by AI credits and the monthly budget, not by tier. Runner, host, policy and provider safety limits continue to apply independently.

Capacity tier records resolve their concurrent-work allowance, open-issue guard, execution feature and unlimited projects from this catalog when read, so existing assignments and grants follow it without new versions; their stored names and prices remain authoritative. A grant of a capacity tier carries that tier's concurrent work and open-issue guard whatever its recorded scope. Legacy pilot_free and comp_team have no concurrent-work or project limit once the capacity catalog is installed. In any other plan, a configured concurrent_work is enforced and an omitted one is unlimited.

Configuration and plan versions

An explicit catalog enables plan allowances and must retain any referenced legacy versions. The following is the legacy pilot catalog, retained for compatibility; Cloud deployments configure the capacity catalog instead:

entitlements:
  base: {id: pilot_free, version: 1}
  window_seconds: 3600
  retention_windows: 24
  connected_seconds: 90
  invitation_seconds: 86400
  plans:
    - id: pilot_free
      version: 1
      features: [collaboration, native_execution, github_integration]
      allowances:
        members: 10
        projects: 10
        repositories: 10
        registered_runners: 10
        connected_runners: 10
        concurrent_work: 5
        api_mutations: 10000
        ingested_events: 10000
        collaboration_bytes: 67108864
        history_records: 10000
    - id: comp_team
      version: 1
      features: [collaboration, native_execution, github_integration, hosted_artifacts]
      allowances:
        members: 20
        projects: 20
        repositories: 20
        registered_runners: 20
        connected_runners: 20
        concurrent_work: 10
        api_mutations: 20000
        ingested_events: 20000
        collaboration_bytes: 134217728
        history_records: 20000

Omitting the entire section, or leaving it empty (entitlements: {}), applies no plan allowances. Any key inside the section, even plans: [], makes it an explicit catalog that must validate. With the pilot catalog above configured, every new organization starts on pilot_free, and comp_team exists only to be granted as complimentary access. comp_team doubles the pilot_free allowances and adds hosted_artifacts; its artifact allowances stay zero until an operator configures them (see below). Both sets of numbers are provisional. Within an explicit plan, omitted allowances other than concurrent_work are zero and omitted features are disabled. Negative values, unknown names and unsupported configuration bounds are rejected. Windows are whole multiples of 60 seconds, at most one day; telemetry retains at most 720 windows and 30 days. Invitation reservations last between one minute and seven days. These are validation bounds, not recommended public retention periods. Operators choose the values before hosting customers.

Plan records are immutable by (id, version). To change a plan, add another version and explicitly assign it. Configured base initializes a new assignment; restarting or changing the file does not overwrite an existing assignment or a later administrator decision. Existing plan versions remain available for audit and historical grants. Legacy plan_id, positive storage_quota_bytes, and positive event_quota configure the initial pilot plan when entitlements is absent; they cannot be mixed with the new section.

Administration and precedence

Configure a separate secret for entitlement administration:

entitlement_administrator: operator_example
entitlement_admin_token_env: DETENT_ENTITLEMENT_ADMIN_TOKEN

The environment variable must contain at least 32 bytes of independently generated secret material. Do not reuse the metadata reporter token. Ordinary staff, support impersonation, organization owners/admins and runners cannot grant complimentary access. This credential changes allowances only; it gives no customer project access or runner execution permission. Keeping it unconfigured disables remote plan mutations while allowing free access and usage reporting.

The authenticated operator posts to /api/v2/organizations/{organization}/entitlements. Each command requires a unique idempotency_key, current expected_revision, action, and bounded nonempty audit reason. The transaction records operator identity, command, reason, time and request hash. A matching retry changes nothing; a changed request under an existing key or a stale revision is rejected. Audit records remain in the owning organization database and never enter routine analytics.

{
  "idempotency_key": "pilot-access-example",
  "expected_revision": 1,
  "action": "grant",
  "grant_id": "pilot_access",
  "plan": {"id": "comp_team", "version": 1},
  "scope": ["projects", "concurrent_work"],
  "reason": "Approved pilot access"
}

GET on the same path with the same credential returns the organization's base and effective plan, current revision, persisted plan versions, and every grant with its recorded reason and administrator identity. No other credential can read it.

A create_plan command creates a new immutable version in the catalog without changing an organization's assignments. It requires a definition matching plan, with features, allowances, and optional display name and monthly USD cents. Creation advances the entitlement revision and uses the same audit and idempotency rules as assignments and grants. For example:

{
  "idempotency_key": "create-capacity-example",
  "expected_revision": 1,
  "action": "create_plan",
  "plan": {"id": "capacity_example", "version": 1},
  "definition": {
    "id": "capacity_example",
    "version": 1,
    "name": "Extended capacity",
    "features": ["collaboration"],
    "allowances": {"collaboration_bytes": 17179869184}
  },
  "reason": "Approved capacity increase"
}

Read the new revision, then assign or grant the created version. Creating an existing version under a different command key conflicts; it never overwrites the stored definition. File plans are startup seeds: matching definitions do nothing, and differing existing versions warn and retain the stored version.

Grant starts_at defaults to transaction time; optional expires_at uses an RFC3339 timestamp after the start. A revoke command names grant_id and a reason. base assigns a plan version. subscription assigns a plan version with a required future validity deadline; end_subscription clears that derived access when Stripe billing is disabled. With hosted subscription billing configured, authoritative Stripe reconciliation owns subscription-derived access and these two commands are rejected. Complimentary grants remain independent of payment records.

Granting from the platform console

On the shared entry, registry platform members with admin or billing roles may create plan versions, set the base plan, and grant or revoke complimentary plans. Subscription commands remain unavailable when Stripe owns access. Configure allocation.entitlement_admin_token_env so the entry can reach tenant entitlements. platform.bootstrap_admin_email restores the operator as an admin at startup. The deprecated entitlement_administrators, support_actors, and staff_emails seed an empty platform membership registry; they do not gate access after seeding. See the shared entry example.

Authorized members can read the base plan, effective plan and active grants, and copy an existing plan into a new immutable version, edit its features and allowances, and apply base or grant changes with a required reason. support and viewer roles, support impersonation sessions, and non-members cannot change plans. Customer organization ownership does not grant platform billing access.

The entry reads and changes the plan server-side through the tenant's private socket, presenting the allocation's entitlement credential; the credential never reaches the browser. It forwards the console's idempotency_key and expected_revision, grants the whole chosen plan (every feature and nonzero allowance), and records each applied change in the registry's append-only entitlement_changes table with the staff email, organization, plan, expiry and reason. A stale revision is shown as "someone changed this organization's plan; reload and try again".

The platform API at /api/cloud/platform/organizations/{organization}/entitlements accepts the same plan actions, with session CSRF protection or the dedicated entitlement bearer credential. The platform MCP endpoint /api/cloud/platform/mcp exposes platform_entitlements and platform_change_entitlement under the same current administrator authority. Customer API keys do not acquire this authority.

Reloading Cloud configuration

On Unix, SIGHUP reloads the existing cloud.yaml file. The full document, including restart-only fields, must parse and validate. The reloadable fields are allocation.entitlements, allocation.billing.prices, allocation.allowed_emails, and allocation.allowed_domains. Existing plan versions and Stripe price mappings remain immutable; use a new version or price rather than changing one already stored. The entitlement policy, including usage-window settings, and the active billing price list publish as one immutable snapshot in each tenant after the database transaction succeeds. New plans, reloads, and their reasons are recorded in hosted_plan_audit.

The entry validates every ready managed tenant, then prepares immutable plan records without exposing the new catalog, policy, or active prices. One atomic publication file selects the prepared settings for all managed tenants and future launches. Tenant Hubs read this shared publication through an entry-owned link beside their database; publication requires no tenant activation request. A rejected preparation, a lost reply after preparation commits, or a persistent transport outage leaves the previous publication active. Unpublished plan records cannot be read or assigned through the entitlement API. Preparation and reload logs retain actor and reason. This replaces tenant-by-tenant application and compensating restoration. Startup never replaces persisted assignments or grants.

After a successful reload, the entry applies the new sign-up allowlist to the next organization-creation permission check and the platform Sign-up allowlist read at /api/cloud/platform/allowlist. Empty email and domain lists allow anyone to create an organization. A failed reload retains the previous allowlist; existing organizations and sessions are unaffected.

Listeners, origins, database/state paths, signing and provider credentials, platform membership seeds, attachments, allocation paths and capacity, entitlement credentials, and billing settings other than prices require a restart. Reload logs list these fields and retain the running values, including for future tenant launches. SIGHUP does not restart the entry or tenant Hubs.

Resolution at the allocation transaction's clock sample is:

  1. Start with the organization base assignment.
  2. A subscription with a validity deadline strictly after now replaces the base.
  3. For each active grant, take the maximum allowance for each explicitly scoped resource and enable each explicitly scoped feature present in its plan. Grants do not add quantities together or enable other features from that plan.
  4. At the exact expiry timestamp the grant/subscription is no longer valid. Revoked grants have no effect. Stored assignments and customer data remain.
  5. Membership, project/runner permissions, policy approvals, repository gates, host capacity and provider safety/budget brakes always continue to apply.

Measurement and exhaustion policy

Allowance Consumption and enforcement
Members Locally active memberships plus unexpired invitations, measured for operation only. No seat-based plan limit applies.
Projects/repositories Stored allocations in the dedicated tenant Hub. Their creation/binding transaction must fit the allowance.
Registered/connected runners Non-revoked runner registrations, including legacy machine identities without a runner registration; connection means a heartbeat strictly newer than the configured cutoff. New enrollments and reconnecting idle runners cannot grow the count beyond the limit.
Concurrent work Unreleased leases whose expiry is strictly after the transaction clock. No plan concurrency limit applies. Operational runner and host capacity still apply.
API mutations Accepted allocation-changing transactions per configured UTC window. Matching native command retries do not consume another unit. Reads, billing, renew/release and completion/checkpoint events remain available.
Ingested events Newly stored collaboration events per window. Native retries and ordered duplicate event sequences do not add an event.
Collaboration bytes UTF-8 bytes of issue text/labels/assignees, comments, versions, event payload/actor records other than run telemetry (run.*) and scheduler decisions, artifact references, change source bundles, import snapshots and stored attachments. Idempotency receipts, attempt records, run telemetry and scheduler decisions are internal bookkeeping and do not count. This is logical retained payload, not disk billing. Database/WAL bytes are separately reported.
History records Collaboration events, immutable versions and ordered attempt-event receipts.

Checks and allocation writes use the same transaction. An operation that increases an exhausted resource is rolled back with HTTP 429, allowance_exhausted, the resource, allowance and prior consumption. Reductions and unchanged resources remain possible after a downgrade. Data is not automatically deleted to make an organization fit a smaller plan. Fenced checkpoint and completion events may grow bounded, validated execution history beyond the tier; ordinary edits do not receive this exemption. Existing request/payload and lease safety limits still bound each event. The worker must checkpoint or finish when ordinary mutations are exhausted. Existing history can still be read and exported; owner billing and administrator plan pages remain accessible.

Usage is available at /organization/plan to owners/admins and through owner-only /api/cloud/billing. The existing staff/reporter metadata endpoint includes the same bounded entitlement summary. Project access remains separate. No permanent banner is added. Model-provider spending is not presented as a Detent charge.

Hosted artifact storage and relay

Customer-mode artifact services never contact the hosted allowance endpoint and keep their configured local storage policy. Hosted services opt in explicitly, use the organization-bound publisher credential, and request allowances from the owning Hub. The first authorized hosted service ID binds the organization's artifact allowance owner. A second ID is refused; changing owners requires an operator-controlled migration of the catalog, not another independent service with the same full quota. Run exactly one process/catalog per service identity.

To enable hosted artifacts, include the hosted_artifacts feature and explicitly configure all of artifact_retained_bytes, artifact_reserved_bytes, artifact_bytes, artifact_upload_bytes, artifact_retention_seconds, and relay_bytes. They default to zero. Service policy intersects these allowances; a paid plan cannot raise the service's independent safety limits.

Reservation admission is serialized in the artifact service and considers stored bytes, unfinished reservations and relay headroom for upload plus verification readback. Duplicate reservations and verified parts reuse existing records. Each reservation pins its admitted part limit and retention deadline. Downgrades block new excess reservations while existing uploads may finish within their reserved bytes, pinned limits and original deadline. Reads/export and deletion remain available; their measured traffic can exceed the allowance and block new uploads for the rest of the window. This policy does not promise unlimited relay.

The service records attempted upload bytes, returned download bytes, and logical storage operations in bounded minute buckets. Failed writes conservatively count attempted bytes; SDK retries, HTTP overhead and provider-internal operations are not invoice measurements. The existing maintenance loop reports only aggregate retained/reserved bytes, current-window relay bytes, operation counts and sample time. Repeated samples use maxima for window counters, not additive charging. The Hub never reads the artifact database or receives object keys/content through this reporting path. Gauges reflect the last received sample and can lag during an outage; the owning service enforces its allocations locally.

Cost evidence and retention

Hub telemetry records request counts, response bytes, aggregate handler duration, heartbeat counts, accepted mutations/events and artifact usage. Telemetry stores only fixed metric names, integer counts and window timestamps. Expired buckets are pruned on subsequent samples; no public ingestion or marginal-cost claim is derived from these counters. Operators must measure idle process/database cost, heartbeat/request load, database growth, retention and their provider's billed traffic/operations before setting prices. The metrics are inputs for that review, not customer model spend or a dollar estimate.

Allocation/grant records, audit records and customer collaboration history are durable records, distinct from bounded operational telemetry. This change does not silently purge customer history, alter public retention promises, configure WorkOS, enable a production artifact service or launch paid billing.

Compatibility and cost drivers

Starting with the capacity catalog retains existing base assignments, grants, subscription deadlines, stored plan JSON, and Stripe price-to-plan bindings. Legacy pilot_free and comp_team versions remain available. Once the capacity catalog is installed, they resolve with the open-issue guard and without project, concurrent-work, runner or repository limits. Their stored records and paid-price bindings stay immutable. Existing pilot organizations keep complimentary execution access and are labeled as legacy access. Changing catalogs does not purchase, cancel, or change a subscription. Never repoint the existing $49 test price: retain its exact versioned-plan mapping. Existing custom catalogs must include their historical referenced versions when adopting new versions. Base changes and scoped grants use the audited entitlement administrator; Stripe reconciliation continues to own subscription access.

The existing owner billing usage export (/api/cloud/billing) adds cost_drivers: active/unarchived and archived native issue counts, separate database and WAL bytes, current-window request count and request/response bytes, reported artifact retained/reserved bytes, reported relay bytes, and separately attributable Hub coordinator AI tokens, turns, and estimated USD cost for the displayed period. Unmeasured storage/relay inputs, absent request samples, and incompletely priced AI cost are JSON null, meaning unknown. A missing WAL file is measured as zero. Request bytes are known only when every sampled request declared its body length. Metrics contain counts and estimates, never customer content or credentials. These inputs support operator review; #2308 owns operating-cost measurements.