Lands Gosalyn's round 3 candidate,a2c72ac95(commit candidate, basec771a36f4), approved by Webby (29979, rev 2731). 21 paths: the new packages/library (src, tests, fixtures, package.json, README), scripts/stack-copy.sh (packages/library/src in COPY_PATHS), packages/cli/tests/stack-copy.test.mjs, scripts/mosaic and agents/gosalyn/work/queue-212/BUILD.md. The 21-path patch-id, ea84d3e35470, equals the candidate's. Evidence: Gosalyn's slot run on round 3: 20 files 451 pass, 0 fail; test-queue 28/0 (gosalyn-0086). Landing gate (Sage, 08:51-09:03Z, base4ced5e63d) on a tree that also held row 197's 32 paths: GATE_PASSED, node tests 981 pass 0 fail (library 39, cli 681, control-board 38, harness 16, webui 51, plus the scripts/test-*.sh node suites), selftests 429 passed 0 failed. Row 212 lands alone (mos-1162); row 197 waits for an r6 after its lane proof stopped. No 212 path changed betweenc771a36f4and this commit's parent. `queue review verify-commit 212` -> ok row 212 round 3:a2c72ac95matches the commit candidate (21 paths).
packages/library
Reads a writing repo by a mapping of folders to books, types, canon and playground. This is Case 2
row C2-01 (docs/plans/2026-10-20_case2-rows.md), built from design sections 1.1 to 1.3 and 7.4 in
agents/gosalyn/work/case2/DESIGN.md on gosalyn/case2-design.
It is pure code with no state. Business file loading (C2-04), sync (C2-03a), the index (C2-05a) and the
guard (C2-06) build on it.
Mapping
checkMapping(entry, where, { fail }) checks a kind: "library" project entry and returns a
deep-frozen copy. where names the entry in messages. fail(code, message) defaults to throwing a
LibraryError. C2-04 passes its own so a business file refusal carries the file name.
{
"kind": "library",
"remote": "https://git.example.invalid/her/books.git",
"branch": "main",
"vaultPrefix": "books",
"types": { "Characters": "character", "Places": "place", "Research": "research" },
"manuscript": ["Chapters"],
"universes": {
"harbor": { "name": "Harbor", "books": ["Book One"], "playground": ["Book One/Playground/"] }
}
}
remoteis https with no credentials, query or fragment.branchis a plain ref name.vaultPrefixis optional, one folder name.typesmaps a folder name tocharacter,place,research,trackerorsummary.manuscriptlists folder names and shares none withtypes.- A universe has
name,booksandplayground. A hybrid addskind: "hybrid", two or more non-hybridsources,constraints(each one{ "exclude": ... }or{ "pin": { "entity", "field", "value" } }) andnotes.
Refusals:
| Code | When |
|---|---|
library-playground-outside-book |
a playground prefix is not strictly inside a book of its own universe |
library-mapping-invalid |
any other shape fault: unknown key, bad remote or branch, a book folder in two universes, a book folder that holds another, a playground prefix that holds another, an unknown hybrid source |
Scope
classify(mapping, path) decides scope from the path alone. Only paths ending in lowercase .md are
mapped. A path under a playground prefix is playground. Any other path under a book folder is
canon. Everything else is unmapped. The type is the first folder after the book, or after the
playground prefix: a types name, manuscript, or null.
Reader
readLibrary({ repo, commit, mapping }) reads one commit, never a working tree. repo may be a
clone or a bare mirror. commit must be a full 40-hex id that names a commit, else library-commit.
Git runs through git() and gitBytes() from packages/project/src/git.mjs, so the same safe config,
environment and ceiling directory apply. A failed git call refuses library-git.
It returns { commit, files, problems }, deep-frozen and sorted. Each file has its path, scope,
universe, book, type, numbered lines, bodyLine, parsed frontmatter or null, frontmatterRaw
and links. Front matter appears_in and relationships wikilinks count as links.
Problems carry { code, path, line, message }:
| Rule | Meaning | File indexed |
|---|---|---|
| R1 | not a regular 100644 blob of at most 1 MiB, or not UTF-8, or holds a NUL |
no |
| R2 | path is unmapped | no |
| R3 | front matter not fully parsed, line is where parsing stopped |
yes, front matter kept raw |
| R4 | link resolves to no file or to more than one | yes, link dropped |
| R5 | canon file links into the playground | yes |
| R6 | canon file links into another universe, a hybrid's sources excepted | yes |
| R7 | two paths differ only in case | no, neither |
The front matter parser takes scalars, quoted strings, flow lists, block lists and wikilinks. Nested maps, anchors, block scalars and duplicate keys stop it with R3.
Links
resolver(paths, { vaultPrefix }) tries four steps in order, each with the target as written and
then with .md added:
root: the path from the repo root.vault-prefix: the same path withvaultPrefix/stripped.relative: relative to the linking file, never above the repo root.shortest: a target with no/that matches the base name of exactly one indexed file.
It resolves only against indexed files. A link to a .canvas file or an attachment is R4. A link into
another repo resolves to nothing. extractLinks skips fenced and inline code.
renderItems
renderItems(body, { scopeOf, changed }) turns a reply body into the plain text both channels show.
It is pure. Item kinds are fact, finding, correction (with from and to), question and
idea. Each cite is a path with commit, line range and quote, or a url with a quote.
1. Correction: Chapter 1 says grey.
From: "green eyes"
To: "grey eyes"
[canon] Book One/Characters/Mara Vell.md:9 @bbbbbbb "green eyes" (changed since)
A body of { "items": [], "notice": ... } gives No cited answer, No answer or The turn failed.
Any other shape gives the fixed line This reply could not be shown. and none of the body's text.
Survey
mosaic library survey <repo> [--mapping <file>]
It reads the repo's HEAD commit and prints folder names, file counts by extension, front matter key
sets and link-form counts. It never prints file text, front matter values, file names or link targets.
A key that is not a short identifier prints as (other key), since it could be her words. With
--mapping it adds scope counts, resolver step counts and problem counts by rule.
Exit codes follow packages/project: 0 done, 2 the repo can't be read (library-git,
library-commit), 3 a bad mapping, 4 usage. scripts/mosaic sends library survey here. Other
library verbs will be bus verbs.
Tests
node --test packages/library/tests/
Fixtures are made-up repos built per test in a temp dir. A marker string stands in for her text, and the survey tests check that it never appears in output.