feat(method): the front door teaches a walk that runs, and the roster names agents that exist - #210
Merged
Merged
Conversation
…tracts Four-lens critic review of the ADD skill, personas and roster on 2026-09-01 produced 4 root causes. This commit persists the frozen contracts only. author: Tin Dang
…eads from stamps, and a routing key outside its taxonomy is a finding Three frozen contracts from the method-truth sweep, all red-first. nested-bundle-guard — `ancestor_bundle()` walks up from the candidate root; `status` in a bundle-less subdirectory names the project and prints `next: cd <project> && add status` instead of `next: add init`; `init` refuses with R:RIVALBUNDLE before it writes anything, unless `--nested` says a second bundle is deliberate. Reproduced before the fix: following the engine's own `next:` line built a rival bundle. beat-read-truth — `status` and `brief` derive the beat from the stamps rather than the `status:` field, which `freeze` never advances. A frozen node read `[direction]` beside `next: add brief`, while `todo` said build and `doctor` reported card_drift. Fixed on the read side, so every existing bundle heals on the next read and reopen is covered too. BEAT_TYPES keeps non-beat node types printing their own status. persona-routing-keys-checked — PERSONA_FLOWS and PERSONA_TASK_KINDS become the single source the scaffold writes and `doctor` checks; an out-of-taxonomy value is an `info` finding naming the node, the key, the offending value and the allowed set. `explore` joins the task kinds: a whole shipped lane whose selector predicate was unsatisfiable. ADD's own two personas carried five of six values outside the taxonomy and now pass. Twin sync and pin re-aim follow once the remaining engine edits land. author: Tin Dang
… reaches the corpus persona-tier-live, red-first. "Personas carry the expertise" was false on every fresh install: 3.0 seeded no personas, so `.add/personas/` was empty and the roster's selector had nothing to search; the 232-lens teacher corpus could not rescue it, because not one of its files carries `flow:` or `task-kinds:`; and neither agent file named the corpus or its routing index at all. The steady state was the generic fallback, taken silently, with nothing in the receipt recording that no expert loaded. `init` now seeds the four persona templates as real Persona nodes and names them in its note. They are the project's from the moment they land — `put` never overwrites, so an edited or deleted persona survives every re-init. Both agents gain a three-tier selection ladder: the project's own roster, then the teacher corpus routed through `personas-index/use-when.md` with a stated tie-break, then the generic fallback — and each must name the tier it selected from in its Return, because a fallback nobody can see is the failure this ladder exists to end. Both corpus trees are optional installs, so a lean install skips that tier silently. Recorded as a replan on the frozen node: A2 read the seed set as the three existing planner templates, whose `flow:` values cover design, advisor and verify but not build — so M2, every roster beat has a seeded match, was unsatisfiable with those three. build-craftsman is the fourth, distilled from the corpus the same way. The templates also lose their 2.x section numbers (§3 Scope, §4 suite), which resolve to nothing in ABF-1 and would otherwise have shipped into every new bundle. Verified on a real fresh bundle: 4 personas seeded, doctor clean, and the subdirectory guard refusing a rival bundle with a runnable recovery. author: Tin Dang
…e suite executes getting-started-executed, red-first. BEYOND-CODE.md's non-code walk was executed by a test; the PRIMARY code walk was executed by nothing — the shipped-doc suite checked it only for phantom verbs. That asymmetry was the root cause, and these were its symptoms: - The walk never showed `add brief`, so a reader following it literally hits R:UNBRIEFED at the gate. `brief` is now Beat 2's first command, with the reason stated. - The freeze line omitted `--authority human`, so the documented ONE approval recorded as `authority: process` — a ledger indistinguishable from an agent approving itself. The paragraph claiming authority is "computed, never asserted" was half true and now says which half: the FLOOR is computed and unassertable; the stamp is not. - The guide promised freeze refuses template placeholders and a node froze with five surviving: `placeholders_in` reads `- ` bullets in RULES/ASSUMPTIONS/CHECKS, and CARD's `goal:` is a keyed line in an unread section. The guard now covers CARD `goal:` at every depth. EVIDENCE and LESSONS stay exempt — they are filled by the run and the close. Executing the walk also found a precondition the guide never stated: the declared `scope:` paths must exist, or the gate cannot digest them and refuses the PASS. Now stated. The runnable blocks are lifted from the shipped document through named anchors, so the document cannot rot while the test passes against a private fork. The fixture writes the reader's own tests using the ids the document's CHECKS name, which proves the binding and not merely the sequence. author: Tin Dang
…finished The CARD `goal:` guard fed `_is_scaffold` through `placeholders_in`, so a node with drafted RULES and CHECKS but no goal yet started reporting the `scaffold` beat — and `todo` stopped counting its assumption sweep down mid-authoring, which is exactly when that countdown is useful. `placeholders_in` now takes `card=`: freeze asks the full question (is this finished), `_is_scaffold` asks the narrower one (was this ever authored). Two questions, two answers, one function. Fixture fallout, all one cause: eight suites reached a post-freeze state through nodes that never stated a goal, which is the thing the guard exists to refuse. The shared `draft_direction` helper now authors CARD — one edit, every suite that uses it follows — and the six suites carrying their own inline authoring were patched the same way. That includes `test_beyond_code_walkthrough`: the shipped non-code walkthrough had also been approving a node whose stated goal was still `<one line>` — the same defect GETTING-STARTED.md claimed freeze prevented, in the guide that proves its own commands. author: Tin Dang
…CARD-guard fixture sweep Twin sync across all four live trees (canonical tooling, the package _bundled twin, and the two gitignored dogfood bundles) plus the three skill trees and the roster. ENGINE_MD5 and ENGINE_PKG_MD5 re-aimed for the sweep's engine changes. Reverted the `placeholders_in` split from the previous commit: `test_authoring_beat` drives the affordance layer and `freeze` against the same node and asserts they agree, so there is exactly ONE notion of "authored" by design. Letting `_is_scaffold` ignore CARD while `freeze` counted it recreated the defect that test exists to catch — a node advised to freeze that freeze then refuses. The `card=` parameter stays for callers that genuinely need only the three body sections, documented as not being a second notion. The remaining fixtures follow the same one-line pattern: a node that reaches a post-freeze state, or the direction beat, now states its goal. `test_assumption_sweep` needed it most — its sweep refusals were being masked by a placeholder refusal, so those checks had been probing the wrong guard. author: Tin Dang
…red, not as wrong `add new Persona` scaffolds `task-kinds: <from the closed taxonomy, comma-separated>`. The routing-key check split that value on commas and then tested each token for a leading `<`, so `the`, `closed`, `taxonomy` and `comma-separated>` all read as values outside the closed vocabulary. Every freshly scaffolded persona reported two findings naming words out of its own placeholder. The guard fired on a MISSING thing — nobody had authored a value yet. It now skips the key whenever the RAW value still carries a placeholder, which is the same notion of "unauthored" `placeholders_in` already uses everywhere else, and drops the per-token bracket test the comma split defeated. Four engine twins synced and ENGINE_MD5 re-aimed. author: Tin Dang
… names agents that exist
Eleven tasks across three milestones, all gated on receipts. Four critic passes over the
skill corpus converged on one shape: the documentation described a method the engine did
not implement, and nothing could see the gap because every guard checked nouns the engine
EXPOSES rather than capabilities the prose PROMISES.
first-run-truth (5/5)
· `init` refuses under an ancestor bundle without `--nested`; `status` names the ancestor
it found instead of reporting no bundle at all.
· `status` and `brief` report the DERIVED beat through one function, so a frozen node
never reads `direction` again — and a new check proves there is only one derivation.
· GETTING-STARTED.md is EXECUTED by the suite: its anchored blocks are lifted, run, and
their claims held. Three of them were false, including the freeze claim it made about
itself.
· The `add run` idiom is one canonical line in all three shipped spots, and the path
appears twice on purpose — `run` READS the report, the wrapped command WRITES it.
· Both front doors dropped the cost claim this repo's own benchmark RETRACTS, gained the
kind×size ladder, and the measured claim now carries its provenance inline.
live-persona-tier (3/3)
· `init` seeds selectable personas, so the roster's tier-1 selector has something to find.
· `personas.md` states all seven scaffolded keys and marks the two the selector reads,
citing the roster file by path — it had called them "read by nothing".
· `seed.md`'s three named archetypes resolved to no corpus file; the resolution guard now
sweeps every shipped file and matches a bare slug, not only a full path.
roster-reachable (3/3)
· The roster table's executor column named four agents this package does not ship, and the
skill named neither of the two it does. Both bound to the installer's roster tree now.
· Each agent file carries an explicit MAY RUN / NEVER RUN verb split, named literally; the
seam verbs stay forbidden and security stays HARD-STOP. `add-worker` gained the `explore`
mode that `phases/explore.md` shipped without.
· The two installer twins declared different global tree sets (`personas-index` was in the
JS and not the Python), and NEITHER deployed the roster anywhere a host looks. A global
install now lands it beside the skill through the shared per-file replace path.
Plus `edges-documented`: the direction guide called five sections four, documented a phantom
`After` part, and stated a `covers:` grammar missing two of the five forms the engine admits.
Every probed assumption is now bound to a check — five gates refused a PASS until they were,
which is the enforcement working. The benchmark report is held byte-identical to HEAD: the
repair for a refuted claim is to change the claim, never the measurement.
Suite: 991 passed, 7 skipped across both test roots.
author: Tin Dang
|
|
||
| # And the gate still REFUSES on that receipt — the assumption's whole point is that the | ||
| # weakness stays visible rather than being papered over. | ||
| node = root / cid.lstrip("/") |
| import sys | ||
| from pathlib import Path | ||
|
|
||
| import pytest |
| import sys | ||
| from pathlib import Path | ||
|
|
||
| import pytest |
…cannot fail is not a guard Review of the method-truth sweep, by a second reader and by re-reading the prose the sweep itself wrote. Seven defects, and the two that matter most are the same shape as the ones the sweep was closing. THE SHIPPED BUG `ancestor_bundle` accepted any directory holding a file named `index.md` as an ADD bundle root. `index.md` is the standard homepage filename for MkDocs — the tool this repo publishes with — plus Docusaurus, Jekyll and Hugo. So in any docs tree, `add init` refused a legitimate project with `R:RIVALBUNDLE` and `add status` announced "you are inside the ADD project at ..." — naming a directory that holds no bundle, and handing back a `cd` that leads nowhere. Three false statements to someone who has never run ADD. `init` writes `abf_version:` into every bundle index and nothing else does, so both branches now read the file's head and require that marker. WHY IT SHIPPED GREEN The test named "ignores a marker that is not a bundle" only ever wrote `graph.json` — a file the function never reads. Delete every `index.md` check from the engine and that test still passed. It discriminated nothing, and its `covers: A2` claimed it did. It now writes the hostile inputs: a bare docs homepage, and an unmarked `index.md` inside a `.add/`. Verified red against the previous engine. THE SAME CLASS, TWICE MORE - `test_the_parity_test_reads_both_twins` inspected a 1768-character slice of its own file, all of it ABOVE its own definition and starting mid-sentence in an unrelated docstring. It could not see the hard-coded tree set twelve lines below, and reordering the two functions flipped its verdict: it measured source layout, not the property it named. It now walks the module's AST for collection literals restating the declaration, and the restated union it was blind to is read from git instead. - `_referent_forms` in test_edges_documented ran a regex over a regex PATTERN, matched nothing, and always fell through to a helper nothing else called. One reader now. PROSE THE SWEEP GOT WRONG - `direction.md` corrected the `covers:` grammar to six forms and called them five — the same off-by-one as the "four sections" bug it was fixing, one paragraph later. Bound to the engine's own count, and verified red at "five". - `streams.md` renamed the executor column and two sentences kept calling it `agentType`, a column that no longer exists. A new check holds the prose to the table's header row; verified red by reintroducing the word. - The root README carried the same measured claim as the package README with no provenance, because the M5 check read one front door while its M1 sibling read both. INSTALLER `_reconcile_global` gained a THIRD write target while the caller kept ONE `except OSError` reporting `cannot write global home <home>`. On a machine where `~/.claude/agents` is not writable — a namespace other tools own — the install aborted naming a home that was fine, after the mirror and skill had landed. The roster write is now non-fatal in both twins and names its own path when it degrades. Also: the seeding loop no longer shadows `init`'s `title` parameter, and `ancestor_bundle`'s docstring no longer claims a containment ceiling the code has no check for — what actually holds is the symlink clause `resolve()` delivers. Suite: 995 passed, 7 skipped. Four engine twins synced, ENGINE_MD5 re-aimed. author: Tin Dang
TinDang97
added a commit
that referenced
this pull request
Sep 3, 2026
…amp is well-formed (#211) * fix(engine): a guard asks whether what a stamp attests is TRUE, not whether 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 * docs(add): the verdict-truth nodes, two gated on real receipts 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 * fix(engine): a refusal names a fix that runs, and a verb reads the flag 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 * fix(docs): a claim on the front door is backed by the shipped artifact, 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 * docs(add): the refusals-that-work nodes, five gated on real receipts 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 * docs(add): record the human interview on done-reads-the-verdict 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 * feat(engine): the human's interview answers, built — a force-close is 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 * docs(add): close verdict-truth (4/4) and refusals-that-work (5/5) 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 * fix(engine): an interview sidecar is a record about a node, not a node 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 * fix(engine): a verb that reports success has read what it is reporting 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 * fix(engine): a slot the scaffold offers is a slot a reader reads 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 * feat(engine): a lane's scaffold produces a node that lane will accept `--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 * fix(test): a check that asserts an empty list must assert it looked at 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 * fix(method): every seeded lens orients on a command that runs, and the 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 * fix(test): a check that cannot fail is a claim, not a guard 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 * feat(engine): run reads the report path the command already names `--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
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Eleven tasks across three milestones, every one gated on a receipt. Four critic passes over the skill corpus converged on one shape: the documentation described a method the engine did not implement, and nothing could see the gap — every existing guard checked nouns the engine EXPOSES rather than capabilities the prose PROMISES.
first-run-truth— 5/5initrefuses under an ancestor bundle without--nested;statusnames the ancestor it found instead of reporting no bundle at all.statusandbriefreport the derived beat through one_beat_of, so a frozen node never readsdirectionagain — with a check proving there is only one derivation.GETTING-STARTED.mdis now executed by the suite: its anchored blocks are lifted, run, and their claims held. Three were false, including the claim it made aboutfreezeitself.add runidiom is one canonical line in all three shipped spots. The path appears twice on purpose:runREADS the report, the wrapped command WRITES it.live-persona-tier— 3/3initseeds selectable personas, so the roster's tier-1 selector has something to find.personas.mdstates all seven scaffolded keys and marks the two the selector reads, citing the roster file by path. It had called them "read by nothing" whileadd-worker.mdselected on both.seed.md's three named archetypes (backend-systems·security-reviewer·frontend-ux) resolved to no corpus file. The resolution guard now sweeps every shipped file and matches a bare slug, not only a full path.roster-reachable— 3/3freeze·gate·done·milestone-done·check) stay forbidden and security stays HARD-STOP.add-workergained theexploremode thatphases/explore.mdshipped without.personas-indexwas in the JS and not the Python), and neither deployed the roster anywhere a host looks. A global install now lands it beside the skill through the shared per-file replace path — idempotent, tombstoning retired agents, never sweeping a user's own subagents.Plus
edges-documentedThe direction guide called five sections four, documented a phantom
Afterpart that exists in no scaffold and no engine surface, and stated acovers:grammar missing two of the five forms the engine'sREFERENTpattern admits.Enforcement worked against me five times
Five gates refused
PASSon an unbound probed assumption — a· probe:line makes anA<n>a first-classcovers:referent, exactly like a Must. Each needed a genuine binding check written and the contract re-frozen. One probe was verified non-vacuous by mutatingbriefand confirming the check bites.Two widenings, both declared: a recorded
add replanbefore touching the repo-rootREADME.md(same refuted claim, outside the frozen scope), then a re-freeze with it added; andtest_shipped_docs.pynow sweepsagents/— nothing had ever linted the roster files.Also fixed en route: the
persona_routing_keycheck fired on untouched scaffold slots — splitting<from the closed taxonomy, comma-separated>on commas left interior words carrying no bracket. A guard firing on a missing thing. Committed separately with the four engine twins synced andENGINE_MD5re-aimed.The benchmark report is held byte-identical to HEAD by a check: the repair for a refuted claim is to change the claim, never the measurement.
Verification
991 passed, 7 skippedacross both test roots (add-method/tests/andadd-method/tooling/), the full CI shape.