Skip to content

A guard asks whether what a stamp attests is TRUE, not whether the stamp is well-formed - #211

Merged
TinDang97 merged 16 commits into
mainfrom
feat/verdict-truth
Sep 3, 2026
Merged

A guard asks whether what a stamp attests is TRUE, not whether the stamp is well-formed#211
TinDang97 merged 16 commits into
mainfrom
feat/verdict-truth

Conversation

@TinDang97

Copy link
Copy Markdown
Collaborator

The pattern

3.3.0's guards ask whether a stamp is well-formed. None of them asks whether the thing it attests is true.

Four independent critic passes over the merged 3.3.0 engine — engine truth, personas, cold start, promise-vs-artifact — converged on that one shape, a level below the one #210 closed. done counted that a gate existed and never read its verdict. The freeze seal covered the contract's text but not the identifiers the gate binds. deltas checked nothing at all. Three milestones close it.

What was measurably broken

Every finding below was reproduced on a fresh bundle through the CLI, with no flags and no hand-editing.

Tier 1 — the trust promise was falsifiable

$ add gate sec2 HARD-STOP --reason "SQL injection in login"
    HARD-STOP recorded; sec2 stays in `direction`
$ add done sec2
    /tasks/sec2.md is done            # and it left the board
  • done read act == "gate" and never outcome. HARD-STOP is the one verdict every integrity refusal deliberately lets through — so it was also the one that closed the node.
  • "Gaming a test to get green is structurally impossible" was false: the seal digests RULES · CHECKS · gives: — the names — never the test files. The README's own demo, run as written, gutted both frozen tests to pass and still gated PASS.
  • Deleting a frozen E1 and stripping · probe: from an A1 gated clean — two of three covers: referent classes sat outside the seal.
  • Slug uniqueness was per-directory while receipts are addressed by bare slug, so a red Task closed on a Milestone's green receipt.
  • add.quick() walked new → freeze(process) → run → gate PASS in one call, wired to no verb, kept green by four assertions.

Tier 2 — the engine steered users into the holes

  • The build hint guaranteed ids: unknown, and the gate then offered RISK-ACCEPTED as the only exit — a permanent false waiver on correct code.
  • No seeded persona had ever reached a brief. Deeper than reported: brief used the path grammar, so a bare lens slug normalised to /tasks/build-craftsman — not even a .md. It missed by either key, every time.
  • gate --authority was parsed and discarded; freeze --authority process downgraded a security freeze (the last item open from 3.3.0).
  • Three of four seeded personas oriented on add.py status --all, which exits 0 with no output — a planner lens read the empty string as "clean bundle".

Tier 3/4 — silent loss and false front-door claims

  • A typo'd delta vanished from the inventory with no warning, while deltas.md documented three reject codes that appeared zero times in the engine.
  • The trust disclosure described a network call that does not exist — no https, urllib, socket or fetch anywhere in the shipped surface, and nothing reads ADD_NO_UPDATE_CHECK. A reviewer setting it in CI believed they had closed an egress. The true statement — zero network — is strictly stronger.
  • The Install section's only flag example (--stage) hard-failed; add upgrade was documented nowhere and the 2.x refusal's next: corrupted the bundle; the plugin storefront sold retired 2.x phases.

Scaffold truth (this round)

  • --kind explore scaffolded the build-lane body, then freeze refused it for a budget: the body never offered — a lane whose scaffold produces a node that lane rejects.
  • The Task template's only scope: slot sat in ## PLAN; every reader looks in frontmatter. Fill the slot offered and the gate says "the node declares no scope:" — phantom_scope had never been able to fire.
  • assumption_sweep waived a dimension on the token alone while its own docstring promised "n/a and a reason" — six bare lines were a six-line off switch for the six-dimension matrix.
  • doctor reported "no findings" over a 100%-template bundle. placeholders_in detects exactly that and had one caller: the gate.
  • Five of eleven task kinds routed to no seeded lens — including security, where the gate refuses a PASS without one (R:NOCOVERAGE). A fresh install dead-ended on its first security task.
  • add new Task x --kind frontend was accepted, and doctor said nothing. add join /typo/.add printed "joined 0 stream(s)" and exited 0.

Where the human overruled the draft

Both security-floored tasks open an interview before the freeze. Four readings came back correct, and one could not be built as written: "a human-authority HARD-STOP closes the node" would have restored the exact walk the milestone existed to close, because a gate's authority is computed — on a security task every stop is already stamped human.

Resolved as a deliberate act rather than a floor: add done <slug> --override "<why>" refuses without a reason, writes its own stamp, and still refuses an unfrozen node. The ledger now distinguishes a person who chose to ship over a finding from a floor that quietly permitted it.

A3 narrowed A13, an assumption frozen in 3.3.0 whose premise both halves of this branch disproved. On checks_sync the instruction was to delete it together with checks_verify — but checks_verify is live (doctor calls it), so only the dead writer went.

Notable self-inflicted defects, caught and fixed

Recorded because they are the same class this PR is about.

  • The scope: slot was first seeded with a placeholder, following the gives: precedent — and turned 29 green tests red. gives: is descriptive; scope: is enforced, so a placeholder makes every fresh node declare a scope it cannot satisfy. A3 re-frozen against the measurement; the slot seeds empty.
  • The explore budget: slot satisfied the ^budget:\s*\S guard it exists to prompt for, so a fully-drafted explore froze with no budget — a well-formed value attesting nothing, inside the milestone about exactly that. My own check was vacuous: it asserted freeze refused without asserting which refusal, and the node was being refused for other placeholders. test_explore_gate caught what my check could not.
  • doctor's first wiring of placeholders_in could never fire: scan() nodes carry no body.
  • add.py's ABF_TYPES and scripts/validate_bundle.py's are two oracles by design; registering Interview in one made them disagree about the format — the one disagreement the parity test exists to forbid.

Verification

  • 1100 passed, 7 skipped, 0 failed across both test roots (tests/ and tooling/), which is what CI runs.
  • 16 new test files, ~100 new checks, every one demonstrated red before green. Where a guard went green on the first run because its artifact already existed, red was proven by withholding the artifact.
  • 3 milestones closed 16/16 exit criteria; 16 tasks all gated PASS on their own receipts, the security ones at authority human with a named lens.
  • The gate refused four first-PASSes across the branch — each for a Reject or referent no CHECKS line cited. Every one repaired by binding and re-freezing; none signed away.
  • Four engine twins synced, three skill trees, both MD5 pins re-aimed.

Still open (not in this PR)

add join on a path that exists but is not a bundle is now refused, but a partially-readable multi-stream join is still all-or-nothing by design. Two Milestone templates in this repo's own bundle (v3-final-collateral, affordance-truth) remain unfilled — both pre-existing, and both now correctly reported by the new unauthored_node finding.

…hether the stamp is well-formed

Four critic passes over the merged 3.3.0 state converged on one shape, a level below the
one PR #210 closed: 3.3.0's guards ask whether a stamp is well-FORMED and never whether
the thing it attests is TRUE. `done` checked that a gate existed, not its verdict. The
freeze seal covered the contract text, not two of the three id classes the gate binds.

Measured on a fresh bundle, CLI only, no flags and no hand-editing:

    add freeze sec2 --by tin --authority human   (sensitivity: security)
    add run sec2 -- false                        -> receipt, exit 1
    add gate sec2 HARD-STOP --reason "SQL injection in login"
        HARD-STOP recorded; sec2 stays in `direction`   <- the engine's own words
    add done sec2
        /tasks/sec2.md is done                          <- and it left the board

HARD-STOP was the one verdict every integrity refusal deliberately lets through — a
security finding must always be recordable — and also the one that closed the node.

Three fixes, each red-first:

* `done` reads each entitling gate's `outcome` against CLOSING_VERDICTS = (PASS,
  RISK-ACCEPTED). `gate` is untouched: all three verdicts stay recordable. A resolving
  PASS after a stop still closes, and a stop before a `reopen` does not block the
  reopened node.

* `binding_digest()` seals the referent SET — real `E<n>` ids and probed `A<n>` ids —
  as a second field beside `direction:`. `referents_of` binds RULES + EDGES + probed
  assumptions; `direction_digest` sealed RULES + CHECKS + `gives:`, so the cheapest way
  past "these rules have no reported passing check: A1, E1" was to DELETE the obligation.
  A second digest rather than a wider `direction:`: widening would re-digest every
  already-frozen node, and sealing only the referent set keeps prose refinement free.
  An absent `binding:` means "cannot verify", so no pre-seal node is stranded.

* `new` refuses a slug held by ANY type, iterating TYPE_DIR rather than a hand list.
  Receipts are addressed by bare slug under `tasks/`, so two nodes sharing a slug shared
  one receipt stream — a red Task closed on a Milestone's green run, defeating R:GREENLIE
  with evidence that was never its own.

Deleted: `add.quick()` — new -> freeze(process) -> run -> gate PASS in ONE call, wired to
no CLI verb, named in no shipped doc, kept green by four assertions. A new census guard
replaces the name check: no public engine function may call both `new` and `done`/`gate`,
so re-adding the lane under any name goes red.

That census also found `checks_sync` unreachable — a real CHECKS-from-suite compiler with
9 tests whose own refusal names `add checks <slug> --verify`, a verb that does not exist.
Recorded as a strict xfail against /tasks/wire-or-delete-checks-sync.md rather than
accommodated by weakening the guard; wiring a 25th verb is outside this cut.

Suite 1006 passed / 7 skipped / 1 xfailed across BOTH test roots. Four engine twins synced,
ENGINE_MD5 re-aimed. `test_stamp_field_integrity`'s freeze-stamp field census gained
`binding` — an enumeration updated for a genuinely new field, not a relaxed assertion.

Two tasks stay UNFROZEN by design: `done-reads-the-verdict` and
`delete-the-unwired-quick-lane` sit at the security floor, whose interview is a human
seam. The code and its checks are green; the attestation is the human's to make.

Refs: critic passes A (engine truth), B (personas), C (cold start), D (promise vs artifact)
author: Tin Dang
Records the ADD nodes for the verdict-truth milestone. Both architecture-floored tasks
are frozen at `human`, briefed, run against their own suites and gated PASS on fresh
receipts:

  seal-covers-what-binds        PASS · receipt runs/2.md · freshness fresh
  slug-is-unique-across-types   PASS · receipt runs/2.md · freshness fresh

Both needed one re-cross: the gate refused the first PASS because each node's Reject
(R:SHEDBIND, R:CROSSRECEIPT) was declared but cited by no CHECKS line. The repair was to
bind the Reject to the check that actually proves it and refreeze — never to sign the gap
away with RISK-ACCEPTED. That is the fifth and sixth time this project's own gate has
refused a PASS over an unbound referent, and each time the refusal was right.

Two tasks stay UNFROZEN, deliberately. `done-reads-the-verdict` and
`delete-the-unwired-quick-lane` are security-floored, so `freeze` opens an interview —
seven readings a human must be shown before the ONE approval. Their code and checks are
green and committed; the attestation is not an agent's to make. Answering that interview
as the human, in the milestone whose entire subject is stamps that attest what never
happened, is the defect wearing a different hat.

`wire-or-delete-checks-sync` is scaffolded, not authored: the reachability census found
`checks_sync` unwired, and whether to wire a 25th verb or delete a working feature is a
direction call, not a build one.

Suite 1013 passed / 7 skipped / 1 xfailed across both test roots.

author: Tin Dang
…ag it accepts

Tier 2 of the four-critic revalidation: the engine was steering users into the holes
Tier 1 had just closed. Every fix here is red-first.

THE NEXT: LINES

`BEAT_NEXT` is the one table `status`, `todo`, `brief`, `freeze` and every CARD line read
from, so a wrong idiom there is wrong in fourteen places.

* build carried `add run {slug} -- <cmd>` — the idiom GETTING-STARTED warns produces
  `ids: unknown`. A builder who obeyed it on genuinely green work then met "no reported
  passing check" with RISK-ACCEPTED named as the only exit: a permanent false waiver on
  correct work. Now the canonical double-`--junitxml` form (braces doubled — both
  consumers pass it through `.format(slug=…)`).
* verify carried `add gate {slug}`, an argparse error. A crash, not a refusal, with no
  `next:` to recover from. Now `add gate {slug} PASS --by "<name>"`.
* The non-git freshness refusal diagnosed git correctly and then said `add run` — which
  provably cannot fix it, so obeying the line looped forever. It now names `git init`.
* `add learn testing` said `next: add status`. The vocabulary is closed and five words
  long; the refusal now names it and shows the form.
* `add check <typo> --all` raised FileNotFoundError at the operator — the only traceback
  across ~35 malformed inputs. `cli.py` read the file before reaching `add.check`'s guard.

ACCEPTED-AND-DROPPED

* `run` was the only verb whose lookup was `scan(root).get(cid) or {}`. A typo'd slug got
  `receipt 1 recorded (exit 0)`, a green line and a `next:` pointing at nothing, while the
  real task still had no receipt — and `doctor` then reported the orphan the engine had
  just manufactured. It refuses like every other verb, keeping `run`'s dict return shape.

* `gate --authority` was declared, passed in, and overwritten before any read. Frozen M3
  (test_gate_verb.py:224) says a gate's authority is COMPUTED, never the caller's claim —
  and that rule is right, since a claim at the gate is the agent approving its own work.
  So the engine is unchanged and the CLI stops accepting an argument it discards.

* `freeze` had the opposite bug: it honoured ANY value, so `--authority process` silently
  downgraded a security freeze. This was the last item still open from 3.3.0. One new
  reader, `claimed_authority()`, serves both: a claim may rise above the computed floor,
  never sink below it -> R:FLOORDIVE.

THE LENS THAT NEVER ARRIVED

`brief` resolved a persona through the PATH grammar, but a lens is written as a bare slug
(`advise` validates exactly that against the Persona nodes). From `/tasks/x.md` the slug
`build-craftsman` normalised to `/tasks/build-craftsman` — neither a persona nor even a
`.md`. It missed every time: no seeded lens has ever reached a brief, by either key. The
brief now resolves the roster the way `advise` and `wave` do, and reads `advised_by:` as
well as `persona:` — the two the gate's R:NOCOVERAGE already treats as equals.

Three of four seeded personas opened with an ORIENT command that was silent or an error:
`add.py status --all` (a LIBRARY — exit 0, no output, which a planner reads as "clean
bundle"), `status --brief`, `graph`, `milestone-confirm`. Repointed at cli.py and real
verbs, under a census over every shipped template so a lens added later cannot skip it.

Suite 1027 passed / 7 skipped / 1 xfailed across both roots. Four engine twins and the
templates tree synced; ENGINE_MD5 and ENGINE_PKG_MD5 both re-aimed.

author: Tin Dang
…t, or it is not made

Tier 4 of the four-critic revalidation, plus the one Tier 3 item that was losing data.
Each false claim is now bound by a guard that reads the ENGINE and fails if the prose
drifts back — the guard-class lesson from 3.2, applied to the front door.

THE TRUST CLAIM WAS THE BIGGEST ONE

Both READMEs promised that "gaming a test to get green is structurally impossible" /
"is treated as tampering". It is not. The freeze seal digests RULES · CHECKS · `gives:`
— the NAMES — and never the test FILES those names point at. Running the README's own
"See it yourself" demo (reopen --to build, gut both frozen tests to bare `pass`, re-run,
gate) records `gate PASS ... freshness: fresh` and closes the node. SKILL.md:123 and
GETTING-STARTED:374 both state this limit plainly; only the front door contradicted them.
The claim now says what the seal actually guarantees — the contract cannot move under a
build without the change appearing in the record — and a counter-guard pins the honest
limit so the claim is never "fixed" by deleting the disclosure.

BOUNDARIES A SECURITY REVIEWER READS

"Network: one optional advisory update check — a single HTTPS GET to the npm registry ...
disable with ADD_NO_UPDATE_CHECK=1". There is no network code in the shipped surface at
all — no https/urllib/socket/fetch in bin/cli.js, _installer.py, _cli.py, add.py or
cli.py — and nothing reads that variable. A reviewer setting it in CI believed they had
closed an egress. The true statement (zero network) is strictly stronger than the false
one. A guard now checks both directions, and a second refuses any documented ADD_* var
the code does not read.

THE REST

* `--stage mvp`, the Install section's only flag example, which the installer explicitly
  rejects — retired in 3.0, and there is no stage concept in 3.x.
* `cli.py deltas` sold as "the per-lane scoreboard: what got gated, passed, healed (GEPA)".
  It prints `[LENS] spec: text`. GEPA, per-lane, scoreboard and "fast lane" name nothing
  in the engine.
* `add upgrade` — a real verb that archives a 2.x bundle byte-for-byte, writes
  MIGRATION.md and inits a fresh 3.0 bundle — was named in no README, and the engine's own
  2.x refusal said `next: add init`, which writes nine files INTO the live 2.x directory
  and leaves a permanent doctor error. Both now name `upgrade`.
* The marketplace entry — the only text a user sees before installing — sold the retired
  2.x seven-phase vocabulary and claimed the book materializes into the project, which
  both READMEs and SKILL.md deny.
* `SOUL.md`, a 2.x bundle file 3.x never creates, was still the routing target for tone
  rules in persona-author.
* GETTING-STARTED said the scaffold lands at beat `direction`; the engine says `scaffold`,
  and the walkthrough test hardcoded the doc's version so it could not catch the drift.
  The guard now measures the beat off the engine.
* The README leads with "Most changes never create a node" and linked a walkthrough with
  ZERO occurrences of ladder, direct lane or explore. §2 now routes by the ladder.
* `--nested` (the only way past R:RIVALBUNDLE, while both READMEs promise monorepo support)
  and `--no-skill` (without it a plugin bootstrap installs a duplicate skill that shadows
  the plugin's) were documented nowhere. Both are now, at the point of need.
* SKILL.md's interview line omitted `correct` — the verdict for "the AI got this wrong".

DELTAS WERE LOSING LINES

A delta whose status was typo'd matched nothing in DELTA_LINE's status group and vanished
from `add deltas` — no warning, no doctor finding — while deltas.md documented three
reject codes for exactly this (`unknown_competency`, `no_evidence`, `unknown_status`),
none of which appeared anywhere in the engine. `deltas` now reports every line it cannot
place, under those three names, and refuses an unknown `--status` instead of reading it as
an empty result. Verified against this repo's own 40 deltas: zero false positives.

Suite 1046 passed / 7 skipped / 1 xfailed across both roots. Engine twins, all three skill
trees and both MD5 pins re-synced.

author: Tin Dang
Records the ADD nodes for the Tier 2 milestone. All five frozen at `human`, briefed, run
against their own suites and gated PASS on fresh receipts — first try each, because the
`covers:` bindings were written at direction rather than discovered at the gate. That is
the fix for the two re-crosses Tier 1 cost.

  run-refuses-a-phantom-node             PASS
  gate-honours-or-refuses-authority      PASS
  next-lines-are-runnable                PASS
  the-persona-reaches-the-worker         PASS
  seeded-personas-orient-on-real-verbs   PASS

author: Tin Dang
Four readings marked `correct` by the human, three deferred. The node stays UNFROZEN:
the engine's own note is that a `correct` is cleared by editing the item, and two of the
four corrections cannot be written yet because they conflict.

A1 (a human-authority HARD-STOP should close the node) collides with the fact that a gate's
authority is COMPUTED from the floor — so on a security node every stop is stamped `human`,
and taking A1 literally restores the exact measured walk this milestone closed. A1 also
pulls against A2, which says a stop blocks permanently.

Raised with the human; unanswered so far. No engine change made on a guess.

author: Tin Dang
… an act, not a floor

Both security-floored tasks are now interviewed, frozen at `human`, and gated PASS on fresh
receipts. Four readings came back `correct`; one of them could not be built as written, and
the human resolved it.

A1 — A HUMAN MAY SHIP OVER A FINDING, BUT MUST SAY SO

The literal correction ("a human-authority HARD-STOP closes the node") would have restored the
exact measured walk this milestone closed: a gate's authority is COMPUTED from the node's floor,
so on a security task EVERY stop is stamped `human`. Raised, and resolved by the human: the
force-close is a DELIBERATE ACT. `add done <slug> --override "<why>"` closes a stopped node,
refuses without a reason, writes its own `act: done, override:` stamp, and never buys the ONE
approval — an unfrozen node is still refused. The ledger now distinguishes a person who chose
to ship over a finding from a floor that quietly permitted it.

A3 — A STOP IS A RECORD, AND A RECORD NEEDS A SEAL

`gate` now refuses a HARD-STOP on a node that was never frozen (R:UNSEALED) and refuses nothing
else, so writing a finding never gets harder. This NARROWS A13, an assumption frozen in 3.3.0
whose premise ("a HARD-STOP never closes a task") both halves of this branch disproved. Its test
is rewritten to the narrowed rule and a new counter-guard enumerates both refusal tiers to prove
every other refusal still lets a stop through.

A4 — AN UNREADABLE VERDICT FAILS OPEN

A gate stamp with no `outcome` closes, rather than stranding nodes an older engine wrote. The one
correction that loosens; recorded as such on the node.

A2, A5, A6, R:STOPSHIPS — confirmed as drafted.

TWO THINGS THE INTERVIEW ITSELF FOUND

* `interview` wrote its sidecar as bare markdown, so `doctor` filed `error missing_frontmatter`
  against a file the engine had just created — the `orphan_receipt` shape one verb over. It now
  carries frontmatter, and `Interview` joins ABF_TYPES. That list lives in TWO places by design
  (add.py compiles the graph, scripts/validate_bundle.py is the independent M0 oracle), and
  updating one made the two disagree about the FORMAT — the one disagreement the parity test
  exists to forbid. Both updated, and a new guard pins them to each other.
* `gate` refused the first security PASS with R:NOCOVERAGE: no seeded persona is a security
  lens (critic B3's gap, hit live). Added `personas/gate-security-reviewer.md`, distilled from
  the teacher corpus — the authorization lens for ADD's own gate. It reaches the brief, which
  is the B1 fix proven end to end on a real node.

CHECKS_SYNC DELETED

The human chose delete. One correction to what was asked: `checks_verify` is LIVE — `doctor`
calls it for `checks_citation` — so deleting both would have removed a shipped check. Only the
dead writer `checks_sync` is gone, with the nine tests that drove it; `checks_verify`,
`checks_of` and their coverage stay. The census now passes outright and the strict xfail is
removed, so `/tasks/wire-or-delete-checks-sync.md` is answered and its scaffold deleted.

Also fixed: `test_checks_of_keys_by_file_too` hardcoded `test_sync_is_idempotent` as a known
name collision and went red when one of the two was deleted for an unrelated reason — reporting
a keying defect that did not exist. It now DISCOVERS a collision instead of naming one.

Suite 1058 passed / 7 skipped across both roots. Twins and both pins re-synced.

author: Tin Dang
Both milestones carry their goal, scope, ground, risks and a per-task evidence row.
Nine tasks, every one gated PASS on a fresh receipt; the two security-floored ones at
authority `human` with a named lens.

`v3-final-collateral` stays open — a pre-existing template this branch did not touch.

author: Tin Dang
…e on the board

Registering `Interview` in ABF_TYPES stopped `doctor` filing a finding against a file the
engine had just written — and put the sidecars in `status` beside real tasks. The two rules
are independent: a type the graph knows, and a type the roster shows. Sidecars now sit with
`Run`, out of the roster.

author: Tin Dang
…g on

Three readers each answered "fine" over input they never examined, and the
interview routed all three to a refusal at the point of entry rather than a
finding somewhere downstream.

`new` judged exactly one slot — `sensitivity:`, because it computes the
authority floor — and recorded every other field verbatim. `kind:` looked
like a prose slot and is not: it is the Task side of a routing predicate
whose Persona side (`task-kinds:`) has been validated since 3.3.0. Two sides
of one match, held to different standards, so `--kind frontend` was accepted
and the task silently lost its lens for life. It now refuses with the closed
taxonomy enumerated, the same shape as the `--profile` and `sensitivity`
refusals already shipped.

`join` iterated `(d / "tasks").glob("*.md")` for each stream path, and glob
on a directory that does not exist yields nothing quietly — so `add join
/typo/.add` printed "joined 0 stream(s)" and exited 0. The first cut keyed
the refusal on the presence of `tasks/`, which was wrong: `init` never
writes that directory, `new` creates it lazily, so a real stream bundle that
has not authored a node yet has none. Corrected at the contract (A2
re-frozen) to `_is_bundle_index` — the `abf_version:` marker `init` always
writes — and the CLI now propagates the refusal to a non-zero exit.

`doctor` reported "no findings" over a bundle whose only task was 100%
scaffold. `placeholders_in` already detects exactly that, and had exactly
one caller: the gate. So the failure only ever surfaced at the END of the
loop, to someone who had already done the work, never to the newcomer
running `doctor` to ask whether their bundle was in good shape. The first
wiring could not fire at all — `scan()` nodes carry no body and
`placeholders_in` reads the body — which is the same asymmetry that made
`_open_decisions` return [] last session.

Three tasks, all gated PASS on their own receipts, 15 new checks, every one
red first. Both pins re-aimed; four engine twins synced.

Refs: milestone scaffold-truth (3 of 7)
author: Tin Dang
Two guards that could not fire, for opposite reasons — one because the
value it judges was written where nothing looks, one because the text it
requires was never required.

`scope:` had exactly one slot in the Task scaffold, in `## PLAN`, and its
only reader is `fm.get("scope")`. Fill the slot the template offers and the
gate answers "the node declares no `scope:`" — so `phantom_scope`, the
refusal for a scope naming paths that do not exist, has never once been able
to fire on a scaffolded node. The slot consumed the author's attention and
returned nothing.

Moving it to frontmatter was the easy half. The first cut seeded a
placeholder, following the `gives:` precedent, and turned 29 green tests red:
`gives:` is DESCRIPTIVE, so an unfilled placeholder is merely unhelpful,
while `scope:` is ENFORCED — a placeholder makes every fresh node declare a
scope it cannot satisfy, degrading freshness and making any edit outside it
a violation. A3 was re-frozen against the measurement and the slot now seeds
EMPTY: the key's presence in frontmatter is the prompt, and its emptiness is
what every reader already means by "none declared".

`assumption_sweep` waived a dimension on the token alone, while the docstring
one line above promised "retired with `n/a` and a reason". Six bare `n/a`
lines were therefore a six-line off switch for the whole six-dimension
matrix, each cheaper to type than one honest assumption. Measured before
changing anything: 584 assumption lines across both bundles, 4 of which
actually waive, and all 4 already state a reason — so making the code mean
what its docstring says refuses nothing already written. The bar is any
non-empty reason, never a quality bar: a notary cannot judge whether a
reason is good, and a bar it cannot judge only teaches padding.

The sweep then caught this branch's own scope task with two unswept
(dimension, surface) pairs at freeze. Fixed by writing the two readings, not
by waiving them.

Two tasks, both gated PASS on their own receipts, 12 new checks, every one
red first. tests/ root: 1077 passed, 7 skipped. Pin re-aimed, four twins
synced.

Refs: milestone scaffold-truth (5 of 7)
author: Tin Dang
`--kind explore` is a whole shipped lane — a guide, a freeze refusal, a gate
path that reads `## FINDINGS` instead of a receipt, three refusal codes of
its own — and it had no front door. It emitted the identical build-lane
body, and `freeze` then refused it for a `budget:` line the body never
offered. The author was handed the wrong lane's node and then blamed for it.

The scaffold now branches: RULES ask for questions, `## PLAN` carries the
budget slot, `## FINDINGS` is present with the exacting
`F<n> (answers M<n>) · … · (evidence: <ref>)` shape and no finding in it.
`## CHECKS` STAYS — the first reading of this task had it dropped, and
explore.md contradicts that in as many words.

Shipping the budget slot then reproduced this milestone's own defect class
inside it: `^budget:\s*\S` matched `budget: <one hard number …>`, so a slot
satisfied the requirement it exists to prompt for, and a fully-drafted
explore froze with no budget. My own check for it was vacuous — it asserted
`freeze` refused without asserting WHICH refusal, and the node was being
refused for its other placeholders. `test_explore_gate` caught what my check
could not. Both fixed: the guard rejects a `<…>` value, and the check names
its refusal.

Five persona lenses seeded — docs-writer, interface-designer, data-steward,
security-reviewer, explore-investigator — each distilled from a named
teacher file, closing 5 of 11 task kinds that routed to nothing. `security`
mattered most: the gate REFUSES a security PASS without a named lens
(R:NOCOVERAGE), and this branch hit that refusal live, authored a lens into
THIS repo's bundle to clear it, and never seeded it. The fix did not ship.
The seeded template is the artifact.

The coverage guard enumerates PERSONA_TASK_KINDS from the constant, so the
twelfth kind fails it, and it names the unclaimed kinds rather than sending
a maintainer through nine files. Proven red by withholding the five
templates: "5 task kind(s) route to no seeded lens: data, docs, explore,
security, ui".

Milestone scaffold-truth CLOSED, 7/7 exit criteria, 7 tasks all gated PASS
on their own receipts. Suite 1060 -> 1099 passing, 7 skipped, both roots.
Four engine twins synced, both pins re-aimed.

Refs: milestone scaffold-truth (7 of 7)
author: Tin Dang
"exactly the decision that has to be explained")
fix = f'add done {slug} --override "<why this is acceptable>"'
else:
gates = [(i, s) for i, s in enumerate(stamps)
Comment thread add-method/tooling/add.py
"exactly the decision that has to be explained")
fix = f'add done {slug} --override "<why this is acceptable>"'
else:
gates = [(i, s) for i, s in enumerate(stamps)
is the `orphan_receipt` shape one verb over.
"""
root = _bundle(tmp_path)
cid = _sealed(root, "sidecar", sensitivity="security")
"""
import json
import re
import subprocess
REPO = Path(__file__).resolve().parents[2]
ROOT = REPO.parent
sys.path.insert(0, str(REPO / "tooling"))
import add # noqa: E402
…t something

CI failed on py3.10 and py3.12 while the tree was green on 3.14. The cause
was mine and it was worse than a crash.

`test_every_orient_command_runs_clean` bound its root with
`add.init(tmp_path, "code", "T") or tmp_path`. `add.init` returns a TUPLE,
which is truthy, so `--root` received the repr of the entire graph dict.
Every ORIENT command then ran against a bundle that does not exist: `status`
printed nothing and exited 0, and the check passed VACUOUSLY on every
command it was ever given.

It surfaced only on CI because `Path.exists()` on a name that long raises
ENAMETOOLONG on 3.10/3.12 and returns False on 3.14 — a version-dependent
crash standing in for a check that was never running. The green was the bug;
the red was the honest signal.

Fixed by binding the root, and guarded against recurrence: the check now
counts the commands it executed and fails if fewer than three ran. Verified
it bites by corrupting a real ORIENT line — `--nosuchflag` is now reported
with argparse's own error, where before it was silently accepted.

This is the milestone's own defect class, in my test rather than the engine:
a check that is perfectly well-formed and attests nothing.

Refs: PR #211
author: Tin Dang
…e guide stops teaching a dead slot

Three of nine seeded personas carried an `ORIENT on load` line, so the guard
that executes those commands only ever inspected a third of the roster —
and its non-vacuity floor was a magic `>= 3` that happened to match. All
nine now carry one, and the floor is the roster itself: a new template with
no ORIENT line, or one whose command the check cannot execute, fails here
rather than quietly shrinking what the guard inspects. Verified by removing
one lens's line (red) and restoring it (green).

Each command is argument-free and chosen for the lens, not filled in:
security-reviewer takes `status --all` because a control is only as good as
the state it reads and the ledger IS that state; explore-investigator adds
`deltas` because the cheapest finding is one somebody already wrote down.
`check` was rejected as a candidate — it requires a ref, so it could never
run clean in this guard.

Docs, same branch, same thesis. GETTING-STARTED's worked `## PLAN` block
still carried `scope: src/, tests/` — the slot this branch deleted, and a
duplicate besides, since the walkthrough's own `add new --scope "src/,tests/"`
had already put it in frontmatter where readers look. A reader following the
guide would have hit "the node declares no `scope:`" at the gate. That is
precisely the false-front-door class the Tier 4 work closed, reopened by my
own engine change, and the walkthrough test executes these blocks so it
would have taught it faithfully.

SKILL.md said `## PLAN` holds "`scope:` tokens". Corrected, and the explore
lane's required `budget:` named there for the first time. The correction
initially pushed SKILL.md to 177 lines against a 176 budget; absorbed by
compressing the same bullet rather than re-pinning the budget. Back to
exactly 176/176, mirrored across all three skill trees.

Suite 1100 passed, 7 skipped, both roots.

Refs: PR #211
author: Tin Dang
A sweep of this branch's new checks for the shape that produced the CI
failure: a check that passes without exercising its subject. Ten confirmed,
seven more hardened. Two of the ten found real defects that had been sitting
behind dead branches.

REOPENED AND RE-GATED: scope-is-where-its-readers-look

Its M4 and S2 named a `phantom_scope` that does not exist. The engine keys
that refusal on the CARD claiming a scope the frontmatter lacks — not on
path existence, and not from `doctor`, which emits sixteen codes and not
that one. Both binding checks were vacuous: one filtered `doctor()` for a
code `doctor()` cannot emit, so the list was empty for every input; the
other hand-rolled a path-existence loop over a value the test itself had
just written, a tautology naming a predicate the engine does not have.

The finding the task was built on is still true and still fixed — the slot
moved to frontmatter where readers look. Only the second claim I attached to
it was false. Reopened to direction, M4 retired in place with a check that
PROVES the retirement by pinning the real predicate from source, S2 dropped,
A4/A6 and the contract line corrected, re-frozen at `human`, re-gated PASS.
The gate refused the first re-PASS for the retired M4 and for an unbound E3;
both were bound rather than waived.

TWO REAL DEFECTS BEHIND DEAD BRANCHES

`brief` said nothing when no lens resolved. Its check passed only because
the fixture's slug was the literal word `unlensed`, which the brief echoed
in `<task id=...>`; re-run with `zzz` it matched nothing at all. The Must
was good and unimplemented, so the engine now says the omission out loud —
a worker could not previously tell "no expert was loaded" from "the expert
had no note", and the receipt recorded neither.

The unbound-covers refusal offered ONLY `RISK-ACCEPTED` even when the
receipt carried no check ids at all. That is R:FALSEWAIVER exactly: a signed
waiver as the sole exit from possibly-correct work whose runner simply
emitted no JUnit report. The check meant to catch it sat under
`if "no reported passing check" in note:` and the fixture, being unauthored,
was refused for placeholders instead — the branch was never taken. Authored
the fixture, made the assertion unconditional, and split the refusal so an
unbound receipt names the re-run FIRST and the waiver second.

THE OTHER EIGHT

- A README flag check whose two filters were mutually exclusive, so its only
  assertion was `[]` for every possible input while the real finding list was
  computed and discarded.
- Three live-bundle reporters reduced to `assert isinstance(...)`. They scan
  `add-method/.add/`, which is GITIGNORED with zero tracked files — so on CI
  they measured a bundle that does not exist, and `isinstance` was the only
  thing left that could pass. Repointed at the root bundle (113 tracked
  files), with an explicit skip when neither is present.
- An orphan-count guard comparing `0 == 0`. Its docstring said the number
  "is expected to stay at eight" and that the test "exists to notice if it
  silently drops". It dropped to zero and stayed green, because both sides
  degrade together. It now injects an orphan, so it has a subject on any
  bundle in any state.
- A `git diff` over a gitignored path, guarding nine nodes that no longer
  exist in this repo at all. It now asserts its own premise, so restoring one
  goes red instead of inheriting a guard that was never running.
- A parity check satisfied by any shared unrelated code; it now requires the
  injected `edge_unresolved` on both sides.
- Seven more given non-vacuity floors tied to a real source rather than a
  magic number — the roster's own length, the stamp-writer census population,
  the reachability candidate pool.

Every fix demonstrated: each new or hardened check was shown red by
withholding its subject, then green when restored.

Suite 1100 passed, 7 skipped. Pin re-aimed, four twins synced.

Refs: PR #211
author: Tin Dang
@TinDang97

Copy link
Copy Markdown
Collaborator Author

Post-review sweep: ten vacuous checks, two real defects behind them

A sweep of this branch's new checks for the shape that produced the earlier CI failure — a check that passes without exercising its subject. Ten confirmed, seven more hardened. Two of the ten were hiding real engine defects.

One PR claim was wrong, and is now corrected

The description above said this branch "revives phantom_scope, which has never once been able to fire." That is false. phantom_scope keys on the CARD claiming a scope the frontmatter lacks — not on path existence, and it is a gate refusal, not one of doctor's sixteen codes. It could always fire, and this branch did not change it.

The finding the task was built on is still true and still fixed: the scope: slot lived in ## PLAN where no reader looks, and now lives in frontmatter. Only the second claim I attached to it was wrong, along with both checks written for it — one filtered doctor() for a code it cannot emit, the other was a tautology over a value the test had just written.

scope-is-where-its-readers-look was reopened to direction, corrected, re-frozen and re-gated. M4 is retired in place with a check that proves the retirement by pinning the real predicate from source. The gate refused the first re-PASS for the retired M4 and an unbound E3 — both bound, neither waived.

Two real defects were sitting behind dead branches

brief said nothing when no lens resolved. Its check passed only because the fixture's slug was the literal word unlensed, which the brief echoes in <task id=...>. With a neutral slug it matched nothing. A worker could not tell "no expert was loaded" from "the expert had no note", and the receipt recorded neither. The engine now says the omission out loud.

The unbound-covers refusal offered only RISK-ACCEPTED even when the receipt carried no check ids at all — a signed waiver as the sole exit from possibly-correct work whose runner emitted no JUnit report. That is R:FALSEWAIVER exactly. The check meant to catch it sat under if "no reported passing check" in note:, and the unauthored fixture was refused for placeholders instead, so the branch was never taken. The refusal now names the re-run first and the waiver second.

The other eight

what it looked like why it could not fail
README flag guard its two filters were mutually exclusive — [] for every possible input, while the real finding list was computed and discarded
three assert isinstance(...) reporters they scan add-method/.add/, gitignored with zero tracked files, so on CI they measured a bundle that does not exist
orphan-count parity 0 == 0; the docstring said the count "is expected to stay at eight" and "exists to notice if it silently drops" — it dropped, and both sides degrade together
git diff over gitignored path guarding nine nodes that no longer exist in this repo at all
doctor/validator parity asserted only that the two code sets intersect, which any shared unrelated code satisfies

Fixed by giving each a subject it can rely on: the reporters repoint at the root bundle (113 tracked files) with an explicit skip; the orphan guard injects an orphan; the parity check requires the injected edge_unresolved on both sides.

Seven further checks got non-vacuity floors tied to a real source rather than a magic number — the roster's own length, the stamp-writer census population, the reachability candidate pool.

Every fix was demonstrated red by withholding its subject, then green when restored.

Suite: 1100 passed, 7 skipped, both roots.

Known open

Five of the ten predate this branch (the 3.0 graft, 099a225a); they are fixed here rather than filed, at the reviewer's discretion to split. And add run --junitxml still has to be named twice — once for the engine to read, once for the runner to write. That is queued as its own task: sniff the path from the command by default, keep the flag as an override.

"marketplace.json is missing or unparseable — the storefront copy is unchecked, "
"which is how it sold retired 2.x phases for two minor versions")
if not blob:
return
`--junitxml` on `add run` told the engine where to READ a JUnit report; the
command itself wrote it. So the documented build line named one path twice:

    add run x --junitxml "$X" -- pytest ... --junitxml="$X"

The engine already held the command as a list, so the second mention carried no
information it did not have. It was a restatement the caller could get wrong,
and getting it wrong cost a receipt with `ids: unknown` — which the gate reads
as every rule unbound, and which (until this branch) offered a signed waiver as
the only way forward. A typo in a path was punished as a coverage gap.

`run` now sniffs the path out of the command when no `junit=` is given.
`_sniff_report` is deliberately narrow: two known flag spellings, `=path` and
two-token form, last occurrence wins — the same precedence the runners
themselves honour. Anything else returns None rather than guessing, because a
receipt bound to a file the command never wrote is worse than an unbound one:
the unbound receipt is visibly unbound, the wrongly-bound one is not.

The flag stays, as an override. A runner may write its report to a path the
command line never names — a config file, a fixed CI location — and it is
consulted first so a sniffed value can never beat a stated one. Everything
downstream is unchanged: staleness, extraction and `ids` see the same value
whichever route it arrived by. `BEAT_NEXT`'s build hint now names the path once.

Dogfooded: this task's own receipt was produced by a command carrying no
engine-side flag, and bound all 7 checks at `kind: test-ids`, `freshness:
content`.

Task: run-reads-the-path-it-was-handed (milestone scaffold-truth)
ENGINE_MD5 re-aimed 72b0bc5c -> 9a800083

author: Tin Dang

REPO = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO / "tooling"))
import add # noqa: E402
@TinDang97
TinDang97 merged commit 040b414 into main Sep 3, 2026
8 checks passed
@TinDang97
TinDang97 deleted the feat/verdict-truth branch September 3, 2026 06:20
TinDang97 added a commit that referenced this pull request Sep 3, 2026
…true (#212)

Two merged-but-unreleased PRs ship here. 3.3.0 was tagged before #210 landed,
so this release carries both it and #211.

#210 — the documentation described a method the engine did not implement, and
nothing could see the gap: every guard checked nouns the engine EXPOSES rather
than capabilities the prose PROMISES. GETTING-STARTED.md is now executed by the
suite, the roster names agents that exist, and `init` seeds selectable personas.

#211 — every integrity guard asked whether a record was SHAPED right; none asked
whether the thing it recorded had happened. A `gate HARD-STOP` walked to `done`.
A red Task closed on another node's receipt through a slug collision. `brief`
resolved no lens at all and said nothing about it. And a scaffold `new` wrote
could not survive the guards `new` recommended next.

Version bumped across all eight declarations — five manifests, the ENGINE stamp,
and the three shipped skill trees — plus the dogfood bundle's own engine stamp.
ENGINE_MD5 re-aimed 9a800083 -> 7932ec06; cli.py untouched, so ENGINE_PKG_MD5
stands.

Also fixed here, self-inflicted last turn: `run` learned to sniff the report
path, which left the shipped skill teaching the doubled `--junitxml` in four
places, one with a sentence explaining that the repetition was deliberate.
`test_receipt_idiom_truth` could not catch it — it decides which examples to
check by whether the engine-side flag is present, so removing that flag makes an
example invisible rather than wrong. The new guard binds the taught line to
`BEAT_NEXT["build"]`, the string a user is actually shown, and was mutation
-tested: reintroducing the doubling turns it red.

Suite: 1111 passed, 7 skipped across both test roots.

author: Tin Dang
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant