The control plane
Everything the gateway trusts arrives as a signed file. obsign-control is
where those files come from, and the reason a rule change is a dated,
reviewed pull request instead of a click in a UI.
Publishing
Section titled “Publishing”# the source tree is a git checkout:# policies/*.cedar tools.json fail-mode.json identity/{provider,jwks}.jsonobsign-control publish \ --source ~/acme-policies --key /tmp/ops.hex --key-id ops-2026 \ --dist /srv/obsign/dist# [control] compiled policies@847d4fca5754 — 1 rule file(s), 2 tool(s), ...# [control] published release 847d4fca5754 -> /srv/obsign/dist/releases/847d4fca5754The version is the commit sha, resolved by reading .git directly; no
git binary is required on the build host. Every decision recorded in the log
cites policies@<sha>; replaying it months later means checking out that sha.
Compilation validates with the gateway’s own code paths, so what passes in CI
cannot fail at startup across the fleet: Cedar syntax, the mandatory @id on
every rule, duplicate tools, fail-mode overrides naming tools that do not
exist, unusable or forbidden JWKS keys. The JWKS is a file in git and is
reviewed like a rule, because it decides who can mint identities.
Publication holds two invariants:
- A version is immutable —
releases/<sha>/is written once; publishing different content under an existing sha is refused. A crash mid-publish is repaired on the next run; a changed source is not. - The current files change atomically — write-then-rename on the files the gateways hot-reload: a reader sees the old release or the new one, never a torn file. Rollback needs no tooling: republish the old sha.
The release manifest is signed, but the artifact hashes inside it are plain
SHA-256 of the file bytes, deliberately: “is the bundle my gateway loaded the
one the manifest names?” must be answerable with nothing but sha256sum.
The audit dossier
Section titled “The audit dossier”obsign-ledger export produces one pack for one chain; an auditor asks for a
period. With the HTTP transport every agent session is its own chain, so
“what did your agents do in Q3” is dozens of packs:
obsign-control export \ --wal /srv/obsign/wal --store /srv/obsign/ledger \ --out /tmp/dossier --key /tmp/ops.hex --key-id ops-2026Every chain is exported, verified on the way out, and listed in a signed export manifest, so the dossier cannot lose a pack in transit without the loss being visible. A pack that fails verification is written and flagged, and neither repaired nor filtered: an export that fixed things on the way out would do exactly what the product exists to make impossible. The exit code says so.
The console
Section titled “The console”obsign-control console \ --wal /srv/obsign/wal --store /srv/obsign/ledger --dist /srv/obsign/distThree server-rendered HTML pages on std::net: current release with its
signature verdict, chains with their sealing state (each re-verified on
request), records. No JavaScript, no template engine, no cache: what the
console shows is what the files say now.
Read-only by construction. The only accepted method is GET, so the console can never become a second write path around git. It binds to localhost by default; authentication is the commercial layer’s job, not a reason to weaken the core.