A guard asks whether what a stamp attests is TRUE, not whether the stamp is well-formed - #211
Conversation
…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) |
| "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
Post-review sweep: ten vacuous checks, two real defects behind themA 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 correctedThe description above said this branch "revives The finding the task was built on is still true and still fixed: the
Two real defects were sitting behind dead branches
The unbound-covers refusal offered only The other eight
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 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 openFive of the ten predate this branch (the 3.0 graft, |
| "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
…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
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.
donecounted that a gate existed and never read its verdict. The freeze seal covered the contract's text but not the identifiers the gate binds.deltaschecked 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
donereadact == "gate"and neveroutcome. HARD-STOP is the one verdict every integrity refusal deliberately lets through — so it was also the one that closed the node.gives:— the names — never the test files. The README's own demo, run as written, gutted both frozen tests topassand still gated PASS.E1and stripping· probe:from anA1gated clean — two of threecovers:referent classes sat outside the seal.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
ids: unknown, and the gate then offeredRISK-ACCEPTEDas the only exit — a permanent false waiver on correct code.briefused 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 --authoritywas parsed and discarded;freeze --authority processdowngraded a security freeze (the last item open from 3.3.0).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
deltas.mddocumented three reject codes that appeared zero times in the engine.https,urllib,socketorfetchanywhere in the shipped surface, and nothing readsADD_NO_UPDATE_CHECK. A reviewer setting it in CI believed they had closed an egress. The true statement — zero network — is strictly stronger.--stage) hard-failed;add upgradewas documented nowhere and the 2.x refusal'snext:corrupted the bundle; the plugin storefront sold retired 2.x phases.Scaffold truth (this round)
--kind explorescaffolded the build-lane body, thenfreezerefused it for abudget:the body never offered — a lane whose scaffold produces a node that lane rejects.scope:slot sat in## PLAN; every reader looks in frontmatter. Fill the slot offered and the gate says "the node declares noscope:" —phantom_scopehad never been able to fire.assumption_sweepwaived a dimension on the token alone while its own docstring promised "n/aand a reason" — six bare lines were a six-line off switch for the six-dimension matrix.doctorreported "no findings" over a 100%-template bundle.placeholders_indetects exactly that and had one caller: the gate.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 frontendwas accepted, anddoctorsaid nothing.add join /typo/.addprinted "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 stampedhuman.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.A3narrowedA13, an assumption frozen in 3.3.0 whose premise both halves of this branch disproved. Onchecks_syncthe instruction was to delete it together withchecks_verify— butchecks_verifyis live (doctorcalls 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.
scope:slot was first seeded with a placeholder, following thegives: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.budget:slot satisfied the^budget:\s*\Sguard 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 assertedfreezerefused without asserting which refusal, and the node was being refused for other placeholders.test_explore_gatecaught what my check could not.doctor's first wiring ofplaceholders_incould never fire:scan()nodes carry no body.add.py'sABF_TYPESandscripts/validate_bundle.py's are two oracles by design; registeringInterviewin one made them disagree about the format — the one disagreement the parity test exists to forbid.Verification
tests/andtooling/), which is what CI runs.humanwith a named lens.Still open (not in this PR)
add joinon 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 newunauthored_nodefinding.