From 0761e603c07709693f5d3a0dc8dea18149dbbb34 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Thu, 22 Oct 2026 13:43:35 -0500 Subject: [PATCH] row 283 (#1788): design draft 1. supporting lists with the playground's prefix rules and library-supporting-other-universe, cross-list rules, the optional editor, supporting first in classify, supportingPath over the listTree array; survey.mjs departure Q1 for Sage, Q2-Q5 for Webby; doc only (sage-2906) --- agents/honker/work/queue-283/DESIGN.md | 170 +++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 agents/honker/work/queue-283/DESIGN.md diff --git a/agents/honker/work/queue-283/DESIGN.md b/agents/honker/work/queue-283/DESIGN.md new file mode 100644 index 000000000..f4cf2a4d6 --- /dev/null +++ b/agents/honker/work/queue-283/DESIGN.md @@ -0,0 +1,170 @@ +# Row 283 (#1788) C2-01d design, draft 1 (Honker) + +Brief: docs/plans/2026-10-20_case2-rows.md "C2-01d" at refactor 94c33a43c. Sources: the design at +agents/gosalyn/work/case2/DESIGN.md b68d79663, the ruling after r11 (:207), the M12 paragraph (:300), +1.1 (:755) and 1.2 (:817). Base 94c33a43c. Reviewer Webby, gate owner Sage. Nothing is built yet. The +build waits for 282 to land (sage-2906). + +## 1. Mapping (packages/library/src/mapping.mjs) + +`supporting`: +- Every universe kind takes an optional `supporting` list: plain `['name','books','playground','supporting']`, + hybrid adds it to its seven keys, and a draft gets `['kind','name','playground','supporting']`. +- Each value is checked as a playground prefix is: a string ending in `/` whose body passes `folder()`. +- The output carries `supporting` on every universe, `[]` when absent, the way a draft carries + `books: []`. classify then needs no `?? []`. The business writer writes the raw document + (organization.mjs:195), not this output, so no business file changes shape. +- The prefix rules, across every entry (ruling after r11): + - A supporting prefix holding another supporting prefix, or one listed twice, refuses + `library-mapping-invalid`. + - A prefix equal to one of its own entry's book folders, or holding one, refuses `library-mapping-invalid`. + - A prefix inside or holding another entry's book folder refuses `library-supporting-other-universe`. + - Level comes from where the prefix sits, as for the playground: inside one of its own entry's books + is book level, and outside every book is universe level. A draft's prefixes are all universe level. +- Between the two lists (my choice, Q2): + - A supporting prefix may equal a playground prefix of the same entry. That folder is writable. + - A playground prefix may hold a supporting prefix of the same entry. The inner folder is supporting + and the rest stays playground. + - A supporting prefix that holds a playground prefix refuses `library-mapping-invalid`. classify + checks supporting first, so that playground prefix could never match. + - A supporting prefix equal to, holding or inside a playground prefix of another entry refuses + `library-mapping-invalid`. Otherwise one path would have two universes. +- The book loop at mapping.mjs:128-140 becomes a helper that both lists call with their own code and + label. The playground's messages and codes stay byte-equal, so no existing test text moves. + +`editor`: +- The entry takes an optional `editor`. When present, it is an object with exactly the keys `name` and + `email` (via `only()`). Each value is a string that is non-empty after trim and has no control + character (`[\u0000-\u001f\u007f-\u009f]`). Anything else refuses `library-mapping-invalid`. +- The output carries `editor: { name, email }` only when present, as `user` and `vaultPrefix` do. A save + with no `editor` refusing is C2-02's job. + +## 2. Scope (scope.mjs) + +- `classify` gets a supporting loop before the playground loop (design 1.2). It returns `place(...)` + with `{ scope: 'supporting', universe, book }`. The book is the book folder holding the prefix, or + null at universe level and in a draft. The type and folder come from the first folder under the + prefix, as for the playground. +- The `.md` check stays first, so a `.canvas` or `.base` under a supporting prefix stays unmapped. +- The comment above `classify` names the three prefix scopes and the order. + +## 3. supportingPath (new supporting.mjs) + +`supportingPath(mapping, universe, path, tree)` returns `path` or throws +`LibraryError('library-write-story-path', ...)`. It is pure and reads no git. + +- `tree` is the array `listTree` returns (reader.mjs:26, `{ mode, type, oid, size, path }`), so the + C2-02 broker passes its listing as it is (Q3). A non-array `tree` throws a TypeError, because that is + a caller bug, not a path class. +- The checks run in this order, each with its own message naming the class: + 1. The path must be a string of at most 1,024 UTF-8 bytes (`Buffer.byteLength`). + 2. It must have no control character, using the same class as `editor`. + 3. It must have no backslash. + 4. It must not be absolute (a leading `/`). + 5. It must have no empty, `.` or `..` segment. That also catches `//` and a trailing `/`. + 6. `classify(mapping, path)` must give scope `supporting` and that same `universe`. This one check + covers an unknown universe, an unmapped path, a `.canvas`, `.base` or attachment, a canon or + story path, a playground path that isn't also supporting, and another universe's prefix. The scope + rule then has one source, and M1 below hits both. + 7. Every proper prefix of the path (`a`, `a/b`, ...) and the path itself must not be in `tree` with + mode `120000` (symlink) or `160000` (gitlink). + 8. No prefix of the path, and not the path itself, may equal a tree path or a tree directory + (derived from the entries' paths) when lowercased, unless it equals that tree path exactly. + Directories are checked too, because the folders under a supporting prefix are hers to name. A + path in a tree that holds both `a.md` and `A.md` refuses, which matches R7. +- The refusal messages quote the path with JSON, except for the control-character class, whose message + leaves the path out. +- The delete rule ("A delete also needs the path to exist at the tip as a 100644 blob", M12) isn't in + the brief's supportingPath list. It stays with C2-02's delete (Q4). +- index.mjs exports `supportingPath`. + +## 4. README (packages/library/README.md) + +- Mapping: `supporting` and its rules, the cross-list rules, and `editor`. +- Refusals: a `library-supporting-other-universe` row, and the new cases in the + `library-mapping-invalid` row. +- Scope: supporting first, then playground, then canon. +- A new section, "Agent write paths", for supportingPath and its classes. + +## 5. Callers of classify outside files owned (Q1 for Sage) + +- survey.mjs:65 builds `{ canon: 0, playground: 0, unmapped: 0 }` and increments + `scopes[classify(...).scope]`. A supporting file adds `supporting: NaN`, and formatSurvey (:92) never + prints it. I propose a departure: add `supporting: 0` and print it, so the line becomes + `scopes canon 5, playground 1, supporting 0, unmapped 3; indexed 6`. That re-pins survey.test:73 and + adds a count test. Without it, the survey reports garbage once she marks a folder. The departure + covers two files, packages/library/src/survey.mjs and packages/library/tests/survey.test.mjs. +- No change in the rest, each for a reason: + - reader.mjs:183 skips only unmapped, so supporting files are indexed like playground files. + - R5 (:207) flags only canon to playground, as design 1.3 R5 says. A canon link to a supporting file + raises nothing. + - render.mjs:23 gives supporting a null badge. The supporting badge belongs to C2-10b (design :1415). + - mirror.mjs:246 refuses only unmapped. Reading supporting files by scope is C2-05c's. + - No bus or webui source filters on scope (git grep at 94c33a43c). +- M9 already protects the whole `projects` key from agent edits (library-projects-protected.test). No + test is added there. + +## 6. Tests + +- mapping.test: + - T1: `supporting` at book level, at universe level, in a hybrid and in a draft. The output carries + it, with `[]` when absent. The draft `deepEqual` at :48 gains `supporting: []`. + - T2: one test per prefix rule. + - These refuse `library-mapping-invalid`: no trailing `/`, a bad folder, one holding another, one + listed twice, one equal to its own book, one holding its own book, one holding a playground + prefix, and one overlapping another entry's playground. + - These refuse `library-supporting-other-universe`: one inside another universe's book, and one + holding another universe's book. + - An equal same-entry playground passes. + - T3: `editor` passes, and absent passes. It refuses `library-mapping-invalid` for a non-object, an + array, an extra key, a missing key, an empty or blank value, a non-string, and a control character + in either value. +- scope.test, T4: + - Supporting at book level, universe level and in a draft each give their universe and book. + - A folder in both lists is `supporting`, and a playground-only folder is `playground`. + - A supporting `.canvas` or `.base` is unmapped. +- supporting.test (new): + - T5 passes at book level, universe level, in a draft, in a both-lists folder, and at exactly 1,024 bytes. + - T6 has one refusal per class against a fixture tree with a symlink, a gitlink and a case variant. + The classes are: + - unmapped, `.canvas`, `.base`, an attachment, canon, playground-only, another universe's + supporting prefix, and an unknown universe; + - absolute, `..`, `.`, `//`, a trailing `/`, a backslash, a C0 control and DEL; + - a symlink folder, a gitlink folder, and a symlink at the path itself; + - a case-variant file and a case-variant folder; + - 1,025 bytes made with a multi-byte character, so bytes and characters differ. + - Each asserts the code `library-write-story-path`. +- business.test: T7 adds one assertion that `validateBusinessDocument` refuses + `library-supporting-other-universe`. +- survey.test (only if Q1 is approved): T8 counts one supporting file, and :73 is re-pinned. + +## 7. Mutants (exact strings go in BUILD.md) + +| Mutant | Change | Killed by | +|---|---|---| +| M1 | classify checks playground before supporting (the brief's mutant) | T4 both-lists, T5 both-lists | +| M2 | the other-universe fail for supporting is dropped | T2 | +| M3 | the symlink and gitlink check is dropped | T6 | +| M4 | the case check is dropped | T6 | +| M5 | the byte length uses `path.length` | T6 1,025 bytes | +| M6 | the `editor` control-character check is dropped | T3 | +| M7 | supportingPath skips the universe match | T6 other universe | +| M8 | the supporting-holds-playground check is dropped | T2 | + +## 8. Gate + +- Webby approves the manifest. +- The library and business suites and every scripts/test-*.sh are green on Sage's gate. +- Timeouts (mos-1275): each package `timeout -k 10 1800`, each script `timeout -k 10 300`, each single + file and mutant `timeout -k 10 600`. These come from my row 256 gate at 10:34Z. +- Before in-review, grep packages/*/tests for 64-hex pins on the changed files and for the changed + message text, then drift-check BASE..refactor. + +## 9. Questions + +- Q1 (Sage): approve the survey.mjs and survey.test.mjs departure in section 5? +- Q2 (Webby): the cross-list rules in section 1? +- Q3 (Webby): `tree` as the `listTree` array? +- Q4 (Webby): the delete's 100644 check stays with C2-02? +- Q5 (Webby): `editor` refuses only blank values and control characters, as the brief says. `<` and `>` + would break a git ident. I'd leave them to C2-02's commit step unless you want them refused here.