Development record
**abcd teaches repo agents the shell commands they must never run — and blocks them when they try.** An agent that runs `cd scratch && rm -rf *` is one failed `cd` away from deleting the working tree, and the facilitator watching the session usually has no way to know that. abcd ships a hazard registry: each entry a dangerous command pattern, its severity, the safe successor, and a plain-language why. One registry, two planes. The rules loader injects the matched safety rules before shell-heavy work, so the agent is taught the safe form up front; a deterministic guard — a core verb any harness hook can call — refuses a matching command at execution time and replies with the safe successor, so the block itself is the lesson. Hosts without hook support still get the teaching plane; hosts with hooks get both, wired in at install. The registry grows from reality: a facilitator who sees something scary needs to know exactly one move — capture it — and recurring captures promote patterns into the bundled defaults, each entry shipping with a fixture that proves its guard fires. "I couldn't have told you why that command was dangerous," said Nia, a facilitator. "I didn't have to. The guard refused it, told the agent what to run instead, and told me why in words I understood. The one time something new scared me, I captured it and moved on."
Press Release
abcd teaches repository agents which shell commands are dangerous — and blocks them at the moment one is about to run. abcd ships a hazard registry in which each entry pairs a dangerous command pattern with its severity, its safe successor, and a plain-language reason. One registry drives two planes. Before shell-heavy work the rules loader injects the matching safety rules, so the agent learns the safe form up front; at execution time a deterministic guard — a core verb any harness hook can call — refuses a matching command and answers with the safe successor, so the block itself is the lesson. A host without hook support still gets the teaching plane; a host with hooks gets both, wired in at install. The registry grows from reality: a facilitator who sees something alarming has exactly one move — capture it — and recurring captures promote patterns into the bundled defaults, each entry shipping with a fixture that proves its guard fires.
Why This Matters
abcd teaches repo agents the shell commands they must never run — and blocks them when they try. An agent that runs cd scratch && rm -rf * is one failed cd away from deleting the working tree, and the facilitator watching the session usually has no way to know that. abcd ships a hazard registry: each entry a dangerous command pattern, its severity, the safe successor, and a plain-language why. One registry, two planes. The rules loader injects the matched safety rules before shell-heavy work, so the agent is taught the safe form up front; a deterministic guard — a core verb any harness hook can call — refuses a matching command at execution time and replies with the safe successor, so the block itself is the lesson. Hosts without hook support still get the teaching plane; hosts with hooks get both, wired in at install. The registry grows from reality: a facilitator who sees something scary needs to know exactly one move — capture it — and recurring captures promote patterns into the bundled defaults, each entry shipping with a fixture that proves its guard fires. "I couldn't have told you why that command was dangerous," said Nia, a facilitator. "I didn't have to. The guard refused it, told the agent what to run instead, and told me why in words I understood. The one time something new scared me, I captured it and moved on."
Acceptance Criteria
- Given the guard hook cannot execute the abcd binary, when any command runs, then the guard fails OPEN with an unmissable in-session warning, and guard health is reported by ahoy status — never a silent no-op, never a bricked session.
- Given a command matches a blocker-tier registry entry, then it is refused with the safe successor as the block message; warn-tier matches pass with the warning injected; no in-session override exists — the only escape is a committed per-repo config override.
- Given a hazard pattern appears inside a quoted string argument (for example a ledger capture whose text mentions a dangerous command), when the guard evaluates, then it does not fire: matching is shell-token-aware and applies in command position only, including cd-chain structure across compound separators.
- Given a registry entry is proposed for the bundled defaults, then it ships with known-bad and known-good fixtures (known-good at least 40% of its corpus) and clears a declared true-negative-rate floor before admission; string payloads inside eval or shell -c are a documented v1 gap, not a silent one.
Open Questions
- The registry config's file home (extend rules.json with a guard domain vs a dedicated committed file) — a spec-time decision.
- Which non-shell tool calls (if any) the guard family later covers.
Grill Settlements (2026-07-27)
- Fail-open-loud on guard breakage; blocker/warn tiers mirror the docs-lint family; overrides are committed and reviewable only.
- Shell-token-aware matching in command position was chosen precisely because a raw-regex guard would have blocked this repo's own incident-capture command — the known-good corpus is not optional.
Audit Notes
Fidelity review — receipt rcp-c09c73d6adc5 (verifier abcd:intent-fidelity-reviewer claude-fable-5).
Provenance: abcd:intent-fidelity-reviewer@claude-fable-5 · rubric_hash sha256:e96fe6d7a3f6bfe3908171b52f5bd42de194298ead0093ffab6b377adc9dd3bc · prompt_hash sha256:f4de8b23c6b6c7a54133bf2042b6b89829c579412cf26f1f66a65c36d57aa6fe Input attestations: diff:main..feat/itd-103-shell-hazard-registry (PR #156, merge 49cffa05): internal/core/guard/, internal/surface/cli/guard.go, internal/core/ahoy/guard_health.go, commands/abcd/guard.md, hooks/hooks.json@sha256:49cffa05e6216d9cbb29dc601961464781e6541d; rubric:.abcd/.work.local/reviews/rcp-c09c73d6adc5.request.md@sha256:e96fe6d7a3f6bfe3908171b52f5bd42de194298ead0093ffab6b377adc9dd3bc;
Acceptance rollup: MET 3 · MET_WITH_CONCERNS 1 · NOT_MET 0 · INCONCLUSIVE 0
Per-criterion verdicts:
- ac-1 — MET: Every non-decision path in the hook adapter routes through one failOpen that exits 1 (non-zero, non-blocking) and prints 'NOT CHECKED — ... runs UNGUARDED'; the hooks.json wrapper catches even an unexpected exit and fails open loudly; and guard health is a first-class ahoy field wired into the status render — never a silent no-op, never a bricked session. evidence: internal/surface/cli/guard.go:145 — ""abcd guard: NOT CHECKED — "+format+". This command runs UNGUARDED.\n" ... return &exitError{Code: 1}" evidence: hooks/hooks.json:13 — "[ $s -eq 0 ] || [ $s -eq 1 ] || [ $s -eq 2 ] || { echo "abcd guard: FAILED TO RUN (exit $s) — shell commands run UNGUARDED ..." >&2; exit 1; }" evidence: internal/core/ahoy/detect.go:78 — "res.Guard = detectGuardHealth(abs, pluginRoot, pluginOK)" evidence: internal/surface/cli/cli.go:1453 — "fmt.Fprintf(w, " guard: %s\n", guardHealthLine(res.Guard))"
- ac-2 — MET_WITH_CONCERNS: A blocker refuses with the successor built into the block message and a warn passes with the warning surfaced, and the only disable route is the per-repo .abcd/guard.json config (no in-session flag or env bypass exists) — CONCERN: that override takes effect from the WORKING TREE before it is committed or reviewed (config.Load reads .abcd/guard.json off disk; iss-147 records this exactly), so it is reviewable-in-a-diff but the criterion's 'committed' qualifier is not actually enforced. evidence: internal/core/guard/guard.go:300 — "return fmt.Sprintf("%s by the abcd guard (%s): %s Run instead: %s", lead, e.ID, e.Why, e.Successor)" evidence: internal/surface/cli/guard.go:198 — "case guard.VerdictBlock: ... fmt.Fprintln(cmd.ErrOrStderr(), termsafe.Sanitize(dec.Message)); return &exitError{Code: 2}" evidence: internal/core/guard/config.go:34 — "data, err := fsutil.ReadGuarded(filepath.Join(repoRoot, ".abcd", "guard.json"), maxGuardFileBytes)" evidence: .abcd/work/issues/open/iss-147-guard-load-reads-abcd-guard-json-from.md:4 — "guard-load-reads-abcd-guard-json-from-the-working-tree"
- ac-3 — MET: tokenize honours shell quoting and recognises operators outside quotes only, so a hazard inside a quoted argument stays one non-command token; matching runs in command position via commandOf/matchSegment; the cd-chain is enforced by AfterCD + precededByCD scanning earlier segments of the SAME chain across &&/;/||/| separators; and TestIncidentCaptureIsKnownGoodFixtureOne pins this repo's own quoted 'cd scratch && rm -rf *' capture as a known-good that must not fire. evidence: internal/core/guard/tokenize.go:23 — "Operators and comments are recognised OUTSIDE quotes only, which is exactly what keeps a hazard named inside a quoted argument from ever reaching command position." evidence: internal/core/guard/match.go:41 — "if matchSegment(p, s) && (p.AfterCD == nil || !*p.AfterCD || precededByCD(segs[:i], s.chain))" evidence: internal/core/guard/match.go:51 — "func precededByCD(before []segment, chain int) bool { ... if cmd, _ := commandOf(s); cmd == "cd"" evidence: internal/core/guard/fixtures_test.go:104 — "if !containsAll(first, "capture", "cd scratch && rm -rf *")"
- ac-4 — MET: The admission gate TestBundledEntriesPassAdmissionGate fails the build unless every bundled entry ships known-bad AND known-good fixtures, known-good is at least 40% of the corpus (10good < 4(good+bad)), and every known-good fires nothing (a declared 100% true-negative floor); the 6 shipped entries all clear it (e.g. rm-rf-after-cd-chain 8/17 good = 47%); and the eval / sh -c string-payload gap is documented, not silent, in the tokenizer, the CLI reference, and the surface brief.
evidence: internal/core/guard/fixtures_test.go:41 — "if 10good < 4(good+bad) { t.Fatalf("entry %s: known-good is %d of %d fixtures, below the 40%% floor" ...)"
evidence: internal/core/guard/fixtures_test.go:66 — "if d.Verdict != VerdictAllow { t.Fatalf("known-good %q fired %v ...; the true-negative floor is 100%%" ...)"
evidence: internal/core/guard/tokenize.go:26 — "v1 GAP (documented, not silent): ... A command string carried as a DATA argument —
sh -c '<payload>',eval '<payload>'... its payload is never parsed" evidence: .abcd/development/brief/04-surfaces/17-guard.md:86 — "a command string handed to an interpreter (eval,sh -c)"
Gap audit:
- honoured:
- A deterministic guard, a core verb any harness hook can call, refuses a matching command and replies with the safe successor so the block itself is the lesson
evidence: internal/core/guard/guard.go:246 — "func (r Registry) Check(command string) (Decision, error)"
evidence: internal/surface/cli/guard.go:122 — "newGuardHookCommand builds
guard hook: the adapter between a host's pre-tool-use hook payload and the core decision" - Two tiers mirroring the docs-lint family: a blocker refuses, a warn passes with the warning attached evidence: internal/core/guard/guard.go:19 — "TierBlocker = "blocker" ... TierWarn = "warn"" evidence: internal/surface/cli/guard.go:200 — "case guard.VerdictWarn: fmt.Fprintln(cmd.ErrOrStderr(), termsafe.Sanitize(dec.Message)); return nil"
- Each entry ships a fixture that proves its guard fires, enforced as a build-time admission gate evidence: internal/core/guard/fixtures_test.go:19 — "func TestBundledEntriesPassAdmissionGate" evidence: internal/core/guard/defaults/guard.json:1 — "6 entries, each carrying known_bad + known_good fixtures"
- The known-good corpus is not optional: this repo's own incident-capture command must not be blocked evidence: internal/core/guard/fixtures_test.go:95 — "func TestIncidentCaptureIsKnownGoodFixtureOne"
- A deterministic guard, a core verb any harness hook can call, refuses a matching command and replies with the safe successor so the block itself is the lesson
evidence: internal/core/guard/guard.go:246 — "func (r Registry) Check(command string) (Decision, error)"
evidence: internal/surface/cli/guard.go:122 — "newGuardHookCommand builds
- diverged:
- The only escape is a COMMITTED per-repo config override — delivered as a per-repo .abcd/guard.json read from the working tree, so it activates before commit/review (reviewable in a diff, but 'committed' is not enforced) evidence: internal/core/guard/config.go:34 — "fsutil.ReadGuarded(filepath.Join(repoRoot, ".abcd", "guard.json") ...)" evidence: internal/surface/cli/guard.go:275 — "the file is read from the working tree, so this can be true before anyone has reviewed the edit that made it true (iss-147)"
- missing:
- Press-release 'two planes': the teaching plane — the rules loader injecting the matched safety rules before shell-heavy work — is not wired in this delivery; only the execution-time guard plane ships (a guard/safety rules domain does not exist in internal/core/rules/)
evidence: .abcd/development/intents/shipped/itd-103-abcd-teaches-repo-agents-the-shell.md:13 — "The rules loader injects the matched safety rules before shell-heavy work"
evidence: internal/core/rules/rules.go:401 — "no guard/safety/hazard domain is registered — the only
guardhere is the stemming short-token guard"
- Press-release 'two planes': the teaching plane — the rules loader injecting the matched safety rules before shell-heavy work — is not wired in this delivery; only the execution-time guard plane ships (a guard/safety rules domain does not exist in internal/core/rules/)
evidence: .abcd/development/intents/shipped/itd-103-abcd-teaches-repo-agents-the-shell.md:13 — "The rules loader injects the matched safety rules before shell-heavy work"
evidence: internal/core/rules/rules.go:401 — "no guard/safety/hazard domain is registered — the only
Changed on 2026-09-29 by the product thinker's ruling J10 of that day (the decision dcn-2609290000000014 landing with this change), recorded as iss-151: the teaching plane the headline promises, missing from the delivery reviewed above, is built. The rules loader bundles a SHELL domain generated from the same bundled hazard registry the guard reads, one rule per entry (the command, whether the guard refuses or warns, the why and the safe successor) recalled by the registry's command heads, so a prompt about shell-heavy work is taught the safe form before a command runs, on a host without hooks as on one with them. The criteria above stand as shipped; none of them concerned the teaching plane, and no ADR's text changes.
Changed on 2026-09-30 by the product thinker's ruling CK1 of 2026-09-29 (the decision dcn-2609300000000008 landing with this change), recorded as iss-2609300756163382: the SHELL domain teaches the registry the guard enforces in the repository, not the bundled registry alone. An entry a repository adds in its .abcd/guard.json is taught by the same generator as the bundled ones, its rule marked (repo) after its entry id, and a guard file the guard refuses is named on every load and never taught. The criteria above stand as shipped, and no ADR's text changes.
Frontmatter
| id | itd-103 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | minor |
.abcd/development/intents/shipped/itd-103-abcd-teaches-repo-agents-the-shell.md
open on GitHub ↗ · commit history ↗
Referenced by3
- adr-42The guard's parse layer is a mistake filter, not a security boundary — matching is two-tier, and completeness is abandoned as a goalrelated
- adr-2610091150447054The guard reads a script the command names before it judges the commandrelated
- spc-16abcd-teaches-repo-agents-the-shell-commands-they-must-neverimplemented by
mentions19
- dcn-26072700000000052026-07-27 — GRILLED itd-101/102/103/104 to settlement (six decisions, each now in its intent's Grill Settlements + acceptance criteria): itd-103 guard fails open loudly, blocker/warn tiers with committed-only overrides, shell-token-aware command-position matching with an itd-81-style TNR-floored corpus; itd-101 refresh is manual-with-nagging (scheduled CI later, own sign-off), staleness warns at 180d and blocks releases at 365d, human verifications age on the same clock; itd-102 identity lives in a parseable markdown block (config points at it), check is warn-tier and never rewrites; itd-104 ideate is an optional verb, never a pre-capture gate, with a fresh-context off-policy adversary. All four drafts are grill-settled and await intent plan.
- dcn-26072700000000102026-07-27: itd-103 guard overrides live in dedicated committed .abcd/guard.json, not a rules.json domain — structured entry schema, and the rules kill switch must not silently disable a safety guard (spc-16).
- dcn-26072700000000112026-07-27: itd-103 guard wiring — `abcd guard hook` is a sub-verb of the USER-facing `guard` verb, not a fifth entrypoint under the hidden `hook` subtree. Reason: guard health has to be legible (AC 1), and a hidden adapter documents nothing; the other `hook` entrypoints are injection transport with no user-facing counterpart, this one is the same decision the user can also ask for by hand. Consequence: it appears in the generated CLI reference and must stay host-agnostic in its prose.
- dcn-26072700000000122026-07-27: itd-103 guard wiring — the two front doors disagree on ONE case by design. A guard that cannot be evaluated (unparsable command, registry that will not load) is a FAULT for `guard check` (exit 2: a script must never read silence as clearance) and fail-OPEN for `guard hook` (exit 0 + loud stderr: a broken guard must never stop a session). Rejected: one shared behaviour, which would either brick sessions or teach scripts that silence means safe.
- dcn-26072700000000132026-07-27: itd-103 guard wiring — the fail-open-loud shim is a shell wrapper in the plugin's `hooks/hooks.json` PreToolUse entry (pass through only the binary's own 0/2; everything else warns UNGUARDED and allows), not Go code. The failure it guards against is the Go binary not running at all, so it cannot live in Go. Tested by executing the committed manifest string under /bin/sh against a fake plugin root.
- dcn-26092500000000162026-09-25 — Correction to the two entries above on the shell guard's unknown word (fix round 4 of lane guard, run A, closing review4-guard). Over-block (2) of the round-3 entry is wider than it says: any command word whose basename ends in a substitution (`$(date) x`, `"$(which go)" build ./...`, `$(command -v X) anything`, `/usr/bin/$(x) y`) followed by any one operand blocks, because killall-by-name and pkill-by-pattern name only a program and an operand; and the verdict carried killall's own lesson ("stop it by pid"). Where the only entries a command fires are ones its unknown program name can be, the verdict is now reported under the reserved id `program-name-unknown`, in the substitution family, naming the entry the line reads as and the way past, spelling the program's name; the entries stay in its matches, and an entry the line does name outranks it. A narrower `$(which NAME)`/`$(command -v NAME)` reading was weighed and not taken. Three further rulings of the round. (a) A line the tokenizer cannot split is BLOCKED on the hook (`command-unparsable`), not failed open: where the tokenizer is right no shell runs the line either, so the block costs nothing, and where it is wrong (the `$'\c'` misreading this round fixed, which swallowed the string's closing quote) a fail-open is a bypass of every blocker by construction. itd-103 ac-1 names the binary failing to run, which still fails open loudly, as do an unreadable payload and a registry that will not load; the check verb keeps reporting the error as a fault (exit 2); the 961-line repository corpus holds no unparsable line. (b) An unquoted here-document body is read as bash expands it: the command substitutions in it are commands of the redirecting command's chain and its text is data, and a delimiter written with a backslash (`<<\EOF`, `<<E\OF`) is quoted, as bash reads it. (c) A `${…}` in which a substitution's output lands is unknown from its `${` on, so a word that is wholly such an expansion falls under allow (1) of the round-2 entry exactly as `"$(x)"` does, and `git push "${X:-$(echo --force)}" origin main` is not seen. The plain-variable half (`$X`, `${X:-git}`, a `${…}` holding no substitution) stays iss-2609251824244354, deferred with its reason; it is not seen as the program name, as a flag, or inside a payload.
- dcn-26092900000000112026-09-29 — The product thinker answered the twenty-six Group J rulings (plan or close), J1 to J26 (recorded by lane recGroupJ of autonomous run A, for orchestrator abcd-fc). The answers are kept verbatim in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29.md`, section "Group J", dated 2026-09-29, and are restated here because that file is not committed. PLAN: the installer script becomes a minimal starter with an ownership-checked hand-over, planned as a new intent with its planning interview owed (J2, iss-377, draft itd-2609292106557115); token metering and size classes are planned as one intent (J3, iss-2608301744251874 and iss-2608301856299268), and session token accounting in the history store joins that metering intent (J4, iss-2608220150157508; all three in draft itd-2609292107351737); the sources tooling moves into abcd's core with ingest and consult as abcd verbs (J5, iss-27, whose corpus half itd-76 already delivered), and the backfill of the placeholder source entries is part of that intent (J6, iss-55; both in draft itd-2609292108089653); the grill hands off to an installed external interview skill and falls back to its own (J11, iss-165, draft itd-2609292108373494); an opt-in local-model prompt sanitiser is planned (J14, iss-2608261543489261, draft itd-2609292109005937); decision-to-transcript links are planned, with the anchor kind and the link's home settled in the planning interview owed for draft itd-171 (J16, iss-2608290819228175); the dredge synthesis, its write-ups as their own memory source class, is planned together with draft itd-25, whose planning interview is owed (J19, iss-2609211905340006); one provenance register across ingest and vendoring is planned together with draft itd-26, whose planning interview is owed (J20, iss-2609211905346507); release retention, keeping the newest release per version line, is automated by planning draft itd-70 (J21, iss-282); a rules-backend seam with an opt-in CARL adapter is planned, the native loader staying the default (J22, iss-64, draft itd-2609292109214516); core-owned managed pre-commit gates are planned with draft itd-62 (J23, iss-84); a behavioural end-to-end scenario suite per verb family is planned (J24, iss-48, draft itd-2609292109475690). Every draft stays in drafts/ until a person plans it, and each source issue links its draft and names that planning interview in its deferral. BUILD: the teaching plane, the rules-loader safety domain generated from the hazard registry, is built as a lane, the registry being the single source (J10, iss-151 and itd-103; a code lane owns it). RULED DETAILS: `source_kind` carries both, as two separate labels, one for the tool and one for the route (J13, iss-2608230752354928); the prompt router's removal signal is the full list of active domains every time, a domain's absence meaning it has stopped (J15, iss-2608261550580260); setup offers an outside AI service (for example OpenRouter) at install, skippable, once the API adapter ships (J18, iss-2609081951416843, which waits on the API adapter); the salvage-hook timeout is 120 seconds, the wait must show a counter or a message while it runs and never stall silently, and every per-event timeout is pinned in a test (J25, iss-323). J13, J15 and J25 are carried by code lanes. CLOSE: the quick-tunnel preview protocol is closed unless the need recurs (J26, iss-2608230617385431, wontfix with that reason). PARKED ON A TRIGGER: agent payment protocols are kept under watch, not now, revisited as the protocols mature (J8, iss-138); the findings-only skill waits for the skill format to be versioned and stewarded (J9, iss-139); the disposition worksheet as an intent and a site page waits until after the first study (J17, iss-2609021815529020). DECIDE LATER: the Homebrew tap stays parked (J1, iss-380); pluggable search back ends (J7, iss-26); forge-backed record numbering (J12, iss-2608210737264758).
- dcn-26092900000000142026-09-29 — Ruling J10 of the product thinker, applied (lane teachPlane, autonomous run A; Refs: iss-151): itd-103's teaching plane is built rather than dropped from the intent (the ruling, verbatim: "BUILD IT (a lane; the registry is the single source)"). The rules loader bundles a `SHELL` domain generated from the bundled hazard registry the guard reads: one rule per entry and one recall term per command head, plus a fixed five-word list for shell work in general (`bash`, `command line`, `force push`, `shell`, `zsh`); no second copy is written in the bundled `rules.json`, and a test fails the build if the domain and the registry part. Two consequences taken by the lane rather than the ruling: the domain is built from the bundled registry only, never from a repo's `.abcd/guard.json`, because the two features keep independent switches (spc-16, "Config home"); and `SHELL` joins `COMMITTING`, `LOAD` and `PII` as a guardrail domain, so an override that withholds a bundled lesson is named on every load.
- dcn-26092900000000202026-09-29 — The product thinker answered the twenty-six Group J rulings (plan or close), J1 to J26 (recorded by lane recGroupJ of autonomous run A, for orchestrator abcd-fc). The answers are kept verbatim in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29.md`, section "Group J", dated 2026-09-29, and are restated here because that file is not committed. PLAN: the installer script becomes a minimal starter with an ownership-checked hand-over, planned as a new intent with its planning interview owed (J2, iss-377, draft itd-2609292106557115); token metering and size classes are planned as one intent (J3, iss-2608301744251874 and iss-2608301856299268), and session token accounting in the history store joins that metering intent (J4, iss-2608220150157508; all three in draft itd-2609292107351737); the sources tooling moves into abcd's core with ingest and consult as abcd verbs (J5, iss-27, whose corpus half itd-76 already delivered), and the backfill of the placeholder source entries is part of that intent (J6, iss-55; both in draft itd-2609292108089653); the grill hands off to an installed external interview skill and falls back to its own (J11, iss-165, draft itd-2609292108373494); an opt-in local-model prompt sanitiser is planned (J14, iss-2608261543489261, draft itd-2609292109005937); decision-to-transcript links are planned, with the anchor kind and the link's home settled in the planning interview owed for draft itd-171 (J16, iss-2608290819228175); the dredge synthesis, its write-ups as their own memory source class, is planned together with draft itd-25, whose planning interview is owed (J19, iss-2609211905340006); one provenance register across ingest and vendoring is planned together with draft itd-26, whose planning interview is owed (J20, iss-2609211905346507); release retention, keeping the newest release per version line, is automated by planning draft itd-70 (J21, iss-282); a rules-backend seam with an opt-in CARL adapter is planned, the native loader staying the default (J22, iss-64, draft itd-2609292109214516); core-owned managed pre-commit gates are planned with draft itd-62 (J23, iss-84); a behavioural end-to-end scenario suite per verb family is planned (J24, iss-48, draft itd-2609292109475690). Every draft stays in drafts/ until a person plans it, and each source issue links its draft and names that planning interview in its deferral. BUILD: the teaching plane, the rules-loader safety domain generated from the hazard registry, is built as a lane, the registry being the single source (J10, iss-151 and itd-103; a code lane owns it). RULED DETAILS: `source_kind` carries both, as two separate labels, one for the tool and one for the route (J13, iss-2608230752354928); the prompt router's removal signal is the full list of active domains every time, a domain's absence meaning it has stopped (J15, iss-2608261550580260); setup offers an outside AI service (for example OpenRouter) at install, skippable, once the API adapter ships (J18, iss-2609081951416843, which waits on the API adapter); the salvage-hook timeout is 120 seconds, the wait must show a counter or a message while it runs and never stall silently, and every per-event timeout is pinned in a test (J25, iss-323). J13, J15 and J25 are carried by code lanes. CLOSE: the quick-tunnel preview protocol is closed unless the need recurs (J26, iss-2608230617385431, wontfix with that reason). PARKED ON A TRIGGER: agent payment protocols are kept under watch, not now, revisited as the protocols mature (J8, iss-138); the findings-only skill waits for the skill format to be versioned and stewarded (J9, iss-139); the disposition worksheet as an intent and a site page waits until after the first study (J17, iss-2609021815529020). DECIDE LATER: the Homebrew tap stays parked (J1, iss-380); pluggable search back ends (J7, iss-26); forge-backed record numbering (J12, iss-2608210737264758).
- dcn-26093000000000082026-09-30 — Ruling CK1 of the product thinker (2026-09-29), applied (lane teachRepoGuard, autonomous run A; Refs: iss-2609300756163382): the `SHELL` domain teaches a repository's own guard entries, generated the same way as the bundled registry (the ruling, verbatim: "YES, generated the same way as the bundled registry"). This replaces the consequence the entry for ruling J10 above recorded, that the domain is built from the bundled registry only. Every rules load rebuilds `SHELL`, before any `rules.json` layer lands on it, from the registry `abcd guard` enforces in the repository (`guard.LoadRepo`: the bundled entries merged with `.abcd/guard.json`), through the same generator. Taken by the lane rather than the ruling: a lesson whose words are the repository's (an entry the file adds, or a bundled entry whose tier, pattern, why or successor it changes) carries `(repo)` after its entry id, so provenance stays visible while the domain itself stays bundled to every other contract; a guard file the guard refuses is refused here too, `SHELL` teaching the registry the guard falls back to and a load note naming the file and the reason on stderr from `abcd rules` and the hook, rather than failing the whole rule set (which would silence `PII` and `COMMITTING` over one broken guard file); the guard file's `disabled` switch does not silence the teaching, which keeps its own switches in `rules.json` (spc-16, "Config home").
- iss-80Record id allocators (itd-N, spc-N, iss-N) are branch-local: parallel agents on separate branches each scan for max+1 and mint the SAME id. Two intents both claimed itd-82 and both merged to main (PRs #46, #47). The iss-N allocator hit the same class before (iss-77 collision, manually renumbered to iss-79; class recorded as iss-74). Resolving a collision forces a renumber, which breaks the record's stated 'ids are capture-stable, never renumbered' invariant -- so the minting scheme, not the renumber, is the defect. This capture is the MINTING half: a collision-free allocation scheme is needed (forge-minted / random-suffix / timestamp / reserve-registry -- SOTA under research). The DETECTION half is armed separately: intent_lifecycle now blocks duplicate intent ids.
- iss-146guard-bash false positive class: a compound command chaining a gh write verb with a LOCAL command taking an absolute path argument (gh pr merge ... ; git worktree remove /abs/path) is blocked by the line-level privacy regex, which cannot see that the path never reaches GitHub. Second guard incident of 2026-07-27 (the first, blocking --no-verify, was a true positive). Both are fixture material for itd-103's calibration corpus: this one is a known-good case the TNR floor must protect; the workaround (split the compound) is recorded here so the friction is not silent.
- iss-147guard.Load reads .abcd/guard.json from the WORKING TREE, so a disabled:true (or a retiered blocker) takes effect on the very next command, before anyone reviews it — spc-16 and the shipped docs both say the only escape is a committed, reviewable override, and nothing enforces the committed half. Reachable in one move: an agent writes .abcd/guard.json and the guard itself allows that write. Mitigated at the front door in itd-103 wiring (a disabled registry now warns UNGUARDED on every command, and abcd ahoy reports OFF) but not enforced. Proper fix is core-side: refuse a disabled:true that is not in HEAD, or drop the committed claim from spc-16. Found by the security reviewer on the itd-103 wiring branch.
- iss-148guard registry coverage gaps found while wiring itd-103 (registry content, not matching semantics): a push whose refspec carries a leading plus is a force in disguise and no entry describes it; xargs, timeout and exec are absent from the matcher's wrappers set, so a hazard launched through one of them is not seen; a backtick command substitution is not followed, while the dollar-paren form is; and a wrapper that IS in the set defangs an entry the moment it carries its own flags, because only the wrapper name is stepped over — `sudo <hazard>` is seen, `sudo -u bob <hazard>` is not, and the same holds for `env -i` and `time -p`. That last one is the sharpest: it turns an entry the registry does describe into an allow with one extra token, and it is the only item here that a facilitator would reasonably assume was covered. Candidates for the admission gate as the registry grows from reality; the wrapper-flag item is matcher-side (`commandOf` in internal/core/guard/match.go), not registry content.
- iss-151itd-103 shipped only ONE of its two promised planes: the execution-time guard plane is fully wired (abcd guard check/hook, ahoy health), but the TEACHING plane — the rules loader injecting matched shell-safety rules before shell-heavy work, per spc-16 'Two planes, one registry' — is not built; there is no guard/safety/hazard domain in internal/core/rules/. All four itd-103 ACs concern the guard plane only, so the fidelity review is 3 MET/1 MWC, but the intent headline ('two planes') is only half delivered. Wire the teaching plane as a rules-loader safety domain sourced from the same bundled hazard registry, or amend the intent's scope.
- iss-2608221342502263itd-102, itd-103 and itd-104 shipped carrying the identical minted press-release placeholder; the landing page's featured-quote derivation now skips the two placeholder templates mechanically, and the three intents want their narratives written
- iss-2609020721396212Intent and spec titles have no length ceiling, and several run to a full sentence that carries the description rather than a name (the longest today are well over a hundred characters), so a roster, a status board, a changelog line and the record export on the website all show a paragraph where a title belongs, and the id-plus-slug filename derived from the title is truncated at a fixed width that cuts mid-word. Two changes: a threshold on the title at mint time (intent new, spec new, capture promote), refused with the excess named and the description offered as the place for the rest, with a size stated in the record schema and enforced by record-lint for hand-written records; and the website's record export truncating an existing over-long title at the display layer with an ellipsis and the full text on hover or on the record page, so the records already in the corpus do not have to be rewritten to render. The ceiling itself is a decision (a fixed character count, or a word count); the site truncation is not.
- 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-2609300756163382The SHELL rules domain teaches only the bundled hazard registry: an entry a repository adds in its own .abcd/guard.json is refused by the guard but never taught before shell work, so the teaching plane and the execution plane of itd-103 part in exactly the repositories that extended the registry. Ruling CK1 (2026-09-29, the product thinker): teach a repository's own guard entries in the SHELL domain, generated the same way as the bundled registry.