fix: drain 52 records: refusals that never echo a secret, stores that refuse a symlinked ancestor, and fixes across the CLI - #747
Merged
Merged
Conversation
The routing resolver's fallback and diagnostic lines prefixed "abcd " to a verb that half its callers (intent audit, intent consistency, spec close, launch ship) already pass prefixed, so the line read "abcd abcd spec close: ...". The resolver now strips a leading "abcd " before adding its own, so both caller spellings print the verb once. The refusal messages are unchanged: they carry the verb as given and the top-level printer adds "abcd:" once. Refs: iss-2609251606543515 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609251606543515 Assisted-by: Claude:claude-opus-5-5
…route reading ingest reads the output once to learn the position it names, and a read that failed (past the size cap, a symlink, a missing file) left the payload empty, so a --route was refused as an output that "names no reading position". Under a --route the read's own refusal is now returned; without one the ingest still refuses the output itself, with its disclosure render unchanged. Refs: iss-2609251606553359 Assisted-by: Claude:claude-opus-5-5
… read's reason Resolves: iss-2609251606553359 Assisted-by: Claude:claude-opus-5-5
The test evaluator's `==` compared a bool to a string by truthiness and everything else by its string rendering, so `true == 'true'` read true where GitHub reads it false. It now follows GitHub's documented loose equality: same-type operands compare directly (strings ignoring case), mismatched operands are both coerced to a number (null 0, true 1, false 0, a string parsed from the JSON number grammar with "" as 0 and anything else NaN, an array or object NaN), and NaN equals nothing. No committed workflow or scaffold template makes a comparison whose answer moves: the lint and scaffold workflow-contract tests pass unchanged. Refs: iss-2609251616248870 Assisted-by: Claude:claude-opus-5-5
…Hub's coercion Resolves: iss-2609251616248870 Assisted-by: Claude:claude-opus-5-5
`abcd site`, `site build` and `lint site` handed the working directory to the core as the repository root, so run from a subdirectory the board reported .abcd/site.json absent and the build had nothing to build, with exit 0: a plausible wrong answer. They now resolve the checkout root the way the other per-repository verbs do (captureRoot: git's toplevel, else the .git-marker walk), and the default output directory is the checkout's `site/`, so a subdirectory reports, builds and checks the same site as the root. An --out the operator names still means what the shell means by it. `site setup` already resolved its root in the core and is unchanged. Outside a checkout nothing changes: the root falls back to the working directory as before. Refs: iss-2609251750202525 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609251750202525 Assisted-by: Claude:claude-opus-5-5
PrimaryWorktreeRoot compared the --git-dir and --git-common-dir answers of `rev-parse --path-format=absolute` without checking their shape. A git older than 2.31 does not know --path-format, echoes it to stdout and exits 0, so an answer can be the flag's text and a path on two lines. Every answer now goes through gitutil.RevParseAbsPath, which refuses anything but one absolute line, so on such a git the resolution fails closed by refusal rather than by a comparison that happens to miss. That is the same outcome the committed pre-commit guard reaches on the same git (its toplevel check cannot match a two-line answer), so the board and the guard still agree about the inherited store. The flag is kept, not dropped: dropping it here alone would let the board report an inherited store the guard does not enforce on an old git. Refs: iss-2608291924452604 Assisted-by: Claude:claude-opus-5-5
…idates rev-parse answers Resolves: iss-2608291924452604 Assisted-by: Claude:claude-opus-5-5
ArchiveTree asked check-attr --cached, which reads .gitattributes from the index, while git archive reads them from the tree it archives. With a .gitattributes change staged but not committed, the launch listing dropped a file the release archive carries, or kept one it drops. The listing now reads the revision's own attributes. When the index holds exactly the revision's .gitattributes (same paths, modes and blobs, none conflicted) it still asks --cached, which every git answers and which is then exact; when they differ it asks --source=<rev>. That option needs git 2.40, so an older git in that state is refused by name with the remedy (commit or unstage the change), never answered from the index. No minimum git version is imposed on the common case. This changes which files the non-plugin launch bundle lists, in one state only: a staged .gitattributes edit. The bundle now follows the tree the tag archives. Refs: iss-2609260933592838 Assisted-by: Claude:claude-opus-5-5
…ion's attributes Resolves: iss-2609260933592838 Assisted-by: Claude:claude-opus-5-5
…s create directories The spec store's ensureDir refused a symlinked LEAF directory and then called os.MkdirAll, which follows a symlinked ANCESTOR and creates the rest of the chain under its target. A committed `.abcd/development -> <elsewhere>` (or a link at .abcd or specs/) therefore redirected `spec create` and `spec close`, and every write after them, out of the checkout. The intent store closed the same hole in 24c2f2e by routing through fsutil.EnsureRealDirAll; the spec store now does too, and its lock-only mode (Close, Discard) proves the existing store with fsutil.ProbeRealDirAll before it locks and writes through it. The sweep for the same create sequence (leaf Lstat or nothing, then os.MkdirAll, on a path inside the checkout) found two more: - decide's mint lock, which put an ADR under a symlinked .abcd/development; - scribe assemble's run directory, created relative to the checkout in the local tier, so a force-added `.abcd/.work.local` link parked the context and the manifest wherever it pointed. It is now proved level by level, leaf included, as the reading assembler proves its own (iss-2609262231500173). Both are fixed here rather than captured, since the fix is the same one line of routing. Out of scope and left alone: the remaining os.MkdirAll sites create under a home-scoped store, a plugin root, or a destination the operator named (lifeboat pack, launch render, site build), none of which a clone commits; and the read side of the spec and intent stores, which follows an ancestor link to read records but writes nothing. Tests watched fail first on a clean export of the base: TestCreateRefusesASymlinkedAncestor and TestCloseRefusesASymlinkedAncestor (spec), TestCreateRefusesASymlinkedAncestor (decide), and TestScribeAssembleRefusesASymlinkedLocalTier (scribe); each asserts a refusal AND an empty link target. Refs: iss-2609012037137250 Refs: iss-2609262231500173 Assisted-by: Claude:claude-opus-5-5
The docs cite refresh fetcher re-guarded the host on every redirect hop but pinned no scheme, so an https citation redirected to plaintext http was followed. The plaintext leg is forgeable by anyone on the path, and the address it reports is what the refresh writes into the committed baseline as the citation's final URL — the same shape GHSA-35fj-9w6f-7h62 closed in memory ingest. A chain that has reached https now never leaves it: the redirect policy refuses a non-https hop once any earlier request was https, ahead of the host guard. The outcome is `blocked`, not `broken`: the host answered and pointed somewhere the checker will not go, which says nothing about whether the source is alive, so it goes to the manual queue (the previous baseline entry kept) instead of becoming a dead citation the gate then enforces. The detail names the scheme only, since the hop's URL is redirect-controlled and may carry a credential in its query. Decision taken, not in the record: the pin is against a DOWNGRADE, not against http. The record's wording ("refuse a hop whose scheme is not https") would also refuse an http citation that moves to another http address, which memory ingest and update can afford because they admit https sources only; the refresh checks whatever the docs cite, and refusing those would push live http citations into the manual queue with no security gain, since their first request is already plaintext. An http citation upgrading to https, the commonest redirect there is, is followed as before and now stays on https. Swept: every CheckRedirect in the tree. update.go and memory ingest pin https per hop; vintage/release.go, the openaiapi adapter and the cloudflare adapter follow no redirects at all. The docs command page and the docs surface chapter say what the queue now holds. Test watched fail first on a clean export of the base: TestCheckDoesNotFollowARedirectOffHTTPS (the plaintext server was hit); TestCheckStillFollowsAnHTTPCitationUpgrade passes at base and after, pinning what the refusal must not reach. Refs: iss-2609012037440084 Assisted-by: Claude:claude-opus-5-5
The hook shims' PATH rung judged the DIRECTORY a PATH entry lives in and never the binary it names, so a recorded, owned, out-of-tree abcd at mode 0777 in an ordinary 0755 directory passed every check, and any local user could replace the bytes that every prompt, tool call and compaction then execute (iss-2609020352438590, shape 2). All five PATH-resolving shims now also refuse a candidate whose own mode is world-writable, read with `ls -ldL` so a symlink is judged by the file it names: a link in an ordinary directory no longer launders a writable target. The check is one more builtin `case` on the output of the absolute /bin/ls the rung already runs, so it resolves nothing from the PATH it is judging and adds no hashing to the fast path (adr-46). The refusal is its own reason line, "the binary itself is world-writable". The record's other shapes were closed by later work, and are recorded in the resolution rather than re-fixed here: the working-directory containment, the relative PATH element the two shells read differently, and the symlink that names an in-tree binary all end at the owned-only pin (c637a73, GHSA-gx3m-3224-qqcv), which runs a candidate only when ~/.abcd/path-entry names that exact path, so a clone can no longer supply one from any working directory; the data directory's artefact and ancestor shapes are closed by the ownership test on the data and cache directories (iss-2609260057111315) and by the attested hash the promotion re-checks (GHSA-4q78-ccfv-f374). The install how-to, the ahoy surface chapter and the configuration chapter state the accepted shape in terms of the file executed. Test watched fail first on a clean export of the base: TestBinaryHooksRefuseAWorldWritablePathBinaryFile, both subtests (a plain 0777 file and a symlink to one), every PATH-resolving event executing the recorded stub. Refs: iss-2609020352438590 Refs: iss-2609260057111315 Assisted-by: Claude:claude-opus-5-5
`abcd source ledger --flip N` marks a ledger line as cited publicly, and under adr-41 gate 2 that is the person's act: the plugin's source and consult pages tell an agent never to run it. The shell guard's default registry did not name it, so nothing stopped an agent that did. ledger.go still refuses a confidential or non-citable source mechanically, so the exposure is provenance, not leakage: a line recording that a human chose to cite a source when a model did. The registry gains `abcd-source-ledger-flip`, a blocker on `abcd source ledger` carrying `--flip` (either spelling), with `--corpus` declared as the value flag the two-level subcommand scan steps over. Its successor tells the agent to name the line and let the person run the flip. Recording a line, listing the ledger and every other source verb stay allowed. The guard's command page and its surface chapter say so. Swept for the same class: every plugin page that reserves a verb for the person. `ledger --flip` is the only one an agent is told never to run at all; `history discard`, `docs cite confirm`, `intent plan` and `ahoy`'s publish step are run by the agent on the person's stated word, so a blocker would refuse the consented run, and they stay out of the registry. Residual, stated on the command page: the entry matches the program by its basename `abcd` (PATH or plugin root), not `go run ./cmd/abcd` in a source checkout or a copy under another name. The guard is a mistake filter, not a boundary (adr-42). Test watched fail first on a clean export of the base: TestSourceLedgerFlipIsLeftToThePerson, all six blocking spellings allowed. Refs: iss-2609252007448074 Assisted-by: Claude:claude-opus-5-5
The ideate verdict recorder redacts every free-text field on the way in, and its stage-two refusal names finding kinds only, because a refusal that quoted the span it refused would publish it into the terminal, a log and the session transcript. Five closed-set refusals in the same file did the opposite: the verdict, the leg kind, a claim status, a grill relation and a kill outcome were interpolated into the error after only termsafe.Sanitize, which strips control sequences and redacts nothing. A payload whose status field carried a token or a home path was refused with nothing written and the value printed verbatim. Each now goes through describeRefused, which gives the value's length (or that it is empty) and never the value; the refusal still names the field and the item, and still lists the admitted set, which is what a composer needs to fix a typo. Swept in the same file, and fixed with them: a grill hit's record id that fails the id grammar was quoted the same way (the sixth site; the over-length branch above it already described rather than quoted). Left alone: the slug refusal, whose value is the command-line operand the caller already typed into the transcript; and the rejected-alternative refusal, which quotes text that has been through the field redactor. The same shape outside this file — lifeboat review quoting its mode, prompt_version and verdict, and intent audit quoting a criterion verdict and ids in the error it returns — is captured as iss-2609290033521472 rather than widened into this change. The ideate surface chapter states the rule. Test watched fail first on a clean export of the base: TestEnumRefusalsDoNotEchoTheValue, all six subtests. Refs: iss-2609090951295881 Refs: iss-2609290033521472 Assisted-by: Claude:claude-opus-5-5
…alue Found on the sweep for the ideate fix: lifeboat review and intent audit quote a refused closed-set value into the error they return. Refs: iss-2609290033521472 Assisted-by: Claude:claude-opus-5-5
The coercion for a mismatched-type `==` followed the documentation's "any legal JSON number" wording literally, while the record's ground is "as the runner would". toNumber now follows actions/runner's ExpressionUtility.ParseNumber on .NET 8 (the runtime the runner targets) rule for rule: trim Unicode white space (empty is 0); Double.TryParse with a leading sign, '.5' and '5.', an exponent, trailing NULs tolerated, overflow to +/-Infinity and a case-insensitive [+-]Infinity fallback; lower-case 0x hex and 0o octal read as a 32-bit two's-complement integer, wider values NaN; anything else NaN. `1 == '+1'` is now true. Not reproduced: .NET clamps a decimal exponent above 1000 to 9999, which differs from strconv only for a mantissa of more than about 1000 digits. Refs: iss-2609251616248870 Assisted-by: Claude:claude-opus-5-5
three-tier-layout's placement check matched NEXT.md, scratch/ and logs/ by exact name and only directly under .abcd/development/ or .abcd/work/, so three shapes one step away passed clean (iss-173): a handover one directory below a tier root, a local-tier artefact at the .abcd/ root itself, and a name in another case on a case-sensitive filesystem, where `next.md` is a different file from NEXT.md and is committed just the same. On a case-insensitive filesystem the old exact-path probe did catch `next.md`, but named it NEXT.md. The check now lists directories rather than probing fixed paths, matches the three names in any case, and names each path as spelled on disk. It widens by name, not wholesale, because the record requires that widening must not start flagging legitimate tier content: NEXT.md is a name the local tier owns outright, so it is flagged at ANY depth in a committed tier (the walk never follows a symlink); scratch/ and logs/ are ordinary words a durable record may legitimately nest, such as a study's own logs/, so they are flagged only where the local tier would put them, directly under a tier and at the .abcd/ root. That split is this change's decision; the residual it leaves, a nested scratch/ or logs/ holding local ephemera, is named here. A .abcd that is a file rather than a directory is reported through the missing tiers, not as a read error. The lint surface chapter states the widened rule. Tests watched fail first on a clean export of the base: TestRule_LocalArtifactResidualEvasions, all six subtests (nothing flagged, or the lower-case spelling reported under the upper-case name); TestRule_LocalArtifactWideningKeepsLegitimateTierContent passes before and after, pinning what the widening must leave alone. The repository's own lint reports no three-tier-layout finding. Refs: iss-173 Assisted-by: Claude:claude-opus-5-5
The enum-refusal test plants a home path as the value a refusal must not echo; the repository lint reads it as a committed absolute path. The allow marker says it is deliberate. Refs: iss-2609090951295881 Assisted-by: Claude:claude-opus-5-5
…an oversized file iss-2609012037125129 recorded that the glossary's readTerm loaded every term file through a bare os.ReadFile, so a committed FIFO hung every verb that builds the index and a committed symlink read an out-of-tree file as a term. That was already closed at the base, in 746a5d2: ScanInRoot reads through fsutil.ReadGuardedInRoot with a term-family cap, maxTermBytes (index.go:188). The record's one open decision, the cap, was taken there. What the record also asked for, and 746a5d2 did not add, is a test in the term store itself. Three pin it: a FIFO at a term name is refused within a deadline rather than blocking, a symlinked term file is refused rather than read, and a term file one byte past the cap is refused. Each was watched fail on a scratch copy with the pre-advisory read restored (os.ReadFile of the joined path): the FIFO scan blocked past the deadline, and the link and the oversized file were read. Refs: iss-2609012037125129 Assisted-by: Claude:claude-opus-5-5
…nt audit errors Found on the sweep for the ideate fix and captured as iss-2609290033521472: two more host-payload verbs carried a refused value to the terminal. - lifeboat review quoted an out-of-set mode, prompt_version and verdict with a bare %q, no redaction and no cleaning beyond Go quoting. Each is now described by termsafe.DescribeRefused (its length, or that it is empty) and the refusal still names the field. - intent audit's dead-letter path redacted the reason in the record, where it quotes the payload (an out-of-enum verdict token, a malformed id), and then handed the same reason back to the surface raw. The returned reason now goes through the same redactor as the record's copy. The describer moves out of ideate into termsafe, so the three verbs share one rather than each carrying a copy; ideate's six sites call it unchanged in behaviour. The reading ingest's refusals echo payload values through echo(), which cleans and caps them at 120 bytes but does not redact; that is its own design across 17 sites, mixing abcd-written manifest values with payload ones, and is recorded separately rather than widened into this change. Tests watched fail first on a clean export of the base: TestReviewLifeboatRefusalsDoNotEchoTheValue (all three fields echoed) and TestDeadLetterReasonIsRedactedWhereItIsReturned (the returned reason carried the planted path, host and name). TestDescribeRefusedNeverQuotes pins the shared describer. Refs: iss-2609290033521472 Assisted-by: Claude:claude-opus-5-5
The last sibling found on the sweep for iss-2609290033521472: the reading ingest's echo() cleans and caps a payload value but does not redact it, across seventeen sites. Deferred past v0.11.1 with its reason on the record: each site needs its own call on whether the value is payload-chosen or abcd-written, which is a lane of its own; no product ruling is owed. Refs: iss-2609290043245353 Refs: iss-2609290033521472 Assisted-by: Claude:claude-opus-5-5
…ked ancestor Fixed in 976e04c, with the two siblings the sweep found (decide, scribe). Resolves: iss-2609012037137250 Assisted-by: Claude:claude-opus-5-5
…ves https Fixed in 8eba5f4. Resolves: iss-2609012037440084 Assisted-by: Claude:claude-opus-5-5
…to the person Fixed in 5b52293. Resolves: iss-2609252007448074 Assisted-by: Claude:claude-opus-5-5
Fixed in a182bb7. Resolves: iss-2609090951295881 Assisted-by: Claude:claude-opus-5-5
Fixed in f2ec7c8. Resolves: iss-173 Assisted-by: Claude:claude-opus-5-5
The generated appendix of /abcd:consult, /abcd:ingest and /abcd:prepare-this-repo said there is no shipped surface while each register row reads shipped. The generator now reads the register's Status cell: a command the tree does not register is host-delegated when its row reads shipped and unbuilt otherwise, and each gets its own sentence. TestShippedChapterNeverClaimsNoShippedSurface pins the committed chapters. Refs: iss-2609231931006041 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609231931006041 Assisted-by: Claude:claude-opus-5-5
itd-4's AC5 says `capture list --open` lists every open issue with id, slug, severity and a one-line summary; the human render printed no summary and only --json carried the body. Each row now ends with the first non-blank line of the body, sanitised and clipped to 80 runes. Refs: iss-2609240307549105 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609240307549105 Assisted-by: Claude:claude-opus-5-5
itd-6 reads READY while its spec stands on a validator stage, the oracle.review route and a command-line runner that are still planned, and on an MCP client go.mod does not carry. Its builds_on now names the three intents behind them and its Implementation status says what is missing. itd-7 waits on itd-6 and also on a dev-sync verb that does not exist and an embark route that writes only four record families; its waiting note says so. Refs: iss-2609240227236354, iss-2609240227447110 Assisted-by: Claude:claude-opus-5-5
…isites Resolves: iss-2609240227447110 Assisted-by: Claude:claude-opus-5-5
itd-1, itd-24, itd-37, itd-48, itd-50 and itd-53 cited spc-6, spc-8, spc-12, spc-17, spc-28, spc-29, spc-31, spc-43, spc-52, spc-66 and the illustrative spc-1, spc-3 and spc-7 unqualified, and each id at or below spc-70 resolves in the live store to a spec on another subject. Each was read against the live spec it collides with and is the predecessor store's, so each carries the specs charter's qualifier. The routed_from and bundle frontmatter values are data, not prose, and are left as they are. Refs: iss-2609261536147903 Assisted-by: Claude:claude-opus-5-5
Captures iss-2609290448510918, the same sweep over the intents the record did not name. Refs: iss-2609290448510918 Resolves: iss-2609261536147903 Assisted-by: Claude:claude-opus-5-5
itd-24 (planned) and the drafts itd-8, itd-9, itd-13, itd-15 and itd-19 spelled the retired `disembark to <path>` and `disembark to home` invocation; each now reads `disembark pack <repo> <path>`. itd-9's acceptance wrote voyage provenance in-tree under .abcd/development/voyage, which adr-35 moved to the operator-level store, and itd-10's question about backing up an in-tree .abcd/lifeboat/ is struck as moot. itd-22 already carried none, itd-2 is superseded and keeps its history, and itd-88's audit notes keep the old spelling as their evidence. Refs: iss-94 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-94 Assisted-by: Claude:claude-opus-5-5
Each needs a choice the record does not settle: a new site gate or an amended itd-157 criterion; release-page quotes required or dropped from the promise; a readiness check on builds_on or none; the scribe's committed-ledger condition or the code; a trufflehog adapter or no deep-scan question; a not-met heading in the owed listing or a narrower itd-53 promise; and a stop-time question gate or a headline narrowed to the question tool. None of the shipped intents is edited. Each record carries deferred_after v0.11.1 and a deferral_reason naming the one question owed. Refs: iss-2609231010077584, iss-2609232155567377, iss-2609240227236354 Refs: iss-2609261056373310, iss-2609261447395216, iss-2609262107472569 Refs: iss-2609290000171068 Assisted-by: Claude:claude-opus-5-5
The v0.7.1 brief-surface crosscheck's remaining findings at this base:
the disembark invocation is spelt `disembark pack <source-repo> <dest>`
throughout the brief, glossary and press release (sixteen sites
across eight files spelt the retired `to` operand), and the adapters and
universal-patterns chapters no longer present internal/adapter/{oracle,
history,spec,run} and internal/registry as the tree: scanner is the one
capability seam there, the rest are design targets, and the source-reader
table names internal/core/history and internal/core/spec, where the
stores live.
Refs: iss-2609020735520603
Assisted-by: Claude:claude-opus-5-5
…ined Resolves: iss-2609020735520603 Assisted-by: Claude:claude-opus-5-5
iss-2609251618079479 (the phase named at about 250 sites in 60 brief files after adr-2609212115255771 retired it) and iss-2609290448510918 (192 unqualified predecessor-store spec citations beyond the six intents this lane qualified) need no ruling, only a reading per site, which this lane's time box did not hold. Each carries deferred_after v0.11.1 and a deferral_reason saying so and naming where a docs lane starts. Refs: iss-2609251618079479, iss-2609290448510918 Assisted-by: Claude:claude-opus-5-5
The prerequisites note said `builds_on` names "the three intents" behind the four missing things, while the line names four new intent ids beside itd-2 (the review-route prerequisite rests on two). Refs: iss-2609240227236354 Assisted-by: Claude:claude-opus-5-5
The adapters chapter said each design-target seam is "introduced by the phase that first consumes it", a fresh use of the term adr-2609212115255771 retired. It now says the first intent that consumes it. Refs: iss-2609020735520603 Assisted-by: Claude:claude-opus-5-5
A `capture list` row's summary is the first non-blank body line, so a body opening with a heading rendered "— # Title". summaryNote now strips one leading CommonMark marker first: an ATX run of one to six "#" when a space, a tab or the line's end follows it, or a blockquote ">" with its optional space. A seven-hash run or a hash glued to a word is text and stays. The capture chapter's listing criterion says so. Refs: iss-2609240307549105 Assisted-by: Claude:claude-opus-5-5
Brings origin/main (baf6f84: #739..#746, integ15 as #744) into integ/land-16. Conflict resolved by hunk: commands/guard.md, where this branch's abcd-source-ledger-flip paragraph and main's rm-rf-root-or-home paragraph were both added at one anchor; both are kept, in that order. .abcd/work/DECISIONS.md merged with this branch's one entry placed before main's last line; it is moved to EOF so main's ledger comes first (DA001-DA003). Generated files regenerated with `go generate ./internal/surface/cli`; release files are main's and `## [Unreleased]` is empty. Assisted-by: Claude:claude-opus-5-5
The reading and intent-consistency ingests describe a refused payload value instead of echoing it; an undeclared key is redacted through the canonical scanner. Merged cleanly; build, vet and the intent, reading, termsafe and scanner tests pass. Refs: iss-2609290043245353, iss-2609290144116254, iss-2609290218032954 Assisted-by: Claude:claude-opus-5-5
Scribe, release, ideate and lifeboat refusals describe or redact a payload value through the new shared scanner.RedactRefusal, and the reading redactor fails closed on a degraded scanner. Merged cleanly; build, vet and the scanner, ideate, lifeboat, reading, release and scribe tests pass. Refs: iss-2609290218032954, iss-2609290300462829, iss-2609290300464268 Assisted-by: Claude:claude-opus-5-5
The release cut refuses a hard_fail privacy finding in its changelog section and release page, the persona refusal no longer quotes the name, both earlier redactors route through scanner.RedactRefusal, and the memory and lane-receipt refusals describe or redact payload values. Merged cleanly; build, vet and the implement, intent, memory, reading, release, scanner and launch tests pass, plus the matching cli tests under GITHUB_ACTIONS=true CI=true. iss-2609290405451613 stays open for its ruling. Refs: iss-2609290300462829, iss-2609290300464268, iss-2609290405381338, iss-2609290411321963, iss-2609290405451613 Assisted-by: Claude:claude-opus-5-5
The appendix generator tells a host-delegated chapter from an unbuilt one, the capture list row ends with a one-line summary, and intents and brief chapters are corrected to the tree. Conflict resolved by hunk: itd-6's Implementation status. Main (3468a4b) replaced the section's post-spc-5 prose with "Nothing of this intent is built"; this lane inserted a prerequisites note at the top of the same section and kept the old prose below it. Both are combined: the lane's prerequisites note, then main's rewritten status; the old prose stays removed as main removed it. Build, vet and the surface, capture, intent, lint and repolint tests pass, plus the matching cli tests under GITHUB_ACTIONS=true CI=true. Refs: iss-2609240227236354 Assisted-by: Claude:claude-opus-5-5
One primitive, fsutil.HomeDeclarationNames, now reads and matches the home declarations for both the rules resolver and the transcript store, with a fold seam each package's tests can force; the bundled COMMITTING domain gains the two attribution rules; test and nit debt is paid. Conflict resolved by hunk: internal/fsutil/home.go imports. Main's descriptor-held declaration read added "syscall"; this lane added the termsafe import for HomeDeclarationNames. Both are kept. The function bodies merged without conflict: HomeDeclarationNames calls main's ReadHomeDeclaration unchanged in signature, and main's new refusal kinds (DeclarationUnreadable for a swapped leaf or an invalid rel, DeclarationExposed, which ReadHomeDeclaration never returns) fall to its default "could not be read" clause. Build, vet and the fsutil, rules, history, capture, grounds, intent, lint, scanner and site tests pass, plus the matching cli tests under GITHUB_ACTIONS=true CI=true. Assisted-by: Claude:claude-opus-5-5
REPPL
enabled auto-merge
September 29, 2026 08:40
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 nine reviewed lanes as one change. Together they close 52 open records. Six are small bugs: a verb named twice in a message, a routed ingest that gave the wrong reason, a test evaluator that disagreed with GitHub, site verbs that misread the checkout from a subdirectory, a launch listing that read the wrong attributes, and a git answer taken on trust. Sixteen are on security paths: store writes that could follow a committed symlink out of the checkout, a citation redirect that could downgrade to plaintext, a hook that would run a world-writable binary, a flip only the person should make, local files the layout check missed, a release cut that could write a secret into public release text, and refusals across the host-payload ingests that echoed the value they refused. Eight make messages, help output and tests say the right thing. Fourteen bring records, documents, one redactor and the surface appendix back in line with the tree. Eight pay down test and code debt, including one shared reader for the home declarations.
drainBugs (six bugs). A routed command names its verb once, not as "abcd abcd". A routed
reading ingestwhose output cannot be read gives the read's own reason. The workflow test evaluator's==follows the Actions runner, sotrue == 'true'no longer reads true.abcd site,site buildandlint sitefind the checkout from a subdirectory, and the default output is the checkout'ssite/. The launch archive listing reads the revision's own.gitattributes, asgit archivedoes, and refuses by name on a git older than 2.40 when it cannot. The primary-worktree resolution accepts only one absolute path per answer, so an old git fails closed.drainSec (security). The spec, ADR and scribe stores refuse a symlinked ancestor instead of writing through it. An https citation that redirects to plaintext is not followed; it is queued as
blocked. The five PATH-resolving hooks refuse anabcdbinary that any local user could rewrite. The guard leavesabcd source ledger --flipto the person. ideate, lifeboat review and the intent audit describe a refused value by its size instead of quoting it. The layout check findsNEXT.mdat any depth. Two records were already fixed at an earlier release and are closed on that evidence. The two sibling ingests it left echoing, reading and intent consistency, are fixed by drainEcho below.drainUx (wording and help). A reframed idea is told its own next step.
abcd --helpandabcd --help --agentprint the person's command groups identically, andabcd help --agentnames the spelling that works.abcd embarknames the lifeboat entry it cannot open. A secondlaunch shipbetween a cut and its tag refuses as "release in flight" instead of asking for a flag it does not take.intent audit --issue-driftsays which ledger it read. Underneath, one rule says what opens an HTML tag, and the mint-lock tests watch real contention instead of timing a wait.drainDrift2 (drift). The CLI's error scrub and the store redactors agree on where a home path ends, so a path such as
<home>.or<home>-oldis redacted by both. The scrub redacts more than before and never less. The writing-style guide, the verification matrix, itd-6, the research inventory and a CI job comment now match the tree. A correction to the sources-refresh decision is appended to the decision log, and nine resolved records get a dated correction section. One open record remains: the two redactors still disagree on the leading boundary for a home under a longer root.drainEcho (refusal echoes, first pass). When
abcd reading ingestorabcd intent consistency ingestrefuses a payload, the message describes the refused value by its length instead of repeating it, so a token or a home path pasted into the wrong field no longer reaches the terminal or the session transcript. The message still names the field and, where there is one, the finding, so the fault can be found. The name of a field the payload should not carry is kept, after the privacy scanner has redacted it.drainEcho2 (refusal echoes, the rest of the class). The same rule reaches the scribe, release, ideate and lifeboat ingests through one shared definition of "redact a refusal". A value is quoted only when it has a shape abcd checks (a record handle, a digest, a tag); anything else is described. When the scanner cannot be built or runs degraded, the refusal describes rather than echoes.
drainEcho3 (release text and the last echoes). A release cut scans the changelog section and the release page it is about to write, and refuses a token, a key or your own home path in either with a new reason code,
privacy, before anything is written. The persona check no longer quotes the name it refused, and the memory page and lane receipt refusals describe or redact what they refuse. One question stays open for the product thinker: whether release text should also refuse another person's home path or a bare GitHub username, which the scanner grades as warnings.drainDrift3 (drift). The three host-delegated surface chapters stop claiming there is no shipped surface.
capture listrows end with a one-line summary of the record. Intents and brief chapters are corrected to the tree: predecessor spec ids are qualified, the lifeboat intents spelldisembark pack, itd-6 and itd-7 name their prerequisites, and the last brief-crosscheck findings are fixed. Seven records are deferred for a product ruling and two on size, each with its reason.drainDebt (debt). One reader decides whether a home declaration (
~/.abcd/trusted-roots,~/.abcd/local-transcript-roots) names a checkout, where the rules resolver and the transcript store each kept their own copy. The bundled COMMITTING rules add two attribution rules: commit as the human, and re-read and strip a session URL or tool footer after posting. The rest adds the tests the records asked for and fixes small wording and doc drift.Reviews: drainBugs SHIP, fix2 verified SHIP. drainSec SHIP (security review), then fix2 done (its LOW fixed). drainUx SHIP. drainDrift2 SHIP, then fix2 done (its MINOR fixed). drainEcho SHIP (security review; its two LOWs fixed in drainEcho2). drainEcho2 SHIP (security review; its two LOWs fixed in drainEcho3). drainEcho3 SHIP (security review). drainDrift3 SHIP, then fix2 done (its two MINORs fixed). drainDebt SHIP.
Integration. The first build merged four lanes with one conflict, in
hooks/hooks.json: each of the five PATH-resolving hook shims is a single JSON line that both main and drainSec edited (main added the refusal of a symlinked~/.abcd, drainSec the check of the binary's own mode), and both checks stand. The re-merge brought main (#744) in and added five lanes, with three conflicts, each combined by hunk. Incommands/guard.mdthis branch's paragraph on the source-ledger flip and main's paragraph on a recursive delete of the root or home were added at one place; both are kept. In itd-6's implementation status, main rewrote the section to say nothing of the intent is built and drainDrift3 added a prerequisites note; both are kept, and the old text stays removed as main removed it. Ininternal/fsutil/home.gomain's descriptor-held declaration read and drainDebt's shared declaration reader each added an import; both are kept, and the reader calls main's read unchanged. The decision log carries main's entries first and this branch's one entry at the end. The generated CLI reference was regenerated after every merge. The reading windows were measured again at the merged tip and each still has at least 1% headroom, so none changed: widening 1,354,784 tokens (window 1,370,000), entailment 391,582 (400,000), detection 1,363,820 (1,380,000).Resolves: iss-173
Resolves: iss-241
Resolves: iss-2608210923437502
Resolves: iss-2608291924452604
Resolves: iss-2608292037564347
Resolves: iss-2608301251394412
Resolves: iss-2608301301041887
Resolves: iss-2608301908288212
Resolves: iss-2609012037125129
Resolves: iss-2609012037137250
Resolves: iss-2609012037440084
Resolves: iss-2609020352438590
Resolves: iss-2609020735520603
Resolves: iss-2609020833531987
Resolves: iss-2609090951283654
Resolves: iss-2609090951295881
Resolves: iss-2609090951297149
Resolves: iss-2609100508573400
Resolves: iss-2609231101102072
Resolves: iss-2609231526392449
Resolves: iss-2609231931006041
Resolves: iss-2609240227150859
Resolves: iss-2609240227447110
Resolves: iss-2609240307549105
Resolves: iss-2609251235119402
Resolves: iss-2609251358062952
Resolves: iss-2609251606543515
Resolves: iss-2609251606553359
Resolves: iss-2609251616248870
Resolves: iss-2609251616310535
Resolves: iss-2609251645374557
Resolves: iss-2609251645376019
Resolves: iss-2609251728586400
Resolves: iss-2609251750202525
Resolves: iss-2609251842111403
Resolves: iss-2609252004013212
Resolves: iss-2609252007448074
Resolves: iss-2609252055533837
Resolves: iss-2609252117203691
Resolves: iss-2609260933592838
Resolves: iss-2609261034583909
Resolves: iss-2609261423210391
Resolves: iss-2609261536147903
Resolves: iss-2609290033521472
Resolves: iss-2609290043245353
Resolves: iss-2609290144116254
Resolves: iss-2609290218032954
Resolves: iss-2609290300462829
Resolves: iss-2609290300464268
Resolves: iss-2609290405381338
Resolves: iss-2609290411321963
Resolves: iss-94
Refs: iss-2609290055092630
Refs: iss-2609290405451613
Refs: iss-2609231010077584
Refs: iss-2609232155567377
Refs: iss-2609240227236354
Refs: iss-2609261056373310
Refs: iss-2609261447395216
Refs: iss-2609262107472569
Refs: iss-2609290000171068
Refs: iss-2609251618079479
Refs: iss-2609290448510918
Assisted-by: Claude:claude-opus-5-5