12 KiB
12 KiB
Row 283 (#1788) C2-01d design, draft 2 (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
supportinglist: 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 passesfolder(). - The output carries
supportingon every universe,[]when absent, the way a draft carriesbooks: []. 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.
- A supporting prefix holding another supporting prefix, or one listed twice, refuses
- 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 keysnameandemail(viaonly()). Each value is a string that is non-empty after trim and has no control character ([\u0000-\u001f\u007f-\u009f]),<or>(webby-0169 R1: git drops them from an ident and commits under a different name, so C2-02 would never see a failure). Anything else refuseslibrary-mapping-invalid. - The output carries
editor: { name, email }only when present, asuserandvaultPrefixdo. A save with noeditorrefusing is C2-02's job.
2. Scope (scope.mjs)
classifygets a supporting loop before the playground loop (design 1.2). It returnsplace(...)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
.mdcheck stays first, so a.canvasor.baseunder a supporting prefix stays unmapped. - The comment above
classifynames 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.
treeis the arraylistTreereturns (reader.mjs:26,{ mode, type, oid, size, path }), so the C2-02 broker passes its listing as it is (Q3). A non-arraytreethrows 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:
- The path must be a string of at most 1,024 UTF-8 bytes (
Buffer.byteLength). - It must have no control character, using the same control class as
editor(not<or>). - It must have no backslash.
- It must not be absolute (a leading
/). - It must have no empty,
.or..segment. That also catches//and a trailing/. classify(mapping, path)must give scopesupportingand that sameuniverse. This one check covers an unknown universe, an unmapped path, a.canvas,.baseor 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.- Every proper prefix of the path (
a,a/b, ...) and the path itself must not be intreewith mode120000(symlink) or160000(gitlink). - No prefix of the path, and not the path itself, may equal a tree path or a tree directory
(derived from the entries' paths) after
normalize('NFC').toLowerCase()on both sides, unless it equals that tree path exactly. NFC as well as case, because APFS treats NFC and NFD forms of a name as one file (webby-0169 N1). Directories are checked too, because the folders under a supporting prefix are hers to name. A path in a tree that holds botha.mdandA.mdrefuses, which matches R7.
- The path must be a string of at most 1,024 UTF-8 bytes (
- 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). The C2-02 brief doesn't name it (only design :308), so BUILD.md carries it to C2-02 under Handoffs (webby-0169 Q4).
- index.mjs exports
supportingPath.
4. README (packages/library/README.md)
- Mapping:
supportingand its rules, the cross-list rules, andeditor. - Refusals: a
library-supporting-other-universerow, and the new cases in thelibrary-mapping-invalidrow. - 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 incrementsscopes[classify(...).scope]. A supporting file addssupporting: NaN, and formatSurvey (:92) never prints it. I propose a departure: addsupporting: 0and print it, so the line becomesscopes 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
projectskey from agent edits (library-projects-protected.test). No test is added there.
6. Tests
- mapping.test:
- T1:
supportingat book level, at universe level, in a hybrid and in a draft. The output carries it, with[]when absent. The draftdeepEqualat :48 gainssupporting: []. - 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.
- These refuse
- T3:
editorpasses, and absent passes. It refuseslibrary-mapping-invalidfor a non-object, an array, an extra key, a missing key, an empty or blank value, a non-string, and a control character,<or>in either value.
- T1:
- 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 isplayground. - A supporting
.canvasor.baseis 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, a case-variant folder, and an NFD-form variant of an NFC tree name;
- 1,025 bytes made with a multi-byte character, so bytes and characters differ.
- unmapped,
- Each asserts the code
library-write-story-path.
- business.test: T7 adds one assertion that
validateBusinessDocumentrefuseslibrary-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 |
| M4b | check 8 drops normalize('NFC') |
T6 NFD variant |
| M5 | the byte length uses path.length |
T6 1,025 bytes |
| M6 | the editor control-character check is dropped |
T3 |
| M6b | the editor < and > 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 scripttimeout -k 10 300, each single file and mutanttimeout -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):
treeas thelistTreearray? - Q4 (Webby): the delete's 100644 check stays with C2-02?
- Q5 (Webby):
editorrefuses 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.
10. Draft 2 (webby-0169)
- R1 folded:
editorrefuses<and>too, with T3 cases and mutant M6b. - Q2 accepted. BUILD.md records the tradeoff: refusing a supporting prefix that holds a playground prefix goes past the brief, but it is the fail-closed reading and uses an existing code. The cost is that she can't nest a playground under a supporting folder, though she can nest the other way.
- Q3 accepted. Q4: carried to C2-02 under Handoffs in BUILD.md, and Sage was asked to add it to C2-02's brief.
- N1 folded into check 8 (NFC and case), with an NFD case in T6 and mutant M4b.
- N2: the survey.test:73 pin gains only
supporting, and the count test is the only addition (mos-1330, its own commit). - N3: before the build, re-grep tests outside packages/library for a
deepEqualof the mapping output on the build base.