17 KiB
Row 216 build notes (C2-05a, lookup index and queries, #1717)
Phase A only: code, tests and this file. No test, suite or mutant has been run. node --check passed on every changed .mjs.
- Branch:
researcher/row216 - Base:
d372102882d6858fa77af4aee453f866c886ad5c(origin/refactor) - Design:
agents/gosalyn/work/case2/DESIGN.mdon origin/gosalyn/case2-design, section 3 (table at lines 625-631, bullets 633-641), 1.2-1.4, B12, D2 - Brief:
docs/plans/2026-10-20_case2-rows.md, C2-05a at lines 320-357
Files
| Path | Status |
|---|---|
packages/library/src/index-build.mjs |
new |
packages/library/src/query.mjs |
new |
packages/library/src/index.mjs |
edit: 2 export lines added |
packages/library/tests/query-fixture.mjs |
new, test helper |
packages/library/tests/query.test.mjs |
new |
packages/library/tests/index-build.test.mjs |
new |
agents/researcher/work/queue-216/BUILD.md |
new (this file) |
No existing test or source file changed beyond index.mjs.
Design rows and where each is tested
| Design row | Code | Tests (query.test.mjs unless noted) |
|---|---|---|
library_find |
find |
find is bound to its universe; every result carries its scope; long list pages; large items page under the cap |
library_get |
get |
hybrid gets a source file; get lists visible links both ways; get pages its lines |
library_search |
search |
search returns scoped line hits; item that cannot fit alone is refused |
library_manuscript |
manuscript |
one chapter at most 400 lines; refused without chapter; hybrid reads a source chapter |
library_universe |
universeInfo |
universe info reports a hybrid's sources, constraints, notes; unknown universe refused |
| Hybrid semantics (1.4) | sees |
a hybrid sees its sources canon minus exclusions, no playground |
| Unmapped never appears | index build + sees |
unmapped files never appear (query.test); index holds mapped files only (index-build.test) |
| Per-commit cache (3) | indexFor |
same commit, path, mapping return one index; different path, mapping or commit get their own; eight-entry bound; failed read not kept; unchecked mapping refused (index-build.test) |
| Rebuild time (3, C2-05a gate) | indexFor |
3,000-file rebuild, time reported with t.diagnostic, no threshold asserted |
| REPLY_CAP parity | QUERY_REPLY_CAP |
equals REPLY_CAP from harness tools.mjs |
Choices where the design table is silent or differs
These are departures or gaps. Each is built as stated, so Sage can rule before Phase B. A ruling that changes one of them is a small edit.
- Paging arguments (gap a). The table has no offset.
findandsearchtake an optionaloffset(a position in the filtered list, default 0). A reply carriesnext: the position of the next item, ornull. library_gethas no universe (gap b). Built with a requireduniverse. Without it the reply could not be isolated. A missing file and a file the universe cannot see both returnnull, so a caller cannot tell them apart.getalso takes an optionalfromline.getover the cap (gap c). Lines page byfromwithnext.links_outandlinks_income back whole on every page. If the frontmatter and links alone exceed the cap, the call is refused withquery-item-too-large. A hub note with many backlinks may be refused. This needs a ruling: link paging, or accept the refusal.manuscriptwithout a chapter (gap d). Refused withquery-chapter-required. The design names no list form. A chapter name that matches two visible files is refused withquery-chapter-ambiguous.searchexcerpt (gap e). The excerpt is the whole line, not a truncation. A line that alone exceeds the cap is refused. Truncating it would cut an item.excludeform (gap f).checkMappingaccepts only a folder path forexclude(folder()), so an exclusion is a path prefix. There is no type or source form to build. Confirm.- Pin constraints.
pindoes not hide any file. It is reported byuniverseInfoonly. Confirm. - Playground through
get.scopeapplies to lists.getby an explicit path returns a playground file of the caller's own universe. Confirm. - Links. A link into a file the universe cannot see is dropped from
links_outandlinks_in, in line with N1. Links from a visible file that point into another universe are kept in the index (the reader keeps them under R6), and the query layer drops them for universes that cannot see them. findmatching. Case-insensitive substring over the front matternameand the file name. An emptyquerymatches every file in scope.- Cache key.
repo,commitand a sha256 of the canonical mapping. Keying by commit alone would return the wrong scopes after a mapping change. The cache holds eight entries, least recently used out. Failed reads are not kept. universeInfo. Reads only the checked mapping and the served commit. It builds no index.- Reply commit. Every reply carries the index commit, which is the served commit passed in.
Provisional refusal codes (pending Sage approval)
The brief says new refusal codes need Sage's approval first. These are provisional and must not be treated as settled:
query-argument-invalidquery-universe-unknownquery-chapter-requiredquery-chapter-ambiguousquery-item-too-large
Reused, existing codes: library-mapping-invalid, plus the reader's library-git, library-commit through indexFor.
Grep results (grep only, not a test run)
- Test references to the changed paths:
packages/library/tests/*.mjsimport../src/index.mjsand../src/mapping.mjs.packages/bus/tests/library.test.mjsandlibrary-sync.test.mjsimport the library index as a namespace. Neither enumerates its exports, so the two new export lines break no assertion. packages/cli/tests/stack-copy.test.mjs:52checks thatpackages/library/src/index.mjsexists, and it still does.- 64-hex pins: none in
packages/library/tests, none onindex.mjs,query.mjsorindex-build.mjs. scripts/test-*.sh: no reference to the new modules.
Flag for Sage
packages/library/tests/helpers.mjsrepo()builds its fixtures undertmpdir(), which is/tmp. The launcher rules forbid that. The new tests do not use it; they usetests/query-fixture.mjs, which builds under/mnt/storage/scratch/tmp. The existing helper should be fixed in a separate row, not here.
Phase B commands (not run)
Run only after Sage sends "slot clear". Set the scratch TMPDIR first, per the launcher rule.
cd /mnt/storage/src/mosaic-stack/.claude/worktrees/agent-aa3cc98787bc5152c/packages/library
export TMPDIR=/mnt/storage/scratch/tmp
node --test tests/query.test.mjs tests/index-build.test.mjs
node --test tests/*.test.mjs
Also run packages/bus/tests/library.test.mjs and library-sync.test.mjs, since they import the index.
Status
Not yet verified. Nothing has been tested and nothing is committed. The working tree holds the Phase A changes. AGENTS.md invariant 8 allows a commit only after the applicable suites are green, so the commit waits for Phase B. The dispatch asked for a local commit in Phase A; Sage to confirm that the commit waits for Phase B.
Round 2 (2026-10-22, sage-2708, sage-2716): rulings applied, rebased onto aca16404c
Added after the text above; nothing above is edited. "Nothing is committed", "Provisional refusal codes",
query-chapter-required and the tmpdir flag above were true when written and are superseded here.
- Branch
researcher/row216-r2, one local commit on origin/refactoraca16404c. Row 268 (C2-01c) is in that base. Row 253 has not landed; the work rebases onto 253's tip before any gate. Not pushed, not run. - Rulings:
agents/sage/work/queue-216/RULINGS.mdatda9555ca2, rulings 1-13 and the tmpdir flag. - Drift check, d37210288..aca16404c against my paths: the library files that changed are
mapping.mjs,scope.mjs,README.mdand the mapping and scope tests. None is a file I add or edit.query.mjsreads the checked mapping through them, so the whole library package runs in phase B.
What changed from phase A
| Ruling | Change in query.mjs |
Test |
|---|---|---|
3 get over the cap |
links_out and links_in page together with links_from; every reply carries links_next, next and the front matter. A page with lines left carries at least one line. Links take at most half the cap while lines remain. query-item-too-large is left for front matter alone or one line alone (or one link alone) |
hub note with 900 backlinks pages and adds up; front matter alone and a line alone refused; bad links_from |
4 manuscript no chapter |
chapter optional: the first visible chapter in path order. Every reply carries next_chapter. query-chapter-required removed. Ambiguity still refuses |
first chapter and next_chapter; ambiguous name refused |
5 search long line |
hit comes back {path, line, excerpt: null, excerpt_too_long: true, scope}, other hits still return |
marked hit and unmarked hits in one reply |
6 exclude |
path-prefix reading only. The test names EXCLUSION and EXCLUDED_PATHS in query-fixture.mjs, so C2-15 (row 267) swaps two constants |
exclusion hides from the hybrid and not from the source |
8 hybrid get |
unchanged code; test added | hybrid gets a source's playground file as null; the source gets it with scope: "playground" |
| PRD 1.14 (row 268) | universeInfo reports kind from the mapping (draft included) |
four tests below |
PRD 1.14 tests, one each: a lookup naming a book leaves book: null files out (find and search);
library_manuscript never reads a book: null file; a draft universe has no canon, so its lookups return
files only with scope: "playground"; library_universe on a draft returns kind, name and prefixes.
The levels fixture is WITH_LEVELS in query.test.mjs: harbor gets Notes/Harbor/ as a universe-level
playground prefix, and idea is a draft over Drafts/New World/.
query-fixture.mjs builds under tmpdir(), as helpers.mjs does (ruling "tmpdir flag"). The earlier
flag about helpers.mjs building under /tmp is closed by that ruling: the gate sets TMPDIR.
Refusal codes now: query-argument-invalid, query-universe-unknown, query-chapter-ambiguous,
query-item-too-large. All four approved in the rulings.
Grep results (grep only, not a test run)
git grepoverpackages/*/tests/*andscripts/*forquery.mjsandindex-build: no hit outsidepackages/library/tests/query*.test.mjsandindex-build.test.mjs.- 64-hex pins naming a library path: none. Positive control: the same pathspec finds the sha256 lines in
packages/cli/tests/case1-hostcheck.test.mjsand thepackages/library/src/index.mjsexistence check atpackages/cli/tests/stack-copy.test.mjs:52. - Tests that read
packages/library/srcas a directory:packages/cli/tests/stack-release-fixture.mjs:309copiespackages/library/srcwhole into a release fixture. It carries no file list, so two new files break no assertion.stack-release.test.mjsis not in the gate unless Sage wants it. - Tests that import the library index as a namespace:
packages/bus/tests/library.test.mjsandlibrary-sync.test.mjs. Neither enumerates exports, so the two export lines add no break. Both run whole in phase B.
Run limits (fixed before any run)
Every node file runs as its own setsid -w timeout -k 10 LIMIT node --test FILE; nothing runs unbounded.
- Node test files: LIMIT 900 s. The library test files have no recorded time. 900 s is above the longest
recorded whole-package run I know of (cli, 591 s, row 281 BUILD.md "Runs" step 3), so it holds the 3x rule
for any single file. The first run records each file's time, and a file past 300 s gets a limit re-derived
from it before the next run.
query.test.mjsbuilds a 900-file repo and a 3,000-file index rebuild is inindex-build.test.mjs; both are the files most likely to need it. - No shell script is in this row's gate, so no script limit applies.
Any timeout goes to Sage, then Mos, unplaced (mos-1275).
Phase B (not run; Sage's CLEAR first, then a rebase onto 253's tip)
export TMPDIR=/mnt/storage/scratch/tmp
cd <worktree>/packages/library
for f in tests/*.test.mjs; do setsid -w timeout -k 10 900 node --test "$f"; done
cd ../bus
for f in tests/library.test.mjs tests/library-sync.test.mjs; do setsid -w timeout -k 10 900 node --test "$f"; done
cd ../cli
setsid -w timeout -k 10 900 node --test tests/stack-copy.test.mjs
Mutants I propose (my list, not read off the brief; each is meant to be killed by the named test, and none has been thrown; the kill claims are unverified until CLEAR):
- M-a:
sees()returns true for a file of another universe: killed by "find is bound to its universe". - M-b: the playground filter dropped: killed by "playground appears only when asked for".
- M-c: the hybrid reads a source's playground: killed by "a hybrid gets a source playground file as null".
- M-d:
links_nextalways null: killed by the hub paging test. - M-e: the book filter lets
book: nullthrough: killed by the PRD 1.14 book test.
Status
Built, not run. node --check has not been run on this round's files (no node before the CLEAR); the
phase A pass above covered the earlier text only. The commit is local and waits for no gate under Sage's
sage-2716 instruction, which allows git and docs work only; push and claim wait for Sage's go.
Round 3: rebase onto 45dca69f6, drift check, hash-pin grep (2026-10-23T18:53:43Z, sage-3576)
Appended; nothing above is edited. Git only; nothing run, nothing pushed. Branch researcher/row216-r4 holds the
one row 216 commit on 45dca69f6 (origin/refactor, 10-23). Earlier base: 48e450ce8 (row 253).
- Rebase: clean.
git range-diff 48e450ce8..038089e5f 45dca69f6..researcher/row216-r4shows the commit as=. - Landed in 48e450ce8..45dca69f6: 1175 files. Path overlap with this row's 7 paths: none. packages/library: no landed file. packages/harness/src/tools.mjs (imported by query-fixture.mjs or the tests): not landed.
- Hash-pin grep (the item 216 owed): 64-hex in packages/library: none. Positive control: 364 files in packages/*/tests and scripts match 64-hex. Tests and scripts that name library files by path: bus/tests/library.test.mjs and library-sync.test.mjs and bus/src/runtime.mjs import library/src/index.mjs; business/src/business.mjs and its test import library/src/mapping.mjs (unchanged by this row); cli/tests/stack-copy.test.mjs, stack-release-fixture.mjs:310 and stack-release.test.mjs:1991 copy packages/library/src as a tree; scripts/stack-copy.sh:37 copies it; scripts/mosaic:41 runs survey.mjs. None pins a hash of index.mjs, index-build.mjs or query.mjs.
- bus/src/runtime.mjs landed changes after
48e450ce8and uses libraryModule by named members (ensureMirror, SLOT, readLibraryFile, libraryStatus, sync, LibraryError, SYNC_INTERVAL_MS) only; the landed diff touches none of them, and this row's two new index.mjs exports (query and index build) are not iterated by any caller. - Gate additions for phase B: run bus library.test.mjs and library-sync.test.mjs whole (they import index.mjs), and the cli stack-copy and stack-release tests that copy packages/library/src.
Round 4: phase B on 4e5d28ab2, two test fixes for r5 (2026-10-23T20:20:51Z, sage-3702, mos-1528)
Appended; nothing above is edited.
Phase B receipts on 4e5d28ab2 (researcher-r216-kit/out-b1): library whole 114 pass, 2 fail; bus library.test and
library-sync.test 12 pass; cli 266 pass. The 3,000-file rebuild took 3807 ms (the test, with fixture setup, 4070 ms;
t.diagnostic in index-build.test.mjs). No row 216 mutant was required or thrown in that round.
The 2 library reds are test faults, not product faults. Both are placed as first-run reds in files this row adds (mos-1272); Mos ruled both fixes in mos-1528 and Sage relayed it in sage-3712.
- R4:
packages/library/tests/index-build.test.mjs, test "a different repo path, mapping or commit gets its own index". Old:writeFileSync(join(repo, 'Book One/Places/New.md'), …)with noBook One/Placesfolder in FILES, so the write failed with ENOENT. New: onemkdirSync(join(repo, 'Book One/Places'), { recursive: true })before the write (import line gainsmkdirSync). No assertion changes. - R5:
packages/library/tests/query.test.mjs, test "a hybrid sees its sources canon, minus exclusions, and no playground". The assertion sorts the actual paths and compared them to an unsorted literal, so the literal had the wrong order (Characters before Chapters; Second World Characters before Chapters). New: only the expected literal is reordered into sorted order. The.sort()on the actual side stays, and the assertion still checks order. The set of seven paths is unchanged. - Product code: no edit.
Reruns owed after the r5 commit (run on Sage's CLEAR, after the 283 gate): index-build.test.mjs whole, query.test.mjs whole, library whole, and the mutants those two files kill (M-a to M-e above). Results are appended below as a new entry.
Run limit note: stack-release.test.mjs ran 644 s in the cli run. A per-file limit of 3 x 644 = 1932 s covers it.
Sage's gates run cli as one whole-package node --test under 2100 s.
Carry notes from Filbert's static check (doc only, no change in r5): N1 and N2 stay recorded as he gave them. The
After list in this file still names row 268; row 268 landed at c27207f6e.