The previous article made the case: agents should never hold reusable credentials. This one walks the actual machinery — how a database, an object store, a cache, or a host is registered without its password becoming plaintext, how protected values are entered through a system-authenticated process, how jump routes and pinned host identity work, and how topology answers are computed from observed evidence rather than assumptions.
SAFA's inventory is a single encrypted, transport-independent directory. "Resource" is a generic aggregate — host, database, object-storage, cache, messaging, search, graph, and service all reuse the same record. Each record binds an immutable template that names the adapter/configuration schema, such as ssh@1, mysql@1, redis@1, s3@1, or http@1.
Registering a type is deliberately not granting access, and it does not claim an adapter exists. The directory answers "what did the user register and where does its protected data live?" — an adapter later decides which operations are implemented for that type and access method.
| Field | Visibility | Notes |
|---|---|---|
| canonical alias | public | Lowercase logical name; the only selector an agent may use |
| resource kind / type | public | e.g. database.mysql, object-storage.s3, host.linux |
| template | public | Immutable adapter/schema binding, e.g. mysql@1 |
| endpoint / username / route | authorized | Encrypted; disclosed only by authorized detailed show |
| typed metadata | allowlist / authorized | Unknown keys always default private |
| credential bindings | broker only | Opaque IDs only; never in Agent DTOs |
| credential values / locators | Keychain / broker only | Never resource metadata |
Entering a password for the first time is the most sensitive moment in the whole system, so SAFA separates it from anything the agent can influence. A separately signed safa-trusted-setup process runs system-authenticated on the Mac: protected fields are typed with terminal echo disabled, and the values travel to the Broker as a typed ProtectedResourceSetupPayload over authenticated local XPC — never through the agent CLI's argv, environment, stdin, stdout, or stderr. If no trusted controlling terminal is available, SAFA returns a safe_for_agent: false command for the user to run themselves.
The result is a CredentialReference: an opaque UUID that also serves as the Keychain account identifier, plus a kind (SSH today; database passwords, object-store access keys, and API tokens extend it without a vault-shape change), a health state, and — critically — an access class:
automaticWithinPolicy — the credential may be used by the Broker when policy permits.userPresenceRequired — the Broker must obtain macOS user-presence approval (Touch ID / password) before the credential can be used.Device-bound private keys have no export transition. Removing a reference deletes or invalidates its Keychain material after dependent resources are disabled. Secrets are not fields in the domain model — there is nothing to leak from the vault schema itself.
A password that is typed into a system-authenticated local process, stored as an opaque Keychain reference, and injected only inside the Broker boundary is a password that never becomes model input, tool output, or chat history.
Connection metadata (endpoint, username, route) is encrypted and never part of the agent-visible surface. When a route is needed, the resource model carries a jumpRoute: an ordered, acyclic list of SSH resources the Broker must traverse. Direct routes are supported too — including an already-running local Core Tunnel listener, so a tunnel can be fronted without exposing anything.
Every execution is a fresh, isolated SSH invocation built from an opaque per-request alias and a temporary ssh_config and known_hosts in a 0700 directory:
StrictHostKeyChecking yes, and the alias/port used for lookup is fixed so canonicalization cannot diverge.GlobalKnownHostsFile /dev/null, HashKnownHosts yes, and a per-request host-key alias keep the temporary world isolated.SSH_ASKPASS with SSH_ASKPASS_REQUIRE=force, Secure Enclave agent socket, or existing OpenSSH identity files/agent.StdinNull yes so OpenSSH prefers SSH_ASKPASS over reading a login secret from stdin.A host-identity record guards the destination: status trusted, changed, or revoked, with a verification method. Changed or missing identity fails closed — no raw SSH fallback, ever.
Relationships are stored as a directed, typed, attributed multigraph — a tree cannot express "this host uses a route in another security domain and depends on three stores". Edges carry a layer and verification:
desired/asserted — what a user intends or believes; a proposed link never becomes proof.observed/verified — created only by a signed adapter or bounded probe, time-bounded.stale / failed — may be shown for diagnosis but cannot prove a path.A successful bounded SSH setup or execution refreshes a single observed, verified runtime.local can-reach <resource> edge for five minutes; an OpenSSH exit 255 never creates one. Reachability returns confirmed only when the Broker finds a directed path made entirely of fresh, verified evidence.
Here is what the real commands return — recorded against a real registered environment (aliases sanitized, numbers verbatim):
$ safa topology show data.home --limit 64 # where is the database host?
schema: dev.safa.cli/v2
command: topology.show
status: completed
task: placement
answer:
outcome: found
source: data.home
count:
nodes: 6
edges: 5
nodes[6]{alias,kind,resource_kind}:
data.home,resource,host
site.home,site,null
desk.win,resource,host
router.home,resource,host
virt.host,resource,host
nas.primary,resource,host
edges[5]{id,from,relation,to}:
...,data.home,located-in,site.home
...
$ safa topology path data.home nas.primary --limit 64 # can the DB host reach the NAS?
schema: dev.safa.cli/v2
command: topology.path
status: completed
task: reachability
answer:
outcome: not-found
source: data.home
target: nas.primary
count:
nodes: 2
edges: 0
Read the contrast: the database host and the NAS are co-located in the same site (placement found, six nodes, five located-in edges) yet the Broker reports no verified path between them. That is the whole point — co-location is not connectivity. An agent that asked "can this node reach the database?" would get not-found and would be forbidden from inventing a route or attempting a direct connection. The same graph revision produces topology impact answers (reverse dependency search) and topology link/unlink mutations, which require macOS user authorization.
The default, non-interactive summary exposes only source-reviewed allow-listed keys: host.os.family, host.docker.available, database.engine, object-storage.provider, cache.engine, service.protocol. IP addresses, kernel releases, CPU/memory/disk details, Docker versions, routes, and every unknown key require resource show --details — and that command relies on the macOS-owned user-presence prompt. Protected details and privileged actions are gated by the same rule: the user is present on the Mac, or the value stays hidden.
Approval is not a vague concept in the codebase: a pending request carries a TrustedApprovalPresentation (resource alias, privilege, exact command, intent, expected effect, risk level, findings), and the user decides through safa-trusted-setup request approve or a denial — with optional scoped grants for sudo (spec 002). The agent's own risk assessment is advisory and can never substitute for this decision.
Today the executable profile is SSH hosts with allow-listed read-only diagnostics. Databases, object stores, caches, and services are registered as typed records whose operations are gated pending review. The generic directory, the opaque credential-reference model, the jump-route field, the access classes, and the user-presence approval path are already in the design and contracts — the adapters are what ship next, each with conformance fixtures rather than promises.