CLI sessions
Status: design draft for review · decided, not built · every transcript below is a mockup
Nothing on this page has run. These are design mockups: hand-written transcripts showing what the
ceremonies in the CLI reference are intended to feel like, so reviewers can judge
the ceremonies before any code exists. Key values, ids, hashes, timestamps and exact output layout
are illustrative; the rules each transcript exercises — show-once issuance, serve binding,
two-party ratification, dry-run previews — are the decided design, and each transcript names the
page that owns its rules. Every refusal code shown is a typed error from the
error taxonomy and every journal seq line names an event from the
event catalog — those identifiers are the contract, not illustration.
The human layout shown here is not a contract; --json output is
(conventions).
The tenant in every transcript is aurora; its crews and key values are invented.
1. Bootstrap: the operator key and the first tenant
At first start, a Helm job mints a single-use, expiring enrollment secret, stored as a
Kubernetes Secret; the job's logs carry progress only — no key or secret material ever transits a
pod log (deployment). The operator exchanges that secret over TLS
for the root operator key with oml bootstrap claim
(reference): the key value is shown
once in the operator's terminal and journalled as issued-without-value
(roles and keys). The operator creates tenants and issues
each tenant's first admin key — and can do nothing else: no reads, no writes, no verdicts.
Provider-level acts — operator key issuance, tenant creation — are journalled in the reserved
provider tenant's chain, created at deployment genesis; every other chain is per tenant
(storage). A new tenant's chain opens with a tenant.genesis event
rooting it by reference to the provider-chain tenant.created event that created it — a
reference, not a second numbering (event catalog). The transcripts
label the chain only in this section: from section 2 on, every journal seq is tenant aurora's
chain.
$ kubectl logs job/oml-bootstrap -n oml
oml-bootstrap: provider tenant chain created (deployment signing key dk_5a21, ed25519)
oml-bootstrap: enrollment secret minted — stored as Kubernetes Secret oml/oml-enrollment
oml-bootstrap: single-use; expires 2026-10-30T09:00:00Z; consumed on first claim
oml-bootstrap: this log carries progress only — no key or secret material appears in it
oml-bootstrap: next step: oml bootstrap claim
$ kubectl -n oml get secret oml-enrollment -o jsonpath='{.data.token}' | base64 -d \
| oml bootstrap claim --url https://oml.internal.example --token-stdin
enrollment secret accepted — consumed (single-use; a second claim is refused)
root operator key — SHOWN ONCE, NOT RECOVERABLE, NOT JOURNALLED AS VALUE
operator key id : key_op_000001
operator key : oml_op_EXAMPLEVALUE_zj4Qm9tW8xKpN2rLcVbH
journal (provider) seq 1: key.issued {id: key_op_000001, role: operator, value_disclosed: false}
store this value now; the server keeps only its hash.
$ oml context add root --url https://oml.internal.example --key-stdin
reading key from stdin… ok
context "root" added (key id key_op_000001, role operator)
$ oml tenants create aurora
journal (provider) seq 2: tenant.created {tenant_id: t_9d41, name: aurora, created_by_key_id: key_op_000001}
journal (aurora) seq 1: tenant.genesis {tenant_id: t_9d41, name: aurora, provider_seq: 2, created_event_id: e_30aa}
tenant aurora created — chain opened, rooted at provider seq 2;
universe workarea wa_000001 (status: committed, permanent)
$ oml tenants issue-admin aurora --label "aurora founding admin"
issuing first admin key for tenant aurora — SHOWN ONCE
key id : key_a1f3e2
key : oml_sk_EXAMPLEVALUE_aurora_Hq7dPw3nXsYvK0mBtRj5
journal (aurora) seq 2: key.issued {id: key_a1f3e2, role: admin, value_disclosed: false}
$ oml tenants issue-admin aurora --label "second attempt"
refused: access.issuer_ceiling — aurora already has its first admin key; an operator
key issues nothing beyond a tenant's first admin key. Further keys come from that
admin under the issuer ceiling.
The last refusal is the shape of the operator role: a bootstrap channel, not a standing back door.
2. Issuing a crew's keys
The aurora admin provisions the corrosion crew: a workarea, writer keys for its working agents,
and dreamer keys for the crew that reviews it. Grants bind to node ids; paths are a convenience
(workareas). An issuance journals key.issued without the value,
then one grant.issued per grant.
$ oml context use aurora-admin
$ oml workareas create universe/research corrosion \
--crew-name "Corrosion Crew" --external-manager "crewd.example/corrosion"
created wa_00004e universe/research/corrosion (status: development)
journal seq 812: workarea.create
$ oml keys issue --role writer \
--grant wa_00004e:writer --grant wa_00004e:reader \
--expires 90d --label corrosion-agent-1
key issued — SHOWN ONCE
key id : key_9c44b7
key : oml_sk_EXAMPLEVALUE_aurora_Wn2gLx8cQe5rTv0zYhDk
journal seq 813: key.issued {id: key_9c44b7, role: writer, value_disclosed: false}
journal seq 814: grant.issued {key: key_9c44b7, wa_00004e: writer}
journal seq 815: grant.issued {key: key_9c44b7, wa_00004e: reader}
$ oml keys issue --role dreamer \
--grant wa_00004e:dreamer --expires 90d --label corrosion-reviewer-1
key issued — SHOWN ONCE
key id : key_51d0ac
key : oml_sk_EXAMPLEVALUE_aurora_Fb6mZq1jUs4xNc9pKtRv
journal seq 816: key.issued {id: key_51d0ac, role: dreamer, value_disclosed: false}
journal seq 817: grant.issued {key: key_51d0ac, wa_00004e: dreamer}
$ oml context use corrosion-reviewer-1
$ oml keys issue --role writer --grant wa_00004e:writer --label corrosion-agent-2
refused: access.verb_ceiling — key_51d0ac (dreamer) is an agent key calling an
admin verb (keys issue); issuance belongs to admin and operator keys (R1), and a
key is in any case issued with a role no higher than the issuer's.
$ oml context use aurora-admin
$ oml keys list --expiring 120d
KEY ID ROLE LABEL EXPIRES GRANTS
key_9c44b7 writer corrosion-agent-1 2027-01-27 wa_00004e:writer, wa_00004e:reader
key_51d0ac dreamer corrosion-reviewer-1 2027-01-27 wa_00004e:dreamer
A writer that must also recall holds explicit reader scope — roles never imply one another. The
key values above appeared exactly once; oml keys list and the journal know only ids and hashes.
3. A review session
A human dreamer drains the corrosion queue. The serve binds (reviewer key, artifact version,
attachment digest set); the verdict consumes it
(serve mechanics). Promotion is a three-event
ceremony in the journal: the verdict (review.approve), the act that finds the policy satisfied
(review.ratify), and the visibility change (lifecycle.promote) — the
event catalog owns each shape. The second item shows the
lineage-closure refusal: the reviewer proposed an edit in this row's ancestry, so the server will
not serve it to them.
$ oml context use corrosion-reviewer-1
$ oml review next --workarea universe/research/corrosion
serving 1 of 3 held items
artifact art_02b8e1 @ v1 kind: memory writer: key_9c44b7 (corrosion-agent-1)
submitted 2026-10-29T08:14:02Z scan: pass (deterministic scan v1)
claim_key corrosion/duplex-2205-pitting
body (text/markdown, 1.2 KiB):
| Duplex 2205 coupons at 60 °C seawater showed pitting onset at ~18 d,
| contradicting the 30 d figure in art_019aa4. Full dataset attached.
attachments (served in full — a verdict without them does not count):
pitting-timeseries.csv sha256:9f27…c4a1 42 KiB -> ./review/art_02b8e1/pitting-timeseries.csv [digest verified]
coupon-photos.pdf sha256:77b0…e9d3 1.8 MiB -> ./review/art_02b8e1/coupon-photos.pdf [digest verified]
serve_id: srv_f3a91c (bound to key_51d0ac + art_02b8e1@v1 + 2 attachment digests; expires 00:30:00)
$ oml review approve srv_f3a91c --note "dataset supports onset claim; contradicts art_019aa4, flagged"
verdict recorded — journal seq 901: review.approve
{artifact: art_02b8e1@v1, serve: srv_f3a91c, served_digests: [9f27…c4a1, 77b0…e9d3]}
journal seq 902: review.ratify {subject: art_02b8e1@v1, verdicts: [seq 901],
policy_snapshot: {k: 1, required_roles: [dreamer]}, lineage_closure_digest: 41cc…09f7}
journal seq 903: lifecycle.promote {artifact: art_02b8e1@v1, previous_tip_version: none}
art_02b8e1@v1 promoted; serve srv_f3a91c consumed.
$ oml review next --workarea universe/research/corrosion
refused for next item art_031c77@v2: review.author_in_lineage
your key key_51d0ac proposed art_031c77@v2 (edit-as-proposal, derives_from art_031c77@v1);
the reviewer is never the author — computed over the lineage closure at promotion time.
skipping to next eligible item… none eligible for this key.
2 items remain held; both carry key_51d0ac in their lineage closure and need
a reviewer outside it.
The refusal is computed per row, not per key pair: key_51d0ac is a perfectly good reviewer for
everything it did not touch. The remaining items wait — the gate fails closed rather than lowering
the bar (admission).
4. Freezing a workarea
Status transitions are forward-only — development → committed → validated, one rung at a time —
and two-party: proposed by one admin or dreamer (journalled workarea.status_proposed), ratified
by a second, non-proposing one (journalled workarea.status_transition with the policy snapshot)
(workareas). The self-ratification attempt here is refused by R2;
the validated proposal is refused while a child is not yet validated.
$ oml context use aurora-admin
$ oml workareas status propose universe/research/corrosion --to committed
transition trn_0a40 proposed (development -> committed); awaiting a second ratifier.
journal seq 1198: workarea.status_proposed {wa_00004e: development -> committed, proposer: key_a1f3e2}
$ oml workareas status ratify trn_0a40
refused: review.ratifier_is_proposer — a transition is ratified by a second,
non-proposing dreamer or admin (R2 extends to transitions).
$ oml context use corrosion-reviewer-1
$ oml workareas status ratify trn_0a40
journal seq 1203: workarea.status_transition {wa_00004e: development -> committed,
proposer: key_a1f3e2, ratifier: key_51d0ac, policy_snapshot: pol_77e2}
$ oml context use aurora-admin
$ oml workareas status propose universe/research/corrosion --to validated
refused: workarea.transition_refused
universe/research/corrosion/scratch (wa_000061) is development
an area may only become validated when all its children are validated.
$ # …scratch walks the same two rungs under distinct principals
$ # (trn_0a44: development -> committed; trn_0a51: committed -> validated), then:
$ oml workareas status propose universe/research/corrosion --to validated
transition trn_0a58 proposed (committed -> validated); awaiting a second ratifier.
journal seq 1236: workarea.status_proposed {wa_00004e: committed -> validated, proposer: key_a1f3e2}
$ oml context use corrosion-reviewer-1
$ oml workareas status ratify trn_0a58
journal seq 1240: workarea.status_transition {wa_00004e: committed -> validated, …}
$ oml context use aurora-admin
$ oml workareas create universe/research/corrosion post-mortem
refused: workarea.frozen — a validated area accepts no new children.
remaining mutations on wa_00004e: demote, erase, crew-metadata update.
5. Running and ending a simulation
Simulation writes are ordinary journalled events carrying indelible provenance: simulation,
visible only to the audience, reviewed only by dreamers in it; ending the simulation demotes its
rows and re-reviews tainted claims (simulations). end is a
mass-reach act, so it defaults to --dry-run.
$ oml context use aurora-admin
$ oml simulations open --audience key_9c44b7,key_51d0ac --label "what-if: 40 °C baseline"
simulation sim_0007 opened (state: open, audience: 2 keys)
note: audience contains 1 dreamer key — promotion inside the simulation is possible.
journal seq 1302: simulation.opened
…the crew writes and reviews inside the fence over the agent API;
every such row carries provenance: simulation + sim_0007 indelibly…
$ oml simulations show sim_0007
sim_0007 open audience: key_9c44b7, key_51d0ac
rows written: 41 (held 3, promoted 38, all provenance: simulation)
sim-derived taints outside the simulation: 2 claims carry sim_0007 in lineage
containment: rows served only to audience keys; excluded from shared FTS/vector
indexes; refused promotion into validated areas.
$ oml simulations end sim_0007
dry-run (default) — ending sim_0007 would:
demote 38 promoted simulation rows
discard 3 held simulation rows
re-enqueue 2 sim-derived claims for re-review: art_0412aa@v1, art_0415c0@v3
nothing journalled. re-run with --apply to act.
$ oml simulations end sim_0007 --apply
journal seq 1346: simulation.ended {simulation_id: sim_0007, disposition: demote}
demoting 38 promoted rows — journal seqs 1347..1384: lifecycle.demote
{reason: simulation_ended, cause_event_id: seq 1346} per row
3 held rows discarded at admission; 2 sim-derived claims re-enter held for re-review.
sim_0007 ended. tainted claims re-enter held; their reviewers see the taint.
6. Evidence export and chain verification
The export produces the evidence an auditor asks for; the chain verification proves the journal
was not rewritten — and, because journal payloads are digest-only, it still verifies after
crypto-shredding (audit and lineage). The export is an
async, journalled job: export.requested is written when the job is requested, and the bundle
exists only once its chain verification finishes — fetched earlier it is export.not_ready.
$ oml audit export --out ./evidence-2026-10-29
journal seq 1390: export.requested {export_id: exp_0003, kind: evidence, from_seq: 0,
include_simulations: false, requested_by_key_id: key_a1f3e2}
export exp_0003 queued (async; the bundle exists when chain verification finishes)…
decision trails 1,482 memories (per-memory: submission, scan, serves, verdicts, policy snapshots)
chain verification seq 1..1390 — recomputed, signatures valid, head hash 7c19…40be
grants history 61 grant/deny/epoch events
read log retention window 90d (tenant policy): 12,044 events, batch digests anchored at journal seqs
reciprocal approvals per key pair; top pair (key_51d0ac, key_60b311): 9 mutual — surfaced, not judged
simulations excluded by default; 1 ended simulation listed by id only (sim_0007)
bundle written: ./evidence-2026-10-29/aurora.oml-evidence.tar (signed, 18.3 MiB)
$ oml audit verify-chain --genesis
verifying tenant aurora from genesis…
seq 1: tenant.genesis — references provider chain seq 2 (tenant.created): ok
seq 1..1390: prev_hash links ok, ed25519 signatures ok (deployment key dk_5a21)
erased artifacts encountered: 4 — digest envelopes verify; bodies unrecoverable (as designed)
chain verified. head: seq 1390, hash 7c19…40be
7. Banning a source
Bans are journalled, checked at admission, and previewed before they act — --dry-run is the
default and shows reach (grants, denials, bans).
$ oml context use aurora-admin
$ oml bans add --source "connector:rss/feedpulse.example" \
--reason "feed republishing fabricated abstracts"
dry-run (default) — banning source connector:rss/feedpulse.example would:
refuse at admission all future submissions naming this source
held items affected 7 held submissions would be refused at their next admission check
promoted items 23 promoted claims cite this source — NOT touched by a ban
(a ban gates admission; demotion of existing claims is a
separate, per-claim journalled act)
keys affected 0 keys are revoked — bans act on the source, not the key
nothing journalled. re-run with --apply to act.
$ oml bans add --source "connector:rss/feedpulse.example" \
--reason "feed republishing fabricated abstracts" --apply
journal seq 1401: ban.imposed {ban_id: ban_0012, subject_kind: source,
subject: connector:rss/feedpulse.example, banned_by: key_a1f3e2}
ban ban_0012 active; admission now refuses this source.
$ oml bans list
BAN ID TARGET BY STATE LIFTED BY
ban_0012 source connector:rss/feedpulse.example key_a1f3e2 active —
The preview's sharpest line is the one about promoted claims: a ban is an admission gate, not a retroactive purge. What was already promoted stays visible until someone demotes it, claim by claim, in the journal — no silent deletion (R4).