The authoritative reference for the ev command surface: the write side (init, decide,
guard, migrate, supersede) and the read side (check, show, verify, why, reopen,
brief, list, log). The package is named evolving; the command is ev.
Every command returns a process exit code: 0 on success, 1 (failure) otherwise.
Throughout, errors are written to stderr as error: <message>; the per-command
sections quote the real strings.
For the model behind these commands — Ticks, Grounds, Checks, identity, and the refusals — see concepts.md.
- Global flags — output rendering
ev initev setupev decideev proposeev ratifyev pendingev guardev migrateev supersedeev checkev showev verifyev whyev reopenev briefev listev log
Two flags apply to every command (placeable anywhere on the line):
| Flag | Value | Effect |
|---|---|---|
--color |
auto (default) | always | never |
When to render the rich human view (colour, glyphs, the unified line grammar). auto colours only a TTY; always forces it (e.g. for | less -R); never is plain. |
--plain / -p |
— | Force the plain output — no colour, glyphs, or aligned layout (the same bytes a pipe gets). Wins over --color=always. |
The two-channel contract: the rich view appears only on a colour TTY (or --color=always). A
pipe, a redirect, CI, NO_COLOR, --color=never, and --plain all emit the plain tab-separated
bytes — so | grep, > file, and a CI text-scraper get stable, escape-free output. The machine path
(--json, events.jsonl, state.json) is never styled. Glyphs degrade to ASCII under EV_ASCII;
truecolor (vs the named-ANSI default, which adapts to the terminal theme) is opt-in via EV_TRUECOLOR.
Synopsis: create the .evolving/ store in the current directory.
ev init
Flags: none.
What it does: creates the store layout — .evolving/ticks/, .evolving/results/
(a receipts/ and a state/ cache), an empty .evolving/HEAD, and a default
.evolving/config. It is idempotent: running it again on an existing store is a no-op
and does not overwrite anything.
Exit code: 0 when the store is created or already exists; 1 if the directory could
not be created.
Output (stdout / stderr):
- created:
created .evolving/ (content-addressed chain + results cache) - already present:
.evolving/ already exists (no-op) - error (stderr):
error: could not create .evolving/: <io error>
Example:
ev init
# → created .evolving/ (content-addressed chain + results cache)Synopsis: set up the ev usage loop for Claude Code in a working tree — co-locate the ledger, install the skill where it is discovered, and wire the session-start brief + pre-commit gate.
ev setup [--dry-run] [target-working-tree]
Flags: --dry-run prints every change and writes nothing. The target defaults to the current
directory.
What it does: in a git working tree it (1) co-locates the ledger (ev init +
staleness_ref=local-head, so a local change can go red as you work), (2) writes the skill to
.claude/skills/ev/SKILL.md — embedded in the binary, so no checkout is needed — and (3) installs the
session-start brief (.claude/hooks/ + a .claude/settings.json SessionStart hook) and the pre-commit
gate (.git/hooks/pre-commit). It is idempotent and non-destructive: it keeps an existing ledger,
backs up a differing skill, never edits an existing settings.json (it prints the one line to add), and
never overwrites an existing pre-commit hook. It raises enforcement; it does not make ev an enforcer.
Exit code: 0 on success (or a clean --dry-run); 1 if the target is not a git working tree or a
write fails.
See also: integrations/claude-code/ — the headless (claude -p)
recipe and the manual equivalent.
Synopsis: record a decision with its grounds (chosen reasons and roads-not-taken), optionally binding a check to each chosen ground.
ev decide "<decision>" [trailing flags…]
ev decide --from-git <commit> [trailing flags…]
The first positional argument is the decision text (required, non-empty) — unless
--from-git is given, in which case the decision text comes from the commit (see
seeding from a commit below). Everything after the source
is a stream of trailing flags parsed left-to-right (see the grammar below).
Flags:
| Flag | Takes | Required | Effect |
|---|---|---|---|
--from-git |
a commit | no* | Seed the decision text (and blame — the commit author, or a leading Role: subject prefix when present — and the subject's #<n> / R<n> plus body Refs #<n> provenance) from a commit instead of the positional argument. Exactly one of {positional decision, --from-git} must be given. |
--authority |
user-ruled | agent-disposable |
no | A declared (non-hashed) authority tag, human-set, surfaced by reopen / show / list / brief. An out-of-vocabulary value is refused. |
--jurisdiction |
A | B | C | D |
no | A declared (non-hashed) jurisdiction tag. A/B may gate; C/D are detect-only — structurally ungateable (see concepts.md). Surfaced by show / list / reopen. An out-of-vocabulary value is refused. |
--source-ref |
a key | no | A declared (non-hashed), opaque source identity ev never interprets — a non-empty string (e.g. R2289, #555, an issue ref) carried verbatim. ev derives only a dedup/reconcile key from it; used by ev migrate to dedup + reconcile a backfill. Surfaced by show / list / reopen. Non-empty if given. (The canonical intake also accepts a structured object — see ev migrate; on this interactive path it is a plain string.) |
--observe |
a string | no | Sets the decision's observe field (the situation being observed). |
--dry-run |
— | no | Assemble + validate the decision and compute its real id, but write nothing (no tick, no event, no HEAD move) — a safe preview before the immutable append. Prints would record <id> (<n> ground(s)) — dry run, nothing written; the id matches what a real ev decide then records. |
--blame |
a name | no* | The author on the hook. If omitted, falls back to git config user.name; one of the two must resolve to a non-empty name. |
--verified-at-sha |
40 lowercase hex | no | The commit a test binding was last verified at. If omitted, defaults to git rev-parse HEAD. Only used by test bindings. |
--reject |
"<option>: <why>" |
no | Opens a road-not-taken ground: supports = rejected:<option>, claim = <why>. Splits on the first :; both sides must be non-empty. |
--assume |
a claim | no | Opens a chosen ground (supports = chosen) with that claim. |
--revisit |
a reference | no | Attaches a Person re-check to the most recent ground (reference = when/where a human re-affirms it). |
--assume-test |
a test selector | no | Attaches a Test check to the most recent ground (reference = the selector). |
--counter-test |
a selector | no† | The test that must flip red if the claim breaks. †Required to complete a test binding. |
--on-platform |
a platform | no† | Adds one liveness platform to the most recent ground. Repeatable. †≥1 required to complete a test binding. |
--triggered-by |
a trigger | no† | Adds one liveness trigger. Repeatable. †≥1 required to complete a test binding. |
--surface |
a surface | no† | Adds one liveness surface. Repeatable. †≥1 required to complete a test binding. |
A decision with no grounds is allowed (it simply records the decision text and observe).
The trailing flags are walked in order and bound to the most recently opened ground:
--assume <claim>opens a new chosen ground;--reject "<opt>: <why>"opens a new rejected road. These are the only flags that start a new ground.- After a ground is opened,
--revisit,--assume-test,--counter-test,--on-platform,--triggered-by, and--surfaceall attach to that ground until the next--assume/--reject. --revisit <ref>makes the ground's check a Person re-check.--assume-test <selector>+--counter-test <selector>+ at least one--on-platform- at least one
--triggered-by+ at least one--surface(and averified_at_sha, resolved from--verified-at-shaorgit rev-parse HEAD) make the ground's check a Test.
- at least one
--observe,--blame,--verified-at-sha,--authority,--jurisdiction,--source-ref, and--dry-runare decision-level, not per-ground. (--dry-runmay sit anywhere in the stream; a literal--dry-runin value position — e.g.--observe "--dry-run"— is kept as the value, not read as the flag.)
If a per-ground flag appears before any --assume / --reject, it is refused:
<flag> has no preceding --assume/--reject ground. A missing value is refused with
<flag> requires a value. An unrecognized flag is refused with decide: unknown flag <x>.
--from-git <commit> seeds the decision envelope from a commit rather than the positional
argument — the thinking is already written in the commit, so it is not re-typed:
- the decision text is the commit subject (
git show -s --format=%s <commit>); - the default blame is the commit author (
%an) — unless the subject starts with aRole:prefix from the closed set {Dev, QA, Product, Mac, User} (case-insensitive), in which case the blame is that role; an explicit--blameoverrides either; - provenance is appended to
observe: any#<n>/R<n>tokens in the commit subject, then anyRefs #<n>lines in the commit body.
Grounds are NEVER inferred from the commit. The subject is scanned only for #<n> / R<n>
provenance tokens and a leading Role: prefix; the body only for Refs #<n> lines — never parsed
for reasons. The chosen reasons and roads-not-taken stay human-authored:
add them by hand with --assume / --reject (and their bindings) exactly as for a positional
decision. A --from-git decision with no --assume / --reject records just the subject and
the provenance.
Exactly one of {positional decision, --from-git} is allowed:
- both given →
decide: decision given twice (positional and --from-git); - neither given →
decide: needs a decision (positional) or --from-git; - a commit git cannot resolve →
decide: cannot read commit <commit>.
- Empty decision →
decision text is empty. - R2 — revisit XOR test. A single ground cannot be both a Person re-check and a Test:
a ground cannot be both --revisit and --assume-test (R2). - Rejected roads carry no check by default; a user-ruled rejected road may carry a falsifiable
tripwire. A human re-check (
--revisit) on a--rejectground is always refused. A test binding (--assume-test) on a--rejectground is refused unless the decision carries--authority user-ruled(capability A — the tripwire lift), and a--counter-testis still required (no harvested rejected-road tripwire) →a rejected road can carry a tripwire test only when the decision is --authority user-ruled(ora road-not-taken (rejected) ground cannot carry a human re-checkfor--revisit). The tripwire binds only a structural token; a prose re-walk with no token (e.g. #1194's milestone re-assignment) has nothing to bind and stays surface-only — it is not caught. - No vacuous test binding.
--assume-testwithout--counter-test→a test binding requires --counter-test (no vacuous binding). - A test binding needs full liveness. A test binding missing a platform, trigger, or
surface →
a test binding requires at least one --on-platform, --triggered-by, and --surface. - Liveness flags without a test.
--counter-test/--on-platform/--triggered-by/--surfaceon a ground that is not a test binding →--counter-test/--on-platform/--triggered-by/--surface require --assume-test. - Unresolvable
verified_at_sha. No--verified-at-shaand no git HEAD →cannot resolve verified_at_sha (not a git repo?) — pass --verified-at-sha; a malformed value →verified_at_sha must be 40 lowercase hex: <sha>. - An author must be named (R5). No
--blameand nogit config user.name→no author: pass --blame, or set git config user.name; an explicit empty--blame→--blame must be non-empty. - A declared authority must be in vocabulary. An
--authorityvalue other thanuser-ruledoragent-disposable→authority must be user-ruled or agent-disposable. - A declared jurisdiction must be in vocabulary. A
--jurisdictionvalue outside{A, B, C, D}→jurisdiction must be one of A, B, C, D (got <v>). - A source-ref must be non-empty. An empty
--source-ref→--source-ref needs a non-empty value. - Exactly one decision source. Positional decision and
--from-git→decide: decision given twice (positional and --from-git); neither →decide: needs a decision (positional) or --from-git; an unresolvable commit →decide: cannot read commit <commit>. - No store. Running outside an initialized store →
no .evolving/ store here — run \ev init` first`.
A best-effort R3 lint is also run over the decision / observe / claim text: a
self-evolve verb (e.g. self-improve) emits a warning: on stderr but does not fail
the command — a re-wording evades it.
Exit code: 0 on success; 1 on any refusal above.
Output (stdout / stderr):
- success (stdout):
recorded <id> (<n> ground(s)) - failure (stderr):
error: <message>
Example — a chosen ground re-checked by a human, plus a road-not-taken:
ev decide "build our own retrieval; reject pgvector" \
--observe "evaluating retrieval backend for v2" \
--assume "team has bandwidth to maintain it long-term" \
--revisit "Q3" \
--reject "pgvector: would lock our schema" \
--blame "You"
# → recorded <id> (2 ground(s))Example — a chosen ground guarded by a test instead of a human:
ev decide "restore-safety counter DB-backed; reject Redis" \
--observe "multi-pod restore-safety counter" \
--assume "no Redis; multi-pod coordination via the existing DB" \
--assume-test "pytest tests/test_redis_absent.py" \
--counter-test "pytest tests/test_redis_absent.py::test_redis_injection_flips_red" \
--on-platform linux-ci \
--triggered-by pyproject.toml \
--surface pyproject-deps \
--verified-at-sha d308afac1b2c3d4e5f60718293a4b5c6d7e8f901 \
--reject "Redis: a new infra dependency" \
--blame "You"Example — seed the decision text + blame + provenance from a commit, then add a human-authored ground and a marked authority by hand:
ev decide --from-git HEAD \
--authority user-ruled \
--assume "team still wants this posture" \
--reject "the alternative: it would lock us in"
# decision text = the commit subject; blame = a leading `Role:` subject prefix when present,
# else the commit author; provenance into observe = the subject's `#<n>` / `R<n>` tokens then
# the body `Refs #<n>` lines; grounds stay hand-authored.
# → recorded <id> (2 ground(s))Synopsis: record an agent proposal — a decision an agent suggests. It reuses ev decide's
grammar for the decision + its grounds, but is always agent-proposed / agent-disposable,
unbound, and inert until a human ratifies it. The trust fields can never be flag-set — that is
the whole point of a separate agent door.
ev propose "<decision>" [--observe …] [--assume …]… [--reject …]… [--jurisdiction …] [--source-ref …] [--blame <agent-id>] [--json]
ev propose --from-git <commit> [trailing flags…]
What it does: assembles the decision exactly as ev decide does, then forces
provenance = agent-proposed and authority = agent-disposable (no flag overrides them). The proposal
is inert: it never gates (LOCK 3 maps any not-green verdict to the non-gating memo) and never
reaches ev brief (the boot-read excludes agent-proposed) until a human ratifies it.
Blame never reads git. Because an agent typically runs under the human's git identity, ev propose
resolves its author without the git config fallback: --blame <id> if given, else the
EV_AGENT_ID environment variable (the runner's declared agent), else the literal agent. A proposal's
author is therefore never silently a human.
Unbound — refused flags. A check and authority attach only when a human ratifies, so ev propose
refuses --assume-test, --counter-test, --on-platform, --triggered-by, --surface,
--verified-at-sha, --revisit, and --authority with
ev propose records an UNBOUND proposal — <flag> is not allowed here ….
Flags: the decision-level + ground flags of ev decide except the refused ones
above, plus:
| Flag | Takes | Effect |
|---|---|---|
--source-ref |
a key | The producer's round / work-unit key (opaque to ev; it derives a dedup key). Re-proposing the same --source-ref that already names a pending proposal is an idempotent no-op — it prints already proposed … and appends no second event, so a fleet that re-emits a round never piles duplicates. |
--blame |
an agent id | The proposing agent's identity. If omitted: EV_AGENT_ID, else agent. Never git config. |
--json |
flag | Emit {"kind":"ev-proposed","id":…,"provenance":"agent-proposed","authority":"agent-disposable","blame":…} — the citable envelope a runner records to cite when a human ratifies it. |
Output (stdout): proposed <id> (<n> ground(s)) — agent-proposed, awaiting ratification (or the
--json envelope). On an idempotent --source-ref repeat it instead prints
already proposed "<decision>" as <id> for this source_ref — no-op (awaiting ratification) and writes
nothing. Exit: 0 ok · 1 on a write/validation failure · refused flags fail with the message above.
Synopsis: a human ratifies an agent proposal — the only bridge from agent-proposed to a
user-ruled ruling.
ev ratify <proposal-id> --blame <human>
What it does: mints a child tick that copies the proposal's hashed payload (decision /
observe / grounds) verbatim, flips provenance → human-now and authority → user-ruled, and attaches
the ratifies:<proposal-id> edge. The proposal itself is never rewritten — it stays immutable,
thereafter shown "ratified by <child>". Same mint-a-child mechanics as ev supersede;
the child's id is content-addressed over the copied payload, so the proposal and its ratified child are
recognizably the same decision (and the proposal collapses under the child in brief / list /
pending).
--blame is REQUIRED and never auto-filled. Ratification is the one op where a git config fallback
would forge a human, so the ratifying human must be named explicitly.
| Flag | Takes | Required | Effect |
|---|---|---|---|
--blame |
a human id | yes | The ratifying human. Never auto-filled from git. |
Refusals: ratifying a tick that is not agent-proposed →
ev ratify only ratifies an agent proposal; tick <id> is <provenance> (nothing to ratify). A missing
--blame is refused by the arg parser (exit 2).
Output (stdout): ratified <proposal-id> → <child-id> (now user-ruled, human-now). Exit: 0 ok ·
1 on a write failure / no such tick / not-a-proposal.
Synopsis: list the agent proposals awaiting ratification. A pull-only view — a query a human runs, never a notifier (no push, no unread, no badge).
ev pending [--source-ref <key>]
What it does: shows every live agent-proposed decision that has not yet been ratified. (A
ratified proposal collapses under its user-ruled child, so it drops out automatically.) Decision-led,
newest first; the ○ provenance glyph leads each row on a TTY, with a … awaiting ratification — ev ratify <id> --blame <you> footer; a pipe gets today's tab-separated bytes (id, status, decision,
blame). Empty → no proposals awaiting ratification.
--source-ref <key> narrows the queue to proposals carrying that source_ref (the producer's round /
work-unit key, matched on the same dedup key ev propose uses) — so a piling queue stays triageable
one round at a time. Empty under a filter → no proposals awaiting ratification for source_ref <key>.
Sunset: aging long-stale proposals out of the default view (to an --all) is a stated future
refinement; for now every un-ratified proposal is shown.
Synopsis: attach a test to an unbound ground of the current HEAD decision after the fact. Because the check is part of the hashed payload, this writes a new child tick rather than mutating the existing one.
ev guard "<selector>" <id> [<target>] \
--counter-test "<selector>" \
--on-platform <p> [--on-platform …] \
--triggered-by <t> [--triggered-by …] \
--surface <s> [--surface …] \
[--verified-at-sha <40-hex>] [--blame "<name>"] [--authority <value>]
Positional arguments:
| Position | Name | Required | Meaning |
|---|---|---|---|
| 1 | selector |
yes | The test selector to bind as the ground's check reference. |
| 2 | id |
yes | The tick to amend — must be the current HEAD. |
| 3 | target |
conditional | Which ground to bind: a claim text or a numeric index. Required only when more than one ground is still unbound. |
Flags:
| Flag | Takes | Required | Effect |
|---|---|---|---|
--counter-test |
a selector | yes | The test that must flip red if the claim breaks. |
--on-platform |
a platform | yes (≥1) | Liveness platforms. Repeatable. |
--triggered-by |
a trigger | yes (≥1) | Liveness triggers. Repeatable. |
--surface |
a surface | yes (≥1) | Liveness surfaces. Repeatable. |
--verified-at-sha |
40 lowercase hex | no | Commit the test was verified at; defaults to git rev-parse HEAD. |
--blame |
a name | no | Author; defaults to git config user.name. |
--authority |
user-ruled | agent-disposable |
no | A declared (non-hashed) authority tag set on the child tick, surfaced by reopen / show / list / brief. Required (user-ruled) when binding a test to a rejected road (capability A — the tripwire must be user-ruled). An out-of-vocabulary value is refused. |
Target resolution: with one unbound ground, target may be omitted. With more than
one, it is required — more than one unbound ground — name the target (claim or index).
A numeric target out of range → ground index <i> out of range; a claim that matches no
ground → no ground with claim "<t>"; a claim that matches several →
ambiguous: multiple grounds with claim "<t>". With no unbound ground and no target,
it is refused — no unbound ground to guard.
The refusals it enforces:
- HEAD only. Amending anything other than HEAD →
guard can only amend the current HEAD decision; <id> is not HEAD (<head>). - A human re-check stays human (R2). A Person-checked ground cannot be force-bound to a
test →
a human-rechecked ground cannot carry a test (R2 hard error). - Rejected roads carry no test by default; a user-ruled rejected road may carry a tripwire.
Binding a test to a
--rejectground is refused unless the child carries--authority user-ruled(capability A); the counter-test stays required (no harvested rejected-road tripwire) →a rejected road can carry a tripwire test only when guarded with --authority user-ruled. - Already bound. A ground that already has a check →
ground already has a check. - No vacuous binding. Empty
--counter-test→a test binding requires a counter-test (no vacuous binding). - Full liveness required. Missing platform / trigger / surface →
a test binding requires at least one platform, triggered-by, and surface. - Plus the same
verified_at_shaand--blameresolution rules asev decide. - A declared authority must be in vocabulary. An
--authorityvalue other thanuser-ruledoragent-disposable→authority must be user-ruled or agent-disposable. - Unknown tick. An
idnot present in the store →no tick with id <id>.
Exit code: 0 on success; 1 on any refusal above.
Output (stdout / stderr):
- success (stdout):
bound; wrote child <child-id> - failure (stderr):
error: <message>
Example — bind a test to the schema stays frozen ground of the HEAD decision:
ev guard "pytest tests/test_schema_frozen.py" <HEAD-id> "schema stays frozen" \
--counter-test "pytest tests/test_schema_frozen.py::test_schema_change_flips_red" \
--on-platform linux-ci \
--triggered-by schema.sql \
--surface schema-ddl
# → bound; wrote child <child-id><HEAD-id> is the id printed by the most recent ev decide / ev guard.
Synopsis: ingest an existing decision history into the ledger from one or more sources —
idempotently. The primary, format-neutral intake is the Canonical Decision Intake
Contract (--source canonical:<path.jsonl>): a producer-owned adapter (or a live runner)
emits one canonical JSON line per decision, and ev re-validates every line through its own
read-path validators on the way in. Four built-in extractors (gitlog, to-human,
decisions-immutable, escalation) are a peripheral convenience for simple substrates,
HARVESTING the rulings and structured roads-not-taken those sources already record. ev migrate
also reconciles a source against the store (the capture-gap report), and harvests an existing
test as a check shape (--bind-check). For the format, the trust boundary, and writing an
adapter, see migrating.md.
ev migrate --source canonical:<path.jsonl> [--source …] [--jurisdiction-map <path>] [--dry-run] [--blame <fallback>]
ev migrate --source <kind>:<path> [--source …] [--jurisdiction-map <path>] [--dry-run] [--blame <fallback>]
ev migrate --reconcile --against <kind>:<path>
ev migrate --bind-check <selector> --on-platform <p> --triggered-by <t> --surface <s> [--verified-at-sha <40-hex>]
Flags:
| Flag | Takes | Required | Effect |
|---|---|---|---|
--source |
<kind>:<path> |
for a backfill | A source to import. <kind> ∈ {canonical, gitlog, to-human, decisions-immutable, escalation} — canonical is the primary intake (JSONL); the other four are convenience extractors. <path> is read from disk. Repeatable; sources import in deterministic source_key order. |
--jurisdiction-map |
a <path> |
no | A source_key → A/B/C/D bucket map (see below). It is how an imported decision gets its jurisdiction: a record whose source_key is in the map carries that bucket; a record absent from the map imports untagged. Purely additive — omitting it imports every record untagged. |
--dry-run |
— | no | Parse + report what would import; write no tick. |
--blame |
a name | no | Fallback author for any source record carrying none. R5 stays intact — a record with neither its own author nor this fallback is not imported; it is reported as a source-only gap (an author is never invented). |
--reconcile |
— | no | Reconcile mode: join --against against the store and report the buckets instead of importing. |
--against |
<kind>:<path> |
with --reconcile |
The source to reconcile against. |
--bind-check |
a selector | no | Harvest an existing test as a bound check shape (counter-test absent, full liveness still required) and print it. Does not write a tick by itself. |
--on-platform / --triggered-by / --surface |
a value (repeatable) | with --bind-check (≥1 each) |
The liveness the harvested check declares. A harvest with an empty set is refused (no half-harvest). |
--verified-at-sha |
40 lowercase hex | no | The sha the --bind-check harvest was verified at; defaults to git rev-parse HEAD. |
--source canonical:<path.jsonl> reads the Canonical Decision Intake Contract: one JSON
object per line (JSONL); blank lines and #-comment lines are skipped. Each line is independent
and idempotent on its dedup key. This is the format-neutral seam both a legacy adapter and a
future live runner emit. The full spec — the closed envelope, the trust boundary, the ingest
gates, and writing an adapter — is in migrating.md; the essentials:
- The closed envelope. Each line's key set is exactly
{kind, decision, observe?, grounds, blame?, authority?, jurisdiction?, source_ref?, provenance}.kindMUST be the fixed string"ev-decision-intake". An unknownkind, or any unknown envelope key, is a hard loud failure — the wire envelope is strict and does not get the on-disk forward-compat tolerance, so a mis-piped file cannot smuggle a field past ingest. evowns identity. The contract carries noid,parent_id,held_since, orstatus—evcomputes/stamps those (parent_id = HEAD;held_since= write-time;status = "live";id =the content-addressed hash). The producer never supplies identity; that is the whole trust boundary.evre-validates every ground. Thegrounds[]/checksub-shape is byte-identical to the on-disk one, and every element is re-parsed throughev's own read-path validators at ingest (claim non-empty;supports ∈ {chosen, rejected:<non-empty>}; full check shape). A malformed ground is rejected at the door.source_refis opaque. Taken verbatim (a string) or carried whole (an object);evderives only a dedup key from it and never re-sniffsobservefor a token whensource_refis present.provenanceis REQUIRED on a canonical record. The producer always knows whether it is emitting backfill (imported) or a live proposal (agent-proposed/human-now), so it must declare it — there is no default, and a record that omits it is refused at the door (acanonical line <n>: a canonical record must declare provenance …error). This closes the silent-failure footgun where an omitted provenance defaulted toimported— inert: not ratifiable, absent frombriefANDpending. (Only the convenience extractor kinds —gitlog/to-human/decisions-immutable/escalation— keep an inherentimporteddefault, since they parse documents that cannot declare provenance.)
A worked record (one JSONL line, shown pretty):
{
"kind": "ev-decision-intake",
"decision": "rate-limit lives at the edge proxy",
"observe": "round R1043",
"grounds": [
{ "claim": "the edge sees every request first", "supports": "chosen" },
{ "claim": "the app tier double-counts", "supports": "rejected:app-tier" }
],
"blame": "Wang Yu",
"authority": "user-ruled",
"jurisdiction": "C",
"source_ref": "R1043",
"provenance": "imported"
}The same refusals ev verify enforces at rest are applied at the door, so a malformed record
never lands:
- a
C/D(detect-only) decision may carry no runnable Test check; - a rejected-road Test check (a tripwire) is allowed only when
authority=user-ruledand acounter_testis present — the same rule asev decide/ev guard(capability A), so the user-ruled-only rule is structural across every producer; - a harvested check (a Test with no counter-test) is allowed only for
provenance=imported— a freshagent-proposedTest binding must carry a counter-test and full liveness, exactly likeev decide/ev guard; - jurisdiction precedence: an inline
jurisdictionon a canonical record wins over--jurisdiction-map; the map fills only a record that declares none; a record declaring a different bucket than the map is a hard error.
The gitlog / to-human / decisions-immutable / escalation kinds are pure, format-aware
extractors for simple substrates — a peripheral path beside the canonical contract. An
adopter with a bespoke history writes a small adapter that emits canonical JSONL instead (see
migrating.md); these built-ins are never widened to swallow one adopter's
grammar. They parse rulings and structured rejected-roads only — a road becomes a ground
iff the source declares it with an explicit rejected: <option>: <why> (or reject …) token. A
free-text prose reason is never mined into a ground: a block with no structured road yields
a record with zero grounds (an honest capture), never a synthesized one.
gitlog— a chat-room / git log; each## R<N> …header is one decision, keyed by itsR<N>/#<n>round token.to-human— theRESOLVED/FLAGmarkdown blocks (the authority substrate); a### RESOLVED <key>: <decision>is a user-ruled decision, a### FLAGan open one — both captured.decisions-immutable— a document split on numbered## N./## §Nsections, one decision per section, keyed§N.escalation— the sameRESOLVED/FLAGreader asto-human, path-parameterized (no layout of its own).
Each extracted record's source_key (e.g. R2289, #555, §3) is carried into the hashed
observe and written to the non-hashed source_ref, so the backfill can dedup and
reconcile durably — from the record's own payload, never from the events log.
A backfill sorts records by source_key, then for each computes the content-addressed id it
would take and skips it if that key is already in the store — so running ev migrate
twice writes nothing the second time. The chain is kept (keep-chain): a back-dated
mid-chain insert that re-parents an existing tick is counted and reported as re-linked,
never rewritten. Every record — canonical or extractor-built — is appended through the same
single hashing path as ev decide (one compute_id, one write, one R3 lint).
Idempotency is keyed on the durable source_key, not on the non-hashed tags. So when a
re-imported record carries the same key as a stored tick but its resolved non-hashed tags
(authority / jurisdiction / provenance) differ from what is already stored, ev migrate does not silently skip it. A tick is immutable, so the difference is never applied —
but it is surfaced loudly, never dropped, because a silently-discarded corrected authority is
exactly the false-green ev exists to refuse (e.g. a ruling first imported as an open item, later
corrected upstream, would otherwise never reach ev brief).
Per differing record, one line on stderr:
discrepancy: source <key> (tick <id>): authority stored=<a> incoming=<b> — NOT applied (ticks are immutable; resolve with `ev supersede <id>`)
(the <…> clause lists every differing tag — authority / jurisdiction / provenance —
each as stored=<v> incoming=<v>, ; -joined), and the run's summary line gains a trailing
, N discrepancy(ies) — see above count. This runs under --dry-run too (it compares against
the stored tick without writing). The record is still skipped (counted in skipped), and the
exit code stays 0 — a discrepancy is a standing report, not a failure.
The remedy is ev supersede: it appends a corrective child carrying the
corrected tag, so the right value surfaces while the stale tick stays as honest history. This
means ev migrate is no longer a clean "all-zeros = done" signal: a non-zero discrepancy
count is a correction pending, and re-running the import will report it again until you resolve
it with ev supersede.
On this import path the convenience extractor kinds (gitlog / to-human /
decisions-immutable / escalation) default a record's provenance to imported (history)
when it declares none; a canonical record must declare provenance explicitly (no default —
see above). Fresh authorship can never reach here: ev decide / ev guard always stamp
human-now, so a forbidden op can never be laundered as imported (see the provenance partition
in concepts.md).
An imported decision is untagged by default — and an untagged decision can gate. --jurisdiction-map <path> is how a backfilled record gets its A/B/C/D jurisdiction tag, so a C/D import becomes
structurally detect-only rather than landing as a gating record.
The file is a plain text source_key → bucket map, one pair per line:
# source_ref -> bucket (a `#` line is a comment; blank lines are skipped)
R2289 C
#1194 C
§3 A
- each non-blank, non-
#line is exactly two whitespace-separated tokens:<source_key> <bucket>; <source_key>is the record's durable key (the dedup key derived from itssource_ref, e.g. anR<N>/#<n>/§Ntoken — the same key the extractors carry intosource_refand used by reconcile);<bucket>is one of{A, B, C, D}. An out-of-vocabulary or malformed line is a hard error that names the offending line and writes nothing (jurisdiction-map line "<line>": …).
A record whose key is in the map imports carrying that jurisdiction; a record absent from the map
imports untagged (the map is purely additive — an omitted --jurisdiction-map tags nothing). For a
canonical record, an inline jurisdiction wins over the map; the map fills only a record that
declares none; a record declaring a different bucket than the map is a hard error
(source <key>: inline jurisdiction <inline> conflicts with the --jurisdiction-map entry <mapped>).
Because jurisdiction is non-hashed, tagging never moves a tick id: a re-run is still idempotent (a
tagged record already in the store is skipped, not rewritten), and the golden vectors do not move.
This is how a detect-only import is made detect-only structurally, not by convention. A
C/D-tagged decision can never gate: any not-green verdict on it is mapped to the non-gating
memo label (so ev check --exit-on-red can never trip on it), and ev verify forbids a C/D tick
from carrying a runnable Test check at all. So a gateway record like #1194 mapped to bucket C
imports as a permanent detect-only MISS — surfaced forever, gating never (see concepts.md).
Reconcile does not import. It reads the source's source_keys and the store's durable keys
(the dedup key of each tick's source_ref, else the first round token in its hashed observe)
and reports four buckets: in-both, source-only (the capture gap — a ruling the source
has that the ledger never captured), store-only, and un-keyable (store ticks with no
derivable key, counted separately). --against accepts the same kinds as --source, including
canonical:<path.jsonl>.
The refusals it enforces:
- A backfill needs a source. No
--source(and not--reconcile/--bind-check) →ev migrate needs at least one --source <kind>:<path> (or --reconcile / --bind-check). - Reconcile needs a target.
--reconcilewithout--against→--reconcile requires --against <kind>:<path>. - A known source kind. A
<kind>outside the five →unknown source kind <k> (expected canonical | gitlog | to-human | decisions-immutable | escalation). - A
<kind>:<path>shape. A--source/--againstmissing the colon →--source expects <kind>:<path>, got <spec>; an unreadable path →reading <path>: <io error>. - A strict canonical envelope. A
canonical:line that is not JSON, not an object, carries an unknown envelope key, or whosekindis notev-decision-intakeis a hard failure naming the line, e.g.canonical line <n>: field outside closed schema: <k>orcanonical line <n>: not an ev-decision-intake record (kind=<v>); a malformed ground fails through the same read-path validator a stored tick uses. - A canonical record needs a durable key. A
canonical:record that yields no dedup key — nosource_refand no round/#issuetoken inobserve— is rejected at the door:canonical line <n>: a record needs a source_ref (or a round/#issue token in observe) for idempotent re-import. Without a durable key, distinct records would collide on an empty key and re-import every run, so the producer must emit a stablesource_ref(see migrating.md). - An ingest gate. A
C/Dcanonical record carrying a Test check →source <key>: a <C|D> jurisdiction (detect-only) decision cannot carry a runnable test check; a harvested check on a non-importedrecord →source <key>: a harvested test check (no counter-test) is allowed only for imported history, not <provenance>; an inline jurisdiction conflicting with the map →source <key>: inline jurisdiction <inline> conflicts with the --jurisdiction-map entry <mapped>. - No half-harvest (
--bind-check). An empty platform / trigger / surface →a harvested binding requires at least one platform, triggered-by, and surface (no half-harvest); an empty selector →a harvested binding requires a non-empty test reference; a malformed sha →verified_at_sha must be 40 lowercase hex: <sha>. - R5 is never bypassed. A source record with no author and no
--blamefallback is not imported — it is surfaced as a source-only gap (an author is never fabricated). - No store. →
no .evolving/ store here — run \ev init` first`.
Exit code: 0 on success; 1 on any refusal above.
Output (stdout / stderr):
- backfill (stdout):
<(dry-run) >imported N, skipped M, re-linked K, J source-only gap(s)(the(dry-run)prefix only under--dry-run); a trailing, D discrepancy(ies) — see aboveis appended only whenD > 0tag discrepancies were surfaced (see above). - per discrepancy (stderr, one line each):
discrepancy: source <key> (tick <id>): <tag stored=… incoming=…; …> — NOT applied (ticks are immutable; resolve with \ev supersede `)`. - reconcile (stdout):
reconcile: in-both N, source-only M (the capture gap), store-only K, un-keyable J. --bind-check(stdout):harvested check (falsifiability not proven; no counter-test): "<selector>" on [<platforms>] triggered-by [<triggers>] surface [<surfaces>].- failure (stderr):
error: <message>.
Example — ingest a canonical decision-intake stream emitted by an adapter (one line per
decision, each declaring its provenance — imported for backfilled history, agent-proposed
for a live proposal), then re-run to confirm idempotency:
ev migrate --source canonical:decisions.jsonl --blame "Wang Yu"
# → imported 12, skipped 0, re-linked 0, 1 source-only gap(s)
ev migrate --source canonical:decisions.jsonl --blame "Wang Yu"
# → imported 0, skipped 12, re-linked 0, 0 source-only gap(s) (idempotent on the dedup key)Example — backfill a chat-room log and a decisions doc in one idempotent pass (with a blame fallback for un-attributed records), then reconcile the authority substrate against the store to find the capture gap:
ev migrate \
--source gitlog:chat-room.md \
--source decisions-immutable:DECISIONS.md \
--blame "Wang Yu"
# → imported 7, skipped 0, re-linked 0, 2 source-only gap(s)
ev migrate --reconcile --against to-human:to-human.md
# → reconcile: in-both 5, source-only 3 (the capture gap), store-only 1, un-keyable 0Example — import another team's rulings tagged detect-only via a --jurisdiction-map, so the
gateway record #1194 lands as a permanent detect-only MISS (bucket C) instead of a gating record:
cat gateway.map
# # source_ref -> bucket
# #1194 C
# R2289 C
ev migrate --source escalation:escalation.md --jurisdiction-map gateway.map --blame "Wang Yu"
# → imported 2, skipped 0, re-linked 0, 0 source-only gap(s)
# `#1194` now carries jurisdiction C: surfaced forever (memo), gating never; a record absent
# from the map imports untagged.Example — harvest an existing test as a check shape (a counter-test-less binding;
falsifiability is not proven, so add one later with ev guard):
ev migrate --bind-check "pytest tests/test_redis_absent.py" \
--on-platform linux-ci --triggered-by pyproject.toml --surface pyproject-deps
# → harvested check (falsifiability not proven; no counter-test): "pytest tests/test_redis_absent.py" …Synopsis: replace a prior ruling under ev's append-only law. It never rewrites the target
tick; it appends a child carrying an explicit supersedes:<target-id> relation-overlay edge
(non-hashed, so the child's id is unaffected). Two branches, dispatched by whether a new ruling text
is given:
- RE-TAG —
ev supersede <id>with only tags, no new ruling: copies the target's hashed payload (decision/observe/grounds) verbatim — recognizably the same decision — and carries a corrected non-hashed tag (authority/jurisdiction/provenance). Write the id; don't restate the ruling. - OVERTURN —
ev supersede <id> "<new ruling>": a fresh decision (theev decidegrammar for its grounds) that replaces the prior ruling. A supersession must say why the prior ruling no longer holds, so at least one--assumeis required.
In both branches supersedes (one of ev's two relation-overlay edges, with ratifies; the
general case-law graph is deliberately not built — a machine-fence test pins this) makes the target
leave every current view: ev brief / ev list collapse the lineage to its current state (reading the
edge precisely), and ev reopen <id> marks an overturned ruling "superseded by <child>" so its
staleness is visible on direct inspection. The stale parent stays as honest history in ev log. A child
that carries no supersedes edge still collapses via content-equality — the fallback that keeps the
edge-less legacy shape working.
ev supersede <id> [--authority <v>] [--jurisdiction <v>] [--provenance <v>] [--blame "<name>"] # re-tag
ev supersede <id> "<new ruling>" --assume "<why>" [--reject "<opt>: <why>"] [tags] [--blame "<name>"] # overturn
Positional arguments: id (required) is the tick this supersedes. decision (optional) is the new
ruling — omit it to re-tag the target's standing in place; give it to overturn.
Flags:
| Flag | Takes | Branch | Effect |
|---|---|---|---|
--assume |
"<why>" |
overturn (required, repeatable) | Why the prior ruling no longer holds — a chosen ground of the new ruling. |
--reject |
"<option>: <why>" |
overturn (repeatable) | A road not taken (same <option>: <why> form as ev decide). |
--authority |
user-ruled | agent-disposable |
both | The standing. Out-of-vocabulary is refused. |
--jurisdiction |
A | B | C | D |
both | The decision class. Out-of-vocabulary is refused. |
--provenance |
imported | agent-proposed | human-now |
re-tag | The corrected provenance. |
--source-ref |
a key | overturn | An opaque producer / work-unit key for the new ruling. |
--blame |
a name | no* | The author. If omitted, falls back to git config user.name; one must resolve to a non-empty name (R5). A supersession is a human-authored act. |
For a re-tag, at least one of --authority / --jurisdiction / --provenance must be given
(else there is nothing to re-tag). It is unreachable from ev migrate / canonical intake, so an
adapter can never launder a tag or overturn an import — the only way is a named human appending a child.
The refusals it enforces:
- Re-tag needs a tag. No new ruling AND no tag →
ev supersede (re-tag) needs at least one of --authority / --jurisdiction / --provenance — or pass a new ruling text to overturn. - Overturn needs a reason. A new ruling with no
--assume→ev supersede <id> <new ruling> needs at least one --assume <why the prior ruling no longer holds>. - No-op re-tag. Every supplied tag already holds that value →
tick <id> already carries those tags — nothing to re-tag. - Unknown id. An
idnot in the store →no such tick: <id>(re-tag) /no such tick to supersede: <id>(overturn). - Vocabulary and the detect-only structural lock (a
C/Djurisdiction may carry no runnable Test) are enforced with the same stringsev decideuses. - No store. →
no .evolving/ store here — run \ev init` first`.
Exit code: 0 on success; 1 on any refusal above.
Output (stdout / stderr):
- re-tag success:
re-tagged <child> (supersedes <id>)— no ground count (it copies the parent's grounds verbatim, so a count would be a zero-entropy echo; a count appears only at creation). - overturn success:
recorded <child> (<n> ground(s)) — supersedes <id>. - failure (stderr):
error: <message>.
Example — a ruling imported with authority omitted (re-tag so it surfaces), then a later overturn:
ev supersede 638c47b0c9dd --authority user-ruled --blame "You"
# → re-tagged <new-child> (supersedes 638c47b0c9dd) (now surfaces in ev brief)
ev supersede <child> "http handling = stream" --assume "buffering OOMs at scale" --blame "You"
# → recorded <new> (1 ground(s)) — supersedes <child>
# ev reopen <child> now marks it "superseded by <new>"A migrate discrepancy (a re-import whose resolved tags differ from the stored tick — see
ev migrate) is resolved by the re-tag
branch.
Synopsis: evaluate every live Test-bound ground against its cached receipts and print one
flat verdict per ground — facts, never a score or a rank. Optionally run the bound tests first
(--run), and gate the exit code (--exit-on-red).
ev check [--run] [--platform <p>] [--exit-on-red] [--offline] [--attest <p1,p2,…>]
Like ev brief / ev list, check first collapses each corrective
lineage to its current state (an ev supersede child supersedes the stale tick it
re-tags) and evaluates only the current live decisions. So a correction that demotes a
decision — to agent-proposed, or away from user-ruled — takes effect at the gate, and a
superseded tick neither prints a duplicate row nor gates. The superseded tick stays reachable
via ev log / ev show.
Flags:
| Flag | Takes | Required | Effect |
|---|---|---|---|
--run |
— | no | For each live Test-bound ground that declares --platform, run its bound ref locally and append one receipt before evaluating. |
--platform |
a platform | no | Which declared platform a --run satisfies (the receipt's platform). Defaults to local. |
--exit-on-red |
— | no | Exit 1 if any ground is not green (and not n/a / exempt). |
--offline |
— | no | Use only the cached staleness reference; never resolve it fresh (non-blocking). |
--attest |
a comma-list of platforms | no | The platforms this runner speaks for. A declared platform not in this set becomes a non-gating exempt fact instead of not-run. Omit --attest to attest all declared platforms (the default). |
Per-runner attestation (--attest). A test binding declares the platforms it must be live
on (its --on-platform set). A single runner usually speaks for only some of them. --attest linux-ci,mac tells ev check that this runner attests linux-ci and mac: any other
declared platform on a binding is reported as exempt — a co-equal, non-gating fact —
rather than counted as a missing not-run. With --attest omitted, every declared
platform is attested (the cross-platform / audit default), so a platform with no receipt is
not-run. exempt, like n/a and green, never trips --exit-on-red.
The flat verdict labels (one per Test-bound ground; non-Test grounds never print):
green, red, gray->red, not-run, stale, unproven, silently-unbound, exempt,
memo. Each is a fact; none outranks another (unproven = ev check --run ran the
counter-test and it did not flip — a vacuous check). memo is the non-gating label a
not-green verdict takes in two structural cases: a C/D-jurisdiction (detect-only)
decision, or an agent-proposed tick (capability B — LOCK 3: an agent cannot author a
gating rule; only a named human ratifies one). In both cases the row still prints, naming the
decision, but it can never trip --exit-on-red — a structural guarantee (see
concepts.md), the sibling of exempt. The agent-proposed case also protects the
tripwire: an agent-authored tripwire cannot gate.
Harvested rows. A Test binding whose counter_test is absent (a harvested binding
from ev migrate) is evaluated exactly as any other — a passing harvested test still reads
green, a failing one still red — but its row's <detail> is prefixed
harvested — falsifiability not proven; …, and after the rows a trailing
harvested-unproven: N of M test bindings have no counter-test line
counts the debt. Run ev guard to add a counter-test and prove falsifiability.
Exit code: 0 normally; 1 only under --exit-on-red when any ground is not green
(n/a, exempt, and memo do not count — including any agent-proposed ground, which is mapped
to memo), or when there is no store / the store cannot be read.
Output (stdout / stderr):
- per Test-bound ground (stdout, one row each):
<label>\t<file>\t<claim>\t(<detail>)—<claim>is quoted ({:?});<detail>ismissing: <platforms>fornot-run, the stale reason forstale, elseran <ts>orno receipt. - after the rows, only when
--runwas not passed (stdout): a note pointing the reader to runev check --run— to re-run each bound check and, when a declared counter-test exists, prove its falsifiability; when all bindings are harvested, record a fresh receipt (the note adapts to which case applies; under--runthe verdict itself carries it — anunprovenrow — so no note prints) - no Test-bound grounds (stdout):
no test-bound grounds to check - no store (stderr):
error: no .evolving/ store here — run \ev init` first` - store read error (stderr):
error: reading store: <io error>
Example — a mac runner attests only the platforms it speaks for, so a linux-ci-only
binding is exempt here, not not-run:
ev check --attest mac
# → exempt <file> "<claim>" (no receipt)
# → note: run `ev check --run` to re-run each bound check and record a fresh receipt
ev check --run --platform linux-ci --exit-on-redSynopsis: print one tick in full, exactly as stored on disk (the pretty JSON: hashed
payload plus the id / status / held_since / blame bookkeeping).
ev show <id>
Flags: none. The single positional id is required.
Exit code: 0 if the tick exists and is readable; 1 otherwise.
Output (stdout / stderr):
- success (stdout): the on-disk JSON of the tick, printed as-is — pure JSON, so
ev show <id> | jqis clean. (The declared tags live inside the JSON; for a human-readable single-tick view with the tags called out, useev reopen <id>.) - not found / malformed id (stderr):
error: no tick with id <id>(a non-id — a path, a..traversal — is refused, never read as a file). - read error (stderr):
error: reading <id>: <io error>
Example:
ev show 638c47b0c9ddSynopsis: audit the whole chain and its refusals; or, with --self-test, reproduce the
four frozen golden vectors.
ev verify [--self-test]
Flags:
| Flag | Takes | Required | Effect |
|---|---|---|---|
--self-test |
— | no | Recompute the four frozen golden-vector ids and exit. |
What ev verify checks: every tick parses against the closed hashed-schema (R1) and
check shape (R2); a C/D-jurisdiction (detect-only) tick carries no test check; every
stored id equals the hash of its payload and matches its filename (R4 / R6); every
parent_id resolves and the lineage is forward-only and acyclic (R6); every tick carries a
non-empty blame (R5); and a best-effort lexical lint flags self-evolve subject (R3) and
forbidden-op (R5) language. It reports all violations, not just the first. A tolerated
unknown top-level (non-hashed) key is not a violation — it is surfaced as a warning: on
stderr (see the two-tier schema in concepts.md). See concepts.md for the
refusals in depth.
Exit code: 0 when the chain is clean (or all golden vectors match); 1 when any
violation is found (or any golden id drifts), or if the store cannot be read.
Output (stdout / stderr) — plain ev verify:
- clean (stdout, two lines):
✓ chain intact: every id == hash(payload), lineage forward-only✓ every tick validates against the closed schema (R1) and check shape (R2)
- violations (stdout, one
✗ <line>per violation), then stderr:<n> violation(s) - store read error (stderr):
error: reading store: <io error>
Output — ev verify --self-test: one line per vector, ✓ or ✗, e.g.
✓ genesis: e2b337f53a1f (want e2b337f53a1f)
✓ case1: 638c47b0c9dd (want 638c47b0c9dd)
✓ harvested: 0cf784b51331 (want 0cf784b51331)
✓ rejected_tripwire: 9c5feb4582ac (want 9c5feb4582ac)
The harvested vector pins that omitting an absent counter_test keeps a harvested binding's
id byte-stable; the rejected_tripwire vector pins the id of a user-ruled decision whose
rejected-road ground carries a Test tripwire (see concepts.md).
Example:
ev verify
# → ✓ chain intact: every id == hash(payload), lineage forward-only
# → ✓ every tick validates against the closed schema (R1) and check shape (R2)
ev verify --self-testSynopsis: reverse lookup — given a test selector, name the decision(s) it guards. Scans
every live tick and matches the selector against each Test-bound ground's reference.
ev why <selector>
Flags: none. The single positional selector is required (the test selector to look up,
exactly as it was bound — e.g. pytest tests/test_redis_absent.py).
What it does: for every live tick, for every ground whose check is a Test whose
reference equals selector, it prints one line naming the tick file (its id), the guarded
decision, the guarding claim, and what that claim supports (chosen or rejected:<option>).
A selector that guards nothing is an error. Person re-checks and unbound grounds are never
matched (only Test bindings carry a selector).
Exit code: 0 when at least one ground matches; 1 when none match, or when there is
no store.
Output (stdout / stderr):
- match (stdout, one line per matching ground):
<file>\t<decision>\tguards: <claim> (<supports>)—<file>is the tick id (bare),<decision>and<claim>are quoted (Rust{:?}debug form),<supports>is bare: e.g.638c47b0c9dd\t"restore-safety counter DB-backed; reject Redis"\tguards: "Argus introduces no Redis; multi-pod coord via existing DB" (chosen) - no match (stderr):
"<selector>" guards nothing - no store (stderr):
error: no .evolving/ store here — run \ev init` first`
Example:
ev why "pytest tests/test_redis_absent.py"
# → <file> "<decision>" guards: "<claim>" (chosen)Synopsis: read one decision as it stands now — its text, what it observed, and the present verdict of every ground (for Test grounds, the frozen-at commit vs. the live check state). Read-only; it never writes a tick.
ev reopen <id>
Flags: none. The single positional id is required (the tick to reopen).
What it does: loads the tick, resolves the live staleness reference (offline — no network
fetch), then prints the decision, the observe line (only when non-empty), and one line per
ground. A Test ground shows the commit it was frozen at (first 8 hex of
verified_at_sha) and its present verdict from the same evaluator ev check uses (green,
red, stale, not-run, …). A Person ground and an unbound ground print their claim
without a check line.
Exit code: 0 when the tick exists and is readable; 1 when the id is missing or
unreadable.
Output (stdout / stderr):
- decision (stdout):
decision <id>: <decision>—<decision>is quoted ({:?}). - observe, only if non-empty (stdout):
observe: <observe>— quoted. - declared tags, each only when present (stdout):
authority: <value>, thenjurisdiction: <value>, thensource_ref: <value>, thensupersedes: <id>(the relation-overlay edge, when the decision is a correction). - per ground (stdout, one line each, indented two spaces):
- Test:
[<supports>] <claim> — test <reference> frozen@<sha8> now: <verdict>—<claim>and<reference>are quoted;<sha8>is the first 8 chars ofverified_at_sha;<verdict>is the live verdict label. - Person:
[<supports>] <claim> — person <reference>—<claim>and<reference>quoted. - unbound:
[<supports>] <claim>—<claim>quoted.
- Test:
- missing id (stderr):
error: no tick with id <id> - read error (stderr):
error: reading <id>: <io error>
Example:
ev reopen 638c47b0c9dd
# → decision 638c47b0c9dd: "restore-safety counter DB-backed; reject Redis"
# → observe: "multi-pod restore-safety counter — chat-room R2289→R2290"
# → [chosen] "Argus introduces no Redis; multi-pod coord via existing DB" — test "pytest tests/test_redis_absent.py" frozen@d308afac now: not-runSynopsis: the boot-read — print the live decisions whose declared authority is
user-ruled, and the roads each of them rejected. A near-zero-cost, 0-network read (the
store only — no git, no receipts) for a fresh agent to load the decisions it must respect and
the options it must not re-propose. Drawn only from human-ratified rulings — an
agent-proposed decision is excluded, so a proposal an agent recorded never governs a fresh
agent until a human vouches for it. Load-bearing rulings — user-ruled decisions that
closed a road via --reject — are pinned above the cap so recency never buries them; the
rest follow most-recent-first, capped, with an honest remainder footer so nothing is
silently hidden (and a hidden closed-road ruling is counted, never silent). --json emits the
same set as one machine-readable object an agent can parse.
ev brief [--limit N] [--json]
Flags:
| Flag | Value | Required | Meaning |
|---|---|---|---|
--limit |
non-negative integer | no | Cap the number of decisions shown. Overrides the config default brief_limit (which itself defaults to 10). --limit 0 shows all decisions (no cap, no footer). |
--json |
flag | no | Emit the frozen ev-brief JSON contract (one object) instead of the human text — for an agent to parse. Honors --limit; the elision counts make any capped-off ruling visible. |
What it does: reads every tick, collapses each corrective lineage to its current state
(an ev supersede child supersedes the stale tick it re-tags — so a ruling whose
authority was corrected to user-ruled surfaces, and one corrected away from it drops out),
keeps the live, authority == "user-ruled", non-agent-proposed ones (the provenance
exclusion is the ratification line: a provenance == "agent-proposed" record never reaches the
boot-read, in either output form, until a human re-authors it), then orders them so that
load-bearing rulings come first. A ruling is load-bearing iff
any of its grounds closes a road (its supports starts with rejected:) — those are the
decisions a fresh agent must not re-walk, so they sort ahead of every non-load-bearing ruling
regardless of recency and are pinned above the cap. Within each of those two groups the
order is most-recent-first (by held_since, tie-broken by id descending so output is
deterministic). It then caps to the effective limit. The effective limit is --limit N when
given, else the config brief_limit (default 10); a limit of 0 from either source means
"show all". For each shown decision it prints the decision marked [user-ruled], then one
indented line per road-not-taken (each ground whose supports is rejected:<option>). When
the cap drops decisions, a remainder footer is printed pointing at ev list (see below), and
when any of the hidden decisions are themselves load-bearing the footer counts them — so a
capped brief never hides a closed-road ruling without saying so. Person re-checks and chosen
grounds are not listed — brief is the what was ruled and what was rejected view, not the
full reopen. A store with no user-ruled decisions says so. It never touches the network.
Exit code: 0 when the store exists (including when there are no user-ruled decisions);
1 when there is no store.
Output (stdout / stderr):
- per user-ruled decision (stdout, load-bearing first then most-recent-first):
<decision> [user-ruled](two spaces before the tag), then one indented line per rejected road:rejected <option>: <claim>. - remainder footer (stdout, only when the cap drops decisions):
… <N> more user-ruled decision(s)<, M with rejected roads> — \ev list` for all— whereis the number of user-ruled decisions beyond the cap, and the conditional, with rejected roadsclause is appended **only when**M > 0of those hidden decisions are load-bearing (carry a rejected road); whenMis0the clause is omitted entirely. Not printed when nothing is dropped (including--limit 0`). - none (stdout):
no user-ruled decisions --json(stdout): one object, always valid JSON even when empty (never theno user-ruled decisionstext), on the frozenev-briefcontract:{"kind":"ev-brief", "decisions":[{"id", "decision", "load_bearing", "rejected_roads":[{"option", "claim"}], "source_ref"?}], "shown", "total", "elided", "elided_load_bearing"}. Each decision carries its citableid;source_refis present only when the producer supplied one; theelided/elided_load_bearingcounts make a capped-off ruling visible (re-pull with a higher--limitrather than act on a partial view).- no store (stderr):
error: no .evolving/ store here — run \ev init` first`
Example:
ev brief
# → restore-safety counter DB-backed; reject Redis [user-ruled]
# → rejected Redis: a new infra dependency
ev brief --limit 2
# → <a load-bearing ruling — pinned above the cap> [user-ruled]
# → rejected …
# → <next ruling, load-bearing first then most-recent-first> [user-ruled]
# → … 3 more user-ruled decision(s), 1 with rejected roads — `ev list` for all
ev brief --limit 0 # show every user-ruled decision, no cap, no footer
ev brief --json
# → {"kind":"ev-brief","decisions":[{"id":"…","decision":"restore-safety counter DB-backed; reject Redis","load_bearing":true,"rejected_roads":[{"option":"Redis","claim":"a new infra dependency"}],"source_ref":"R2289"}],"shown":1,"total":1,"elided":0,"elided_load_bearing":0}Synopsis: inventory the ledger — one line per recorded decision, sorted by id (deterministic).
ev list
Flags: none.
What it does: reads every tick in the store, sorts by id, and prints one row per
decision: its id, status, and decision text. A corrective lineage is collapsed to its
current state — an ev supersede child supersedes the stale tick it re-tags, so
only the latest tag of a decision is listed (the full lineage stays in ev log). A tick that
fails to parse still lists its id with ? for status and <unparseable> for the decision (so a
corrupt file is never silently dropped — ev verify owns the schema error). An empty ledger says
so.
Exit code: 0 when the store exists (including when it is empty); 1 when there is no
store.
Output (stdout / stderr):
- per tick (stdout, one row each, sorted by id):
<id>\t<status>\t<decision>—<decision>is quoted ({:?}); e.g.638c47b0c9dd\tlive\t"restore-safety counter DB-backed; reject Redis". When the tick carries a declared tag, the row gains a trailing field for each set, in order:\tauthority=<value>,\tjurisdiction=<value>,\tsource_ref=<value>. - empty ledger (stdout):
no decisions yet - no store (stderr):
error: no .evolving/ store here — run \ev init` first`
Example:
ev list
# → 638c47b0c9dd live "restore-safety counter DB-backed; reject Redis"
# → e2b337f53a1f live "freeze the retrieval schema for v2" authority=user-ruledSynopsis: walk the decision lineage from HEAD back to genesis, newest first.
ev log
Flags: none.
What it does: reads HEAD, then follows each tick's parent_id back to genesis,
printing the same row shape as ev list for each tick in the chain (newest first). It is the
lineage view — only the HEAD ancestry, not every tick in the store. A content-addressed chain
cannot cycle, but a cycle guard stops the walk if one ever appears, and a parent_id that
does not resolve emits a broken-lineage warning and stops. An empty ledger (no HEAD) says so.
Exit code: 0 when the store exists (including when it is empty); 1 when there is no
store, or when a tick in the lineage cannot be read.
Output (stdout / stderr):
- per tick (stdout, one row each, newest first):
<id>\t<status>\t<decision>— same shape asev list,<decision>quoted ({:?}). - empty ledger (stdout):
no decisions yet - broken lineage (stderr):
warning: <id> not found (broken lineage)(the walk stops) - read error (stderr):
error: reading <id>: <io error> - no store (stderr):
error: no .evolving/ store here — run \ev init` first`
Example:
ev log
# → 638c47b0c9dd live "restore-safety counter DB-backed; reject Redis"
# → e2b337f53a1f live "freeze the retrieval schema for v2"