Development record
A reading is about something narrower than everything, and its scope is a committed, adjustable choice
Press Release
A reading is commissioned about something. The assembler takes a scope naming what this reading is about — one record, one record family, or a named preset — and passes that rather than the position's entire corpus. The presets are committed, named and adjustable:
coldandwarmare two settings of one dial, not two mechanisms, because warmth is how much a reader was given and nothing else. A scope may be widened or narrowed at invocation while calibrating, and the manifest then records the scope a run actually had, so a stale preset can never make a past run unreadable. The bundle tells the reading what it was given, because a reader told its object is the shipped tree and handed a tenth of it will report the missing nine tenths as a finding.
"I want to point a detection reading at one family of records rather than at everything, every time," said an AI/agent researcher who runs cold readings against their own design record. "Not at everything, every time. And when I find out tomorrow that I need one more record family in scope, I want to change it and rerun, not open a pull request."
Why This Matters
The four positions were ruled as four objects and every one of them is corpus-shaped: the whole brief against the whole shipped tree, the whole claim record against the whole constraint set. That ruling was right and this intent does not disturb it. What nobody computed is what those objects weigh. Measured, the answer is about 2.3 million estimated tokens at every position, of which the records are 210 thousand and the rest is code (iss-2608311501186646). No selection between those two figures both fits a reader and preserves a position's stated object, which is why this is a redesign and not a filter.
The originating intent, itd-86, scoped a cold reading to "exactly one target document ... the constraints visible in that document alone". The shipped verb's --target names a commit, not a document, and the word survived while its referent changed. That is not a reversal of anything ruled — the four corpus-shaped objects follow from a ruling that stands — but it does mean the instrument lost the ability to be pointed at something, and the size is the consequence.
The second finding is the same root seen from the other side: three of the four positions receive a byte-identical item set (iss-2608311501240566). A table that cannot say what a reading is about cannot distinguish four readings that are about different things.
What's In Scope
- A scope operand naming what a reading is about, in a closed grammar and never prose: a record id, a record family, or the name of a preset. No repository path is accepted at the invocation — a raw path is prose, and would make the invocation the framing channel the 2026-08-28 rulings exist to close. Absent, the verb refuses, as it already refuses an absent position or target.
- The ruling that admits a third operand at all (maintainer, 2026-08-31), recorded as adr-58 and carried into brief invariant 15. This intent cannot be built under the rules as they stand, and says so rather than shipping past them. Two records forbid it, and the difference between them matters.
itd-183's ac-3 rules the interface takes a position and a target state and nothing else. It names one anticipated revision, and that revision is narrower than this intent on three counts: it fires only "if the evaluative position proves to need candidates at invocation", it is "restricted to the intent family", and it carries candidates rather than a scope. This intent's trigger is size at every position, its grammar spans every record family, and — per the comparative ruling below — it defers the evaluative position entirely. So none of the three tokens is the anticipated revision, a bare record id included. M8 is superseded on the record rather than read as though it had already permitted this.
Brief invariant 15 states that invocation "carries no free text (position and target state only)". That parenthetical is an exhaustive enumeration, not an illustration, so a third operand contradicts it whether or not the operand is free text. The invariant moves to name the scope operand and to state the property it was actually protecting: that no operand carries prose.
The preset name earns its place on its own argument, not the record-id-list one. A preset name resolves into a committed, reviewed, shape-validated file inside the dirty gate, so what it expands to — repository paths included — is a repository fact under review rather than something typed at a prompt. The id and family tokens are shape-validated closed forms. Nothing at the invocation is prose, which is the property both records were protecting.
- Committed, adjustable presets at
.abcd/config/reading-presets.json, carrying a schema version. A preset is where a repository path may be named, because the file is committed, shape-validated and inside the dirty gate, so a path there is a reviewed repository fact rather than something typed at a prompt. This is what keeps the source lever — 82 per cent of the measured weight — without reopening the free-text ruling. - A preset maps position to scope, rather than carrying one scope every position shares. The measured finding this intent exists to fix is that three of the four positions receive a byte-identical item set, and a single scope intersected with four admissions that are already near-identical reproduces it exactly. Per-position scopes make the distinctness of the four readings a property of the configuration rather than a hope about it.
warmiscoldplus a delta, by construction, so a scope added to cold appears in warm without anyone remembering to add it twice, and warm can never be narrower than cold. A preset that is neither is a third preset with its own name, not a redefinition of warm.- No warm definitions. A wider scope changes what a reader was given and changes nothing about the seven blindness conditions, which are carried verbatim by all four definitions and stay carried.
- A single document as a valid scope, which is the narrowest form and restores what itd-86 asked for without disturbing the four positions.
- The scope carried in the bundle, so the reading knows what it was given. This is the reader's fact, not the auditor's: the manifest is the auditor's alone by construction, and a reading told its object is the shipped tree while handed a subset will report the absence of the rest as a tension against the claim record, with every gate green.
- The effective scope and its hash recorded in the manifest, and a run whose scope was overridden at invocation stamped as overridden — so drift between the committed preset and what people actually run is countable rather than invisible.
- The preset configuration in the dirty gate. An uncommitted edit to it reshapes the assembly as surely as an edit to a record does, and it sits under the structural deny so no include row reaches it. The record-lint configuration is already named into the dirty set for exactly this reason and by exactly this argument; the preset file joins it.
- A stated precedence between the definition and the bundle. A definition states what its position MAY read; the bundle states what THIS run was given. Where they disagree the bundle governs, and each definition says so in its Object section, because otherwise a reader holds two accounts of its object with no rule and reports the difference as a finding.
- The comparative position REFUSES, naming the channel it lacks (maintainer, 2026-08-31). Its object is the widening run's pre-admission output, which is not repository material and has no channel today; and the readings family that would hold a prior run is denied to the assembler structurally, by itd-183's ruling on the manifest's home and by itd-186's ac-4. So there is no scope this intent can give comparative that is its stated object. What it does today is hand back a bundle byte-identical to detection's, which is a false green: the run succeeds, every gate passes, and the reading is about the wrong thing. Refusing is the
loud-stagingprinciple applied to a position — a stage that cannot do its job says so. Supplying the missing channel is a separate intent, and this one does not pretend to. - The assembler version moved, because both the bundle shape and the manifest shape change. The version's own documentation says it covers the include table, the projection, the bundle shape and the manifest shape together, while the only gate pinning it hashes the table's rendering; two manifests stamped alike, one carrying an effective scope and one not, would be indistinguishable by the field that exists to tell runs apart.
- The pinned operand set updated with a statement of what the new operand does. itd-184's guard pins this verb's operands exactly and fails CLOSED on any addition, by design, so that a new operand has to say what it does before it can ship. A scope operand trips it, and that is the guard working.
What's Out of Scope
- Any route from a scope to the local ledger tier. Brief invariant 14 forbids every agent from consuming the framing traces "unconditionally and under no flag". The exclusion is measured from each row's source downward, is not expressible as a scope, and does not move.
- Widening beyond a position's admission. A scope intersects what the include table already admits at that position and can only narrow it. No scope admits a row the table denies there — which is what keeps itd-184's deliberate drafts asymmetry intact, and what makes the all-scopes case finitely checkable.
- The supply regime, which stays keyed on position: the licence bounds what may be produced, never what was seen, so a wide-scoped entailment reading is still
explicative. - The size report, which is itd-198's and lands first.
- A fifth position. Position resolves to a definition and a regime and the disposition machinery keys on it; temperature is a scope, not a position.
Scope Conditions
- Whether a scope should be expressible as a set of record families rather than one target is unknown until several readings have run. The grammar starts closed and narrow; widening it is cheaper than retracting it.
- The comparative position's object is the widening reading's pre-admission output, which is not a subset of the repository and has no channel today. A scope operand does not supply one, and that absence stays open — which is why this intent makes the position refuse rather than quietly serve it the wrong corpus.
- The impact stays
fix, and the reasoning is stated rather than left to be reconstructed. A required operand where none existed is the textbook breaking change, and two things make it a fix here instead. The verb's output cannot be handed to any reader at any position, so there is no working invocation to break; and the comparative position was returning a corpus that is not its object, so its every past run was already wrong. Calling thisbreakingwould assert that something usable stopped working, which would be the more misleading of the two labels.
Acceptance Criteria
- Given an invocation with no scope, when it runs, then the verb refuses and names the closed grammar, as it already does for an absent position.
- Given a scope naming one record, when the assembly runs, then the bundle carries that record's material as the position admits it, and no material outside the scope.
- Given any scope, including one an operator wrote, when the assembly runs, then no item from the local ledger tier appears in the bundle.
- Given a scope naming the intent record family at the widening position, when the assembly runs, then no item from the draft or planned intent families appears, because a scope intersects what the table admits there and never widens it.
- Given an assembled bundle, when the reading reads it, then the bundle states the scope it was assembled under.
- Given a run under a committed preset, when its manifest is read, then the manifest records the effective scope and its hash.
- Given a run whose scope names a record id or a record family directly rather than a committed preset, when its manifest is read, then the run is stamped as overridden; and given a run whose scope names a committed preset, then it is not. Naming a preset is running as reviewed; departing from the presets is what the stamp counts, so drift between what is committed and what people actually run is countable rather than invisible.
- Given an uncommitted edit to the preset configuration, when an assembly runs against a target state, then the verb refuses, as it does for an uncommitted edit to any other configuration that reshapes the assembly.
- Given the committed presets, when an assembly runs at each of the three positions that assemble against one target state, then no two of them carry the same ITEM SET.
- Given an invocation at the comparative position, when it runs, then the verb refuses and names the channel it lacks, rather than assembling a bundle identical to another position's and reporting success.
- Given a preset naming
warm, when it is resolved, then its scope contains every scope member ofcoldat the same position. - Given a definition and a bundle that disagree about the reading's object, when the reading reads them, then the bundle's scope governs and the definition says so.
Grounds
- pursued: This conjecture is pursued now because the instrument is finished, correct and unusable, and the cause is that it cannot be pointed at anything: every position assembles its whole corpus, three of the four assemble the same one, and the total is an order of magnitude past what a reader accepts. What would show it wrong is a reader that accepts the unscoped artefact, or a scoped assembly that proves unable to answer a position's question because the question genuinely needs the whole corpus — in which case the scope grammar is the wrong remedy and a projection of the source is the next one to try.
Audit Notes
Fidelity review — receipt rcp-7a42dfc23828 (verifier abcd:intent-auditor claude-opus-5[1m]).
Provenance: abcd:intent-auditor@claude-opus-5[1m] · rubric_hash sha256:542ed2cd51ff938717a3f47b2b332e8d47910beec0ca7ecdfd238ae7edf5ced5 · prompt_hash sha256:37eabecc8c8815b4403c8924adec561e0687fafcdd279340cc06ac061c39b983 Input attestations: diff:0e0743e8..a97dccfbf193698d2cb1a0be5c25a307a5f5e64f scoped to internal/core/reading/, internal/surface/cli/reading.go, commands/reading.md, agents/, .abcd/config/reading-presets.json, evals/@sha256:c2478d85c48b509aeff4b5de007ab7c1ee771110f9222223e0fe4dbdb29f153c; record:.abcd/development/intents/shipped/itd-199-a-reading-is-about-something-narrower.md@sha256:c70771c636e9f7f357ca88db627f15ab95cb95fcd9003147e654521a2487a6ad;
Acceptance rollup: MET 8 · MET_WITH_CONCERNS 4 · NOT_MET 0 · INCONCLUSIVE 0
Per-criterion verdicts:
- ac-1 — MET_WITH_CONCERNS: Both front doors refuse an absent scope and name the grammar — the CLI before dispatch and ResolveScope in the core — and TestScopeIsRequired asserts the refusal names a record id, a kind and a preset. The concern: the core's own empty-scope refusal still advertises
adr-N, iss-N, the two tokens this delivery removed from recordIDRe as dead, so an operator who follows that refusal and types adr-58 is contradicted by the very next message ('unknown scope'). The removal reached the CLI, the plugin page and the brief but not the core refusal the test itself reads. evidence: internal/core/reading/scope.go:439 — ""Name a record id (itd-N, spc-N, adr-N, iss-N), a material kind, or a committed preset")" evidence: internal/core/reading/scope.go:55 — "var recordIDRe = regexp.MustCompile(^(itd|spc)-[0-9]+$)" evidence: internal/surface/cli/reading.go:98 — ""reading assemble: --scope is required: a record id " + "(itd-N, spc-N), a material kind, or a committed preset named in "" evidence: internal/core/reading/scope_test.go:54 — "func TestScopeIsRequired(t *testing.T) {" - ac-2 — MET_WITH_CONCERNS: The mechanism is present and readable: a record token resolves to a single Record selector, Scope.selects admits only candidates whose basename names that record, pathNamesRecord refuses a bare prefix so itd-19 cannot select itd-198, and the filter runs over the collected set so nothing outside the scope survives. The concern is that the positive half is never demonstrated end to end: every preset in the delivery — the fixture generator and the committed file alike — carries "records": [], and the only assembly under a record-id scope uses itd-9999, which selects nothing and exercises the refusal path instead. The narrowing half is proved generically by TestScopeNarrowsNeverWidens, but no test shows a record scope carrying that record's material. evidence: internal/core/reading/scope.go:441 — "case recordIDRe.MatchString(token):" evidence: internal/core/reading/scope.go:95 — "case sel.Record != "" && pathNamesRecord(c.path, sel.Record):" evidence: internal/core/reading/scope.go:108 — "func pathNamesRecord(rel, id string) bool {" evidence: internal/core/reading/scope_test.go:381 — "Scope: "itd-9999", DryRun: true," evidence: .abcd/config/reading-presets.json:8 — ""records": [],"
- ac-3 — MET: The local ledger tier is unreachable structurally rather than by a scope rule:
.abcdis a denied path segment measured from each include row's Source downward, so no row rooted at.descends into.abcd/.work.local, and no row's Source is the ledger. An operator-written scope is only a record id or a kind, neither of which can name a location; the one place a path may be written — a committed preset — is refused at load if any component is denied. TestScopeCannotReachTheLedgerTier runs four scopes, including operator-writable kinds, and asserts no manifest item comes from .work.local or .abcd/work/. evidence: internal/core/reading/deny.go:24 — "var denySegments = []string{".git", ".abcd", "agents", "evals"}" evidence: internal/core/reading/scope.go:423 — "if pathContainsDeniedSegment(clean) || prefixDenied(clean) {" evidence: internal/core/reading/scope_test.go:246 — "func TestScopeCannotReachTheLedgerTier(t *testing.T) {" evidence: internal/core/reading/scope_test.go:248 — "for _, scope := range []string{fixtureScopeName, "doc", "config", "source"} {" - ac-4 — MET: The scope filter is an intersection applied after collection, so it can only remove candidates the include table already admitted at the position; the drafts and planned rows are position-scoped in the table and a scope naming the intent kind cannot promote them. TestDraftsStayDeniedAtWidening assembles the widening position under the intent-projection kind and asserts no /drafts/ or /planned/ path appears and the sentinel draft body does not reach the bundle. evidence: internal/core/reading/assemble.go:276 — "scoped := make([]candidate, 0, len(cands))" evidence: internal/core/reading/scope_test.go:266 — "func TestDraftsStayDeniedAtWidening(t *testing.T) {" evidence: internal/core/reading/scope_test.go:283 — "if text := bundleText(res.Bundle); strings.Contains(text, sentinelDraftBody) {" evidence: internal/core/reading/scope_test.go:75 — "func TestScopeNarrowsNeverWidens(t *testing.T) {"
- ac-5 — MET_WITH_CONCERNS: The bundle carries a Scope block and TestBundleCarriesTheScopeAndManifestCarriesItsHash refuses a bundle that states none. The named caveat is a deliberate, recorded narrowing: BundleScope is a projection, not the resolved scope, because a Path selector is a repository path and brief invariant 15 forbids one entering the reading's working set (the breach was live and is captured as iss-2608312058244357). So a location narrowing reaches the reader as a bare COUNT and the source token is absent — under the shipped
warmdetection preset, which carries the path internal/core/lint, the reader is told '1 location narrowing' and cannot tell which. That is enough to know it holds a subset, which is the stated purpose, but it is narrower than 'the bundle states the scope it was assembled under' reads. evidence: internal/core/reading/manifest.go:81 — "type BundleScope struct {" evidence: internal/core/reading/manifest.go:87 — "// count and never a list, because the list would be the paths." evidence: internal/core/reading/scope_test.go:287 — "func TestBundleCarriesTheScopeAndManifestCarriesItsHash(t *testing.T) {" evidence: internal/core/reading/scope_test.go:432 — "func TestBundleScopeCarriesNoRepositoryPath(t *testing.T) {" evidence: .abcd/config/reading-presets.json:39 — ""paths": ["internal/core/lint"]" - ac-6 — MET: The manifest carries the resolved Scope, its content hash and the override stamp, and the hash is computed from the canonicalised selectors so a manifest names what was selected rather than how it was typed. The test recomputes Manifest.Scope.Hash() and asserts the stored ScopeHash equals it; TestScopeHashIsOrderIndependent pins the canonicalisation the hash rests on.
evidence: internal/core/reading/manifest.go:154 — "Scope Scope
json:\"scope\"" evidence: internal/core/reading/assemble.go:301 — "scopeHash, err := scope.Hash()" evidence: internal/core/reading/scope_test.go:306 — "if res.Manifest.ScopeHash != want {" evidence: internal/core/reading/scope_test.go:408 — "func TestScopeHashIsOrderIndependent(t *testing.T) {" - ac-7 — MET: Both directions are implemented and both are tested. A record id and a kind each resolve with Overridden: true; a preset name resolves with Overridden: false, and the comment states the reason the stamp counts departures rather than direct naming. TestPresetNameIsNotAnOverrideAndADirectScopeIs asserts the preset run is not stamped and the kind run is.
evidence: internal/core/reading/scope.go:445 — "Overridden: true,"
evidence: internal/core/reading/scope.go:471 — "return Scope{Source: token, Selectors: sels, Overridden: false}, nil"
evidence: internal/core/reading/scope_test.go:168 — "func TestPresetNameIsNotAnOverrideAndADirectScopeIs(t *testing.T) {"
evidence: internal/core/reading/manifest.go:156 — "ScopeOverridden bool
json:\"scope_overridden\"" - ac-8 — MET: The preset configuration is named into the dirty set explicitly, beside the record-lint configuration and by the same argument, because the structural deny keeps it out of every include row. dirtyPaths takes every porcelain entry regardless of which status column reports it, so a staged-only edit refuses as surely as a worktree one; TestUncommittedPresetRefuses asserts the refusal names the preset path. The delivery also closed the hole the gate could not see: LoadPresets now requires the file to be in git's index and to be a regular file, so a gitignored or symlinked preset is refused rather than read and stamped 'ran as reviewed'. evidence: internal/core/reading/assemble.go:573 — "included[PresetConfigPath] = true" evidence: internal/core/reading/assemble.go:604 — "func dirtyPaths(out string) []string {" evidence: internal/core/reading/scope_test.go:356 — "func TestUncommittedPresetRefuses(t *testing.T) {" evidence: internal/core/reading/scope.go:217 — "if !tracked[PresetConfigPath] {" evidence: internal/core/reading/scope_test.go:606 — "func TestAnUntrackedPresetRefuses(t *testing.T) {"
- ac-9 — MET_WITH_CONCERNS: Distinctness of ITEM SETS across the three assembling positions is proved by TestThreePositionsCarryDistinctItemSets, which assembles under a preset and keys on the manifest's item paths — but that runs against a fixture preset, not the committed one. The gate that does read the committed file, TestTheShippedColdScopesEveryAssemblingPositionDistinctly, compares resolved SELECTOR sets and not item sets. Distinct selectors do not entail distinct item sets: two positions whose differing kinds happen to be empty in a repository would collapse to the same items and pass. So the criterion's literal subject — the committed presets producing distinct item sets — is held by proxy, and the shipped cold preset does give the three positions different kind sets. evidence: internal/core/reading/scope_test.go:222 — "func TestThreePositionsCarryDistinctItemSets(t *testing.T) {" evidence: internal/core/reading/scope_test.go:788 — "func TestTheShippedColdScopesEveryAssemblingPositionDistinctly(t *testing.T) {" evidence: internal/core/reading/scope_test.go:794 — "sels := presetSelectors(pf, pf.Presets["cold"], p)" evidence: .abcd/config/reading-presets.json:6 — ""widening": { "kinds": ["brief-section", "glossary-term", "discipline"]"
- ac-10 — MET: The comparative position refuses before the presets are loaded, before the scope is resolved and before anything is collected, and the message names the channel it lacks — the widening run's pre-admission output. TestComparativeRefuses deliberately passes a KIND token rather than a preset, so the position check is the only thing that can refuse, and asserts the message names both 'comparative' and 'channel'. validatePresets independently refuses a preset that names a comparative scope, and the position's own definition file states the refusal. evidence: internal/core/reading/assemble.go:238 — "if position == PositionComparative {" evidence: internal/core/reading/scope_test.go:196 — "func TestComparativeRefuses(t *testing.T) {" evidence: internal/core/reading/scope.go:358 — "return fmt.Errorf("%s: preset %q names a scope for the comparative position, "" evidence: agents/cold-reading-comparative.md:49 — "This position does not currently assemble."
- ac-11 — MET: Containment is by construction rather than by review: presetSelectors unions the parent's selectors with the child's at the same position, extension is capped at one level so the guarantee stays checkable, and TestExtendsIsAUnionNotAReplacement kills the mutation where a child entry replaces the parent's. The delivery closed the gap that the construction alone left — nothing required a preset named warm to extend cold — with a test that loads the COMMITTED file, asserts warm.Extends == "cold", and checks containment selector by selector at every assembling position. evidence: internal/core/reading/scope.go:476 — "func presetSelectors(pf PresetFile, p Preset, position Position) []Selector {" evidence: internal/core/reading/scope_test.go:759 — "func TestTheShippedWarmContainsTheShippedCold(t *testing.T) {" evidence: internal/core/reading/scope_test.go:766 — "if warm.Extends != "cold" {" evidence: .abcd/config/reading-presets.json:24 — ""extends": "cold"," evidence: internal/core/reading/scope_test.go:139 — "func TestExtendsIsAUnionNotAReplacement(t *testing.T) {"
- ac-12 — MET: Re-checked across all four definitions after the amendment, not carried over: each of cold-reading-widening, -entailment, -detection and -comparative now carries the 'What this section states, and what governs' paragraph stating that the section says what the position MAY read, the bundle says what THIS run was given, and where they disagree the bundle governs. Three carry the operative corollary that absent material is the scope rather than a finding; comparative carries the precedence sentence and records that it is already true if its channel ever exists. The bundle side is delivered by Bundle.Scope, so the two accounts the criterion contemplates both exist and the rule between them is stated.
evidence: agents/cold-reading-widening.md:44 — "Where the two disagree, the bundle governs."
evidence: agents/cold-reading-entailment.md:44 — "Where the two disagree, the bundle governs."
evidence: agents/cold-reading-detection.md:41 — "Where the two disagree, the bundle governs."
evidence: agents/cold-reading-comparative.md:47 — "the two disagree the bundle governs — the same rule every other position"
evidence: internal/core/reading/manifest.go:61 — "Scope BundleScope
json:\"scope\""
Gap audit:
- honoured:
- A scope operand in a closed grammar, with no repository path accepted at the invocation — the property adr-58 supersedes the M8 ruling on. evidence: internal/core/reading/scope.go:435 — "func ResolveScope(pf PresetFile, position Position, token string) (Scope, error) {" evidence: internal/surface/cli/reading.go:147 — ""what the reading is about: a record id (itd-N, spc-N), a material kind,""
- Committed, adjustable presets carrying a schema version, mapping position to scope rather than one scope every position shares.
evidence: .abcd/config/reading-presets.json:2 — ""schema_version": 1,"
evidence: internal/core/reading/scope.go:181 — "Positions map[string]PositionScope
json:\"positions\"" - warm is cold plus a delta by construction — extends is a union, capped at one level, and the committed file's linkage is now gated. evidence: internal/core/reading/scope.go:346 — "one level of extension only" evidence: internal/core/reading/scope_test.go:766 — "if warm.Extends != "cold" {"
- The preset file's whole safety argument is now actually checked: tracked in git's index, a regular file rather than a symlink, no duplicate preset keys, strict on unknown fields, and inside the dirty gate. evidence: internal/core/reading/scope.go:230 — "if fi.Mode()&os.ModeSymlink != 0 {" evidence: internal/core/reading/scope.go:271 — "func refuseDuplicatePresetKeys(raw []byte) error {" evidence: internal/core/reading/scope_test.go:675 — "func TestDuplicatePresetKeysAreRefused(t *testing.T) {"
- The shipped preset file — the gap the earlier advisory pass called the largest — is now loaded and judged by three tests that read the committed file rather than a fixture. evidence: internal/core/reading/scope_test.go:739 — "func TestTheShippedPresetFileIsValid(t *testing.T) {" evidence: internal/core/reading/scope_test.go:759 — "func TestTheShippedWarmContainsTheShippedCold(t *testing.T) {" evidence: internal/core/reading/scope_test.go:788 — "func TestTheShippedColdScopesEveryAssemblingPositionDistinctly(t *testing.T) {"
- The pipeline ORDER is now falsifiable rather than a comment: the dirty gate and the exclusion assertion run over the unfiltered walk, so a narrow scope cannot quiet a breach a wide one would catch. evidence: internal/core/reading/assemble.go:268 — "The scope filter runs LAST, after the dirty gate and the exclusion" evidence: internal/core/reading/scope_test.go:545 — "func TestGatesSeeTheUnfilteredWalk(t *testing.T) {"
- prefixDenied now folds case, so validPresetPath's stated contract — a path that LOOKS like it reaches a denied place is refused at the door — holds on a case-insensitive filesystem. evidence: internal/core/reading/deny.go:66 — "if strings.EqualFold(rel, p) || strings.HasPrefix(strings.ToLower(rel), strings.ToLower(p)+"/") {" evidence: internal/core/reading/scope_test.go:703 — ""a case-folded deny": "Internal/Core/Reading","
- validPresetPath refuses control characters, surrounding whitespace, backslash separators, the whole-repository "." form and an escaping path. evidence: internal/core/reading/scope.go:400 — "for _, r := range raw {" evidence: internal/core/reading/scope.go:419 — "if clean == "." {" evidence: internal/core/reading/scope_test.go:694 — "func TestPresetPathsRefuseTerminalAndWholeRepoForms(t *testing.T) {"
- The assembler artefact version moved because both artefact shapes changed, so two manifests carrying and not carrying an effective scope are distinguishable. evidence: internal/core/reading/manifest.go:23 — "const SchemaVersion = 4"
- itd-184's pinned operand set was updated WITH a statement of what the new operand does — the guard failed closed and was answered rather than widened. evidence: internal/surface/cli/regime_surface_test.go:78 — "scope added by itd-199 under adr-58. This pin fails CLOSED on any" evidence: internal/surface/cli/regime_surface_test.go:86 — ""abcd reading assemble": {"dry-run", "out", "position", "scope", "target"},"
- Brief invariant 15 was amended to name the three operands and the preset-name argument, and the amended clauses are held by the implementation: every accepted token is a shape-validated closed form and no path token can pass ResolveScope.
evidence: .abcd/development/brief/02-constraints/03-invariants.md:43 — "the operands are a position, a target state and a scope, each a shape-validated closed form"
evidence: internal/core/reading/scope.go:59 — "var presetNameRe = regexp.MustCompile(
^[a-z][a-z0-9-]{0,31}$)" - The brief's surface chapter for the verb was corrected to three operands and lists the grammar without the dead tokens.
evidence: .abcd/development/brief/04-surfaces/23-reading.md:29 — "
assembletakes exactly three operands, every one closed in shape." evidence: .abcd/development/brief/04-surfaces/23-reading.md:38 — "|--scope| a record id (itd-N,spc-N), a material kind, or a committed preset |" - The plugin page carries the third operand in its argument hint and its shipped invocations pass --scope.
evidence: commands/reading.md:4 —
assemble --position <widening|entailment|detection> --target <HEAD|sha> --scope <itd-N|spc-N|kind|preset>evidence: commands/reading.md:40 — "--position widening --target HEAD --scope cold --json"
- diverged:
- The dead-token removal did not reach the core's own empty-scope refusal, which still advertises adr-N and iss-N — the exact affordance the change's own comment condemns as worse than an absent one. evidence: internal/core/reading/scope.go:439 — ""Name a record id (itd-N, spc-N, adr-N, iss-N), a material kind, or a committed preset")" evidence: internal/core/reading/scope.go:50 — "so those tokens validated, resolved, selected nothing, and ended in the"
- The CLI's own help was NOT corrected to three operands, though the plugin page and the brief chapter were: the usage string, the Long text and the positional-argument refusal all still say a position and a target and nothing else.
evidence: internal/surface/cli/reading.go:62 — the
Usestring named a position and a target only evidence: internal/surface/cli/reading.go:77 — ""carries a position and a target state and nothing else"}" evidence: internal/surface/cli/reading.go:7 — "The verb's whole interface is two closed operands." - The two shipped examples on
abcd reading assembleomit --scope, so both now exit 2 as written — and the generated CLI reference inherits the stale usage line and both broken examples verbatim. evidence: internal/surface/cli/reading.go:70 — "Example: " abcd reading assemble --position widening --target HEAD --dry-run\n" +" evidence: docs/reference/cli/commands.md:789 — "Usage:abcd reading assemble --position <position> --target <HEAD|sha> [flags]" evidence: docs/reference/cli/commands.md:816 — "abcd reading assemble --position widening --target HEAD --dry-run" - The brief's surfaces index still describes the verb as two-operand while its own chapter says three, so the record carries two accounts of the invocation.
evidence: .abcd/development/brief/04-surfaces/README.md:29 — "
assemblecarries a position and a target state and no free text at any position" evidence: .abcd/development/brief/04-surfaces/23-reading.md:29 — "assembletakes exactly three operands, every one closed in shape." - The bundle states a location narrowing as a count rather than as the scope — a deliberate, recorded departure from 'the scope carried in the bundle', taken to keep a repository path out of the reading's working set. evidence: internal/core/reading/manifest.go:65 — "BundleScope is the scope as a READING sees it, and it is deliberately NOT" evidence: internal/core/reading/manifest.go:87 — "count and never a list, because the list would be the paths."
- ac-9's committed-preset gate compares resolved selector sets, not the item sets the criterion names; item-set distinctness is proved only against a fixture preset. evidence: internal/core/reading/scope_test.go:794 — "sels := presetSelectors(pf, pf.Presets["cold"], p)" evidence: internal/core/reading/scope_test.go:224 — "writeFile(t, root, ".abcd/config/reading-presets.json", narrowPresets())"
- Brief invariant 15's pre-existing clause that no repository path enters a reading's context remains broader than the implementation: the bundle's STRUCTURE is pathless and gated, but item text carries path-like strings and the four definition files list repository directories to the reader. The delivery documents this rather than closing it; it predates itd-199 and is not chargeable to it.
evidence: internal/core/reading/scope_test.go:458 — "Item text legitimately contains path-like strings — a"
evidence: agents/cold-reading-comparative.md:35 — "-
.abcd/development/brief/01-product— the construal as it presently stands."
- missing:
- No test exercises a record-id scope that actually selects material. Every preset in the delivery — the generated fixture and the committed file — carries an empty records list, and the sole record-scoped assembly names itd-9999, which selects nothing. ac-2's positive half is asserted by the code and by no test.
evidence: internal/core/reading/fixture_test.go:233 — "
%q: {\"kinds\": [%s], \"records\": [], \"paths\": []}," evidence: internal/core/reading/scope_test.go:381 — "Scope: "itd-9999", DryRun: true," evidence: .abcd/config/reading-presets.json:8 — ""records": []," - No gate holds the definition-versus-bundle precedence sentence in the four definition files. ac-12 is satisfied by prose alone; a future edit dropping the paragraph from one definition would leave that reader holding two accounts of its object with no rule, and every gate green. evidence: agents/cold-reading-detection.md:41 — "Where the two disagree, the bundle governs." evidence: internal/core/reading/definitions.go:3 — "definitions.go is the definition locator: it resolves a reading position to"
- No test exercises a record-id scope that actually selects material. Every preset in the delivery — the generated fixture and the committed file — carries an empty records list, and the sole record-scoped assembly names itd-9999, which selects nothing. ac-2's positive half is asserted by the code and by no test.
evidence: internal/core/reading/fixture_test.go:233 — "
Scope-condition dispositions:
- cond-2608312031029678 — narrowed: The condition's premise held — the grammar shipped closed and narrow, and a set-valued scope is expressible only inside a committed preset, never at the invocation. It narrowed further during delivery than the intent body describes: adr-N and iss-N were struck from the record-id form as dead tokens, because no include row names a decisions or issue store, so those tokens validated, selected nothing and died in the selects-nothing refusal.
narrowing: The invocation grammar shipped narrower than the intent's own body advertises: a record id may name only itd-N or spc-N. adr-N and iss-N were removed from recordIDRe as dead tokens, so the closed grammar is record id (itd/spc) | material kind | committed preset name.
evidence: internal/core/reading/scope.go:47 — "It names ONLY the families the include table can admit.
adr-Nandiss-N" evidence: internal/core/reading/scope.go:55 — "var recordIDRe = regexp.MustCompile(^(itd|spc)-[0-9]+$)" evidence: internal/core/reading/scope.go:163 — "type PositionScope struct {" - cond-2608312031028702 — survived: The absence the condition names is still open and the delivery says so loudly rather than quietly serving the wrong corpus: comparative refuses before anything is resolved, naming the channel it lacks, validatePresets refuses a preset that names a comparative scope at all, and the position's definition file stands as the statement of what the object would be if a channel existed. evidence: internal/core/reading/assemble.go:239 — ""reading: the comparative position does not assemble. "" evidence: internal/core/reading/scope.go:358 — "which does not assemble: its object is the widening run's pre-admission" evidence: agents/cold-reading-comparative.md:49 — "This position does not currently assemble."
- cond-2608312031020321 — survived: The impact stayed
fixand the delivery does not disturb the reasoning: the verb gained a required third operand, which is the textbook breaking shape, but the pre-existing output was unusable at every position and comparative was returning a corpus that was not its object. The record carries the label unchanged. evidence: .abcd/development/intents/shipped/itd-199-a-reading-is-about-something-narrower.md:9 — "impact: fix" evidence: internal/surface/cli/reading.go:97 — "if scope == "" {" evidence: internal/surface/cli/regime_surface_test.go:86 — ""abcd reading assemble": {"dry-run", "out", "position", "scope", "target"},"
Frontmatter
| id | itd-199 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | critical |
.abcd/development/intents/shipped/itd-199-a-reading-is-about-something-narrower.md
open on GitHub ↗ · commit history ↗
References3
- itd-183The cold reading sees exactly what the assembler passes — positive inclusion, field projection, and a per-run manifestbuilds on
- itd-184Four cold-reading definitions, one blindness core — each position licenses a different output, and none may hold another's licencebuilds on
- itd-198An assembly reports what it would cost before a reading is commissionedbuilds on
Referenced by11
- adr-58A reading is commissioned about something, so the invocation takes a scoperelated
- adr-2609021016272867The comparative reading receives one widening run's candidates, a positional exception to the prior-run exhaust, with the run derived from the record and no operand addedrelated
- adr-2609021016286571The invocation is a position and a target state, and the committed preset for the position supplies the restrelated
- iss-2610262031532200itd-199 ships a scope operand at invocation that it says was 'carried into brief invariant 15'; invariant 15 says that operand is superseded and the operands are position and target only.related
- iss-2610262036260273itd-199 lets a reading's scope be widened or narrowed at invocation and records the override; the reading chapter says the invocation is a position and a target with no override and nothing to stamprelated
- iss-2610262036267743itd-199 has the comparative position refuse because it has no channel; the reading chapter has it derive its candidate set from a committed widening runrelated
- iss-2610262042593698The glossary defines warm as what a cold reading must never see; itd-199 defines warmth as how much a reader is given.related
- itd-2609020625400445A committed preset declares the window it was calibrated for, and the record holds every preset to its declarationbuilt on by
- itd-2609020625407419A comparative reading receives the widening run's items as its candidate set, and characterises them before anyone admits onebuilt on by
- itd-2609021003095168The invocation is a position and a target state, and the committed preset for the position supplies what the reading is handedbuilt on by
- spc-69The scope operand: a reading is commissioned about something, in a closed grammar, from a committed presetimplemented by
mentions27
- adr-2609021016275803No session holds both a reading and the ledger, and a per-run context stamp is how the record shows it
- dcn-26090200000000122026-09-02 — What a reading is handed has two committed dimensions, both the documents' own (maintainer): the OBJECT SET (the framework's term, section 13: which records and delivered paths the run is about; for Iteration 2, Iteration 1's shipped state, which here is the ledger: itd-177 to itd-189, itd-198, itd-199, spc-55 to spc-69, and the packages and pages they delivered) and the KINDS within it (the companion's four, section 4.5). A committed preset entry per position carries both plus the window it measures at; nothing at the invocation chooses either. A reading of the object set includes all kinds when the object set fits the reader's window, with the size report stating any figure over the two-hundred-thousand target; measured on 2026-09-02, the ledger at the detection position with all kinds is about 611,000 estimated tokens, which fits a million-token reader and is stated over target. A kind that proves useless leaves the default entry by a commit, which is how the record learns; a larger object set in future narrows kinds the same way. The word "object" alone stays the companion's, for what a position reads; "object set" is the run's, and "object of interest" is its plain-language gloss.
- dcn-26092500000000022026-09-25 — A scope condition's standing is folded by source precedence, not by position, and a re-ingest for an INGESTED receipt whose payload renders differently replaces the ingested verdict (run A orchestrator, ruling on finding 1 of the lane review of itd-2609020625405251 / spc-2609020626046252, branch feat/scope-condition-verb). The spec's fold took the last disposition in document order, on the premise that both writers append; the verdict ingest does not, since it replaces the receipt's OWED stub in place and the stub is parked at ship time, so on every record the writers produce a verdict ingested after a condition block sits above it, and a same-receipt re-ingest was a noop whatever it carried. The documented override (a verdict whose rationale names the occasion) was therefore unreachable. The ruling: (1) a verdict entry replaces a condition entry iff its rationale names that condition's occasion, wherever either sits; condition blocks among themselves, and verdict blocks among themselves, keep document order; consequently a verdict that has named an occasion keeps overriding a later block written for that same occasion, and a researcher re-dispositioning after it cites a new occasion. (2) A re-ingest whose payload renders to the block already on the record stays a noop; one that renders differently replaces it in place after the first ingest's checks; one that does not validate is refused with nothing written, never dead-lettered over a verdict already ingested. No recorded decision makes the ingest write-once: itd-80's ac-5 ("ingest runs again with the same receipt_id … is a no-op: `## Audit Notes` is not duplicated") is an acceptance criterion whose stated property, one block per receipt and no duplication, the replacement keeps, and `TestIngestIdempotentNoOp` still holds. The closed spec carries a dated correction in place. Separately, the lane's commit bumping cold-reading-detection to 0.1.3 while editing itd-199's markers says readings are pinned to an earlier commit; this tree cannot show that pin, and needs none: no study in this repository is mid-flight, because the reading workstream lives in the research fork (the 2026-09-23 entry above on iss-2609021857343626). History is not rewritten.
- iss-2608311501186646The assembled input for a real reading of this repository is about 9.8 MB at every position, roughly 2.45 million tokens, so no reading can be handed one. Measured by running the shipped assembler over the tree at a real commit: widening 9808456 bytes across 951 items, entailment 10156762 across 1297, comparative and detection the same as widening, with the largest single item at 280242 bytes. Field projection works and is not the problem: 104 of the 951 items are projected to a named field and 847 travel whole, so the whole-file rows dominate by two orders of magnitude. The evals cannot see this because they run over a miniature fixture repository of about thirty files, which is the right corpus for asserting a firewall and the wrong one for asserting that the artefact fits anywhere. Nothing in the record states a size budget for the assembled input, and no criterion measures one, so the instrument can be entirely correct and still undeliverable. The instrument has never been run, which is how this survived thirteen intents.
- iss-2608311501240566Three of the four reading positions receive a byte-identical item set. Comparing the manifests of a real assembly at one commit, widening, comparative and detection each carry the same 951 items with the same fields, and only entailment differs, by the 346 draft and planned items the drafts asymmetry admits. That asymmetry is itd-183's and it works exactly as designed. The other three do not. The definitions state four distinct objects: widening reads the brief, glossary, disciplines, specs and shipped tree; comparative reads the candidate set, which is the widening reading's pre-admission output, against the declared selection criteria; detection reads the shipped tree against the claim record. What the assembler actually passes at comparative and at detection is the widening object unchanged. The comparative case is the sharper one, because its object is not a subset of the repository at all but the output of a previous reading, and the assembler has no channel for it -- spc-64 says as much in passing, that the in-cycle candidate set arrives by whatever channel spc-61 defines and the eval makes no claim about it, but nothing states that the position therefore receives the wrong object. No criterion measures cross-position differentiation, and the evals run on a fixture too small for the difference to show.
- iss-2608312058244357The scope carried in the cold-reading bundle serialised a preset's path selectors verbatim, so a repository path reached the reading's own working set and breached brief invariant 15's pathless-bundle rule; the manifest may carry paths and the bundle may not, and one Scope type was being written to both
- iss-2609011207114761The changelog composer cannot see the previous release surface, so its `Changed`
- iss-2609012259581057The cold-reading workstream's intents carry no mechanism claim and twelve of fourteen declare no scope condition, so the record was populated by demonstration rather than use
- iss-2609020626100041ADR owed: the comparative channel admits two fields of one widening run's items from the readings family at one position, a positional exception to the prior-run exhaust
- iss-2609020626114155ADR and brief invariant owed: no session holds both a reading and the ledger, and the session-kind stamp changes the reading bundle's shape
- iss-2609081941074556releaseOf stamps the homepage feature block by strings.Contains of the record id against each CHANGELOG line, with no word boundary, walking newest dated section first. A short id therefore inherits a longer one: on ec7f40d6 releaseOf(itd-9) returns 0.4.1 off the itd-93 credit and releaseOf(itd-1) returns 0.7.1 off itd-130, and a future superstring landing in a newer section restamps the featured id. site check does not validate the stamp. The same package already carries bodyHandleRe. Fix: match the record id at a word boundary (reuse bodyHandleRe, or split the line into tokens); newest-section-first can stay. Detector: a changelog whose newer dated section mentions itd-1990 must not stamp featured itd-199, while a line naming itd-199 as its own id still matches. Independent of the Audit Notes rollup scanner. Reported as GitHub issue 624 against ec7f40d6. <!-- record-lint: illustrative -->
- iss-2609090951280114releaseOf now compares whole handles found by the package handle pattern rather than testing a substring, which stops a digit continuation such as itd-1990 from being read as a credit for itd-199. That pattern ends in a word boundary, and a hyphen is not a word character, so a hyphen-suffixed compound still yields the short handle as a match: run over the line renamed branch fix/itd-199-cleanup the pattern returns itd-199, and because the walk takes the newest dated section first, such a mention in a newer section out-stamps the real credit in an older one. Verified by running the shipped pattern over that line and over itd-1990, which correctly returns only the long handle. Dormant today, since the committed changelog carries no such compound token and nothing is mis-stamped now, and recorded because branch names, file stems and run ids of exactly that shape are ordinary changelog prose and the failure would be silent on the day one lands. Fix direction: require the character after the handle to be neither a word character nor a hyphen, or compare against the record ids the export already knows rather than against any handle-shaped token. Detector: a newer changelog section mentioning a hyphen-suffixed compound built on a handle must not stamp that handle, while a line naming the handle as itself still does. Residual of iss-2609081941074556. <!-- record-lint: illustrative -->
- iss-2609250947113245itd-199's three scope-condition identity markers are wrapped in backticks in its shipped record, so the claim reader treats each as documentation of the marker grammar rather than an identity: ParseClaims reports zero conditions, the verdict dispositions keyed to cond-2608312031029678, cond-2608312031028702 and cond-2608312031020321 key to nothing the record carries, and abcd intent condition refuses to re-disposition cond-2608312031028702, the worked example its own spec names
- iss-2610262041359075The reading-position glossary says `comparative` does not assemble and refuses for want of a channel; the comparative-reading intent says it can now be commissioned and assembles a derived widening run
- iss-2610262042256389The glossary's detection reading compares the shipped tree with the claim record; itd-86's cold reading detects contradictions inside one document
- itd-19Defaults That Match Where the Project Lives
- itd-86abcd Can Read Its Own Design Documents As A Stranger Would
- itd-186The read-block eval falsifies the firewall — planted warm content that reaches a reading fails the build loudly
- itd-194The reading include table admits only what the exclusion floor can read
- itd-2609020625402599The scribe's context is assembled and its output is ingested by a verb, and the record can show that no session held both a reading and the ledger
- itd-2609020625405251A scope condition is dispositioned from a reading run, keyed to the condition's identity and joined to the item that occasioned it
- spc-64The read-block eval falsifies the firewall
- spc-68The size report: a per-kind cost on every assembly, a test kind split from source, and a version that cannot misreport its table
- spc-2609020626039834The comparative channel: one widening run's candidates, two fields each, characterised before anyone admits one
- spc-2609020626046252A second writer into the disposition surface: `abcd intent condition` dispositions one scope condition from a reading item or a delivered intent
- spc-2609020626048722One committed entry per position, in three parts: the object set, the kinds within it and the window it measures at, held by an eval that re-measures every entry on every committed change
- spc-2609021004075744The invocation is a position and a target state: the scope operand and the override stamp leave, and the committed preset for the position is applied by the assembler