Data model — credentials, metering and routing policy
Seventeen Mongo collections hold what funds a run: a credential sealed at rest, the connector alias a workflow addresses it by, the bundle that carries it to a runner pod, the four meters that bill it, and the policy that decides where it may route.
Drawn from the code at commit
6c136a77d— main at v3.281.3, with #2652, #2005 PR-A and the PR-B'1 carriage; how this page is kept true: README.
Block 1 — a credential at rest, and who may see it
api_keys,generic_secretsandbot_secret_bindingsare scoped by the team and fail closed. Twoapi_keysreads and writes are the stated exceptions:GetOwnedcarries no tenant term at all and is bounded byscope_userinstead — only a key's own owner is served that way — andMarkFingerprintUsedfails open on a tenant-less context. Everyapi_keyswrite is a$setof the fields it names (Patch); the store has no whole-document writer, so a row keeps the columns a newer server build wrote on it and a stale read never carries back anis_defaultor alast_used_atanother writer just moved.oauth_credentialshas notenant_idfield and no tenant filter at all. Its whole tenancy model lives in theuser_idowner namespace, and the spellings do not mean what they look like:org:carries a TEAM id, while the genuinely org-level one isorgtier:.- A bot id is a slug, never a key:
bot_secret_bindings.bot_id,api_keys.botsandcred_pledges.botsare compared by exact string equality — onlyapi_keys.botsis canonicalised; bindings and pledges keep the raw spelling — and the launch resolves a slug through teambot_sources, then platformbot_sources, then the baked catalog on the server's own filesystem. An FK tobot_sources._id(a UUID) would be wrong. (scope_team, name)ongeneric_secretsis held by NOTHING: two team-wide secrets may share a name.teams,usersandorgsare anchors — the identity model is its own page. Every edge toorgson this page is zero-or-one exceptcred_pools.org_id, so no collection here but a pool may be read as proving a parent org.
Block 2 — a credential in flight: the sealed bundle and the pool lease
- A sealed bundle carries the credential itself, not a pointer to it. The ref maps ride beside it, and only the OAuth ref is drawn, dotted, because no query on
run_secretsresolves them — they live insidesealed_bundle, not in an indexed field. Theapi_keysedge carries no id at all. - The link runs the other way from what a reader expects: the run document holds no
secrets_ref.run_secretspoints back at the run. - Deleting a pledge removes its own document and nothing else: the
cred_pool_usagerows keyed on its id survive until their own 400-day TTL, which is why that edge is optional at both ends. - No DOCUMENT id links a lease and a bundle — which is why no edge joins them. The lease's inlined
source/ref/key_idname the pledged credential and are copied from the pledge, never resolved independently — which is why the lease draws no second edge toapi_keys. - The zero
audienceadmits only teams of the owning org — the opposite polarity fromorgs.credential_audience, whose zero value admits nobody.
Block 3 — metering and policy: four meters on four identities
- Four meters, four identities, and none substitutes for another. Their tier vocabularies differ too:
credential_usage.tierhas four values because it merges a tenant's key and its subscription intoteam, whereruns.credential_tierskeeps the publisher's five and holdsbyokandoauth-forfaitapart. - Five dotted edges in this block carry the parent id inside a composed string:
usage_windows.key,org_usage._id,llm_routing_policies._id. No store joins onorgs._idorteams._id, so none of them is a key lookup. The solid edges are the ones whose field is the reference:credential_usage.tenant_idandmodel_prefs.tenant_id, both written verbatim, andplatform_credentials.teams, an allow-list of team ids. The sixth dotted edge,runner_pool, is a pool name resolved in therunner_poolsfamily. org_usage's subject is a team id whenever the launch gate read the zeroOrg— an org-less team, or an org the gate could not read — so anorg|subject is not proof of an org, and a meter row's owner is zero-or-one.usage_windows.keycarries five scope spellings —platform,local,tenant:<id>,org:<id>and the literalaccount. Three of its windows (frequency,spend,auth) are not provider windows at all.platform_settingsis one collection, one document per family. This page draws the four a run's funding and routing read —platform_credentials,runner_pools,usage_capsandbudget_floor; the bot-facing families are drawn on data-model-forge-bots.md. Sovereign runner pools are therunner_poolsfamily, not a collection of their own.llm_routing_policiesis not aplatform_settingsfamily: its documents are tenant data, one per org or team, whileplatform_credentials.routingholds the platform level of the same policy. A document is a team's or an org's, never both — its_idcarries exactly one of the two prefixes.
Block 4 — connector connections: a team's alias for one connector instance
- The unique
(tenant_id, connector, alias_norm)index is what keeps the alias rule true between replicas: the memory and file twins check alias-free under a lock they hold, and a cloud deployment has no such lock. Two concurrent Creates of one alias both passing an in-code check would answer one name twice — the index turns the second into a duplicate-key refusal the store reports asErrExists. - The tenant is in every filter, not merely on the record: a read, update or delete that exists but belongs elsewhere is not found, never forbidden.
updated_atis the record's own version — the update writes against the stamp it read, so a successful read followed by a zero-match replace is a conflict, not a missing record. auth_placementrecords what the scheme said at creation, so a workspace-tier package update that keeps the connector's id but moves the credential into a query string cannot move a stored credential with it: the stored placement is compared at resolution and a mismatch is refused by name.
The credential tier walk
walkCredentialPlan enumerates seven tiers in a fixed order. These seven are the plan's own; what a run is stamped with is the five of runs.credential_tiers.
- The two gates are not symmetric:
HAScloses the Pool tier and nothing else, so a team holding its own unpinned key still walks Platform. Of the two, onlyPGcloses Platform. SOVis a closure, not the skip of one tier: a team mapped to a sovereign runner pool, or holdingllm_fallback: none, loses Org, Pool and Platform. The two conditions diverge at the refusal: underllm_fallback: nonethe launch is refused asRFdraws it, while a pool-mapped team on the default fallback is, by default, at most warned.envFundedremoves every tier except Generic. It holds only for a run whose every model route names anopenai_compatible/…model, and not for every such run — not, for example, when a route's model or provider text reads the environment.RF's funding check reads the providers the walk attributes to the run's routes (spendableProviders), so a route the walk cannot read — an unpinned agent, a human gate's or a tool recovery's model, a forfait rung of the routing ladder, for example — can turn a refusal into a start, unless the policy isllm_fallback: noneand the team holds no credential of its own.- After the walk, the fallback door (
acquireDoorGrant) can lease a pool credential;SOVshuts it. - Tier 7 is not a fallback credential but a retry posture: a credential a tier passed over comes back only where the restore's own vetoes allow it; the run then parks on its refusal with a durable retry.
- Outside the sovereign posture nothing here refuses a credential-less run by default.
Collections
| collection | package | document struct | tenant field (values) | unique / TTL indexes | twin |
|---|---|---|---|---|---|
api_keys | pkg/secrets | ApiKey | tenant_id: team | orgtier:<org> | platform: | none unique; no TTL | none (cloud-only) |
connections | pkg/connection | mongoDoc (Connection + derived alias_norm) | tenant_id: team | tenant_connector_alias unique on (tenant_id, connector, alias_norm); no TTL | file connections.json — the memory twin checks aliases under a lock the Mongo twin replaces with the unique index |
oauth_credentials | pkg/secrets | OAuthRecord | none — user_id namespace: user | org:<team> | orgtier:<org> | platform: | user_id+kind+rank unique; no TTL | none (cloud-only) |
oauth_pending | pkg/secrets | OAuthPending | none — owner_key namespace | owner_key+kind unique; TTL expires_at (10 min) | none (cloud-only) |
generic_secrets | pkg/secrets | GenericSecret | tenant_id: team (empty in the file store, which pins scope_team: local) | none unique; no TTL | file secrets.json — the file and memory twins keep no order among duplicate names |
bot_secret_bindings | pkg/secrets | BotSecretBinding | tenant_id: team | tenant_id+bot_id+secret_name_for_workflow unique | none (cloud-only) |
run_secrets | pkg/secrets | RunSecretsRecord | tenant_id: team (fails open with no ctx) | none unique; TTL expires_at (24 h) | none (cloud-only) |
cred_pools | pkg/credpool | Pool | none — org_id names the owning org (GetByOrg resolves on it) | none unique (pool_org only); no TTL | none (cloud-only) |
cred_pledges | pkg/credpool | Pledge | none — _id is the donor's | none unique; no TTL | none (cloud-only) |
cred_leases | pkg/credpool | Lease | tenant_id: team (audit + the cross-team takeover guard) | none unique; TTL acquired_at (90 d) | none (cloud-only) |
cred_pool_usage | pkg/credpool | ledgerDoc | none — _id is pledge + period | none unique; TTL period_start (400 d) | none (cloud-only) |
credential_usage | pkg/credusage | usageDoc | tenant_id: team | empty (an _id segment) | none unique; TTL month_start (400 d) | none (cloud-only) |
cred_slots | pkg/store | credSlotDoc | none — _id is the credential fingerprint, one cap shared by every tenant a platform key serves | none unique; no TTL (the reconcile pulls a holder whose run document is gone) | memory-only twin credslot_fs.go (one process, one mutex) |
usage_windows | pkg/usagecap | readingDoc | none — scope inside key: platform | local | tenant:<id> | org:<id> | account | none unique; TTL observed_at (14 d) | memory-only |
org_usage | pkg/orgusage | usageDoc | none — _id subject is an ORG (a team for an org-less team, or an org the gate could not read) | none unique; TTL month_start (400 d) | none (cloud-only) |
model_prefs | pkg/modelprefs | Pref | tenant_id: team | empty in local mode (an argument, not ctx) | tenant+user+key unique, tenant+user+slot unique partial | file model-prefs.json |
llm_routing_policies | pkg/platformcfg | RoutingPolicyRecord | none — _id is team:<id> | org:<id> | none (no EnsureSchema) | none (cloud-only) |
platform_settings | pkg/platformcfg, pkg/usagecap (the budget_floor type is pkg/budgetfloor's) | PlatformCredentials, RunnerPools, settingsDoc, budgetfloor.Policy | none — platform-scoped, one document per family | none | none (cloud-only) |
runs | pkg/store/mongo | store.Run | tenant_id: team | drawn on data-model-runs.md | file runs/<id>/run.json |
teams | pkg/identity | Team | _id IS the tenant | slug unique, runner_pool unique partial | none (cloud-only) |
users | pkg/identity | User | none | email unique | none (cloud-only) |
orgs | pkg/identity | Org | _id IS the org | slug unique; migrated_from_team_id unique partial | none (cloud-only) |
FS: baked catalog | bots/ | — | — | — | FS, not Mongo |
Related
- secrets-reference.md — every secret kind, the scope semantics, the sealer wire format and the per-record AAD table; byok.md for
api_keysfield by field and oauth-forfait.md for the PKCE connect flow, the ranks and the refresh worker - cloud-llm-credentials.md — the maintained resolution guide across every tier; credential-pool.md for pledges, leases, the budget clamp and the audience; sovereign-pools.md for the runner-pool registry and
llm_fallback: none - usage-caps.md — the provider usage-window guard and the per-credential key format; quotas-and-limits.md for the launch-admission gate and the per-credential usage view
- agents/engine/tenancy-credentials-spend.md — the per-package invariants of this whole domain
- adr/123-run-bundle-dek-in-envelope.md · adr/121-adaptive-routing-policy.md · adr/032-quota-optimistic-inc-with-detached-context-rollback.md · adr/090-runtime-operational-settings-db-backed.md
