Skip to content

feat: ground open-questions, naming and internals at the conventions tier (itd-95, itd-96)#128

Merged
REPPL merged 14 commits into
mainfrom
feat/itd-95-96-conventions-adapters
Jul 21, 2026
Merged

feat: ground open-questions, naming and internals at the conventions tier (itd-95, itd-96)#128
REPPL merged 14 commits into
mainfrom
feat/itd-95-96-conventions-adapters

Conversation

@REPPL

@REPPL REPPL commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Lands both conventions-tier adapter intents together. Rolls up the reviewed and merged #124 (itd-95) and #126 (itd-96); this PR carries no new work of its own.

ID collision with the concurrent programme, resolved

Both this programme and the derived-versioning programme minted spc-10/spc-11 on the same day from branches cut off the same base, and both minted iss-110. abcd mints the next free id from the local tree, so concurrent branches always take the same number and neither can see it.

The spec collision breaks the bidirectional intent-spec link in both directions at once — whichever spec wins the store lookup leaves the other intent's link dangling, so on the naively merged tree abcd intent ready failed for both itd-95 and itd-67. The other programme's ids are already on main, so this branch renumbered: spc-10 -> spc-12 (itd-95), spc-11 -> spc-13 (itd-96), iss-110 -> iss-116. All four affected intents (itd-95, itd-96, itd-67, itd-73) now report READY on the merged tree.

The issue collision was caught automatically by record-lint's issue_id_unique blocker at the push gate; the spec collision was not, because specs have no equivalent lint — iss-115 captures that gap.

The commit messages and PR bodies of #124 and #126 still name the old ids. That history was accurate when written and is left alone.

Resolves ledger issues iss-99 and iss-100 (.abcd/work/issues/open/) — this repository tracks issues in the record, not on GitHub.

What lands

Three brief sections that blanked on any repository without an .abcd/ record now ground from the repository's own files:

  • evidence/open-questions (itd-95) — a marker scan citing path:line for TODO, FIXME, XXX, HACK, BUG.
  • constraints/naming (itd-96) — a dedicated naming document, falling back to the glossary with an explicit qualifier at lower confidence.
  • internals (itd-96) — architecture prose plus a package-layout scan.

Plus SourceContext.WalkFiles, the bounded recursive walk the probe lacked, mirroring embark.go's existing fs.WalkDir(root.FS(), …) rather than adding a second traversal idiom.

All three ceiling at StatusPartial, matching what each mapping row already predicts for the conventions tier, so mapping.go — the brief-to-lifeboat contract — is unchanged and signal strength lives in Confidence. No new dependency. CHANGELOG.md is untouched: both intents carry impact: additive, so the entry derives from the intent corpus.

Evidence

Probe of a record-less fixture repo (README, NAMING.md, ARCHITECTURE.md, an internal/ tree, work markers, no .abcd/):

constraints/naming      | partial | medium | conventions  → NAMING.md
evidence/open-questions | partial | low    | conventions  → 4 markers across 2 files
    internal/core/convert.go:3 (TODO)   internal/core/convert.go:6 (FIXME)
    internal/store/store.go:3 (XXX)     internal/store/store.go:4 (HACK)
internals               | partial | high   | conventions  → ARCHITECTURE.md,
    cmd/wreck/, internal/core/, internal/store/
glossary                | blank                            → still convGlossarySource's, no duplicate adapter

Every file in that tree hashed identically before and after the probe — disembark is read-only over its source, asserted byte-for-byte rather than by argument.

Review trail

Each intent was built detector-first and reviewed by independent ruthless and security agents before its PR. Both rounds returned FIX-FIRST; the findings that mattered:

  • Fabricated evidence — the marker pattern matched the XXX-XXX-XXX redaction shape its own comment claimed it rejected, so a support phone number became a cited "open question". Fixed and pinned by a spelling table.
  • Unbounded aggregate read — the file cap and the per-file cap multiply to ~195 GiB with nothing bounding the product. An aggregate read budget now bounds it.
  • Denial of service on a hostile tree — the walk's cap counted regular files only, so a tree of directories holding nothing regular never reached it; and os.Root re-resolves each directory one component at a time, making a directory chain quadratic in depth. A depth-1500 chain did not finish in two minutes; it now walks in 0.4s. Directories are counted against the same cap and descent is capped at maxWalkDepth.

Running through all of it: a bound that fires must say so. Every cap is named in the cited evidence, and a blank drawn from a scan that was cut short says the scan was cut short rather than generalising over files it never opened — a blank is a first-class result (adr-35) only while it is trustworthy.

Decisions recorded

.abcd/work/DECISIONS.md gains two entries a future session would otherwise re-litigate: that the mapping table's per-tier columns are a ceiling adapters honour rather than a loose prediction (with the revisit path — amend the row first), and that any whole-tree traversal must state which of {entries, depth, aggregate bytes} bounds it, because a per-item cap and a count cap multiply and their product is not a bound.

Open captures

iss-116 (the walk skip set knows only Node and Go, so a Python .venv is cited as the team's own markers and can consume the walk cap ahead of src/), iss-111 (the bare-word alternative matches prose about markers — 42 such hits on this repository), iss-112 (fs.WalkDir's per-directory ReadDir is unbounded, so the file cap does not bound walk memory as its comment claims), iss-114 (os.Root path re-resolution is the quadratic underneath the depth cap). iss-113 resolved in-branch.

REPPL added 11 commits July 21, 2026 12:35
The maintainer signed off the design for both conventions-tier adapter
intents in a planning interview, so they move drafts -> planned and mint
their specs, per the itd-94 readiness gate (an unplanned intent cannot be
implemented). Spec bodies and the implementation follow on this branch.

Assisted-by: Claude:claude-opus-4-8
The minted spc-10 stub carried no design record, so `abcd intent ready
itd-95` failed its spec_body check and the intent could not be
implemented. Write the spec body: the bounded WalkFiles primitive over
root.FS(), the conventions-tier marker adapter, and a decided answer for
every one of itd-95's open questions (marker set, tier, scan scope,
caps, output shape, dedup, status ceiling, native-tier interaction).

impact: additive on the intent records the change class on the intent
itself rather than in CHANGELOG.md, so the entry is derived from the
intent corpus instead of hand-maintained.

Assisted-by: Claude:claude-opus-4-8
evidence/open-questions was grounded by exactly one adapter, the native
one, so every repository without an .abcd/ record fell through to a blank
even when its source was dense with TODO and FIXME. The mapping row
already named those markers as a read and predicted a partial conventions
status; nothing delivered it.

Add SourceContext.WalkFiles, the bounded recursive walk the probe lacked,
mirroring embark.go's fs.WalkDir over root.FS() rather than inventing a
second traversal idiom. It is contained by os.Root, skips symlinks
instead of following them (a probed foreign tree carries ordinary
symlinks, unlike a packed lifeboat where one is a trust violation), skips
VCS/dependency/generated trees, and reports truncation at its file cap.

Add convOpenQuestionsSource on top of it: a marker scan citing
path:line for TODO, FIXME, XXX, HACK and BUG. Its ceiling is
StatusPartial by construction — a marker says something is unfinished,
not what the question is — with volume moving the confidence.

The scan is bounded on three dimensions, not two: the file cap and the
per-file cap multiply to 195 GiB, so an aggregate read budget bounds the
product the way maxEmbarkTotalBytes does on the embark side. Every bound
that fires is named in the evidence, and a blank drawn from a scan that
was cut short says the scan was cut short rather than generalising over
files it never opened.

Captured, not fixed: iss-110 (the skip set knows only Node and Go),
iss-111 (bare-word matches prose about markers), iss-112 (fs.WalkDir's
per-directory ReadDir is unbounded).

Assisted-by: Claude:claude-opus-4-8
The minted spc-11 stub carried no design record, so `abcd intent ready
itd-96` failed its spec_body check. Write the spec body, resolving every
one of itd-96's open questions: a dedicated naming document is naming's
primary source with the glossary as an explicitly-qualified fallback,
the preference order for architecture docs, the package-layout scan on
top of spc-10's WalkFiles, and the thresholds separating the confidence
levels.

Both new sections ceiling at partial, matching what the mapping row
already predicts for the conventions tier, so the strength of the signal
is carried by confidence rather than by inflating the status. That keeps
mapping.go — the brief-to-lifeboat contract — unedited.

impact: additive records the change class on the intent rather than in
CHANGELOG.md.

Assisted-by: Claude:claude-opus-4-8
constraints/naming and internals were each grounded by exactly one
adapter, both native, so a repository with a NAMING.md and an
ARCHITECTURE.md sitting in plain sight still reported both sections
blank for want of an .abcd/ record.

convNamingSource reads a dedicated naming document first and falls back
to the glossary, cited with an explicit qualifier at lower confidence: a
glossary defines terms rather than ruling on what may be renamed, so it
is real but weaker evidence. The glossary section keeps its own adapter
and its own row; naming is visibly a derived reading, never a duplicate.

convInternalsSource combines architecture prose, measured with the
README prose threshold, with a package-layout scan over WalkFiles. Both
sections ceiling at partial, matching what the mapping row already
predicts for this tier, so the strength of the signal lives in the
confidence rather than in an inflated status.

Also harden the walk itself, which the security review showed the tier
gate now makes reachable from a source tree carrying no other
conventional file. Its cap counted regular files only, so a tree of
directories holding nothing regular never reached it; and every
directory is resolved from the containment root one component at a time,
so a chain of directories costs the square of its depth. A depth-1500
tree took over two minutes and now takes 0.4s. Directories are counted
against the same cap and descent is capped at maxWalkDepth, both
reported through the existing truncated flag.

A layout scan cut short by a cap no longer reports as a complete one:
the truncation note is no longer conditional on having found a package,
and a blank drawn from a partial walk says the walk was partial.

Captured, not fixed: iss-113 (the marker adapter's truncation wording
names only the file cap), iss-114 (os.Root path re-resolution is the
quadratic underneath the depth cap).

Assisted-by: Claude:claude-opus-4-8
An independent security review of the itd-96 branch found the walk this
spec landed can be made to run without terminating on a hostile tree,
which is exactly the population disembark exists to serve: an abandoned
repository nobody vouches for.

Two dimensions were unbounded. The cap counted regular files only, so a
tree of directories holding nothing regular never reached it. And every
directory is opened by resolving its path from the containment root one
component at a time, so a chain of directories costs the square of its
depth to walk — a depth-1500 chain, trivial to create, did not finish in
two minutes and now takes 0.4s.

Directories are now counted against the same cap, and descent is capped
at maxWalkDepth. The depth cap prunes the chain rather than the tree, so
what sits above it is still walked. Both report through the existing
truncated flag, so every loud-staging path already in place names them,
and the marker adapter's truncation wording now names all three bounds
instead of the file cap alone.

Captured: iss-114 (os.Root path re-resolution is the quadratic
underneath the depth cap; per-directory OpenRoot would make each open
constant-time).

Assisted-by: Claude:claude-opus-4-8
The bound-the-walk fix the security review forced belongs with the
WalkFiles primitive, so it landed on the parent branch. Merging it back
leaves this branch's diff as the itd-96 adapters alone.
The marker adapter's truncation evidence named only the file cap; it now
names all three bounds the walk enforces, fixed on the itd-95 branch
alongside the change that made the wording imprecise.

Assisted-by: Claude:claude-opus-4-8
Two decisions from the itd-95/itd-96 work that a future session would
otherwise re-litigate: that the mapping table's per-tier columns are a
ceiling adapters honour rather than a loose prediction, and that a walk
over a foreign tree must name which of entries, depth, and aggregate
bytes bounds it — a per-item cap and a count cap multiply, and their
product is not a bound.

Assisted-by: Claude:claude-opus-4-8
feat: ground naming and internals from a repo's conventional docs (itd-96)
feat: ground open-questions on a repo's in-code work markers (itd-95)
@REPPL
REPPL enabled auto-merge July 21, 2026 18:36
@REPPL
REPPL disabled auto-merge July 21, 2026 18:38
REPPL added 3 commits July 21, 2026 21:26
Both this programme and the derived-versioning programme minted spc-10
and spc-11 on the same day, from branches cut off the same base. abcd
mints the next free id from the local tree, so concurrent branches
always mint the same number and neither branch can see the collision.

It surfaces only on merge, and it breaks the bidirectional intent-spec
link in both directions at once: whichever spec wins the store lookup
leaves the other intent's link dangling, so `abcd intent ready` failed
for itd-95 and itd-67 simultaneously on the merged tree.

The derived-versioning programme's ids are already on main, so this
programme renumbers: spc-10 -> spc-12 (itd-95), spc-11 -> spc-13
(itd-96), with both intents' spec_id and every reference in the record
updated to match. The commit messages and PR bodies of #124 and #126
still name the old ids; that history is accurate as of when it was
written and is left alone.

Captured iss-115: specs need the uniqueness lint issues already have,
in record-lint so CI catches a collision on the PR rather than a human
catching it at merge.

Assisted-by: Claude:claude-opus-4-8
…tions-adapters

# Conflicts:
#	.abcd/work/DECISIONS.md
Same collision as the specs, one directory over: main captured its own
iss-110 while this branch was open, and abcd mints the next free issue
id from the local tree, so both branches took 110.

The difference is that issues already have the uniqueness lint iss-74
added, so record-lint caught this at the push gate instead of letting a
human find it at merge — which is exactly the guard iss-115 asks for on
the spec side.

Main's iss-110 is already merged, so this branch renumbers. Only the id
and the filename change; the finding is untouched. The commit message
and PR body of #124 still name iss-110.

Assisted-by: Claude:claude-opus-4-8
@REPPL
REPPL enabled auto-merge July 21, 2026 20:29
@REPPL
REPPL merged commit 655db34 into main Jul 21, 2026
12 checks passed
@REPPL
REPPL deleted the feat/itd-95-96-conventions-adapters branch July 21, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant