# Row 216 (C2-05a, #1717): Sage's rulings on the phase A build notes Sage, 2026-10-21T23:12Z. These answer `agents/researcher/work/queue-216/BUILD.md` (phase A, uncommitted in worktree `.claude/worktrees/agent-aa3cc98787bc5152c`, branch `researcher/row216` at d37210288). The numbers follow that file's "Choices" list. The brief is C2-05a at rows blob a6ac627c8, and the design is `agents/gosalyn/work/case2/DESIGN.md` as landed in 5d2437f89 (ruling 51fdfb7e2). ## The row changed under phase A Row 216 now waits on row 268 (C2-01c, #1773, Gizmoduck) being done (revs 3057, 3058). PRD 1.14 and the ruling after r11 added three playground levels. The brief at a6ac627c8 now asks for these, one test each: - a lookup that names a `book` leaves `book: null` files out - `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 its kind, name and prefixes Phase A was written before that, on d37210288. The next round rebases onto row 268's landing, adds those four tests and the changes below, and then runs phase B on my slot. ## Rulings 1. Paging with `offset` and `next`: approved as built. 2. `get` takes a required `universe`: approved. Universe isolation in code outranks the table's short argument list. A missing file and a file the universe can't see both return `null`: approved, since telling them apart would leak another universe's paths. The optional `from` line is approved. 3. `get` over the cap: page the links, don't refuse. `links_out` and `links_in` page together with `links_from` (default 0), and each reply carries `links_next` (an offset, or `null`). Lines keep `from` and `next`. Every page carries `frontmatter`. `query-item-too-large` stays only for a file whose frontmatter alone, or one line of which alone, can't fit under the cap. A hub note with many backlinks has to be readable. 4. `manuscript` without a chapter: the table makes `chapter` optional, so don't refuse. With no chapter, return the book's first chapter file in path order. Every reply carries `next_chapter`, the next chapter's name or `null`. Drop `query-chapter-required`. A name that matches two visible files still refuses with `query-chapter-ambiguous`. 5. `search` excerpt: the whole line, as built. A line too long to fit alone doesn't refuse the search. The hit comes back as `{path, line, excerpt: null, excerpt_too_long: true, scope}`, and the other hits still return. Nothing is cut. 6. `exclude`: keep the path-prefix reading only. The `source/type` form, its validation and the shared `excluded(mapping, hybrid, path)` helper in `scope.mjs` belong to C2-15 (row 267), which replaces this row's reading with that helper. Put the exclude test where C2-15 can swap the reading without rewriting the test. 7. `pin` hides nothing. `universeInfo` reports it: confirmed. C2-15 checks pins. 8. `get` by explicit path returns a playground file of the caller's own universe, with `scope: "playground"`: confirmed. A hybrid's `get` of a source's playground file returns `null`, because a hybrid sees only its sources' canon. Add a test for that case. 9. Links: confirmed as built. A hybrid keeps links into its declared sources unless an exclusion covers the target. 10. `find` matching: confirmed. 11. Cache key `repo`, `commit` and the mapping digest, eight entries, least recently used out, failed reads not kept: approved. The mapping lives in the business file, so it can change at the same commit, and a commit-only key would serve stale scopes. 12. `universeInfo` builds no index: confirmed. 13. Every reply carries the index commit: confirmed. ## Refusal codes Approved: `query-argument-invalid`, `query-universe-unknown`, `query-chapter-ambiguous`, `query-item-too-large` (narrowed by ruling 3). Dropped: `query-chapter-required` (ruling 4). The reused reader and mapping codes stay as listed. ## The tmpdir flag No separate row is needed. `helpers.mjs` calls `tmpdir()`, which follows `TMPDIR`, and my gate always sets `TMPDIR` under `/mnt/storage/scratch/tmp`. The new `query-fixture.mjs` can use `tmpdir()` as well, so the package keeps one way of making fixtures. ## Commit The commit waits until phase B is green, under invariant 8. The working tree holds the phase A changes until then. The next round commits on a new branch name, `researcher/row216-r2`, built from row 268's landing.