Development record
Acceptance Criteria Verify Themselves, And The Manual Rest Renders For A Human
Press Release
abcd now runs a record's acceptance criteria as a two-part verification suite.
abcd verify <record-id>executes every machine-checkable assertion a record's criteria carry — guard-checked, toolchain-agnostic, in the environment class each assertion declares — and writes a receipt keyed to the content it verified. What cannot be automated is not faked: the remaining criteria render into a step-by-step page (--htmlbuilds and opens it) with their commands inline, a human walks them, and their verdicts land in the same receipt through the same verb. A record's checklist is always a view — boxes tick themselves only while the receipt's sha still matches the criteria, so an edited criterion loses its tick by construction. Shipping an intent, completing a plan, or cutting a release now reads these receipts the way the release gate already reads docs-currency: a fail or a pending manual verdict refuses the move unless the maintainer records a disposition."The § 4 install gate was a hand-written page I had to keep honest by hand," said Alice, a maintainer. "Now the criteria are the page. When I edit one, its tick vanishes until something — or someone — proves it again."
Why This Matters
The Cut A install experience produced the exemplar: a hand-authored § 4 verification gate page (see the install-experience plan, § 4) whose five scorecard rows restate acceptance criteria that already exist in itd-105 and the iss-204..208 wave — connected to them only by prose, kept current only by discipline. That page also proved why the manual half is irreducible: the harness's parallel hook execution and plugin cache do not exist in CI, so some assertions are only checkable by a human on a real machine. Meanwhile the repo's other gates (record-lint, docs-currency, the release receipt gate) are sha-keyed and deterministic — but the one thing they cannot see is whether delivered behaviour still matches the criteria that justified shipping it.
Without this intent, every future cut re-invents the § 4 page by hand, every checklist is a drift risk against its criteria, and a green box has no provenance. With it, verification is derived — never separately authored — and a tick is a claim with a sha and a receipt behind it.
Design Decisions (grilled 2026-08-15)
- Criteria are the single source. The testing script is a rendered view of a record's own Given-When-Then acceptance criteria, never a separately authored artifact. A test step that fits no criterion is evidence of a missing criterion.
- Assertions live inline. An auto-tagged criterion carries a fenced,
typed assertion block: plain shell, exit-code semantics, optional
expected-output pattern;
abcd --jsonprobes preferred where they exist. Record-lint: auto without assertion block = blocker; manual with one = warning. The format is host-agnostic — assertions in a Python or TypeScript repo invoke that repo's own toolchain commands; nothing assumes Go. - Results are receipts, never state on the record. A run writes a receipt
(record id, criteria content-sha, per-criterion pass/fail/manual-pending,
timestamp, sha256 of evidence files). Raw evidence stays in
.abcd/.work.local/and is cited by hash only. Ticked boxes are a render-time join of record × latest sha-matching receipt; a criterion edit makes its tick vanish (stale, never green). - One verb, action as default.
abcd verify <record-id>runs part (a), stamps the receipt, and — on a TTY — walks any pending manual criteria (pass/fail/skip + evidence path) into the same receipt.--htmlrenders and opens the generated page (disposable artefact, never committed); other formats may follow. Non-TTY runs report pending and never prompt, never default (the iss-221 lesson). Ships on both doors: CLI verb and/abcd:verifyplugin page. - Protocol derives from criteria; record types set defaults. A record needs part (b) iff it has ≥1 manual criterion. Defaults as lint expectations: issues default all-auto (manual criterion on an issue warns); intents mix freely; a plan whose criteria are all auto must state why in one line — skipping (b) is a stated decision, never an omission.
- Gating. A current, fully-passing receipt is required to move an intent
to shipped/, to claim plan completion, and at
launch shipfor every shipping intent in the cut. Override only via an explicit disposition recorded on the receipt (the iss-192 precedent): shipping over a fail is allowed, silently is not. Issue resolve: warn-only, ratchet later if the warning proves toothless. - Three environment classes; machine folds into (b).
repo(read-only probes, runnable anywhere),scratch(isolated workspace the runner creates; what "workspace" means is a per-repo toolchain config key), andmachine(state that cannot be conjured locally — fresh install, no-Go). Machine-class assertions are never executed locally; they render into the (b) page with commands inline and return as human verdicts. - Execution is a trust boundary. Every assertion passes the guard registry before running; a refusal is a loud lint finding, not a silent skip. No network unless declared. The implementation plan carries a mandatory security review before code lands.
SOTA
Anchors: Cucumber/Gherkin (executable BDD criteria bound to step definitions)
and Go's testscript format (command + expected output as one document).
Declared path: 2 — native floor with a real seam. Adoption fails the
fit-challenge: a step-definition registry reintroduces exactly the
criteria-to-script drift decision 1 closes, and it would be a new required
dependency. The seam is kept honest: criteria stay strict Given-When-Then
(already linted), the receipt is a documented JSON schema, and the assertion
block borrows testscript's conventions rather than inventing syntax — an
exporter to either anchor stays writable against stable interfaces. The
independent adversarial fit-challenge of this declaration runs at plan time
(evaluator-outside-the-loop).
Rollout (ratchet, not big-bang)
- Phase 1 — calibration corpus. Annotate ~12–15 shipped intents and resolved issues with crisp criteria (itd-89, itd-105, the iss-171/204..208 install wave). Assertions derive from criteria as written, never from observed behaviour. Run verify; triage every failure as regression, criteria drift, or harness defect — the failures are the product, and this phase is itd-109's own acceptance evidence.
- Phase 2 — forward gating. New and touched records get annotations at authoring time; the decision-6 gates arm.
- Phase 3 — opportunistic backfill. Open issues gain annotations on-touch, when picked up for fixing — never as a standalone slog.
Acceptance Criteria
- Given a record whose criteria carry auto assertions, when
abcd verify <record-id>runs, then each repo- and scratch-class assertion executes only after a guard-registry pass and a receipt records per-criterion outcomes keyed to the criteria content-sha. - Given a record with at least one manual criterion, when
abcd verify <record-id> --htmlruns, then a generated page opens showing sha-matched auto results ticked, manual criteria with their commands inline, and the page artefact is not committed. - Given pending manual criteria on a TTY, when the default action finishes part (a), then the verb walks each pending criterion (pass/fail/skip plus evidence path) into the same receipt; given no TTY, then pending criteria are reported pending and no prompt is defaulted.
- Given a criterion edited after its receipt was written, when the record renders, then that box shows stale/unticked, never a match against the old sha.
- Given an intent with a failing or pending receipt and no recorded disposition, when a move to shipped/ is attempted, then the move is refused and the refusal names the criteria that block it.
- Given a machine-class assertion, when verify runs on any local machine, then the assertion is not executed and appears on the rendered (b) page instead.
- Given the phase-1 calibration corpus, when verify runs across it, then every record yields a receipt and every failure carries a triage label (regression | criteria-drift | harness-defect) before phase 2 arms.
Open Questions
- Receipt home and naming: a new working-tier ledger (e.g.
.abcd/work/verification/) vs beside specs — naming decision for the plan interview. - Assertion-block syntax details (fence info-string, expected-pattern grammar,
netdeclaration shape) — borrow fromtestscript, settle at spec time. - The per-repo
scratchtoolchain config key shape. - Disposition record schema (who, why, expiry?) shared with the iss-192-style release dispositions.
- Whether itd-84 decomposition splits runner, renderer, and gates into separate specs under one intent.
Audit Notes
Empty. Populated by intent-fidelity-reviewer when intent moves to shipped/.
Frontmatter
| id | itd-109 |
|---|---|
| type | intent |
| lifecycle | drafts |
| severity | minor |
.abcd/development/intents/drafts/itd-109-a-two-part-verification-suite-for-abcd.md
open on GitHub ↗ · commit history ↗
Referenced by3
- adr-40Review, audit, lint, and gate are four buckets, separated by what each comparesrelated
- iss-2610262043093810itd-109 and itd-50 both define how a human's verification of an intent's criteria is recorded before shipping, as a receipt and as not a receipt.related
- iss-2610262043202769itd-109 lets 'the maintainer' record a disposition, where itd-2609212137129937 requires naming the product thinker or the facilitatorrelated
mentions14
- dcn-26081500000000012026-08-15 — three forward plans adopted at a maintainer grill, absorbing the existing queue rather than adding to it: `2026-08-15-plugin-user-safety.md` (execution priority 1; owns install-experience Cut B pick-up and the v0.5.0 security remainder; fix queue opens iss-200 then iss-195), `2026-08-15-predictable-development.md` (priority 2; itd-109 promotion is the headline; absorbs the v0.5.0 consistency remainder), `2026-08-15-facilitator-experience.md` (continuous, rides along; supersedes the explainability plan's pick-up role). The plan 1/3 boundary is touch-vs-machinery: what a facilitator directly invokes or reads versus automation that guarantees behaviour without human action. Each plan admits items by a stated admission test with the ledger remaining the backlog of record. Same grill: iss-189/190 retired by SUPERSESSION, not wontfix — the defects are real, the local-patch avenue is exhausted (three BLOCKed rounds), so both resolve into the new iss-229 (the recorded 2026-08-08 galloping-probe/trueMatchEnd structural design) with their repros as its acceptance corpus; and itd-111's one-tap micro-prompt open question graduated to its own capture iss-230, homed in the facilitator plan. Rejected: wontfixing 189/190 (asserts the defects don't matter), a full 104-issue triage into the plans (stale mirror of the ledger), and executing the plans in the listed 1-2-3 order (the safety floor and the compounding accelerators come first).
- dcn-26081600000000032026-08-16 — the assessment vocabulary settled at a maintainer grill and filed as adr-40 (proposed): **four buckets — lint / review / audit / gate — separated by what each compares**, not by determinism, not by trigger. The rule was already half-recorded (05-internals/01-agents.md rules the two verdict families disjoint: "reviews assess a change, criterion verdicts assess a promise vs reality") and three shipped surfaces contradict it — `intent review` emits family 2 so it is an audit, `abcd audit` is deterministic rule-checking so it is a lint (and it consumed the `/abcd:audit` name 02-constraints/04-naming.md reserves for itd-16), and `disembark oracle` emits family 1 while the brief calls its output an audit. Grill rulings: **one surface performs exactly one act** (multi-act surfaces split at design time — cheap, because every multi-act surface abcd has is unbuilt, so they are specified rather than refactored); the buckets are a **closed list extended by PR, not a criterion** (an autonomous run given a criterion interprets it; a table lookup matches or fails); `intent ready` survives as the fourth bucket **gate** because it names a decision rather than a comparison, preserving itd-94's exit-code contract; determinism is orthogonal (itd-16's hash-chain check is a deterministic *audit*; itd-85's conformance check is a *lint* however built); automatic-vs-manual never enters a verb name (the trigger is the facilitator's, per itd-97); the oracle is the model-access seam beneath review and audit, never a bucket. **Clean break, no aliases** — pre-1.0.0, `--impact breaking` drives version derivation (iss-171 precedent), users re-download; an alias would additionally collide with the documentation discipline's ban on change-narration, leaving an undocumented silent forward. The role mapping is the operational reason the split earns its cost: product thinker reads `audit` and essentially nothing else, which makes itd-99's "never hand a product thinker a technical decision" mechanically checkable. Same grill corrected a wrong finding: iss-246 was filed claiming abcd's record documents a surface far larger than the binary with no gate catching it; **false** — the `Status` column in 04-surfaces/README.md is machine-checked by `surface_coverage` (iss-35), the brief carries ~100 inline `design target` markers, and adr-5 makes the brief aspirational by design. The real defect is narrower: `surface_coverage` is blind *inside* a row, so six of twenty rows read `shipped` then qualify themselves in prose, and documents outside the registry cite those sub-verbs as live (all three citing **predecessor-store** spec ids spc-29/spc-30, whose prose survived the migration to the native store). Fix per adr-40 §6: sub-verb tables carrying two facts per verb — bucket and existence — with `surface_coverage` extended to registered cobra sub-commands, one table serving both disciplines. Also established: the twelve-step record walk completes at ten steps; step 2 (`capture promote`) has no verb and step 12 (`capture resolve`) cannot write `resolved_by`, so **walkability is sequenced before coherence** — renaming ~90 files across a process nobody can walk is polish before function. Plan at plans/2026-08-16-process-coherence-and-walkability.md; six intents to plan, none plannable unattended. Rejected: one polymorphic `review <id>` verb (merges the disjoint families, erases the role signal), classifying acts rather than surfaces (makes the bucket a statement of intent rather than a guarantee, and hands product thinkers lint findings), three buckets without `gate` (forces `intent ready` to become `intent lint`), and adding `verify`/`validate` (itd-109's verification suite is a packaging of lints and audits, not a fifth kind).
- iss-171The PATH-install detector and installer assume one blessed layout: binTarget defaults to /usr/local/bin/abcd (sudo territory abcd cannot assume) and symlink detection only recognises that exact target, so a working ~/.local/bin/abcd symlink install — the field-standard single-user location used by uv/pipx/rustup-class tools — reports symlink.missing, a false gap; the detector is itself running as abcd from PATH while reporting abcd not installed. Letting ahoy install 'fix' it would write a symlink to <plugin-root>/abcd without validating the target exists — a dangling symlink shadowing the working install. Redesign per fix-the-detector and no-sudo: detection scans PATH for abcd, resolves symlinks (EvalSymlinks, same seam as iss-170), classifies dev-shim/owned/foreign; install defaults to ~/.local/bin (create if needed), adopts an existing owned install in place, does system-wide dirs only behind an explicit --bin-dir flag failing loudly when unwritable, never escalates privileges, refuses to create a symlink whose target does not exist; '~/.local/bin not on PATH' becomes its own loud gap with the printed one-line fix (script-first: print the export line, do not auto-patch shell profiles as the first rung).
- iss-192Brief surface chapters lag the shipped surface: the v0.4.2 release-gate crosscheck (full tier, 22 checkers) found 26 discrepancies across 01-ahoy, 04-launch, 05-intent, 07-memory, 15-prepare-this-repo, 16-audit and 05-internals/08-skills — banlist missing from the fourteen-command roster, hooks/bootstrap.sh absent from every hook enumeration, the shipped citation-baseline dry-run gate undocumented, ahoy's envelope/render enumerations missing banlist/guard-detail/citations fields, intent §6 attributing unshipped acceptance-criteria/bundle/supersession checks to the record lint, prepare-this-repo's stale commands/abcd/ layout and undocumented identity phase, audit's privacy-hygiene row understating network-identifier scanning, and memory's bare-render/schema/spec-range drift. Full finding list with evidence: the iss35-brief-surface-crosscheck receipt for the v0.4.2 content commit under .abcd/work/reviews/. Same residue class as iss-152 (v0.4.1).
- iss-204hooks/hooks.json puts bootstrap.sh and the two binary-backed SessionStart hooks in ONE event group and relies on list order, but the harness runs all matching hooks in PARALLEL — the Claude Code hooks reference states verbatim 'All matching hooks run in parallel.' spc-21 asserts the opposite: 'Ordering within one event's hook list is preserved by the harness, so the binary-backed session hooks run after the bootstrap in the same event.' That warrant is false, and it is load-bearing. On a fresh install the two gated hooks evaluate [ -f "$CLAUDE_PLUGIN_ROOT/abcd" ] while bootstrap.sh is still downloading the ~10.7MB release binary (timeout 240), lose the race, and both print 'the plugin binary is not installed'. Observed on the first manual marketplace install (2026-08-10): both errors printed BEFORE bootstrap's own success notice, which is direct evidence of parallel execution. Consequence is not only noise — 'hook prompt-router-reset' and 'hook session-start' genuinely do not run for that session, and itd-105 AC#1 ('every hook executes successfully — no No such file or directory, no unguarded-shell warning') fails on every fresh install AND every plugin update, since each update lands in a fresh commit-stamped cache directory with no binary. Fix direction: collapse the three entries into ONE SessionStart command that runs bootstrap.sh and then the two binary calls in a single shell, so the sequencing is owned here rather than assumed of the harness.
- iss-221ahoy install's re-founding lineage prompt is a one-shot: under --yes/non-TTY it prints and silently takes the [y/N] default No, registers the root as new, and a second run reports already_up_to_date without re-asking — no verb links lineage after the fact, leaving two active index entries for the same project (old epoch 85573f…, new 488a0aa… after the attribution rewrite). Non-TTY identity prompts should refuse rather than default, and lineage should be linkable post-registration
- itd-75abcd Proves Your CLI Actually Runs — From Fixtures You Drop In
- itd-84An Intent Is Decomposed Before It Is Filed, And A Reversal Is Only Ever Flagged
- itd-89Capture the Session You Just Had, Because You Cannot Capture It Later
- itd-105A fresh plugin install just works: the hook binary survives install and every update
- itd-111A Stale abcd Never Answers Silently
- itd-2609292109475690Every verb family has a behavioural scenario that drives the built binary end to end
- spc-22a-stale-abcd-never-answers-silently-every-surface-that-runs
- spc-2609230613208843Staleness behaviour is calibrated per platform