# ToolHost — Tenancy & RBAC / Isolation Matrix (E-TEN-3 / E-TEN-5)

*Anchor: project-docs/03-engineering-design/02-tenancy-rbac.md §6–7; 00-FINAL-GOAL.md §5 #9,
01-product-spec/02-capabilities-enterprise.md E-TEN-3/E-TEN-5.*

## Visibility ≠ permission (E-TEN-3)

Two independent axes govern every resource. Neither substitutes for the other — a visibility
check passing contributes nothing to an action check, and vice versa.

**Visibility** (a property of the resource row): `private` (owning principal only), `team`
(members of the owning team), `org` (every principal in the org, including principals with no
team memberships).

**Role** (what an authenticated, visible-to principal may *do*): view, execute, approve
(schema-pin), manage.

| Role | Views resource? | Executes it? | Approves schema pins? | Manages it (toggle/delete)? |
|---|---|---|---|---|
| Viewer | team/org-scoped read | no | no | no |
| Developer | yes | yes | no | no |
| Approver | yes | yes | yes | no |
| Admin/Owner | yes | yes | yes | yes |

A Viewer sees team resources but cannot toggle them; a Developer executes but cannot approve
schema pins — the matrix is explicit and published, not implied by role name alone.

## Tenant isolation is complete and silent (E-TEN-5)

[Anchor: 00-FINAL-GOAL.md §5 #9] Every governed row is org-keyed through a store layer that
composes the filter itself; cross-tenant reads 404 indistinguishably from nonexistence; forged
team claims are rejected; audiences never cross.

Mechanically, this holds because:

- Callers never write `WHERE` clauses — they never write SQL. The store package exposes only
  tenancy-composing interfaces (`org_id` is stamped from the request context, never from
  caller-supplied data).
- Ownership mismatches return the same not-found error as genuine absence — there is no
  existence oracle a probe could use to distinguish "wrong tenant" from "doesn't exist."
- Every core mechanism is tenant-keyed end-to-end: policy ceilings, rate/quota buckets (workspace
  folded into the aggregate key), credential access, admin object routes, and audit export
  filters.
- The one cross-tenant path (platform administration) is a distinct interface, distinct context
  type, enumerable by inspecting a single interface, and every call across it is evidenced
  `platform_bypass: true`.

## Revocation and staleness, exactly (§5.7 / S4b)

[Anchor: 00-FINAL-GOAL.md §5 #7; 06-flows/09-sec-full-flow.md S4b] Key revocation and principal
status-flip bind **≤30s** (hot-path liveness). Team membership is a **mint-time snapshot**
bounded by token TTL — **1h default, 15m compliance profile**; removal keeps allows up to one
TTL, leaving sheds team denies at re-mint; durable prohibitions belong at org level; the hard cut
is key revocation or status flip. Stated to Sec verbatim (`06-flows/09` S4b): a principal
*removed* from a team retains that team's allows for up to one TTL; a principal who *leaves* a
team sheds that team's denies at re-mint — prohibitions that must survive membership changes
belong at org level. The offboarding proof therefore uses status-flip/key-revoke, never "removed
from the team," as the severing act.

## What this rules out

An export or a list call physically cannot contain another org's rows — there is no parameter
that would let a caller ask for one, and no silent union of global and per-org scope; global
visibility is opt-in per call site.
