Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .abcd/work/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2109,6 +2109,31 @@ parallel-agent merge contention bites.
pronouns in two planned intents. Recorded open: the memory store-lock S_IFMT
mask (rides iss-129), the quoted-enum impact split, and a deferral-currency
detector seed.
- 2026-08-26 — Bug-hunt round 9 (bughunt-a): baseline green after unshallowing
the environment's clone. Five parallel hunters returned 21 candidates across
the four dimensions; per-finding adversarial refutation confirmed 5
substantive and 4 fixable nitpicks and refuted or deferred 12. Fixed and
resolved with provenance: the memory-store guarded-read sweep (every reader
outside ingest followed a committed symlink unbounded — five reproduced
hangs), the issue-resolution gate's swallowed git probes (a dubious-ownership
refusal read as an empty ledger, exit 0), lint-config severity validation
with strict decoding (off-enum severities counted toward no exit code),
the docs-lint renderer's unsanitised config-derived fields, runBounded's
whole-buffer trim corrupting the first NUL-list entry, privacy-hygiene's
silent skip of unreadable tracked files, the terminology page's corpus
"ships" overclaim, the intake S4 container pin below the module toolchain,
and the site command description's missing check-write disclosure. Recorded
open: the scanIntentTree ENOENT-parting alignment (all claimed triggers
proved closed upstream). Refuted with prior art: the spc-28 planned/drafts
sweep (iss-94 convention), the verification-matrix capture-promote row
(iss-2608231346137587 embargo), the attribution fence guard (iss-270
wontfix; zero verdict flips across all historical PR bodies), isHexSHA
40-hex (prior round's refutation stands), frontmatterOpen's comment latch,
the skew-meta precedence claim, guard check's exit-2-on-disabled (specified),
the prepare-this-repo bucket gloss, the MADR label, the itd-27 pronoun
candidate (a cited real person is outside the persona rule), and the
record-lint job-name mismatch (mirror is correct by construction).
nitpicks-only: no.
- 2026-08-26 — Bug-hunt round 9 (bughunt-b): baseline green on the unshallowed
clone. Five parallel hunters, per-finding adversarial refutation: 12
substantive and 5 nitpick findings confirmed and fixed, 4 refuted or prior
Expand Down
8 changes: 7 additions & 1 deletion .abcd/work/intake.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ code-owner path, and S1 says so explicitly.
## S4 — review runs contained, never bare

```sh
docker run --rm --network=none -v "$PWD:/src" -w /src golang:1.25
# Provision modules first: the module cache mount is what lets the build work
# once the network is cut, and the image tag MUST track go.mod's toolchain
# (an older container refuses a newer `go` directive, and --network=none
# blocks the GOTOOLCHAIN auto rescue). Bump the tag with go.mod.
go mod download
docker run --rm --network=none -v "$PWD:/src" \
-v "$(go env GOMODCACHE):/go/pkg/mod:ro" -w /src golang:1.26.7
```

`go test` executes contributor Go via `init()` and `TestMain`; a bare run on a
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
schema_version: 1
id: "iss-2608261533419897"
slug: "scanintenttree-stat-swallow-alignment-with-fail-closed-doctrine"
severity: "nitpick"
category: "observation"
source: "agent-observation"
found_during: "bughunt-a round 9"
found_at: "internal/core/lint/lint.go"
---

scanIntentTree and the two spec-store stat probes in internal/core/lint swallow every stat error as tree-absent, unlike scanIssueLedger and scanRecordStores which part ENOENT from real faults — the doctrine the round-9 ScanSpecLinks fix states as a tree that is present but cannot be read IS a fault. No leg is currently reachable past markdownFiles, os.ReadDir, and the armed delivery_state floor (adjudicated: the claimed vacuous-blocker triggers are all closed one line later or upstream), so this is a consistency alignment, not a live defect: part ENOENT from other errors at the three sites and stop discarding WalkDir errors, matching the sibling scanners. Recorded for a scoped consolidation rather than fixed mid-hunt.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
schema_version: 1
id: "iss-2608261532379188"
slug: "memory-store-readers-follow-committed-symlinks-unguarded"
severity: "major"
category: "security"
source: "agent-observation"
found_during: "bughunt-a round 9"
found_at: "internal/core/memory/bare.go"
resolution: "Every memory-store and ingest-source read routes through fsutil.ReadGuarded with in-package caps; WalkDir crawls skip non-regular entries; watched-fail symlink tests on bare, lint, budget, writer and licence paths"
impact: fix
resolved_by:
commit: "9e8f3235"
---

Every memory-store reader outside ingest follows a committed symlink unbounded. internal/core/memory/ingest.go adjudicates the store as a trust boundary (maxMemoryPageBytes: a committed page symlink to /dev/zero would hang or OOM the CLI) and routes its own reads through fsutil.ReadGuarded, but the sibling readers kept raw os.ReadFile: readOrEmpty (bare.go, reached with no type check for index.md/contradictions.md), bareHeadroomLines and the lint WalkDir sweeps (lint.go — WalkDir yields symlinks as non-dir entries and the read follows them), loadQuotationBudget (coverage.go reading config.json), the .coverage_index.json reads (bare.go, coverage.go — its literal sibling .sources_index.json is guarded), triStateRead (writer.go), and the ingest licence probes (provenance.go manifestLicence/licenceFileLicence under an arbitrary sourceRoot). Reproduced: five distinct hangs via committed mode-120000 fixtures against abcd memory and abcd memory lint. The ReadDir-filtered sites (ask.go QueryPages, barePageInfos) carry the ReadDir-to-open swap window and no size cap — the exact TOCTOU ingest.go documents closing. Same class as the resolved lint.LoadConfig unguarded read (major/security). Detector: watched-fail tests planting a symlinked page/index; acceptance: every memory-store and source read routes through the guarded primitive.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
schema_version: 1
id: "iss-2608261532488176"
slug: "issue-resolution-gate-git-probe-failures-read-as-clean"
severity: "major"
category: "bug"
source: "agent-observation"
found_during: "bughunt-a round 9"
found_at: "scripts/check-issue-resolution.sh"
resolution: "All four git probes on the gate rc-check and exit 2 with stderr surfaced; empty-ledger pass stays loud; cases pin both fault shapes and the clean pass"
impact: internal
resolved_by:
commit: "b3d4c18c"
---

The issue-resolution gate reports OK having scanned zero records whenever a git probe fails. scripts/check-issue-resolution.sh ledger arm: cd "$(git rev-parse --show-toplevel)" collapses to cd '' (rc 0) when the substitution fails under set -e; the ls-tree listing carries || true so a git failure reads as an empty ledger and check_ledger returns 0 with 'no ledger records — nothing to check'; and the is-shallow probe compares a failed substitution against 'true', so the exit-2 environment-fault arm is disarmed by the same fault it exists to report. Reproduced: GIT_TEST_ASSUME_DIFFERENT_OWNER=1 (git's dubious-ownership refusal, the reachable local form for containers/sudo/devcontainers) turns 113-records-checked into OK exit 0; from a non-git cwd likewise. The commits arm fails closed (bare assignment) — divergence within one file, and RS003 is the sole detector for resolved_by.commit shas rewritten by the squash/rebase merges the repo permits. Same class as the round-9 check-reviews.sh fail-closed rewrite (every git probe rc-checked, exit 2), which left this sibling untouched. Detector: cases asserting exit 2 under a failing git; acceptance: every git probe on the ledger arm is rc-checked and a git fault exits 2, with the legitimate empty-ledger pass kept loud.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
schema_version: 1
id: "iss-2608261533033587"
slug: "docs-lint-renderer-prints-config-severity-and-ruleid-raw"
severity: "nitpick"
category: "security"
source: "agent-observation"
found_during: "bughunt-a round 9"
found_at: "internal/surface/cli/cli.go"
resolution: "The renderer sanitises Severity and RuleID alongside File and Message and the enum-constrained comment is corrected; watched-fail ESC-in-token-id test"
impact: fix
resolved_by:
commit: "b215c8c6"
---

docs lint's findings renderer asserts 'Severity/RuleID are enum-constrained' and prints both unsanitised, but neither is constrained: Severity is verbatim committed-config text and RuleID for the banned_tokens family is the token's configured id. The config file is adjudicated a trust boundary in internal/core/lint/config.go (cross-repo-clonable), and the same surface sanitises the enum-validated banlist severity — so the one unvalidated pair on the line is the unsanitised one. ToUpper does not neutralise OSC/CSI escapes (verified), and RuleID gets no ToUpper at all. Sibling scope: the recorded cmd/record-lint File/Message gap asserts the abcd CLI renderer sanitises — true for File/Message only. Acceptance: the renderer sanitises every config-derived field or the loader makes the comment true by validation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
schema_version: 1
id: "iss-2608261533033894"
slug: "lint-config-severity-unvalidated-findings-count-toward-no-exit"
severity: "minor"
category: "bug"
source: "agent-observation"
found_during: "bughunt-a round 9"
found_at: "internal/core/lint/config.go"
resolution: "LoadConfig decodes strictly (DisallowUnknownFields) and refuses an enabled rule or token with an off-enum severity; both live configs load unchanged"
impact: fix
resolved_by:
commit: "ac06c817"
---

A record-lint or docs-lint rule whose severity is missing, misspelt, or off-enum emits findings that print but count toward no exit code, so the gate exits 0 beside a non-empty findings list. internal/core/lint/config.go decodes with a plain json.Unmarshal (a misspelt key silently zero-values the field) and validates only banned-token successors; the exit paths in cmd/record-lint and abcd docs lint count Severity == blocker verbatim. The sibling engines fail closed on exactly this vocabulary (repolint Evaluate refuses a finding severity outside error/warn as a rule bug; guard Validate rejects an unknown tier on the committed guard.json; banlist AddPublic refuses to write a severity lint will happily read). The config is a documented trust boundary in this very file. The same underlying cause silently disarms a rule via a misspelt enabled key. All 27 live rules spell both correctly today — latent, one character from silent. Detector: watched-fail test loading a config with an off-enum severity; acceptance: LoadConfig refuses an enabled rule or token whose severity is outside the engine's enum, and unknown config keys are refused.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
schema_version: 1
id: "iss-2608261533173466"
slug: "intake-s4-container-pin-below-module-toolchain-and-no-module-cache"
severity: "minor"
category: "documentation"
source: "agent-observation"
found_during: "bughunt-a round 9"
found_at: ".abcd/work/intake.md"
resolution: "S4 pins the image to the module toolchain, pre-fetches modules and mounts the module cache read-only before the network is cut"
impact: internal
resolved_by:
commit: "7e274002"
---

The intake protocol's contained-review stage pins a container that cannot build the module. The S4 recipe pins golang:1.25 while go.mod declares a higher toolchain, and --network=none in the same line blocks the GOTOOLCHAIN auto rescue, so the one command the protocol hands a maintainer fails at the toolchain check and S5's build-or-INCONCLUSIVE tri-state misreads the stale pin as an inconclusive contribution. Drift, not born broken: the pin matched go.mod when written; the toolchain bump that advertised lockstep did not include this file, and the work tier sits outside every lint-configured tree. The recipe also lacks any module-cache provision, so even a corrected tag fails on dependency fetch under --network=none — the line has never worked as written. S4 is load-bearing: a recorded maintainer decision cites it as the mitigation that closed a review-agent exposure question. Acceptance: the pin tracks the module toolchain and the recipe provisions modules before the network is cut.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
schema_version: 1
id: "iss-2608261533174500"
slug: "terminology-rag-row-claims-the-sources-corpus-ships"
severity: "minor"
category: "documentation"
source: "agent-observation"
found_during: "bughunt-a round 9"
found_at: "docs/reference/terminology.md"
resolution: "The RAG row states the corpus as a user-tier script MVP with core absorption cited, matching the script-first-mvp principle"
impact: fix
resolved_by:
commit: "6d9a7b24"
---

docs/reference/terminology.md's RAG row claims 'the sources corpus ships the script-first version' — a false shipped-ness claim on a page whose sibling rows use ship as a precise delivery marker (No MCP server ships today; abcd ships zero skills). Nothing corpus-related is in the repo or any released artefact; the consult and ingest commands refuse when the corpus is absent and point at a README no user has; and the committed script-first-mvp principle states a script MVP never ships as product behaviour, naming this corpus tooling as the live instance. Same defect class as the open Memory-row overstatement on the same page. Acceptance: the row states the corpus as a user-tier script MVP with core absorption tracked, not as shipped behaviour.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
schema_version: 1
id: "iss-2608261533290815"
slug: "privacy-hygiene-silently-skips-unreadable-tracked-files"
severity: "nitpick"
category: "bug"
source: "agent-observation"
found_during: "bughunt-a round 9"
found_at: "internal/core/repolint/rule_privacy.go"
resolution: "Open failures are classified: EACCES/EPERM/EIO warn not-scanned, absent paths and symlink-shaped refusals stay silent by design; polarity table pinned"
impact: fix
resolved_by:
commit: "e6f90055"
---

repolint privacy-hygiene silently skips a tracked file it cannot open: readTrackedFile returns not-ok with no oversize marker on an open failure, and the caller emits a finding only for the oversize case — so an EACCES/ENOTDIR tracked file produces no finding and the rule reports the repository clean, against the engine contract that a check that cannot run must not be silently reported as passing. The oversize arm of the same helper got exactly this fix; the open arm beside it stayed silent. Constraint on the fix: TrackedFiles lists index entries, so ENOENT (deleted-in-worktree, sparse checkout) must stay silent and only genuine unreadability warns. Acceptance: a non-ENOENT open failure yields a not-scanned warn finding, watched-fail via an ENOTDIR fixture.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
schema_version: 1
id: "iss-2608261533297309"
slug: "runbounded-trimspace-corrupts-first-entry-of-nul-lists"
severity: "nitpick"
category: "bug"
source: "agent-observation"
found_during: "bughunt-a round 9"
found_at: "internal/gitutil/repo.go"
resolution: "runBounded trims the trailing side only; NUL-list first-entry fidelity pinned by a leading-space fixture"
impact: fix
resolved_by:
commit: "89603e7c"
---

gitutil.runBounded returns strings.TrimSpace over the whole capture buffer, and lifeboat's pathIsIgnored NUL-splits that string from ls-files -z to build the not-ignored set. The -z form exists so whitespace in filenames cannot desync the list, but the shared trim strips leading whitespace off the first entry, so a repo whose first-sorting path begins with space/tab/CR/newline gets that one file silently classified ignored and dropped from the evidence walk — the quiet-evidence-loss shape the adapter's own contract forbids. Verified: -z disables quoting, the trailing NUL survives (NUL is not IsSpace), only the leading side is damaged; TrackedFiles bypasses the trim via raw Output. One-path blast radius, rare shape. Acceptance: NUL-list consumers receive the raw buffer (or a trailing-only trim) with a watched-fail test on a leading-whitespace name.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
schema_version: 1
id: "iss-2608261533419396"
slug: "site-command-description-omits-checks-render-write"
severity: "nitpick"
category: "documentation"
source: "agent-observation"
found_during: "bughunt-a round 9"
found_at: "commands/site.md"
resolution: "The description discloses check's render-if-absent write alongside bare and build"
impact: fix
resolved_by:
commit: "0458d586"
---

commands/site.md's frontmatter description enumerates write posture for the bare and build forms only, while check — added later with the argument-hint updated in the same diff — renders the whole site into an output directory that has no index.html. The body documents this correctly; the description line is a stale roster, and it drops the counter-intuitive form. House style names every form's posture, surprising ones explicitly (identity, version). Acceptance: the description discloses check's render-if-absent write.
2 changes: 1 addition & 1 deletion commands/site.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: site
description: Render this repository's website — the landing page composed from repository text under the single-source rule, and the record export derived from the record, git history and the changelog — by invoking the abcd binary. The bare form performs zero writes; build writes only inside its output directory.
description: Render this repository's website — the landing page composed from repository text under the single-source rule, and the record export derived from the record, git history and the changelog — by invoking the abcd binary. The bare form performs zero writes; build and check write only inside the output directory (check renders the site first when the directory has no index.html).
argument-hint: "[build|check]"
---

Expand Down
Loading