Development record
A Stale abcd Never Answers Silently
Press Release
Every abcd surface now knows its own vintage and says so. The binary carries its build revision; at session start the plugin compares it against what should be running — the source tip in a dogfood checkout, the plugin manifest's pinned version everywhere else — and a mismatch is announced with the one-command fix, never discovered a session later through misbehaviour.
abcd versionandabcd ahoyalways show install mode and vintage. And the one verb where stale logic writes state refuses outright:ahoy installrun through a binary older than its own source declines to touch the machine until the binary is rebuilt. abcd never asks the network "what's new?" on its own: implicit checks read only what is on disk, the network answers only an explicit--check, and when a plugin update names a new binary version, provisioning fetches exactly that pinned, checksum-verified artifact — completing an update the user already chose, not phoning home."A month-stale binary spent a morning confidently applying month-stale install logic at my machine," said Alice, a maintainer. "Now the session opens by telling me the binary is behind the tip, and the install verb won't even run through it."
Why This Matters
The 2026-08-15 install session is the evidence (iss-228): the repo-root
plugin binary predated the no-sudo install work, so it targeted root-owned
/usr/local/bin, rejected the --bin-dir flag its own skill documentation
described (iss-225/226 — the docs were current, the binary was not), and sent
a whole session into detective work before make build revealed the trivial
root cause. Nothing in the system knew — or could say — that the binary was a
month behind the surface fronting it. The failure class is invisible by
construction: a stale binary behaves plausibly, just wrongly, and the newer
the documentation the more misleading the combination. iss-227 (error paths
that report partial with no note) compounds it: silence on top of silence.
Detection is cheap and already half-present — Go stamps the VCS revision into the binary, the checkout knows its tip, the plugin cache knows its pinned version. What is missing is only the comparison and the refusal to stay quiet.
Design Decisions (grilled 2026-08-15)
- Separate intent, detection-only. itd-108 ships a distribution channel; this ships a standing invariant across both channels. itd-111 never writes and never heals — rebuild is the dev shim's job, re-download is itd-105/108's provisioning job. It detects and refuses to be silent.
- The network trichotomy is a system-wide trust rule, extracted. The itd-84 decomposition at planning (2026-08-15) routed it out of this intent: the rule now lives as adr-38 and brief invariant 7, which this intent cites and implements — implicit checks disk-only; the network answers only an explicit ask; provisioning fetches only the manifest-named, checksum-verified version.
- Warning surfaces. SessionStart notice (the existing gap-notice
channel) names the stale binary, its vintage, and the one-command fix.
abcd versionandabcd ahoyalways print install mode + vintage. Ordinary verbs stay silent — with one deliberate exception:ahoy installrun through a stale-against-tip binary refuses with the mismatch named and writes nothing, because stale install logic mutating the machine is the trap the evidence session actually fell into. - Doc/binary version-match is a corollary, not a mechanism. The plugin ships docs and binary as one unit, so drift exists only where vintage drift exists (dev checkouts, failed provisioning); the vintage warning is the doc-drift warning. No separate doc-version machinery.
- Platform parity is verified, not assumed. macOS and Linux share the same paths and semantics by design; the claim becomes a machine-class criterion in the itd-109 calibration set (fresh Linux box on the (b) page), not an assumption in prose.
- The explicit check lives at
abcd version --check(planning ruling, 2026-08-15). Vintage isversion's domain;ahoystays disk-only. - Unknown vintage fails closed at the refusal gate (planning ruling,
2026-08-15, from the fit-challenge's stamping-gaps finding). A binary
whose vintage cannot be determined — unstamped build,
-buildvcs=false, or a dirty (vcs.modified) rebuild — is reported as unknown, never silently treated as fresh;ahoy installrefuses through it, naming a rebuild (or an explicit override flag, specced there) as the out. - Decomposition record (itd-84 hand-run, 2026-08-15). Verdict SPLIT:
the network posture → adr-38 + brief invariant 7 (above); the anti-
wallpaper micro-prompt seed → iss-230. Typed links:
refinesitd-105 (this intent reports the transition provisioning performs),refinesitd-109 (parity as a machine-class calibration criterion),refinesthe iss-206 skew-notice retirement — a scoped replacement, not a reversal: steady-state skew machinery stays retired; this intent covers the non-steady states (dev checkouts, failed provisioning) pinned provisioning cannot reach.
SOTA
Anchors: the update-notifier pattern (npm ecosystem), Homebrew's
auto-update-on-use, and Go's embedded build info (runtime/debug.BuildInfo
VCS stamping). Declared path: 2 — native floor. The anchors' implicit
network checks fail the fit-challenge outright (a privacy-positioned tool
making background HTTP calls is the posture this record scores against
elsewhere); what survives is their UX grammar — cached comparison, gentle
nudge, one-command fix — implemented over disk-only sources.
Fit-challenge (independent, 2026-08-15): UPHELD, with three recorded
caveats. (a) The seam is a version-source provider interface feeding one
comparator — compare(current, expected) with expected drawn from a
provider (disk providers now: embedded revision, checkout tip, manifest pin;
a network/selfupdate provider is a drop-in later, behind the new-dependency
gate). A fixed-arity function over three disk sources would be reuse, not
swappability. (b) The closest disk-only precedent joins the anchors: git's
own "behind upstream" notice — comparison against locally cached refs,
refreshed only by explicit fetch — is exactly the grammar built here.
(c) Go's VCS stamping has known holes (go run/go test binaries,
-buildvcs=false, .git-less builds, go install module@version history,
vcs.modified dirty rebuilds), so the comparator has an explicit
unknown outcome — see design decision 7. The challenge also confirmed
no adoptable path-1 candidate exists (every checker in the class is
network-based) and that the pattern's ecosystems offer opt-out, not a
disk-only mode.
Scope Conditions
None stated.
Acceptance Criteria
- Given a dogfood checkout whose plugin-root binary predates the source tip, when a session starts, then the SessionStart notice names the binary, its embedded revision, the tip it trails, and the one-command rebuild fix.
- Given a binary stale against its checkout tip, when
abcd ahoy installruns through it, then the install refuses before any write, naming the vintage mismatch; a fresh binary proceeds normally. - Given any install mode, when
abcd versionorabcd ahoyruns, then the output includes install mode and vintage (embedded revision or pinned version) and staleness relative to the disk references. - Given any verb without an explicit check flag, when it runs, then no network request for version discovery is made — verifiable in the zero-network test harness the citation gate already uses.
- Given the explicit check (
abcd version --check), when the user invokes it, then the latest release is fetched once, compared, and reported with its source named. - Given provisioning has fetched a new pinned binary (itd-105/108's job),
when the next session starts, then the session reports the version
transition performed.
Amended 2026-09-30 by the product thinker's rulings CJ1 and CJ1b of
2026-09-29 (recorded in dcn-2609300000000011): the
transition is reported once, by the process that performed the swap, when
it completes: the bootstrap's success notice and
abcd update's receipt open withabcd updated from X to Y, and the session start shows it only for a swap made by a hook that discards its output, once. The per-repo setup_version comparison that first met this criterion is removed (iss-2609291942520919). - Given a binary whose vintage cannot be determined (unstamped build, dirty
vcs.modifiedrebuild), when staleness is evaluated, then the state is reported as unknown — never as fresh — andabcd ahoy installrun through it refuses before any write, naming the rebuild fix. - Given the same staleness scenarios on macOS and on Linux, when they run on each platform, then observed behaviour matches — verified by an agent's check on macOS plus the Linux leg of CI, with the record naming both runs. Amended 2026-09-29 by the product thinker's ruling (recorded in dcn-2609290000000007): the agent's check on macOS and the Linux CI run stand in for the per-platform check by a person that this criterion first asked for ("recorded as a machine-class criterion, human-verified per platform", with the itd-109 calibration as the harness). itd-109 is still a draft, so no calibration harness exists to run; the ruling refines this criterion and no record link type names a ruling, so the amendment is stated here. The two runs are recorded in spc-2609230613208843.
Open Questions
All resolved or explicitly deferred at planning (2026-08-15):
- Sampled re-surfacing / one-tap micro-prompt — graduated to its own capture, iss-230 (facilitator-experience plan); struck from this intent.
- Explicit check naming — resolved:
abcd version --check(design decision 6). - Harness portability of the SessionStart channel — explicit deferral to the itd-22 lineage: the comparison provider is host-agnostic core; each harness adapter wires its own session-start channel when that harness lands. Not a criterion of this intent.
- Refusal breadth — resolved: deliberately narrow,
ahoy installonly (design decision 3); extending to other state-writing verbs is a later intent if evidence arrives.
Audit Notes
Fidelity review — receipt rcp-69424cae8106 (verifier intent-auditor claude-fable-5-1).
Provenance: intent-auditor@claude-fable-5-1 · rubric_hash sha256:effa65b3e9e88ff29433b443ec2be159522a8b0b71cf1434526514aa61edb13e · prompt_hash sha256:826d0d983d9ba33c40ce27386862f78a1b69f70dcfa0af491ab32d1abda59ee8 Input attestations: diff:ce8e1c9ae..8ffb6d069 (feat/itd-111-close; spc-22 behaviour judged in the tree at 8ffb6d069)@sha256:95e8eba0442aba7b2e180d12914484e2715c4a0b68d5f2808bea280a0176e2ed;
Acceptance rollup: MET 3 · MET_WITH_CONCERNS 5 · NOT_MET 0 · INCONCLUSIVE 0
Per-criterion verdicts:
- ac-1 — MET: the session-start hook runs the plugin-root binary, which renders a notice naming the binary path, its embedded revision, the checkout tip and
make buildwhen the dogfood comparison is stale; TestFormatStalenessNotice asserts all four tokens evidence: internal/surface/cli/cli.go:1735 — "if exe, err := os.Executable(); err == nil { if n := stalenessNotice(cwd, exe)" evidence: internal/surface/cli/staleness.go:32 — ""abcd: %s was built from commit %s but this checkout is at %s — the binary is behind its own source. Rebuild it withmake build."" evidence: internal/surface/cli/staleness_test.go:18 — "for _, want := range []string{exe, rev[:12], tip[:12], "make build"}" evidence: hooks/hooks.json:19 — ""$CLAUDE_PLUGIN_ROOT/abcd" hook session-start" - ac-2 — MET_WITH_CONCERNS: the refusal sits before the first apply step and names both revisions; TestInstallRefusesStaleAgainstTip proves no repo write and TestInstallProceedsThroughFreshBinary proves the fresh path; the concern is that under an explicit --bin-dir the writability probe at apply.go:91 (dirWritable, store.go:431) creates and removes a temp file BEFORE the refusal fires, while its comment at apply.go:267 claims it creates nothing evidence: internal/core/ahoy/apply.go:157 — "if !opts.AllowStaleBinary { if reason := staleBinaryRefusal(currentVintage(), abs); reason != """ evidence: internal/core/ahoy/vintage.go:158 — ""the running abcd binary was built from commit %s but this checkout is at %s — it is behind its own source" evidence: internal/core/ahoy/refusal_test.go:49 — "func TestInstallRefusesStaleAgainstTip" evidence: internal/core/ahoy/refusal_test.go:97 — "func TestInstallProceedsThroughFreshBinary" evidence: internal/core/ahoy/store.go:431 — "f, err := os.CreateTemp(dir, ".abcd-write-probe-*")" evidence: internal/core/ahoy/apply.go:267 — "Probe writability without creating anything"
- ac-3 — MET_WITH_CONCERNS:
abcd --versionandabcd ahoyboth render install mode, vintage and staleness from the one ahoy.Vintage comparator, in text and JSON; the concern is wording drift: the literalabcd versionthe criterion names is a moved stub that refuses and points atabcd --version(itd-2609212130136102) evidence: internal/surface/cli/version.go:105 — "fmt.Fprintf(w, " install: %s\n", out.InstallMode) ... vintage ... staleness" evidence: internal/surface/cli/cli.go:252 — "return runVersion(cmd, asJSON, false)" evidence: internal/surface/cli/cli.go:3298 — "vin := ahoy.Vintage(cwd)" evidence: internal/surface/cli/cli.go:3312 — "fmt.Fprintf(w, " vintage: %s\n", out.Vintage)" evidence: internal/surface/cli/version.go:77 — "return movedRefusal("abcd version", "abcd --version")" evidence: internal/core/ahoy/vintage_test.go:77 — "func TestVintageDisplayAndStaleness" - ac-4 — MET: the release fetcher is constructed only behind the explicit check; TestOnlyUpdateCheckTouchesTheNetwork counts zero fetcher constructions across --version, ahoy and session-start, and TestModeAndStatuslineTouchNoNetwork counts http.DefaultTransport hits at zero for mode, statusline and the bare verb
evidence: internal/surface/cli/version_check_test.go:44 — "if got := atomic.LoadInt32(&calls); got != 0 { t.Fatalf("an implicit path fetched"
evidence: internal/surface/cli/version.go:102 — "if check { out.Check = runReleaseCheck(v.Version) }"
evidence: internal/core/vintage/release.go:23 — "ReleaseFetcher ... reached only by an explicit
abcd update --check. A disk path never constructs one." evidence: internal/surface/cli/statusline_test.go:252 — "http.DefaultTransport = countingTransport{&hits}" - ac-5 — MET_WITH_CONCERNS: the explicit check fetches the latest tag exactly once, compares through vintage.Compare and names its source; the concern is wording drift: the criterion names
abcd version --check, which is a moved stub refusing towardsabcd update --check(itd-2609212130136102), where the behaviour lives evidence: internal/surface/cli/update.go:49 — "if check {" evidence: internal/surface/cli/update.go:53 — "return runVersion(cmd, *asJSON, true)" evidence: internal/surface/cli/version.go:129 — "exp := vintage.ReleaseProvider(newReleaseFetcher()).Expected()" evidence: internal/surface/cli/version.go:30 — "const checkSource = "github.com/intentdriven/abcd releases"" evidence: internal/surface/cli/version_check_test.go:50 — "if got := atomic.LoadInt32(&calls); got != 1 { t.Fatalf("update --check fetched %d time(s), want exactly 1"" evidence: internal/surface/cli/version.go:75 — "return movedRefusal("abcd version --check", "abcd update --check")" - ac-6 — MET_WITH_CONCERNS: the session-start hook reports a running version that differs from the repo's recorded meta.setup_version, proven by TestSessionStartReportsVersionTransition; the concern is the narrower mechanism: the reference is the per-repo setup_version written by
ahoy install(not a record beside the plugin-cache metadata as spc-22 stated), so a repo never set up reports no transition, and a dev build on either side is never a transition evidence: internal/surface/cli/cli.go:1744 — "if from, to, changed := ahoy.VersionTransition(cwd); changed {" evidence: internal/core/ahoy/vintage.go:221 — "return versionTransitionFrom(recordedSetupVersion(cwd), core.Version)" evidence: internal/core/ahoy/vintage.go:230 — "if isDevOrUnknown(running) || isDevOrUnknown(recorded) { return recorded, running, false }" evidence: internal/surface/cli/transition_test.go:15 — "func TestSessionStartReportsVersionTransition" - ac-7 — MET: an unstamped or vcs.modified build yields Current{Known:false}, Compare returns Unknown before consulting any provider, vintageFrom reports it terminally, and staleBinaryRefusal refuses naming
make build; TestInstallRefusesUnknownVintage proves no write evidence: internal/core/vintage/vintage.go:80 — "if !cur.Known { return Report{Outcome: Unknown, Current: cur.Revision} }" evidence: internal/core/vintage/vintage.go:138 — "if haveModified && modified { return Current{Revision: rev, Known: false} }" evidence: internal/core/ahoy/vintage.go:155 — ""the running abcd binary's vintage cannot be determined (an unstamped or modified/dirty build) ... Rebuild it withmake build" evidence: internal/core/ahoy/refusal_test.go:23 — "func TestInstallRefusesUnknownVintage" evidence: internal/core/ahoy/vintage_test.go:41 — "func TestVintageFromUnknownCurrentIsTerminal" - ac-8 — MET_WITH_CONCERNS: the closed spec names both runs; the Linux run (36608836118 at de42f275a, job check (ubuntu-latest) 109544802516) reads success on the forge, and the auditor re-ran the 25 named tests on darwin/arm64 (25 pass, 0 skipped); the concerns are that the amendment cites a 2026-09-29 ruling 'recorded in' the decision log, which no line of that log carries at 8ffb6d069 or on main, and that the end-to-end scratch-copy check is narrative only, with no script or log in the tree to re-run
evidence: .abcd/development/specs/closed/spc-2609230613208843-per-platform-staleness-calibration.md:36 — "CI workflow run 36608836118, job
check (ubuntu-latest)(job 109544802516)" evidence: .abcd/development/specs/closed/spc-2609230613208843-per-platform-staleness-calibration.md:42 — "macOS: the agent's check, 2026-09-29, at the same commit (darwin/arm64)." evidence: .abcd/development/intents/shipped/itd-111-a-stale-abcd-never-answers-silently.md:160 — ".abcd/work/DECISIONS.mdunder that date" evidence: dcn-2609290000000003 — "2026-09-29 — An autonomous run keeps at most five sub-agents alive at once (the last 2026-09-29 line; none names itd-111, platform parity or a stand-in check)"
Gap audit:
- honoured:
- one comparator with a first-class unknown outcome, fed by a provider interface (fit-challenge seam) evidence: internal/core/vintage/vintage.go:64 — "type Provider interface { Expected() Expected }" evidence: internal/core/vintage/vintage.go:79 — "func Compare(cur Current, p Provider) Report"
ahoy installrefuses through a stale or unknown-vintage binary before the first apply step, with a documented override evidence: internal/core/ahoy/apply.go:157 — "if !opts.AllowStaleBinary {" evidence: internal/core/ahoy/refusal_test.go:73 — "func TestInstallOverrideProceedsThroughStaleBinary"- implicit checks are disk-only; the network answers only the explicit check (adr-38) evidence: internal/surface/cli/version_check_test.go:38 — "// Every implicit path: none may fetch."
- the session-start notice names binary, revision, tip and the one-command rebuild
evidence: internal/surface/cli/staleness.go:32 — "Rebuild it with
make build." - both platforms exercised the staleness tests: Linux CI success and a macOS re-run by the auditor evidence: .abcd/development/specs/closed/spc-2609230613208843-per-platform-staleness-calibration.md:36 — "CI workflow run 36608836118"
- diverged:
- the explicit check is at
abcd update --check, not theabcd version --checkAC5 and design decision 6 name (wording drift from itd-2609212130136102, judged on behaviour) evidence: internal/surface/cli/version.go:75 — "return movedRefusal("abcd version --check", "abcd update --check")" evidence: internal/surface/cli/update.go:53 — "return runVersion(cmd, *asJSON, true)" - the vintage report AC3 places on
abcd versionis atabcd --version;abcd versionrefuses with a moved notice (same consolidation) evidence: internal/surface/cli/version.go:72 — "cmd.Deprecated = "its report moved toabcd --versionand its check toabcd update --check"" - the amended AC8 cites a 2026-09-29 product-thinker ruling recorded in the decision log; no such line exists at 8ffb6d069 or on main (the PR body says a separate rulings lane appends it)
evidence: .abcd/development/intents/shipped/itd-111-a-stale-abcd-never-answers-silently.md:160 — "
.abcd/work/DECISIONS.mdunder that date" evidence: dcn-2609290000000003 — "2026-09-29 — An autonomous run keeps at most five sub-agents alive" - under an explicit --bin-dir a writability probe creates and removes a temp file before the stale refusal, and its comment says it creates nothing evidence: internal/core/ahoy/apply.go:91 — "binTargetPath, err := resolveInstallTarget(opts, det.pluginRoot)" evidence: internal/core/ahoy/store.go:431 — "f, err := os.CreateTemp(dir, ".abcd-write-probe-*")" evidence: internal/core/ahoy/apply.go:267 — "Probe writability without creating anything"
- the transition report keys on the repo's meta.setup_version (per repo, silent for dev builds and for a repo never set up), not on a record beside the plugin-cache metadata as spc-22 stated evidence: internal/core/ahoy/vintage.go:242 — "func recordedSetupVersion(cwd string) string" evidence: .abcd/development/specs/closed/spc-22-a-stale-abcd-never-answers-silently.md:84 — "the last-reported one recorded beside the plugin-cache"
- the explicit check is at
- missing: (none)
Surfaces named, recorded on 2026-09-29 as iss-2609292057441479 after the fidelity review above: criterion 3's abcd version report is met by abcd --version (and by abcd ahoy), and criterion 5's abcd version --check is met by abcd update --check, the spellings itd-2609212130136102 consolidated them into; both old spellings refuse with a notice naming the new one. Design decision 6 and the resolved open question on check naming read the same way. The criterion text above stands as shipped, and adr-38's rule is unchanged, so it is not superseded.
Probe ordering paid, recorded on 2026-09-30: the ac-2 concern above, that under an explicit --bin-dir the writability probe created and removed a temp file before the stale-binary refusal fired, is resolved by iss-2609291942529461. The refusal runs before the adoption question and before the install target is resolved, so a stale or unknown-vintage binary refuses before it touches the filesystem, and TestStaleRefusalPrecedesTheBinDirProbe holds that order. The ac-2 verdict above stands as the audit recorded it.
Frontmatter
| id | itd-111 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | minor |
.abcd/development/intents/shipped/itd-111-a-stale-abcd-never-answers-silently.md
open on GitHub ↗ · commit history ↗
References1
- itd-108The plugin installs from the curated release artifact, not the repo, and every cut release reaches users automaticallybuilds on
Referenced by9
- adr-38Implicit checks are disk-only — the network answers only an explicit askrelated
- iss-2610262034492769itd-111 has abcd version show install mode and vintage; the board chapter says version is a retired name and the report is now a flag on the root.related
- iss-2610262039403534itd-111 says `abcd version` always shows install mode and vintage; the surfaces register says `version` is a root flag, not a verbrelated
- iss-2610262045033573itd-111 names abcd version as where install mode and vintage always show; itd-2609212130136102 removes the abcd version verbrelated
- itd-2610031026190632abcd's update check says when a harness is older than abcd needsbuilt on by
- itd-2610161616086564After a plugin update, abcd offers to bring the repository's setup up to datebuilt on by
- itd-2610161617096084After a plugin update, abcd says what changed for this repositorybuilt on by
- spc-22a-stale-abcd-never-answers-silently-every-surface-that-runsimplemented by
- spc-2609230613208843Staleness behaviour is calibrated per platformimplemented by
mentions36
- adr-44abcd never mutates a remote uninvited, and identity derives from caller-local facts
- 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-26081500000000032026-08-15 — itd-111 planned at the live interview (spc-22 minted, `intent ready` exit 0). The itd-84 decomposition's first live hand-run rendered SPLIT, confirmed: the network trichotomy extracted to adr-38 ("implicit checks are disk-only — the network answers only an explicit ask") + brief invariant 7, which the intent now cites rather than declares; the SessionStart staleness notice vs the iss-206 skew-notice retirement was ruled a SCOPED REPLACEMENT (`refines`, not `reverses`) — steady-state skew machinery stays retired, itd-111 covers dev checkouts and failed provisioning. Interview rulings: explicit check at `abcd version --check` (ahoy stays disk-only); refusal-on-stale deliberately narrow (ahoy install only); unknown vintage (unstamped/dirty builds) is a first-class comparator outcome that fails closed at the refusal gate; harness portability of the session-start channel explicitly deferred to the itd-22 lineage. Independent SOTA fit-challenge UPHELD path 2 with three recorded caveats (seam = version-source provider interface, not a fixed-arity function; git's behind-upstream notice joins the anchors; BuildInfo stamping holes motivate the unknown outcome). AC 6 narrowed to the transition report (the fetch is itd-105/108's); one AC added (unknown vintage). Hand-run graded into the decomposition-calibration note (entry 2).
- dcn-26092500000000282026-09-25 — The build's open-question check reads the record's own settled convention and stays fail-closed (lane implementer, autonomous run A, fix round 1 of the loop lane, on iss-2609252053576085; the lane's first ruling, that every list item under `## Open Questions` is a question whatever it says, lived only in its report and refused intents the record had settled). `intent.OpenQuestions` recognises exactly two markers, learned from the 12 planned intents that carry list items there. A section whose first non-blank line is an italic `_All resolved …_`, with at most one count word (`_All four resolved …_`), is settled whole (itd-111, itd-93); `_All but one resolved_` is not. An item explicitly marked resolved or deferred is not a question: a bold span opening with the word (`**Resolved — …**`, `**Deferred**`, `**Deferred to a follow-up intent**`, `**explicitly deferred**`, `**explicit deferral**`) or the word as a label (`resolved:`, `RESOLVED:`, `Deferred:`), anywhere in the item including its continuation lines. Everything else stays a question: an item led `**Open` whatever it goes on to say (itd-60's "Open, and not gating scope"), an item that only points to another record (itd-76's "travel with itd-126"), `**Out of scope …**`, and a question that merely mentions deferral. With the rule, itd-111 and itd-93 pass the check; itd-7, itd-60, itd-65, itd-66, itd-76, itd-82, itd-117, itd-131, itd-2609170822093401 and itd-2609211116005482 still refuse, each on an item the record does not mark settled. Relabelling those items is the product thinker's call through the planning interview, not the loop's.
- dcn-26092900000000032026-09-29 — An autonomous run keeps at most five sub-agents alive at once, in any mix of roles (the user, directly to the run A orchestrator at 06:53Z, verbatim: "use up to five sub-agents from now on"; recorded by lane remerge-integ17 of autonomous run A). The ceiling of five supersedes the ceiling of four recorded above on 2026-09-24 (02:02Z). Reviews and audits run on Fable stay one at a time; that rule is unchanged. The run still counts a fork toward the ceiling, as its own lane rule, because a fork is an agent alive.
- dcn-26092900000000072026-09-29 — The product thinker answered twenty-six owed rulings and the published-history question in one interview, asked one at a time by orchestrator abcd-23 of autonomous run A (recorded by lane recRulings for orchestrator abcd-cb). The answers are kept verbatim in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29.md`, dated 2026-09-29, and are restated here because that file is not committed. Published history is left as it is, with no rewrite. The build loop's fidelity audit runs once, on the lane that closes the spec, over the whole delivery (AI). On the vendor denylist the product thinker answered, verbatim: "I think we need an allow list rather than trying to catch a deny list." The interviewer's reading of that answer, recorded as a reading and not as the product thinker's words, is that the per-provider allowlist is the control and the vendor denylist is not extended to chase spellings, with whether the bundled `anthropic/*` entry stays as a backstop left to the technical facilitator (AA(a)). Only a route the person set up on their own machine may use their paid key, and a repository's own route to it is refused, which replaces the route half of itd-2609081951381895 decision 8 (AA(b)). The live site setup, the API adapter and the decision adapter may each close on tests against a simulated service, with the live paid check recorded as owed to a person (iss-2609291925387011, iss-2609291925393913, iss-2609291925398978). itd-111's last criterion is met by an agent's timing check on macOS plus the Linux CI run, so the intent may be finished. Automated dependency updates are switched on, pushed with a personal or app token the person creates and stores as a repository secret (itd-2609221842494980; creating the token is the person's act, and the lane builds everything else and names the secret it expects). itd-6 adds a Go MCP module, proposed for the person's explicit sign-off before it is fetched; closing itd-6 on a fake server was not ruled in this interview. (A later answer the same day, H1b in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29-b.md`, dated 2026-09-29, rules that it is not: itd-6 is finished only after running once against the real RepoPrompt app, a check owed to a person.) For itd-148: the store under the home folder wins (adr-2609091248200336); the store draft itd-2609091014076309 owns add, list and clean-up and itd-148 uses them; a session that declares itself the coordinator is exempt from the write block in the primary checkout and every other session is blocked; and the store draft is planned first, with nothing from the store draft, the merged-worktree clean-up included, built before its planning interview (Q4). itd-148's own wait is not part of that ruling: it follows from Q2, because itd-148 uses the store's verbs, and is carried by the `blocked_by` below. itd-148 and spc-42 are rewritten to match in this change, and itd-148 is now `blocked_by` the store draft. A release retrospective is seeded from a release, the intents a tag shipped (AD); itd-24 and its spec are rewritten to the release in this change. An unpointed route is refused naming the one setting to add, as built in 35100512e, so AC11 of itd-2609170822093401 is complete (BR1). A missed target becomes `next`, whatever the following release is numbered (BS1). Spec pieces keep "step" and the lane stages are renamed, a breaking change to the JSON fields `step`, `performed` and `refusal.step` that resolves iss-2609291313276243 and corrects the glossary entry `core/step.md` (BU1), and the old command spellings are removed in that same breaking release (BT1). The text board shows Later as a count only while the site and `--json` keep the full list (BV1), and an intent in progress appears only under Now (BV2). A pause of zero is allowed (BW1); drain stays in the agents' help list until it runs end to end (BX1); a managed repository must hold the drain eligibility decision in its own record, and drain refuses there until it does (BX2); a capture without a remedy is refused (BX3), which tightens the one-line capture friction `commands/capture.md` promises, so the lane that builds it reconciles that page, the principle wording and every caller that files a capture; a run's pick reason keeps the shared `pursued:` kind with its marker (BZ1); and an intent whose blocker was superseded waits on the successor, transitively (BZ2). The five itd-48 follow-ups are planned as one intent, drafted as itd-2609291923559186 (BT2), and all warn rules share one shrink-only baseline file in which a new warning fails, drafted as itd-2609291924469783 (BT3); both drafts wait on their planning interviews. Each ruling that contradicts a shipped behaviour or another record is amended in the change that builds it, not here. itd-7's planning interview stays owed.
- dcn-26092900000000082026-09-29 — Two itd-111 follow-ups from its fidelity audit (lane itd111Follow of autonomous run A). The stale-binary refusal of `ahoy install` runs before the adoption question as well as before the `--bin-dir` writability probe, so a refusal asks nothing it then ignores and touches nothing (iss-2609291942529461; the record fixed only "before any write"). The criterion 6 transition reference that spc-22 placed beside the plugin-cache metadata is not built: who writes that record, where it lives when the data directory comes from the environment, and whether it replaces the per-repo setup_version comparison are unsettled, so iss-2609291942520919 is deferred past v0.11.1 with those questions owed to the product thinker.
- dcn-26092900000000172026-09-29 — Two itd-111 follow-ups from its fidelity audit (lane itd111Follow of autonomous run A). The stale-binary refusal of `ahoy install` runs before the adoption question as well as before the `--bin-dir` writability probe, so a refusal asks nothing it then ignores and touches nothing (iss-2609291942529461; the record fixed only "before any write"). The criterion 6 transition reference that spc-22 placed beside the plugin-cache metadata is not built: who writes that record, where it lives when the data directory comes from the environment, and whether it replaces the per-repo setup_version comparison are unsettled, so iss-2609291942520919 is deferred past v0.11.1 with those questions owed to the product thinker.
- dcn-26093000000000012026-09-30 — The 2026-09-29 itd111Follow entry above records the ordering of the stale-binary refusal but not what it changes for a repository that is already set up, which only commit ea70f3c9b and its pull request named (recorded by lane integ23 of autonomous run A, from the review of itd111Follow). Because the refusal runs before the adoption question and before the idempotency check that answers a set-up repository with already_up_to_date, `ahoy install` run through a stale or unknown-vintage binary on an already set-up repository refuses and names both revisions, instead of reporting it already up to date; an explicit `--adopt=false` through such a binary yields refused, with its note, rather than aborted. Both are the louder answer itd-111 asks for: a stale binary that reports "up to date" is the silent answer its design decision 1 forbids (iss-2609291942529461).
- dcn-26093000000000042026-09-30 — The 2026-09-29 itd111Follow entry above records the ordering of the stale-binary refusal but not what it changes for a repository that is already set up, which only commit ea70f3c9b and its pull request named (recorded by lane integ23 of autonomous run A, from the review of itd111Follow). Because the refusal runs before the adoption question and before the idempotency check that answers a set-up repository with already_up_to_date, `ahoy install` run through a stale or unknown-vintage binary on an already set-up repository refuses and names both revisions, instead of reporting it already up to date; an explicit `--adopt=false` through such a binary yields refused, with its note, rather than aborted. Both are the louder answer itd-111 asks for: a stale binary that reports "up to date" is the silent answer its design decision 1 forbids (iss-2609291942529461).
- dcn-26093000000000092026-09-30 — The person answered eighteen rulings of autonomous run A on 2026-09-30 (CF1, CF2, CG1, CI1, CI2, CJ1, CK1, CL1, CM1, DR1, DR2, DR4, DR5, DR6, DQ1a, DQ1b, DQ2, DQ3), relayed by the run's interviewer abcd-23 [b1e81b] and recorded by lane recRulingsDR for orchestrator abcd-8d [5bfdfb]. The answers are kept in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29-b.md`, lines 13 to 30, and are restated here because that file is not committed. SETTLED BLOCKERS: a blocker replaced by an accepted ADR counts as settled, so the waiting intent may start (CF1); a blocker reclassified as a discipline counts as settled too (CF2). Neither is built at this entry's base: the blocked pre-start check still refuses both shapes, and iss-2609300751191426 carries the change. AS BUILT: the App token for the dependency re-authoring workflow is minted in the shell, an openssl-signed JWT exchanged with curl and revoked on exit, with no new action (CG1); when the prompt router cannot evaluate a prompt, its envelope carries the error and leaves the active-domain list out, so the client keeps what it had (CI1); a domain that returns to the active set is re-injected on every host, one that appends to a transcript included, not only on a client that snapshots (CI2); `abcd update` shows progress only when stderr is a terminal, and saved output stays clean (DR4). BUILD: the installer (`hooks/bootstrap.sh`) writes the previous release tag into binary-meta when it swaps a binary, the session hook stays read-only, and that tag replaces the setup_version comparison of the itd-111 update notice (CJ1); the SHELL domain also teaches a repository's own guard entries, generated the same way as the bundled registry's (CK1); `step` in `abcd implement check` is renamed `stage` too, in the same breaking v0.12.0 (CM1); the fix rounds a run spends before it hands a lane back are a per-run setting beside `--pace`, for example `--fix-rounds`, default 3 (DR1); the dependency-bump intent itd-2609221842494980 ships now, with the first live bump recorded as owed until the person has created the App and its secrets (DR2); only a self-contained agent is sent to a paid provider by default, so the cold-reading positions may go while a file-reading agent such as the intent auditor is refused with the reason, and a central override, a machine-level settings list the person keeps that no agent or repository can change, may name providers that take bundled-context requests for file-reading agents (DR5); a run works in parallel, building several pieces and running reviewers at once up to its agent limit, which is new build-loop work that makes the pacing intent's criterion 6 testable (DR6); an undecided fidelity audit reopens the work, sending the feature back to a lane, and never closes like a pass (DQ1a, iss-2608290820473197); the audit moves before the merge, as part of the build loop's last lane (per ruling AI), and until that loop is ready it stays after the merge and reopens on an undecided or failed verdict (DQ1b, iss-2608290822140563); the filing-time duplicate check covers every route, the promoted inbox report, the consistency pass and the reading ingest, in a lane after remedyRequired (DQ2, iss-2609281911024185); `abcd ahoy` offers to install gh on an explicit yes (DQ3, iss-2609281911024838). KEEP: a promoted inbox report's own remedy is kept but not usable: the sender's suggestion stays in the issue's text for reference, the remedy field is `none (filed automatically)`, and a drain skips the record until a person writes the fix, so the conservative default recorded above on 2026-09-30 stands and is not widened (CL1). The person added to CL1 that when a person comes to write the real fix for such an issue, the agent offers a state-of-the-art research pass first (principle `prefer-sota`), which `commands/capture.md` says under the `capture remedy` verb. The three rulings of 2026-09-29 owed to this ledger by earlier sessions of the run, the ceilings of five and of eight sub-agents and the remedies ruling, already have their entries above, so this entry adds none.
- dcn-26093000000000112026-09-30 — Rulings CJ1 and CJ1b of the product thinker (2026-09-29), applied (lane installerMeta2, autonomous run A; Refs: iss-2609291942520919): an update of the abcd binary is announced once, by whatever swapped it, when the swap completes. CJ1, verbatim: "the INSTALLER (bootstrap.sh) writes the previous release tag into binary-meta when it swaps a binary; the session hook stays read-only. Follow-up: it REPLACES the setup_version comparison." CJ1b, verbatim: "ONCE ONLY. Preferred mechanism (the person's note): couple the notice to the install/update process itself — the installer shows 'updated from X to Y' when it concludes, so it is naturally linked to the installation and the session check need neither show it nor write anything. Only IF that is not possible (e.g. the swap happens where no one sees its output), the session check may write one small 'shown' marker as a single exception to its read-only rule." As built: `hooks/bootstrap.sh` records `previous_tag` in the `binary-meta` it writes at a cache or per-root swap and opens its success notice with `abcd updated from X to Y` (`update.UpdatedFormat`, the one wording, which `abcd update` also opens its receipt with); the session-start setup_version comparison (`ahoy.VersionTransition`) is removed, and ahoy's own `version.upgrade` gap stays. The exception applies: the bootstrap salvage in the UserPromptSubmit, PreToolUse and PreCompact hooks discards its output (`hooks/hooks.json`), so those runs pass `--unseen`, record `transition_unseen=yes`, and the next session start shows the line once and writes `cache/update-shown` in the plugin data directory, its single write. itd-111 criterion 6 is amended to match.
- dcn-26093000000000192026-09-30 — Six entries above appear twice, verbatim: the five dated 2026-09-29 from "Two itd-111 follow-ups from its fidelity audit" to "Ruling J13", and the 2026-09-30 entry beginning "The 2026-09-29 itd111Follow entry above". Two histories carried them in opposite order relative to the 2026-09-30 BU1/BT1 entry (main below them, the implement-loop lanes above them), so joining them in integration 24b-3 kept main's order and repeated the six after BU1 in the lanes' order, the one merge result the append-only gate admits (every parent's lines kept in their order, DA002; no line beyond what the merge base held plus what each side added, DA003). Each pair is one decision recorded once: the first copy is the record, and the second repeats it (recorded by the integration lane of autonomous run A).
- iss-206The version-skew notice promised by itd-105 ('Given an installed surface newer than the newest published binary, when a session starts, then the skew is surfaced') can never fire, and fails silently. itd-105's warrant says the harness names each plugin cache directory for the commit it was cloned from; that holds only as a 12-character SHORT sha. Observed .binary-meta from the first manual install (2026-08-10): plugin_root_basename=872f0a1e6aab, which is exactly 'git rev-parse --short=12 HEAD', and plugin_sha=unknown. hooks/bootstrap.sh gates plugin_sha on exactly 40 hex chars, and internal/surface/cli/skew.go resolvedSHA() requires 40 again, so plugin_sha is permanently 'unknown' and binarySkewNotice() returns "" forever on every machine. This is precisely the failure the comment at hooks/bootstrap.sh anticipated and armed a diagnostic for — recording the raw ungated plugin_root_basename beside the gated field so that 'why has the skew notice never fired' has an answer in the file rather than only in a comment. The diagnostic worked exactly as designed; the gate is what is wrong. Fix direction: accept 7-40 hex in both places and compare on the shorter value's prefix length, since one side is a short sha and the other a full one.
- iss-225ahoy skill doc says the PATH entry defaults to ~/.local/bin (created when absent), but the install engine targeted /usr/local/bin/abcd and stopped when it was unwritable instead of falling back; either the doc or the engine's bin-dir resolution has drifted
- iss-227installDevShim silently swallows failures: the os.Remove, MkdirAll, and WriteFileAtomic error paths are bare returns (internal/core/ahoy/apply.go, installDevShim), so a failed shim write yields status=partial or clean with no note explaining what was not done or why; only the success path calls a.note
- iss-228The plugin-root binary (repo-root abcd -> bin/abcd-darwin-arm64) sat a month stale after iss-171 merged, so the ahoy skill's first resolution rung reported pre-iss-171 gaps (wrong target, missing --bin-dir) with no staleness signal; the skill and detection have no guard that warns when the plugin-root binary predates the source tip in a source checkout
- iss-230offer a one-tap micro-prompt channel (survey pattern) for maintainer decisions: when abcd needs a single small human ruling (a yes/no, a pick from two or three options), it renders one micro-prompt the facilitator can answer in one tap instead of a full interview session. Lifted out of itd-111's open questions during the 2026-08-15 forward-plans grill so the staleness intent keeps a clean scope; strike the open question from itd-111 when it goes through intent plan. Facilitator-touch item, homed in the facilitator-experience plan.
- iss-232itd-111 latent coupling: the ahoy install staleness refusal keys on the binary's vcs.revision (via vintage current-from-BuildInfo). Release binaries are VCS-stamped today (release.yml runs make build VERSION=<tag> from a git checkout, -buildvcs=auto default), so pinned users have a determinable vintage and proceed. But if a future release build ever sets -buildvcs=false or builds from a .git-less source archive, Known flips false for every pinned user and ahoy install begins REFUSING until they pass --allow-stale-binary. Add a guard/test on the release build config asserting the shipped binary carries vcs.revision, so this coupling cannot silently break. Non-blocking; surfaced by the independent ruthless review of feat/itd-111-staleness. NOTE: captured in the iss-200 design worktree to avoid an allocator collision (iss-231 taken there); it is an itd-111/release-build follow-up, not iss-200 work.
- iss-2609252053576085The build's open_questions check (intent.OpenQuestions, read by internal/core/implement/loop/check.go) counts every list item under '## Open Questions' as a question whatever it says, so it refuses records that follow the record's own settled convention: 12 of 50 planned intents carry list items there, and some are settled by their own words, itd-111's section opening '_All resolved or explicitly deferred at planning_' and itd-93's '_All four resolved_', itd-76's item marked '**explicitly deferred**', itd-60's items marked '**Resolved', itd-117's '**Deferred**' items. 'abcd build next' (itd-2609211116005482) would meet refusals nobody planned, and the ruling lives only in a lane report. Keep the check fail-closed, but read the markers the record actually uses.
- iss-2609290448510918The predecessor-store qualifier sweep reaches past the six intents iss-2609261536147903 named: outside drafts/ and those six, 192 citation sites across about forty intents name a spc-N at or below spc-70 that is not the citing intent's own spec, with no '(predecessor store)' on the line. Some are live cross-references (the cold-reading family's intents cite each other's live specs), and some are the predecessor store's (itd-4, itd-6, itd-29, itd-47 and itd-49 describe pre-rebuild work in its terms), so each site needs the same reading against the live spec it collides with; the specs charter's Two spc-N Namespaces section is the rule. Found while sweeping the pattern of iss-2609261536147903 in lane drainDrift3; a census script over intents/{planned,shipped,disciplines} reproduces the count.
- iss-2609291942515955itd-111 acceptance criterion 8, as amended at the close, says the product thinker's 2026-09-29 ruling (an agent's check on macOS plus the Linux CI run stand in for the per-platform check by a person) is recorded in .abcd/work/DECISIONS.md under that date, but no line of DECISIONS.md carries it at 8ffb6d069 or on main: the three 2026-09-29 entries concern the pre-commit sources refresh, a guidance principle and the five-agent ceiling. The shipped intent cites a ruling the ledger does not hold. Either the ruling is appended (the staleClose pull-request body says a separate rulings lane appends it) or the amendment cites where the ruling actually lives.
- iss-2609291942520919itd-111 acceptance criterion 6 (after provisioning fetched a new pinned binary, the next session reports the version transition performed) is delivered as a per-repo comparison: ahoy.VersionTransition (internal/core/ahoy/vintage.go:220-234) compares core.Version against meta.setup_version in the repo's .abcd/config.json, written by ahoy install, and reports no transition when either side is a dev build. spc-22 stated the reference would be recorded beside the plugin-cache metadata. So a repo never set up by ahoy install, or a dogfood dev build, sees no transition report after provisioning fetched a new binary, which is the scenario the criterion describes. Either the plugin-cache record the spec named is the reference to consult, or the narrowing is recorded on the criterion.
- iss-2609291942529461itd-111 acceptance criterion 2 promises that abcd ahoy install run through a stale binary refuses before any write, but under an explicit --bin-dir the writability probe runs first: install (internal/core/ahoy/apply.go:91) calls resolveInstallTarget, whose dirWritable (internal/core/ahoy/store.go:431) creates and removes a .abcd-write-probe-* temp file in the named directory or its nearest existing parent, and only then (apply.go:157) does staleBinaryRefusal fire. The comment at apply.go:267 says the probe runs without creating anything, which store.go contradicts. Net state is unchanged, but a stale or unknown-vintage binary touches the filesystem before it refuses. Move the refusal above the probe, or amend the comment and the criterion's wording.
- iss-2609292057441479itd-111 acceptance criteria 3 and 5 name command spellings the CLI no longer answers to: AC3 says the vintage report comes from 'abcd version' and AC5 says the explicit release check is 'abcd version --check' (design decision 6 and its open question say the same). Since itd-2609212130136102 consolidated the verbs, the report is 'abcd --version' and the check is 'abcd update --check'; both old spellings refuse with a moved notice naming the new one. The behaviour each criterion asks for is present at the new spelling, so the shipped criteria read true only once the record says which surface meets them. adr-38 tier 2 also cites 'abcd version --check' as an example of an explicit ask; the rule it states still holds.
- iss-2610021502084298No check meets the retired role word in an intent's press release at the record: the docs-lint rule roles/retired-role-word roots at the docs, the command pages and the rules, so a shipped or planned intent's press release carrying it is refused only when the site composer renders it as the landing page's hero quote, which is how itd-60's first MET audit broke the site-render gate (iss-2610021446271464). After the R5 reword of itd-60, thirteen shipped press releases still carry the word (itd-65, itd-66, itd-67, itd-69, itd-73, itd-74, itd-93, itd-102, itd-111, itd-114, itd-125, itd-2609111003026787, itd-2609212137129937), so the next MET audit ingested for any of them refuses the render again on a branch that only recorded an audit; the product thinker's ruling R5 (2026-10-02) accepted that a future slip breaks the build and did not ask for this check. Related to iss-2610020731591808, which names the brief, the principles and the personas registry as swept but unguarded.
- iss-2610040758303107Bare abcd ahoy prints 'staleness: up to date' when the running binary matches its own install's pin, a comparison made from disk alone by design (itd-111; internal/core/ahoy/vintage.go, Staleness), and the line says neither what it compared against nor that abcd update --check is the verb that asks for the newest release. A downstream user on v0.9.0, several releases behind, read 'up to date' as current and kept working against bundled guidance that predated the retirement of phases and milestones.
- itd-22abcd Reaches Any Harness Through an Adaptor
- itd-84An Intent Is Decomposed Before It Is Filed, And A Reversal Is Only Ever Flagged
- itd-105A fresh plugin install just works: the hook binary survives install and every update
- itd-109Acceptance Criteria Verify Themselves, And The Manual Rest Renders For A Human
- itd-2609151138388536Every Decision Is Its Own Record, and the Log Stops Conflicting
- itd-2609212130136102abcd's verbs consolidate: modes become flags and five checks become one lint
- itd-2610301346085246An agent learns how to call abcd from the abcd it runs
- spc-2610031342374947when-a-person-runs-abcd-s-check-on-purpose-abcd-also-says
- spc-2610301431075964An agent learns how to call abcd from the abcd it runs