Files
turnstone/docs
metaclassing 9adde920d4 feat(models): per-alias backend auth via Entra OBO and app identity (#898)
Adds a per-alias `auth_mode` on model definitions so a model backend can
authenticate to an Entra-fronted gateway with a per-request minted token instead
of one shared static API key, letting the gateway attribute calls to the actual
user or to the app as a machine identity.

- `static` (default, unchanged) sends the stored `api_key`.
- `entra_obo` mints a per-user On-Behalf-Of token for `obo_audience` from the
  caller's captured refresh credential.
- `entra_app` mints an app-identity token via the client-credentials grant, and
  covers userless turns that OBO cannot.

Reuses the existing OBO grant legs, refresh-token rotation CAS, cluster advisory
lock and the `mcp_user_tokens` mint-cache, keyed under synthetic
`__model_obo__:<audience>` / `__model_app__:<audience>` rows. The token binds at
the call site through `client.with_options(api_key=...)` so each SDK emits it on
its own auth path rather than through header injection.

Migration 068 adds `auth_mode` and `obo_audience`. Both are additive and existing
rows default to `static`, so behaviour is unchanged unless an alias opts in.

Operator controls: `model.auth_audience_allowlist` is an exact-match allow-list
that gates which audiences may be configured and denies all by default, and
changing a mode or audience requires `admin.mcp`. `model.auth_fail_closed`
decides whether a failed mint may fall back to an explicitly configured static
key. A delegated call with no user, or a dynamic alias with no real static key,
always refuses.

Two changes here apply regardless of whether any alias opts in:

- Storage and app state are now wired into the console MCP client manager. This
  fixes per-user `oauth_user` / `oauth_obo` dispatch for coordinator-hosted
  sessions, which previously raised `RuntimeError` on first call because
  `set_app_state` was only ever called on the node.
- Unattended watch restores and `--resume` resolve the persisted workstream
  owner instead of constructing the session under an empty principal. A
  workstream with no owner is now a permanent refusal rather than an anonymous,
  auto-approved run.
2026-08-02 15:15:02 -07:00
..