# Row 268 build notes (Gizmoduck) Case 2 C2-01c, playground levels. Gitea #1773, Vikunja 464. Brief: docs/plans/2026-10-20_case2-rows.md, "C2-01c playground levels: universe-level prefixes and draft universes" (blob a6ac627c8). The ruling is agents/gosalyn/work/case2/DESIGN.md "Ruling after r11: playgrounds at three levels" (:49-88 at db1e7732a), and it wins where the two differ. Reviewer Webby, gate owner Sage. Branch gizmoduck/row268 from refactor db1e7732a. Line numbers are on this branch unless a commit is named. ## mapping.mjs - `kind` (:71). An entry with a `kind` key must say `"hybrid"` or `"draft"`. Before, a plain universe with `kind` refused as an unknown key, and `kind: "draft"` did the same. - A draft (:73-86) refuses `books` with its own message before the key check, so the cause reads plainly. It takes `kind`, `name` and `playground`, needs one prefix or more, and comes back as `{ kind: 'draft', name, books: [], playground }`, frozen with the rest. - The prefix level check moved out of the per-entry loop to :127-140, after every entry is read, because it needs every entry's books. It runs after the book-holds-book, prefix-holds-prefix and listed-twice checks, so an overlap still reports as before. For each prefix and each book folder: - equal to any book folder: `library-mapping-invalid` (brief, What ships, "or equal to a book folder"); - inside or holding another entry's book: `library-playground-other-universe`, and the message says which, with the book and its entry; - holding its own entry's book: `library-mapping-invalid`. A prefix inside its own book is book level, and one that touches no book is universe level. A draft has no books, so its prefixes are either outside every book or refused as another entry's. - `sources` (:117-120) now refuses any entry with a `kind`, so a draft can't be a source either. The message changed from "not a non-hybrid universe" to "not a plain universe". mapping.test.mjs:61 on db1e7732a pinned the old words, and that line moves with it. - `library-playground-outside-book` is gone from packages and docs/case2. ## scope.mjs `classify` (:12-24) now runs two passes. The first finds a prefix in any entry, and the file is `playground` with that entry's id and `book` set to the entry's book folder that holds the prefix, or `null`. The second finds a book folder, and the file is `canon`. `place` (:26-32) works out `type` and `folder` from the first folder under the prefix or the book, as before. The validator keeps prefixes from overlapping, so the first pass finds at most one. ## Readers on this base - reader.mjs:182 and :194 spread `classify`'s answer into each file record, so a universe-level or draft file carries `book: null`. Nothing in the reader reads `book`. - reader.mjs:206-209. R5 fires for a canon file linking into any playground. R6 fires when the target's universe differs and isn't a source. A draft can't be a source, so a link into one gives R5 and R6. A link into the file's own universe-level prefix gives R5 only. No code change was needed. The new test covers both. - survey.mjs:66 and mirror.mjs:246 read only `scope`. mirror.mjs:251 calls `readLibrary` and checks only `path`. - No other package calls this `classify` or reads `universes` (git grep over packages/*/src). - C2-05a (row 216) and C2-06 (row 217) have no candidate branch on origin at the time of writing. I check again before in-review. ## Tests - mapping.test.mjs: the old outside-book test splits into the other-universe refusals (inside another entry's book, holding one, a draft prefix inside a book), the mapping-invalid ones (equal to a book, own or another's, and holding its own), universe level in a plain universe and in a hybrid with `books: []`, and the draft shape (frozen with `books: []`, with `books`, with none or empty prefixes, an extra key, and five bad `kind` values). The overlap test gains a cross-entry hold and a cross-entry duplicate. The hybrid test gains a draft source. The caller-supplied `fail` test now uses `Second World/Playground/` and sees the new code, since `Elsewhere/` is valid now. - scope.test.mjs, new: one case per level (book, universe in a plain universe, universe in a hybrid, draft), `.canvas` under each new prefix staying unmapped, prefixes first inside a book that holds a prefix, and the reader case for R5 and R6, with `book: null` on the read records. - business.test.mjs: `Elsewhere/Playground/` now loads, and `Second World/Playground/` refuses with `library-playground-other-universe` through `validateBusinessDocument`. ## Docs - packages/library/README.md: the universe keys, a line on the three levels, the refusal table and the Scope section. - docs/case2/SETUP-SHEET.md "Your playground": one line per level and the draft entry. - docs/case2/SURVEY-RUN.md:114: the new code and message. ## Hash pins `git grep -E '[0-9a-f]{64}'` over packages/*/tests finds no pin on any changed file. ## Runs - r2 ecd9b35eb on dcd8d4dda, Sage's CLEAR sage-2449, one pass with the first red stopping it (run-268-c.sh), 2026-10-22 04:40:22Z to 04:42:17Z, 115 s. M1, M2 and M3 all killed (scope.test 2 of 3 failed, mapping.test 2 of 11, mapping.test 1 of 11), and each file's sha256 matched after the restore. Brief files 14/14, library 83/83, business 209/209. test-auth 15, test-conductor 17, test-config 31, test-discord 78, test-extension-package 18, test-foundation 44, test-land-work 86, test-queue 28 plus 156 node, test-release 14, test-task 91, with 0 failures. Node skipped 0. The scripts skipped three cases: test-queue's verify and render check (not the canonical root) and test-task's live provider cases twice (row 244). Neither test-task skip is a dead-Docker skip. - r3 aca53c4f0 on 0fa7bf421, after row 191 landed. The cherry-pick of r2 has range-diff `=` and stable patch-id 2942acc8e6e2b0171b96ebbf033ab5c08c0f0eac. Rerun on CLEAR sage-2461, 04:50:29Z to 04:50:38Z: brief files 14/14, library 83/83 (survey.test on 191's scripts/mosaic), business 209/209, 0 skipped. Webby approved r3 as a carry (webby-0112). - r4 c27207f6e on ce6a96ad6, after row 205 landed (sage-2466: 205's gate went first). Cherry-pick of r3 with range-diff `=` and the same patch-id. Rerun on CLEAR sage-2480, 05:04:50Z to 05:04:58Z: brief files 14/14, library 83/83, business 209/209, 0 skipped. - Every sweep found the worktree clean and no node process in it or in its temp dir.