config — top-level modules
config.loader
FACADE — the config loader now lives in core.config.loader (bp-067, finding-0103).
core owns its config-loading code (self-contained, stdlib-only, network-free). This module is a
thin re-export so the ~147 non-core importers (from config.loader import get_config, Config, …)
are untouched — outside → core is the allowed arrow. It DEFINES nothing but the token-capable
get_secret (the machinery zone MAY reach the network Vault path config.secrets_backend, which
core may not); the env path delegates to core.config, the single source of truth.
The public API is re-exported explicitly (not import *) so get_secret is unambiguously the
token-capable form here while core.config's stays env-only — the trust-boundary split, visible to
the type checker. tests that MONKEYPATCH loader internals (LEVERS_OVERLAY/_LOCAL/get_config)
must patch core.config.loader — the real module whose load_config runs (finding-0104).
get_secret(name, token=None)
Token-capable secret access (the machinery form). With no token: the environment (delegates
to core.config). With a token: a Vault ephemeral token minted for the calling agent's role —
this branch reaches config.secrets_backend (network-capable, hvac), which is why it lives
OUT here and not in core (Invariant 1). Secrets are never stored in config, read by a model, or
logged (Invariant 10).
config.secrets_backend
Vault as a per-interaction runtime authorization layer (design-notes/vault-runtime-auth.md).
The object-capability model already scopes store handles at the code level (the dreamer
gets a MirrorView, never a raw vector store). This module closes the matching gap at the
credential level: an agent never holds a real secret, only an ephemeral token minted by the
supervisor and scoped to a named policy (role). A token that doesn't cover a path is denied —
the agent learns nothing beyond "denied" (VaultPermissionDenied).
hvac (the Vault HTTP client) is real-Vault-only and lazily imported inside VaultClient, so
importing this module — or config.loader, which imports it lazily too — never requires hvac
to be installed. The import-firewall (ops/import_lint.py) additionally blocks hvac from
ever appearing under core/: agents receive tokens in context (Phase 5), they never call
Vault directly. This module lives in config/, one level below get_secret(), exactly like
the design note's import-discipline section specifies.
Phase scope (Steps 4–5 of the security & attestation track, NOT a numbered phase): the primitives
below (FakeVault, VaultClient, build_secrets_backend, and MintedToken) and the
scope-enforcement tests that exercise them. Step 5 added the accessor half of MintedToken — a
mint's non-secret audit handle, which an attestation records in vault_token_accessor to tie an
action to its authorization (the Vault↔attestation join; never the token — see MintedToken).
Threading minted tokens into the dreamer/curator/vault-sync call sites is explicitly deferred to
Phase 5 (agent factory + dispatcher) per the design note's own framing — this module makes that
wiring possible without itself performing it.
VaultPermissionDenied
Bases: Exception
Raised when a token is unknown, or known but its role's policy doesn't cover the path.
Deliberately uninformative beyond that — the caller (an agent) must not learn why it was
denied, only that it was; the Vault audit log (or FakeVault.denials in tests) is where the
detail belongs (vault-runtime-auth.md §6 — denials are an alignment signal, not noise).
MintedToken
dataclass
What a mint returns: the secret token AND its accessor — Vault hands back both in one
response (resp["auth"]["client_token"] / ["accessor"]). The two live in different
keyspaces and do different jobs (the Step-5 Vault↔attestation join, attestation-layer.md §2):
• token — the credential. The agent uses it (get_secret(name, token=...)) and
nothing else may. NEVER logged, attested, or shown to a model (Invariant 10).
• accessor — a non-secret audit handle. It can look up a token's metadata or revoke it,
but cannot authenticate or read any secret. THIS is what an attestation
records in vault_token_accessor, tying an action to its Vault authorization
without ever exposing the credential.
The supervisor holds the whole MintedToken: it passes .token to the agent (in context,
Phase 5) and records .accessor in the attestation it emits for that action.
token
instance-attribute
accessor
instance-attribute
SecretsBackend
Bases: Protocol
What both FakeVault (tests) and VaultClient (real Vault) implement — the supervisor
and get_secret(..., token=...) depend on this shape, never on which one is wired.
mint_token(role, ttl)
read_secret(name, token)
FakeVault
dataclass
An in-memory dev/test double — no real Vault, no network, no subprocess. policies
maps a role name to the exact set of secret names its tokens may read (the dev-mode
analogue of an HCL policy's path stanzas, see ops/vault/policies/); secrets is the
backing key-value store. Every mint and every read/deny is recorded for assertions.
policies
instance-attribute
secrets = field(default_factory=dict)
class-attribute
instance-attribute
minted = field(default_factory=list)
class-attribute
instance-attribute
denials = field(default_factory=list)
class-attribute
instance-attribute
mint_token(role, ttl)
read_secret(name, token)
role_for_accessor(accessor)
The dev-mode analogue of Vault's lookup-accessor: resolve an accessor to the role it
was minted for, WITHOUT the token. This is what makes the Step-5 join verifiable — an
attestation's vault_token_accessor can be confirmed to match its claimed agent_role.
Returns None for an unknown accessor (or for a token passed here — wrong keyspace).
VaultClient
Real Vault, via hvac. Construction is side-effect-free — no connection is opened until
mint_token/read_secret is actually called — mirroring OllamaClient/lancedb.connect
elsewhere: safe to build in a wiring test without a live Vault dev-server running.
supervisor_token is the supervisor's own bootstrap credential (the bottom turtle for this
layer — placed in Keychain/env via get_secret("vault-supervisor-token"), same pattern as
the attestation signing keys). It is used only to mint child tokens; reads always go through
a freshly-scoped client built from the caller's token, never the supervisor's.
hvac is imported per-method, not in __init__: edge/bridge/bridge.py holds the same
line for boto3 ("imported LAZILY... so tests with a fake client never require boto3
installed") — this is that pattern applied here, so a wiring test can construct and inspect
a VaultClient (addr, kv_mount) with no Vault dev-server and no hvac installed; only an
actual mint_token/read_secret call needs the real package.
addr = addr
instance-attribute
kv_mount = kv_mount
instance-attribute
mint_token(role, ttl)
read_secret(name, token)
build_secrets_backend(config=None)
Wire a real VaultClient from [secrets] — None when disabled, the normal state
until the owner stands up a Vault dev-server (Step 6 runbook). Unlike attestation's
fail-closed signing gate, a missing supervisor token here is not a silent-fallback risk
(there is no insecure fallback path to slip into) — it simply surfaces as an hvac auth
error on the first real mint_token call, not at construction.