# Architecture `geth` is a single-binary local-first mesh runtime. One executable provides both daemon mode and control mode. The daemon owns local identity, metadata storage, the shared Iroh endpoint, resource registry, module routing, and local control socket. Control commands connect to the Unix socket and send typed JSONL requests. Within `geth-node`, daemon lifecycle code is separated from feature handlers: `daemon.rs` owns `geth daemon run` startup, local socket binding, shutdown signal handling, Iroh endpoint startup, the Iroh accept loop, and background live-sync task spawning. `local_control.rs` owns async local `ControlRequest` routing and safe trace-field classification before delegating to feature handlers. Runtime registries for pubsub, pipes, and overlays live behind narrow mutex-protected structs in `runtime.rs`. Command-family handler modules and protected peer-control feature dispatch remain separate refactor targets. The local metadata store is SQLite product state. `geth-store` tracks a numeric `schema_version` in the `meta` table and applies ordered migrations up to the crate's current schema version when the store opens. Fresh database creation and repeated opens are idempotent; migrations that change existing schemas run in a SQLite transaction where SQLite supports it. File-backed stores deliberately use SQLite WAL mode with `synchronous=NORMAL`: committed transactions remain consistent after process crashes, while the most recent transaction can be lost on an OS crash or power loss before the WAL is durable. `geth status` reports daemon uptime, the observed schema version, journal mode, synchronous mode, and whether those values match the expected store policy. Service management is also exposed through the single binary. `geth daemon service ...` installs and controls a user-level service definition for the local daemon. The initial backends are systemd user units on Linux, launchd user agents on macOS, and per-user scheduled tasks on Windows. Geth does not install itself as a privileged system service. ## Iroh-Only Remote Communication Remote geth node-to-node communication is Iroh-only. The daemon will own one shared Iroh endpoint and register module protocols on ALPNs such as `/geth/cas/1`, `/geth/kv/1`, `/geth/pipe/1`, `/geth/ssh-proxy/1`, and `/geth/overlay/1`. The pinned Iroh integration uses `iroh = 1.0.0`. `geth-iroh` wraps `iroh::Endpoint::builder(iroh::endpoint::presets::N0)`, configures geth ALPNs with `Builder::alpns`, uses `Builder::relay_mode`, adds `iroh-mdns-address-lookup` when local discovery is enabled, persists an `iroh::SecretKey` as hex-encoded 32-byte key material, and shuts down through `Endpoint::close().await`. The default config uses Iroh's default relay policy; local-only/offline development can set `[iroh].relay_mode = "disabled"`. Named custom relay maps are configured under `[iroh.relay_maps.]`, selected with `relay_mode = "custom"` plus `relay_map = ""`, validated at config load, and reported in status as `custom:` without exposing relay URLs. The native module-backend crates for the intended CAS, KV, and pubsub replacements now compile against the same endpoint generation: `iroh-blobs 0.103.0`, `iroh-docs 0.101.0`, and `iroh-gossip 0.101.0`. `geth-iroh` exposes their native ALPNs without creating a second daemon endpoint. CAS now registers an `iroh-blobs` provider handler on `/iroh-bytes/4`; local CAS writes are mirrored into the native blob store, and remote `geth cas fetch` performs a geth control-ALPN authorization preflight before transferring payload bytes over `iroh-blobs`. KV now starts `iroh-docs` with `iroh-gossip` and the same native blob store, mirrors named KV stores into read-shared Iroh Documents namespaces, and sends read-only docs tickets only after geth control authorization succeeds. Pubsub joins deterministic native `iroh-gossip` topics after the geth control path authenticates the peer-card endpoint binding and authorizes the topic capability. `geth status` reports CAS, KV, and pubsub as wired native backends. Module ALPNs are registered through `geth-iroh`'s protocol router scaffold. The router owns the default protocol descriptors, rejects duplicate ALPN registrations, and returns explicit unknown-ALPN errors. The current daemon accept loop dispatches geth control, pipe, SSH-proxy, and native CAS blob streams directly, plus native docs and gossip streams used by KV and pubsub. The target product should use Iroh relay support for practical internet connectivity and mDNS/LAN discovery for local networks. These are connectivity and candidate-discovery mechanisms only. They do not grant trust, mutate authorization state, or make EndpointID knowledge sufficient for access. The current daemon can enable Iroh's local-network discovery service through `[iroh].local_discovery = true`, which is the default. This publishes and discovers Iroh node addressing. `geth peer export/import/list` supports manual exchange of signed peer cards as untrusted candidates. Peer cards include the Iroh EndpointID plus relay/direct address candidates when the daemon can observe them. `geth peer ping ` dials an imported peer card over Iroh and exchanges signed peer-card metadata. `geth peer auth-check ` sends a protected Iroh control request that validates the caller's signed peer card against the actual Iroh EndpointID before evaluating resource-local capabilities. When local discovery is enabled, the daemon also advertises and discovers signed peer cards through a geth-specific mDNS service. The LAN payload is TXT-encoded signed metadata only; remote geth traffic still uses Iroh. Peer cards are the discovery payload. A peer card carries node ID, agent ID, endpoint candidates, timestamp, signing public key, and an Ed25519 signature over a canonical payload. Imported and ping-discovered peer cards are stored as untrusted metadata in `peer_cards`; trust reduction is future work. `auth explain` reports when a subject is only a discovered peer candidate and denies access. It also reports whether a trusted node has no endpoint binding, whether the discovered peer card has no endpoint bound to that node, or whether a peer card endpoint matches the reduced keychain view. The peer ping path authenticates the Iroh endpoint and peer-card signature, but it does not authorize any resource module. Protected peer control requests must also prove that the signed peer card binds the observed Iroh EndpointID, then reduce resource auth ops; an EndpointID alone is not accepted as a resource principal. Remote geth JSONL messages over Iroh are bounded before decoding: peer-control and module wire requests/responses are limited to 16 MiB, streaming handshakes for SSH proxy and TCP/Unix pipe forwarding are limited to 64 KiB, and each remote line read has a 30 second timeout. Oversized remote lines are rejected before the request is decoded or dispatched to a resource handler. The daemon starts this endpoint during `geth daemon run` and keeps it alive for the daemon lifetime. When endpoint startup succeeds, the Iroh EndpointID is recorded as a transport binding for the stable geth node identity. If local UDP binding is unavailable, the daemon keeps local control running and reports the Iroh startup error through status output. SSH keys are not transport keys. They are admin trust anchors and signing identities for keychain and authorization operations. The bootstrap `geth ssh proxy ` command performs an authorized Iroh control-plane handshake: the remote daemon validates the caller's signed peer card against the observed Iroh EndpointID and requires `ssh_proxy.connect` on `resource:ssh-proxy:local`. It returns connection metadata only. Carrying SSH bytes over an Iroh stream is implemented on the dedicated `/geth/ssh-proxy/1` ALPN and only connects to remote `127.0.0.1:22` after authorization. `geth ssh admin-shell ` is a separate restricted geth admin workflow over the protected Iroh control path. It requires `ssh_proxy.admin_shell` and executes only built-in geth commands, never host shell commands. Neither path makes SSH a geth transport backend. SSH certificate flows use the same split. Nodes can request new OpenSSH certificates or renewals through geth metadata. A machine with the CA key or YubiKey can approve the request and run an explicit `ssh-keygen -s ...` command, or pass `--sign` to execute that command immediately and import the resulting certificate into local metadata for distribution. This relies on the local OpenSSH ecosystem, so hardware-backed keys remain mediated by `ssh-keygen` and the host's agent/security-key flow. Certificate and key revocations are stored as signed-list-ready records. Sync materializes those records as ordered SSH distribution log entries: certificate requests, certificate imports, and revocations each have stable log entry IDs and timestamps, and the receiver reduces the entries into local state after provenance and conflict checks. The bootstrap can pull certificate-flow log entries over Iroh with `geth ssh cert sync ` when the peer grants `ssh_cert.sync` on `resource:ssh:certs`, and revocation log entries with `geth ssh revocation sync ` when the peer grants `ssh_revocation.sync` on `resource:ssh:revocations`. The daemon also runs a configurable background live-sync tick for known peers and records per-peer high-water cursors in `module_state`, so repeated ticks request only entries at or beyond the last remote cursor. The default interval is 30 seconds and can be changed under `[sync]` in `config.toml`. Boundary duplicates are harmless because records are keyed by stable IDs. Before issuing per-module pulls, the daemon can request an authorized sync status summary over the same protected Iroh control ALPN. The serving peer validates endpoint/card binding and returns only watermarks for streams where the caller already has the required resource capability, which reduces blind polling without letting discovery reveal private resource names. Keychain and auth operation logs are also advertised through this watermark path. Pulls are delta-style by per-peer cursor, but every received operation is still verified against trusted-admin OpenSSH signatures before import. Operators can run `geth sync now [node]` to trigger the same best-effort pass immediately and `geth sync status` to inspect locally recorded last-attempt, last-success, cursor, import/rejection counts, and errors for each peer stream. JSON status also includes `state`, `stale`, `stale_after_ms`, `consecutive_failures`, `retry_after_ms`, `retry_in_ms`, and `next_action` fields so scripts can fail on unhealthy streams. Background live-sync backs off failed streams without blocking explicit `geth sync now [node]` retries. Common daemon errors include a `next:` recovery line for missing peer cards, missing grants, missing resources, unavailable endpoints, and missing DB/KV/document registrations. Daemon logs use structured `tracing` fields for local control requests: `command`, `peer_node`, `resource`, `capability`, `stream`, and stable `error_code` where applicable. The request tracer classifies requests instead of formatting full payloads, so bearer tokens, private key paths, packet/message payloads, and document JSON are not logged by that layer. Host-opening paths are intentionally narrow. TCP pipe forwarding accepts only explicit loopback socket addresses on both the local listener and remote target; Unix pipe forwarding requires absolute paths without parent-directory components; SSH proxying always connects the authorized remote stream to `127.0.0.1:22`; and overlay interface creation happens only after the operator runs `geth overlay up`. Host setup failures should be handled as local operator or entitlement problems. For overlay platform recovery, see `docs/overlay-platforms.md`; for missing peer cards, endpoint bindings, or resource grants, use `geth peer import`, `geth node endpoint-add`, and `geth auth grant`/`geth node grant` as indicated by `next:` error output. ## Resource Model Everything meaningful is modeled as a resource. Resources have a kind, name, authority reference, local role, replication policy, retention policy, and status. Authorization is resource-scoped and capability-based. Resource kinds: - `db`: cr-sqlite-backed SQLite synchronization - `kv`: Iroh Documents backed key-value data - `pipe`: authorized byte streams and forwarding - `document`: Automerge CRDT documents - `pubsub`: lossy notifications and presence - `cas`: content-addressed blobs - `ssh-proxy`: SSH/admin proxying over Iroh - `overlay`: optional Iroh-carried packet overlay planning ## Module Overview The current per-resource sync and conflict behavior is a documented automation-facing contract in [`conflict-semantics.md`](conflict-semantics.md). `geth-cas` is implemented locally first using BLAKE3 hashes and filesystem blob storage. Local pin/unpin metadata is tracked in SQLite and surfaced in `cas list`. `cas cleanup` removes unpinned local blobs while retaining pinned blobs. The CAS crate can build deterministic tree objects that describe directories, files, executable bits, and file blob hashes; those tree objects are stored as CAS blobs. The daemon can register local file roots and scan them into CAS tree objects while reporting create/update/delete/rename changes. These scans are local metadata only and never overwrite the working tree. A peer can pull authorized remote file-root tree metadata with `geth cas root sync ` when it has `cas.fetch` on `resource:cas-tree:`; sync imports the remote CAS tree bytes and records a peer-qualified remote root whose path is `remote::` without applying files. File roots also participate in the daemon background live-sync loop through authorized `cas-tree:` watermarks. Repeated remote file-root syncs retain the previous imported remote tree as the base and compare base/local/remote tree state. When both local and remote roots changed, geth records durable concurrent edit, delete/edit, or divergent rename conflicts instead of applying remote content. `geth cas root apply --to ` is conservative when the target has no registered base: it creates missing files and directories from the CAS tree, does not delete extra local files, does not overwrite differing local files, and records conflicts for manual resolution. When the target path matches a registered local root with a previous scan, apply uses that scan as the base for a three-way base/local/remote check. It can safely accept remote creates, updates, deletes, and renames only when the current local filesystem still matches the base; ambiguous paths remain durable conflicts. The daemon also has durable local file-conflict records with explicit resolution choices. As a bootstrap network path, `geth cas fetch ` dials an imported signed peer card over the daemon-owned Iroh control ALPN. The serving daemon validates the caller's peer-card signature and observed Iroh EndpointID, then reduces local auth ops and requires `cas.fetch` on `resource:cas:local` before returning blob bytes. The requester verifies that the returned bytes hash to the requested BLAKE3 CAS hash before storing them. Successful fetches update durable local provider metadata keyed by CAS hash and peer node, which can be inspected through `geth cas providers `. Iroh-blobs provider/fetch integration is future work. `geth-db` currently registers local SQLite paths as DB resources and reports local-only sync status plus a read-only SQLite schema summary/hash. It also inspects `crsql_changes` metadata when that table or view exists, reporting change count, columns, and max `db_version`. The crate and local daemon can extract read-only typed change batches from `crsql_changes` with schema metadata through `db changes`. As a staged network path, `geth db sync ` uses the protected Iroh control ALPN to request remote typed change batches when the caller has `db.sync` on the remote `resource:db:`. The requester checks remote schema metadata against its local DB before applying changes and advancing its per-peer/per-DB cursor. Compatible remote batches are inserted into the local `crsql_changes` table or view before the cursor advances. Loading/configuring the cr-sqlite extension for real application databases remains the database owner's responsibility; the bootstrap tests use deterministic fixture tables because this dev environment has no `sqlite3` CLI or cr-sqlite extension artifact. DB sync intentionally does not use CAS-backed snapshots or batch blobs in the prototype. Those become useful when initial catch-up or large batches outgrow the protected control path. `geth-kv` keeps SQLite as the durable local index for named KV stores through `kv create/set/get`. `kv set --subject ` evaluates local auth ops for `kv.write_key:` so prefix grants can be tested. The daemon mirrors local KV entries and metadata into an Iroh Documents namespace per named store. `geth kv sync ` still starts with a protected geth control request that requires `kv.read` on the remote `resource:kv:`; if authorized, the remote daemon returns a read-only docs ticket and the requester imports entries through Iroh Documents. The control response still carries bootstrap entries for compatibility. The daemon background live-sync loop runs the same KV sync for local KV stores and known peers. Private value encryption should use resource secret epochs before payloads are exposed to remote peers. `geth-document` registers local document resources and stores durable Automerge save bytes in the local SQLite metadata store. The CLI still accepts and returns validated JSON views for `document create/status/set/get`, but the persisted state is an Automerge envelope containing binary save data plus the current JSON view for operator output. `geth document sync ` pulls remote Automerge state over the protected Iroh control ALPN when the peer grants `document.read` on `resource:document:`. The daemon background live-sync loop runs the same sync for local documents and known peers using per-peer/per-document cursors. Received Automerge documents are merged before being stored. `geth-pubsub` supports local publish/subscribe snapshots through the daemon control protocol. Messages live in a bounded in-memory ring buffer and are lost when the daemon stops. This is deliberate: pubsub is a lossy wakeup and presence channel, not authoritative storage. Durable facts must be written to CAS, KV, document, or DB resources before pubsub is used as a wakeup. `geth pubsub pub --node ` first uses the protected Iroh control ALPN for authorization. The remote daemon validates endpoint/card binding and requires `pubsub.publish` on `resource:pubsub:` before recording the message and broadcasting it through a deterministic native `iroh-gossip` topic. `geth pubsub sub --node ` uses the same protected path, joins the gossip topic when the caller has `pubsub.subscribe`, and returns the peer's current daemon-lifetime snapshot. Private topics remain future work. `geth-overlay` defines an optional packet-overlay plan inspired by `iroh-lan`. The target runtime is a private L3-style overlay where packets from an explicit TUN/Wintun interface are carried over the daemon-owned Iroh endpoint on `/geth/overlay/1`. The overlay is a geth resource (`resource:overlay:`) with `overlay.join`, `overlay.route`, and `overlay.admin` capabilities. The prototype exposes `geth overlay status`, `geth overlay plan `, `geth overlay join --secret `, and `geth overlay leave `. Join creates or reuses `resource:overlay:`, persists local membership in `module_state`, assigns a deterministic virtual IP from the CIDR, and stores only a BLAKE3 fingerprint of the supplied secret. If bearer access already exists for that overlay resource, join requires the supplied secret to be a bearer token with `overlay.join`; this lets operators create explicit resource-scoped overlay invites through `geth secret bearer create`. The `geth overlay interface-plan` generates Linux, macOS, or Windows host-interface plans for review. `geth overlay up ` is the explicit opt-in that creates a real L3 TUN/Wintun-style interface through `tun-rs`, assigns the local deterministic overlay IP, reads IPv4 packets from the interface, maps destination overlay IPs to imported peer cards, and carries those packets over the dedicated `/geth/overlay/1` Iroh ALPN. `geth overlay down ` stops the runtime and drops the device handle. The serving daemon validates the signed peer card against the observed EndpointID and requires `overlay.route` on `resource:overlay:` before queuing a received packet into the active interface; if no runtime is active, the packet is persisted for `geth overlay recv`. Interface setup may require `CAP_NET_ADMIN`, sudo, a preconfigured `/dev/net/tun`, Wintun availability, or platform-specific network entitlements. Release artifacts include `docs/overlay-platforms.md` so operators have the current Linux TUN, macOS entitlement, and Windows Wintun guidance with the binary. Overlay discovery can use mDNS, peer exchange, and resource metadata, but discovery remains untrusted and cannot grant overlay access. `geth-pipe` currently supports `pipe listen/connect/send/recv` against a daemon-lifetime runtime. `geth pipe connect --node ` sends an authorized remote connect request over the protected Iroh control ALPN. The remote daemon validates endpoint/card binding and requires `pipe.connect` on `resource:pipe:` before recording the connection attempt and reporting whether a listener exists. `geth pipe send [message|--in |--in -] --node ` uses the dedicated `/geth/pipe/1` ALPN to write a byte message to a peer listener after the same endpoint/card and capability checks. `geth pipe recv ` drains local daemon-lifetime messages. `geth pipe listen --node ` can also ask a peer to register a daemon-lifetime listener after checking `pipe.listen` on the same resource. `geth pipe forward-tcp --listen 127.0.0.1: --node --target 127.0.0.1:` runs a local loopback listener and opens one authorized `/geth/pipe/1` byte stream per accepted connection. The remote daemon validates endpoint/card binding and requires `pipe.forward` on `resource:pipe-tcp:` before connecting to the remote loopback TCP target. TCP forwarding is loopback-only in the prototype; `geth pipe forward-unix --listen --node --target ` uses the same authorized Iroh pipe stream and requires `pipe.forward` on `resource:pipe-unix:` before connecting to an absolute remote Unix socket path. `geth-ssh-proxy` defines proxy target and connection metadata. `geth ssh proxy ` is a streaming command intended for OpenSSH `ProxyCommand`: the CLI streams stdin/stdout through the local daemon, the local daemon dials the remote daemon with `/geth/ssh-proxy/1`, the remote daemon validates the signed peer card against the observed Iroh EndpointID, reduces `ssh_proxy.connect` on `resource:ssh-proxy:local`, and only then connects the Iroh stream to `127.0.0.1:22`. OpenSSH still performs its normal login authentication over the resulting byte stream. SSH is not used as a geth transport backend. `geth-ssh-identity` defines SSH trust namespaces plus certificate request, approval, certificate import, and revocation-list data models. The bootstrap persists these flows locally and exports revocations as JSONL or OpenSSH KRL specification text. Certificate approval normally emits the exact `ssh-keygen -s ...` command, and `approve --sign` can run that command, import the resulting OpenSSH certificate, and mark the request signed. It can also invoke `ssh-keygen -k` to produce a binary OpenSSH KRL; serial and key-ID KRL entries require a CA public key via `--ca-public`, matching OpenSSH behavior. It can import geth JSONL revocation exports and OpenSSH KRL specification source files. Binary OpenSSH KRL files are not enumerable through OpenSSH tooling, so geth treats binary import as unsupported and asks for JSONL or the spec source. Revocation lists are not yet full CRDT-replicated resources, but the daemon now syncs certificate-flow and revocation state as a small ordered resource log over the protected Iroh control ALPN. Manual sync commands and the background live-sync loop share the same capability checks and cursor state. Sync import rejects conflicting records with ids that already exist locally instead of replacing local metadata. Local SSH certificate and revocation metadata commands also accept an optional subject principal for authorization testing: non-owner subjects must hold `ssh_cert.*` capabilities on `resource:ssh:certs` or `ssh_revocation.*` capabilities on `resource:ssh:revocations` before requests, approval/import/read operations, or revocation publish/read/import operations are accepted. The live-sync loop first asks for authorized stream watermarks and skips module pulls whose remote high-water value has not advanced. File-root live-sync uses only sync-status-advertised `cas-tree:` streams, because the local node otherwise does not know which roots the peer is willing to expose. ## Keychain, Auth, And Secrets The identity plane is `geth-keychain`: admin keys, users, devices, nodes, agents, and endpoint bindings. Endpoint rotation must not destroy higher-level node identity. Keychain operations reduce into an active view containing current admin keys, users, devices, node records, agent bindings, and endpoint-to-node bindings. Revoked identity subtrees are excluded from that active view. `geth init --admin-key --signing-key --node-name ` records an owner/admin key, user, device, node, and agent binding as keychain operations and signs them with OpenSSH under `geth.keychain.v1@geth.local`. Both keys are required when owner setup options are used, so the node does not create unsigned owner statements by accident. `geth node list` shows the active reduced node view. `geth node rename` and `geth node revoke` record signed keychain operations and require `--signing-key`. Endpoint rotation is explicit: `geth node endpoint-add` and `geth node endpoint-revoke` record signed `NodeEndpointAdd` and `NodeEndpointRevoke` keychain operations. Admin SSH keys are updated with signed `geth keychain admin-add` and `geth keychain admin-revoke` operations; `AdminKeyAdd` carries the public key material needed to reconstruct an OpenSSH `allowed_signers` view. `geth keychain verify` replays the log against the previously accepted admin-key view, mirroring the `git-skm` pattern of verifying key-registry changes from a prior trusted state. The transport-neutral replay rules, application-specific signature namespaces, allowed-signers projection, and JSONL sigchain helpers live in `geth-keychain` so other applications can reuse the same identity-log model without depending on the daemon, SQLite, Iroh, or local control. The CLI can export the same reduced key registry as OpenSSH `allowed_signers` or as appendable JSONL sigchain data for website publication. It can also sign and verify arbitrary snapshots, such as externally managed `authorized_keys`, with an active keychain signer under an explicit OpenSSH namespace. `geth keychain publish-bundle` writes a website-ready bundle for `https://example.com/.well-known/sshsigchain/`, including `allowed_signers`, `geth.sigchain.jsonl`, a signed checkpoint, and optional signed snapshots. `geth keychain fetch --import` verifies the checkpoint and records the last accepted checkpoint per retrieval source URL to reject older bundles. The retrieval source may be a local mirror; the checkpoint still carries the signed advertised publication base URL, and explicit checkpoint verification can pin it. `geth keychain explain` and `explain-signer` provide basic auditability for why a keychain operation or signer is trusted. `geth keychain sync ` pulls keychain operations and signatures from an imported peer over Iroh and imports only operations with a valid OpenSSH signature from a currently trusted admin key over the canonical payload. See `docs/sigchain-keychain.md` for the detailed sigchain design. This is currently a pull-based signed operation log, not a CRDT or Keyhive-style convergent authority. New devices can use the node enrollment flow instead of hand-editing keychain state. `geth node enroll request` creates a canonical, agent-key-signed request containing the requesting node ID, agent ID, requested node name, optional Iroh endpoint, and requested resource capabilities. The request can be submitted over Iroh to an imported owner peer or moved as a JSON file to the owner machine. `geth node enroll approve` runs on the owner/YubiKey machine and records signed keychain operations for the device/node/agent/endpoint binding plus signed auth operations for approved capabilities. `geth node enroll sync ` pulls both signed logs so the new node can see its approved identity and permissions. The authorization plane is `geth-auth`: resource-local signed operation logs, grants, revocations, groups, and `auth explain`. Auth operations reduce into a current permission view for resources, grants, groups, and bearer access. The library can explain direct grants, group grants, missing grants, revoked grants, and bearer-secret access. The daemon persists local auth grant/revoke operations and `geth auth explain` enriches the reducer result with keychain and discovery diagnostics, including discovered-only peers and endpoint-binding state. Human output prints those diagnostics and JSON output exposes them as structured strings for scripts. `geth node grant`, `geth node revoke-grant`, `geth auth grant`, and `geth auth revoke` require `--signing-key` in the CLI and store OpenSSH-signed auth operations. Enrollment approval uses the same signed auth operation path. Auth sync imports only auth operations signed by currently trusted admin keys. Broader delegated authority is still future work. Capability evaluation supports exact matches plus explicit scoped forms. For KV, `kv.write_prefix:` grants writes requested as `kv.write_key:` only when the key is under that prefix; `kv.write` remains the broad write capability. Command-level KV enforcement is still future work. Both keychain and auth operations use `geth-codec` canonical envelopes for signature payloads. The envelope includes a version, an explicit signature namespace, and the operation payload encoded with postcard. JSON remains useful for CLI/control output, but it is not the signed representation. The payload access plane is `geth-secrets`: resource master secrets, epochs, key envelopes, bearer secrets, and rotation. Revocation for private data is modeled initially as secret epoch rotation. The daemon persists resource secret epoch metadata through `secret create/rotate/status`. Bearer access is recorded as resource-scoped auth operations and rejects trust-mutation capabilities such as `auth.delegate`, `auth.revoke`, and `node.enroll`. Bearer creation returns a private bearer token once and stores a separate public bearer id plus token verifier in the auth log. Bearer challenge/proof commands derive deterministic BLAKE3 keyed responses from the private token, resource, nonce, and requested capabilities, then verify them against active resource-scoped bearer grants. Remote resource operations can carry optional bearer proofs over the protected Iroh control path; a valid proof authorizes only the requested resource capability and does not create node trust. The daemon does not yet store payload key material, encrypt resource data, or distribute key envelopes. ## Multi-User Direction The project is structured for future multi-user local-first authorization: - authorization is replicated data, not one mutable ACL blob - resources can carry or delegate to their own auth state - users, devices, nodes, agents, and endpoints are separate principals - capabilities are the underlying permission unit - bearer access is resource-scoped and does not mutate the trust graph - offline revocation is eventual - encryption key distribution is part of authorization ## Keyhive/BeeKEM Roadmap Resource secret epochs are the v0/v1 approximation for private payload access. Private CAS writes use an AES-256-GCM envelope keyed from the resource, local secret id, and epoch with resource-bound associated data and random nonces. Prototype BLAKE3-XOR private blob envelopes from earlier pre-deployment builds are rejected and should be recreated from plaintext. Later designs can add Keyhive-like convergent capabilities and BeeKEM/CGKA-style group key evolution. The bootstrap does not implement BeeKEM and does not claim strong forward secrecy or post-compromise security. ## Security Invariants - All remote node-to-node communication is over Iroh. - SSH is not a geth transport. - SSH keys are admin trust anchors and signing identities. - Agent/node keys handle routine local identity. - Discovery is untrusted. - Knowing an EndpointID does not grant access. - Bearer secrets are resource-scoped capabilities. - Bearer access does not imply trust graph mutation rights. - Authorization is capability-based and resource-scoped. - SSH certificate issuance must be explicitly approved by an authorized principal before signing. - SSH certificate and key revocations are durable metadata that should be distributed over Iroh, not fetched through unauthenticated discovery. - Network and control decoders treat input as untrusted. - Service installation targets user service managers, not system service managers.