diff --git a/agents/honker/work/queue-283/BUILD.md b/agents/honker/work/queue-283/BUILD.md new file mode 100644 index 000000000..01a73db35 --- /dev/null +++ b/agents/honker/work/queue-283/BUILD.md @@ -0,0 +1,111 @@ +# Row 283 (#1788) C2-01d build notes (Honker) + +Append-only. Design: DESIGN.md d2 16e809457, APPROVE webby-0172. Gate owner Sage, reviewer Webby. + +## 2026-10-23 build on 45dca69f6 (sage-3577) + +Branch honker/row283-build from origin/refactor 45dca69f6e4bce52834587f66f2ff33ba839ab8a. The old branch +honker/row283 (16e809457) held only the design, so its two commits were cherry-picked as 398314cf4 +(d1) and 4f66dccde (d2). The DESIGN.md blob equals 16e809457's. + +Commits: +- 331653b38: code, README and tests T1-T8. +- 28918824c: the survey.test:73 re-pin, alone (mos-1330 item 4). +- this file. + +Nothing has run. No node, test or mutant ran during the build: Darkwing held the slot. Every count +below is a plan until Sage's CLEAR. + +### Drift check (sage-3577) + +`git diff --stat 94c33a43c 45dca69f6` (design base to build base) over the paths the tests read or +hash-pin: +- No change in packages/library, packages/business/src/business.mjs or + packages/business/tests/business.test.mjs. +- No test hash-pins a library file. bus library.test and library-sync.test import index.mjs, and + stack-copy.test only checks that index.mjs exists. +- webui library-cli.test changed one line and doesn't read scope. The `classify` in webui reads.mjs is + the business one, not the library's. + +I redo the drift check on BASE..refactor before the in-review move. + +### N3 (webby-0169): deepEquals of the mapping output outside packages/library + +`git grep -n playground -- 'packages/*/tests/*' ':!packages/library/*'` and the files naming +`universes` give two files: +- business.test.mjs:20 compares `validateBusinessDocument(...).projects.books` with + `checkMapping(mappingEntry())`. That is output against output, so `supporting: []` shows up on both + sides. :27 compares only `.playground`. +- library-projects-protected.test.mjs compares file bytes (:31, :32) and change paths (:58), never the + mapping output. + +No re-pin is needed outside library. Inside library, only the draft deepEqual at mapping.test:48 gains +`supporting: []`, as the design says. + +### Q1 departure (sage-2923) and the :73 re-pin (mos-1330 item 4) + +survey.mjs:65 counts `supporting: 0` and formatSurvey prints it. T8 is the only added test +(survey.test "C2-01d: with --mapping the survey counts supporting files"). + +mos-1330 item 4, packages/library/tests/survey.test.mjs:73, commit 28918824c: +- old: `'scopes canon 5, playground 1, unmapped 3; indexed 6',` +- new: `'scopes canon 5, playground 1, supporting 0, unmapped 3; indexed 6',` + +N2: the pin gains only `supporting 0`. No other text on the line or in that test moved. + +### Q2 tradeoff (webby-0169) + +A supporting prefix that holds a playground prefix refuses `library-mapping-invalid`. That goes past +the brief, but it is the fail-closed reading, and it uses an existing code: classify checks supporting +first, so that playground prefix could never match. The cost is that she can't nest a playground under +a supporting folder, though she can nest the other way (a playground holding a supporting folder). + +### Handoffs + +- Q4 (webby-0169, sage-2930): the M12 delete rule ("A delete also needs the path to exist at the tip as + a 100644 blob", case2 DESIGN.md :308) is not in supportingPath. It binds row 288 (C2-02) via Gitea + #1793 comment 30738, with tests for a missing path and a non-100644 entry. +- A save with no `editor` refusing is C2-02's (DESIGN.md section 1). + +### Choices the design left open + +- The book loop is now `againstBooks(owners, list, code)` in mapping.mjs, called once per list. The + playground's messages and codes are byte-equal to before. +- A non-string path refuses `library-write-story-path` ("the path must be a string") as part of + check 1. A non-array `tree` is a TypeError (DESIGN.md section 3). +- Check 8 builds the tree's names as every path and every folder above it, keyed by + `normalize('NFC').toLowerCase()`. A name on the way refuses if its key holds any name other than + itself, so a tree that already holds case twins refuses a write to either (R7). + +### Mutants (exact strings) + +Each applies one replacement to the build tip. Run one at a time, and restore with `git checkout --` +on the one file before the next. + +| Mutant | File | Replace | With | Killed by | +|---|---|---|---|---| +| M1 | src/scope.mjs | `const prefix = entry.supporting.find(p => inside(p, path));` | `const prefix = entry.supporting.find(p => inside(p, path) && !entry.playground.some(q => inside(q, path)));` | scope.test "supporting comes before playground", supporting.test "passes at book level" (both-lists path) | +| M2 | src/mapping.mjs | `if ((inside \|\| holding) && owner !== id)` | `if ((inside \|\| holding) && owner !== id && list !== 'supporting')` | mapping.test "each supporting prefix rule", business.test C2-04 | +| M3 | src/supporting.mjs | `for (const p of own) if (LINKS.has(modes.get(p)))` | `for (const p of own) if (false && LINKS.has(modes.get(p)))` | supporting.test "symlink or gitlink" | +| M4 | src/supporting.mjs | `const fold = name => name.normalize('NFC').toLowerCase();` | `const fold = name => name.normalize('NFC');` | supporting.test "case or Unicode form" | +| M4b | src/supporting.mjs | `const fold = name => name.normalize('NFC').toLowerCase();` | `const fold = name => name.toLowerCase();` | supporting.test "case or Unicode form" (NFD case) | +| M5 | src/supporting.mjs | `Buffer.byteLength(path, 'utf8') > MAX_BYTES` | `path.length > MAX_BYTES` | supporting.test "1025 bytes" | +| M6 | src/mapping.mjs | `const IDENT_BAD = /[\u0000-\u001f\u007f-\u009f<>]/;` | `const IDENT_BAD = /[<>]/;` | mapping.test "editor is optional" | +| M6b | src/mapping.mjs | `const IDENT_BAD = /[\u0000-\u001f\u007f-\u009f<>]/;` | `const IDENT_BAD = /[\u0000-\u001f\u007f-\u009f]/;` | mapping.test "editor is optional" | +| M7 | src/supporting.mjs | `if (place.scope !== 'supporting' \|\| place.universe !== universe)` | `if (place.scope !== 'supporting')` | supporting.test "outside the universe's supporting folders" | +| M8 | src/mapping.mjs | `if (sid === pid && s !== p && holds(s, p))` | `if (false && sid === pid && s !== p && holds(s, p))` | mapping.test "each supporting prefix rule" (Workshop case) | + +(The `\|` in the table is an escaped `|` in the source text.) Each mutant runs the one test file named, +plus business.test for M2, under `timeout -k 10 600`. A mutant passes the gate only when its run is red +on the named test and the restored file's sha256 equals the build tip's. + +### Gate plan (on Sage's CLEAR) + +1. The touched files whole, each under `timeout -k 10 600`: library mapping, scope, supporting and + survey tests, and business.test.mjs. Counts go below. +2. M1-M8 as above. +3. The full gate, under mos-1275 timeouts: `node --test packages/library/tests/` and + `node --test packages/business/tests/` each under `timeout -k 10 1800`, and every scripts/test-*.sh + under `timeout -k 10 300`. pipefail and tee, with skips named (test-task Docker groups). +4. Before in-review: grep packages/*/tests for a 64-hex pin on any changed file and for the changed + message text, then the drift check on BASE..refactor.