fix: close 29 records: declarations judged folder by folder, a benign config replace re-judged, home-deletion guard residuals, and a credential-store audit - #748
Merged
Conversation
…rd allows A backslash-newline inside the variable's name, a variable inside a brace expansion, and a parameter expansion of HOME with an operator each reach a recursive delete of the home that rm-rf-root-or-home allows. Refs: iss-2609290419119456 Assisted-by: Claude:claude-opus-5-5
…rators
rm-rf-root-or-home compares a delete's target with the words it names as the
line wrote its variables. Three spellings of the home reached that compare as
no word it names, and allowed:
- a backslash-newline inside the name (`rm -rf $HO\<newline>ME`): bash drops
it before it reads the name, and simpleParamEnd now runs the name on across
it, so the spelling is `$HOME`, not `${HO}ME`;
- a variable inside a brace group (`{$HOME,x}`, `$HOME/{.*,}`, `$HO{ME,}`):
bash expands the group before it reads the variable, and each word the
expander makes now keeps its variables' sites (bword.s), so it is spelled
as bash reads it, a bare name running on into the group's unquoted text;
- a parameter expansion with an operator that can leave the value as it is
(`${HOME%/}`, `${HOME:-x}`, `${HOME#}`, `${HOME/x/x}`, a substring, a case
change, a subscript), and an alternative whose word is one of these
(`${X:+$HOME}`): spellParameter spells each as `${NAME}`.
Judgement taken: "can be the home" decides, so an expansion whose pattern
might not match blocks, and a suffix trim that leaves the path above the home
(`${HOME%/*}`) blocks too. Stated over-blocks, all rare: `${HOME#/}` (a
relative path), `$HO''{ME,}`, and `sh -c "rm -rf $HO{ME,}"`, where the outer
shell has already expanded `$HO`. Named as residuals in 17-guard.md and
commands/guard.md: a default's own word (`${DIR:-$HOME}`), an alternative
nested more than three deep, and `${PWD:0:1}` (the root, read as `$PWD`).
Verdict diff over 12,595 pre-existing inputs (every guard test literal, both
corpora and every bundled fixture, bare, in sh -c "..." and in bash -c '...'),
base 8cd7f88 against this tree: 6 differences, all tightening, all the two
residual pins this change flips (`{$HOME,x}`, `${HOME:-/}`).
Refs: iss-2609290419119456
Assisted-by: Claude:claude-opus-5-5
rm -rf ${DIR:-$HOME} deletes the home when DIR is unset and allows: a word's
written spelling holds one text, and a default can print two. Named in
17-guard.md's residuals and deferred past v0.11.1 with what is owed.
Refs: iss-2609290426544292
Assisted-by: Claude:claude-opus-5-5
…spellings resolved_by is ad44726, which spells a backslash-newline inside a name, a brace group's words and a value-keeping parameter expansion as bash reads them. Resolves: iss-2609290419119456 Assisted-by: Claude:claude-opus-5-5
The operator switch in spellParameterAt compared a byte with the tilde, which the fence-run detector (TestNoFenceRunReaderOutsideMdrecord) reads as a markdown fence reader outside mdrecord. The operators are one string now, and the reading is unchanged. Refs: iss-2609290419119456 Assisted-by: Claude:claude-opus-5-5
… pending here-document cat <<E <(x, cat <<E $(x and cat <<E with an open backtick, each followed by a newline and E, panic flushSegment with an index out of range: the newline inside the substitution resets docOwners while the enclosing command's curDocs still indexes it. The hook fails closed on the panic's exit 2, and abcd guard check prints a stack trace. Refs: iss-2609290521415701 Assisted-by: Claude:claude-opus-5-5
…ative's word
Three spellings inside the mechanisms iss-2609290419119456 reads still
deleted the home and allowed (review-guardResid):
- A subscript was cut at its first `]`, so `${HOME[x[0]]}`, `${HOME[0]]}`
and `${HOME[a]]}` kept their raw text. It is read to its matching `]` now,
and whatever follows it but an alternative spells as the variable: the
bash 3.2 of macOS (its /bin/sh and /bin/bash) prints the value past
`${HOME[0]]}`, `${HOME[0]x}` and `${HOME[0]@q}`. A subscript whose `]`
never comes spells as the variable too, on the side of the block.
- A sequence expression's terms carried no wordStruct flag, so a bare name
did not run on into them: `$HO{M..M}E`, `$HOM{E..E}`, `$H{O..O}ME` and
`$HO{M..N}E` spelled `${HO}ME`. A term's letters, digits and underscores
are unquoted name bytes now; no other byte is flagged, so `[` from
`{Z..a}` is never a glob.
- An alternative's word was spelled only where it was one expansion. An
alternative prints its word or nothing, one text, so the word is spelled
as written through its own expansions: `${X:+/}` is `/`, `${X:+~}` is `~`,
`${X:+$HOME/*}` is `$HOME/*`, and `${X:+"$HOME"/}` is `$HOME/`. Only a
simple name is braced where name bytes follow it, so a word spelled that
way is never rewritten. `${X:+x}` still allows (its pin is unchanged).
Over-blocks, stated: `${HOME[0]@q}` blocks although bash 5 quotes the value,
and a quoted `~` or a backslash inside an alternative's word spells as the
unquoted text. homeresiduals_test.go pins each spelling bare, in bash -c and
(where the outer shell leaves it) in sh -c, plus six allow pins, and three
new cost shapes stay linear under the unchanged bar.
Refs: iss-2609290419119456
Assisted-by: Claude:claude-opus-5-5
cat <<E <(x, cat <<E $(x and cat <<E with an open backtick, each followed by a newline and E, panicked with an index out of range. The newline inside the substitution reads the pending bodies and clears docOwners, while the suspended command that opened them kept their indices in its saved curDocs; resumed at the end of the input, flushing it indexed the cleared slice. Reading the bodies now clears that record in every frame a substitution suspends, since the documents it names are read. The line is read as its sibling without the document is: `rm -rf / <<E $(x` blocks, `cat <<E $(x` allows as `cat $(x` does. Refs: iss-2609290521415701 Assisted-by: Claude:claude-opus-5-5
…ment in an open substitution resolved_by is 627a73a, which clears the suspended command's record of the documents whose bodies were read inside the substitution. Resolves: iss-2609290521415701 Assisted-by: Claude:claude-opus-5-5
The resolution named the three mechanisms without the parts 1de705c adds inside them: a name running on into a sequence's letters, a subscript read to its matching bracket, and an alternative read as its word as written. It now states each, and the pin count is 193. Refs: iss-2609290419119456 Assisted-by: Claude:claude-opus-5-5
…ed token The drainEcho3 security review found that scanner.RedactRefusal leaves a token raw when it sits right after an underscore or a letter: the scanner's patterns anchor on a leading word boundary. Refs: iss-2609290541525428 Assisted-by: Claude:claude-opus-5-5
…wlist spellAlternative (1de705c) steps an alternative's shell word and stops at a backtick, which opens a command substitution and leaves the word unspelled. That is shell grammar, not a markdown code span, so the file is named with its one scan and the reason, as tokenize.go is. Refs: iss-2609290419119456 Assisted-by: Claude:claude-opus-5-5
Every bundled secret pattern opens on a leading \b, and '_', a letter and a digit are word characters, so a token glued behind one (a key spelled notes_<token>, a path spelled x<token>y) was never matched and came back from a refusal raw, through every RedactRefusal caller. The primitive now adds a glued sweep: the hard_fail secret patterns that open on \b are recompiled without it and run once through the scanner's own adjacency machinery, so every start position a suffix sweep would try is tried in one linear pass. Its findings are deduplicated with ScanText's and sealed by Redact, so a bounded token keeps the fingerprint it had. The primitive still fails closed: on a degraded scanner, on a sweep it cannot build, and now on a secret span that survives Redact. ScanText itself is unchanged. Refs: iss-2609290541525428 Assisted-by: Claude:claude-opus-5-5
The drainEcho3 security review's INFO: the launch scan and the memory writer's page-name bar share RedactRefusal's blind spot, since ScanText's secret patterns open on a leading word boundary. The record carries the measurement taken before deciding to fix it in the scanner. Refs: iss-2609290551363398 Assisted-by: Claude:claude-opus-5-5
…cter The glued sweep moves from RedactRefusal into scanText, so the launch scan, the memory writer's page-name bar and every store-before-commit redactor find a token right behind a letter, a digit or an underscore, at its own byte span. The sweep is built once per scan (gluedSweep), runs the hard_fail secret patterns that open on \b without that anchor through scanAllPatterns, and applies each pattern's Skip and SkipAt; a span the bounded pass already holds is deduplicated. RedactRefusal now reads ScanText alone and fails closed when the sweep cannot be built whole. Measured before deciding: over the repository's 4605 tracked text files the sweep adds 0 findings to the 13 hard_fail secret findings, the launch dry-run's count stays 1, and it costs 7 to 10 per cent of the bounded scan's time; the package's linear-cost guard still passes. Refs: iss-2609290551363398 Assisted-by: Claude:claude-opus-5-5
…d token Resolves: iss-2609290541525428 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609290551363398 Assisted-by: Claude:claude-opus-5-5
…status render The bare `abcd reading` render listed the assembly parking area and the ingest stage with a plain os.ReadDir on a joined path. A clone that commits `.abcd/.work.local`, or either listed directory, as a symlink out of the checkout had the render echo run-id-shaped names from the far end into `staged_runs` and `orphaned_ingests`. The sweep that deletes from the same stage already lists it through the root and refuses a linked directory, so the read side now does the same: both listings go through the one os.Root the render already opened for its commit-marker probes, via readDirIn, and a listing that would leave the checkout refuses the render. Refs: iss-2609012043432648 Assisted-by: Claude:claude-opus-5-5
…ing it frontmatter.Unquote takes a scalar's INNER text, so every reader holding a raw, possibly double-quoted value strips the pair first. The ledger's reader (issuerecord), record-lint's schema gate and the cold-reading definition locator each kept a private copy of that strip, and a caller that forgot it refused well-formed records with a message comparing a value against itself. frontmatter.UnquoteScalar is the strip beside the decoder: a value that opens and closes with a double quote comes back decoded with quoted=true, anything else unchanged with quoted=false, so the ledger reader that reads a bare value as a number branches on the flag instead of re-testing the quotes. The three call sites and the ledger reader's quotedScalar predicate move onto it; it trims nothing, as none of them relied on it trimming. The changelog gate's scalar is left alone: it strips either quote kind on purpose and says why. Refs: iss-2608311039531552 Assisted-by: Claude:claude-opus-5-5
The include table's two Match forms compared case differently with no stated reason — an extension with strings.EqualFold, an exact basename with == — so `.MD` matched while `makefile` did not match `Makefile`, and whoever added a fourth form had no rule to follow. The rule is now written on Row.Match and restated where matches applies it: a form that names a KIND of file folds case (an extension), a form that names one FILE matches the spelling the repository commits (a basename; a row wanting another spelling lists it), and a form that follows a tool's own rule keeps that rule (MatchSuffix, the Go toolchain's lowercase _test.go). A new form states which of the three it is. No compare changes, so nothing the assembler admits moves and the assembler version stands; TestTheMatchFormsFollowTheOneCaseRule pins each form to the stated rule. Refs: iss-2608311949421873 Assisted-by: Claude:claude-opus-5-5
A record id written in prose is checked by prose_citation_resolves, but the rule read the ten record stores alone, so an id in the brief, a principle, the roadmap, a plan or a research note was judged by no gate. The rule now honours extra_roots — a directory or one file, held inside the repository and refused when absent, exactly as links_resolve holds its own — and reads each for prose the way it reads a store; the write-path check a verb makes before it files text agrees. The shipped config names .abcd/development whole, so a file a store already covers is read once. The first run over the widened set found one unresolvable id, spc-82, cited by the retired predecessor store's own triage note; it joins the baseline beside its never-minted siblings spc-74..spc-83. The lint chapter says what the rule now reads. This widens the existing record-lint gate rather than the site export's reference extraction the record proposed: record-lint already owns prose citations through the one resolver, so the export stays on typed edges and no second resolver is added. Refs: iss-2608271804497247 Assisted-by: Claude:claude-opus-5-5
The binary reads two per-repository files under .abcd/config/ that this checkout does not carry — pii.json, the redaction scanner's pattern override, and scripts-closure.json, the pinned scripts/ closure the launch payload applies — and no document named either. The .abcd/README.md index now lists both as optional, with what each does when absent and where its schema is stated, beside the config/ members it already listed plus the two it had missed (artefact.json, reading-presets.json). The root table also gains prose-citations-baseline.json, the one tracked root file it did not index. Refs: iss-2608271804499169 Assisted-by: Claude:claude-opus-5-5
…env var harness/claude-code matched the product name only as a phrase, so the install page named the host twice with no finding: a link to the plugin's manifest directory and the host's per-plugin data variable. The pattern now also matches that dotted directory and an upper-case environment variable prefixed with the host's name, staying quiet on a documentation host inside a URL and on a lowercase identifier. harness/codex and harness/gemini already caught a path (a dot is a word boundary) but not an environment variable, where an underscore joins the name to the rest; both gain the same arm. Watched RED first (TestDocsLintHarnessNameGateReachesPathsAndEnvVars, all five cases, against the old config), and watched the widened rule fire on both install page sites. The page is then rephrased in generic terms rather than given a per-line allow marker: whether install pages are a sanctioned place to name a host is the ruling iss-216 still owes, and this change does not pre-empt it. Refs: iss-2608271711539855 Refs: iss-216 Assisted-by: Claude:claude-opus-5-5
.gitattributes gives CHANGELOG.md and .abcd/work/DECISIONS.md the union merge driver, and nothing told an author it stops at the local clone. The forge computes a pull request's mergeability and the merge queue's merge without it, so two open pull requests that each append to one of those files conflict there as soon as the first merges, while a local merge of the same two is clean — every records pull request of autonomous run A went dirty that way. The attributes file and the one-writer-per-file principle, the convention that names the attribute as a remedy, now say so, and point at the remedy that removes the conflict on the forge too: one file per entry, already accepted as adr-2609151138420062. The changelog comment also stops claiming that pull requests append to [Unreleased], which record-lint now refuses. Refs: iss-2609240646538011 Assisted-by: Claude:claude-opus-5-5
The configuration chapter lists disembark.maxAgentTokens under its staged keys, which no shipped code reads, but the meta chapter still cited it as a budget in force. It now names it as the staged key it is. Refs: iss-2608221254566264 Assisted-by: Claude:claude-opus-5-5
… read A here-document a substitution opens and never reads stays pending after the close in the guard's reading, while bash 3.2 and /bin/sh run the lines it would cover. And a root or home operand written with repeated slashes, a /./ segment or a /../ segment under the root is compared as an exact word and allows. Both are present at main. Refs: iss-2609290625381759 Refs: iss-2609290625482831 Assisted-by: Claude:claude-opus-5-5
bash 3.2, bash 5 and /bin/sh read no pending document's body at a newline inside a substitution that opened after it: the lines run inside the substitution, and the body begins on the line after it closes. 627a73a read the bodies there instead, so `cat <<E $(x`, `rm -rf ~`, `E`, `)` allowed where its sibling without the document blocks. The pending documents are now saved with the substitution's frame, a newline inside reads only the documents the substitution opened, and the close restores the rest. A document the substitution opens and never reads is pending after the close in bash 5 and dropped in bash 3.2 and /bin/sh, which run the lines it would cover, so the line blocks fail-closed as an unterminated document. heredoc_test.go's arithmetic pin moves from allow to block. Refs: iss-2609290521415701 Refs: iss-2609290625381759 Assisted-by: Claude:claude-opus-5-5
… loader's root A harness registers every markdown file at the top of agents/ as an agent, with no frontmatter requirement and no name exemption, so agents/README.md and agents/CHANGELOG.md were listed as abcd:README and abcd:CHANGELOG agents on every installed surface — workers nothing can dispatch, and names a real agent could not take. The loader is the host's, so the fix is where the files live, as it was for commands/README.md (iss-160). Both move, unchanged in substance, to .abcd/development/agents/: the operator statement of the prompt contract and the itd-5 prompt-version log, durable record rather than plugin payload. record-lint's agent_contract reads the log from its configured `changelog` path, which follows it; the prompt-quality chapter, the surfaces chapter, itd-5's rule text, the Makefile note and the code comments that named the old paths follow too, and the development index gains the folder. Dated plans, research notes and closed records keep the paths they were written against. TestPluginAgentSurfaceRegistersOnlyAgents is the detector, the agent half of the iss-160 command-surface test: every markdown file at the top of agents/ must open a frontmatter block naming itself after its file. Watched RED on the two files before the move and GREEN after. Refs: iss-110 Refs: iss-160 Assisted-by: Claude:claude-opus-5-5
bash 3.2, the /bin/sh and /bin/bash of macOS, steps over any text after a
subscript's closing bracket to the first operator byte, and a `+` or `:+`
there is an alternative: `${X[0]]:+$HOME}`, `${PATH[0]]:+$HOME}`,
`${X[0]x:+$HOME}` and `${X[0]]]:+$HOME}` print the home. Only a leading
`+` or `:+` was read, so those allowed. A `-`, `=`, `?`, `%`, `#`, `/`, a
lone `:` or a backslash first still spells as the variable
(`${X[0]]-$HOME}` prints X's value).
Refs: iss-2609290419119456
Assisted-by: Claude:claude-opus-5-5
Both are open and minor, so the release cut does not require a deferral, but each needs a ruling no implementer can take, and saying so on the record is what keeps it from reading as forgotten. The persona-role check fails existing quotes whichever way it is built, so what happens to them is a roster decision; the spec-step marker changes the build loop's rule, as the record itself says. Each carries deferred_after v0.11.1 and the ruling it waits on. Refs: iss-371 Refs: iss-2609260932372448 Assisted-by: Claude:claude-opus-5-5
The subscriptOperators comment and the `${X[0]]-$HOME}` allow pin said the
form prints X's value. That holds only with X set: with X unset, bash 3.2
and /bin/sh print the word for `${X[0]]-$HOME}`, `${X[0]]:-$HOME}`,
`${X[0]]=$HOME}`, `${X[0]]:=$HOME}` and `${X[0]]-/}` (checked with echo;
bash 5 refuses each as a bad substitution). That is the default's-word
class the open record defers, so the comment, the pin and 17-guard.md's
residuals now say so, and the record carries the subscript forms as
evidence. No behaviour changes.
Refs: iss-2609290426544292
Assisted-by: Claude:claude-opus-5-5
…ory reaches the root or the home Refs: iss-2609290745243990 Assisted-by: Claude:claude-opus-5-5
…aded ScanText carried no signal when the glued sweep was incomplete: a configured secret pattern whose leading \b carries a quantifier has no boundary-free form, the sweep ran without it, and only RedactRefusal read the sweep's completeness. New now folds the gap into the scanner's degraded state, with a reason naming each pattern it could not build, so Unavailable says it and every write-time redactor, the launch scan (ScanBundle) and RedactRefusal fail closed on it, the way they already do on a bad override regex. RedactRefusal drops its own rebuild of the sweep and reads Unavailable alone. A configured pattern with a plain leading \b leaves the scanner available. Refs: iss-2609290743362554 Assisted-by: Claude:claude-opus-5-5
… or the home cleanSeparators read a `..` only directly under the root, so `rm -rf ~/../*` (the home's parent globbed, which holds the home), `~/../../*`, `$HOME/../../*`, `/tmp/../*` and `/etc/../*` allowed, bare and in `sh -c`. foldParents folds each `..` into the segment before it where the target begins at the root or the home, as the path reads lexically. At the root a `..` stays at the root. Past the home it climbs to a directory that holds the home, so the path is the home where each segment it then descends through is `*` (`~/../*` and `~/..` read as `~`, `~/../*/*` as `~/*`), and `~/../x` stays a sibling. The kernel reads `..` otherwise only after a symlink; the lexical reading is the one that blocks. A trailing `..` is folded too, though rm refuses it. Nothing is folded across a segment that holds a variable or a substitution, whose directory count is not known. 17-guard.md and commands/guard.md say so and name the residuals: a `..` after a symlink or after a variable's segment, and `~/../?*` beside `/?*`. Refs: iss-2609290745243990 Assisted-by: Claude:claude-opus-5-5
…ory reaches the root or the home Resolves: iss-2609290745243990 Assisted-by: Claude:claude-opus-5-5
…ed views Resolves: iss-2609290743362554 Assisted-by: Claude:claude-opus-5-5
A `~/x/…/../…/*` and a `/tmp/a/../…*` operand grow with the input as the other home spellings do; workPerByteBar stays 24. Refs: iss-2609290745243990 Assisted-by: Claude:claude-opus-5-5
termsafe's TestNoSecondCodeSpanPairer refuses a backtick scan outside termsafe. literalSegment needs none: a substitution, backtick or `$(…)`, is spelled as a mark below 0x20, and a backtick left in the text is a quoted name byte. `~/`x`/../*` still blocks under the vanish reading. Refs: iss-2609290745243990 Assisted-by: Claude:claude-opus-5-5
…ts verb The sentence added with the next-move change quoted the spec close verb's spelling in the chapter's hand-written prose, where shape is not stated (itd-147 ac-5, held by TestSurfaceChapterProseStatesNoShape); the verb's spelling lives in the generated appendix. The sentence now says the move is closing the spec. Refs: iss-2609100508566033 Assisted-by: Claude:claude-opus-5-5
Refs: iss-2609290656480443, iss-2609290656491358, iss-2609290703091174 Assisted-by: Claude:claude-opus-5-5
Refs: iss-110, iss-2608221254566264, iss-2608271711539855, iss-2608271804497247, iss-2608271804499169, iss-2608311039531552, iss-2608311949421873, iss-2609012043432648, iss-2609240646538011, iss-2609260932372448, iss-2609290630234596, iss-371 Assisted-by: Claude:claude-opus-5-5
Refs: iss-2608220150157503, iss-2608231607594913, iss-2608260941298050, iss-2608290820473197, iss-2609091717146700, iss-2609091955574760, iss-2609100506269348, iss-2609231050273096, iss-2609250834251447 Assisted-by: Claude:claude-opus-5-5
Refs: iss-124, iss-193, iss-209, iss-211, iss-213, iss-2608210932052003, iss-2608210934566224, iss-2608230847432285, iss-2608230847432286, iss-2608231000561060, iss-2608231120121681, iss-2608241612007530, iss-2608250844259345, iss-2608290822140563, iss-2608290956522870, iss-2609012313465609, iss-2609020716570699, iss-2609091256264547, iss-2609091956001547, iss-2609100505146979, iss-2609100506256173, iss-2609100507439414, iss-2609100519122086, iss-2609211105023379, iss-92 Assisted-by: Claude:claude-opus-5-5
Fidelity review of the credential store intent (receipt rcp-ebf7d171b544), re-emitted at the integration tip and ingested from the audits12 worktree: five criteria, MET 1 and MET_WITH_CONCERNS 4, none NOT_MET; the one scope condition narrowed, because the keychain home has only run against the test binary's fake (iss-2609281654467661 is the owed real round trip). Two divergences of substance are captured, not fixed: - ac-3: the record says the write path runs the scanner and refuses a tracked-path write; the store scans the index alone and refuses only the abcd home's value write inside a working tree (ruled at review in 278e266), and the record does not say so. - ac-2: the spec and the press release say the CLI asks for the home; the CLI takes it as a --home flag and only the plugin page asks. The unwired provider call (APIConfig.Call has no production caller, so the route receipt's provider_call is never produced) is not captured: call.go declares it as spc-2609251028149555's scope. Refs: itd-2609221017023290 Refs: iss-2609290825240166 Refs: iss-2609290825319136 Refs: iss-2609281654467661 Assisted-by: Claude:claude-fable-5-1
main carries integ16 (drainBugs, drainSec, drainUx, drainDrift2, drainEcho..drainEcho3, drainDrift3, drainDebt). Conflicts, resolved by hunk: - internal/core/rules/root.go and internal/core/history/location.go: this branch (fsVet) added DeclarationDirectoryExposed to the refusal switch beside DeclarationBehindSymlink, while main (drainDebt) replaced that switch with a call to fsutil.HomeDeclarationNames. Main's call is taken in both files. Semantic collision, fixed in this merge: - internal/fsutil/home.go auto-merged textually, but HomeDeclarationNames' switch did not know fsVet's new refusal, so an exposed ~/.abcd fell to the default arm and read "it could not be read (...)" around the refusal's own clause. DeclarationDirectoryExposed joins the DeclarationBehindSymlink arm, which carries the clause as written. New test TestHomeDeclarationNamesNamesTheExposedDirectory (internal/fsutil/home_scope_mode_test.go) holds it; watched red on a scratch copy without the arm, green with it. DECISIONS.md: triageMajorB's 2026-09-29 entry was auto-merged above main's last line; it is moved to EOF so the diff from main is +1/-0. commands.md and surface.json regenerated; release files are main's and ## [Unreleased] is empty. Assisted-by: Claude:claude-opus-5-5
The secret scanner and the refusal redactor find a token glued behind a word character, the glued sweep also runs over the decoded views, and a sweep that cannot be built whole reports degraded. Stacked on the echo chain main now carries, so only its own ten commits are new. Merged cleanly; build, vet and the scanner, memory, history, capture and implement/loop tests pass; go generate produced no drift. Refs: iss-2609290541525428, iss-2609290551363398, iss-2609290743362554 Assisted-by: Claude:claude-opus-5-5
Records only: the fidelity audit of itd-2609221017023290 (receipt rcp-ebf7d171b544; MET 1, MET_WITH_CONCERNS 4, NOT_MET 0) lands in the intent's Audit Notes, and two drift captures (ac-3, ac-2) enter open/, where they stay. Merged cleanly; no Go changed. Refs: itd-2609221017023290 Refs: iss-2609290825240166, iss-2609290825319136, iss-2609281654467661 Assisted-by: Claude:claude-opus-5-5
Drains fifteen open records: six fixed and resolved, nine re-deferred past v0.11.1 with the ruling each owes. Every config-named repo path refuses the git directory, the record dispatcher says the spec close ships the intent, AGENTS.md states scan-before-mutating by blast radius, and the bootstrap hook's network refusal names the environment it ignores. Conflict, resolved by hunk: - itd-5 prompt-quality-additions, the pre-flight steps: this lane rewrote steps 2-3 (the calibration corpus is the gate, length is no tiebreak) and still named agents/CHANGELOG.md in step 4, while this branch (drainObs, d5091da) had already renamed step 4's target to "the prompt-version log" when it moved that file. Kept the lane's steps 2-3 and this branch's step 4. Build, vet and the lint, positioning, record and cli tests pass; go generate produced no drift. Refs: iss-2608291814578333, iss-2609100508566033, iss-2608230957104179 Refs: iss-2608261437042674, iss-2608310912206749, iss-2608210923438110 Refs: iss-2608291814562032, iss-2608210934566220, iss-2608220750029985 Refs: iss-2608282026177429, iss-2609012111162089, iss-2609201954342967 Refs: iss-2609212142568782, iss-2609252055532027, iss-2609262011091645 Assisted-by: Claude:claude-opus-5-5
The shell guard reads more spellings of the home and the root the way bash does, so a recursive delete through them blocks: a backslash-newline inside the name, a brace group's words, an expansion that can leave the value as it is, an alternative's word, redundant separators, and a ".." folded into the directory before it. A pending here-document waits out a substitution opened on its line. Merged cleanly beside triageMajorA's and main's guard prose in commands/guard.md and 17-guard.md; build, vet and the guard and termsafe tests pass; go generate produced no drift. iss-2609281134544802 is neither resolved nor re-deferred by this lane: it stays in open/ with its lapsed deferred_after v0.11.0. Its renewal is lane fix5-guardGlob's, which is not in this build. Refs: iss-2609290419119456, iss-2609290521415701, iss-2609290625381759 Refs: iss-2609290625482831, iss-2609290745243990, iss-2609290426544292 Refs: iss-2609281134544802 Assisted-by: Claude:claude-opus-5-5
The user ruled directly to the run A orchestrator at 06:53Z, "use up to five sub-agents from now on". The entry supersedes the four-agent ceiling of 2026-09-24; Fable reviews and audits stay one at a time. Assisted-by: Claude:claude-opus-5-5
Dry-run assemble on a clean clone of 8fdf0d0: widening measures 1,358,172 tokens / 5,228,966 bytes, which left the 1,370,000 window 0.87% headroom, so it moves to 1,380,000; detection measures 1,367,208 tokens / 5,263,754 bytes, 0.93% under 1,380,000, so it moves to 1,390,000. Entailment (392,765, 1.84%) keeps its window and figures. Refs: iss-2609251455354719 Assisted-by: Claude:claude-opus-5-5
REPPL
enabled auto-merge
September 29, 2026 11:17
… refuses a concurrent rewrite The Linux check leg of PR 748 failed TestConcurrentConnectsKeepEveryKeyAndBlock with ErrDeclarationSwapped from ~/.abcd/config.json: the re-vetting fix for iss-2609290518278152 lives in the path-based ReadDeclaration, while every home-scoped reader goes through the descriptor-based readDeclarationIn, which still refuses a replacement on sight. Refs: iss-2609291157309818, iss-2609290518278152 Assisted-by: Claude:claude-opus-5-5
… and its read ReadHomeDeclaration (and ReadHomeDeclarationDenying) judge the leaf on a root-relative Lstat, open it relative to the same directory descriptor, and confirm with os.SameFile that the descriptor is the file judged. A file renamed into place inside that window was refused on sight with ErrDeclarationSwapped, so the ordinary rewrite a concurrent abcd makes through WriteFileAtomic under the writers' lock made an unlocked reader (layered.Load via oracle.LoadAPI) refuse its own ~/.abcd/config.json. iss-2609290518278152 fixed this in b342b2b, but in ReadDeclaration, the path read; the integration merge that landed it (24e7850) kept a07ad67's descriptor read, readDeclarationIn, which every home-scoped reader calls and which had no retry. The fix never reached a production reader. readDeclarationIn now loops over one vetting and read (readDeclarationInOnce) up to declarationAttempts (8) times, re-running on ErrDeclarationSwapped only. Every attempt runs every guard from scratch: the Lstat (regular file), CallersAlone (no group/other write, owned by this uid), the O_NOFOLLOW open, the descriptor's regular-file and SameFile check, the descriptor's uid, and the caller's deny mask on the descriptor's mode. The bytes returned are always those of a descriptor os.SameFile ties to an Lstat that passed every guard. A replacement that fails a guard is refused by that guard; one still unsettled after the bound is refused as a swap. The directory walk (openHomeScope) is unchanged: a directory level replaced while it is opened is still refused. Refs: iss-2609291157309818 Refs: iss-2609290518278152 Assisted-by: Claude:claude-opus-5-5
…dges a benign replacement The fix is ecd41fd: readDeclarationIn, behind every home-scoped declaration read, judges a leaf renamed into place after its vetting from scratch, a bounded number of times, rather than refusing it on sight, so the re-vetting iss-2609290518278152 intended now reaches the readers that use it. Resolves: iss-2609291157309818 Refs: iss-2609290518278152 Assisted-by: Claude:claude-opus-5-5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This lands eight reviewed lanes as one change. Together they close 29 open records, re-defer 42 others out loud against v0.11.1 (each naming the ruling, planning or lane it waits on), and record one fidelity audit. Four of the fixes are on a security path: a declaration file in
~/.abcdwas trusted even when the folder holding it could be changed by another account; the status-line setting was read through a second look that judged nothing; the secret scan missed a token glued onto the word before it; and the shell guard let several spellings of the home or the root past its recursive-delete check. The rest remove two spurious plugin agents, close a symlink read in theabcd readingstatus render, state the guard's tool reach on every surface that describes it, keep config paths out of.git, and bring records, gates and documents back in line with the tree.fsVet (security). Every folder level below home on the way to a declaration is now judged on the descriptor opened for it: a folder every account can write, or one owned by an account that is neither you nor root, is refused as
DeclarationDirectoryExposed, naming the folder and thechmod o-wthat repairs it. Absence is still decided first, home itself is still not judged, and every caller renders the refusal beside its symlinked-folder case. abcd's own writers make~/.abcdat 0755 or 0700, so neither case arises from an install.statusline.ReadSettingsFilereads~/.abcd/statusline.jsonthrough the same canonical read, so a file swapped in between two looks is no longer read on the judgement made about the file it replaced. One question stays open for the product thinker: whether a group-writable~/.abcd(0775, which the documentedmkdir -p ~/.abcdmakes under a user-private-group umask of 002) should be refused too.drainObs (observations). The installed plugin stops listing
abcd:READMEandabcd:CHANGELOGas agents: the agent directory's readme and prompt-version log move to.abcd/development/agents/. The bareabcd readingstatus render refuses a local tier that is a symlink out of the checkout instead of listing names from outside it.prose_citation_resolvesreads the whole durable record; the docs-lint harness rules catch a host named through its plugin directory or an environment variable; one strip-then-decode reader,frontmatter.UnquoteScalar, replaces three private copies; and the include table, the.abcd/README.mdindex,.gitattributes, one principle and the brief's meta chapter now say what the tree does. Two records are deferred for a product ruling (the persona role check and a spec step that waits on another intent), and one new capture records that the agent contract's default log path still sits insideagents/.triageMajorA (major findings, first half). The guard's tool reach is stated as a standing limit on the guard brief chapter, the plugin page and the
guard hookhelp (and so the generated CLI reference): the hook manifest hands the guard the shell tool and the question tool and nothing else, and a call through any other tool never reaches it. A new test pins the clause on all four surfaces. The matcher is not widened. Eight lapsed major findings are re-deferred, each naming what it owes.triageMajorB (major findings, second half). The smoke lane runs the record-writing verbs (
capture,capture resolve,decide) through the built binary against a scratch repository and asserts what lands on disk. The product thinker's ruling on third-party interface guidance and redaction purposes is written into a new principle,guidance-carries-its-evidence-and-its-purpose.md, with one decision-log line saying why it lives there. Twenty-three lapsed major findings are re-deferred; five are promoted into draft intents and stay open until those intents ship, as the ledger's promotion rule requires.drainEcho4 (security, secret scan). The scanner's secret patterns only started a match at a word boundary, so a key written
notes_<token>, or a token with a letter on each side, went unrecognised: a refusal naming such a key or path repeated the token, and the launch scan, the memory writer's page-name check and the store-before-commit redactors missed it. Every scan now also runs a glued-token sweep that retries the boundary-anchored hard-fail patterns without the boundary in one linear pass, over the raw line and its percent-decoded and JSON-unescaped copies. A refusal seals a glued token byte for byte and keeps the rest readable; a memory page whose name carries one is refused; and a pattern the sweep cannot build marks the scanner degraded, so every redactor and the launch scan refuse rather than run a narrower sweep. Over the repository's 4,605 tracked text files the sweep adds no finding and 7 to 10 per cent to the scan's time.audits12 (fidelity audit). The fidelity audit of the shipped credential-store intent (itd-2609221017023290) is recorded on the intent: one criterion met and four met with concerns, none unmet. Two places where the record says more than the code does are captured as minor drift and stay open: the setup takes the credential home as a flag rather than asking for it, and only the abcd home's write is refused inside a working tree, where the intent and its ADR still say every write. The audit also narrows one condition: the keychain home is exercised only through the test binary's fake, which is already an open, deferred record. No code changed.
drainRest (drain of fifteen records). Six are fixed and resolved: every repo-relative path in the positioning, docs-lint and record-lint configs refuses a path inside
.git, through the shared check the site manifest already used;abcd <itd-N>andabcd <spc-N>say that the spec close is what ships the intent;AGENTS.mdstates the scan-before-mutating rule by blast radius; itd-5's pre-flight steps agree with its own amendment; two closed specs stop stating tree facts that were false; and the ingest page carries the two author-name conventions. The bootstrap hook's network refusals now name the proxy and CA-bundle variables the lockdown ignores (a single-quoted constant, never expanded; the lockdown is unchanged), and that record stays open on its deferral, because whether to offer a way through the lockdown is a product decision. Eight more are deferred past v0.11.1, each with the ruling it owes on the record.guardResid (security, shell guard). The guard promises to block a recursive delete of the home or the root, and several spellings got past it: a backslash-newline inside a variable's name, a variable inside a brace group or a sequence expression, a
${HOME...}expansion whose operator can leave the value as it is, an alternative after a subscript as bash 3.2 reads it, an alternative's word split on whitespace, a root or home path with redundant separators (//*,/./*,$HOME//), and a path that climbs with..from a named directory (/tmp/../*is the root,~/../*holds the home). Each is now read the way bash reads it, so it blocks. A here-document whose body a substitution on the same line postpones is read where all three shells read it, and one that a substitution opens and never reads refuses the line. The stated over-blocks are rare and listed in the guard chapter; the residuals (a default's own word, an alternative nested more than three deep,${PWD:0:1}, and three..shapes the text cannot decide) are named in 17-guard.md and commands/guard.md and deferred past v0.11.1. Evidence: 249, 40, 144 and 129 new or extended subtests, each block pin watched fail at the base; verdict diffs over 127,805 and 150,497 inputs (every tightening under the home-or-root entry; the only loosenings are lines every shell reads as a document's text); two 3-minute fuzz runs with no panic.race17 (concurrent-config race, on the integration branch). The Linux check leg of this pull request failed
TestConcurrentConnectsKeepEveryKeyAndBlock: oneahoy connectrefused~/.abcd/config.jsonas "replaced between its vetting and its read" because another connect had just rewritten it. The earlier fix for this race (iss-2609290518278152) had gone into the path-based declaration read, which no reader of a home declaration calls any more; the descriptor-based read they all use still refused a replacement on sight. That read now judges a file renamed into place after its check from scratch, up to eight times, and reads it only if it passes every check again (a regular file, owned by you, writable by nobody else, and inside the reader's own mode rule). A symlink, FIFO, folder, group-writable, foreign-owned or too-open replacement is still refused by the check it fails, and a file that keeps changing is still refused as swapped. Evidence: eight new tests and subtests, seven of them watched fail before the fix (the symlink case was refused either way, and still is).Reviews: fsVet SHIP (security review). drainObs SHIP. triageMajorA SHIP. triageMajorB SHIP. drainEcho4 SHIP (its fix round then closed the review's one low finding and one note). drainRest SHIP. guardResid SHIP (security verification). audits12 is records only.
Integration. The first four lanes merged in order with no conflict. Main (#747) was then merged in:
internal/core/rules/root.goandinternal/core/history/location.goconflicted, because fsVet added its new refusal to a switch that main had replaced with one shared function,fsutil.HomeDeclarationNames. Main's call is kept in both files, and the new refusal joins the symlinked-folder arm of that shared function, which merged without a textual conflict but would otherwise have fallen to its default wording; a new test holds it, watched fail without the arm. drainEcho4, audits12, drainRest and guardResid then merged with no conflict. Build, vet and each lane's touched tests passed after every merge, under the local toolchain and the onego.moddeclares, and the generated CLI reference and surface appendix were regenerated at the merged tip and match it. The decision log carries main's entries first, then this branch's two 2026-09-29 entries at the end: triageMajorB's, and one recording the user's ruling that an autonomous run keeps at most five sub-agents alive, superseding the ceiling of four. The reading windows were measured again at the merged tip: widening (1,358,172 tokens) and detection (1,367,208) had fallen under 1% headroom and move to 1,380,000 and 1,390,000; entailment (392,765) keeps 400,000.One record this build does not settle: iss-2609281134544802 (a variable's carried value is not read by the shell guard, major) stays in open/ with its deferral against v0.11.0, which lapsed when v0.11.1 was cut. guardResid did not carry it; renewing the deferral belongs to a later lane, and until that lands the next release cut refuses on it.
Resolves: iss-110
Resolves: iss-2608221254566264
Resolves: iss-2608231120121681
Resolves: iss-2608271711539855
Resolves: iss-2608271804497247
Resolves: iss-2608271804499169
Resolves: iss-2608311039531552
Resolves: iss-2608311949421873
Resolves: iss-2609012043432648
Resolves: iss-2609091955574760
Resolves: iss-2609100506256173
Resolves: iss-2609240646538011
Resolves: iss-2609290656480443
Resolves: iss-2609290656491358
Resolves: iss-2609290541525428
Resolves: iss-2609290551363398
Resolves: iss-2609290743362554
Resolves: iss-2608291814578333
Resolves: iss-2609100508566033
Resolves: iss-2608230957104179
Resolves: iss-2608261437042674
Resolves: iss-2608310912206749
Resolves: iss-2608210923438110
Resolves: iss-2609290419119456
Resolves: iss-2609290521415701
Resolves: iss-2609290625381759
Resolves: iss-2609290625482831
Resolves: iss-2609290745243990
Resolves: iss-2609291157309818
Refs: iss-2609290703091174
Refs: iss-371
Refs: iss-2609260932372448
Refs: iss-2609290630234596
Refs: iss-2609250834251447
Refs: iss-2609231050273096
Refs: iss-2609091717146700
Refs: iss-2609100506269348
Refs: iss-2608290820473197
Refs: iss-2608231607594913
Refs: iss-2608220150157503
Refs: iss-2608260941298050
Refs: iss-124
Refs: iss-193
Refs: iss-209
Refs: iss-211
Refs: iss-213
Refs: iss-2608210932052003
Refs: iss-2608210934566224
Refs: iss-2608230847432285
Refs: iss-2608230847432286
Refs: iss-2608231000561060
Refs: iss-2608241612007530
Refs: iss-2608250844259345
Refs: iss-2608290822140563
Refs: iss-2608290956522870
Refs: iss-2609012313465609
Refs: iss-2609020716570699
Refs: iss-2609091256264547
Refs: iss-2609091956001547
Refs: iss-2609100505146979
Refs: iss-2609100507439414
Refs: iss-2609100519122086
Refs: iss-2609211105023379
Refs: iss-92
Refs: iss-160
Refs: iss-216
Refs: iss-2608291814562032
Refs: iss-2608210934566220
Refs: iss-2608220750029985
Refs: iss-2608282026177429
Refs: iss-2609012111162089
Refs: iss-2609201954342967
Refs: iss-2609212142568782
Refs: iss-2609252055532027
Refs: iss-2609262011091645
Refs: iss-2609290426544292
Refs: iss-2609290825240166
Refs: iss-2609290825319136
Refs: iss-2609281654467661
Refs: iss-2609281134544802
Refs: iss-2609251455354719
Refs: iss-2609290518278152
Assisted-by: Claude:claude-opus-5-5