Multi-Tenancy & Per-Team Partition (V11)
A single vultrino can serve multiple teams/tenants, each with its own enforcement posture and credential isolation — so a federated enterprise can run one team in enforce mode while another is observe-only, on the same instance.
Tagging a principal with a tenant
An API key or use token carries an optional tenant. For a use token, set it at the admin mint:
curl -XPOST .../api/v1/tokens -H "Authorization: Bearer vk_admin_..." -d '{
"name": "team-b-bot", "credential_scope": "*", "tenant": "team-b"
}'
The principal's tenant is what selects its enforcement mode and scopes its credential access.
Per-tenant enforcement mode
Configure each tenant's mode under [[tenants]]:
[[tenants]]
id = "team-a"
mode = "enforce" # the default — a policy Deny blocks the action
[[tenants]]
id = "team-b"
mode = "observe" # a policy Deny is recorded + emitted but NOT blocked
enforce(the default, and the mode for any untenanted or unlisted principal — fail-closed): a policyDenyblocks the action as usual.observe: a policyDenyis downgraded to allow — the action runs, a warning is logged, and apolicy.observed_denialevent is emitted to the signed outbox (carrying the credential, action, and what would have happened). This lets a team onboard and watch what would be blocked before flipping toenforce, while other teams enforce on the same vultrino. Note this also downgrades the engine's fail-closedno_policydefault-deny, so in an observe tenant a credential lacking an explicit allow policy is usable (the point of observe-only onboarding) — size the blast radius accordingly.
Observe mode downgrades an authorization-posture denial only. The following are security/financial/abuse boundaries and are not observable-away — they hold even in an observe tenant: cross-tenant isolation (below), use-token scope, RBAC, the dual-control gate, SpendCap / RateLimit resource guards (a credential under a per-action spend cap or a rate cap is never downgraded — those are financial/abuse boundaries, not authorization posture; conservatively, if any policy matching a credential carries a spend/rate rule, observe mode enforces all of that credential's denials), and — critically — a halt / kill switch (a halted agent stays blocked).
Credential isolation
A credential can be tagged to a tenant via its tenant metadata:
vultrino meta set team-a-secret tenant team-a
A principal may only use credentials in its own tenant; an untenanted credential is shared (usable by any tenant). A principal in team-b attempting to use a team-a-tagged credential is denied — regardless of team-b's enforce/observe mode (isolation is a hard boundary, not a policy that observe mode can downgrade).
Approval partitioning
When a gated action opens an approval, the approval is tagged with the opening principal's tenant — so it can be partitioned the same way credentials are. The partition rule is the visible_to_tenant predicate:
- A global view (no acting
tenant) sees every tenant's approval. - An untenanted (shared) approval is visible to every tenant (like an untenanted credential).
- Otherwise a tenant only sees its own approvals —
team-acan never see ateam-bapproval.
Where this is wired today:
- Admin metrics (
GET /api/v1/metrics) scopes its approval counts to the calling admin key'stenant(and echoes thetenant_scopeit applied) — so a tenant-scoped admin key sees only its own (+ shared) approvals in the read-back. - The web admin panel is a global console (the session admin carries no tenant): it lists and decides across all tenants. Per-tenant decision scoping is therefore an API concern — the
visible_to_tenantprimitive is the gate a tenant-scoped decision endpoint uses; the panel itself is intentionally global.