Compare commits
154
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8306d6cd2b | ||
|
|
8978559b32 | ||
|
|
1f3b83f604 | ||
|
|
f2e66f1e29 | ||
|
|
1005f99b6a | ||
|
|
2364c521b2 | ||
|
|
23d43a930d | ||
|
|
c708442c4c | ||
|
|
cbf142b7f0 | ||
|
|
36842190b7 | ||
|
|
6b632d4ec9 | ||
|
|
0b3db9c22a | ||
|
|
ea022163d5 | ||
|
|
203d666f8c | ||
|
|
5fe8a03dc0 | ||
|
|
f12b790b4c | ||
|
|
7d550703e2 | ||
|
|
d83af73634 | ||
|
|
b6d604e96b | ||
|
|
dce214677f | ||
|
|
7e1b374d46 | ||
|
|
49ade06654 | ||
|
|
fb986ab2a9 | ||
|
|
b24c0f2ddb | ||
|
|
7d88908c53 | ||
|
|
daea42820a | ||
|
|
e906040957 | ||
|
|
911872a5ee | ||
|
|
16542b141e | ||
|
|
c18d267879 | ||
|
|
849e5bfb30 | ||
|
|
73ee45fa91 | ||
|
|
a6275ca3a8 | ||
|
|
6b4202fb6d | ||
|
|
7a2504991f | ||
|
|
0f1ebbab55 | ||
|
|
5fb0f88a23 | ||
|
|
43faeff461 | ||
|
|
dac7482bb7 | ||
|
|
00502a740d | ||
|
|
923c88e7dc | ||
|
|
9d3c9a3162 | ||
|
|
0603896bc6 | ||
|
|
edf7f504ef | ||
|
|
c226abfac7 | ||
|
|
a800944bce | ||
|
|
e08062d7c5 | ||
|
|
171480e6e8 | ||
|
|
32f05027cc | ||
|
|
6ef7be7330 | ||
|
|
cde575d283 | ||
|
|
471be47e5b | ||
|
|
e826c6dac8 | ||
|
|
78bd0d404b | ||
|
|
873992a9e2 | ||
|
|
39c821e7c0 | ||
|
|
34e1ab2bf5 | ||
|
|
057eebc46d | ||
|
|
55760b938b | ||
|
|
3cf210d00e | ||
|
|
1741bf720c | ||
|
|
d774c2626a | ||
|
|
de018df873 | ||
|
|
3dee8c4a73 | ||
|
|
0242780bac | ||
|
|
ad2be8ae45 | ||
|
|
a06e8f000a | ||
|
|
e2e7c60e56 | ||
|
|
14ad3baadc | ||
|
|
7703865349 | ||
|
|
8ca09fedf7 | ||
|
|
ef7ce90895 | ||
|
|
5c67029398 | ||
|
|
f0f9ff89c4 | ||
|
|
d3fa3a0ada | ||
|
|
fb8cabc649 | ||
|
|
c80230196d | ||
|
|
860e2012f5 | ||
|
|
9f3d724dff | ||
|
|
cb5700b617 | ||
|
|
2e91ddef65 | ||
|
|
88f7ebe0b6 | ||
|
|
e44598b48d | ||
|
|
7c00c83450 | ||
|
|
35130c26bb | ||
|
|
2f488b4ea7 | ||
|
|
660d25e656 | ||
|
|
15c09fd7b5 | ||
|
|
ebb3d83bf6 | ||
|
|
a11a9926cd | ||
|
|
5d8f0f4b54 | ||
|
|
ef2098c967 | ||
|
|
bb7c028852 | ||
|
|
de135fc029 | ||
|
|
ef63393ee4 | ||
|
|
bbca2935ba | ||
|
|
191048fbec | ||
|
|
4a3dc25763 | ||
|
|
bef9a62f4c | ||
|
|
a0a00630ea | ||
|
|
efd6c1f028 | ||
|
|
02bb0afb3a | ||
|
|
387f04dfda | ||
|
|
7a542c0ea2 | ||
|
|
858f773625 | ||
|
|
e10a6f1166 | ||
|
|
bba870ee9c | ||
|
|
2fb6815963 | ||
|
|
57da6fda51 | ||
|
|
361bd753fa | ||
|
|
69d5b39600 | ||
|
|
4e931c4f1f | ||
|
|
d2344ff8bf | ||
|
|
3ab3c010b1 | ||
|
|
deda9f48e8 | ||
|
|
433a95292e | ||
|
|
7a9a90a7a2 | ||
|
|
2f00563249 | ||
|
|
2a216fd169 | ||
|
|
928405649c | ||
|
|
2dfc1b9ad6 | ||
|
|
5bd27e804b | ||
|
|
449c3458ff | ||
|
|
82f50d1273 | ||
|
|
2beb49daeb | ||
|
|
d37be6ff29 | ||
|
|
a5643f9bd7 | ||
|
|
9705a6aaca | ||
|
|
1f404c973b | ||
|
|
b2793f2aa1 | ||
|
|
f942bd7d43 | ||
|
|
425900b036 | ||
|
|
85a72842c3 | ||
|
|
19bb3ae364 | ||
|
|
216302d65f | ||
|
|
0d0df1ecee | ||
|
|
dba33aaf60 | ||
|
|
0597cc1988 | ||
|
|
c7a672d622 | ||
|
|
04e78354a6 | ||
|
|
fcac66f7bf | ||
|
|
ca6b32d668 | ||
|
|
5a02ee3954 | ||
|
|
e1e64ff011 | ||
|
|
6d2e502728 | ||
|
|
c9d65664af | ||
|
|
18f5daaaef | ||
|
|
822bf1aa04 | ||
|
|
162ce46dbc | ||
|
|
dada066753 | ||
|
|
9afb947c73 | ||
|
|
8d53b93c78 | ||
|
|
365a763122 | ||
|
|
f9d218ab1e |
No files matched your search
@@ -0,0 +1,70 @@
|
||||
# Webby review: C2-01c brief, playground levels (mos-1240, sage-2218), round d1
|
||||
|
||||
Webby, 2026-10-21T22:38Z (from `date -u`). Author: Gosalyn (gosalyn-0130). Candidate: b9bcc6b23 on
|
||||
gosalyn/case2-01c-brief, one commit on origin/refactor 6ba8a582e. The rows file blob is 978990319.
|
||||
Diff: `git diff 6ba8a582e b9bcc6b23`, +103/-5. It's a brief, so there's no queue record.
|
||||
|
||||
What I checked, without runs: the brief against the ruling I approved (51fdfb7e2, my REVIEW-d2). I ran
|
||||
`git grep` at 6ba8a582e for `library-playground-outside-book` across the whole tree, and for
|
||||
`playground` in packages/*/src, packages/*/tests, scripts and docs/case2. I read every cite in Files owned
|
||||
and What ships, and the C2-05a and C2-06 briefs in the same file.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
B1. The gate's grep can't pass as written. It says "Grep `packages/*/tests` and `docs` for
|
||||
`library-playground-outside-book` before in-review: no hit may remain." Under `docs`, at b9bcc6b23:
|
||||
- docs/plans/2026-10-20_case2-rows.md:71, C2-01's own brief, which names the old code as what C2-01
|
||||
shipped;
|
||||
- the C2-01c brief itself, at :1193, :1205, :1245 and :1279.
|
||||
Neither is C2-01c's file, and the briefs shouldn't be rewritten to satisfy the grep. Scope it to what the
|
||||
row owns: `packages` (src, tests and README) and `docs/case2`. Those are the hits a builder can actually
|
||||
clear: mapping.mjs:84, README.md:44, mapping.test.mjs:16-20 and :72, business.test.mjs:25 and
|
||||
SURVEY-RUN.md:114. The agents/ hits (herb queue-215 patch, sage queue-218 BUILD.md) are history and stay
|
||||
out of the grep too.
|
||||
|
||||
## Your three questions
|
||||
|
||||
1. Files owned. My whole-tree grep agrees with yours. The old code appears in exactly the six places
|
||||
above outside docs/plans and agents/, and all six are owned. I also read the other playground
|
||||
fixtures: tests/fixtures/mapping.json (book level only), reader.test.mjs:18 and :134-139,
|
||||
survey.test.mjs:73, render.test.mjs (a scopeOf stub that never calls classify), and
|
||||
library-projects-protected.test.mjs:15 and :37. None uses a prefix outside a book, so none changes.
|
||||
The SETUP-SHEET text at :23-27 ("Each playground folder sits inside one book folder") and README :36
|
||||
are right to own.
|
||||
2. C2-05a and C2-06. I agree with both. The C2-05a line covers the book filter, the manuscript, the
|
||||
draft's playground-only lookups and the draft's `library_universe` reply, with a test for each. The
|
||||
C2-06 brief (:365-410) only sends the guard to design 5.2. Its universe check compares each cite's
|
||||
universe, which comes from `classify` through the reader, and it never reads `book`. A draft can't be
|
||||
a hybrid source, so the "hybrid plus its sources" allowance can't let one in. It needs no line.
|
||||
3. Tests. One case per level, the `.canvas` case, the eight refusals, the R5 and R6 reader cases and
|
||||
the business-file case cover the ruling. Two cases are missing. Neither blocks:
|
||||
- A prefix equal to a book folder still refuses `library-mapping-invalid`. The refusals list in What
|
||||
ships names it, but the test list doesn't. mapping.test.mjs:20 (`['Book One/']`) covers it today
|
||||
with the old code, so it has to move, not disappear.
|
||||
- A file in a book folder outside every prefix stays `canon` when a universe-level prefix exists
|
||||
elsewhere. That's the one case for the new order.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. Say what `checkMapping` returns for a draft. The input keys are `kind`, `name` and `playground`,
|
||||
but the frozen output is what `classify`, the reader and C2-05a read. Today's `classify` does
|
||||
`entry.books.find(...)` on every entry (scope.mjs:11). If the output leaves `books` out, any reader that
|
||||
keeps that shape throws. I'd return `{kind: 'draft', name, books: [], playground}`, so `books` is always
|
||||
a list. Add one test asserting the shape.
|
||||
|
||||
N2. What ships defines universe level only for "a plain universe's `playground`". The ruling says a
|
||||
prefix in `universes.<u>.playground` outside every book folder, which includes a hybrid. A hybrid
|
||||
keeps `playground` in its key list (mapping.mjs:70), and it may have `books: []`, which leaves it no
|
||||
book-level prefix to have. Say whether a hybrid can have a universe-level prefix. I read the ruling as
|
||||
yes. If so, one test.
|
||||
|
||||
## Checked and fine
|
||||
|
||||
- The cites hold at 6ba8a582e: mapping.mjs:81-86 (the outside-book check), :115-120 (the sources check
|
||||
that must also refuse a draft), :123-124 (prefix in prefix, listed twice), scope.mjs:5-7
|
||||
(the comment) and :10-20 (`classify`), README.md:36 and :44-45, SETUP-SHEET.md:23-27,
|
||||
SURVEY-RUN.md:114 and business.test.mjs:24-25.
|
||||
- The refusal codes match the ruling. `library-playground-other-universe` goes to Sage on the new-codes
|
||||
line.
|
||||
- After C2-01 and before C2-05a and C2-06, with their candidates rerun if they exist. The reviewer is
|
||||
me, and Sage picks the Opus seat.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Webby review: C2-01c brief, playground levels (mos-1240), round d2
|
||||
|
||||
Webby, 2026-10-21T22:41Z (from `date -u`). Author: Gosalyn (gosalyn-0132). Candidate: `git diff b9bcc6b23 dde2b44cf`
|
||||
on gosalyn/case2-01c-brief, one commit on b9bcc6b23. The rows file blob is a6ac627c8. I checked it
|
||||
against my d1 (REVIEW-d1.md, e44598b48). It's a brief round, so there's no queue record.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
- B1. The gate grep covers `packages` (src, tests and README.md) and `docs/case2`. I reran
|
||||
`git grep -n library-playground-outside-book 6ba8a582e -- packages docs/case2`, and the hits are the
|
||||
six the brief names: mapping.mjs:84, README.md:44, mapping.test.mjs:16-20 and :72,
|
||||
business.test.mjs:25, SURVEY-RUN.md:114. All six are owned.
|
||||
- N1. A draft comes back frozen as `{kind: 'draft', name, books: [], playground}`, and there's a test
|
||||
for that shape.
|
||||
- N2. A hybrid can have a universe-level prefix, with `books: []`, and its files take the hybrid's id.
|
||||
The universe-level test runs in a plain universe and in a hybrid.
|
||||
- Test gaps. A prefix equal to a book folder has its own refusal case, and mapping.test.mjs:20 moves to
|
||||
`library-mapping-invalid`. The prefixes-first case checks canon outside the prefix and playground
|
||||
inside it, in the same book.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
None. Two edited lines in What ships and Tests run past the file's wrap width. That's cosmetic.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Webby review: Case 2 playground ruling, three levels (mos-1233), round d1
|
||||
|
||||
Webby, 2026-10-21T22:29Z (from `date -u`). Author: Gosalyn (gosalyn-0122). For mos-1233, Vikunja 464.
|
||||
Candidate: the section "Ruling after r11: playgrounds at three levels" and the edits to 1.1 and 1.2 in
|
||||
agents/gosalyn/work/case2/DESIGN.md, `git diff 624212842 7a7c00464` on gosalyn/case2-design. It's a design
|
||||
round, so there's no queue record.
|
||||
|
||||
What I checked, without runs: the diff against TARGET.md:92-95 (Mos's STATE repo, read only), REQ-LIB-2
|
||||
and REQ-LIB-3 as written in PRD 1.12 r1 (origin/ludwig/prd-1.12 docs/prd/mosaic-stack.md:1103-1120 and the
|
||||
level note at :1487-1490), design 1.3, 1.4, 3 and 5.2, and the code C2-01c changes at refactor cca206cdc:
|
||||
packages/library/src/mapping.mjs and scope.mjs, and the mapping tests.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
B1. A universe-level or draft prefix can hold a book folder, and then REQ-LIB-3 doesn't hold.
|
||||
- The ruling places a universe-level prefix "outside every book folder of every universe" and refuses
|
||||
three cases: a prefix inside another universe's book folder, a prefix that holds another prefix, and a
|
||||
prefix equal to a book folder. Nothing refuses a prefix that holds a book folder. Today that can't
|
||||
happen, because every prefix has to start with one of its own books (mapping.mjs:82-85). The ruling
|
||||
removes that rule, and the only containment checks left are book in book and prefix in prefix
|
||||
(mapping.mjs:122-123).
|
||||
- The new scope rule checks prefixes first. Take a draft `new` with playground `["Worlds/"]` and a
|
||||
universe `harbor` with books `["Worlds/Book One"]`. Every file in Book One then classifies as
|
||||
playground in `new`, with book null. A lookup in `new` with `scope: "playground"` returns harbor's
|
||||
canon, and harbor's lookups lose the whole book. That breaks REQ-LIB-3's "results never leave it" and
|
||||
turns canon into playground with no problem reported. The same happens with a universe-level prefix of
|
||||
harbor itself: `["Worlds/"]` swallows its own books.
|
||||
- The fix is one refusal: a prefix that holds any book folder refuses `library-playground-other-universe`
|
||||
when the book belongs to another entry, and `library-mapping-invalid` when it's the entry's own book.
|
||||
Add a test for each in C2-01c. With that in place, I agree REQ-LIB-3 holds as written. A draft is a
|
||||
universe entry, every lookup still names one, and a hybrid can't take a draft as a source.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. The 1.2 table's `unmapped` row still ends "anything outside a book folder". After the ruling, a `.md`
|
||||
file under a universe-level or draft prefix lies outside every book folder and is playground. Say
|
||||
"anything outside a book folder and outside every playground prefix". REQ-LIB-2's PRD text already reads
|
||||
correctly ("a Markdown file under a playground prefix is playground").
|
||||
|
||||
N2. C2-01c has to say what `kind` may be. Today a non-hybrid entry can't carry `kind` at all (the `only`
|
||||
list at mapping.mjs:70). After the ruling, `kind` is either absent, `"hybrid"` or `"draft"`, and any other
|
||||
value refuses `library-mapping-invalid`. A draft's allowed keys are `kind`, `name` and `playground`. The
|
||||
ruling lists them, but the brief should list them too, together with the refusal for a draft carrying `books`.
|
||||
|
||||
N3. `classify` finds the book first and the prefix second (scope.mjs:10-18), and the survey and reader
|
||||
call it (survey.mjs:66, reader.mjs:182). C2-01c rewrites it to check prefixes first. The brief should name
|
||||
every reader of `universe.books` and of `classify(...).book`, so a draft with no books and a null book
|
||||
don't reach code that assumes them. C2-05a, C2-06 and the survey's per-book counts are the likely ones.
|
||||
The existing mapping tests for `library-playground-outside-book` (mapping.test.mjs:16-20) change code
|
||||
too, so they're C2-01c's files.
|
||||
|
||||
N4. Section 8's row list has no C2-01c line, and the rows file has no brief for it. Sage briefs the row.
|
||||
The ruling's last paragraph is what that brief should copy.
|
||||
|
||||
## Checked and fine
|
||||
|
||||
- TARGET.md:92-95. Book level is unchanged. Universe level covers "for books in a universe" outside any
|
||||
one book. The draft covers "where new universes can be created". The PRD level note (:1487-1490) can
|
||||
drop its caveat once this lands.
|
||||
- REQ-LIB-2. Scope still comes from the path alone. A playground at any level is never canon. The
|
||||
lookups' canon default and `playground-cited-as-canon` don't depend on the level.
|
||||
- `citation-cross-universe` treating a draft as its own universe, R5 for a canon link into any playground
|
||||
and R6 for a link into a draft fit 1.3's table as written.
|
||||
- The type of a universe-level file comes from the first folder under its prefix, as `classify` does for
|
||||
playground files today (scope.mjs:14-17). So `library_find`'s `type?` keeps working there.
|
||||
- `library_manuscript` needs a book, so it never reads book-null files. That matches the PRD, which
|
||||
names the manuscript only per book.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Webby review: Case 2 playground ruling, three levels (mos-1233), round d2
|
||||
|
||||
Webby, 2026-10-21T22:34Z (from `date -u`). Author: Gosalyn (gosalyn-0125). Candidate: `git diff 7a7c00464
|
||||
51fdfb7e2` on gosalyn/case2-design, one commit, against my d1 (REVIEW-d1.md, 35130c26b). It's a design
|
||||
round, so there's no queue record.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
- B1. The ruling and the 1.1 bullet now refuse a prefix that holds a book folder:
|
||||
`library-playground-other-universe` when the book is another entry's, and `library-mapping-invalid`
|
||||
when it's the entry's own. Section 8's C2-01c line asks for a test of each. My d1 example, a draft
|
||||
`Worlds/` over harbor's `Worlds/Book One`, now refuses. With that, REQ-LIB-3 holds as written. A draft
|
||||
is a universe entry, every lookup names one, and no prefix can reach into another entry's books.
|
||||
- N1. The 1.2 `unmapped` row ends "and outside every playground prefix".
|
||||
- N2. `kind` is absent, `"hybrid"` or `"draft"`, and any other value refuses. A draft takes `kind`,
|
||||
`name` and `playground` with at least one prefix.
|
||||
- N3. I checked the reader list at cca206cdc. mapping.mjs:73-78 builds `books` and `bookOwner`.
|
||||
reader.mjs:182 classifies, :194 spreads `where` (book included) into each file record, and :206-209 reads
|
||||
`sources` for R6, which works for a draft with none. survey.mjs:66 and mirror.mjs:246 read only
|
||||
`scope`. The unlanded C2-05a and C2-06 parts are named, and so are mapping.test.mjs:16-20.
|
||||
- N4. Section 8 has the C2-01c row, after C2-01, owner Opus, with the test gate.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
None.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Case 2 Q219 idea caps (design 2852348d2, C2-06 brief 3d381b0a0), Webby
|
||||
|
||||
Request gosalyn-0136. Ruling Q219 (DECISIONS.md:2157 in the Mos STATE repo, read only), mos-1299,
|
||||
Vikunja 465. Git and reading only. I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
## What I checked
|
||||
|
||||
- 2852348d2 has parent 51fdfb7e2, the tip of origin/gosalyn/case2-design. It touches only DESIGN.md,
|
||||
and the base blob is 0cf1bef29, the same blob as on origin/refactor aca16404c. The new blob is 897ba113b.
|
||||
- 3d381b0a0 has parent aca16404c, which is origin/refactor. It touches only
|
||||
docs/plans/2026-10-20_case2-rows.md, and the new blob is 7e0abdbe2.
|
||||
- Neither commit has a co-author line.
|
||||
- Q219 changes ideas from 5 of 200 to 3 of 120 and leaves the rest of M5 alone. It drops the residual
|
||||
from 1,000 to 360 and says Jason's acceptance is enough. The design matches each point:
|
||||
- the new ruling section (:117-124)
|
||||
- the M5 row (:901)
|
||||
- the residual paragraph (:908-914)
|
||||
- 10 step 8 (:1418-1419)
|
||||
B5 (:418-420) keeps the r1 text as history and adds a pointer to Q219.
|
||||
- In the new DESIGN.md, the only "200" left beside an idea limit is B5's history line. The other
|
||||
hits are the 200 KB page store (:946). The arithmetic holds: 3 x 120 = 360, inside the 1,200 total.
|
||||
- The brief's What ships names both limits (:395). Its Tests line names the refusals (4 ideas, a
|
||||
121-character idea), and the general rule still accepts at each boundary. Out of scope (:404) now
|
||||
says the residual is closed in Q219. In the new rows file, no idea limit says 200 or 5.
|
||||
- The design's section 8 C2-06 row has no residual text, so leaving it unchanged is correct.
|
||||
|
||||
## Notes (non-blocking)
|
||||
|
||||
N1. docs/case2/SETUP-SHEET.md:115-117 on origin/refactor still tells Melanie "at most 5 ideas of up to
|
||||
200 characters each" and "You or Jason can ask for lower limits". After Q219 both are stale. This is
|
||||
a doc change outside these two commits (decision 137, so it doesn't block). It needs a home. The
|
||||
simplest one is a line in C2-06's What ships and its manifest, so the sheet and the guard land
|
||||
together.
|
||||
|
||||
N2. Rows file :398 runs to 143 characters. The paragraph around it wraps at about 105. Rewrap it
|
||||
next time the brief is touched.
|
||||
|
||||
N3. Both commits are unpushed (Gizmoduck's slot). This review covers the commits by sha. If a
|
||||
rebase changes either one, the review doesn't carry over until I compare the new commit with
|
||||
this one.
|
||||
|
||||
## Follow-up 6aecdb675 (gosalyn-0138): APPROVE
|
||||
|
||||
6aecdb675 has parent 3d381b0a0 and no rebase, so the approval of 2852348d2 and 3d381b0a0 stands. It
|
||||
touches only the rows file (blob 15cad9112) and has no co-author line.
|
||||
|
||||
- N1 taken. Files owned adds the sheet's reply limits paragraph only. What ships says what the sheet
|
||||
must say (3 ideas of up to 120 characters) and drops the lower-limits offer. No test in packages or
|
||||
scripts reads SETUP-SHEET.md.
|
||||
- N2 taken. Lines 379-412 are all 110 characters or shorter.
|
||||
|
||||
N4 (non-blocking). The new cite says `:114-117` at aca16404c. At aca16404c, line 114 is blank and the
|
||||
paragraph runs from :115 to :118, ending with "No cited answer" instead. "The reply limits paragraph
|
||||
only" still names it without doubt, so this doesn't block. Fix it next time the brief is touched.
|
||||
|
||||
## Cite fix 33ac28398 (gosalyn-0140): APPROVE
|
||||
|
||||
33ac28398 has parent 6aecdb675 and no rebase. It changes one line, the sheet cite `:114-117` to
|
||||
`:115-118` (rows blob 4b25e76ec). N4 is closed, and the approvals above stand.
|
||||
@@ -0,0 +1,131 @@
|
||||
# Case 2 Q222 reply limits as settings (design df29550c9, C2-06 brief 467be26e3), Webby
|
||||
|
||||
Request gosalyn-0143. Ruling Q222 (mos-1302): "Things should NOT be hardcoded". Mos asked for two
|
||||
checks: no path lets an agent session change a limit, and no bad value switches the guard off. I read
|
||||
with git against origin/refactor aca16404c. I ran nothing.
|
||||
|
||||
## Verdict: CHANGES (R1-R3), design and brief
|
||||
|
||||
The structure is right. The settings live in a top-level key outside `projects`. A human-only verb
|
||||
writes them, outside `ORGANIZATION_KEYS` and `TESTER_ORGANIZATION_VERBS`. A writer fence covers
|
||||
every other path, and each key falls back on its own. The three gaps below are in the text the build
|
||||
will follow.
|
||||
|
||||
## What I checked
|
||||
|
||||
- df29550c9 is a child of 2852348d2 and touches only DESIGN.md (blob 05de030ad). 467be26e3 is a child
|
||||
of 33ac28398 and touches only the rows file (blob 10a818f44). Neither has a co-author line.
|
||||
- Agent paths to a write, at aca16404c:
|
||||
- `organization.set` maps to `limits.set` with actor `<definition>.<role>`, keys limited to
|
||||
`ORGANIZATION_KEYS` (broker.mjs:219, :4205-4213).
|
||||
- The chief's hire and fire use actor `<definition>.<role>` (:4306).
|
||||
- The tester uses actor `"tester"` (:4201), gated by `TESTER_ORGANIZATION_VERBS` (:139, :4158).
|
||||
- Credential verbs (credential-verbs.mjs:27-50) clone the file document.
|
||||
- The launcher's delete uses actor `r.by`, the human who asked (:3892).
|
||||
- Every setter builds `next` with `structuredClone` of the file's document: instance-settings.mjs
|
||||
:226/:291/:327, launch-settings.mjs:180/:288, plugin-settings.mjs:57, credential-verbs.mjs:27/:47.
|
||||
So none of them changes `replyLimits` today, and the M11 fence won't refuse an ordinary agent write.
|
||||
The design's claim holds.
|
||||
- M9 is already in `organizationWriter.write` (organization.mjs:191-201). It compares `document` and
|
||||
then `written`, the JSON round-trip, against `old.projects`. `old` is the on-disk file.
|
||||
- `validateBusinessDocument` (business.mjs:229) rejects unknown top keys, so `replyLimits` has to join
|
||||
`TOP_KEYS`. With any value accepted, a bad hand edit can't stop her business from loading. I agree
|
||||
with that choice.
|
||||
- The ceilings make sense as validity bounds. Every key resolves to an integer within a range, so no
|
||||
value reads as "no limit". Replacing the whole object, with a missing key meaning its default, is
|
||||
fine. It matches how `plugins.set` takes its whole value.
|
||||
|
||||
## Required changes
|
||||
|
||||
R1. Say which `human` the fence compares against: the on-disk file's (`old.human` in `write`), never
|
||||
the incoming document's. The design says "unless `actor` equals the file's `human`". A direct `write`
|
||||
call is in M11's scope. Compared against `document.human`, a write that sets `human` to its own actor
|
||||
and changes `replyLimits` would pass. Add a test: a direct `write` that changes both `human` (to the
|
||||
actor) and `replyLimits` refuses `organization-reply-limits-human-only`, and the file and
|
||||
`changes.jsonl` are unchanged. Put the same test in the brief's M11 list.
|
||||
|
||||
R2. Say what the guard does when the business file can't be read or parsed at check time: missing,
|
||||
unreadable, not JSON, or a document that isn't an object. The fallback paragraph covers a bad
|
||||
`replyLimits` value, not a failed read. The guard must refuse the reply in that case, or check it at
|
||||
the defaults. It must never skip the check. Name the outcome and its code, and add a test to the
|
||||
brief: with the file unreadable, an over-limit reply refuses and `messages` is unchanged.
|
||||
|
||||
R3. Name booleans, arrays and objects in the fallback list next to `null`, a string and a fraction,
|
||||
and test them. JavaScript comparisons coerce: `true >= 1`, and `[7] <= 1000` with `[7]` read as 7. An
|
||||
implementation that checks ranges with `<` and `>` and no type check would let these through. Say
|
||||
"an integer by `Number.isInteger`" or something equally exact, and add `true`, `[7]`, `{}` and `"5"`
|
||||
to the brief's invalid-value tests.
|
||||
|
||||
## Notes (non-blocking)
|
||||
|
||||
N1. M9 checks both `document` and `written` (organization.mjs:193 and :200). Write M11 the same way
|
||||
and say so, so a value with a `toJSON` from a direct caller can't differ between the check and the
|
||||
disk. Agent sessions can't send such a value through the broker, so this doesn't block.
|
||||
|
||||
N2. The guard should read each key with `Object.hasOwn`. `library.limits.set` should refuse unknown
|
||||
keys with `invalid-request`, the way `keys()` does for the other verbs. The design says unknown keys
|
||||
are ignored when read. For the verb it says only that a value outside the table refuses.
|
||||
|
||||
N3. Line cites drift against aca16404c:
|
||||
- `TOP_KEYS` is business.mjs:24, not :21.
|
||||
- `write` is organization.mjs:180, not :177.
|
||||
- `ORGANIZATION_KEYS` is broker.mjs:219 and `TESTER_ORGANIZATION_VERBS` is :139, not :103 and :145.
|
||||
Fix these in the design and the brief.
|
||||
|
||||
N4. "After: C2-04". M9 is already in `write` at aca16404c, so C2-04's writer part has landed. If
|
||||
C2-04 is done, the dependency is met. If parts of it are still open, say which.
|
||||
|
||||
N5. At the ceilings, a human-set value can make the residual very large (up to 100,000 characters
|
||||
of item text) and can make the correction check permissive (`correctionPercent` 100). The guard
|
||||
stays on, since cites are still required, and Q222 gives the choice to the human. It's worth one line
|
||||
in the setup sheet and in `library.status`: the residual is `ideasPerReply` times `ideaLength`.
|
||||
|
||||
N6. The tester's actor is the literal `"tester"` and a role's is `<definition>.<role>`.
|
||||
`ID_PATTERN` (vocabulary.mjs:199) allows both `tester` and dotted ids as the business `human`. So a
|
||||
human id of `tester` or `assistant.bean` would equal an agent actor, and the M11 compare would pass
|
||||
for that agent. The human picks the id, so this is unlikely. Still, the fence could also require
|
||||
that the write came from a human verb, or the validator could refuse a `human` id that collides
|
||||
with those actor shapes.
|
||||
|
||||
## After mos-1305
|
||||
|
||||
Mos accepted four of Gosalyn's choices: the ceilings as validity bounds, 0 as the strict value for
|
||||
`ideasPerReply` and both correction keys, After C2-04, and the verb in limits.mjs. None of R1-R3
|
||||
conflicts with them. All three are about the settings mechanism (the M11 fence, the read at check
|
||||
time, the per-key fallback), which carries into the rework after Q30, so they stand. N4 is withdrawn,
|
||||
since Mos accepted After C2-04. Findings on the item format itself wait for the Q30 rework.
|
||||
|
||||
## Round 2 (gosalyn-0150): APPROVE, design f164dcef8 and brief 51cc0445c
|
||||
|
||||
f164dcef8 is a child of df29550c9 and touches only DESIGN.md (blob 6c5a36eb7). 51cc0445c is a child
|
||||
of 467be26e3 and touches only the rows file (blob 8f350730f, +26/-14). Neither has a co-author line.
|
||||
Both are local, so this approval binds to these two shas. A rebase needs a compare before it carries.
|
||||
|
||||
- R1 met. The fence compares against the on-disk `old.replyLimits` and `old.human`, never
|
||||
`document.human`. It also refuses `tester` and `<definition>.<role>` for any role in `old.roles`.
|
||||
The brief adds the test for a write that changes both `human` and `replyLimits`, plus a mutant that
|
||||
compares `document.human`.
|
||||
- R2 met. A missing, unreadable, non-JSON or non-object file checks at the defaults and
|
||||
`library.status` shows `reply-limits-unreadable`. The brief tests an over-limit reply with the file
|
||||
unreadable, `messages` unchanged. R2 allowed refuse or defaults; defaults is fine.
|
||||
- R3 met. Keys are read by `Object.hasOwn` and used only if `Number.isInteger` and in range. The list
|
||||
names booleans, strings, arrays, objects, fractions and `NaN`, and the brief tests `true`, `[7]`, `{}`,
|
||||
`"5"` and an array as `replyLimits`.
|
||||
- N1 (both `document` and `written`), N2 (unknown key refuses `invalid-request`), N5 (setup sheet line
|
||||
and the residual in `library.status`) and N6 (agent actor shapes) are taken.
|
||||
- N3 cites check at aca16404c: business.mjs:24, organization.mjs:180 and :191-193/:199-200, broker.mjs
|
||||
:139, :219, :419 and :570.
|
||||
|
||||
## Notes (non-blocking)
|
||||
|
||||
N7. broker.mjs:327 (`checkBusiness`, row 177) already refuses a business whose `human` is `tester`, so
|
||||
that half of N6 was closed before this round. A dotted human id equal to a role actor is still legal.
|
||||
With the new rule, that human can't change the limits at all, since their own verb's actor matches
|
||||
the refused shape. That fails closed, so it doesn't block. The build should name the code that verb
|
||||
returns in that case, and the cleaner fix is still a validator refusal of such an id.
|
||||
|
||||
N8. The brief's dotted-human test says "that role's write refuses". `organization.set` can't carry
|
||||
`replyLimits` (it isn't in `ORGANIZATION_KEYS`), so the test has to be a direct `write` with the role's
|
||||
actor. Say so when the brief is next touched, or the test may pass for the wrong reason.
|
||||
|
||||
N9. Rows file :383 runs to 115 characters. Rewrap when the brief is next touched.
|
||||
@@ -0,0 +1,132 @@
|
||||
# PRD 1.15, REQ-LIB-6 rewrite (594add438), Webby
|
||||
|
||||
Request gosalyn-0168 (17:23Z 10-22), for mos-1320 item 8. Commit 594add438 on gosalyn/prd-req-lib-6,
|
||||
parent aca16404c, local, no co-author line. Three files: docs/prd/mosaic-stack.md (+105/-35 with the
|
||||
others), packages/tasks/requirements.json and the version pin in requirements.test.mjs. Mos accepted the
|
||||
REQ-LIB-1 and REQ-LIB-2 widening in mos-1322 and named this review the gate. Read with git and against
|
||||
Mos's STATE repo (read only). I ran nothing.
|
||||
|
||||
## Verdict: CHANGES (B1)
|
||||
|
||||
One sentence in REQ-LIB-6 says more than the design does. Everything else is right.
|
||||
|
||||
## What I checked
|
||||
|
||||
- The quotes. Q223's two passages are verbatim from DECISIONS.md:2212. "Q30: yes." and the F43 line
|
||||
"citations should be requireable. Tunable setting." are Jason's words in the Q226 record (:2253,
|
||||
:2261). Mos's reading of F43 is verbatim from the bullet at :2270. TARGET.md:101 is "Agents NEVER write
|
||||
prose for the user".
|
||||
- REQ-LIB-6 against design b68d79663 and Q223/Q226: agents never write story, they write only `.md`
|
||||
files under a supporting prefix a human marks (design :215-218, :797-802), every other path refuses
|
||||
before git, each write is one commit by the agent's own Gitea account with a trailer, she sees a
|
||||
notice and can revert, chat is free text, every present citation is checked, `requireCitation` is
|
||||
human-only and off by default.
|
||||
- REQ-LIB-2. The scope order matches the design's table (:823): supporting first, so a folder in both
|
||||
lists is supporting, then playground, then canon. Lookups default to canon. The canon-cite rule is the
|
||||
only claim left once Q223 removed the item kinds, which is Mos's reading too.
|
||||
- The residual replacement matches the design's: a supporting doc can hold any text, the path check in
|
||||
the bus is the only path limit, and the agent's token can write any path.
|
||||
- The Version 1.15 note keeps the old REQ-LIB-6 and residual texts verbatim and lists REQ-LIB-3, 4, 5
|
||||
and 7 as unchanged. LIB-4's reading (writes in C2-02 and C2-03b, the fetch read-only through the
|
||||
chief's grant) is right.
|
||||
- requirements.json: version 1.15, three titles changed, all within 120 characters (117, 112, 119). The
|
||||
count stays 85. The test's pin at :24 moves to 1.15, and the status regex at :23 matches the new
|
||||
status line.
|
||||
|
||||
## Required change
|
||||
|
||||
B1. REQ-LIB-6 says `requireCitation` "makes every reply carry a citation". The design keeps the
|
||||
runner's fixed texts passing when it's true (design :245 and :1286-1287, and webby-0151 R1 added the
|
||||
too-long text): no answer, the turn failed, and the answer was too long to send. A test written from
|
||||
this sentence would refuse those notices. Say "makes every reply carry a citation, except the runner's
|
||||
fixed notices", or name them. The 1.14 text had the same exception, so the 1.15 text shouldn't lose it.
|
||||
|
||||
## Notes (non-blocking)
|
||||
|
||||
N1. REQ-LIB-1's rewrap leaves "organization file" alone on a line (prd.md, the second line of the
|
||||
item). Refill the paragraph.
|
||||
|
||||
N2. REQ-LIB-2's "follow the same placement rules" is fine at PRD level. The design also refuses a
|
||||
supporting prefix that holds another supporting prefix, and names `library-supporting-other-universe`.
|
||||
The design is where those live, and the parenthetical already points to it.
|
||||
|
||||
## Round 2: 6742cf5ad (gosalyn-0173, 17:33Z, parent 594add438)
|
||||
|
||||
Only docs/prd/mosaic-stack.md, +6/-4, local, no co-author line. Read with git, nothing run.
|
||||
|
||||
Verdict: APPROVE.
|
||||
|
||||
- B1 met. `requireCitation` now excepts the runner's fixed notices and names all four: no answer, a
|
||||
failed turn, no answer passing the citation check, and the too-long text. Those are the four bodies the
|
||||
design's guard accepts after webby-0151 R1.
|
||||
- N1 met. "organization file" rejoins its sentence. The refill leaves "is refused. So" short on its line,
|
||||
which is cosmetic.
|
||||
|
||||
The gate is `packages/tasks/tests/requirements.test.mjs` and the `scripts/test-*.sh` that read the PRD or
|
||||
requirements.json, on Sage's run.
|
||||
|
||||
## Round 3: row 282 candidate 6b5b06819 on 94c33a43c, and Scrooge's row 285 blocker (18:40Z)
|
||||
|
||||
The rebase is clean: range-diff shows both commits as `=` (1841f7d4e, 6b5b06819), and none of the three
|
||||
paths moved between aca16404c and 94c33a43c. But Scrooge's row 285 report (scrooge-285-routing-q1-webby)
|
||||
shows a gap I missed in both earlier rounds, so the approval does not carry. Read with git, nothing run.
|
||||
|
||||
Verdict on 6b5b06819: CHANGES (B2).
|
||||
|
||||
B2. 1.15 gives creative agents a write (REQ-LIB-6, prd.md:1253-1260), and design f6e3e7f20:259 and the
|
||||
C2-08b brief (883f78f14 rows :1821-1822) put the new action `library.write` in each creative role's
|
||||
`withinRole`. PRD 1.15 never says that action is routed:
|
||||
- REQ-WAIT-3 (prd.md:698-707): a new action is a hard wait "until a PRD version says it isn't", and the
|
||||
paragraph after it lists each routed addition by version. 1.15 adds no line there.
|
||||
- REQ-WAIT-2 (:692-697): no role file may grant a hard-wait action. The list changes only "under a new
|
||||
PRD version that Jason approves".
|
||||
- Code agrees. vocabulary.mjs:62-66 and broker.mjs:51-52 add an action to ROUTED only by the reviewed
|
||||
commit that names it routed. Scrooge cites role.mjs:43 as the refusal.
|
||||
So C2-08b cannot be built to its brief under 1.15 as written. The fix belongs in this PRD row, the
|
||||
version that creates the write. Add a REQ-WAIT-3 line in the existing pattern, for example:
|
||||
"`library.write` was added as a hard wait and routes under REQ-LIB-6 since version 1.15." Also name the
|
||||
bounds that make it routable: creative sessions only, supporting `.md` paths only, a live chief grant,
|
||||
one commit per write with the notice and revert. Then record it in the 1.15 version note.
|
||||
REQ-WAIT-2 makes this Jason's approval, not mine, and Mos's mos-1322 acceptance covered the LIB-1/LIB-2
|
||||
widening, not a hard-waits change. Mos or Jason has to say yes to the routing sentence before it lands.
|
||||
|
||||
My miss: in rounds 1 and 2 I checked REQ-LIB-6 against Q223/Q226 and the design, but never against the
|
||||
REQ-WAIT rules that govern any new action. The design and brief rounds (webby-0151, -0155) missed it too.
|
||||
|
||||
## Round 4: Mos ruling mos-1328 (18:42Z), and the routing text in 759a18d85 (gosalyn-0181)
|
||||
|
||||
Mos ruled that routing `library.write` needs Jason's explicit yes. Q223 and Q226 say what agents may do,
|
||||
but they don't approve a version text. So 1.15 lands as I approved it, and a separate 1.16 carries only
|
||||
the routing. Row 285 goes `--after` the 1.16 row.
|
||||
|
||||
- Row 282 on 6b5b06819: APPROVE again. webby-0160 carries, and B2 moves to 1.16 by Mos's ruling.
|
||||
The rebase checks in round 3 still hold.
|
||||
- 759a18d85 (parent 6b5b06819, PRD only, +22/-3, no co-author line, every new line within 75
|
||||
characters, `git diff --check` clean): it does not go into 282. The text is right, and I'd approve it
|
||||
as the 1.16 content. The REQ-WAIT-3 paragraph (prd.md:711) matches design f6e3e7f20:259-260: creative
|
||||
definitions holding the action within-role, supporting `.md` paths only, a live Gitea grant on the
|
||||
library project, one commit per write with the trailer, the notice and revert. The REQ-LIB-6 sentence
|
||||
points to those bounds without restating them.
|
||||
- For the 1.16 row: say "since version 1.16" in both places, add a 1.16 header and note in place of the
|
||||
1.15 edits, bump requirements.json to 1.16, and move the test pin at requirements.test.mjs:24 to 1.16.
|
||||
I review that text, then Mos puts it to Jason.
|
||||
|
||||
## PRD 1.16 (row #1796), 482e865ac on 6b5b06819 (gosalyn-0184): APPROVE
|
||||
|
||||
I reviewed the diff 6b5b06819..482e865ac only. It touches three files (+35/-6): the PRD (blob a6db9b4a1),
|
||||
requirements.json (version only) and requirements.test.mjs:24 (pin only). No co-author line,
|
||||
`git diff --check` is clean, and the only added line over 75 characters is the test pin. Read with git,
|
||||
nothing run.
|
||||
|
||||
- The REQ-WAIT-3 paragraph (prd.md:719-725) follows the 1.7 and 1.8 routing paragraphs and gives the
|
||||
bounds of design f6e3e7f20:259-260. "Six creative roles" matches `CREATIVE_DEFINITIONS`
|
||||
(packages/business/src/vocabulary.mjs:207 at 94c33a43c).
|
||||
- Whose grant gates a write: Gosalyn's text says the writing agent's own grant. That matches design
|
||||
:259-260 (`library-write-no-grant`), :1396 and :1702, and REQ-LIB-6's "by that agent's own Gitea
|
||||
account". The chief's grant covers only the fetch. If Mos meant the chief's grant, that changes the
|
||||
approved design, so it's Mos's call, not a PRD edit. I agree with the text as written.
|
||||
- The REQ-LIB-6 sentence (:1280-1282) names the action and points back to REQ-WAIT-3. It doesn't restate
|
||||
the bounds.
|
||||
- The header, the 1.16 source line and the 1.16 note are accurate. The note says ROUTED is added only by
|
||||
C2-08b (row 285), after this version, which matches mos-1328.
|
||||
- The gate is the same three files as 282, because the version pin moved.
|
||||
@@ -0,0 +1,97 @@
|
||||
# Case 2 Q226 round 2 (design b68d79663, briefs d4d503db9), Webby
|
||||
|
||||
Request gosalyn-0165 (17:19Z 10-22), for webby-0151. Design b68d79663 on gosalyn/case2-design (parent
|
||||
f164dcef8, DESIGN.md blob 61394054e) and briefs d4d503db9 on gosalyn/case2-06-idea-caps (parent
|
||||
51cc0445c, rows blob 5254e360c). Both are amended and local. I read the deltas from af3349f00 and
|
||||
965dec0ee with git, against my last-fetched origin/refactor aca16404c. I ran nothing.
|
||||
|
||||
## Verdict: CHANGES (R4)
|
||||
|
||||
R1, R2, N1-N5 and the mos-1320 items are met. R3 is met in its wording, but the "ever existed" check
|
||||
has no named source, and the obvious one loses the record on revoke. That's R4, one paragraph and one
|
||||
test line in C2-03b and design 7.2. The PRD REQ-LIB-6 row brief is approved as written.
|
||||
|
||||
## Round 1 items
|
||||
|
||||
- R1 met. A `request-too-large` on a JSON body gets one retry turn naming the limit, then the fixed text
|
||||
`(the answer was too long to send)`. The guard accepts four bodies when `requireCitation` is true.
|
||||
Design 5.2 and the C2-06 brief agree, and the runner test stores the fixed text for a cited reply over
|
||||
the limit.
|
||||
- R2 met. An unreadable business file resolves `requireCitation` to true, `library.status` shows
|
||||
`reply-limits-unreadable`, and the fixed texts still pass. It's in the Q222 paragraph, the ruling
|
||||
fallback, M5 and C2-06's settings and tests. A bad hand-edited value still reads as off, which is right.
|
||||
- R3 wording met. The fallback applies only while no chief grant record has ever existed, in any state.
|
||||
After that a revoked or `held` grant, or a fired chief, refuses `library-fetch-no-grant`. A write, a
|
||||
revert and a save never fall back. The tests cover a valid slot and a mode-0000 slot. Step 4b deletes
|
||||
the slot and revokes its token, and design 7.2, 10 and the brief agree. See R4 for the source.
|
||||
- N1 met. A revert and her save are authored by `editor` and push with `library-human`.
|
||||
`library-mapping-invalid` and `library-human-missing` refuse before any git call.
|
||||
- N2 met. Design 5.2 cites runner.mjs:571-574, :572 and :577-583.
|
||||
- N3 met. M3 names packages/harness/src/tools.mjs and the `raise_decision` skip at :241.
|
||||
- N4 and mos-1320 item 5. C2-21 has both record-only Gitea checks.
|
||||
- N5 met. One line in C2-08b.
|
||||
|
||||
## mos-1320 and sage-2833
|
||||
|
||||
- Item 2. The C2-21 brief and the design keep the `#giteaRole` comment citing mos-1320
|
||||
(broker.mjs:2118-2121 at aca16404c). The `host-creative-role` refusals stay: the grant writer at
|
||||
broker.mjs:1096, `host.run` at :1244, and the step 1 recheck. Each has a named existing test:
|
||||
grant-host.test.mjs:125 and grant-executor.test.mjs:1035 and :1046. I checked all five lines.
|
||||
- sage-2833. C2-05b is back as row 224, built at 23816cf28. The new C2-05c (reviewer Dewey) owns the
|
||||
supporting scope and blob, and C2-02 now runs after C2-05c. The scope enum pin at
|
||||
library-tools.test.mjs:49 in 23816cf28 is `['canon', 'playground']`, so C2-05c changing it is right.
|
||||
- Item 8. The PRD row brief is below.
|
||||
|
||||
## Required change
|
||||
|
||||
R4. Name what the "ever existed" check reads, and make it survive a revoke. At aca16404c a revoke
|
||||
deletes the `gitea_grants` row (broker.mjs:2785), and so does `grant-lost` (:2609). The table has no
|
||||
state column (schema.sql:469-480). A check written as "is there a `gitea_grants` row for the chief"
|
||||
finds nothing after a revoke or a fire, and the fetch falls back to slot `library-gitea`. That's the hole
|
||||
R3 closed. The `events` table is append-only (schema.sql:131-132), and grant events carry `method:
|
||||
'gitea'` and `agent` under `GRANT_EVIDENCE` (broker.mjs:246, :2366, :2469). Say:
|
||||
|
||||
- The check reads grant evidence events, not `gitea_grants`. Any `GRANT_EVIDENCE` event with `method`
|
||||
`gitea` and `agent` = the chief ends the fallback for good.
|
||||
- Drop "on the library project", or name where the project is read after a revoke. `grant.applied` and
|
||||
`grant.revoked` carry no `projects` (broker.mjs:2469, :2786-2789), and the row that has them is gone.
|
||||
Dropping it fails safe: a chief granted on any project loses the slot.
|
||||
- The revoked and fired test cases go through the broker's real revoke and fire paths, so the row is
|
||||
actually deleted when the fetch runs. A fixture that leaves the row in place passes either way.
|
||||
|
||||
## PRD REQ-LIB-6 row brief: approved
|
||||
|
||||
The cites hold at aca16404c: REQ-LIB-6 at docs/prd/mosaic-stack.md:1230, the title at
|
||||
packages/tasks/requirements.json:85, and the `['docs/prd/mosaic-stack.md', '1.14']` pin at
|
||||
requirements.test.mjs:24. The count pin at :21 stays 85 because the id stays. The owned files cover the
|
||||
PRD's `Status: approved, version` line, which :23 matches. Landing before C2-01d and C2-02 is right,
|
||||
since both cite the new text. When the PRD commit comes, I check the new REQ-LIB-6 against Q223 and
|
||||
Q226, the `:1594` residual, the list of unchanged REQ-LIB ids, and the version against whatever landed
|
||||
last.
|
||||
|
||||
## Notes (non-blocking)
|
||||
|
||||
N6. The briefs now order a docs row first (item 0) and C2-05c before C2-02. When Sage adds the queue
|
||||
rows, the `After` fields should match that order, so `queue next` doesn't offer C2-02 early.
|
||||
|
||||
## Round 3: design f6e3e7f20, briefs 883f78f14 (gosalyn-0171, 17:30Z)
|
||||
|
||||
f6e3e7f20 has parent b68d79663 and touches only DESIGN.md (+13/-8). 883f78f14 has parent d4d503db9 and
|
||||
touches only the rows file (+21/-12). Neither has a co-author line. Both are local. Read with git, nothing
|
||||
run.
|
||||
|
||||
Verdict: APPROVE. R4 is met.
|
||||
|
||||
- The check reads the append-only events table for any `GRANT_EVIDENCE` event with `method` `gitea` and
|
||||
`agent` the chief, of any action, and never `gitea_grants`. The design's ruling paragraph, 7.2 and
|
||||
steps 4 and 4b say the same thing as the C2-03b bullet. The cites hold at aca16404c: broker.mjs:246,
|
||||
:2469, :2609, :2785 and :2787, and schema.sql:131-132 and :469-480.
|
||||
- "On the library project" is gone, and the brief tests a chief grant on another project ending the
|
||||
fallback.
|
||||
- The revoked and fired cases go through `grants.revoke` and `instance.fire`, and each asserts the row is
|
||||
gone before the fetch.
|
||||
- The `held` case has an event to find. `held` is a credential-file state (gitea-grant.mjs:400, :414-425)
|
||||
that follows a commit `grant.step` or an unknown token. Both come after gitea grant events (broker.mjs
|
||||
:2366, :2236), so a held chief never falls back to the slot.
|
||||
|
||||
No new notes. N6 (the After order for Sage) still stands.
|
||||
@@ -0,0 +1,115 @@
|
||||
# Case 2 Q226 rework (design af3349f00, briefs 965dec0ee), Webby
|
||||
|
||||
Request gosalyn-0161 (17:07Z 10-22), for mos-1309. Design af3349f00 on gosalyn/case2-design and briefs
|
||||
965dec0ee on gosalyn/case2-06-idea-caps, both local. I read with git against my last-fetched
|
||||
origin/refactor aca16404c. I ran nothing.
|
||||
|
||||
## Verdict: CHANGES (R1-R3)
|
||||
|
||||
The rework follows mos-1309. The shared bot is gone from the write path, the Q222 fence carries over to
|
||||
the two new keys, every write and save keeps the stale-base refusal, and M12 runs before any git call.
|
||||
The three gaps are a silent drop in the runner plan, one setting that fails open, and the fetch fallback
|
||||
Gosalyn asked me to question.
|
||||
|
||||
## What I checked
|
||||
|
||||
- af3349f00 has parent f164dcef8 and touches only DESIGN.md (blob 83bef1bd1, +387/-150). 965dec0ee has
|
||||
parent 51cc0445c and touches only the rows file (blob f3ee8ce29, +677/-126). Neither has a co-author
|
||||
line. Both are local, so this review binds to these shas, and a rebase needs a compare.
|
||||
- Gosalyn's three code findings hold at aca16404c:
|
||||
1. `#giteaRole` is broker.mjs:2122-2135. :2126 refuses every `CREATIVE_DEFINITIONS` role with
|
||||
`gitea-not-declared`, and the comment at :2121 cites mos-1163.
|
||||
2. `clip` is runner.mjs:130-131 (`BODY_MAX` 32000 at :56). :572 holds both fixed texts and :573 sends
|
||||
`clip(body) + unslop.note()`. A JSON body would lose its shape either way, so the library-business
|
||||
rule is needed.
|
||||
3. The runner's tool socket refuses past 65536 at :294. The bus server's `LIMIT` is 65536 (:6), with
|
||||
the save-verb exception at :9, :47-48 and :62. The client checks at client.mjs:19.
|
||||
- The Q222 fence carries. M11 is unchanged: the on-disk `old.human`, the actor-shape refusal, both
|
||||
`document` and `written`, and the `document.human` mutant. Keys are read by `Object.hasOwn`,
|
||||
`requireCitation` only as a `boolean`, and `citationsPerReply` only as an integer from 1 to 1,000. The
|
||||
invalid-value list names `"true"`, `1`, `null`, `[true]` and `{}` for the boolean.
|
||||
- My Q222 notes: N7 is taken (the writer's code is named, no validator change, said why). N8 is taken (a
|
||||
direct `write` with the role as actor). N9 is taken (C2-06's section is within 110 characters).
|
||||
- Writes have no shared-credential path. A write needs a live, not `held`, seat grant
|
||||
(`library-write-no-grant`), the helper answers one named seat's user and file, and it refuses another
|
||||
seat's user. `identity` and `publish` aren't in `ORGANIZATION_KEYS` (broker.mjs:219) or `INSTANCE_KEYS`
|
||||
(:222), so no agent can give itself a grant target.
|
||||
- Stale-base holds on all three commit paths: the agent write, the revert (base is the commit's own
|
||||
blob) and her save. The race retry re-applies only when the blob at the new tip still equals `base`.
|
||||
- The commit message carries only the path and the trailer. M12 refuses control characters, so a path
|
||||
can't inject a second trailer line.
|
||||
- The revert choice is right. A revert is her act, and authoring it as her `editor` with her own token
|
||||
means it still works after the agent's grant is revoked or the seat is fired.
|
||||
|
||||
## Required changes
|
||||
|
||||
R1. Say what she sees when a cited reply is too long to send. The brief removes the clip for a JSON body
|
||||
and says a body over the 64 KiB request limit "fails the send as any failed send does today". Today that
|
||||
is the catch at runner.mjs:577-583: it logs and skips `message.read`. She gets no answer and no notice.
|
||||
The retry doesn't help, since it runs only on a code from the 5.2 table, and `request-too-large` comes
|
||||
from the client (client.mjs:19-20) before the broker. Removing the clip makes this more likely: 32,000
|
||||
characters of non-ASCII text with JSON escaping can already pass 64 KiB. Pick one outcome and test it.
|
||||
The simplest is to treat `request-too-large` on a JSON body like a table refusal: one retry turn naming
|
||||
the limit, then a fixed text such as `(the answer was too long to send)`. That text then joins the bodies
|
||||
the guard accepts when `requireCitation` is true. Put it in 5.2 and the C2-06 brief, with a runner test
|
||||
that a cited reply over the limit stores the fixed text.
|
||||
|
||||
R2. An unreadable business file turns `requireCitation` off. Under Q222 the defaults were the strict
|
||||
values, so "check at the defaults" failed safe. `requireCitation` defaults to `false`, the loose value. If
|
||||
she sets it to `true` and the file can't be read at check time, an uncited reply passes. Mos's check for
|
||||
this round was that an unreadable file fails safe. For the unreadable case only, resolve
|
||||
`requireCitation` to `true` (or refuse the reply) and keep `library.status` showing
|
||||
`reply-limits-unreadable`. The fixed texts still pass, so she still hears that something went wrong. Add
|
||||
the test to C2-06: with the file unreadable, an uncited reply refuses `citation-missing` and `messages` is
|
||||
unchanged. A bad hand-edited value reading as off is fine. That's a human edit, Jason's default, and
|
||||
`library.status` names the key.
|
||||
|
||||
R3. Narrow the C2-03b fetch fallback to slot `library-gitea`. The brief falls back "only while the chief
|
||||
has no live read grant". That also covers a chief whose grant was revoked or `held`, or a fired chief. A
|
||||
revoke would then quietly move the fetch onto the old shared token instead of stopping it, and the
|
||||
shared credential the ruling removes stays live with no end date. The design (10 step 4) says C2-03b
|
||||
retires the slot, and the brief keeps it, so the two disagree. Say:
|
||||
- The fallback applies only when no chief grant record exists at all. Once one has existed, a revoked or
|
||||
`held` grant refuses the fetch with a named code and `library.status` shows it.
|
||||
- The fallback never applies to a push. Add a test: a write with no live seat grant refuses
|
||||
`library-write-no-grant`, and the slot file is never opened (a read trace, or the slot at mode 0000).
|
||||
- Name the step that removes the slot after C2-21 lands, and make the two documents agree.
|
||||
|
||||
## Notes (non-blocking)
|
||||
|
||||
N1. The design's write steps say "The author and committer are the seat's `identity`", and the revert
|
||||
and her save follow "the same steps". Only the C2-02 brief says a revert is authored by her `editor` and
|
||||
pushes with `library-human`. Say it in the design too. Also name the code for a revert or save when
|
||||
`editor` or slot `library-human` is missing. The brief names `library-mapping-invalid` for a save
|
||||
without `editor`, and nothing for a missing slot.
|
||||
|
||||
N2. Design 5.2 still cites `runner.mjs:449` for the fixed texts and `:451-456` for the dropped answer. At
|
||||
aca16404c those lines are `founderCheck` and `role.claim`. The texts are at :572 and the catch is at
|
||||
:577-583. The C2-06 brief has the right lines. Fix the design.
|
||||
|
||||
N3. M3 says `tools.mjs:225-280` "adds those to every session". At aca16404c `raise_decision` is already
|
||||
skipped for creative definitions (packages/harness/src/tools.mjs:241). The rest of the list is right.
|
||||
Also name the path, `packages/harness/src/tools.mjs`.
|
||||
|
||||
N4. Gitea branch protection can block force-push for collaborators. That would back D6 at the remote
|
||||
for the seat tokens, which "can write every path". It doesn't limit paths, so the residual stands. Worth
|
||||
one line next to the protected-file-patterns sentence for C2-21's live check.
|
||||
|
||||
N5. Adding the gitea credential to `roles/chief-of-staff.json` reaches every business that uses the
|
||||
role, Case 1 included. A grant still needs `identity` and `publish` in the business file, which only the
|
||||
human sets, and a human-approved `credential.mint`. So nothing widens without her or Jason. C2-08b's
|
||||
brief could say that in one line, so the reviewer of that row doesn't have to rebuild the argument.
|
||||
|
||||
## After mos-1320 (cc, 17:09Z)
|
||||
|
||||
Mos ruled on Gosalyn's points in gosalyn-0162. None of R1-R3 conflicts with those rulings. R1 and R2
|
||||
are about the runner and the guard. R3 is about the fetch's slot `library-gitea`, which mos-1320 doesn't
|
||||
address. Item 3 covers slot `library-human` for her saves, and I agree with it.
|
||||
|
||||
- Item 5 asks for a record-only check of protected file patterns on the scratch Gitea. N4 fits the same
|
||||
run: whether branch protection refuses a seat user's force-push while her normal push passes.
|
||||
Record-only, like item 5.
|
||||
- Next round I check item 2 in the C2-21 brief: the comment at broker.mjs:2120 cites mos-1320, and the
|
||||
host-grant refusals (`host-creative-role` in the grant writer, the step 1 recheck and `host.run`) stay
|
||||
as they are, each with a test or a named existing one.
|
||||
- Item 8, the REQ-LIB-6 docs row, I review when Gosalyn sends it.
|
||||
@@ -0,0 +1,173 @@
|
||||
# Webby review: Case 2 design r1 (#416)
|
||||
|
||||
Webby, 2026-10-20 (from `date -u`). Independent reviewer, replacing Dewey (Mos, mos-0980).
|
||||
Subject: `agents/gosalyn/work/case2/DESIGN.md`, blob fba567f71dc1, at e997f3b76 and on
|
||||
origin/gosalyn/case2-design 88222f3f3 (same blob). Author: Gosalyn. Static review only. I ran no
|
||||
tests and changed no product code.
|
||||
|
||||
Code base: `git archive 5d019d876`. `git diff --stat 5d019d876 9ae8c73ac -- packages adapters roles scripts`
|
||||
is empty, so every line below also holds at origin/refactor 9ae8c73ac. Paths are relative to `packages/`
|
||||
unless they start with `adapters/` or `roles/`.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
Twelve blockers. Section 6's no-schema claim holds. Section 5's write-side guardrails hold. The
|
||||
message-side guardrails have two channels the design misses (B3, B4), and the smallest slice can't
|
||||
run as written (B1, B2).
|
||||
|
||||
## Citations checked
|
||||
|
||||
These hold as cited: git.mjs:8 (`protocol.allow=never` in SAFE), git.mjs:44 (any `remote.*` key
|
||||
refuses), git.mjs:55-56; project index.mjs:12, :31-84, :97, :195-234, :209, :248-261, :256-258;
|
||||
paths.mjs:7, :14-15, :34-48 (`absolute(project.root)` at :41 throws on a missing root);
|
||||
business.mjs:186-187; gate.mjs:130-167 (typed by name at :134, built-ins at :137-138);
|
||||
claude-gate.mjs:13; tools.mjs:6-7, :95-102, :186, :211-220, :271-280; vocabulary.mjs:92-97, :131;
|
||||
role.mjs:19-28, :55-66; broker.mjs:290-291, :642-650, :1378-1430, :1434-1435, :1867, :2331-2347,
|
||||
:2374-2399; schema.sql:240-250, :296-306, :310; runner.mjs:446-456; seat session.mjs:59-64;
|
||||
adapters/claude/adapter.sh:47-53; launcher.mjs:135, :236-238, :353; webui bus.js:885, :919.
|
||||
|
||||
One miss: the `ls-tree` and `cat-file` reads that 1.3 cites at read.mjs:67-86 are in `blob()` at
|
||||
read.mjs:19-26 (N1).
|
||||
|
||||
## Section 6: "no schema or trigger change"
|
||||
|
||||
It holds. `threads` has no definition column or check (schema.sql:240-250).
|
||||
`message_threads_match` (:296-299) and `delivery_same_thread` (:300-306) check one thread's
|
||||
instance, and a room is one thread per instance, so each insert in `room.send` passes as written.
|
||||
`thread_one_per_run` allows one held thread per run, which matches one session per agent. The only
|
||||
constraint the design must name is `UNIQUE (project, name)` (:249). `room.open` names each thread
|
||||
after its instance, so an existing thread with that name refuses (N9).
|
||||
|
||||
The single-instance rule is code, not schema: broker.mjs:291, :2332-2334, launcher.mjs:135 and
|
||||
:236-237. Changing it is C2-09's job, which is why B1 matters.
|
||||
|
||||
## Jason's three guardrails
|
||||
|
||||
| Guardrail | Enforced in code? |
|
||||
|---|---|
|
||||
| Agents never write prose for the user | Into the library: yes. Gate (gate.mjs:137-138), typed tools (no `write_record` without `record.write`, tools.mjs:211) and human-only verbs make an agent write impossible. In messages: only the form is bounded (M5), and two channels skip M5 (B3, B4). The 400-character limit is stated in 5.1, but the stated residual is too small (B5) |
|
||||
| Agents always refer to the source material | Structural once B6 and B7 are fixed. The quote-in-lines check at a reachable commit is real verification |
|
||||
| Tool use is guarded | Yes: gate, typed tool list, broker re-check (tools.mjs:6-7) and `#checkMessaging` (broker.mjs:1867). N6 closes a role-file gap |
|
||||
|
||||
## Blockers
|
||||
|
||||
B1. The smallest slice gives Melanie no way to chat with a creative agent. `#threaded` is true only
|
||||
for definition `assistant` (broker.mjs:291). `thread.create` refuses `thread-instance-ambiguous`
|
||||
with more than one (:2334). The launcher refuses `thread-not-allowed` for any other definition
|
||||
(launcher.mjs:135, :237). C2-08 adds the `THREAD_DEFINITIONS` constant, but C2-09 is the row that
|
||||
makes the broker and launcher read it and adds `instance` to `thread.create`, and C2-09 is outside
|
||||
the slice (DESIGN 9). The Console's thread creation must pass `instance` too. Fix: put C2-09's
|
||||
broker, launcher and `thread.create` parts in the slice. The Console chat must pick the instance.
|
||||
`room.*` can stay out.
|
||||
|
||||
B2. In the slice, Melanie reads raw JSON and can't see sync failures. Replies are JSON bodies (5.2),
|
||||
and only C2-10 renders them. 7.2 step 2 says "The Console shows them with file and line", but the
|
||||
library pages are C2-11b, outside the slice. A bad Obsidian push silently stops agents seeing new
|
||||
work. Fix: the slice needs a minimal cited-item renderer in the existing chat view and a sync status
|
||||
line (last valid commit, problems, `unpushed`).
|
||||
|
||||
B3. `raise_decision` is a second prose channel to the human. Every agent gets it with free-text
|
||||
`question` and option `text` (tools.mjs:221, comment at :5-6). An uncited raise goes to the human.
|
||||
M3's "no write tool exists" is true, but M5 covers only `message.send`. Fix: either refuse
|
||||
`decision.raise` from creative definitions in the broker and drop the tool from their typed set, or
|
||||
put the raise text through the same guard. Test both paths.
|
||||
|
||||
B4. The Console conversation view shows the model's raw text, including text M5 refused.
|
||||
webui serve.mjs:559-569 proxies `/api/conversation` to the control board, and the reader emits
|
||||
assistant text blocks (conversation/src/events.mjs:58, :82). A refused prose answer stays in the
|
||||
session file and is readable. Fix: leave creative sessions out of the catalogue on a library
|
||||
instance, or show stored messages only. Test that a creative session isn't listed.
|
||||
|
||||
B5. The stated limit understates what M5 lets through. A reply holds 20 items of 400 characters,
|
||||
which is 8,000 characters. An `idea` item's text is free, and any real quote of any line satisfies
|
||||
the cite. That is a full scene per reply, not "a one-sentence suggestion" (5.1). Jason wrote
|
||||
"NEVER", and Q176 says the system enforces it. Fix: add a total-characters cap per reply and a lower
|
||||
cap for `idea` items (for example 200 characters and at most 5 items). State the residual with
|
||||
those numbers. Mos takes the residual to Jason or Melanie for acceptance, because it is their rule.
|
||||
|
||||
B6. A `correction` isn't bound to the source. M5 limits the edit distance between `from` and `to`,
|
||||
but nothing requires `from` to be text in the cited lines. An agent can invent both. Fix: `from`
|
||||
must equal one cite's `quote`, after the same whitespace normalization, inside that cite's lines.
|
||||
Test that an invented `from` is refused.
|
||||
|
||||
B7. `citation-unfetched` has nothing to check against. The web trail stores the URL
|
||||
(broker.mjs:1521) and a summary of status, bytes and redirects (bus/src/web.mjs:46), not the page
|
||||
text. "The quote occurs in the fetched text" needs that text. Also, Claude's built-in WebSearch and
|
||||
WebFetch are on when `policy.webBuiltin` is set (gate.mjs:28-32), and those fetches never reach the
|
||||
broker. Fix: say where fetched text is kept (bounded, per run, or a new table, and if a table then
|
||||
say so). M1 must refuse the built-in web source for creative definitions, so `research` uses only
|
||||
the broker's tools.
|
||||
|
||||
B8. Remote content is a new untrusted input, and the S-rules don't check tree entries. Today only
|
||||
the bus writes record repos. Under 7.2, Obsidian or anyone with push to the Gitea repo writes too.
|
||||
A fast-forward checks out whatever the commit holds. No rule refuses symlinks (120000), gitlinks
|
||||
(160000), executables or nested `.gitattributes` and `.gitmodules`. Separately, S1 refuses the
|
||||
`.gitignore` that C2-12 commits, and Obsidian puts new notes and `.trash/` at the vault root by
|
||||
default. Fix: add a rule that every entry is a 100644 blob or a tree, and refuse those names at any
|
||||
depth. Allow `.gitignore` at the top. Commit Obsidian's new-file location and trash settings in
|
||||
`app.json`. Test each case.
|
||||
|
||||
B9. The fail-closed state in 7.2 strands Melanie's writes. If remote main is ahead and invalid, the
|
||||
bus stays on the old commit (step 2). The next bus write commits on that base. The push can't
|
||||
fast-forward, so the commit is `unpushed`, then `library-diverged`, with no merge until C2-17.
|
||||
S5 makes this likely: Obsidian users create `[[stub]]` links to notes that don't exist yet, so one
|
||||
dangling link in the playground blocks the whole sync. Fix: refuse library writes while remote main
|
||||
is ahead and invalid (`library-remote-invalid`). Apply S5 to canon only, or allow unresolved links
|
||||
in `playground/`. Test both.
|
||||
|
||||
B10. The 7.2 gate can't catch the obvious mutant. Tests use the file protocol through a test-only
|
||||
argument, so the https path, `GIT_ASKPASS` and the protocol override are never run. A mutant that
|
||||
sets `protocol.allow=always` passes them all. `git()` passes only `gitEnv()` plus the identity
|
||||
argument (git.mjs:10-12, seat checkout.mjs:79-89), so sync needs its own env entry for the helper.
|
||||
`GIT_TERMINAL_PROMPT=0` (checkout.mjs:85) doesn't stop askpass, which is fine. Fix: one function
|
||||
builds the production fetch and push argv and env. A test asserts that the argv holds SAFE, then
|
||||
exactly `-c protocol.https.allow=always`, and no other `protocol.*` key. Another asserts that
|
||||
`GIT_ASKPASS` names a helper outside the repo that reads the slot file. Keep the file-protocol
|
||||
switch out of any path that a business file or a bus verb can reach.
|
||||
|
||||
B11. An optional `root` breaks more than the overlap loop. `ensureCheckouts` clones every entry's
|
||||
root (seat checkout.mjs:310, :319). With no root it calls `clone(project, undefined, ...)`, so "a
|
||||
project with no root gives agents no clone" (2.1) is false today. `projectFilePath(entry.root)`
|
||||
(business/src/project.mjs:9-10) throws on undefined, and it is called at business/src/cli.mjs:72,
|
||||
:78, :118, resolve.mjs:44 and instance-settings.mjs:142. Fix: C2-04 lists these consumers, each
|
||||
skips a library project explicitly, and each has a test.
|
||||
|
||||
B12. Scope and crossovers can also come through front-matter fields, and in the slice a hybrid
|
||||
rests on convention. S2 and S3 speak of links only. `extends:`, `sources`, and `exclude` and `pin`
|
||||
paths are references too, and a canon book entity with `extends: playground/...` would bring
|
||||
playground text into canon lookups labelled canon. `library_manuscript` has no `scope`, so it must
|
||||
label or refuse a playground book. In the slice there is no crossover dialog (C2-11b), so a hybrid
|
||||
is a hand-written `universe.json` pushed from Obsidian, and S6 doesn't require `sanctioned`. Fix:
|
||||
put every path field under S2 and S3. Make S6 require `sanctioned`. State that in the slice a hybrid
|
||||
is Melanie's hand edit, and that only her account and the bus account can write to the Gitea repo.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. Cite read.mjs:19-26 for the blob reads, not :67-86.
|
||||
N2. `END_PROTECTED` is `[THREAD_DEFINITION]` (vocabulary.mjs:135). Say whether creative
|
||||
definitions become end-protected when `THREAD_DEFINITIONS` replaces it.
|
||||
N3. The runner sends `(the session gave no answer)` and `The turn failed: ...` (runner.mjs:449).
|
||||
The guard will refuse both, so Melanie never learns a turn failed. Add a fixed turn-failed notice,
|
||||
and test that any notice other than the fixed bodies is refused.
|
||||
N4. Promotion moves several files. `commitFile` commits one path (index.mjs:219). C2-02 needs a
|
||||
multi-path commit.
|
||||
N5. Ids allow capitals (paths.mjs:7). Two universes that differ only by case collide on a
|
||||
case-insensitive file system. Enforce lowercase in `checkLibrary`.
|
||||
N6. A role with no `messaging` key may message anyone (role.mjs:50). M1 must require `messaging`
|
||||
for creative definitions.
|
||||
N7. C2-05b's sync-before-read needs C2-03, which its row doesn't list. C2-03 reads the remote URL
|
||||
that C2-04 adds to the business file, so either pass the URL in or order C2-04 first.
|
||||
N8. C2-06 must run the guard through both the runner's RESULT path and `send_message`, with a
|
||||
non-creative sender in a library business. Otherwise a mutant keyed on `CREATIVE_DEFINITIONS`
|
||||
passes, against 5.2's "role choice doesn't matter".
|
||||
N9. Name the refusal when `room.open` meets an existing thread with an instance's name
|
||||
(schema.sql:249).
|
||||
|
||||
## Rows and the date
|
||||
|
||||
The rows are small and most gates name the right refusal. B6, B10 and N8 are the gates that miss
|
||||
their obvious mutant. The listed slice (C2-01 to C2-08, C2-12, C2-13) does not let Melanie start.
|
||||
She can write in Obsidian, but she can't reach an agent (B1) or read its answers (B2). With
|
||||
C2-09's broker part and a minimal renderer added, it can be done by 10-27 only if T1 row 1 (row
|
||||
190) lands by about 10-24, as section 9 says. Reaching the Console from her machine also needs SSO
|
||||
(Q175).
|
||||
@@ -0,0 +1,74 @@
|
||||
# Webby review: Case 2 design r10 (#416), two system units
|
||||
|
||||
Webby, 2026-10-20T21:01Z (from `date -u`). Author: Gosalyn (gosalyn-0045). Commit 4281b0064 on
|
||||
gosalyn/case2-design, DESIGN blob 4a1860e698e0. Diff `git diff 51006710a 4281b0064`, one commit. I
|
||||
read both unit templates, scripts/webui-service.sh, webui `cli.mjs serve`, scripts/mosaic and decisions 62,
|
||||
94 and 102 on origin/refactor. Static review.
|
||||
|
||||
## Verdict: CHANGES (B1, B2, B3)
|
||||
|
||||
The fix is the right one. Two units from the existing templates match how the stack runs today, and C2-10b
|
||||
needs the Console. One copy for both units keeps the human proof working, since the bus accepts only its
|
||||
own human-cli.mjs path (webui-service.sh:266-273). Making `%h` literal is correct for a system unit. The new
|
||||
refusal cases are good.
|
||||
|
||||
B1. The Console's boot start is Jason's call, not mine or Mos's, and the design should default to the strict
|
||||
reading until he rules. Decision 94 (lead-decisions:2016-2023) says the process that starts the Console
|
||||
holds human authority, and that a start through systemd gives a clean ancestry the proof accepts, "the
|
||||
detachment gap decision 62 names". Decision 102 lets one named seat, Mos, start it. It calls that a
|
||||
tradeoff Jason accepted, because "the human proof now rests on Jason's grant to one named seat" (:2253). A
|
||||
boot-enabled system unit hands that start to PID 1 on every reboot, with no named starter. That extends a
|
||||
human-authority boundary, so under AGENTS.md it needs an owner decision. Decision 94 also accepts the
|
||||
local-forgery limit only "for this PoC on dragon-lin under Jason's user ... this decision doesn't carry the
|
||||
limit there" (:2058-2062). On .54 any process that reaches the Console port can send as Melanie, and
|
||||
`melanie`'s Hermes processes and sudo are on that host until #423 step 5. Nobody has accepted that limit
|
||||
for .54.
|
||||
|
||||
Fix: render the Console unit with no `[Install]` section, so a human or Mos starts it at step 9 and after
|
||||
each boot. That fails closed and is cheap to reverse, because adding `[Install]` later is one re-render.
|
||||
Put two questions to Jason through Mos: may a boot start stand in for the human's start on .54, and does he
|
||||
accept the local-forgery limit there? The bus unit can stay boot-enabled, since it holds no human
|
||||
capability.
|
||||
|
||||
B2. `@PATH@`, `@NODE@` and `@FLAGS@` have no render rule. Both templates set
|
||||
`Environment=PATH=@PATH@` (mosaic-bus.service.in:15, mosaic-webui.service.in:25). The bus `ExecStart` is
|
||||
scripts/mosaic, a bash script that runs `exec node` from PATH (scripts/mosaic:22-40). So the bus's node is
|
||||
whatever PATH the render writes, not `--node`. Row 195 found v22 on .53, and the precheck checks
|
||||
`node --version` on `<user>`'s shell PATH, not the unit's. Fix: say `@PATH@` is `dirname(--node)` followed
|
||||
by `/usr/local/bin:/usr/bin:/bin`, as webui-service.sh:246-248 does, and `@NODE@` is `--node`. Say
|
||||
`@FLAGS@` is the checked serve flags, and refuse any `@[A-Z_]+@` left in either render, not only `%h`. Add
|
||||
a test that the bus unit's PATH starts with the `--node` directory.
|
||||
|
||||
B3. The hardening lines are never listed. In r9 they lived in the new template that C2-13 owned. r10 drops
|
||||
that template, and the render "adds ... the hardening lines", but neither the design nor the brief says
|
||||
which ones. The test "a template with a hardening line removed" has nothing to remove: the templates carry
|
||||
only `NoNewPrivileges=yes`. Some common lines break sessions. `RestrictNamespaces=`, `PrivateUsers=` or a
|
||||
`SystemCallFilter=` without the namespace calls stops bwrap, and then no role starts. The bwrap precheck
|
||||
runs in `<user>`'s shell, outside the unit's sandbox, so it can't catch that. Fix: list the exact lines in
|
||||
the brief (for example `ProtectHome=yes`, `ProtectSystem=strict` with `ReadWritePaths=<home>`,
|
||||
`PrivateTmp=yes`, `NoNewPrivileges=yes`), say none of them restricts user namespaces, and add a step 9
|
||||
check that one session starts under the bus unit.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. Both templates already end with `[Install]` `WantedBy=default.target` (bus :41-42, webui :39-40). The
|
||||
render should replace that line with `multi-user.target`, not add a second one. Under B1 it removes the
|
||||
Console's section.
|
||||
|
||||
N2. The render adds no `After=` or `Wants=`, so at boot the two units start in parallel. That is harmless
|
||||
today, because serve calls the bus per request (webui cli.mjs, `busCaller`) and starts without it. But the
|
||||
stated order applies only to a manual step 9. Either add `After=mosaic-bus@<business>.service` to the
|
||||
Console unit, or say that boot order doesn't matter.
|
||||
|
||||
N3. "The serve flags pass scripts/webui-service.sh's own flag checks" can't be done by calling that
|
||||
script. `serve_flags` (:146-207) is internal. Its proof-file check compares the owner to `id -u` (:201),
|
||||
which is root under case2-unit.sh. `render` finds node with `command -v` (:237), not `--node`. And
|
||||
webui-service.sh isn't in the files owned. Say whether case2-unit.sh copies the checks, with tests, or
|
||||
whether the row may move `serve_flags` into a sourced file. Also state which serve flags Case 2 uses.
|
||||
|
||||
N4. With a copy, webui-service.sh passes `--checkout <canonical>` (:251). Her host has no canonical
|
||||
checkout, so the queue read runs from the copy, and the queue store refuses that. Say that the Console unit
|
||||
gets no `--checkout`, and that the queue panel isn't part of Case 2.
|
||||
|
||||
N5. A re-pin must move both units to the same copy, or the human proof fails (webui-service.sh:266-273).
|
||||
Have case2-unit.sh refuse a render whose two units name different copy ids, and test it.
|
||||
@@ -0,0 +1,74 @@
|
||||
# Webby review: Case 2 design r11 (#416), Console without [Install], render rules, 9.3
|
||||
|
||||
Webby, 2026-10-20T21:14Z (from `date -u`). Author: Gosalyn (gosalyn-0048, gosalyn-0051). Two commits on
|
||||
gosalyn/case2-design, reviewed together as r11:
|
||||
- fb20a61b4: DESIGN blob 49eafb5a5666, rows blob edbc493fb092.
|
||||
- eac92d956: rows only, rows blob 3a86d5f048dc. This is the one to re-pin.
|
||||
|
||||
Diffs: `git diff 4281b0064 fb20a61b4` and `git diff fb20a61b4 eac92d956`. I read B1 against mos-1097 and
|
||||
the port squat against mos-1100. I read the cited code at fb20a61b4. Its packages and scripts differ
|
||||
from origin/refactor only in scripts/stack-release.sh, which none of these citations use. This is a
|
||||
static review. I ran nothing on any host.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
B1 is fixed, per mos-1097. The Console unit renders with no `[Install]`, the script enables only the bus
|
||||
unit, and step 9 makes Jason the one who starts it, after each boot too. The tradeoff after an unplanned
|
||||
reboot is written down. Q185 (jason-master-restart DECISIONS.md:1573) does put him in the homelab for
|
||||
1012 reboots. Mos asked for two facts, and 9.3 gives both with citations:
|
||||
- Who shares the Console's uid: the bus host and its children, the Console and its `human-cli.mjs`
|
||||
children (webui bus.mjs:1-3), every session inside bwrap, and the admin steps.
|
||||
- What a bwrap session can reach. The port, yes. sandbox.mjs:67 has no `--unshare-net`, and :29-31 says
|
||||
the network isn't confined. The proof file, no. The binds at :101-116 are all under the run directory,
|
||||
the workspace, the system paths or the sockets, and :21-22 says there's no `~/.config`. The one
|
||||
harness mount, claude-code's `mounts()` (plugins/harness/claude-code/index.mjs:73-77), is only the
|
||||
pinned binary.
|
||||
|
||||
The 401/403 reading of serve.mjs holds. The proof check at :495-503 comes before any auth-header read, a
|
||||
proof header with a loopback Host gets 403 at :497, and every write route needs `named` (:526, :532,
|
||||
:537, :542, :546).
|
||||
|
||||
B2 is fixed. I listed every token in both templates. The bus has `@REPO@`, `@PATH@`, `@CLAUDE_ENV@`,
|
||||
`@PM_FLAGS@`, `%h` and `%i`. The Console has `@REPO@`, `@PATH@`, `@NODE@` and `@FLAGS@`. Every `@` token
|
||||
and `%h` has a rule, and `%i` is left alone, which is right for a system unit. The leftover refusal is
|
||||
`@[A-Z_]+@` or `%h`, so it doesn't catch `%i`. The `@PATH@` rule fixes the case I missed in r10. With
|
||||
`--node /usr/bin/node`, webui-service.sh:246-248 would let a node in /usr/local/bin win, and there's a
|
||||
test for that case.
|
||||
|
||||
B3 is fixed. I checked the four lines against the sandbox:
|
||||
- Every read-write bind (workspace, run directory, thread store) and every socket is under the data
|
||||
root, inside `<home>`: config.mjs:19, git-socket.mjs:1, launcher.mjs:5. So `ReadWritePaths=<home>`
|
||||
covers them.
|
||||
- Nothing uses `/run/user`, so `ProtectHome=yes` takes away nothing the bus needs.
|
||||
- No line restricts namespaces, and the refusal for `RestrictNamespaces=`, `PrivateUsers=` and
|
||||
`SystemCallFilter=` has a test.
|
||||
- The step 9 session start is the real check, and it waits on #383 as stated.
|
||||
|
||||
N1-N5 from r10 are all in the C2-13a brief, with tests.
|
||||
|
||||
The port squat and its fix are right. A session inside bwrap holds capabilities only in its own user
|
||||
namespace, which doesn't own the host's network namespace, so it can't bind below the port floor. cli.mjs
|
||||
:27 takes ports 0..65535, so serve needs no change. eac92d956 adds both mos-1100 conditions:
|
||||
- The precheck refuses a floor at or below the port.
|
||||
- The bind test builds a real session argv with `binds()` (sandbox.mjs:101) and `sandboxArgv()` (:162).
|
||||
It expects `EACCES` below the floor, runs a control that binds successfully, and fails rather than
|
||||
skips on a host with a low floor.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. A correction to my own r10 B1. I cited the dragon-lin forgery acceptance as decision 94. It is
|
||||
decision 95 (lead-decisions.md:2048, the text at :2058-2062). r11 9.3 has it right. Decision 94
|
||||
(:2003) is the one about Jason's start, which r11's "Changes from r10" also has right.
|
||||
|
||||
N2. The bind test's control binds "a free port of 1024 or above". On a host whose floor is above 1024,
|
||||
that control fails for the wrong reason. Pick the control port at or above the floor the test just
|
||||
read, and the low port below it. It's one line in the brief.
|
||||
|
||||
N3. The precheck reads the Console's `--port` from the installed unit, so it can only pass after step 2c.
|
||||
Say what it does when no Console unit is installed, presumably refuse with a named error, add a case for
|
||||
it, and say in section 10 that the precheck runs after 2c.
|
||||
|
||||
N4. `AmbientCapabilities=` carries across exec, so each `human-cli.mjs` child the Console starts also
|
||||
holds `CAP_NET_BIND_SERVICE`. That's harmless today, because they run reviewed code and start no
|
||||
session. Add one sentence to 9.3 so that a later Console child that starts something doesn't inherit the
|
||||
capability unnoticed.
|
||||
@@ -0,0 +1,85 @@
|
||||
# Webby review: Case 2 design r2 (#416, creative writing hub)
|
||||
|
||||
Webby, 2026-10-20T19:28Z (from `date -u`). Independent reviewer. Author: Gosalyn.
|
||||
Subject: `agents/gosalyn/work/case2/DESIGN.md` at c1913fb07 on gosalyn/case2-design, blob
|
||||
05a79cbca624, sha256 bda7a717...4055 (verified). Code read at 9ae8c73ac, where the citations point.
|
||||
Static review. I ran nothing. I read the whole file. The r1 items I don't name below read as fixed.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
Four blockers. Three are in the persona path, which is new in r2. The data model rewrite onto her
|
||||
real repo is the right call, and the 1.3 claim mostly holds (see "The three questions").
|
||||
|
||||
## Blockers
|
||||
|
||||
B1. A top-level playground has no universe. 1.1 (:169-170) suggests "one at the top", but 1.2
|
||||
(:191) gives a file the universe whose `books` list holds its book folder. A file under a top-level
|
||||
prefix has no book folder, so `library_find` (universe required) can't return it, and the cite
|
||||
check's one-universe rule (:464) has nothing to compare. Fix: put `playground` inside each universe
|
||||
entry, or refuse a prefix that isn't inside a book folder. Also give playground the `.md` condition
|
||||
canon has (:187). As written, a `.canvas` under a playground prefix is playground and citable.
|
||||
|
||||
B2. Where `persona.text` lives, and who can read it. Instance entries take inline `vars` only
|
||||
(business.mjs:89 closes the keys). Nothing lets an entry point at a file, so "a 0600 file that the
|
||||
business file entry takes" (:665, :722-723) has no mechanism. Inline, the text is read by more than
|
||||
the design says:
|
||||
- The tester's `instances` read is allowed (broker.mjs:103) and returns every instance's vars
|
||||
whole (instance-settings.mjs:58-63). That breaks "No agent sees the persona text" (:724) on any
|
||||
host with `bus.tester` on (vars.mjs, `bus.tester`).
|
||||
- Every organization write logs old and new values of each changed path in
|
||||
`businesses/.history/<id>/changes.jsonl` (organization.mjs:70-80, :201-204).
|
||||
Fix: say the text sits inline in the business file, or add a file-reference var with its own
|
||||
checks. Then either refuse persona keys in the tester's reads, or make C2-13's precheck refuse
|
||||
`bus.tester` on `<host>`. State the mode of the business file and `.history`. Test it in C2-08.
|
||||
|
||||
B3. Name the places `THREAD_DEFINITION` must stay `assistant`. Section 4 (:404-406) replaces it
|
||||
"wherever it means may hold a thread" and keeps only tools.mjs:186. launch-settings.mjs:140, :186
|
||||
and :191 also use it. They let `organization.set`'s `assistants` key set an assistant's whole vars,
|
||||
which the chief of staff holds (`ORGANIZATION_KEYS`, broker.mjs:145, :1266-1271, Q169). Read as "may hold a thread", that
|
||||
lets the chief of staff rewrite Bean's `persona.text` and model. Fix: list those three lines as
|
||||
staying `assistant`, and add a C2-08 test that `assistants` with `bean` refuses
|
||||
`organization-not-assistant`.
|
||||
The same change has a second effect the design doesn't state. With `END_PROTECTED` set to
|
||||
`THREAD_DEFINITIONS` (:407-408), `instances.set` refuses to add or change a creative instance
|
||||
(instance-settings.mjs:23, :109), and `definitions()` marks all six not accepted (:52). So her model
|
||||
choice for Bean, a persona edit and adding a specialist later are all hand edits of the business
|
||||
file. 4 (:394) and 10 step 7 read as if she can pick specialists later. Say how that happens.
|
||||
|
||||
B4. Name the persona block's exact place. `prompt()` is the contract (bundle.mjs:177), then
|
||||
`## This session` (:179) with the session lines. The last of those says "end each turn with the
|
||||
answer itself" (:188), then the end_run and review-loop lines follow. "After the contract" can
|
||||
mean before `## This session` or at the very end, and the C2-08 snapshot depends on which. I'd put
|
||||
it between the contract and `## This session`, so the broker's facts come after the persona. Fence
|
||||
it the way skills are (`SKILL_END`, :107, :170), and refuse persona text that holds the end marker
|
||||
or a line starting `## `. Hermes text is 8,000 characters that nobody here has read, so it can't be
|
||||
allowed to forge a section. The snapshot then pins the order: contract, persona, session.
|
||||
|
||||
## The three questions
|
||||
|
||||
1.3, reporting is enough. For the agent-side guarantee, yes. Scope comes from the path alone, and
|
||||
the cite check (:463) refuses a playground cite on `fact`, `finding` and `correction` whatever her
|
||||
files contain. None of R1-R7 changes a scope. Holding back her head would add nothing to that
|
||||
check. Two limits belong in the text (N1, N2).
|
||||
|
||||
M9. It holds today. Every organization setter builds its document from the file and changes only
|
||||
its own keys (broker.mjs:1261-1269, instance-settings.mjs:93-121, launch-settings.mjs:182-191), so
|
||||
no verb can change `projects` now. M9 guards against a future setter. Place it where that setter
|
||||
can't skip it (N3).
|
||||
|
||||
Persona order. See B4.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. `library_get` takes only `path` (:346), so its `links_out` and `links_in` are the one place a
|
||||
lookup can name a file in another universe or an unmapped file. Say both lists drop those targets
|
||||
(R6, R2), and add a C2-05a test.
|
||||
N2. Scope is the current mapping applied to the path. A note she moves into a book folder is canon
|
||||
on her next push, and a mapping edit re-scopes old commits for the cite check. Both are right, and
|
||||
both are hers. Put the first in the setup sheet (C2-12).
|
||||
N3. Put M9 in `organizationWriter.write` (organization.mjs:177), comparing `canonicalJson` of old
|
||||
and new `projects`, hybrid entries included. Since no verb can build the change, the C2-04 test
|
||||
calls `writer.write` directly with a changed playground prefix, and one with a changed hybrid.
|
||||
N4. Obsidian resolves a shortest-path link across the whole vault, which is `~/src` with her other
|
||||
repos in it. The reader sees one repo. A base name unique in the books repo but not in the vault
|
||||
can resolve to a different file than Obsidian shows her. Count those in the survey's link-form
|
||||
counts if the survey can see it, or note it in the setup sheet.
|
||||
@@ -0,0 +1,79 @@
|
||||
# Webby review: Case 2 design r3 (Vikunja #416)
|
||||
|
||||
Webby, 2026-10-20T19:43Z (from `date -u`). Author: Gosalyn. Subject: `agents/gosalyn/work/case2/DESIGN.md` at
|
||||
2958acb81 on gosalyn/case2-design, blob 60bc7d682e0d, sha256 f368c27d...c325 (verified). Read as the diff from
|
||||
c1913fb07 (r2, my REVIEW at 162ce46db), with the code at 9ae8c73ac. Static review. I ran nothing.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
My r2 B1, B2 and B4 are fixed, and so are N1 to N4. B3 is fixed for the three paths it names, but it misses a
|
||||
fourth (B1 below).
|
||||
|
||||
- r2 B1. Playground prefixes now sit inside a universe and must start with one of its book folders, or the
|
||||
file refuses with `library-playground-outside-book`. A `.canvas` under a prefix is unmapped. 1.2's universe
|
||||
rule then holds for playground files.
|
||||
- r2 B2. The text moves to `<configDir>/personas/<business>/<instance>.md`, and the vars hold only the name and
|
||||
the digest. I checked the modes against the code. `durable` (organization.mjs:59-61) and `log` (:138-141)
|
||||
open with 0600 and O_NOFOLLOW. `log` refuses a group or other bit. `directory` (:39, :45) makes 0700 and
|
||||
refuses a loose one. The `instances` read (instance-settings.mjs:61-63) copies vars whole, so it now carries
|
||||
only the digest. `previous.json` (organization.mjs:197-198) holds the old business file, which has no text
|
||||
either.
|
||||
- r2 B3. launch-settings.mjs:140, :186 and :191 stay `THREAD_DEFINITION`. `organization.set`'s `instances` key
|
||||
refuses a creative instance for every actor. `instances.set` gets the human-only creative branch.
|
||||
- r2 B4. The fence follows `SKILL_END` (bundle.mjs:107, :170). The block sits between `contract.trimEnd()`
|
||||
(:177) and "## This session" (:179). The snapshot pins that order.
|
||||
- The correction is right. broker.mjs:147-149 says the chief of staff or a human-resolved decision ends an
|
||||
`END_PROTECTED` instance.
|
||||
|
||||
## Blocker
|
||||
|
||||
B1. `routes.set` lets the tester change a creative instance's harness, model and thinking. It is in
|
||||
`TESTER_ORGANIZATION_VERBS` (broker.mjs:103), and the gate at :1259 lets a tester through. `setRoutes`
|
||||
(launch-settings.mjs:240-272) has no protected-definition check. It replaces the route vars of any named
|
||||
instance, then writes at :277. So the design's 4 is wrong in two places:
|
||||
- DESIGN.md:474-493 lists every path to a creative instance and leaves this one out.
|
||||
- DESIGN.md:493 says that with `bus.tester` on, the tester "could change no creative instance".
|
||||
|
||||
M10 (DESIGN.md:563) has no test for it. `bus.tester` is off on her business and C2-13 refuses it, so this is
|
||||
not reachable there today. But the design claims the rule holds even if testing were on, and on any library
|
||||
business that turns testing on, it doesn't.
|
||||
|
||||
Fix: in C2-08, have `setRoutes` refuse a `CREATIVE_DEFINITIONS` instance from actor `tester` with
|
||||
`organization-creative-human-only`, as the `instances.set` branch does. The human keeps `routes.set`, which is
|
||||
the Console's routing table. Add the path to the list at 474, fix 493, and add the case to M10's tests: the
|
||||
tester's `routes.set` on `bean` refuses, the file is unchanged, and the human's still works.
|
||||
|
||||
## Your two questions
|
||||
|
||||
Keying the creative branch on actor `"tester"` is enough. broker.mjs:1272 sets the actor to `"tester"` only
|
||||
for a tester session. The business load check refuses a human id of `tester` (broker.mjs:245) and an instance
|
||||
of that name (:248). instance-settings.mjs:86 refuses one too. So the string can't name the human, and the
|
||||
writer is the only place that knows the definition, because it reads the document. A broker-side check would
|
||||
have to read the file a second time to learn the definition. The one thing to add: make C2-08's tester case go
|
||||
through the broker with a bound tester session, not just `set(..., "tester")`, so a change to the actor line at
|
||||
:1272 fails the test. The same goes for B1's case.
|
||||
|
||||
The `# `/`## ` refusal is safe, because it fails closed, but it may refuse her real personas. The fence alone
|
||||
stops a section escape, because nothing after `</persona-file>` belongs to the persona. The heading rule
|
||||
guards against a model reading a `## ` line inside the fence as a new section, which is reasonable. I don't
|
||||
know what her Hermes persona files look like. If they use Markdown headings, C2-20 stops at the fence check
|
||||
(DESIGN.md row C2-20) and the design names no next step. See N2.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. The chief of staff can change which model a creative instance runs without touching the instance.
|
||||
`organization.set` takes `models` (broker.mjs:145, ORGANIZATION_KEYS), and launch-settings.mjs:169 replaces
|
||||
the whole `launch.models` table. If `bean`'s `model` var is a key the chief of staff rewrites, Bean runs the new
|
||||
model. That matches what 4 promises, since the instance and the persona stay unchanged, but mos-1033's "only
|
||||
the human" reads wider. Either say in 4 that the models table is shared and the chief of staff may retune it,
|
||||
or refuse a `models` change to a key a creative instance names. Mos's call, so I'd put it to him.
|
||||
N2. For C2-20, count the lines starting `# ` or `## ` in each persona and print the count with the byte count,
|
||||
before Jason's go. Then state the remedy in 9.1. That could be Melanie or Jason editing the text, or a recorded
|
||||
rewrite to `### `. Any rewrite needs her say, because the text is hers.
|
||||
N3. "Only Bean's or Chet's own session sees it" (DESIGN.md:866) needs a qualifier. `buildBundle` writes
|
||||
prompt.md, persona included, once per run into the run directory at 0600 (bundle.mjs:241-244, :265). Run
|
||||
directories are write-once and stay until `prune`. The claude adapter also passes prompt.md as one argument
|
||||
(bundle.mjs:104-105), and that argument is visible in `/proc/<pid>/cmdline` to other local users. No verb reads
|
||||
either copy, and on her VM the only other user is `melanie`, whose personas they are. So this doesn't change the
|
||||
design's safety. The text should name both copies, and the C2-13 precheck should check the run root's mode
|
||||
with the rest.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Webby review: Case 2 design r4 (Vikunja #416)
|
||||
|
||||
Webby, 2026-10-20T19:58Z (from `date -u`). Author: Gosalyn. Subject: `agents/gosalyn/work/case2/DESIGN.md` at
|
||||
5fc296e70 on gosalyn/case2-design, blob 7c62c40cbbb5, sha256 d06d3053...ab0a (verified). Read as the diff from
|
||||
r3 (2958acb81, my REVIEW at fcac66f7b), with the code at 9ae8c73ac. Static review. I ran nothing, apart from
|
||||
reading pi 0.85.1's resource-loader.js in the canonical checkout's node_modules.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
My r3 B1 is fixed, and so are N1, N2 and N3.
|
||||
|
||||
- r3 B1. `routes.set` is now on the list (DESIGN.md:549-552), and :493 is fixed (now :565). The one rule keys
|
||||
on the business file's `human`. I checked the actor lines: broker.mjs:1272 sets `tester` or the human id,
|
||||
and :1278 sets `<definition>.<instance>` for a role. M10 has the tester's `routes.set` on `bean` refusing
|
||||
and the human's working, and the tester cases go through a bound tester session (:567-570).
|
||||
- N3. The adapter lines are right (claude adapter.sh:79, :82; pi :106, :121). I read pi 0.85.1's
|
||||
`resolvePromptInput` (dist/core/resource-loader.js:17-31). It reads the value as a file when
|
||||
`existsSync` is true, and on a read error it warns and returns the path itself. See N1 below for the case
|
||||
where the file doesn't exist.
|
||||
- N2. The dry run prints counts only, and the rule changes before the import if a count is nonzero. That's
|
||||
what I asked for.
|
||||
|
||||
## Blocker
|
||||
|
||||
B1. The human does reach the `models` write, through `limits.set`. DESIGN.md:29-30 and :545-548 say that
|
||||
`organization.set` takes only an agent session, "so no verb writes `launch.models` for the human today", and
|
||||
that Jason edits it by hand. The first half is true. The conclusion isn't:
|
||||
|
||||
- `limits.set` is a human verb (broker.mjs:1248). It takes the same `ORGANIZATION_KEYS`, `models` included
|
||||
(:145, launch-settings.mjs:16).
|
||||
- `organization.set` is `limits.set` for a role (broker.mjs:1249-1251). Both reach the same `set()`
|
||||
(:1284, launch-settings.mjs:150-169), and the `models` replace is at :169.
|
||||
- So the human's `limits.set { models }` replaces `launch.models` today, with actor = her id. The tester
|
||||
can't, because `limits.set` isn't in `TESTER_ORGANIZATION_VERBS` (broker.mjs:103).
|
||||
|
||||
The one actor rule still gives the right behaviour here, since the human passes and the chief of staff
|
||||
refuses. But section 4 is the map of who can change a creative instance, and it states this path wrongly.
|
||||
M10 also has no test showing the `models` check keys on the actor rather than on the verb. If someone builds
|
||||
it as "refuse for every actor", the way the `instances` key at :539-541 works, her own model retune refuses,
|
||||
and no test catches it.
|
||||
|
||||
Fix:
|
||||
- In both places, say that the human writes `launch.models` with `limits.set` and the chief of staff with
|
||||
`organization.set`, both through `set()`, and that the check refuses unless the actor is the human.
|
||||
- Add to M10 and C2-08: the human's `limits.set` change to Bean's `launch.models` key works, next to the chief
|
||||
of staff's change that refuses.
|
||||
- :541 already says the human's `instances: {bean: ...}` refuses. That case also goes through `limits.set`.
|
||||
Name the verb there too, so the test is written against it.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. Pi also uses the path as the prompt when the file doesn't exist. Then `existsSync` is false and
|
||||
`resolvePromptInput` returns the input with no warning (resource-loader.js:21, :30). So the adapter's check
|
||||
has to cover existence as well as readability. `[ -r "$f" ]` does both. Add a missing file to C2-08's pi
|
||||
adapter test, next to the unreadable one.
|
||||
|
||||
N2. The claude adapter test can only show what the adapter puts in argv. Whether claude 2.1.304 reads
|
||||
`--system-prompt-file` as a full replacement, the way `--system-prompt` works today, rests on its `--help`
|
||||
until C2-13. Have the C2-13 precheck run one prompt through the host's claude with a marker system prompt and
|
||||
check the reply, not only that the flag is listed. If it acts as an append, the contract and gate text would
|
||||
sit on top of claude's default prompt, which changes what the session is told.
|
||||
@@ -0,0 +1,21 @@
|
||||
# Webby review: Case 2 design r5 (Vikunja #416)
|
||||
|
||||
Webby, 2026-10-20T19:54Z (from `date -u`). Author: Gosalyn. Subject: `agents/gosalyn/work/case2/DESIGN.md` at
|
||||
e1635308b on gosalyn/case2-design (parent 5fc296e70), blob fd5df75ce635, sha256 9ba4f965...2d22d (verified).
|
||||
Read as the diff from r4 (my REVIEW at 0597cc198), with the code at 9ae8c73ac. Static review. I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
- r4 B1 is fixed. Section 4 (DESIGN.md:555-557) says `limits.set` and `organization.set` take the same
|
||||
keys and reach the same `set()`, and that the check keys on the actor. I re-read
|
||||
packages/bus/src/broker.mjs:1248-1251 and :1284 at 9ae8c73ac, and they match. The `instances` case
|
||||
(:565-566) names both verbs. The `models` case (:568-573) now refuses unless the actor is the human, and
|
||||
its test has her `limits.set` change to Bean's key working next to the chief's refusal. M10 (:665) and
|
||||
C2-08 (:899) carry the same case. The r3 Changes entry (:44-45) records the correction rather than
|
||||
deleting the old claim. No stale "agent-only" or "by hand" claim is left (grep).
|
||||
- r4 N1 is fixed. :535-537 covers the missing file, the check is `[ -r ]`, and C2-08 tests a missing and
|
||||
an unreadable prompt file.
|
||||
- r4 N2 is fixed. C2-13 (:905) runs one marker prompt through the host's claude with
|
||||
`--system-prompt-file` and checks that it replaces the default prompt.
|
||||
|
||||
No new findings. Section 10 step 1 stays on hold per mos-1050. That isn't mine to clear.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Webby review: Case 2 design r6, section 10 step 1 (Vikunja #416)
|
||||
|
||||
Webby, 2026-10-20T19:59Z (from `date -u`). Author: Gosalyn. Subject: `git diff e1635308b 1508fd175` of
|
||||
`agents/gosalyn/work/case2/DESIGN.md` (r5 blob fd5df75ce635, approved in webby-0021, to r6 blob 80893dd4cff1).
|
||||
Static review. I checked the cites in jason-master-restart at 1c378b4 (read only): DECISIONS.md Q183 and
|
||||
projects/mosaic-mvp/MELANIE-HOST-RECON.md:27-39. Both say what r6 says they say.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
The Q183 reading is right. #421 is closed, FED R8 records Jason's acceptance, in-VM tightening is
|
||||
allowed and is #423, and each VM gets a `mosaic` user without sudo. Section 9's isolation paragraph
|
||||
(DESIGN.md:961-965) states the residual as it is now, with the recon cite.
|
||||
|
||||
## Blocker
|
||||
|
||||
B1. Step 1 has lost the owner of the root steps on .54. Until r5, ops-01's plan section 3 (#421) created
|
||||
`mosaic`, `/srv/mosaic` at 0700, and the unit (DESIGN.md:947-950 still cites it). That plan stopped. r6
|
||||
step 1 (:1017) says "the install creates the `mosaic` user". But step 2 (:1023) runs the install "as
|
||||
`<user>`", and C2-13 runs as `<user>`. A user can't create itself, and `mosaic` without sudo can't
|
||||
write a system unit `mosaic-stack.service` (:948). So nothing names who does the root part, with which
|
||||
access, or under whose go. The only root paths into .54 today are pve root and `melanie`'s sudo
|
||||
(MELANIE-HOST-RECON.md:31, :35-36), and using either one is an act on Melanie's machine.
|
||||
|
||||
Fix: list the root steps as their own item: create `mosaic` (system user, no sudo, no groups, no keys),
|
||||
create `/srv/mosaic` at 0700 owned by it, and install and enable the unit. Name who runs them (Jason, or
|
||||
ops-01 under his go), and through which path (melanie's sudo or pve). Say that this is step 2's go and not
|
||||
step 1's. C2-13's precheck then checks the result, as it already does (rows brief a0ce2ffb8:729). Also
|
||||
re-cite :950: the user spec now rests on Q183, not on the stopped #421 plan.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. :1018 says the precheck "refuses if it has sudo or any group". A system user still has its own
|
||||
primary group. Say "no supplementary groups", and check `id -G` has one entry, or the precheck refuses
|
||||
every real install. The rows brief (:729) uses the same words, so fix both.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Webby review: Case 2 design r7, sections 9 and 10 and C2-13 (Vikunja #416)
|
||||
|
||||
Webby, 2026-10-20T20:02Z (from `date -u`). Author: Gosalyn. Subject: `git diff 1508fd175 8645cb456` on
|
||||
agents/gosalyn/work/case2/DESIGN.md (blob aeaad378a344) and `git diff a0ce2ffb8 8645cb456` on
|
||||
docs/plans/2026-10-20_case2-rows.md (the rows file arrived in a0ce2ffb8, between r6 and r7). Static
|
||||
review. I ran nothing.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
r6 B1 is fixed, and so is N1.
|
||||
- Section 10 step 2 (DESIGN.md:1054-1064) now splits 2a (root: useradd, /srv/mosaic 0700), 2b (the install
|
||||
as `<user>`) and 2c (root: install and enable the unit). It names ops-01 as owner, the localadmin
|
||||
path from #423 step 1, and Jason's go. Step 1 (:1045-1053) is right as written. Section 9 (:973-975)
|
||||
rests the layout on Q183, and the rows brief (:724-725) says the script doesn't create `<user>` or
|
||||
install the unit.
|
||||
- N1. "No supplementary groups" appears in all four places, and the precheck checks `sudo -n true` fails
|
||||
and `id -G` prints one entry (rows :730-731).
|
||||
- Mos's item 3. The residual split at :985-992 reads correctly against MELANIE-HOST-RECON.md:31, :35-36.
|
||||
|
||||
My one blocker is in step 2c and C2-13, not in step 1. I have no objection to the mos-1050 hold on step 1
|
||||
lifting now. Mos decides that.
|
||||
|
||||
## Blocker
|
||||
|
||||
B1. Root installs a unit file that the unprivileged user wrote. C2-13's script runs as `mosaic` and
|
||||
writes the `mosaic-stack.service` file (rows :724-725, DESIGN.md :940). 2c then has root install and
|
||||
enable "the unit file that C2-13 writes" (:1063-1064). By 2c, 2b has already run `bootstrap.sh`, npm
|
||||
install and the release activate as `mosaic`, so third-party package code has run under that uid. Any
|
||||
code running as `mosaic` can change the file before 2c, for example to `User=root` or another
|
||||
`ExecStart`. Root then installs a root service. The same holds on every later re-install. So 2c turns
|
||||
the no-sudo user into a path to root, which is the boundary section 9 relies on.
|
||||
|
||||
Fix: 2c must not trust the content. Either root renders the unit from the reviewed template in the
|
||||
release, with `<user>` and the paths as inputs, or root copies the file to
|
||||
/etc/systemd/system/ as root:root 0644 and refuses unless `User=mosaic`, `Group=mosaic`, the
|
||||
`ExecStart` path, and the hardening lines (`ProtectHome` and the rest) match fixed expected values.
|
||||
Never `systemctl link` it, because a link keeps the file writable by `mosaic`. Put the check in C2-13's
|
||||
tests: a unit file edited to `User=root` makes the 2c step refuse.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. 2b runs the install "as `<user>`, by him or his delegate", but `mosaic` has no keys and no login.
|
||||
The only way in is `sudo -u mosaic` from `localadmin`. Say so, so that nobody adds a key to `mosaic`
|
||||
to make 2b work.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Webby review: Case 2 design r8, step 2c and C2-13 (Vikunja #416)
|
||||
|
||||
Webby, 2026-10-20T20:04Z (from `date -u`). Author: Gosalyn. Subject: `git diff 8645cb456 eab6e1a3a` on
|
||||
agents/gosalyn/work/case2/DESIGN.md (blob 5dd7a9c9b4c0) and docs/plans/2026-10-20_case2-rows.md. Static
|
||||
review. I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
r7 B1 is fixed, and so is N1.
|
||||
- `mosaic` writes no unit. In 2c (DESIGN.md:1084-1091, rows :728-736), root renders the unit from a
|
||||
root-owned checkout of the commit Jason's go names, outside /srv/mosaic. It checks `User=`, `Group=`,
|
||||
`ExecStart=` and the hardening lines, writes /etc/systemd/system as root:root 0644, never uses
|
||||
`systemctl link`, refuses a symlink, refuses a drifted installed unit, and reads nothing under
|
||||
`<user>`'s home. The tests (rows :753-759) cover an edit to `User=root`, an edited `ExecStart=`, a
|
||||
removed hardening line, a symlink, and the 0644 write.
|
||||
- Gosalyn's change from my fix is right, and my suggestion was wrong. The release is under /srv/mosaic and
|
||||
`mosaic` can write it, so a template there is the same path to root. The root-owned checkout closes it.
|
||||
- N1. 2b runs as `sudo -u mosaic -H` from localadmin, and `mosaic` gets no key (:1080-1081, rows
|
||||
:724-726). `sudo -u` with a command doesn't need a login shell.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. The check covers named lines, not the whole file. A template line such as `ExecStartPre=+...`,
|
||||
`ExecStartPost=!...` or `PermissionsStartOnly=` runs as root even with `User=mosaic`. If it pointed
|
||||
into /srv/mosaic, that is the same path to root through a line the check doesn't read. The template is
|
||||
reviewed, so this is defence in depth. Either compare the whole render to one fixed expected file, or
|
||||
refuse any `Exec*=` value starting with `+` or `!` and any `PermissionsStartOnly`. Add one test case
|
||||
for it. The existing packages/cli/systemd/mosaic-bus.service.in has a single `ExecStart=` (:30), so
|
||||
the new template can follow it.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Webby review: Case 2 design r9 (#416)
|
||||
|
||||
Webby, 2026-10-20T20:48Z (from `date -u`). Author: Gosalyn (gosalyn-0035). Commit 29e85196b on
|
||||
gosalyn/case2-design, DESIGN blob 60a2d6cb751e. Round 8: case2-r8/REVIEW.md, approve on eab6e1a3a.
|
||||
|
||||
Scope: `git diff eab6e1a3a 29e85196b` on DESIGN.md and docs/plans/2026-10-20_case2-rows.md, including
|
||||
13a98bfd0 and 29404f996 as asked. Row A's and row B's briefs (f325657c7) are not part of this round. I
|
||||
read their citations only where the design shares them. This was a static review. I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
Model path (section 9). The citations hold on origin/refactor 088087ac9:
|
||||
- `ENV_ALLOW` (packages/seat/src/session.mjs:83-88) has no `ZAI_API_KEY`, so today the key is dropped.
|
||||
- pi-ai reads `zai` from `ZAI_API_KEY` (env-api-keys.js:89), and `glm-5.3` is a zai model id (see N1 for
|
||||
the file path).
|
||||
- pi's harness sets `countsAccount` to false (pi/index.mjs:42), so the account check at launcher.mjs:296-308
|
||||
never runs for pi. `preflight()` (:26) checks only the binary. `credentialStatus` (launcher.mjs:343) is
|
||||
metadata passed into the launch object (:385), not a refusal. So "a pi launch has no credential check"
|
||||
is true, and the precheck key-file line is the right place to catch a missing key.
|
||||
- `PI_PROVIDER` comes from `session.provider` (pi/index.mjs:57).
|
||||
|
||||
2d. The key file is the path the bus unit already loads (mosaic-bus.service.in:24), and the `%h` in that
|
||||
line resolves to /srv/mosaic because `<user>` is mosaic with that home (DESIGN 1010-1012). The design
|
||||
says plainly that melanie's sudo and her Hermes processes can read it until #423 step 5 (:1042). On
|
||||
Gosalyn's naming question, I'd keep `claude-session.env`. A new name means a second `EnvironmentFile=`
|
||||
line, which adds a line to case2-unit.sh's fixed set and departs from the bus unit. The cost of keeping
|
||||
it is a misleading name. A README line saying the file holds every session provider key, not only
|
||||
Claude's, fixes that, and it can ride row A's README edit.
|
||||
|
||||
C2-13 and the rows doc:
|
||||
- The marker prompt check now runs only for a claude-code creative instance, and a test shows a missing
|
||||
claude passes when there is none. That's right, since Bean and Chet run pi.
|
||||
- The key-file precheck has refusal cases for missing, 0644, symlink and empty, plus a search of the test
|
||||
output for the fixture value.
|
||||
- `EnvironmentFile=` joins the fixed lines, with removed and pointing-elsewhere cases.
|
||||
- The Exec prefix rule covers the whole leading run, with `+` and `-+` cases, as webby-0027 asked.
|
||||
- 13a98bfd0, `--profile factory|library`. `factory` requires the tester and refuses a library project
|
||||
(`profile-library-business`), and `library` refuses the tester. Each direction has a test, so a wrong
|
||||
flag can't reach a tester-on Case 2 install.
|
||||
- 13a98bfd0, node >= 24 and bwrap prechecks. Both have fake-binary refusal cases, and a bwrap refusal
|
||||
prints the userns sysctl and PATH state.
|
||||
- 29404f996, the copy id. The rule matches `copy_root` (scripts/stack-copy.sh:20): 40 lowercase hex and
|
||||
`scripts/mosaic` present. The tests cover short, uppercase, `/`, `..` and missing.
|
||||
|
||||
C2-20. The Hermes name count prints one count per persona file and never the line, number or word, and
|
||||
the test checks the output holds none of the matched words. The list comes from code at df5700ebe, not
|
||||
her profiles, so the script carries no persona text.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. The model data file is `dist/providers/data/zai.json`, not `data/zai.json` (DESIGN:1023, and row A's
|
||||
brief). The id `glm-5.3` is there. Fix the path when the file is next touched.
|
||||
|
||||
N2. `copy_root` reads `COPIES` as `${XDG_DATA_HOME:-$HOME/.local/share}/mosaic-stack/copies`
|
||||
(stack-copy.sh:6), while case2-unit.sh fixes `<home>/.local/share/mosaic-stack/copies`. If mosaic's
|
||||
login env ever sets `XDG_DATA_HOME`, copies land elsewhere and the unit refuses. That fails closed, so it
|
||||
doesn't block. The install could check `XDG_DATA_HOME` is unset, or the rows doc could say why it's safe.
|
||||
|
||||
N3. "Sets a nonempty `ZAI_API_KEY`" should parse the file the way systemd does: the last assignment wins,
|
||||
and quotes are stripped. A file with `ZAI_API_KEY=x` then `ZAI_API_KEY=` passes a first-match grep but
|
||||
gives the session an empty key. Add that case to the refusal list.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Webby review: row A brief, ZAI_API_KEY in the session env allowlist
|
||||
|
||||
Webby, 2026-10-20T20:55Z (from `date -u`). Requested by Sage (sage-1477). Brief text: the "Brief: row A"
|
||||
section of docs/plans/2026-10-20_case2-rows.md at Gosalyn's a2d94b2c2 (file blob 81140d017441), which is the
|
||||
version Sage is re-pinning to. Local refactor ecbfd6deb still carries the 29e85196b text and isn't on origin.
|
||||
I read the cited code on origin/refactor. Static review.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
Citations hold on origin/refactor:
|
||||
- `ENV_ALLOW` is session.mjs:83-88, with the provider line at :86 (`OPENAI_API_KEY`, `OPENROUTER_API_KEY`).
|
||||
- `SESSION_CREDENTIALS` and `hasSessionCredential` are at :94-95, and `sessionEnv` copies only allowlist
|
||||
names at :97-99.
|
||||
- launcher.mjs:299 runs the account check only when `countsAccount` is true. pi returns false
|
||||
(pi/index.mjs:42).
|
||||
- cli README:435 is the "A Pi session gets the same variables" sentence, and :439-444 says a key that isn't
|
||||
an `ENV_ALLOW` variable launches and then fails at the first turn.
|
||||
- session.test.mjs:51-59 is the existing allowlist test. Its `deepEqual` input holds no `ZAI_API_KEY`, so it
|
||||
still passes after the change. :76 checks that every `SESSION_CREDENTIALS` name is in `ENV_ALLOW`, which
|
||||
keeps holding.
|
||||
- No test under packages/*/tests or scripts/test-*.sh pins a hash of session.mjs.
|
||||
|
||||
The scope is right:
|
||||
- Leaving `SESSION_CREDENTIALS` alone means a Claude Code launch still refuses without a Claude login, even
|
||||
when a zai key is set.
|
||||
- The mutant has a real target, because the new test asserts both membership and passthrough.
|
||||
- The README sentence states the cost plainly: every session the host starts gets the key, and its shell can
|
||||
read it.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. The cli README's "Unit" section says the file "holds one line, for example
|
||||
`CLAUDE_CODE_OAUTH_TOKEN=<token>`" (packages/cli/README.md:711). The brief adds a second sentence there. If
|
||||
the subagent only adds, the README will say both "one line" and "every session provider key". The brief
|
||||
should say to replace "holds one line" with something like "holds one line per variable, for example". This
|
||||
is a doc-only fix. Fold it into the re-pin if that's cheap, since a Haiku subagent follows the brief
|
||||
literally.
|
||||
|
||||
N2. The same paragraph says "nothing in the repository creates or reads it" (:713). That stays true for row
|
||||
A. It becomes false when C2-13's precheck parses the file, so C2-13 should own that line.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Webby review: Case 2 v1 briefs C2-09b, C2-10c and C2-15 (mos-1233), round d1
|
||||
|
||||
Webby, 2026-10-21T22:29Z (from `date -u`). Author: Gosalyn (gosalyn-0122). For mos-1233, Vikunja 464.
|
||||
Candidate: docs/plans/2026-10-20_case2-rows.md, the section "v1 rows past the slice", `git diff cca206cdc
|
||||
72ceda935` on gosalyn/case2-v1-briefs (one commit, +227). These are briefs, so there's no queue record.
|
||||
|
||||
What I checked, without runs: each brief against design 1.4 and 6 at gosalyn/case2-design 7a7c00464 and
|
||||
against the code it cites at cca206cdc:
|
||||
- broker.mjs: `thread.send` (:3511-3536), `#named` (:1480-1488), `TESTER_VERBS` (:117-119),
|
||||
`THREAD_PAGE` (:260), `#route` (:3678-3681) and `#delivery` (:3787-3797);
|
||||
- webui chat-http.mjs's STATUS table and safeMessage (:12-26);
|
||||
- the webui channel plugin's VERBS (:4-12);
|
||||
- library mapping.mjs's hybrid branch and mapping.test.mjs.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
B1. C2-15's `exclude` check drops the design's second form, and C2-01's own test uses that form.
|
||||
- Design 1.4 allows `{"exclude": "<source path or source/type>"}`.
|
||||
- mapping.test.mjs:57 pins the source/type form: `exclude: 'second/Characters'`, where `second` is a
|
||||
universe id with books `['Second World']` and `Characters` is a type folder. mapping.mjs:98-100 accepts
|
||||
it today.
|
||||
- The brief says the validator refuses "an `exclude` that doesn't lie inside a book folder of one of the
|
||||
hybrid's sources". `second/Characters` lies inside no book folder, so the row's first change would
|
||||
refuse a form the design allows and break C2-01's test.
|
||||
- The brief has to keep both forms and say how to tell them apart. My suggestion: if the first segment is
|
||||
one of the hybrid's sources and the rest is one key of `types`, it's the type form, and it excludes that
|
||||
type folder in every one of that source's books. Otherwise it must lie inside a source's book folder. If
|
||||
both readings fit, it refuses. The R6 excluded-link case, the guard case and the lookups' drop then
|
||||
have to cover both forms, with one test each. Also name how C2-05a (row 216) reads `exclude`, because
|
||||
C2-15's reader and guard have to agree with that index.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. Your question, R8's second case. I agree with the reading in principle. A hybrid can't edit a
|
||||
source's file, so "a hybrid file changes it" (design 1.4) can only mean that a hybrid file states another
|
||||
value for the pinned field. Linking to the entity is a fair test of "about that entity". But as written
|
||||
it gives false positives. A place file in the hybrid with `age: 300` and a `relationships: [[Mara]]` link
|
||||
would report R8 against a pin on Mara's `age`. Narrow it to hybrid files whose type (from the type folder)
|
||||
equals the entity's type. Then think about the other gap: a hybrid copy of Mara with the same front matter
|
||||
`name` and no link back. I'd count that too, since it's the likeliest way she'd write a hybrid version of
|
||||
a character. Either way it's report-only, so it doesn't block. Say which front matter keys count as the
|
||||
link (all of them, or only `appears_in` and `relationships`), and test the place-file case as a non-R8.
|
||||
|
||||
N2. C2-15's validator should also refuse a `pin.entity` or a path `exclude` that lies under one of the
|
||||
source's playground prefixes. The hybrid reads only its sources' canon (REQ-LIB-3), so a pin there
|
||||
checks a file the hybrid never sees.
|
||||
|
||||
N3. C2-09b's `room.read` groups by shared `at`, body and sender. Every insert in one send uses one
|
||||
`#now()`, but two separate sends of the same body in the same millisecond (a double click) would merge
|
||||
into one entry with each instance listed twice. Since one send's rows go in one transaction, their
|
||||
`messages.seq` values are consecutive. Group only a consecutive seq run with the same `at`, body and
|
||||
sender, and dedupe `to`.
|
||||
|
||||
N4. C2-09b resolves a tag through `#named` or as an instance key. A tag can match one instance's display
|
||||
name and another instance's key. The brief should rule which wins, or refuse it as ambiguous, with a test.
|
||||
|
||||
N5. C2-10c's line "maps `unknown-addressee` and `room-thread-name-taken` to 409 and `room-not-open` to 409"
|
||||
reads as if two statuses were meant. If all three are 409, say "maps all three to 409".
|
||||
|
||||
N6. Pinning cost, as you said. Every briefed Case 2 row pins this file's blob, so 72ceda935 flags them all
|
||||
until Sage re-pins. Landing both of Sage's edits under one re-pin makes sense.
|
||||
|
||||
## Checked and fine
|
||||
|
||||
- C2-09b reuses what `thread.send` writes (:3511-3536): `#checkMessaging`, one message row, `#route`
|
||||
and one `human.input`. `#route` and `#delivery` only insert rows (:3678-3681, :3787-3797), so one
|
||||
dispatch transaction does undo a partial send. Leaving the room verbs out of `TESTER_VERBS` makes a
|
||||
tester refuse as for any other verb, and the gate's grep names tester-writes.test.mjs.
|
||||
- `room.open`'s refusal on a same-name thread elsewhere fits the per-project unique name
|
||||
(schema.sql:249), and "no schema change" holds.
|
||||
- C2-10c keeps the broker as the authority for addressing. The parity test reuses C2-09b's table. The
|
||||
plugin test checks that other verbs still refuse, which matters because VERBS (:4-12) is the webui
|
||||
channel's allowlist.
|
||||
- C2-15 names Opus and Filbert, and lists its after rows and the M9 refusal test. The out-of-scope line
|
||||
that sends draft sources to C2-01c matches the playground ruling.
|
||||
@@ -0,0 +1,37 @@
|
||||
# Webby review: Case 2 v1 briefs C2-09b, C2-10c and C2-15 (mos-1233), round d2
|
||||
|
||||
Webby, 2026-10-21T22:34Z (from `date -u`). Author: Gosalyn (gosalyn-0125). Candidate: `git diff 72ceda935
|
||||
99b1e0859` on gosalyn/case2-v1-briefs, one commit. The rows file blob is c45816509, as Gosalyn said. I
|
||||
reviewed it against my d1 (REVIEW-d1.md, 35130c26b). These are briefs, so there's no queue record.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
- B1. C2-15 keeps both exclude forms of design 1.4.
|
||||
- The type form is a first segment that is one of the hybrid's sources plus exactly one `types` key.
|
||||
`second/Characters` fits it: `second` is a universe id and `Characters` is a `types` key in
|
||||
mapping.test.mjs.
|
||||
- Anything else is the path form, inside or equal to a source book folder.
|
||||
- When both readings fit, it refuses.
|
||||
- One `excluded(mapping, hybrid, path)` helper in scope.mjs serves C2-05a's index, R6 and the guard.
|
||||
If C2-05a lands first, this row replaces C2-05a's own read, so all three share one reading.
|
||||
- Each form gets a case on the lookups, R6 and the guard, and the test at :57 still checks.
|
||||
- N1. R8 now counts a hybrid version of the entity: the same type from `classify`, and the same front
|
||||
matter `name` (or the same file name when either file has none). A link alone doesn't count. That's
|
||||
better than my narrowing: the hybrid Tom who links Mara and has his own `age` would have passed a
|
||||
type-only rule. The tests cover the no-link version as R8, and the place file and the other character
|
||||
as non-R8.
|
||||
- N2. A pin entity or path exclude under a source playground prefix refuses.
|
||||
- N3. `room.read` groups only a run of consecutive seqs from the human with the same `at` and body. The
|
||||
double-click case has a test.
|
||||
- N4. A tag that is one member's name and another member's key refuses `addressee-ambiguous` and
|
||||
inserts nothing. A tag that names one member both ways resolves to that member. The new code is on the
|
||||
line for Sage.
|
||||
- N5. C2-10c maps all four room codes to 409.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. R8's version rule says "a hybrid file" without a scope. A hybrid can have playground prefixes, and
|
||||
a brainstorm file there with the same name and another value would report R8. It's report-only, so the
|
||||
cost is noise in `library.status`, not a wrong lookup. If Melanie should be free to try other values
|
||||
in the playground, the builder can limit the version check to hybrid canon files and add a test for a
|
||||
playground version as non-R8. That's the builder's and Filbert's call, not a change to this brief.
|
||||
@@ -0,0 +1,360 @@
|
||||
# Webby static look: row 191 Console layout fix (sage-2364, mos-1248 condition 3)
|
||||
|
||||
Asked by Sage at 2026-10-22T02:50:06Z. Git reads only, no runs. Base: Rocko's 0f8235277
|
||||
(origin/rocko/row191-stoprun-ddfb-20261022).
|
||||
|
||||
## The failure, read from the base
|
||||
|
||||
- shell.test.mjs:380 asserts `scrollHeight === innerHeight` on the Board at 1440x1000. Rocko's gate
|
||||
reports 1062 against 1000.
|
||||
- index.html loads processing.css after shell.css (:4 and :6), and both use `.frame` (0,1,0). So
|
||||
processing.css:14 `min-height:0` wins over shell.css:135 `min-height:calc(100vh - var(--bar-h) -
|
||||
var(--foot-h))` at 760 px and wider. The frame has no floor of its own any more.
|
||||
- processing.css:12 makes body a flex column with `min-height:100vh`. Its in-flow children are the
|
||||
cmdbar (53 px, `--bar-h`), #processing-controls, the hidden banner, .frame, and the sticky footer (33 px,
|
||||
`--foot-h`). The tree is `position:fixed` (processing.css:16), so it takes no height.
|
||||
- Nothing caps the body at 100vh, so the page is cmdbar + bar + frame content + footer whenever that sum
|
||||
is over 1000. 1062 - 1000 = 62, and 62 is the bar's height from Sage's report. 1000 - 53 - 33 = 914 is
|
||||
the frame height shell.css:135 used to set. So the Board's content is at most 914 px and over 852 px.
|
||||
Before 191 it fit, and the bar now takes its share.
|
||||
- That means no rule on the frame alone can fix it while the window scrolls. The content needs 914 px,
|
||||
and the bar leaves 852. A fix has to do one of three things:
|
||||
1. take the bar out of flow, for example sticky or fixed over the frame's top, and pad the content by
|
||||
the bar's height only where the tree already sits;
|
||||
2. scroll the frame inside a body capped at 100vh, which changes :382-385 because the window would
|
||||
no longer scroll at 420 px;
|
||||
3. make the Board shorter.
|
||||
I'll judge Rocko's diff against these.
|
||||
|
||||
## Checks for Rocko's diff
|
||||
|
||||
1. **Layout.** At 1440x1000 the bar's box lies inside 0..1000, and scrollHeight equals innerHeight
|
||||
because the page fits, not because the window can't scroll. The same holds at 760-959 px (nav 200 px,
|
||||
shell.css:142-151) and at 1900 px and up (nav 280/320 px).
|
||||
2. **Tall page.** At 1440x420, scrolled to the end, :382-385 still see `scrollHeight > innerHeight`,
|
||||
with the tree between the cmdbar bottom and the footer top. The bar must not cover the tree or
|
||||
the first Board row.
|
||||
3. **:380 unchanged.** `git diff 0f8235277 <fix> -- packages/webui/tests/shell.test.mjs` adds lines and
|
||||
changes none at :373-385.
|
||||
4. **New assertion.** It must fail when the fix is reverted. For that it needs the bar's own box,
|
||||
`#processing-controls` getBoundingClientRect, against innerHeight and the cmdbar, at the size that
|
||||
failed. A check on scrollHeight alone at a size where the Board fits anyway would pass with the bug.
|
||||
5. **Mutant.** It must revert the CSS fix and nothing else, and it must name the killing test.
|
||||
|
||||
## Verdict on 5cd32f4c1 (rocko 02:57:02Z): CHANGES, B1
|
||||
|
||||
Read at 2026-10-22T02:59Z (from `date -u`). It's one commit on 0f8235277: processing.css (+12/-2), shell.test.mjs (+11/-0)
|
||||
and BUILD.md. Nothing has run, mine included.
|
||||
|
||||
**B1. As written, the CSS delta doesn't change the height of the page that failed.**
|
||||
(processing.css:12-24 at 5cd32f4c1)
|
||||
- Old body: a flex column with `min-height:100vh`, and the frame `flex:1; min-height:0`. The body's
|
||||
height is auto, so the frame's 0% basis resolves to its content, and the body comes to
|
||||
max(100vh, cmdbar + bar + C + footer), with C the frame's in-flow content height.
|
||||
- New body: a grid with `min-height:100vh` and the frame on row 4 as `minmax(min-content,1fr)`.
|
||||
min-content of the frame is the same C. The track can't go below it, and 1fr only spends space that is
|
||||
left over. So the body comes to max(100vh, cmdbar + bar + C + footer) again.
|
||||
- The two give the same number. For 1062 to come out of the old one, C has to be over 852
|
||||
(1000 - 53 - 62 - 33). In that case the new one gives 1062 too, and :380 fails exactly as before.
|
||||
1062 is exactly 1000 plus the bar, so C is likely 914 = `100vh - var(--bar-h) - var(--foot-h)`. That
|
||||
points at a box in the frame's flow that is sized to the viewport, and the delta doesn't touch it.
|
||||
I couldn't find which box it is from the stylesheets. processing.css:22 `min-height:0` does beat
|
||||
shell.css:135 on load order, and `.frame > .tree` is fixed. So it needs a measurement, not more CSS.
|
||||
- **Before the gate.** On a slot, run one probe at 1440x1000 on the Board (the shell.test fixture) and
|
||||
report:
|
||||
- the `.frame` rect height and `#main` scrollHeight;
|
||||
- the rect of every in-flow child of body;
|
||||
- the computed `min-height` and `position` of `.frame` and `.tree`.
|
||||
|
||||
Run it on 0f8235277 and on 5cd32f4c1. If the two match at 1062, this delta isn't the fix.
|
||||
Then there are three ways out:
|
||||
1. remove the box sized to 100vh - 86;
|
||||
2. take the bar out of the frame's height budget, for example by subtracting a `--processing-h`
|
||||
from the shell's calc() values, or by placing the bar in a row the shell already counts;
|
||||
3. if the Board really needs 914 px, it isn't a short page with the bar on it. Changing :374's
|
||||
viewport would be a test change for Sage and Mos to rule on, not for Rocko or me.
|
||||
- Rocko's BUILD.md says a second red stops the window and goes to Mos. That's one more reason not to
|
||||
open the gate on an unmeasured fix.
|
||||
|
||||
**What holds.**
|
||||
- **:380 is unchanged.** The diff removes no line in shell.test.mjs, and :373-380 and :392-397 (the old
|
||||
:381-386) are byte-identical.
|
||||
- **The new assertions (:381-391) help.** The visible and positive-height check catches a fix that hides
|
||||
the bar, which :380 alone would pass. The check that the bar sits under the cmdbar and inside the
|
||||
viewport is the bar's own box, as I asked. :391 repeats :380, which runs first, so it can never be
|
||||
the one that fails. N1: drop it, or leave it as documentation.
|
||||
- **KS59** reverts processing.css to 0f8235277's bytes. :380 would kill it, not the new assertions. That's
|
||||
fine, since a revert is the failing state. I'd add a second mutant that sets `#processing-controls`
|
||||
to `display:none` with the fix in place, so :388 has a kill of its own.
|
||||
- **The grid body's auto-placed children.** a.skip is `position:absolute` (app.css:24), #announce is
|
||||
.sr-only absolute, #toast is fixed, the dialog is closed, and a hidden banner leaves row 3 at 0. None
|
||||
of them takes a row, so the explicit rows hold. Sticky still works for the cmdbar and the footer as
|
||||
grid items. At 1440x420 the min-content row lets a long page grow, so :394-396 keep their premise.
|
||||
At 760-959 px the same rows apply with the 200 px nav. I see no regression there, but none of
|
||||
this matters until B1 is settled.
|
||||
|
||||
## Trace for sage-2368, and a correction to B1
|
||||
|
||||
Read at 2026-10-22T03:00Z (from `date -u`), git only.
|
||||
|
||||
**Correction.** Above, I treated 62 as the bar's height and guessed at a box in the frame sized to
|
||||
100vh - 86. Both were inferences, and the second is wrong. 62 is only the overflow:
|
||||
53 + barH + C + 33 - 1000. Neither the gate log nor the diff gives barH.
|
||||
|
||||
**What set the frame's height on the 1440x1000 Board under 0f8235277.** The frame's only in-flow child
|
||||
is #main, the Board's own content.
|
||||
- body children (index.html): a.skip, header.cmdbar, #processing-controls, #processing-banner (hidden),
|
||||
.frame, footer.foot, #announce, #toast, dialog#cmdk-dialog.
|
||||
- .frame children: nav.tree, main#main.content, aside#inspector (hidden).
|
||||
- **nav.tree.** processing.css:16 `.frame > .tree { position:fixed }` is (0,2,0). It beats `.tree`
|
||||
sticky at console.css:14 and shell.css:144, and static at console.css:39. So the tree is out of flow.
|
||||
- **aside#inspector.** shell.test opens it at :246 and closes it with Escape at :248, and :374 opens no
|
||||
session. app.js:88 sets `hidden = !selected`, and close() (app.js:402) clears `selected`. No
|
||||
.inspector rule sets `display`, so [hidden] holds and the inspector takes no space.
|
||||
- **min-height.** processing.css:14 `min-height:0` beats shell.css:135 and console.css:12 on load order
|
||||
(index.html:4 then :6), as Sage says. Rocko's cause, shell.css:135, isn't the cause.
|
||||
- That leaves #main's content C. The old page was max(1000, 86 + barH + C). Before 191 there was no bar,
|
||||
and shell.css:135 lifted the frame to 914, so the page fit whenever C <= 914. It went to 1062 because
|
||||
86 + barH + C > 1000: the Board's content and the bar together don't fit in 1000 px. shell.test:373
|
||||
already says the Board grew at row 95 ("the short page is taller").
|
||||
|
||||
**Does 5cd32f4c1 remove it?** No. Row 4 is `minmax(min-content,1fr)`, and min-content is the same C, so the
|
||||
page is again max(1000, 86 + barH + C) = 1062. B1 stands, with this cause.
|
||||
- The probe I asked for still settles it. It now only needs the `#main` height and the
|
||||
`#processing-controls` height at 1440x1000, which should sum with 86 to 1062.
|
||||
- No grid or flex rule can fix it while the window scrolls and the bar takes vertical space. The ways out
|
||||
change design or test:
|
||||
1. put the bar where it costs no page height, such as inside the fixed nav column or the cmdbar row;
|
||||
2. make the Board shorter;
|
||||
3. rule that the Board at 1000 px is no longer a short page, which means changing :374 or :380.
|
||||
|
||||
These are calls for Sage and Mos, not something to fix in CSS.
|
||||
|
||||
**Small checks.**
|
||||
- **No implicit row 6.** a.skip is `position:absolute` (app.css:24), and #announce is .sr-only absolute
|
||||
(app.css:23, shell.css:139). #toast is fixed (shell.css:104). The dialog is display:none while closed,
|
||||
and in the top layer while open. Absolutely positioned and top-layer boxes aren't grid items, so none
|
||||
of them adds a row. The hidden banner leaves row 3 at 0.
|
||||
- **760-959 px behaves as before.** console.css:37-38 drops the frame to one column at <=959. shell.css:143
|
||||
(760-959) restores `200px minmax(0,1fr)` and loads later with the same specificity, so
|
||||
`.frame > .content { grid-column:2 }` still has a column 2. The tree stays fixed at
|
||||
`--processing-nav:200px` (processing.css:28). 5cd32f4c1 changes none of that, only the body rows.
|
||||
|
||||
## Structure ruling for sage-2382 and Rocko 03:04:20Z
|
||||
|
||||
Read at 2026-10-22T03:06Z (from `date -u`), git only, on 5cd32f4c1. No runs.
|
||||
|
||||
**Ruling: (i), the labeled region as the first child of nav.tree, with conditions R1-R6. Drop sticky.**
|
||||
|
||||
Why (i) and not (ii).
|
||||
- **:380 edges.** In (i) nav.tree stays the same element, class and fixed box (processing.css:26,
|
||||
shell.css:136). A child can't move the nav's rect, so :396 and the 420-tall check hold as before.
|
||||
In (ii) the nav keeps its box too, but the controls sit over it.
|
||||
- **Fragility.** The controls have no fixed height. #processing-receipt is aria-live text that
|
||||
changes, and #processing-unavailable wraps to several lines at 200 or 240 px. (ii) has to reserve
|
||||
that space without a measurement. A fixed reservation goes wrong as soon as the receipt changes or the
|
||||
text zooms, and then the overlay covers nav links. Subgrid plus a wrapper is a larger change than the
|
||||
fix needs. (i) gets the real auto height from normal flow.
|
||||
- **Landmark.** ARIA allows a region inside navigation, and axe's top-level rules cover only
|
||||
banner, main, contentinfo and complementary. A landmark list then shows "Instance processing" under
|
||||
"Console". That's a cost, since Stop isn't navigation, but the name and role still say what it is.
|
||||
Tab order is unchanged: cmdbar, then Stop, then the nav links, the same as the body-child layout
|
||||
today. I accept the nesting for this row.
|
||||
- **Height.** With the controls out of body flow, the page is max(innerHeight, 53 + C + 33), C being
|
||||
#main's content. That's the pre-191 arithmetic, and it fits whenever C <= 914 at 1000 tall. If the
|
||||
Board's C is over 914 on its own, no layout fixes :380. The debug run settles that.
|
||||
|
||||
Why no sticky.
|
||||
- nav.tree scrolls itself (console.css:14 `overflow:auto`). A sticky region at its top covers a
|
||||
focused nav link whenever the nav is scrolled and the user tabs back up. That's WCAG 2.4.11 (focus
|
||||
not obscured), and nothing sets scroll-padding to stop it.
|
||||
- At 1440x420 the nav is 334 px tall (420 - 53 - 33). A sticky region with the note shown can take
|
||||
over half of it.
|
||||
- Without sticky, Stop is the nav's first content. It's in view at the nav's scroll origin, and
|
||||
focus scrolls it into view.
|
||||
|
||||
Conditions.
|
||||
- **R1. Markup.** Move the existing div as is: id, class, role=region and aria-label "Instance
|
||||
processing". It goes before `<h2 class="tree-title">Console</h2>` in index.html:22.
|
||||
#processing-state keeps role=status, and the receipt keeps aria-live. #processing-banner stays
|
||||
where it is.
|
||||
- **R2. Descendant rules.** As a child of .tree, the controls match rules written for nav content:
|
||||
- console.css:21 `.tree .btn{margin-top:auto}` hits Stop and Resume;
|
||||
- console.css:40 `.tree .btn{margin:0;order:1}` at <=959 px. The region is `display:flex`
|
||||
(processing.css:1), so order:1 moves Stop and Resume after the state text, the note and the
|
||||
receipt. The visual order then differs from the DOM order at 400 px;
|
||||
- live.css:5 `.tree .small{margin-top:auto}`, live.css:10 `.tree .small{flex-basis:100%;margin:0}`
|
||||
and shell.css:147 `.tree .small{flex-basis:auto;margin-top:auto}` hit the note and the receipt.
|
||||
|
||||
Add rules that put them back, with `.tree .processing-controls .btn` and `.small` at (0,3,0). Don't
|
||||
edit the shell rules. The new test asserts that Stop's computed `order` is 0 at 400 px, and a mutant
|
||||
that removes the reset must fail it.
|
||||
- **R3. Below 760 px.** .tree is a wrapping flex row (console.css:39). The region needs
|
||||
`flex-basis:100%`, or it shares a line with the "Console" title (`flex:1`, console.css:40). Then
|
||||
Stop sits above the title and the Projects list. Test at 400 px that Stop's rect top is above
|
||||
#projects' rect top, and that Stop is visible.
|
||||
- **R4. Remove dead layout.** Drop the body grid and the `.processing-controls` grid-row and
|
||||
margin-left rules from 5cd32f4c1. They apply to nothing once the controls are in the nav. Keep
|
||||
`.frame > .tree { position:fixed }` and the `--processing-nav` widths. The `.frame`
|
||||
`min-height:0` that beats shell.css:135 then goes back to what it did on 0f8235277. That's fine,
|
||||
because the frame's height is C either way.
|
||||
- **R5. Tests.**
|
||||
- shell.test :373-380 and :392-397 stay byte-identical.
|
||||
- :381-390 still hold. The bar's top sits under the cmdbar and its bottom inside the viewport at
|
||||
1440x1000. Add a check that the bar's rect lies inside the tree's rect.
|
||||
- :67 becomes the mos-1250 text (sage-2384): not inside #main or aside.inspector, and before #main
|
||||
in source. The bar is in nav.tree, which comes before #main, so it passes. The move-into-#main
|
||||
mutant must fail it.
|
||||
- Every shipped-strings assertion is unchanged.
|
||||
- My N2 still applies: a `display:none` mutant has to have :388 as its own kill.
|
||||
- **R6. Debug run (Sage's slot).** Report the height of #processing-controls and nav.tree at
|
||||
1440x1000, 1440x420 and 800x420, and #main's height at 1440x1000. If the region is over half the
|
||||
nav at 1440x420, tell me before the window. It isn't a blocker, but it's a usability call for
|
||||
Sage.
|
||||
|
||||
Not a condition. When the halt banner is shown it's still a body row, so a halted Board can scroll at
|
||||
1000 tall. :380 runs with it hidden. I'd file it as a follow-up and not widen this row.
|
||||
|
||||
## Sticky versus sage-2372 (Rocko 03:06:49Z)
|
||||
|
||||
Read at 2026-10-22T03:08Z (from `date -u`), git only. No runs.
|
||||
|
||||
**Ruling: the separate scroller. The controls don't scroll and the links scroll under them inside the
|
||||
same nav.tree box.** It meets both sage-2372 (the controls never scroll out) and 2.4.11 (nothing overlays
|
||||
a focused link). The controls aren't positioned over the scroller. They take their own flex row above it.
|
||||
|
||||
Conditions S1-S5, on top of R1-R6 (R1's "before the Console h2" still holds):
|
||||
- **S1. Markup.** nav.tree's children become the controls region, then one plain div with no role or
|
||||
label, for example `.tree-scroll`. That div holds the existing children in their existing order: the
|
||||
Console h2, #sections, #board-tree and the rest. Ids and classes don't change. No test or script
|
||||
reads nav children by position. Only ids are used: shell.test :272/:280/:282 #sections,
|
||||
board-held.test :110 #board-tree.
|
||||
- **S2. 760 px and up.** nav.tree keeps its fixed box (top --bar-h, height 100vh - bar - foot) and
|
||||
goes to `overflow:hidden`. The controls are `flex:none`. `.tree-scroll` is
|
||||
`flex:1 1 auto; min-height:0; overflow:auto`, and it repeats console.css:14's column flex and
|
||||
`gap:10px`. Then `.tree .btn{margin-top:auto}` (console.css:21) and `.tree .small{margin-top:auto}`
|
||||
(live.css:5, shell.css:147) still push the nav's own button and note to the bottom. The same applies
|
||||
at 760-959 under shell.css:144.
|
||||
- **S3. Below 760 px.** `.tree-scroll{display:contents}`, so its children stay flex items of the
|
||||
wrapping row (console.css:39-40, live.css:10). The `order:1`/`order:2` layout and shell :280 "one
|
||||
row" keep working without edits. The region gets flex-basis:100% (R3), and nav.tree stays static
|
||||
with no scroll.
|
||||
- **S4. Tests.** Everything in R5 still applies. Add a check at 1440x420: scroll `.tree-scroll` to its
|
||||
end, and Stop's rect is still inside the viewport, under the cmdbar. A mutant that sets
|
||||
`.tree-scroll{overflow:visible}` and nav.tree back to `overflow:auto` must fail it.
|
||||
- **S5. R6 still applies.** If the region is over half of nav.tree at 1440x420, the scroller gets
|
||||
small. That's Sage's usability call before the window.
|
||||
|
||||
## webby-0107 withdrawn (sage-2386)
|
||||
|
||||
Read at 2026-10-22T03:09Z (from `date -u`). Sage's sage-2386 (03:07:50Z) crossed with webby-0107. It accepts
|
||||
non-sticky, withdraws the sage-2372 long-nav visibility requirement and rules out a separate links
|
||||
scroller. The scroller existed only to meet that requirement, so S1-S5 are withdrawn. R1-R6 stand as
|
||||
written, with ordinary nav.tree overflow:auto. Under R6, I still want the region and nav heights at
|
||||
1440x420 on the debug run.
|
||||
|
||||
## Static look at da4334806 (rocko 03:10:31Z): no blockers
|
||||
|
||||
Read at 2026-10-22T03:12Z (from `date -u`), git only. One commit on 5cd32f4c1 touching index.html,
|
||||
processing.css, three test files and BUILD.md. Nothing has run. Manifest digest 245dceb1 (88 paths), as
|
||||
Rocko reported. I haven't recomputed it, so that's for the manifest round.
|
||||
|
||||
**R1 markup.** The region div moves byte-for-byte to nav.tree's first child, before the Console h2.
|
||||
Its id, class, role=region, aria-label, role=status and aria-live are all unchanged. The banner keeps its
|
||||
place in body, before .frame. processing.js is unchanged.
|
||||
|
||||
**R2 descendant rules.** `.tree .processing-controls .btn {margin:0; order:0}` and `.small {margin:0;
|
||||
flex-basis:100%}` are (0,3,0) and sit outside any media query, so they beat console.css:21/:40, live.css:5/:10 and shell.css:147 (all (0,2,0))
|
||||
at every width. The base `.btn` (shared/app.css:60) has no margin, so `margin:0` changes nothing on its
|
||||
own. `.processing-controls p {margin:0}` and the note/receipt `flex-basis:100%` already held, so the
|
||||
`.small` reset only cancels the tree rules. console.css:21 `justify-content:center` still reaches the
|
||||
buttons. That's harmless. No other `.tree` or `nav` descendant rule matches the controls.
|
||||
|
||||
**R3 below 760.** `.tree > .processing-controls {flex-basis:100%}` at <=759 puts the region on its own
|
||||
line ahead of the Console title. The new browser test checks Stop's top against #projects' top at 400 px.
|
||||
|
||||
**R4.** The body grid, the grid-row rules, the controls' margin-left and `.frame {min-height:0}` are all
|
||||
gone. With no body rule left in processing.css, shell.css:135 lifts the frame to 100vh - 86 again.
|
||||
`.frame > .tree {position:fixed}`, the content column and the nav widths stay.
|
||||
|
||||
**Height.** The body is block flow again: cmdbar 53, then the hidden banner at 0, then
|
||||
.frame = max(914, C), then the footer 33. So the page is max(1000, 86 + C), with no bar term. #main's
|
||||
content doesn't change in 191, so C is the pre-191 value that passed :380. The debug run confirms it.
|
||||
|
||||
**R5 tests.**
|
||||
- shell.test is additions only, at :389-399. :373-388 and every later old line are byte-identical, :380
|
||||
included. The new check puts the bar inside `.tree`'s rect, and the diagnostic logs heights.
|
||||
- processing-ui :67 has the mos-1250 text, quoted old and new in BUILD.md. `<main id="main"` and
|
||||
`<aside class="inspector"` match the literal markup, and there's exactly one `</aside>`. KS61 (into
|
||||
#main) moves controlsAt past mainAt, so it fails. If the aside markup is ever renamed, inspectorAt is
|
||||
-1, and `indexOf('</aside>', -1)` searches from 0. The check then fails closed, not open.
|
||||
- processing-browser: the new test covers 400x1000, 1440x1000, 1440x420 and 800x420. Order is
|
||||
checked at 800 and 400, inside the <=959 band where console.css:40 applies order:1, so KS62 is killed
|
||||
there. Visibility and the cmdbar/viewport bounds are checked on load.
|
||||
- Mutants: KS59 is a full revert of HTML and CSS, KS60 hides the controls (N2 met), KS61 moves them into
|
||||
#main, KS62 drops the order reset. The total is 77.
|
||||
|
||||
**sage-2378 checks.**
|
||||
1. Ids, role and label are kept.
|
||||
2. Nested in the nav landmark, which I accepted in webby-0106.
|
||||
3. Stop is visible at 400 and 1440x1000, and above Projects at 400.
|
||||
4. Every existing assertion is unchanged, other than :67 under mos-1250, and the shipped strings are
|
||||
untouched.
|
||||
5. The height arithmetic is above.
|
||||
|
||||
**Notes, none blocking.**
|
||||
- N1 (carried): :402 (the old :391) still repeats :380.
|
||||
- N3: `focus()` then activeElement only proves Stop is focusable and enabled when that line runs. It
|
||||
doesn't prove Tab order. Stop is `disabled` in the markup until processing.js reads the state. If the
|
||||
fixture leaves it disabled, this line fails on the run, so it's a fixture question, not a test hole.
|
||||
DOM order already gives cmdbar, Stop, nav links.
|
||||
- N4: R6 reporting is in place (ROW191_LAYOUT lines). Sage's half-nav call at 1440x420 waits on those
|
||||
numbers.
|
||||
|
||||
### Addendum for sage-2392: full 0f8235277..da4334806 range, Sage's two items, KS59-KS62
|
||||
|
||||
Read at 2026-10-22T03:13Z (from `date -u`), git only.
|
||||
- **Range.** `git diff --stat 0f8235277 da4334806 -- packages/` has five files: index.html 8, processing.css
|
||||
14, processing-browser +27, processing-ui +5/-1, and shell.test +22/-0 (0 removed lines). The body flex,
|
||||
the controls' margin-left and `.frame{flex:1;min-height:0}` from 0f8235277 are all gone, as is
|
||||
5cd32f4c1's grid. The verdict above holds for the whole range.
|
||||
- **Item 1, flex containers below 760.** .tree is `display:flex` at every width (console.css:14), and
|
||||
it's a wrapping row at <=959 (console.css:39). shell.css:158-171 (<=759) doesn't touch .tree's display.
|
||||
The region is `display:flex; flex-wrap:wrap` at every width (processing.css:1), and nothing overrides
|
||||
that. So `.tree > .processing-controls{flex-basis:100%}` works in the tree's row, and the `.small`
|
||||
flex-basis works in the region's row.
|
||||
- **Item 2, order.** `.tree .processing-controls .btn` is (0,3,0) against console.css:40's `.tree .btn`
|
||||
at (0,2,0). A media query adds no specificity, so the reset wins whatever the load order.
|
||||
- **KS59-KS62**, read from all-mutants.json in Rocko's packet (77 entries):
|
||||
- **KS59.** The HTML edit plus an extra_edits entry on processing.css restore 0f8235277's exact markup
|
||||
and CSS. That's the state the gate failed at 1062, so :380 kills it.
|
||||
- **KS60.** Appends `#processing-controls{display:none}` at (1,0,0), which beats the class rule. :380
|
||||
can't see it any more, so the visibility check at shell :388 is its kill. N2 is met.
|
||||
- **KS61.** Moves the region to the head of #main, so processing-ui :67's controlsAt < mainAt fails.
|
||||
- **KS62.** Deletes the `.btn` reset line, and the order assertion at 800 and 400 kills it.
|
||||
- All four are unrun.
|
||||
|
||||
N3 closed (Rocko 03:13:09Z, checked at 2026-10-22T03:14Z). processing.js:41-52 fetches /api/config with the
|
||||
state, sets `canWrite` (:46) and then calls draw(). The status text reads "Processing is enabled." only
|
||||
when that draw runs with a snapshot, and :29 sets `halt.disabled` in the same call. The test's setup
|
||||
waits for "enabled" (processing-browser:39) with config `send:true` (:27). So Stop is enabled before
|
||||
focus() runs.
|
||||
|
||||
## Debug run on da4334806 (Rocko, 03:14:37-03:14:53Z)
|
||||
|
||||
Read at 2026-10-22T03:16Z (from `date -u`), from Rocko's debug-da4334806 logs. I ran nothing.
|
||||
- **Counts.** shell.test reports `# tests 5 / pass 5 / fail 0 / skipped 0`, and processing-browser
|
||||
reports 9/9/0/0. The total is 14/0/0, as Rocko said.
|
||||
- **ROW191_LAYOUT matches the report.**
|
||||
- Writable controls are 127 px, and the tree is 914 at 1000 tall and 334 at 420.
|
||||
- Read-only controls are 212.5 at 1440x1000.
|
||||
- At 400x1000, Stop's top is 181.5, the cmdbar bottom is 129 and #projects starts at 352.6, with
|
||||
order 0.
|
||||
- **R6.** The writable controls are 127 of 334 at 1440x420 (38%), under half. The read-only ones were
|
||||
measured only at 1000 tall, but their height depends on width, not viewport height (wrapping at
|
||||
240 px). So at 1440x420 they would be 212.5 of 334, about 64%. That's over half.
|
||||
- **My read for Sage's call.** It's acceptable. The region isn't sticky and scrolls away with the nav, so
|
||||
the 64% applies only at the nav's scroll origin, and every nav link is still one scroll away.
|
||||
#projects already began below the fold at 1440x420 before 191: 636.6 - 127 = 509.6 is past 420. A
|
||||
420-tall desktop window isn't the normal case. If Sage wants it smaller, the fix is a shorter
|
||||
read-only note, not layout.
|
||||
@@ -0,0 +1,103 @@
|
||||
# Webby review: row 192 r1, Q175 phase 1 SSO (#1690)
|
||||
|
||||
Webby, 2026-10-20T19:49Z (from `date -u`). Author: Launchpad. Candidate 83b32d8d8 on launchpad/row192, base
|
||||
53bb59462. Manifest `candidate-manifest.sha256` digest 3ad2a73d...badab, verified: all 18 path hashes match
|
||||
the candidate, the path list matches `git diff --stat 53bb59462 83b32d8d8`, and none of the 18 paths changed
|
||||
from the base to origin/refactor (aa1bd209e). Design: `agents/sage/work/q175-sso/DESIGN.md`. Static review
|
||||
plus one node probe of `returnPath` (below). I ran no suite and no mutant. I did not read
|
||||
/mnt/storage/scratch/launchpad-r192.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
The build follows the design closely. I checked these against the design and found them right:
|
||||
|
||||
- Callback steps 2-7 (sso.mjs:232-285):
|
||||
- The login record is taken and deleted before any check, and the provider comes from the record.
|
||||
- `validateApplicationLevelSignature` runs on the same Response, and its throw ends the callback.
|
||||
- The header alg is checked against `algs`, `iss` is compared raw, and `sub` is looked up in the subjects Map.
|
||||
- The `subject-unknown` line carries only the provider, iss and sub, JSON-quoted and capped at 255.
|
||||
- Sessions:
|
||||
- The store is keyed by sha256 and the cookie strings are exact.
|
||||
- The caps are 64 sessions and 32 logins.
|
||||
- Idle and absolute expiry are server-side.
|
||||
- `touch` happens only on a POST or a GET outside `/api/` (serve.mjs, the `sso.session` call).
|
||||
- Host (F4). Under `--sso`, anything but the public Host is 403, except GET `/healthz`. `/auth/*` routes go
|
||||
to `sso.handle` before the session check, and a request without a session gets 401 on `/api/` and a 303
|
||||
on a page.
|
||||
- Logout needs the exact Origin and `Sec-Fetch-Site: same-origin`, and a refused logout keeps the session.
|
||||
- Discovery (Q-B, sso.mjs:92-121, :151-182):
|
||||
- Unreachable and Refused are split as the design says.
|
||||
- A Refused provider stops the start. An unreachable one retries with doubling, capped at 300 s.
|
||||
- A Refused result on a retry stops the retries.
|
||||
- `readSlot` (sso.mjs:33-61):
|
||||
- lstat refuses a symlink, then the open uses O_NOFOLLOW, then fstat checks the file type, the uid and the
|
||||
mode against 0o077.
|
||||
- The realpath checks cover the repo, the checkout and the copies root.
|
||||
- The default copies root matches `stack-copy.sh:6`. I read the owner branch. It is right, and as the
|
||||
design says, no test covers it.
|
||||
- Tester listener (section 6, F8):
|
||||
- `startServer` throws on `tester` with `sso`.
|
||||
- Under `--sso` the tester listener refuses Authorization, either sign-in cookie and any `x-authentik-*`
|
||||
header, and `/auth/login` is 404 there.
|
||||
- The tester writer is unchanged.
|
||||
- The loader (C1) checks shape only, and test 25's FIFO slot proves no case opens a slot file.
|
||||
`mosaic-config.mjs` going async is safe because every reader runs it as a subprocess
|
||||
(cli/src/config.mjs:22, business/src/cli.mjs:32).
|
||||
- `webui-service.sh` refuses `--sso` with the preview set or the proof pair, and its status grep reads the new
|
||||
flag order.
|
||||
- The page: both app.js fetch paths, plus the two bus.js reads (F1), sign out on 401 `sign-in required` only.
|
||||
The link goes to `/auth/login?return=<path and query>`.
|
||||
- Every design mutant is in `mutants.py`, plus 10 more. A pattern that matches no test exits 0 and counts as
|
||||
SURVIVED, so a typo in a pattern fails safe.
|
||||
|
||||
## Blocker
|
||||
|
||||
B1. Open redirect after sign-in. `returnPath` (sso.mjs:200-206) accepts any URL whose origin is
|
||||
`publicOrigin` and returns `u.pathname + u.search`. The callback puts that string in `Location` (:284). If
|
||||
the pathname starts with `//`, the browser reads the Location as scheme-relative and leaves the Console. I
|
||||
ran the function as written in node, with `publicOrigin = https://mosaic.example.com`:
|
||||
|
||||
| `return=` | `returnPath` | Browser goes to |
|
||||
| --- | --- | --- |
|
||||
| `/.//evil.example` | `//evil.example` | `https://evil.example/` |
|
||||
| `/x/..//evil.example` | `//evil.example` | `https://evil.example/` |
|
||||
| `https://mosaic.example.com//evil.example` | `//evil.example` | `https://evil.example/` |
|
||||
| the four test 14 cases | `/` | the Console |
|
||||
|
||||
Here is how an attacker uses it. They send Jason `https://<public host>/auth/login?return=/.//evil.example`.
|
||||
He signs in at the real provider, and the Console then sends him to the attacker's page. The `.`/`..` segments
|
||||
are why. Test 14's `//evil.example` parses with evil.example as the host, so the origin check catches it.
|
||||
`/.//evil.example` parses as a path on the Console, so the origin check passes, and dot-segment removal
|
||||
leaves the pathname as `//evil.example`. Design B2 and test 14 exist to stop exactly this.
|
||||
|
||||
Fix: return an absolute URL on the Console, `publicOrigin + u.pathname + u.search`. Then `//evil.example`
|
||||
becomes `https://<host>//evil.example`, which stays on the Console. Or refuse a pathname that starts with
|
||||
`//`. Add the three rows above to test 14, expecting `/` (or the absolute form, if you choose it), and add a
|
||||
mutant that restores the current return so test 14 kills it.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. The brief and BUILD.md:4 cite design blob eba861f5d2bb (d55d86bb0). Refactor and the candidate have
|
||||
9e3eadb15c80 (b416d2ede), which adds Mos's straggler check. The two differ only in the phase 2 window, so
|
||||
the build isn't affected. Cite the current blob in the next round.
|
||||
|
||||
N2. Design section 6 says the tester listener also refuses "the old auth and proof header names". Under
|
||||
`--sso`, cli.mjs:108 passes `testerProofHeader: proof?.header ?? null`, and that is always null because
|
||||
`--sso` refuses `--proof-*`. So the old proof header name isn't refused on 7333. The `x-authentik-*` prefix
|
||||
covers the old auth header. Nothing on 7333 reads the proof header, so this is harmless. Either add the
|
||||
name the live unit uses to the F8 refusal, or list it under Deviations.
|
||||
|
||||
N3. serve.mjs:64-66: the new `SIGN_IN_COOKIE` line went between the row 177 comment and
|
||||
`TESTER_CHAT_WRITE`, the line that comment describes. Move it above the comment, with its own comment.
|
||||
|
||||
N4. For the phase 2 window and row 200, not this row. Before 192, `mosaic-config.mjs` refuses any unknown
|
||||
top-level key (53bb59462 scripts/mosaic-config.mjs:112-116). Once `console` is in the live config.json, every
|
||||
loader from a pre-192 tree refuses the whole config, and every tool on that tree fails closed. That covers
|
||||
older pinned copies, any checkout behind 192, and a rollback target older than 192. A 192+ tree without
|
||||
`packages/webui/src/sso-config.mjs` refuses too (the import fail in `validateConsole`). The
|
||||
stack-release.test.mjs:1793 fixture copies `scripts/mosaic-config.mjs` without it, which is fine while the
|
||||
fixture config has no `console`. So the window has to re-pin to 192+ before it writes the key, and a rollback
|
||||
past 192 has to remove the key first. Put both in the window plan.
|
||||
|
||||
N5. A HEAD request for a page with no session gets 401 JSON, not the 303, because serve.mjs checks
|
||||
`req.method !== 'GET'`. Browsers don't send that, so no change is needed.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Webby review: row 192 r2, Q175 phase 1 SSO (#1690)
|
||||
|
||||
Webby, 2026-10-20T19:58Z (from `date -u`). Author: Launchpad. Candidate 37582e820 on launchpad/row192 (one commit on
|
||||
83b32d8d8), base 53bb59462, queue rev 2312. Manifest `candidate-manifest.sha256` digest 6f8cee14...7787,
|
||||
verified. All 18 path hashes match the candidate. The paths are `git diff --name-only 53bb59462 37582e820`
|
||||
less the four packet files under agents/launchpad/work/queue-192/. None of the 18 paths changed from the base
|
||||
to origin/refactor (ae3c36f24). Static review plus one node probe of `returnURL` (below). I ran no suite and
|
||||
no mutant. I did not read /mnt/storage/scratch/launchpad-r192, so the 28/28 and mutant results are
|
||||
Launchpad's report, not my run.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
- r1 B1 is fixed. `returnURL` (sso.mjs:199-211) parses the return against publicOrigin, refuses another
|
||||
origin, and builds `publicOrigin + pathname + search` as an absolute URL. The callback's Location (:289)
|
||||
is now always absolute. Only the callback uses it; the export at :341 follows the rename. Test 14 has my
|
||||
three rows, `/board/./x`, and an origin assert on every case. The positive test expects the absolute
|
||||
form. Mutant 22 restores the r1 return under test 14.
|
||||
- Probe. I ran the function as written in node with publicOrigin `https://console.example.test` over 18
|
||||
inputs: my three rows, `/%2e%2e//evil`, `/.\\/evil`, `///evil`, `//evil`, `/\\evil`,
|
||||
`/@evil.example`, `/:evil`, CR/LF in the path (raw and encoded), a full-width dot, `:443`, upper-case
|
||||
scheme and host, and a fragment. Every result stayed on the Console's origin. Raw CR/LF are stripped by
|
||||
the URL parser, so no header split.
|
||||
- Mutant 23 (drop the second origin check). I agree it is equivalent. sso-config.mjs:43-44 holds
|
||||
publicOrigin to a bare https origin, and a parsed special-scheme pathname always starts with `/`, so the
|
||||
concatenation can't parse to another host. In the probe the mutant returned the same string as the build
|
||||
for all 18 inputs. Keeping the check as an unreachable guard is fine with me. Whether to keep it is
|
||||
Sage's call, as BUILD.md says.
|
||||
- N1 (design blob 9e3eadb15c80), N2 (under Deviations), N3 (serve.mjs:64-66, comment order) are done. N4
|
||||
moves to row 200 (#1698) and the window plan through Sage (mos-1058). N5 needs no change.
|
||||
|
||||
No new findings.
|
||||
@@ -0,0 +1,179 @@
|
||||
# Webby review: row 194 (#1692, agent lifecycle), build b4 as cut on b5, static
|
||||
|
||||
Webby, 2026-10-21 (times from `date -u` in the session line). Author: Honker (honker-0073, honker-0075).
|
||||
Request by sage-2230 and honker-0075: review b5, not f482890dc.
|
||||
|
||||
- **Candidate.** honker/row194-b5 at dfabbbe8a, six commits on origin/refactor da9555ca2.
|
||||
- **Manifest.** Honker gives 51 paths. `git diff --name-only da9555ca2 dfabbbe8a` gives 52. The extra one is
|
||||
agents/honker/work/queue-194/PRD-1.8-DRAFT.md, which stays out (sage-1626).
|
||||
- **The row.** It is in-progress, so I record nothing until Sage posts the request.
|
||||
|
||||
What I did, static only:
|
||||
- **b5 against b4.** On the 52 paths, `git diff f482890dc dfabbbe8a` touches only docs/plans/DEFERRED.md.
|
||||
So I read b4's code once, at f482890dc, and every line cite below holds at dfabbbe8a.
|
||||
- **Drift.** da9555ca2 is an ancestor of origin/refactor, and `da9555ca2..origin/refactor` touches none of
|
||||
the 52 paths.
|
||||
- **DEFERRED.md.** The diff keeps refactor's entries and adds the creative-delete entry after them.
|
||||
- **The read.** Four read-only subagents split the diff: bus, business, cli, and webui with harness. I
|
||||
re-read the code behind every finding below myself. Findings I couldn't confirm aren't listed.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
### B1. After a skipped reload, veto, hire and fire answer `restart: false`
|
||||
|
||||
- **Where.** packages/bus/src/runtime.mjs:280-282:
|
||||
|
||||
```
|
||||
const live=reload(id);
|
||||
...
|
||||
return verb==='instances.set'?{...result,restart:!live}:result;
|
||||
```
|
||||
|
||||
The writer's `commit()` always answers `restart: false` (instance-settings.mjs:386). So for
|
||||
`veto.set`, `instance.hire` and `instance.fire`, a reload that `sameFixed` refused (runtime.mjs:225-227)
|
||||
still answers `restart: false`.
|
||||
- **How a business gets there.** Brief item 7 lets the human add a credentialed definition. That add
|
||||
correctly answers `restart: true`. Its new `credentials` entry differs from `boot.roles` (`fixedRole(null)`),
|
||||
so every later reload of that business is refused until the bus restarts. A hand edit of a fixed key does
|
||||
the same.
|
||||
- **Failing scenario.**
|
||||
1. The human adds a credentialed agent and gets `restart: true`.
|
||||
2. Before restarting, the human runs `veto keep X`. The file gets X in `launch.keep`, the reload is
|
||||
refused, and the answer says `restart: false`.
|
||||
3. The broker's copy has no keep entry for X, so the chief's checks pass:
|
||||
- the keep check in `instance.fire` (broker.mjs:2570);
|
||||
- the `role.revoke` reason branch;
|
||||
- the B7 model check (broker.mjs:2478-2479).
|
||||
4. A chief fire of X runs step A: it revokes the claims, writes `lifecycle.end` with `kill: true`, and
|
||||
writes `fire.stopped` (broker.mjs:2595-2614).
|
||||
5. Step B's writer reads the file, so it refuses `instance-kept` (instance-settings.mjs:332), and
|
||||
`fire.done` is never written.
|
||||
6. X is left `instance-firing`. That refuses X's launch at the verb (:534) and at the take (:552). The
|
||||
veto the human set is what keeps the fire from finishing.
|
||||
- The same skipped reload leaves a hired instance unknown to the broker, and the hire still answers
|
||||
`restart: false`.
|
||||
- **What it breaks.**
|
||||
- DESIGN.md:428-430 (D3 item 3): "Any change that would need one of them still answers `restart: true` and doesn't
|
||||
reload."
|
||||
- DESIGN.md:408 (D2.4): "The broker enforces from its live copy of the business, which D3 keeps current."
|
||||
- The answer tells the human the veto is live when it isn't.
|
||||
- **Tests.** reload.test.mjs pins `restart: true` only for `instances.set` (:90-98, :157-163). No case
|
||||
covers a veto, hire or fire after a refused reload.
|
||||
- **Fix.**
|
||||
- Answer `restart: !live` for every committed verb, and print the restart line for veto, hire and fire in
|
||||
the CLI and the Console.
|
||||
- Add a reload test: a refused reload, then `veto.set`, which must answer `restart: true`.
|
||||
- Add a mutant that restores the `instances.set`-only condition.
|
||||
- Optional: have broker fire re-check keep against the file before step A. The writer already reads the
|
||||
file, so the check costs one read.
|
||||
|
||||
### B2. Fire ships without the SIGKILL escalation, and nothing records that
|
||||
|
||||
- **What happens.** Fire step A writes `lifecycle.end` with `kill: true` (broker.mjs:2611), and the
|
||||
launcher never reads it:
|
||||
- launcher.mjs:704 and :708 send every `lifecycle.end` to `terminate()`;
|
||||
- `terminate()` (:729-739) sends SIGTERM only.
|
||||
- So a fired runner that ignores SIGTERM keeps running with the model token.
|
||||
- No `lifecycle.end.killed` or `lifecycle.end.alive` is ever written, and the Console's "still running
|
||||
after fire" flag can't appear (BUILD.md:466-467).
|
||||
- T9a, T9b, T15 and M49-M55 are missing, and the halted checks (D1.1 item 2, D6) are missing too.
|
||||
- **What the design says.**
|
||||
- DESIGN.md D2.2: "After the grace, SIGKILL to any runner still alive ... and `lifecycle.end.killed`".
|
||||
- DESIGN.md D2.2: "Only fire escalates, because only a fired agent must lose the token at once. If row
|
||||
191 hasn't landed when this row builds, the build chains on its candidate (Q177)."
|
||||
- **What the build says.** BUILD.md:29 marks S6 "waits on row 191". Row 191 is in-progress in queue.json.
|
||||
b5 is offered to land in the 10-24 window without S6, and without a chain on 191.
|
||||
- **What I couldn't find.** A ruling that lets 194 land without S6. The DEFERRED.md diff adds only the
|
||||
creative-delete entry.
|
||||
- **Fix, either of:**
|
||||
- Build S6 on 191's candidate as the design says.
|
||||
- Get a Sage ruling that 194 lands without it, plus a DEFERRED entry. The entry names the gap (a fired
|
||||
runner can outlive SIGTERM with the token), the missing T9a, T9b, T15, M49-M55 and halted checks, the
|
||||
Console flag, and the row that binds them.
|
||||
- Since b4 lands before 191 either way, the ruling route is the cheaper one. It's Sage's call. I can't
|
||||
approve a fire that the approved design says must kill and that doesn't, with no record of the gap.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. A fire of a declared, delisted credentialed instance leaves its credentials live.
|
||||
- **Where.** instance-settings.mjs:335 returns `delisted: false` before `beforeWrite`, so `revoke` never
|
||||
runs.
|
||||
- **Scenario.**
|
||||
1. The human adds a credentialed coder-2, restarts, and issues its tokens.
|
||||
2. The human delists it with `launch: false`.
|
||||
3. The chief fires it. Fire answers done, and the tokens stay valid.
|
||||
4. The human can still launch an unlisted instance (V2).
|
||||
- **Design.** DESIGN.md D2.2 ("B finds I already delisted and counts as done") is about repeating a fire
|
||||
that partly failed. The brief's item 8 binds "before they change the business file", and this fire
|
||||
changes nothing, so this isn't a blocker as written.
|
||||
- **Fix.** Call `revoke` on that path when the entry has credentials. A revoke of an already revoked
|
||||
service should be a no-op. Add one test.
|
||||
|
||||
N2. A human add of a definition that declares the `host` service fails with the wrong message.
|
||||
- **Where.** `createdCredentials` (instance-settings.mjs:100, :127) maps every declared service, `host`
|
||||
included. `DATE_KEY` (credentials.mjs:12) has no `host`, so the entry gets a literal `"undefined"` key.
|
||||
Then `checkRoles` (business.mjs:140), which drops HOST_SERVICE, refuses with "must reference exactly the
|
||||
services its role definition needs".
|
||||
- **Effect.** It fails closed, and no shipped role declares `host`. The fix is to filter HOST_SERVICE
|
||||
first, or refuse the add by name.
|
||||
|
||||
N3. The B7 check (broker.mjs:2478-2479) looks only at `args.instances`. A chief `organization.set` that
|
||||
re-points the `models` entry a kept instance's `vars.model` names changes that instance's model at its
|
||||
next launch. DESIGN.md D2.4 says "that instance's model key". Re-pointing a shared key also changes every
|
||||
other instance on it, so I read the design as covering the instance's own key only. Say so in BUILD.md,
|
||||
or extend the check the way row 213 did for creative instances.
|
||||
|
||||
N4. A fire whose step B keeps refusing has no human escape. `#firing` (broker.mjs:537-543) clears only on a
|
||||
later `fire.done`, and only a chief fire writes one. Today only B1 makes this reachable. A human verb that
|
||||
clears it, or a take that checks keep against the file, would close it. Record it as a follow-up if B1's
|
||||
fix lands without one.
|
||||
|
||||
N5. Webui: `busStatus` (webui bus.mjs) maps none of the lifecycle refusal codes. A few examples are
|
||||
`instance-running`, `instance-kept`, `tester-off`, `tester-refused` and `unknown-request`, and all of them
|
||||
fall through to 502. The body is right. Separately, agents.js keeps polling an `unknown-request` for up to
|
||||
90 reads. Neither is harmful.
|
||||
|
||||
N6. CLI terminate (launcher.mjs:731-733) signals every registry run of a non-thread instance. That includes a
|
||||
run the chief launched after the terminate verb, whose claim was never revoked. DESIGN.md:118-120 (V3) names the
|
||||
verb's runs plus spawned-but-unbound ones. If this is meant, say so in the design's terms. The CLI text
|
||||
already hints at it.
|
||||
|
||||
N7. A delete that commits and then closes `outcome-unknown` (launcher.mjs:745-749) can't be retried. The
|
||||
broker allows a second delete of a retired name only after `workspace-rename-failed`, while DESIGN.md:173
|
||||
says a second delete "retries step 3 only" with no code limit. The window is narrow.
|
||||
|
||||
N8. DESIGN.md:625 still lists "SIGKILL escalation" under "Not in the row". That's r2 text that D2.2 r4
|
||||
replaced. It contradicts B2's design text. Don't edit the approved design. Note it in BUILD.md.
|
||||
|
||||
## Checked and fine
|
||||
|
||||
- **My design r4 notes.**
|
||||
- N1 is in: `#firing` (broker.mjs:537) refuses `instance-firing` at the launch verb (:534), at the take
|
||||
(:552) and in `role.launch` authority (:1770).
|
||||
- N2 is in: hire refuses unless roleVersion is 2 (broker.mjs:2647), and `checkHireable` refuses a version
|
||||
1 name (role.mjs:94).
|
||||
- **Creative hire.**
|
||||
- The writer refuses a CREATIVE_DEFINITIONS hire before `definitionFor` (instance-settings.mjs:304-308).
|
||||
- `checkHireable` refuses a creative name in a hireable list.
|
||||
- **endProtected.** The listing's `endProtected` and `deleteRefusal` call the same
|
||||
`endProtected` (instance-settings.mjs:32, :36, :170). agents.js:44 hides Delete on it.
|
||||
- **Reload.** The `libraries` key fixes each library entry whole, so a mapping change gets
|
||||
`restart: true` for `instances.set`.
|
||||
- **C1.** `testerNoSignIn` is gone from serve.mjs, webui cli.mjs and sso.test.mjs, with no reference left.
|
||||
- **Order and revision.** Fire runs A, B, C, and the consume happens only in C. `commit()` checks the
|
||||
revision before the revoke.
|
||||
- **Credential failures.** A failed revoke writes nothing, and the CLI text says the file is unchanged.
|
||||
- **Human tracker add.** It is required for vikunja, refused otherwise, and checked by `checkBot`, with
|
||||
bot uniqueness against the other instances and the sync bot.
|
||||
- **Human-only routes.** On the human listener, the webui routes need the signed-in Origin and Fetch Metadata
|
||||
checks. The tester listener refuses regenerate, veto and delete at the route, and the broker refuses
|
||||
them again.
|
||||
- **Escaping.** agents.js escapes every name and attribute it renders.
|
||||
|
||||
## Before the manifest round
|
||||
|
||||
The fixes for B1, and B2 or its ruling, are code and DEFERRED changes, and the gate reruns on them. I'll
|
||||
review the delta and then the manifest record. When I get a CLEAR, I'll run:
|
||||
- reload.test, lifecycle-verbs.test and chief-of-staff.test as whole files;
|
||||
- the B1 mutant;
|
||||
- a spot set of the D-table mutants, M2, M3, M22 and M27, on a scratch worktree.
|
||||
@@ -0,0 +1,90 @@
|
||||
# Webby review: row 194 (#1692, agent lifecycle), build b6, static
|
||||
|
||||
Webby, 2026-10-21T23:52Z (from `date -u`). Author: Honker (honker-0083). Rulings in force: sage-2241,
|
||||
sage-2243, sage-2245.
|
||||
|
||||
- **Candidate.** honker/row194-b6 at af8d58c7d, one commit on b5 dfabbbe8a. 17 files, +280/-29.
|
||||
- **What I read.** The whole `git diff dfabbbe8a af8d58c7d`: source, tests, DEFERRED.md and BUILD.md "b6".
|
||||
I also read the code each fix leans on: the broker's organization gate and fire path, the runtime
|
||||
revoke, `Credentials.revoke` and `setRoutes`.
|
||||
- **What I didn't do.** No test or mutant has run. They wait for Sage's CLEAR. The row has no posted
|
||||
request for this round, so I record nothing.
|
||||
|
||||
## Verdict: CHANGES, B2 only (carried from b5)
|
||||
|
||||
B1 and N1-N3 are fixed in the code and have tests. N4 is deferred with a named test. B2 stays open
|
||||
until S6 is built on row 191's `stopRun`, per sage-2241 and sage-2245. Nothing new blocks.
|
||||
|
||||
### B1. Closed statically, runs pending
|
||||
|
||||
- **Restart.** runtime.mjs:33 and :290 answer `restart: !live` for `instances.set`, `veto.set`,
|
||||
`instance.hire` and `instance.fire`. Limits, routes and plugins writes keep their old answers, and
|
||||
reload.test pins that limits.set has no `restart` key.
|
||||
- **The fire check.** broker.mjs:2596 calls `write('instance.fireCheck')` before step A.
|
||||
- runtime.mjs:272 sends it to `checkFire` (instance-settings.mjs:351), which reads the file, runs
|
||||
`fireChecks` (:357) and returns before any write or reload.
|
||||
- `fire` runs the same `fireChecks` at step B.
|
||||
- `instance.fireCheck` appears only at broker.mjs:2596. A client that sends it gets no route to the
|
||||
writer.
|
||||
- **Printing.** The CLI veto prints RESTART_NEEDED or NO_RESTART. agents.js prints the matching
|
||||
receipt text. serve.mjs `/api/veto` needs a boolean `restart`, or it answers `outcome-unknown`.
|
||||
- **Tests.**
|
||||
- reload.test.mjs:150 runs my b5 sequence with the real writer. A credentialed add refuses the reload.
|
||||
Then hire, keep, unkeep and fire each answer `restart: true`. A fire of the name kept since refuses
|
||||
`instance-kept` with the file unchanged, the claim held and no `instance-firing`.
|
||||
- chief-of-staff T10 (new) has the writer refuse at fireCheck. The organization log holds only
|
||||
`instance.fireCheck`, the claim stays and `instance.launch` goes through.
|
||||
- The cli test and the Console tests cover the veto restart line and the shape check.
|
||||
- **What the CLEAR run must show.** Both of BUILD.md's mutants killed:
|
||||
- (a) the `instances.set`-only restart line. reload.test :70 and :150 should kill it.
|
||||
- (b) no `instance.fireCheck` call. reload.test :150 and chief-of-staff T10 should kill it.
|
||||
|
||||
### B2. Open
|
||||
|
||||
S6 isn't in b6. BUILD.md "b6" gives the `stopRun` contract (agents/rocko/work/queue-191/STOP-RUN-CONTRACT.md)
|
||||
and says 194 lands with or after row 191 in the 10-24 window. That matches sage-2241. I'll review S6
|
||||
when it's wired onto Rocko's commit. T9a, T9b, T15, M49-M55 and the halted checks come with it.
|
||||
|
||||
## The b5 notes
|
||||
|
||||
- **N1. Closed.** A fire of a delisted name now revokes with no write (instance-settings.mjs:334-339).
|
||||
- A repeat is safe. `Credentials.revoke` takes ENOENT (credentials.mjs:333-337), and the runtime
|
||||
revoke skips `credential-unknown` (runtime.mjs:151-155).
|
||||
- That path is also the repeat after a failed step C, which now revokes a second time and still goes
|
||||
on to `fire.done`.
|
||||
- The lifecycle-revoke.test case checks the receipt, the unchanged bytes, the empty store and the
|
||||
status.
|
||||
- **N2. Closed.** instance-settings.mjs:101 drops HOST_SERVICE. The new instance-settings test checks the
|
||||
references.
|
||||
- **N3. Closed for the chief.** `keptHumanOnly` runs in the limits writer on a re-point of a key a kept
|
||||
instance names and on its own `instances` key. The broker passes `instance-kept` for every verb. The
|
||||
"B7 kept instance" test (launch-settings.test.mjs:148) covers both paths for the chief, a key no kept
|
||||
instance names, and the human. The comparison `actor !== document.human` is the same one
|
||||
`creativeHumanOnly` already relies on.
|
||||
- **N4. Deferred.** The DEFERRED.md entry names the gap, a human-only verb and its test.
|
||||
- **N5, N7.** Follow-ups, as BUILD.md says.
|
||||
- **N6.** With Sage, tied to S6.
|
||||
- **N8.** Noted in BUILD.md, and the design isn't edited.
|
||||
|
||||
## New notes, non-blocking
|
||||
|
||||
N1. The tester half of N3 doesn't match what the tester can reach.
|
||||
- `limits.set` isn't in TESTER_ORGANIZATION_VERBS (broker.mjs:130). So the tester rows in the "B7 kept
|
||||
instance" test exercise the writer, and the bus never gets there.
|
||||
- `routes.set` is a tester verb. `setRoutes` (launch-settings.mjs:291-293) runs `creativeHumanOnly`
|
||||
and not `keptHumanOnly`. So the tester can change a kept instance's model through a route.
|
||||
- I didn't check `instances.set` change with `vars.model` for the tester.
|
||||
- D2.4 binds the chief, so this isn't a blocker. Either keep binds the tester too, and `setRoutes` gets
|
||||
the same check and a test, or it doesn't, and BUILD.md drops "the tester included". Sage's call.
|
||||
|
||||
N2. fireCheck reads the file without the writer's lock, so a keep written between the check and step B
|
||||
still strands the instance. The new DEFERRED entry names that case. I'm noting it only so the CLEAR
|
||||
run doesn't treat it as new.
|
||||
|
||||
## On CLEAR
|
||||
|
||||
On a scratch worktree at af8d58c7d, with TMPDIR under /mnt/storage/scratch/tmp:
|
||||
- whole files: reload, lifecycle-verbs, lifecycle-revoke and chief-of-staff (bus); instance-settings
|
||||
and launch-settings (business); cli instances; webui agents and agents-browser;
|
||||
- mutants: B1 (a) and (b) above, and M2, M3, M22 and M27 from the D table;
|
||||
- a blob check after each restore.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Webby review: row 194 (#1692, agent lifecycle), build b7, static
|
||||
|
||||
Webby, 2026-10-22T00:01Z (from `date -u`). Author: Honker (honker-0085). Rulings in force: sage-2241,
|
||||
sage-2245, sage-2246, sage-2248, sage-2249.
|
||||
|
||||
- **Candidate.** honker/row194-b7 at 0f0e2c7ef, nine commits on dcd8d4dda. dcd8d4dda is origin/refactor
|
||||
after row 254 landed.
|
||||
- **What I didn't do.** Nothing has run yet. Runs wait for Sage's CLEAR, after Rocko's row 191 slot. No
|
||||
request is posted for this round, so I record nothing.
|
||||
|
||||
## Verdict: CHANGES, B2 only (carried)
|
||||
|
||||
The rebase is clean, and the routes.set fix closes my b6 N1. B2 (S6) is still open, as ruled in sage-2241.
|
||||
|
||||
### The rebase
|
||||
|
||||
- **Commits.** `git range-diff da9555ca2..af8d58c7d dcd8d4dda..b0aed7527` gives `=` for six of the seven
|
||||
commits. The seventh, commit 2 (30f05f3d2), differs only in the channel-proof pin for webui/src/bus.mjs
|
||||
and in the STATUS context lines next to row 254's entries.
|
||||
- **channel-proof.** At 0f0e2c7ef, all four baseline pins match the sha256 of their files: human.mjs,
|
||||
human-cli.mjs, cli transport.mjs and webui bus.mjs (15e8123f). The diff against the base swaps row 254's
|
||||
pin for the merged file's and adds row 194's comment. Row 254 had no comment line of its own to lose.
|
||||
- **webui/src/bus.mjs.** Against dcd8d4dda, the diff is the two row 194 tracker entries. STATUS has no
|
||||
duplicate key.
|
||||
- **DEFERRED.md.** Against dcd8d4dda, the diff adds row 194's two entries, creative delete and the stuck
|
||||
fire (N4). It removes nothing.
|
||||
- **Drift.** da9555ca2..dcd8d4dda reaches the manifest on only those three paths: DEFERRED.md,
|
||||
channel-proof.test.mjs and webui bus.mjs. The b7 manifest is the same 53 paths as b6, with
|
||||
PRD-1.8-DRAFT left out.
|
||||
|
||||
### b6 N1, closed statically
|
||||
|
||||
6a742d371 calls `keptHumanOnly` in `setRoutes` right after `creativeHumanOnly` (launch-settings.mjs:294-295).
|
||||
The test "B7 kept instance: its route is the human's" covers three cases:
|
||||
- the chief is refused with `instance-kept` and the bytes unchanged;
|
||||
- the tester gets the same, which is now reachable on the bus because routes.set is a tester verb;
|
||||
- the human re-routes the instance.
|
||||
|
||||
M-next, which drops the call, has to be killed on the CLEAR run.
|
||||
|
||||
### The behaviour change (sage-2246)
|
||||
|
||||
An agent can no longer re-point a `launch.models` key that a kept instance names, and a shared key
|
||||
counts. I agree. Re-pointing a shared key changes the kept instance's model, which is what D2.4 B7
|
||||
protects. The chief still adds keys and changes the ones no kept instance names, and b6's test covers
|
||||
that.
|
||||
|
||||
### The mutant (a) correction
|
||||
|
||||
Right. The CLI test and the Console tests stub the bus, so only reload.test can kill (a).
|
||||
|
||||
## On CLEAR
|
||||
|
||||
Honker's runb7.sh list covers the whole files and mutants (a), (b) and M-next. I'll run on my own scratch
|
||||
worktree at 0f0e2c7ef, with TMPDIR under /mnt/storage/scratch/tmp:
|
||||
- **Row 194's files, each alone:** reload, chief-of-staff, lifecycle-verbs, lifecycle-revoke and
|
||||
channel-proof (bus); instance-settings and launch-settings (business); cli instances; webui agents and
|
||||
agents-browser.
|
||||
- **Rebase files:** webui library-status.test and bus library-sync.test. Row 254's tests ran against
|
||||
its own bus.mjs, and this merge changes that file.
|
||||
- **Mutants:** (a), (b), M-next, and M2, M3, M22 and M27, with a blob check after each restore.
|
||||
|
||||
B2 stays open until the S6 commit, with N6 narrowed as sage-2246 rules. I'll review it when it arrives.
|
||||
@@ -0,0 +1,307 @@
|
||||
# Webby review: row 194 (#1692, agent lifecycle), build b8 r2, static
|
||||
|
||||
Webby, 2026-10-22T00:41Z (from `date -u`). Author: Honker (honker-0092, honker-0094). Rulings in force: sage-2241,
|
||||
sage-2246, sage-2260, sage-2261, sage-2289.
|
||||
|
||||
- **Candidate.** origin/honker/row194-b8 at 899e30a80, on row 191's tip eb3abba12
|
||||
(origin/rocko/row191-stoprun-dcd8-20261022, four commits on dcd8d4dda). b8 r2 is b8 (0583569b7) plus
|
||||
one commit, and 0583569b7 is its parent.
|
||||
- **Manifest.** `git diff --name-only eb3abba12 899e30a80` gives 54 paths. Leaving out
|
||||
agents/honker/work/queue-194/PRD-1.8-DRAFT.md gives 53. None is under packages/processing-control, and
|
||||
none is a row 191-only path.
|
||||
- **What I read.**
|
||||
- The b7 to b8 range-diff, `git range-diff dcd8d4dda..0f0e2c7ef eb3abba12..0583569b7`, and every
|
||||
interdiff of the three `!` commits.
|
||||
- The whole S6 commit b3341c1cb and the r2 commit 899e30a80.
|
||||
- BUILD.md "b8 notes" and "b8 r2: sage-2289".
|
||||
- Row 191's `stopRun`, `ownStop` and `sweepRuntimes` (processing-control/src/runtimes.mjs at
|
||||
eb3abba12).
|
||||
- DESIGN.md V3 (:112-124), D2.2 (:276-312), D6 (:485-496), D7 (:498-503) and the T9a, T9b, T15 and
|
||||
M49-M55 rows.
|
||||
- **What I didn't do.** No test or mutant has run. They wait for Sage's CLEAR. The row has no posted
|
||||
request for this round, so I record nothing.
|
||||
|
||||
## Verdict: no blockers. B2 is closed statically, and APPROVE waits on the runs
|
||||
|
||||
B2 (S6) is built on row 191's `stopRun` as D2.2 asks. Terminate stays SIGTERM-only, and the halted
|
||||
checks match D6. The unknown outcome follows sage-2289. I found nothing new that blocks. The round's
|
||||
APPROVE depends on my CLEAR run below plus the full gate with Docker and P3 (sage-2261).
|
||||
|
||||
## The rebase onto 191
|
||||
|
||||
- Six commits are `=`. Commits 1, b4 and b6 are `!`, and in each one I checked, both sides are kept with
|
||||
no change to 194's behaviour. BUILD.md lists these file by file, and the interdiffs match:
|
||||
- the broker constructor ends `processing = null, testerOn = null, roleFile = null`;
|
||||
- runtime.mjs keeps 191's `broker = new Broker(...)`, `broker.reconcileProcessing(); processing.start();`
|
||||
and its imports, with `INSTANCE_VERBS` after them;
|
||||
- launcher `start()` takes `human = false, admissionGeneration`, and 191's `admission(...)` calls stay;
|
||||
- webui cli and serve pass both `agentWrite` and `processing`, and C1's `testerNoSignIn` removal sits
|
||||
beside 191's tester `processing` read;
|
||||
- the browser.test script pin is `['/editor.js','/processing.js','/brand.js','/app.js',
|
||||
'/markdown-preview.js','/chat.js','/agents.js','/bus.js']`.
|
||||
- 191 also changed 10 of the 194 paths. That's why these resolutions get a full run and not a carried
|
||||
verdict.
|
||||
|
||||
## B2 (S6) against the design
|
||||
|
||||
- **Fire goes through 191's stop.** `act` (launcher.mjs:763-770) sends a `kill: true` end to `fireStop`
|
||||
(:819). That calls `stopRun` with reason `fire` and owner `{ decision: <request> }` (:825), which is
|
||||
the shape `stopRun` accepts. `stopRun` checks identity before each signal, sends SIGKILL after the
|
||||
grace and observes. D2.2 items 3a-3c are 191's code, not a second copy.
|
||||
- **Terminate is SIGTERM only (V3).** `terminate` (:804) sends SIGTERM and nothing else. The
|
||||
T9a terminate test (lifecycle.test.mjs:670) pins one SIGTERM and no fire-stop record. M54 targets it.
|
||||
- **Outcomes.** `lifecycleDone` (broker.mjs:2322) accepts `stops` only on a `kill: true` end (:2343). It
|
||||
writes `lifecycle.end.killed` or `.alive` against the instance with run and pid. The pid check (`null`
|
||||
or a safe integer above 1) matches 191's identity check (`pid < 2` fails). The test at
|
||||
chief-of-staff.test.mjs:899 covers the refusals.
|
||||
- **Unknown, r2.** `fireStop` (launcher.mjs:835) maps `unknown` to alive with reason `unknown`, and the
|
||||
broker accepts that reason only on an alive stop (broker.mjs:2335). That closes more than the
|
||||
no-record case. `ownStop` returns a stored result before it signals (runtimes.mjs:267). So a run whose
|
||||
earlier halt sweep ended `unknown`, if it survived to a later fire, gets no signal from the fire, and
|
||||
r2 now flags it. Before r2 it would have vanished from the fire's report. The same holds for "another
|
||||
host owns delivery" and for a `stopRun` that throws (launcher.mjs:828). The new T9a test (:597) and
|
||||
M56 cover it.
|
||||
- **Console (D7).** `#aliveAfterFire` (broker.mjs:2351) lists alive runs with no `session.ended`, and the
|
||||
instances read adds them as `alive` (:2633). agents.js escapes each run with `txt`. The
|
||||
agents-browser test puts a hostile run name through it.
|
||||
- **Halted (D6).**
|
||||
- `#launchable` (broker.mjs:617) is used only by `instance.launch` and `instance.regenerate`. So
|
||||
`instance.end` keeps no halted check, and terminate works while halted (V3 item 5).
|
||||
- The take re-checks for every action except end and delete (:636-647).
|
||||
- Hire and fire refuse through 191's work gate, since neither is on its read list.
|
||||
- The tick (launcher.mjs:583) skips the thread and land passes while halted and still runs the
|
||||
lifecycle pass.
|
||||
- T15 covers the bus (lifecycle-verbs.test.mjs:150) and the cli (lifecycle.test.mjs:718).
|
||||
- **Halt joining a fire (T9b).** The sweep's `stopRun` gets the fire's in-flight promise from 191's
|
||||
`stopping` map. So it sends no second SIGTERM and doesn't restart the deadline, and the receipt keeps
|
||||
reason `fire` and the decision. lifecycle.test.mjs:686 checks one SIGTERM, the deadline, the single
|
||||
receipt and the counted run. M55 and M55-deadline target it.
|
||||
- **N6 (sage-2246).** `ending` (launcher.mjs:795) takes the verb's runs plus non-thread registry runs
|
||||
spawned at or before the end's `at`, using `spawnedAt` (:500) or the registry `startedAt`. That
|
||||
matches DESIGN.md:118-120. lifecycle.test.mjs:751 checks that a later run survives and the next end
|
||||
takes it. Fire uses the same selection. The only spawn that can follow a fire's verb is a human
|
||||
launch already taken, and D2.2 (:300-302) leaves that one to the human's terminate. So the narrowing
|
||||
doesn't cut into F4.
|
||||
- **Regenerate** now signals every registry run of the instance (launcher.mjs:772), with no N6 cut.
|
||||
It waits for an empty registry before starting, and a thread instance refuses regenerate at the verb.
|
||||
That's b7's behaviour, and it's right.
|
||||
- **Tests and mutants present.** T9a has four cli tests (:562, :597, :619, :670) and one bus test. T9b,
|
||||
T15 (bus and cli), N6 and the agents-browser flag are all there. BUILD.md lists M49-M56,
|
||||
M55-deadline, N6-widen, D7 and S6-done with named kill tests. The DESIGN.md test table names
|
||||
`launcher.test.mjs`, and the tests are in `lifecycle.test.mjs`. The b8 notes record that deviation,
|
||||
and I agree with it.
|
||||
|
||||
## Notes, non-blocking
|
||||
|
||||
N1. The clock caveat is narrower than BUILD.md says. The bus's `#now` (broker.mjs:524-526) is monotonic:
|
||||
`max(Date.now(), clock + 1)`. So after a clock step back, the end's `at` stays later than the launcher's
|
||||
spawn times, and the pass signals more runs, which was b7's breadth. It doesn't signal fewer. A run can
|
||||
be missed only when the clock steps back between its spawn and the verb, with no bus event in between to
|
||||
carry the clock forward. That's fine as it stands. BUILD.md could say which direction the error goes.
|
||||
|
||||
N2. `stops` is capped at 64 (broker.mjs:2330). A fire of an instance with more runs than that gets
|
||||
`invalid-request` from `lifecycleDone`. The request stays taken, the CLI reads outcome-unknown, and no
|
||||
killed or alive event is written, so the Console flags nothing. No business is near 64 runs of one
|
||||
instance today. Either raise the cap to the registry's real limit, or add one line to DEFERRED.
|
||||
|
||||
N3. The CLI `instances` output (instances.mjs:201) prints `running` and not `alive`. D7 binds only the
|
||||
Console, so a human on the CLI never sees "still running after fire". A follow-up row, not this one.
|
||||
|
||||
N4. An alive flag clears only on the run's `session.ended`. A run this host never spawned (the
|
||||
no-record case) has no exit handler here, so its flag stays until something writes `session.ended`. A
|
||||
false flag is the safe side per sage-2289. I'm noting it so nobody reads a lingering flag as a bug in
|
||||
the round.
|
||||
|
||||
N5. With r2, the Console says "still running after fire" for a stop that couldn't be confirmed, and the
|
||||
run may not be running. If a short change is cheap, "not confirmed stopped after fire" for
|
||||
`reason: unknown` would be more accurate. Cosmetic.
|
||||
|
||||
## On CLEAR
|
||||
|
||||
On my own scratch worktree at 899e30a80, with TMPDIR from `mktemp -d /mnt/storage/scratch/tmp/...`:
|
||||
- **Whole files, one at a time.**
|
||||
- bus: reload, chief-of-staff, lifecycle-verbs, lifecycle-revoke and channel-proof.
|
||||
- business: instance-settings and launch-settings.
|
||||
- cli: instances, lifecycle, launcher, launch-limit-checks, launch-limits, launch-routes,
|
||||
launch-account and thread-launch.
|
||||
- webui: agents, agents-browser and browser.
|
||||
- library: status and sync.
|
||||
- **Skips.** lifecycle.test skips when `sandboxProblem()` reports one (:33). I'll grep `^skip` and
|
||||
`# SKIP` and name every skip, because a skipped T9a isn't a pass.
|
||||
- **Mutants, with a blob check after each restore.**
|
||||
- B1 (a) and (b), and M-next.
|
||||
- From the D table: M2, M3, M16, M22 and M27.
|
||||
- S6: M49-M56 and M55-deadline (M52, M55 and M55-deadline edit 191's runtimes.mjs), plus N6-widen,
|
||||
D7 and S6-done.
|
||||
- The anchors are mine. I don't use Honker's runb8.sh.
|
||||
|
||||
## Final candidate 062375507 (honker-0098, sage-2290, sage-2291)
|
||||
|
||||
Checked at 2026-10-22T00:46Z (from `date -u`).
|
||||
- origin/honker/row194-b8 is 062375507, two commits on 899e30a80: 95c0e1220 (the N1-N5 notes) and 062375507
|
||||
(the N4 correction). Both are doc-only.
|
||||
- `git diff 899e30a80 062375507 -- packages` is empty. The changed paths are BUILD.md and
|
||||
docs/plans/DEFERRED.md. With the PRD draft left out, the manifest is still 53 paths.
|
||||
- **The DEFERRED line names what sage-2291 asked for.** The cap is 64 stops in `lifecycleDone`, and
|
||||
the refusal is `invalid-request`. The follow-up test is a done over the cap that refuses, while the
|
||||
runs it left alive still get the flag. Its consequence is right too. fireStop signals before the done,
|
||||
and `fire.done` is written in the verb, so only the events and the flag are lost.
|
||||
- **N1 as corrected in BUILD.md is still too strong.** "A run spawned before the verb can't fall
|
||||
outside it" holds only if some bus event moved `#clock` past the spawn. Nothing on the bus is written
|
||||
between a spawn and its bind, so a clock step back in that gap can still put the spawn after `at`.
|
||||
That's the narrow case from my N1. It's doc-only, so it doesn't block (decision 137), and it needs no
|
||||
new commit.
|
||||
- The static review carries to 062375507. All runs use 062375507.
|
||||
|
||||
## Rebased candidate 6411a31c0, origin/honker/row194-b9 (sage-2295, sage-2296, honker-0100)
|
||||
|
||||
Checked at 2026-10-22T01:08Z (from `date -u`).
|
||||
- 6411a31c0 is 062375507's 14 commits on Rocko's e6800380d, which is one test-only commit on eb3abba12
|
||||
(packages/discord/tests binding.test.mjs and recover.test.mjs, +2 each).
|
||||
- `git diff 062375507 6411a31c0 -- packages` is byte-for-byte `git diff eb3abba12 e6800380d`. Nothing
|
||||
outside packages changed.
|
||||
- `git range-diff eb3abba12..062375507 e6800380d..6411a31c0`: all 14 commits are `=`.
|
||||
- Against e6800380d the manifest is 53 paths without the PRD draft. None is under processing-control or
|
||||
packages/discord.
|
||||
- The static review carries to 6411a31c0. All runs use 6411a31c0.
|
||||
|
||||
## Rebased candidate b52b17514, origin/honker/row194-b10 (sage-2318, honker-0103)
|
||||
|
||||
Checked at 2026-10-22T01:59Z (from `date -u`).
|
||||
- b52b17514 is 6411a31c0's 14 commits on Rocko's 8e1f55c1d. 8e1f55c1d's parent is e6800380d, and it is a
|
||||
product fix, not a test-only one: webui processing.css and processing.js, plus processing-browser.test.mjs
|
||||
and processing-ui.test.mjs.
|
||||
- `git diff 6411a31c0 b52b17514 -- packages` is byte-for-byte `git diff e6800380d 8e1f55c1d`. Nothing
|
||||
outside packages changed.
|
||||
- `git range-diff e6800380d..6411a31c0 8e1f55c1d..b52b17514`: all 14 commits are `=`.
|
||||
- Against 8e1f55c1d the manifest is 53 paths without the PRD draft. None is under processing-control, and
|
||||
194 touches neither processing.js nor processing.css nor Rocko's two tests.
|
||||
- **Where the two meet.** 194 changes webui src/public/index.html (it adds `/agents.js` to the script
|
||||
line), and Rocko's processing-ui.test.mjs:66 reads index.html for the processing controls. Its asserts
|
||||
are on the processing nodes and the processing.js and processing.css tags, which 194 leaves alone, so I
|
||||
expect a pass. shell.test.mjs:40 also reads index.html with app.js and bus.js, and it wasn't on my list.
|
||||
- **Run list change.** I add webui processing-ui, processing-browser and shell to the whole files, which
|
||||
makes 23. The rest of "On CLEAR" stands.
|
||||
- The static review carries to b52b17514. All runs use b52b17514.
|
||||
|
||||
## Merge-scoped carry to b11 469f441e5, origin/honker/row194-b11 (honker-0105, sage-2330, mos-1160)
|
||||
|
||||
Checked at 2026-10-22T02:08Z (from `date -u`).
|
||||
- **Base.** eafcbd3e7 is Rocko's six 191 commits restacked on row 202's landing ddfb29af7. It is not a
|
||||
descendant of 8e1f55c1d. `git range-diff dcd8d4dda..8e1f55c1d ddfb29af7..eafcbd3e7` gives five `=` and one
|
||||
`!`. The `!` is commit 1 (9a7251403), and its only change is the scripts/mosaic dispatch line, which now
|
||||
carries 202's `access`. `git diff 8e1f55c1d eafcbd3e7 -- packages/processing-control` is empty, so the
|
||||
`stopRun` that S6 relies on hasn't changed.
|
||||
- **The 14 commits.** `git range-diff 8e1f55c1d..b52b17514 eafcbd3e7..469f441e5`: all 14 are `=`.
|
||||
- **b10 to b11.** `git diff b52b17514 469f441e5` touches the same 15 files as `git diff 8e1f55c1d eafcbd3e7`.
|
||||
The added and removed lines match file by file, and the only differences are hunk offsets.
|
||||
- **Tree.** `git merge-tree --write-tree b52b17514 ddfb29af7` (merge base dcd8d4dda) gives 510b79875. That
|
||||
tree differs from b11 only in scripts/mosaic. It has one conflict there, and b11 resolves it to
|
||||
`halt|resume|processing|...|thread|access`, which keeps both sides. 194's `instances|instance|veto` line
|
||||
(:32) is unchanged.
|
||||
- **Overlap with 202.** Only two paths are in both 202 and the 194 manifest: packages/cli/src/cli.mjs and
|
||||
scripts/mosaic. In b11's cli.mjs, 202's import is at :23 and its `if (verb === "access")` block is at :619.
|
||||
- **Manifest.** Against eafcbd3e7 it's still 53 paths without the PRD draft. None is under
|
||||
processing-control or access.
|
||||
- **Run list change.** 202's only test is packages/cli/tests/access.test.mjs. It reads cli.mjs and
|
||||
runtime.mjs, and 194 changes both, so I'm adding it. That makes 24 whole files. The mutant list stands.
|
||||
- The static review carries to 469f441e5. All runs use 469f441e5.
|
||||
|
||||
## Rebased candidate c13134eb1, origin/honker/row194-b12 (honker-0107)
|
||||
|
||||
Checked at 2026-10-22T02:33Z (from `date -u`).
|
||||
- 0f8235277 is two commits on eafcbd3e7: 4db8b5778, a one-line change in
|
||||
packages/webui/tests/processing-browser.test.mjs, and Rocko's packet BUILD.md.
|
||||
- `git diff 469f441e5 c13134eb1` is byte-for-byte `git diff eafcbd3e7 0f8235277`.
|
||||
- `git range-diff eafcbd3e7..469f441e5 0f8235277..c13134eb1`: all 14 commits are `=`.
|
||||
- Against 0f8235277 the manifest is 53 paths without the PRD draft. Neither of Rocko's two paths is in it.
|
||||
- The static review carries to c13134eb1. The run list (24 whole files) and the mutants stay as they are,
|
||||
and all runs use c13134eb1.
|
||||
|
||||
## b13 7609210a3 carry check (honker-0110, sage-2458)
|
||||
|
||||
Git reads only. No tests have run yet.
|
||||
|
||||
- The 14 commits sit on refactor 0fa7bf421, the landed 191. The base of b12 c13134eb1 was 0f8235277.
|
||||
- range-diff 0f8235277..c13134eb1 against 0fa7bf421..7609210a3 shows 14 of 14 `=`.
|
||||
- The series patch-id --stable is 086085a8dfe41a47cdaa0b12effa8d4542783637 on both sides.
|
||||
- The path list is identical, 54 paths.
|
||||
- The drift 0f8235277..0fa7bf421 covers 14 files. The only one 194 also touches is packages/webui/src/public/index.html. 191 changes lines 14 and 20-22, and 194 changes line 7 (adds agents.js to the script line), so the hunks are separate.
|
||||
- Tests that read index.html: agents.test (in 194, row 194 N5 at :214), processing-ui.test:66 and shell.test:40. None of them pins the script list. processing-ui checks the controls' placement and attributes, and shell checks the joined source. stack-release.sh drift is read only by the cli stack-* tests, and 194 touches none of those.
|
||||
- My run list stays at 24 files. processing-ui, processing-browser and shell are already in it since b10. Runs go on 7609210a3 at Sage's CLEAR, after Honker's runb8.
|
||||
|
||||
## b14 b565e589b carry check (honker-0113, sage-2493, sage-2498)
|
||||
|
||||
b14 is a carry of b13 with no blockers. These are git reads only. I did no runs, because the slot is Honker's.
|
||||
- 14 commits on refactor 23c3d6218.
|
||||
- range-diff 0fa7bf421..7609210a3 against 23c3d6218..b565e589b shows 14 of 14 `=`.
|
||||
- The series patch-id is 086085a8dfe41a47cdaa0b12effa8d4542783637 on both sides.
|
||||
- The path list is identical.
|
||||
- The drift 0fa7bf421..23c3d6218 (205 and 268, 24 files) meets 194 at two paths, broker.mjs and business.test.mjs. For both files, the +/- lines of 194's own diff are identical on the old and new bases. So 194's hunks are unchanged, and git applied them without touching 205's or 268's lines.
|
||||
- Whether the merged files run together is a matter for Honker's runs on b14, which Sage is gating.
|
||||
|
||||
## b15 ce7fd1b88 delta review (honker-0119, sage-2501/2503/2507)
|
||||
|
||||
Verdict: APPROVE, no blockers. These are git reads only, and nothing has run on b15.
|
||||
- b15 is b14 b565e589b plus two commits.
|
||||
- range-diff: commits 1-14 show `=`, plus new commits 15 (b73452287) and 16 (ce7fd1b88).
|
||||
- The series patch-id is 64303253f8e6. The path list is the same as b14's.
|
||||
- Neither new commit has an attribution line.
|
||||
|
||||
(a) b73452287, broker.mjs +4/-3:
|
||||
- The three function-argument checks (harnessOn, testerOn, roleFile) move to just after `invalid-business`, ahead of `this.#store` and every store read. Each check's text is unchanged. One new comment line goes above them, and the descriptive comments stay with their assignments.
|
||||
- Behaviour changes only when a bad function argument and a store failure occur together. The argument error now wins, and that is what T8b expects (chief-of-staff.test:868, `store: null`).
|
||||
- The only other pin on these messages is routes-verb.test:89, which uses a real store and is unaffected.
|
||||
|
||||
(b') ce7fd1b88, test-only:
|
||||
- T15 removes the exited reviewer stand-in's registry entry after `x.exited === 143`.
|
||||
- The refusal it avoids comes from business/src/launch.mjs:123, which refuses when a running session has no exact provider/model. The stand-in's entry is a fixture artifact, because the launcher's exit handler removes only the runs it started.
|
||||
- This is the same convention as N6 at lifecycle.test:763-764. standIn is unchanged.
|
||||
- I accept Sage's ruling of (b') over (b). Giving the stand-in entry a model would only move the refusal to the `{ sonnet: 1 }` limit.
|
||||
- The second remove in standIn's t.after is a no-op.
|
||||
|
||||
N1, non-blocking: no test pins the `testerOn must be a function` message. This is old, not new in b15.
|
||||
|
||||
Manifest digest 4aaa3316 (honker-0119, sage-2509): its 53 paths equal git's path list 23c3d6218..ce7fd1b88 minus the PRD draft, and every sha256 in it matches the blob at ce7fd1b88.
|
||||
|
||||
## b16 9cdc47d04 carry check (honker-0122, sage-2521)
|
||||
|
||||
b16 is a carry of b15 with no blockers. These are git reads only, and nothing has run.
|
||||
- 16 commits on ceadad993, which is origin sage/row279-carry-343b.
|
||||
- range-diff 23c3d6218..ce7fd1b88 against ceadad993..9cdc47d04 shows 16 of 16 `=`.
|
||||
- The series patch-id is 64303253f8e6, the same as b15. The path list is identical.
|
||||
- No commit has an attribution line.
|
||||
- The drift 23c3d6218..ceadad993 is 26 files, and none of them is a 194 path.
|
||||
|
||||
The drift does reach code that 194 runs. 279 changes processing-control/src/runtimes.mjs:333 in ownStop, from `while (now() <= deadline)` to `for (;;)`. Now the loop's only exit at the deadline is the final status check, which returns alive, killed, stopped or identity-changed. Before, the loop could fall through to `finish('unknown')`.
|
||||
- 194's S6 fire stop calls stopRun (lifecycle T9a/T9b, and the launcher's fireStop maps `unknown`). So the outcomes 194 sees at the deadline can change.
|
||||
- The whole runb8 rerun on b16 settles that. It is required, and a carry from b15's runs would not cover it.
|
||||
- M52, M55 and M55-deadline mutate this file. Before the mutants run, each anchor must still match runtimes.mjs at ceadad993 exactly once. The broker.mjs anchors were checked the same way at b15. 279 changed only line 333, so the anchors are probably intact, but the runner has to confirm it, not assume it.
|
||||
|
||||
Anchors (honker-0124): I spot-checked them on 9cdc47d04, whose runtimes.mjs is the same as ceadad993's. M52's line occurs once, M55/M55-deadline's ` let problem = null;` once, and the unknown return at :294 is there. Before the mutants run, runb8 asserts count==1 for each anchor. My b16 condition is met.
|
||||
|
||||
## b17 3f451772d carry (honker-0130), APPROVE
|
||||
|
||||
b16 9cdc47d04 was rebased onto b0fd28b6f (row 280 landed). I checked this from the canonical checkout.
|
||||
|
||||
- 3f451772d is honker/row194-b17. b0fd28b6f is its ancestor, with 16 commits on top.
|
||||
- `git range-diff ceadad993..9cdc47d04 b0fd28b6f..3f451772d` shows all 16 commits `=`.
|
||||
- The series patch-id is 64303253f8e6 on both bases.
|
||||
- The changed-path list is the same on both bases. The packages diff from b16 to b17 is only 280's three bus test files, and none is in 194's manifest. No 194 test or script mentions them.
|
||||
- DEFERRED.md is the only path where 280 and 194 overlap. 194's +/- lines there are byte-identical on both bases (same md5 over the diff lines).
|
||||
- The manifest file manifest-b17-3f451772d.txt has 53 paths and sha256 29f131ab...694afc. Its paths match `git diff --name-only b0fd28b6f 3f451772d`, minus PRD-1.8-DRAFT. Every blob matches `git show 3f451772d:path`. Of the manifest paths, only DEFERRED.md (f878b3de) differs from b16.
|
||||
- No attribution lines.
|
||||
|
||||
Verdict: APPROVE carries from b16 to 3f451772d. Once the review request naming 3f451772d is posted, I record it on the queue.
|
||||
|
||||
## b18 fd6dd327c carry (honker-0132, sage-2546), APPROVE
|
||||
|
||||
b17 3f451772d was rebased onto e122aad8e. The new base commits are 4c4b8559c (BUILD-LOG, SESSIONS) and e122aad8e (QUEUE.md, queue.json). Neither touches a 194 manifest path; I checked that before the rebase.
|
||||
|
||||
- fd6dd327c is honker/row194-b18. e122aad8e is its ancestor, with 16 commits on top.
|
||||
- `git range-diff b0fd28b6f..3f451772d e122aad8e..fd6dd327c` shows all 16 commits `=`. The series patch-id is 64303253f8e6.
|
||||
- The changed-path list is the same on both bases. `git diff 3f451772d fd6dd327c` lists only BUILD-LOG.md, docs/SESSIONS.md, QUEUE.md and queue.json, all from the base move.
|
||||
- The manifest file manifest-b18-fd6dd327c.txt has sha256 29f131ab..., the same digest as b17. Its 53 paths match the diff minus PRD-1.8-DRAFT, and every blob matches `git show fd6dd327c:path`.
|
||||
- No attribution lines.
|
||||
|
||||
Verdict: APPROVE carries to fd6dd327c. Once the request naming fd6dd327c is posted, I record it on fd6dd327c.
|
||||
@@ -0,0 +1,52 @@
|
||||
# Webby review: row 194 design r2 (#1692, agent lifecycle and per-agent veto)
|
||||
|
||||
Webby, 2026-10-20T19:31Z (from `date -u`). Independent reviewer. Author: Honker.
|
||||
Subjects at c0a35f47a on honker/row194, both sha256 verified: `agents/honker/work/queue-194/DESIGN.md`
|
||||
(313c206e...fc25) and `PRD-1.7-DRAFT.md` (98f4f49e...9708). I read the whole of both and the diff
|
||||
from 14a6f5f9d. Code read at 53bb59462. Static review. I ran nothing.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
One blocker. B3-B8, Q2 and N1-N8 from webby-0003 are fixed as the closing table says. I checked
|
||||
each against the text. B1 is fixed: hard waits, no grant, and a PRD 1.7 draft for the routed form.
|
||||
B2's floor is the gap.
|
||||
|
||||
## Blocker
|
||||
|
||||
B1. The hire floor is a deny list, and it misses actions a hireable definition can hold today.
|
||||
D2.3 refuses seven actions. role.mjs:40-43 already refuses the hard waits in both lists, so what is
|
||||
left is the routed set (vocabulary.mjs ROUTED). The floor doesn't name these:
|
||||
- `git.merge.protected`. `roles/merge-gate.json` holds it in crossRole and declares no credentials,
|
||||
so it passes D2.2 item 3 and the floor. Listed in `hireable`, the chief could hire its own merge
|
||||
gate. That is the same author-is-reviewer problem `review.verdict` is on the list for.
|
||||
- `decision.resolve.technical`. A hired holder resolves technical decisions, so the chief would pick
|
||||
who answers its own requests. `cto.json` is credentialed today, but a credential-free role
|
||||
holding it would pass.
|
||||
- `policy.change` and `prd.approve`. Both are routed. A credential-free role holding either passes.
|
||||
- `instance.name`. It was in my r1 B2 list, which Mos accepted (mos-1018), and it is missing here.
|
||||
Every action added to the vocabulary later is also hireable by default, which is the wrong
|
||||
direction for a fail-closed floor (AGENTS.md invariant 6).
|
||||
Fix: turn `HIRE_FLOOR` into an allowlist, `HIRE_ALLOWED`. A definition is hireable only when every
|
||||
action in withinRole and crossRole is on it. From the role files at 53bb59462, the list a working
|
||||
hire needs is `message.send`, `record.write`, `review.request`, `task.update.assigned`,
|
||||
`task.scope.change` and `git.push.working`. The last two only matter after row 199 (credentials).
|
||||
T8b and T8c then loop over `ACTIONS` minus the allowlist, one case each, so a new action is covered
|
||||
without a test edit. M30-M43 become "one action added to the allowlist in the broker" and "in
|
||||
role.mjs". Update the PRD draft's bound to match: "The chief hires only role definitions whose
|
||||
actions are all on a fixed list of working actions. Adding to that list is a PRD change."
|
||||
|
||||
## Q4 (for Sage, my view)
|
||||
|
||||
Start `hireable` empty. Under the hard-wait form Jason resolves each hire anyway. With the B1
|
||||
allowlist, today's credential-free definitions that pass are assistant and the five chief-officer
|
||||
roles (message.send and record.write only). assistant is END_PROTECTED. A first kind is a policy
|
||||
call and belongs in its own reviewed commit.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. D2.1 step 3 compares the call's arguments to "the decision body". Name the field-by-field
|
||||
comparison (business from the cap, name, definition, canonical vars). A body with extra keys should
|
||||
refuse, not be ignored.
|
||||
N2. The PRD draft's notes say "Replace" covers the model write. The REQ-COS-3 text itself says
|
||||
only "fire, replace or end". Put the model write in the requirement text, so a later reader of the
|
||||
PRD alone sees it.
|
||||
@@ -0,0 +1,37 @@
|
||||
# Webby review: row 194 design r3 (#1692)
|
||||
|
||||
Webby, 2026-10-20T19:32Z (from `date -u`). Author: Honker. Subjects at d0afda024 on honker/row194:
|
||||
DESIGN.md sha256 885fce0d...65fc, PRD-1.7-DRAFT.md sha256 04c32917...d728 (verified). Read as the
|
||||
diff from c0a35f47a, which my r2 review (REVIEW-r2.md, webby-0010) covered. r3 crossed with webby-0010
|
||||
and doesn't answer it yet. Static review.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
r2 B1 stands in part. r3 adds `instance.name` to the floor. `git.merge.protected` (merge-gate.json,
|
||||
crossRole, no credentials), `decision.resolve.technical`, `policy.change` and `prd.approve` still
|
||||
pass it, and so will any action added later. The fix is unchanged: an allowlist, as in REVIEW-r2.md.
|
||||
M47 and M48 fold into the allowlist mutants.
|
||||
|
||||
## New blocker
|
||||
|
||||
B2. Fire can't be "in the same transaction" as the delist. The delist is a business file commit
|
||||
through the organization writer (organization.mjs write: lock, rename, receipt). The claim revoke
|
||||
and the `lifecycle.cancelled` and `lifecycle.end` events are store rows. No transaction spans the
|
||||
two. Fix: name the order and the failure between them. I'd write the store side first (revoke,
|
||||
cancel, end), then commit the delist. If the delist fails, the runs are ended and I is still
|
||||
listed, so the chief can fire again, and that second fire must work with no claims left. The other
|
||||
order leaves a delisted agent with a live process and the model token, which is the F4 case. Test
|
||||
a writer failure after the store commit, and add a mutant that swaps the order.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. REQ-LAND-2 says "Gitea itself then refuses a push to upstream or to another agent's fork". That
|
||||
holds only if the agent's Gitea user has no write access to those repos. Gitea allows a push from any
|
||||
collaborator. The bound should be the host's check that the remote's owner is the agent's own user,
|
||||
made at each push. Gitea's refusal is the second layer. Megavolt is checking this text against row
|
||||
199, so I'm sending it there as a note, not a blocker here.
|
||||
N2. F4's 10 s recheck reads a pid. Use the same start-time check as the signal (launcher.mjs:526),
|
||||
so a reused pid isn't reported as the fired run.
|
||||
|
||||
The rest of r3 reads right: F4's reason (ENV_ALLOW, session.mjs:83-94), T9a, M49-M51, R7 and the Q4
|
||||
ruling.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Webby review: row 194 design r4 (#1692)
|
||||
|
||||
Webby, 2026-10-20T19:40Z (from `date -u`). Author: Honker. Subjects at 7d0788236 on honker/row194:
|
||||
DESIGN.md sha256 824ef884...3f34, PRD-1.7-DRAFT.md sha256 379cc318...05bc (both verified). Read as the diff
|
||||
from d0afda024 (my r3 review), with the role files at origin/refactor. Static review. I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
B1 (webby-0010 and webby-0011) is fixed. D2.3 is now an allowlist, `HIRE_ALLOWED`, with six actions. T8b and
|
||||
T8c loop over `ACTIONS` minus the list and the hard waits. There is a mixed case and a pin test, and M47/M48
|
||||
are "some instead of every". The table of today's files checks out against the role files. chief-of-staff,
|
||||
cto, merge-gate, pm, reviewer and security-reviewer each hold an action off the list. Coder's six actions
|
||||
are all on it, but coder is credentialed (2). The C-suite files and assistant hold only `message.send` and
|
||||
`record.write`. The PRD bound now names the fixed list and says a change to it is a reviewed commit.
|
||||
|
||||
B2 (webby-0011) is fixed. Fire runs A (store), then B (delist), then C (consume). A and B are safe to repeat,
|
||||
and T9c covers both failure points. M56-M58 kill the order swap, the early consume and the `not-held`
|
||||
refusal.
|
||||
|
||||
The r2 N1 and N2 items and the r3 N2 item are in. The SIGKILL escalation through row 191's stop, with one
|
||||
stop receipt and the halt join, reads right (T9a, T9b, M51-M55). V3 terminate stays SIGTERM-only (M54).
|
||||
|
||||
## Non-blocking, for the build
|
||||
|
||||
N1. Between a failed B and the repeat, I is still listed, so a launch of I passes (`launch` takes listed
|
||||
instances). A repeat's A would stop that run, but nothing makes anyone repeat the fire. I'd have `launch`
|
||||
refuse `instance-firing` while I has a `fire.stopped` with no `fire.done`, and add that case to T9c.
|
||||
N2. "Every action is on the list" is true of a definition with no action lists at all. `roles/researcher.json`
|
||||
is `roleVersion` 1 with no withinRole or crossRole, and the design counts it as passing. The load check's
|
||||
version 2 rule refuses it, but the hire-time check in the broker should require version 2 too. A role file
|
||||
edited to version 1 after load would then refuse, not pass the allowlist with no actions. Add it to T8b.
|
||||
@@ -0,0 +1,118 @@
|
||||
# Webby review: row 194 design r1 (#1692, agent lifecycle and per-agent veto)
|
||||
|
||||
Webby, 2026-10-20T19:14Z (from `date -u`). Independent reviewer. Author: Honker.
|
||||
Subject: `agents/honker/work/queue-194/DESIGN.md` at 14a6f5f9d on honker/row194, sha256
|
||||
eec9f011567d (honker-0003, which supersedes 8f80c278b from honker-0002; I read both and the diff).
|
||||
Brief blob a907109409ac. Static review only. I ran no tests and changed no product code.
|
||||
|
||||
Code base: `git archive 5d019d876`. `git diff --stat 5d019d876 53bb59462 -- packages adapters roles scripts`
|
||||
is empty, so every line below holds at the design's base. Paths are relative to `packages/` unless
|
||||
they start with `roles/`.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
Eight blockers. The verb split, the launcher-only start, the D3 reload through the boot loader and the
|
||||
`retired` list are sound. The chief's side (O1, hire) and the request lifecycle need work.
|
||||
|
||||
## Citations checked
|
||||
|
||||
These hold: broker.mjs:145 (ORGANIZATION_KEYS), :208, :446, :770-774, :781-788 (decisions bind to the
|
||||
role instance), :1601-1628; launcher.mjs:232-327 (check reloads the file at :233), :239, :527,
|
||||
:624-668; broker.mjs:542-546; tools.mjs:116-119; vocabulary.mjs:135; runtime.mjs:75-81, :121-133; config.mjs:88-105;
|
||||
business.mjs:117-132; instance-settings.mjs:19-31; serve.mjs:282-289, :535-538; session.mjs:245-270.
|
||||
|
||||
## Blockers
|
||||
|
||||
B1. O1 can't load as written. Adding `role.hire` and `role.fire` to ACTIONS alone makes them hard
|
||||
waits (vocabulary.mjs:52-54, REQ-WAIT-3; broker.mjs:43-69 keeps its own ROUTED). role.mjs:42 then
|
||||
refuses `roles/chief-of-staff.json`, because withinRole lists a GATED_ONLY action. O1 needs both
|
||||
ROUTED lists, and vocabulary.mjs:52-54 says an action leaves the hard waits only when a PRD version
|
||||
says so. So O1 is a PRD change, which is Jason's, not only Sage's and Mos's. Fix: restate Q1 with
|
||||
that. O2 needs only the ACTIONS entries and fits the brief's "verbs, not authority". My
|
||||
recommendation is O2 for this row, and O1 after a PRD version routes the two actions.
|
||||
|
||||
B2. A hire can still create authority, and `hireable` needs a schema. The r2 `hireable` list is
|
||||
a reviewed role-file entry, which helps. But the structural rules (D2.2) still refuse only
|
||||
credentialed, END_PROTECTED and version 1 definitions, and `roles/chief-of-staff.json` declares no
|
||||
credentials. A `hireable` list that names chief-of-staff, by mistake or later, lets the chief hire a
|
||||
second chief with role.launch, role.end, organization.change, instance.name, and hire and fire. Under
|
||||
O2 there's no list at all. Sage's example, `security-reviewer`, holds review.verdict
|
||||
(`roles/security-reviewer.json`), so Mos should rule knowing that hire would let the chief add
|
||||
verdict holders. Fix: hire refuses (`definition-not-hireable`) any definition whose withinRole holds
|
||||
role.launch, role.end, role.revoke, organization.change, instance.name, role.hire or role.fire,
|
||||
whatever `hireable` and `noHire` say. role.mjs validates `hireable` (a new role-file key: name it as a
|
||||
role schema change) and refuses such an entry at load. Test with chief-of-staff.
|
||||
|
||||
B3. Re-hire contradicts itself and carries decisions. The D2.1 table refuses a hire "when ... the name
|
||||
exists". The text below it (DESIGN.md:156-157) says a fired name comes back by a hire of the same
|
||||
name. If that re-hire can name another definition, approvals bound to the instance
|
||||
(broker.mjs:781-788) carry to a different role. That is the problem `retired` solves for delete.
|
||||
Fix: pick one. Either re-hire of a fired name keeps its definition and refuses another, or re-hire
|
||||
is refused. Test the chosen rule.
|
||||
|
||||
B4. The V2 request isn't exactly-once, and the human's brake lapses at start. The pass runs `check()`
|
||||
then `start()`. It skips authorizeLaunch (launcher.mjs:653), and `check()` never reads launch_state,
|
||||
so the only revoked check is at request time (V2 step 1). A `launch.revoke` inside the 60 s window
|
||||
doesn't stop the launch, against "applies to the human until they restore it". Separately, if
|
||||
`lifecycleDone` fails after `start()`, the next tick sees the request still open. `check()` refuses
|
||||
`instance-running` only while the run lives (launcher.mjs:239), so a short run can start twice.
|
||||
Fix: one host op takes the request (writes `lifecycle.taken`) and re-checks revoked, retired and,
|
||||
for a tester request, `bus.tester` (B8) in the same transaction, before `start()`. A taken request never starts again. Test a revoke between
|
||||
request and pass, and a `lifecycleDone` that throws once.
|
||||
|
||||
B5. Terminate and delete race a launch in flight. A run that is spawned but not yet bound holds no
|
||||
claim. V3 answers `not-held` for it, and an open V2 request for I still starts after a terminate.
|
||||
Delete's `instance-running` reads claims only. The workspace rename isn't serialized with
|
||||
`start()`'s mkdirSync (launcher.mjs:350), and the design doesn't say which process renames it or
|
||||
what happens when the file commit succeeds and the rename fails. Fix: terminate closes open
|
||||
requests for I as `request-cancelled`. Delete refuses `instance-starting` while a request is open or
|
||||
the launcher's registry holds I. The rename runs under the launcher's `serialize` as a host op, and
|
||||
the design states the order and the recovery after a failed rename. Test both races.
|
||||
|
||||
B6. The tester gets delete. D4.1 says the tester gets no delete, but TESTER_ORGANIZATION_VERBS
|
||||
holds `instances.set` (broker.mjs:103), and `/api/instances` lets a tester through without `send`
|
||||
(serve.mjs:537). Op `delete` rides `instances.set`, so the tester can delete. M18 covers `veto.set`
|
||||
only. Fix: the broker refuses op `delete` from a tester session with `tester-refused`. Add it to T12
|
||||
and a mutant.
|
||||
|
||||
B7. Keep doesn't cover the chief's model write. Through `organization.set` the chief changes an
|
||||
instance's model key (broker.mjs:1266-1269). On a kept instance that changes the agent the human
|
||||
chose to keep, at its next launch, which the chief controls. Fix: `organization.set` refuses
|
||||
`instance-kept` for a write that names a kept instance. Test it in T10.
|
||||
|
||||
B8. D4.1's live `bus.tester` check has nothing live to read. `bus.tester` is a business var
|
||||
(broker.mjs:544). No bus verb writes business vars: ORGANIZATION_KEYS (broker.mjs:145) and
|
||||
`instances.set` don't reach them. So it changes only by hand edit, and D3 item 6 says a hand edit
|
||||
needs a restart. The broker's live copy then sees Jason turn testing off only at the next restart,
|
||||
which is today's behaviour, and T12a's "a live write turns `bus.tester` off" has no write to run.
|
||||
Fix: each tester call reads `bus.tester` from the business file as it stands, as `harnessOn` does
|
||||
(runtime.mjs:136-142), and an unreadable file refuses `tester-off`. T12a turns it off with a file
|
||||
edit and no restart. M20 stays as written.
|
||||
|
||||
## Q2
|
||||
|
||||
Yes, keep should block the chief's `role.end` on a kept instance. End then launch is a replace
|
||||
(E6), and the limits (`#stops`) and the human's V3 still stop a runaway. Two things go with it.
|
||||
The block belongs in the reason branch only (broker.mjs:1607-1613). The decision branch
|
||||
(:1614-1615) cites a decision the human resolved, which is the human's own act, so it should still
|
||||
end a kept run. Say so and test it. B7 extends the same rule to the model write.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. D3 item 2 lists six verbs. `plugins.set` and `routes.set` commit too (runtime.mjs:128-130).
|
||||
Reload after every committed write in one place in the callback, so a later verb can't miss it.
|
||||
N2. `noHire` must name definitions present in `rolesDir` (D2.3). A reviewed commit that removes a
|
||||
role file would then stop every business that lists it from loading. A stale entry on a deny list is
|
||||
harmless, so accept unknown names, or check them at write only.
|
||||
N3. Hire takes `vars`. Agent-layer vars include `harness` and the fallback route (vars.mjs:142-147),
|
||||
which is more than the chief sets today through `organization.set`. Limit hire's vars to that set
|
||||
plus the narrowing `limits.*` keys.
|
||||
N4. D3 item 4 changes the existing `instances.set` op `change` on a running instance. Name the
|
||||
existing business and webui tests that change with it.
|
||||
N5. `agents.js` must join the static file list at serve.mjs:28 and `index.html`.
|
||||
N6. V4 waits for the old run's exit. A run that ignores SIGTERM lets the request expire at 60 s.
|
||||
State that, and whether anything escalates to SIGKILL.
|
||||
N7. Keep refuses END_PROTECTED instances, so the human can't keep the assistant from the chief's
|
||||
`role.end`, which Q169 allows. Say that this is intended.
|
||||
N8. With `human: true`, `check()` must still send `launch.by` through the `pm` branch
|
||||
(launcher.mjs:235). Add that case to T2.
|
||||
@@ -0,0 +1,79 @@
|
||||
# Webby review: row 197 implementation packet, round 2 (gate lanes)
|
||||
|
||||
Webby, 2026-10-20T20:44Z (from `date -u`). Author: Herb (herb-0023). Worktree herb-row197, branch herb/row197,
|
||||
parent a4e3e857e, source uncommitted. Round 1: REVIEW-impl.md at f942bd7d4 (webby-0029, CHANGES B1 B2).
|
||||
|
||||
Packet hashes I checked: BUILD.patch 0b3871bd...f43e, SOURCE.json 91ba149f...596b, gate.sh 48e06e23...fed1. All
|
||||
25 SOURCE.json rows match the worktree on sha256 and mode. The patch paths are the same 25 paths. The patch
|
||||
applies cleanly to a4e3e857e in a scratch worktree of mine, and the result is byte-identical to Herb's worktree
|
||||
for every path. I removed that worktree afterwards. BUILD-LOG.md has 25 added lines and no removed ones, outside
|
||||
the packet.
|
||||
|
||||
This was a static review. I ran no test, no suite and no Docker command, and I did not read the frozen checkout
|
||||
or Herb's other scratch roots. The counts below come from Herb's packet logs.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
B2 is fixed. `leaseContext` (gate-lane-fixtures.test.mjs:29-33) takes `MOSAIC_GATE_LANE` and `MOSAIC_GATE_STATE`
|
||||
when they're set, and every lease goes through the active lane's real lock dir. The other band is checked with
|
||||
arithmetic (:10), and no test reserves the other lane's ports. The exhaustion test (:36-48) listens only on a
|
||||
port it leased itself, and narrows `BANDS` only inside its own process. It restores `BANDS` in `finally`.
|
||||
review-t4-lane-a.log: 8/8, and lane a's lock namespace is the only one present.
|
||||
|
||||
B1 is fixed. Each manifest case now has a test, and BUILD.md's table maps them all:
|
||||
|
||||
- T1 (MANIFEST.md:119-130). gate-lane.test.mjs:66-70 tests canonical, builder and existing-output refusals.
|
||||
`validateTooling` has missing-file and changed-file tests (:71-81). The 92-byte socket example passes and
|
||||
108 bytes refuses (:82-87). Duplicate steps stay, and NODE_TEST_CONTEXT is cleared from `cleanEnv` and from
|
||||
both supervised steps (:88-99). Scoped port 9 addresses refuse (:100-103).
|
||||
- T2 (:147-151). Two real shell wrappers race (gate-lane-state.test.mjs:84-98). There is one start receipt,
|
||||
and the loser exits 2. The supervisor passes `verifyLocks`, the step child's /proc/self/fd has no lock
|
||||
target, and the inode is stable. `activeConflicts` is tested for same-checkout and for a legacy land gate on
|
||||
a free lane (:99-106). Recovery kills the proven child, a foreign child survives, a recovery killed during
|
||||
Docker cleanup stays quarantined, and a resumed recovery clears the lane (:107-124).
|
||||
- T3 (:171-174). Missing receipt, missing image and wrong release tag (gate-lane-image.test.mjs:46-50). A tag
|
||||
that changes between steps stops the next step, and no terminal rc 0 follows (:51-61). A rebuild with the
|
||||
same input digest gets a new ID, the old receipt refuses it, and cold and warm counters stay separate
|
||||
(:62-72). The tag is checked again after the last step (:73-80). runGate calls the same
|
||||
`executePinnedSteps` (gate-lane.mjs:184), so the test covers the real path.
|
||||
- T4 (:201-209). Two generated node test files take different leases in the same lane and state. The lock is
|
||||
held while the child runs and released after it exits, and the other lease stays held
|
||||
(gate-lane-fixtures.test.mjs:49-62). A SIGKILLed allocator frees its own lock and leaves a foreign one alone
|
||||
(:63-72). `fixtureCacheEnv` has a replacement-env test (:73-79).
|
||||
- T5 (:226). Another process appends under a fixture HOME while a supervised step is running, and attribution
|
||||
puts that change in `unrelated` (:80-93).
|
||||
|
||||
Counts from the packet logs: review-t1-resume 11/11, t2 14/14, t3 8/8, t4 8/8, all with 0 skipped and 0
|
||||
cancelled. I counted the test blocks in each file, and they match. The cleanup-order fix is in place:
|
||||
`ctx.cleanup` stops both children and waits for them before it removes `root`, and leaseContext's after hook runs
|
||||
callbacks before it removes the state root.
|
||||
|
||||
The round 1 notes are addressed. N1: `closed` is no longer bound (lane-fixtures.mjs:16). N2: TMPDIR is a `tmp`
|
||||
subdir (:32). N3: `%iface` is stripped before the port 9 match (lane-state.mjs:28), with a test. N4: unit roots
|
||||
use gt-, gf-, gs- and gi-, and README:84-85 says so. N5: README:84 documents the node_modules link. N6: BUILD.md
|
||||
records the overlap with row 200.
|
||||
|
||||
The existing-file edits are the same shape as round 1. They swap helpers and add env and the concurrency
|
||||
option. No assert or timeout line changed (`git diff -U0` grep). Hash pins: no test under packages/*/tests or
|
||||
scripts/test-*.sh pins a sha256 next to any of the 25 path names.
|
||||
|
||||
gate.sh matches the template shape. It skips pkg-queue, which script-queue covers. It runs every package and
|
||||
script with `--test-concurrency=2`, stops on the first failure, and runs `cleanupProject` on exit.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. Outside a lane, `leaseContext` falls back to lane a with a private gf- state root
|
||||
(gate-lane-fixtures.test.mjs:30). That is still the B2 shape. Suppose someone runs this file singly while a
|
||||
real lane-a gate holds closed leases. The exhaustion test can then take and listen on one of lane a's ports,
|
||||
because the private lock dir excludes nothing. A lane claim refuses beside a running `node --test`, but a single
|
||||
started after the claim isn't blocked by anything. Default to the real state root
|
||||
`/mnt/storage/scratch/mosaic-gate-lanes` when no lane is set, or document that singles of this file wait for
|
||||
`gate-lane.sh status` to show both lanes CLEAR.
|
||||
|
||||
N2. The band check at :10 compares literal numbers, not the imported `BANDS`, so it would pass whatever
|
||||
lane-state.mjs:7 says. Compare against `BANDS` (it's imported at :26).
|
||||
|
||||
N3. The T5 step child (:88) and the appender poll with no deadline. If the append never happens, the test hangs,
|
||||
and the runner has no timeout. Give both a bound, for example 5 s, and exit nonzero.
|
||||
|
||||
None of these block. Herb can fold them into the commit or record them as a follow-up.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Webby review: row 197 implementation packet, round 3 (gate lanes, N1-N3 delta)
|
||||
|
||||
Webby, 2026-10-20T20:53Z (from `date -u`). Author: Herb (herb-0026). Worktree herb-row197, parent
|
||||
a4e3e857e. Round 2: REVIEW-impl-r2.md at 1f404c973 (webby-0031, approve, N1-N3).
|
||||
|
||||
Packet hashes I checked: SOURCE.json 35847636...2aab, BUILD.patch 55fe390d...0fde, gate.sh 48e06e23...fed1
|
||||
(unchanged). All 25 SOURCE.json rows match the worktree. BUILD.patch applies to a4e3e857e in a temporary
|
||||
index, and every one of the 25 blobs equals the worktree file. I couldn't diff against the r2 files because
|
||||
I kept no copy. Instead, file mtimes show only two of the 25 paths changed after the r2 request (20:42Z):
|
||||
packages/cli/tests/gate-lane-fixtures.test.mjs and scripts/testing/README.md. That matches Herb's list. I
|
||||
read both in full where they changed.
|
||||
|
||||
This was a static review. I ran no test. Counts come from Herb's logs: review-r3-fixtures 8/8 and
|
||||
review-r3-lane-a 8/8, with 0 failed, skipped or cancelled and no `not ok` lines.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
N1 is fixed. `leaseContext` (gate-lane-fixtures.test.mjs:30) now defaults to the shared
|
||||
`/mnt/storage/scratch/mosaic-gate-lanes`, so a single run takes its leases through the same lock files as a
|
||||
live lane-a gate. It creates the root at 0700 if missing, the same mode lane-fixtures.mjs:20 uses, and no
|
||||
longer removes it. README:95-96 says so.
|
||||
|
||||
N2 is fixed. The band check (:8-10) reads the imported `BANDS`. The 127 widths stay literal, and that's
|
||||
fine because they pin the band size, which is the contract.
|
||||
|
||||
N3 is fixed. The appender (:87) and the step child (:89) each have a 5 s timer that exits 8. A missed
|
||||
append makes the step's rc nonzero or `appended` nonzero, and the asserts at :91 fail. README:97 says so.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. No test drives the 5 s path itself. It only guards against a hang, so I'm not asking for one.
|
||||
@@ -0,0 +1,84 @@
|
||||
# Webby review: row 197 implementation packet (gate lanes)
|
||||
|
||||
Webby, 2026-10-20T20:23Z (from `date -u`). Author: Herb (herb-0018). Worktree herb-row197, branch herb/row197,
|
||||
HEAD a4e3e857e, base aa1bd209e. The source is uncommitted. BUILD.patch sha256 6a1cd3e5...7792 and SOURCE.json
|
||||
d0f6333a...d6ed, both checked twice. All 25 SOURCE.json rows match the worktree on hash and mode, and the patch
|
||||
paths equal the SOURCE.json paths. Plan: MANIFEST.md r2, which I approved at ca6b32d66.
|
||||
|
||||
This is a static review plus two FD probes on node 26.8.1 in a private mktemp dir. I ran none of the new tests, no
|
||||
suite and no Docker command, and I did not read Herb's scratch roots.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
Most of the code matches the manifest. The existing-file edits change helpers and env only, and no removed line
|
||||
holds an assert or a timeout. The Docker guard turns every `compose run` into `--no-build --pull never` and refuses
|
||||
everything else except `info` and an inspect of the pinned tag. Cleanup goes by exact project label and checks
|
||||
each ID's label again before removing it. `compose.yaml` declares no named volumes, so containers and networks
|
||||
are everything a run can leave. The env allowlist drops `NODE_TEST_CONTEXT`. Status and recovery work from
|
||||
receipts, not from flock alone.
|
||||
|
||||
I had one open question about the lock FDs, and the probes settled it. A bash `exec 3>> 5>>` plus flock, then
|
||||
`exec node`, leaves node holding FDs 3 and 5, and a second `flock -n -x` on the lane file fails while node runs. A
|
||||
child started with `spawn(..., {stdio: ['ignore', fd, fd]})` lists only 0-2 in /proc/self/fd. That matches
|
||||
MANIFEST.md:156 ("ordinary child stdio does not inherit them").
|
||||
|
||||
## Blockers
|
||||
|
||||
B1. Several tests the manifest requires are missing. BUILD.md:25 says T1-T5 source is present. The manifest names
|
||||
these tests, and the packet doesn't have them:
|
||||
|
||||
- T1, MANIFEST.md:121, 127, 130. No refusal test for a canonical or builder checkout or an existing output. No
|
||||
92-byte socket test and no over-107-byte socket test (`socketInventory` has no test). No test that both candidates
|
||||
carry the approved tooling. No test that duplicates stay, and none that Node entries clear `NODE_TEST_CONTEXT`.
|
||||
- T2, MANIFEST.md:147-148, 151. No claim race with one winner, no same-checkout rejection, no inherited-FD test,
|
||||
no surviving-child test and no interrupted-recovery test. The legacy test (gate-lane-state.test.mjs:17) checks
|
||||
status only. Nothing shows that a claim refuses while a legacy gate runs and the flock is free.
|
||||
- T3, MANIFEST.md:171-174. No test for a missing receipt, a missing image, a tag changed mid-gate or a rebuild
|
||||
from a mutable tag. No cold/warm counter test beyond `builds === 1`.
|
||||
- T4, MANIFEST.md:201-203, 209. Leases are taken one after another, not from parallel test files. No test for an
|
||||
exhausted band, an occupied port, owner death or a foreign listener, and none holding a lease through child
|
||||
shutdown. No cache test for `fixtureCacheEnv`, although the harness tests now import it.
|
||||
- T5, MANIFEST.md:226. Attribution has a unit test with fixed entries. Nothing appends from an unrelated session
|
||||
while a run is in progress.
|
||||
|
||||
The inherited-FD test is cheap now. The probe above shows the shape: hold the lane lock through the wrapper, spawn
|
||||
a step child, and check that the child's /proc/self/fd has no lock file and that the lock stays held after the
|
||||
child exits. Either add the missing tests, or list each one in BUILD.md as dropped with a reason and get Sage's ruling.
|
||||
A test the plan requires is part of the change, the same rule Sage applied to row 110 N1.
|
||||
|
||||
B2. gate-lane-fixtures.test.mjs:5-11 leases from a private state root, in both bands, and then listens on the port
|
||||
it got. That test runs inside the pkg-cli step of every lane gate. The private root's lock files don't exclude the
|
||||
real leases under `/mnt/storage/scratch/mosaic-gate-lanes/ports/<lane>`, and both leasers go upward from the
|
||||
band's first port (lane-fixtures.mjs:22). Here is how it fails in a paired proof. Lane a holds a closed lease on
|
||||
61000, with no listener, which is how closed endpoints work (MANIFEST.md:206). Lane b's cli step runs this test.
|
||||
The test's probe of 61000 succeeds, and :10 listens on 127.0.0.1:61000. Lane a's closed endpoint now accepts
|
||||
connections, and that test fails. The same thing happens inside one lane, because two test files run at once.
|
||||
This is the cross-lane interference the bands exist to prevent. Fix: when `MOSAIC_GATE_LANE` is set, lease only
|
||||
through the run's own lane and state. Check the other band's range with plain arithmetic instead of a lease. Drop
|
||||
the listen at :10, since no assertion reads it.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. `leasePort` ignores `closed` (lane-fixtures.mjs:16). Every call site passes it, and gate-lane-fixtures:5 is
|
||||
named after it. Use it, for example by recording the lease kind for evidence, or drop it.
|
||||
|
||||
N2. `fixtureCacheEnv(root)` points TMPDIR at the config dir itself (lane-fixtures.mjs:32). Temp files from the
|
||||
claude and pi children then land next to the fixture config they test. A `tmp` subdirectory keeps them apart. I
|
||||
didn't trace whether any assertion lists that directory.
|
||||
|
||||
N3. The port 9 check compares exact address strings (lane-state.mjs:28). `ss` prints scoped forms such as
|
||||
`127.0.0.53%lo`, and this host shows `%lo` and `%tailscale0`. A listener on `127.0.0.1%lo` or `0.0.0.0%lo`
|
||||
covers 127.0.0.1:9 but doesn't match. Strip `%iface` in `hostFacts` (:22) before comparing.
|
||||
|
||||
N4. The new tests make roots like `/mnt/storage/scratch/ga-XXXXXX` (gate-lane.test.mjs:12,
|
||||
gate-lane-state.test.mjs:46, 66). That is the run-root prefix gate-lane.mjs:201 uses, and inside a lane they sit
|
||||
outside the run's own temp root. If a test crashes, its leftover looks like a lane root, and README:41-42 tells
|
||||
operators not to remove another run's resources. Use a different prefix such as `gt-`.
|
||||
|
||||
N5. gate-lane.test.mjs:20 symlinks `/mnt/storage/src/mosaic-stack/node_modules` into the fixture, which ties the
|
||||
test to the canonical checkout's tree. That is fine on dragon-lin, but please write it down in "Not tested" or as
|
||||
an assumption.
|
||||
|
||||
N6. Coordination: row 200 (Launchpad, approved, round 2 open) also edits packages/cli/tests/stack-release.test.mjs.
|
||||
It adds T56 and fixtures, and this row replaces its `freePort` calls. Whichever row lands second has to rebase
|
||||
and rerun that file singly. Neither manifest says so.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Webby review: row 197 manifest r2 (#1695)
|
||||
|
||||
Webby, 2026-10-20T19:44Z (from `date -u`). Author: Herb. Subject: `agents/herb/work/queue-197/MANIFEST.md` at
|
||||
bdc9cd56a, sha256 2a1a3ad0...7cec (verified). Read as the diff from 84f21923c (my REVIEW-manifest.md), against
|
||||
aa1bd209e. Static review. I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
B1 is fixed. P2 keeps every literal Board `:9` URL, and `closedEndpoint` is gone from T4. The two leases left
|
||||
are real close-then-rebind cases. preview.test.mjs:208-209 and serve.test.mjs:99-100 each bind a probe on port
|
||||
0, close it and use the freed port as the dead board. Another lane's fixture could take that port in the gap,
|
||||
so a lease belongs there. T1 refuses a listener covering 127.0.0.1:9, a missing inventory or
|
||||
`ip_unprivileged_port_start` at 9 or below, and makes no connection to the endpoint.
|
||||
|
||||
N1 is in P3. Sage's order (sage-1384: 197 lands first, 198 rebases onto it before its manifest) makes the
|
||||
two P3 lines row 198's to replace when it removes the board URL from those tests. The BUILD-LOG change is one
|
||||
appended correction entry.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Webby review: row 197 manifest (#1695)
|
||||
|
||||
Webby, 2026-10-20T19:39Z (from `date -u`). Author: Herb. Subjects on herb/row197: `MANIFEST.md` at 84f21923c,
|
||||
sha256 16768433...b23b, and `TIMING.md` at fa820fed6, sha256 148a1416...52bd (both verified). TIMING's commit
|
||||
sits on top of the manifest's. I read both whole, against DESIGN.md at 6ad25d745 (my r2 APPROVE) and the base
|
||||
aa1bd209e. Static review. I ran nothing.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
T1-T5 cover the design. My r2 N1 is in T5: metadata-only listings, with assertions only on lane-attributed
|
||||
entries. TIMING.md copies Sage's table and says what it is not: a controlled solo baseline. The 25 values sum
|
||||
to 659 s, as stated.
|
||||
|
||||
## Blocker
|
||||
|
||||
B1. Drop the dead Board `:9` replacements from P2. Port 9 is not a resource the two lanes share. On this host
|
||||
`net.ipv4.ip_unprivileged_port_start` is 1024, so no test, lane or seat can bind 127.0.0.1:9. A connection
|
||||
there is refused at once in both lanes. Leasing a closed endpoint for it adds isolation for nothing, and it
|
||||
touches 20 files at aa1bd209e (`git grep -l "127.0.0.1:9'"`: 17 under webui/tests plus stack-copy,
|
||||
launch-routes and bus tasks). I approved R9 in the design. That was a miss on my part, because R9 only allows
|
||||
the change "where compatible", and nothing here needs it.
|
||||
The row 198 rev 3 design also rewrites those same lines, replacing the board URL with a dead-board context.
|
||||
Whichever row lands second would redo the other's edit.
|
||||
Fix: remove the `:9` rows from P2 (the settings-form/proof/bus/views/tester-listener row, the browser and
|
||||
tester-fixture row, the dead-Board parts of the tester-cli/proof-cli and stack-copy rows). Keep the leases
|
||||
where a test closes a port and binds it again (control-board serve, stack-release freePort, service closedPort,
|
||||
serve.test :100 if it rebinds). Add a T1 preflight refusal for a listener on 127.0.0.1:9, so a host where
|
||||
something does listen there fails loudly instead of quietly turning those tests into connects to a live
|
||||
service.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. Tell Sage that row 198 edits `stack-copy.test.mjs`, `pinned-copy.test.mjs` and the webui serve/preview tests
|
||||
too. Even with B1, those are the files where the two rows meet.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Webby review: row 197 (#1695), queue round 2, merge
|
||||
|
||||
Webby, 2026-10-21T07:18Z (from `date -u`). Author: Herb (herb-0034). Request comment 29961 (revs 2722-2723,
|
||||
posted as jarvis, sage-1815). This round only checks the merge (sage-1760, sage-1776). Round 1 was APPROVE
|
||||
on manifest 6318f0d0 over base aa1bd209e (comment 29665). Candidate: manifest digest
|
||||
8119a6082d3bb3150874dbe45410ca4830bcbb3887d71bce8d1edbe67df47ce3, 32 paths, on herb/row197-r4-234
|
||||
304c67753. Base c771a36f4 is its ancestor, and c771a36f4..304c67753 is that one commit.
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- I took the manifest text from `queue show 197` (round 2), not from Herb's packet. It hashes to the
|
||||
digest above, and each path's sha256 from `git show 304c67753:<path>` matches its line.
|
||||
- Against round 1's manifest, 22 paths have the same hash, 3 changed and 7 are new. The new ones are
|
||||
Herb's agents/herb/work/queue-197 packet files. `git diff --name-only c771a36f4 304c67753` names only
|
||||
manifest paths.
|
||||
- Base drift: of round 1's 25 paths, only stack-release.test.mjs, webui service.test.mjs and
|
||||
scripts/test-discord.sh have a different blob at c771a36f4 than at aa1bd209e. Those are the 3 changed paths.
|
||||
- For each of those three, the -U0 +/- lines of aa1bd209e to round 1 are identical to the -U0 +/- lines of
|
||||
c771a36f4 to 304c67753. Round 1's tree came from a commit whose files match all 12 tracked lines of
|
||||
round 1's manifest (2767b8854). Each file at 304c67753 is the landed blob plus Herb's own lines, so
|
||||
every landed hunk survives. The changed lines are the two `leasePort` imports, the one-line
|
||||
freePort and closedPort bodies, PI_CODING_AGENT_DIR in the discord sandbox, and the
|
||||
MOSAIC_TEST_CONCURRENCY branch in node_tests. No assertion or timeout changed.
|
||||
- T46 (stack-release.test.mjs:913) keeps row 234's `host_grants` and `hosts` entries in the untimed list.
|
||||
- File modes are the same as round 1's: gate-lane.sh and test-discord.sh 100755, the rest 100644.
|
||||
- Overlap check (sage-1815): `git diff --name-only c771a36f4 c515273db` names 13 paths and none is in the
|
||||
manifest. So row 240's landing doesn't touch this candidate.
|
||||
|
||||
Herb's slot run on 304c67753 was 559 pass, 0 fail, no skips (r4-granted/RESULTS.md). I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
The merge keeps both sides, with the same change lines as the approved round 1. Round 1's notes stand as
|
||||
recorded. `leasePort` still drops the `closed` option at lane-fixtures.mjs:16, the way round 1 accepted it.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Webby review: row 197 (#1695), queue round 3 (r5), merge
|
||||
|
||||
Webby, 2026-10-21T08:13Z (from `date -u`). Author: Herb (herb-0036). Request comment 29981
|
||||
(sage-197-request-5, revs 2750-2751, posted as jarvis, sage-1846). This round only checks the merge past
|
||||
the 228 and 238 landings. Round 2 was APPROVE on manifest 8119a608 at 304c67753 over c771a36f4
|
||||
(rev 2724, comment 29961). Candidate: manifest digest
|
||||
69fad4e7ed8f9e67268378b356893cbee81e106942ffc894a9652320218c8e5e, 32 paths, on herb/row197-r5
|
||||
9aa6d52d9. That commit's one parent is e7078e6a8.
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- I took the manifest text from `queue show 197` (round 3), not from Herb's packet. It hashes to the
|
||||
digest above, and each path's sha256 from `git show 9aa6d52d9:<path>` matches its line.
|
||||
- It has the same 32 paths as round 2's manifest. The only line that changed is
|
||||
packages/cli/tests/stack-release.test.mjs.
|
||||
- That file is blob 7398247ce983, which is what `git merge-file` of 304c67753 and 238's candidate
|
||||
4c90099ea over c771a36f4 gives with no conflict. I ran that preview before Herb built r5 (webby-0063).
|
||||
At e7078e6a8 the file is 238's candidate blob, and the -U0 +/- lines of e7078e6a8 to 9aa6d52d9 are
|
||||
identical to round 2's c771a36f4 to 304c67753: the two `leasePort` imports and the one-line freePort.
|
||||
238's hunk at :1686 in T57 and row 234's T46 `host_grants` and `hosts` entries are kept.
|
||||
- The other 31 paths have the same blob and mode in 9aa6d52d9 as in 304c67753. e7078e6a8..9aa6d52d9
|
||||
touches no path outside the manifest.
|
||||
- Overlap: the refactor tip is 4ced5e63d. e7078e6a8..4ced5e63d is one sessions commit that changes one
|
||||
path, docs/SESSIONS.md, which isn't in the manifest.
|
||||
|
||||
Herb's slot run of stack-release.test.mjs on 9aa6d52d9 was 196/0 in 501 s (sage-1846). I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
r5 is round 2's approved code on e7078e6a8, and the drifted path is only stack-release.test.mjs.
|
||||
Round 1's notes stand as recorded. The two-lane proof driver is a separate review (sage-1846) and isn't
|
||||
part of this verdict.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Webby review: row 197 (#1695), queue round 4 (r6)
|
||||
|
||||
Webby, 2026-10-21T09:29Z (from `date -u`). Author: Herb. Request comment 30004 (sage-197-review-4b,
|
||||
sage-1896, mos-1162). The last approved state is round 3, r5 9aa6d52d9, manifest 69fad4e7 (rev 2755,
|
||||
comment 29981). Candidate: manifest digest
|
||||
1556cd296be0527697793b626ce7ae7f94de437a4fe4dee8a5714d7db434fc82, 33 paths, on herb/row197-r6
|
||||
df0c59608. That commit's one parent is fefd54e13.
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- I took the manifest text from `queue show 197` (round 4), not from Herb's packet. It hashes to the
|
||||
digest above, and each path's sha256 from `git show df0c59608:<path>` matches its line.
|
||||
- It is round 3's 32 paths plus agents/herb/work/queue-197/r6-OVERLAP.json. Four paths differ in blob
|
||||
or mode from 9aa6d52d9: gate-lane.mjs, gate-lane.test.mjs, Herb's BUILD.md and the new
|
||||
r6-OVERLAP.json. The other 29 have the same blob and mode.
|
||||
- No manifest path changed between e7078e6a8 (r5's base) and fefd54e13. fefd54e13..df0c59608 touches
|
||||
no path outside the manifest.
|
||||
- packages/bus/tests/process.test.mjs is identical at fefd54e13 and df0c59608, as intended (row 243
|
||||
owns the chmod).
|
||||
- Overlap: the refactor tip is 38b83ea5c. fefd54e13..38b83ea5c is one sessions commit, and none of its
|
||||
paths is in the manifest.
|
||||
|
||||
Herb's focused run on the granted slot: gate-lane.test 13/0, process.test 8/0 (sage-1892). I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
The one code change is scripts/testing/gate-lane.mjs:46. Each step now spawns
|
||||
`/bin/bash -c 'umask 022; exec "$@"' gate-step <argv...>`. On Sage's three questions:
|
||||
|
||||
1. Argv quoting is kept. `gate-step` fills `$0`, so `"$@"` is exactly step.argv, with no word
|
||||
splitting or globbing. Arguments after the `-c` string are positional, so bash doesn't parse a
|
||||
leading `-` in them as an option. `node` and `bash` resolve through the step env's PATH, as
|
||||
spawn did before.
|
||||
2. Exit status and signals are kept. `exec` replaces bash in the same pid, so `close` reports the
|
||||
test's own code or signal. The process is still the detached group leader, so `process.kill(-pid)`
|
||||
in abort (:49) and cleanProcesses still reaches the whole group. identity() reads pid and
|
||||
/proc starttime (lane-state.mjs:33), and exec doesn't change either, so the recorded `child` owner
|
||||
still matches under sameProcess. A missing command now exits 127 from bash. That's the same rc as
|
||||
the old spawn `error` path (:51), and bash's message lands in the step log.
|
||||
3. The supervisor keeps 077. gate-lane.sh sets `umask 077` at :3 and execs node at :26. The new umask
|
||||
is set only inside the bash child. The step log is opened by the supervisor with mode 0600 before
|
||||
the spawn (:44). The supervisor's other writes give explicit modes: the out and bin dirs 0700,
|
||||
docker.json 0600, the guard shim 0700 (:177-181), the state root 0700 (:89). mkdtemp roots are 0700.
|
||||
The new tests check the child's mask (022), a 0644 child file, the parent mask after the run (077),
|
||||
the 0600 log, and gate-lane.sh's 0700 state dir and 0600 locks.
|
||||
|
||||
The step env still comes from cleanEnv (:109-115). It keeps no BASH_ENV, ENV, SHELLOPTS or BASHOPTS,
|
||||
so the non-interactive bash sources nothing.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N8. bash adds PWD, SHLVL and `_` to the env the test process sees. That's closer to the legacy
|
||||
shell-run environment, not further from it, and nothing in cleanEnv depends on their absence.
|
||||
|
||||
N9. Files a step makes with 022 are 0644. Under the 0700 temp root and out dir they stay private. In
|
||||
the scratch checkout they get the same modes a legacy shell run would give.
|
||||
@@ -0,0 +1,79 @@
|
||||
# Webby review: row 197 (#1695), queue round 5 (r7)
|
||||
|
||||
Webby, 2026-10-21T11:29Z (from `date -u`). Author: Herb. Request comment 30036 (rev 2819, sage-1958,
|
||||
posted by Sage as jarvis). Conditions are from sage-1955. Round 4 was APPROVE on r6 df0c59608
|
||||
(rev 2773, webby-0068). Its proof then stopped at solo-a script-release, because Compose 5.5.1's
|
||||
`run` has no `--no-build` (#1695 c30031). I missed that in round 4, since an argv-only test can't
|
||||
show that a flag exists. Candidate: manifest digest
|
||||
4e54024aacfb01dda7631b562db754d92a8bd1ffe4106e9262908883a72b45c3, 35 paths, on herb/row197-r7
|
||||
9c3166915. That's one commit on b1fece6f0, which is on origin/refactor.
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- I took the manifest text from `queue show 197` (round 5). It hashes to the digest above, and each
|
||||
path's sha256 from `git show 9c3166915:<path>` matches its line.
|
||||
- b1fece6f0..9c3166915 touches only manifest paths.
|
||||
- Against r6 df0c59608, five paths differ in blob or mode: lane-docker.mjs, gate-lane-image.test.mjs,
|
||||
Herb's BUILD.md, and the new r7-no-build.mutant.patch and r7-OVERLAP.json. The other 30 have the
|
||||
same blob and mode, as Herb says.
|
||||
- Base drift: fefd54e13..b1fece6f0 changes none of the 35 paths. On those 30, the -U0 +/- lines over
|
||||
b1fece6f0 equal r6's over fefd54e13.
|
||||
- Overlap: the refactor tip is 335bb3e30. b1fece6f0..335bb3e30 changes none of the 35 paths.
|
||||
- I read the lane-docker.mjs and test diffs, the mutant patch, Herb's r7 BUILD.md section, the
|
||||
docker.json writer (gate-lane.mjs:172, :178) and the proof driver's temp-root cleanup
|
||||
(herb/row197-proof run-proof.mjs).
|
||||
|
||||
Herb's focused run on grant sage-1956: red 9/2 on the r6 guard (`unknown flag: --no-build`, plus the
|
||||
missing helper), green 11/0, mutant 0/1 killed with the same error, then restored 11/0 (BUILD.md). I
|
||||
ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
On Sage's four conditions:
|
||||
|
||||
1. The guard owns the files. For `compose run`, guardDocker returns `compose -f <checkout>/compose.yaml
|
||||
-f <tempRoot>/compose-no-build.yaml run --pull never --no-deps ...` (lane-docker.mjs:36-37).
|
||||
composeFiles checks the imageId is `sha256:` plus 64 hex characters and the checkout is absolute
|
||||
(:6-10). A caller's option before the service must be one of `--rm -T --no-deps --interactive -i`
|
||||
(:35). So `-f`, `--file`, `--file=`, `--project-directory`, `--build`, `--pull` and `--no-build`
|
||||
there refuse. A top-level `compose -f ...` or `compose --project-directory ...` refuses, because
|
||||
argv[1] isn't `run` (:33, :39). The test at gate-lane-image.test.mjs:120-126 checks all of them.
|
||||
The record the guard reads has checkout, tempRoot and imageId from the receipt (gate-lane.mjs:172).
|
||||
That closes the r6 failure class for the record fields.
|
||||
2. The override goes at `shortRoot(run.tempRoot)/compose-no-build.yaml` (:9). shortRoot needs an
|
||||
owned, private scratch dir of 30 bytes or less (lane-state.mjs:10-16). The file is written `wx`
|
||||
at 0600 to a nonce name, then hard-linked into place. EEXIST keeps the existing file (:15-19). It
|
||||
is reopened O_NOFOLLOW, and anything other than a regular file owned by us, at mode exactly 0600,
|
||||
with the exact bytes, refuses (:20-27). The test checks 0600, a reuse with the same content, a
|
||||
changed file and a symlink. The symlink case leaves the target untouched. Cleanup: the proof driver
|
||||
removes a passed gate's temp root with `rmSync(..., {recursive:true})` after its ownership checks,
|
||||
and keeps a failed one. The override sits inside that root.
|
||||
3. The config check runs the installed binary: `docker compose -f ... -f ... config --format json`
|
||||
on a copy of the candidate compose.yaml, with made-up env and auth and an empty DOCKER_CONFIG
|
||||
(:103-119). It asserts that `services.mosaic-agent` has no `build` key and that `image` equals
|
||||
run.imageId. It also checks `--no-deps` and `--pull never`. The fixture imageId stands in for the
|
||||
receipt's. In a real run, the guard takes it from docker.json, which gate-lane.mjs:172 fills from
|
||||
receipt.imageId.
|
||||
4. The parser test (:93-101) puts `--help` after `run` and before the rewritten options and SERVICE,
|
||||
so the installed Compose parses every flag and starts nothing. The mutant patch puts `--no-build`
|
||||
back at :37 only. Herb's red run and the mutant run both show `unknown flag: --no-build` from that
|
||||
test, which is the fault the r6 proof hit.
|
||||
|
||||
compose.yaml has one service, mosaic-agent, with `build:` and `image: ${MOSAIC_IMAGE_TAG...}`, and the
|
||||
repo has no compose.override.yaml. With `build: !reset null` and `--pull never`, `run` can't build or
|
||||
pull. A missing image fails.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N10. No test starts a container. The parser and config tests show that Compose accepts the argv and
|
||||
merges to an image-only service. They don't show that `run` with `--pull never` starts from an
|
||||
`image:` that is a bare `sha256:` ID. Docker accepts full IDs, so I expect it to work, but that's
|
||||
the step r6 failed at. Sage's fresh D5 proof is the evidence for it, and it counts only if it gets
|
||||
past solo-a script-release.
|
||||
|
||||
N11. `-f` sets the project directory to the candidate checkout, whatever the caller's cwd, and turns
|
||||
off compose.override.yaml auto-loading. Every current caller runs from the checkout:
|
||||
mosaic-task.mjs:397-399 (`cwd: PROJECT_ROOT`), hello.sh:24, verify.sh:45 and agent.sh:208. So nothing
|
||||
changes today. A future caller run from a copy would get the candidate's compose.yaml, not the copy's.
|
||||
|
||||
N12. A failed gate's kept temp root still holds the override. It contains only the image ID, so
|
||||
nothing secret is left behind.
|
||||
@@ -0,0 +1,59 @@
|
||||
# Webby review: row 197 proof driver, a5b636750 (proof 4 watcher change)
|
||||
|
||||
Webby, 2026-10-21T15:19Z (from `date -u`). Author: Herb (herb-0090). Requested per sage-2029, sage-2034
|
||||
and sage-2035, after mos-1174. This is a review of the proof driver only. It isn't a queue round, and
|
||||
197's candidate stays r8 c45ddeb6b (manifest 80dc1468, my round 6 approval, rev 2867). Candidate:
|
||||
herb/row197-proof a5b636750, one commit on b8461266c.
|
||||
|
||||
What I checked, without runs:
|
||||
- b8461266c..a5b636750 changes exactly 3 paths under agents/herb/work/queue-197/proof/:
|
||||
run-proof.mjs (sha256 998f41948355ea86169c1711324a3c9e6cf4bdbe5157dcae9e78e414c1c16342, as Herb
|
||||
says), events.test.mjs (new, 7a40bb46ed4e) and BUILD.md (5ede409c4480). All three are 100644.
|
||||
- None of 197's 37 manifest paths is in that range. The branch sits on an older base (9aa6d52d9), so
|
||||
its tree holds older copies of the lane scripts. That doesn't matter, because the driver imports
|
||||
every lane module from `config.launcherRoot` (run-proof.mjs:85-89). It also refuses a launcher
|
||||
whose HEAD isn't `config.launcherCandidate` or whose checkout is dirty (:81-82).
|
||||
- I read the whole diff and r8's cleanupProject (lane-docker.mjs:41-52 at c45ddeb6b).
|
||||
|
||||
Herb's grant sage-2035 results (by BUILD.md and herb-0090): events.test.mjs 8/0, node --check passes,
|
||||
and one real-daemon probe with r8's cleanupProject saw all four events within 309 ms. Both objects
|
||||
were removed, and the post-probe listings and /proc scan were empty. I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
- The defect. Proof 3's watcher filtered both event types by the Compose project label in one
|
||||
stream. Docker 29's network events don't carry that label, so the watcher never saw a network
|
||||
create, and the evidence check reported missing creation evidence.
|
||||
- Network events (:12-14). The network stream filters only on type and event (:26-27), with no label
|
||||
filter. dockerEvent keeps a network only if its name starts with `<project>_`, so `<project>x_...`,
|
||||
`<project>-...` and the bare project don't match (the test at events.test:15-21). A network that
|
||||
does carry a foreign project label throws, which fails closed. The project is
|
||||
`mg197-[ab]-<12 hex>` (:7), checked before any watcher starts (:21). A foreign network would have
|
||||
to use our random id to match.
|
||||
- Container events (:15). These still need the exact project label, which the stream also filters
|
||||
(:27). A labelled container whose name falls outside the project throws "name outside" (:16),
|
||||
the same rule as the older recorded-name guard in logObject (:109).
|
||||
- Shared code. The proof's ensureWatchers now calls the same watchDockerProject (:116). The final
|
||||
cleanup awaits each watcher's bounded stop (:232), which SIGKILLs after 1 s and records it
|
||||
(:33-37). Wrapping the body in runProof and running it only on direct execution (:264) lets
|
||||
the test import the module without starting a proof.
|
||||
- The preflight (:39-64, :209-211). It runs after the image check and before wave 1, and a failure
|
||||
stops every wave. It creates one labelled network and one container from the pinned imageId with
|
||||
`--pull never`, `--network none` and no start. The test asserts no `start` or `build` argument
|
||||
(events.test:46). Cleanup always runs in `finally`, through r8's label-checked cleanupProject,
|
||||
which accepts the bounded invoke function (lane-docker.mjs:43). Both objects carry the project
|
||||
label, so cleanupProject finds and removes them. All four events must arrive within the 5 s
|
||||
deadline, and a late event counts as missing (:59, and the test at events.test:57-60). The result
|
||||
goes into summary.json and docker-summary.json.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. If the probe's own cleanup fails, a refusal or a timeout, the proof stops through `fail()`, but
|
||||
the `finally` cleanup loop (:231) covers only knownRuns, not the probe project. Leftover probe objects
|
||||
would stay until someone removes them by hand. The failure is loud and watcher-probe-plan.json
|
||||
(:209) names both objects, so nothing goes unseen. Adding the probe project to that loop would make
|
||||
the cleanup retry once.
|
||||
|
||||
N2. Herb asks Sage to update proof-phase.sh's digest pin to 998f4194...6342. That file isn't in this
|
||||
commit, so I haven't checked the pin. Sage should check it against the run-proof.mjs hash before
|
||||
proof 4.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Webby review: row 197 (#1695) two-lane proof driver, round 2
|
||||
|
||||
Webby, 2026-10-21T08:31Z (from `date -u`). Author: Herb (herb-0053). Requested by Sage (sage-1858,
|
||||
sage-1864); not a queue candidate. Scope: B1 and N3 only, from `git diff 8ae9d8acd b8461266c`. Subject:
|
||||
commit b8461266c on herb/row197-proof, a fast-forward from 8ae9d8acd. run-proof.mjs sha256 is
|
||||
ace376a9a333..., which I checked. The diff touches run-proof.mjs (+39 -14) and adds proof/BUILD.md. I
|
||||
didn't review BUILD.md beyond noting it now exists. Static review, I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
B1 is fixed. The `finally` takes each temp root from the run's start record in knownRuns. It never
|
||||
scans scratch (run-proof.mjs:174-193). Before it removes anything:
|
||||
- The path must match `^/mnt/storage/scratch/g[ab]-[A-Za-z0-9]{6}$` with the run's own lane.
|
||||
- lstat must show an owned real directory, not a symlink, and its realpath must equal the recorded
|
||||
path.
|
||||
- The gate's owner process must be gone, by `sameProcess` with the start time and boot id. No live
|
||||
process may be in one of the run's recorded `child` groups or have the temp root or checkout in its
|
||||
argv. If the process inventory fails, the root is kept and the run fails.
|
||||
- The last terminal record must say clean. Otherwise the root is kept and counted as a cleanup error.
|
||||
|
||||
A passed gate's root is then removed (:190). A failed gate's root is kept for diagnosis. Every outcome
|
||||
(`removed`, `kept` with a reason, or `error`) goes to `tempRoots` in docker-summary.json (:194), and any
|
||||
error sets exit 1. The launcher helpers it calls exist at 9aa6d52d9 with the fields it reads:
|
||||
processInventory gives `group`, `state` and `argv` (lane-state.mjs:48-50), and gates record
|
||||
`{kind:'child',group}` (gate-lane.mjs:46).
|
||||
|
||||
N3 is fixed. The handlers now go in at :9, before the config read, the launcher imports and any
|
||||
write. `abortOnSignal` is a no-op until :97 makes it abortActive. Preparation moved inside the `try`
|
||||
(:142). Each clone first yields with `setImmediate` and checks `interrupted`, so a pending signal
|
||||
ends preparation through `catch` and `finally`.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N5. Kept roots are left by design: failed gates, and runs whose cleanup wasn't verified. After reading
|
||||
the evidence, Sage removes each `kept` path listed in docker-summary.json `tempRoots`.
|
||||
|
||||
N6. One small orphan path remains in the launcher, not the driver. gate-lane.mjs makes its temp root
|
||||
at :162 but writes the start record at :172 and installs its signal handlers at :174. A SIGTERM from
|
||||
the driver's abort in that window kills the gate before any record exists, and the driver can't know
|
||||
the root. It's an empty or near-empty `g<lane>-` dir in scratch. That only happens if one lane fails
|
||||
while its partner is still validating at wave start.
|
||||
@@ -0,0 +1,78 @@
|
||||
# Webby review: row 197 (#1695) two-lane proof driver
|
||||
|
||||
Webby, 2026-10-21T08:22Z (from `date -u`). Author: Herb. Requested by Sage (sage-1850); not a queue
|
||||
candidate. Subject: commit 8ae9d8acd on herb/row197-proof, parent 9aa6d52d9, adding
|
||||
agents/herb/work/queue-197/proof/run-proof.mjs (176 lines, sha256 b8bb3df6..., which I checked) and
|
||||
config.example.json. I read the driver whole and the launcher code it calls at 9aa6d52d9: gate-lane.sh,
|
||||
gate-lane.mjs runGate and cleanEnv, lane-docker.mjs guardDocker and cleanupProject, lane-state.mjs
|
||||
shortRoot, and lane-evidence.mjs summarizePairs. Static review, I ran nothing.
|
||||
|
||||
Sage named proof/BUILD.md for the procedure. No such file exists at 8ae9d8acd, and
|
||||
agents/herb/work/queue-197/BUILD.md has no proof procedure, so I reviewed against the code and Sage's
|
||||
four questions.
|
||||
|
||||
## Verdict: CHANGES (one blocker)
|
||||
|
||||
### Sage's four questions
|
||||
|
||||
1. Stops at the first failure: yes. A 100 ms monitor reads new bytes in each gate's out/*.log and stops
|
||||
at the first failure line (run-proof.mjs:107, :116). A gate that exits nonzero sets `firstFailure`
|
||||
(:151). Either way the driver sends SIGTERM to every active gate, then SIGKILL after 30 s (:98-101).
|
||||
The wave check (:154) throws, so no later wave starts. Each wave also requires both lanes CLEAR before
|
||||
it starts (:149) and after it ends (:157), and every gate must end with rc 0, clean, no image build and
|
||||
no required skip (:131-132). A SIGINT, SIGTERM or SIGHUP sets `interrupted` and aborts the same way
|
||||
(:140-141).
|
||||
2. Cleanup: containers and networks yes, temp dirs no (B1). The gate cleans its own project
|
||||
(gate-lane.mjs:190, :192). The driver's `finally` then inventories each project it saw, runs
|
||||
cleanupProject again, and fails if anything is left (:165). It also needs create evidence for a
|
||||
container and a network in each project (:172). That covers the failure and signal paths, because the
|
||||
gate's SIGTERM handler aborts its detached step group and runs the same cleanup. compose.yaml uses
|
||||
bind mounts only, and lane-docker.mjs allows only `compose run`, so no named volume can be created.
|
||||
3. Writes and reads: the driver's own writes are under runRoot (plan, clones, state, out, logs,
|
||||
docker-objects.jsonl, summaries), with one exception, B1. It reads the config, the image receipt, the
|
||||
launcher modules and the canonical repo through `git clone --local`. Its Docker inspect formats read
|
||||
only the name and the compose project label (:77), and it saves no raw events. It reads no secret and
|
||||
no seat scratch. Gate steps get cleanEnv's allowlist (gate-lane.mjs:108-112), not the driver's
|
||||
environment.
|
||||
4. Concurrency and isolation evidence: enough. summarizePairs refuses unless each pair has two lanes
|
||||
and two candidates, pair 2 swaps lanes, and the later start is before the earlier end
|
||||
(lane-evidence.mjs). summary.json keeps each gate's terminal receipt with startMs and endMs. Each run
|
||||
has its own clone, its own mkdtemp temp root, its own `mg197-<lane>-<nonce>` project, the lane's port
|
||||
band, home-before/after attribution and a socket inventory. docker-objects.jsonl records each Docker
|
||||
object against its project.
|
||||
|
||||
### Blocker
|
||||
|
||||
B1. Every gate leaves its temp root in /mnt/storage/scratch, outside runRoot, and nothing removes it.
|
||||
The driver passes no `--temp-root` (run-proof.mjs:122), so runGate makes
|
||||
`mkdtempSync('/mnt/storage/scratch/g<lane>-')` (gate-lane.mjs:162). It removes that dir only when
|
||||
validation fails (:170). On a pass, a failure or a signal, it stays, with whatever TMPDIR, jiti and
|
||||
XDG cache files the steps wrote into it. The proof's eight gates leave eight of these dirs. Seven
|
||||
`ga-*`/`gb-*` dirs from earlier runs are already in scratch (I listed names only). The driver can't put
|
||||
the temp roots under runRoot, because shortRoot refuses any path over 30 bytes
|
||||
(lane-state.mjs:13). Fix: in the `finally`, take each run's `tempRoot` from its start record in
|
||||
knownRuns. Check it matches `^/mnt/storage/scratch/g[ab]-[A-Za-z0-9]{6}$` and is owned by the current
|
||||
user, then `rmSync(..., {recursive:true})` it. Record each removal, or its error, in
|
||||
docker-summary.json, and fail on any error. Do this after the gate has exited and the process check
|
||||
has run. An alternative is to have the driver mkdtemp short roots itself, pass `--temp-root`, list them
|
||||
in plan.json, and remove them the same way.
|
||||
|
||||
### Non-blocking
|
||||
|
||||
N1. Each of the 8 runs makes a `--no-hardlinks` clone under runRoot. The repo's .git is 421 MB, so
|
||||
together they can take a few GB. /mnt/storage has 206 GB free. They aren't evidence, so the driver
|
||||
could remove the `wt` dirs at the end, or Sage can remove them after reading the evidence.
|
||||
|
||||
N2. The failure regex also matches any log line that starts with `FAIL` and whitespace (:90, :107). A
|
||||
passing step that prints such a line would stop a good proof. That fails safe but costs a rerun. If that
|
||||
happens, read the preserved log before treating it as a test failure.
|
||||
|
||||
N3. Before line 141 the driver has no signal handlers. That covers the 8 clones and the tooling and
|
||||
image checks, so a Ctrl-C there kills the driver with no Docker objects yet and leaves only a partial
|
||||
runRoot. A SIGKILL of the driver mid-wave leaves gates running. Use `gate-lane.sh recover` then.
|
||||
|
||||
N4. Outside the driver: compose.yaml defaults PI_AUTH_FILE to the host's ~/.pi/agent/auth.json, mounted
|
||||
read-only, and cleanEnv neither keeps nor sets PI_AUTH_FILE. I didn't trace whether any gate step
|
||||
reaches `compose run mosaic-agent` without its own PI_AUTH_FILE fixture. The approved gate already
|
||||
behaves this way and the driver doesn't change it, so it isn't part of this verdict. Sage may want it
|
||||
checked before calling the proof secret-free.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Webby review: row 197 (#1695), queue round 6, Herb's r8
|
||||
|
||||
Webby, 2026-10-21T12:16Z (from `date -u`). Author: Herb. Request comment 30054 (revs 2865-2866,
|
||||
sage-1989, posted by Sage as jarvis). Merge scope per mos-1162. The last round was r7 9c3166915,
|
||||
manifest 4e54024a, which I approved as webby-0074 (rev 2822). Its D5 proof stopped in pkg-cli. Candidate:
|
||||
manifest digest 80dc14686fe9f916d8420f7c946b327b14a3f081f75b4def704c60b1b4e7bbae, 37 paths, on
|
||||
herb/row197-r8 c45ddeb6b. That's one commit on 5c6f73ed7.
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- I took the manifest text from `queue show 197` (last round), not from the scratch path. It hashes to
|
||||
the digest above, and each path's sha256 from `git show c45ddeb6b:<path>` matches its line.
|
||||
- 5c6f73ed7..c45ddeb6b touches exactly the 37 manifest paths.
|
||||
- r7 against r8, by `git ls-tree` blob and mode for each path: 33 paths are the same. Two change,
|
||||
gate-lane-image.test.mjs and BUILD.md. Two are new, r8-OVERLAP.json and
|
||||
r8-record-lookup.mutant.patch. That's 35 r7 paths, with 33 unchanged, not 35. Herb's BUILD.md
|
||||
gives the same count. The production guard, lane-docker.mjs, has the same blob as in r7.
|
||||
- Base drift: b1fece6f0..5c6f73ed7 (r7's base to r8's) changes none of the 37 paths, as
|
||||
r8-OVERLAP.json says.
|
||||
- Overlap: refactor is now f8276c5eb. 5c6f73ed7..f8276c5eb changes queue-218's BUILD.md,
|
||||
SESSIONS.md and the two case2 sheets, none of them in the manifest.
|
||||
- I read the test diff, the mutant patch, the r8 BUILD section, and the record writer and shim in
|
||||
gate-lane.mjs and lane-docker.mjs.
|
||||
|
||||
Herb's slot checks (herb-0078, 12:09:28Z, by BUILD.md): the whole file is 11/0 on a normal PATH and
|
||||
11/0 with the real guard shim first on PATH plus a private record. The mutant fails the argv probe
|
||||
with the shim's undefined-path error, and the restored file is 11/0 again. I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
- The cause. composeFixture's env (gate-lane-image.test.mjs:100-101) keeps the lane's PATH, whose first
|
||||
entry is `<out>/bin` (gate-lane.mjs:113), but drops MOSAIC_GATE_DOCKER_RECORD. So `docker` hit the
|
||||
shim, and the shim's readFileSync(undefined) (lane-docker.mjs:57) exited 2. That matches the r7
|
||||
proof stop.
|
||||
- The fix. installedDocker() (test:87-93) reads the record named by the test process's own
|
||||
MOSAIC_GATE_DOCKER_RECORD, which the lane sets (gate-lane.mjs:112). It takes `.docker` and asserts
|
||||
it's an absolute string. The lane writes that field from `which docker` at gate-lane.mjs:170,
|
||||
before it creates the bin dir (:177) or writes the record (:178). That's the same field the shim
|
||||
itself executes (lane-docker.mjs:58). Outside a lane it keeps `docker`. Only the two read-only
|
||||
probes use it, `--help` at :109 and `config` at :120. The fixture env is unchanged, and no guard or
|
||||
allowlist code changes.
|
||||
- Other callers. With a positive control at r7 (2 hits), no other test under packages/*/tests spawns
|
||||
`docker` by name. The `docker info` checks in test-task.sh and test-release.sh inherit the full lane
|
||||
env, record included, so the shim can read it.
|
||||
- Sage's question, whether the fix can pass with the shim missing from PATH. It can, and it should.
|
||||
Without the shim, both `docker` and the record's path reach the real binary. So a pass by itself
|
||||
proves nothing. The proof is the mutant: with the lookup removed, the argv probe fails with the
|
||||
shim's undefined-path error. That error comes only from lane-docker.mjs:57, so the run had the shim
|
||||
first on PATH. In the D5 proof, the lane always puts the shim first (gate-lane.mjs:113), so pkg-cli
|
||||
there tests the fix for real.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. The mutant receipt in BUILD.md names only the argv probe ("installed-Compose argv regression
|
||||
0/1"). The config probe at :120 uses the same helper and should fail the same way, but no count shows
|
||||
it. The D5 proof's pkg-cli step covers both probes under the shim, so no rerun is needed for this.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Webby review: row 197 design r2 (#1695)
|
||||
|
||||
Webby, 2026-10-20 (from `date -u`). Author: Herb. Subject: `agents/herb/work/queue-197/DESIGN.md` at
|
||||
6ad25d745 on herb/row197, sha256 732ee0e2 (verified). Read as the diff from r1 (85a6eb0a9), plus
|
||||
both new probe logs. Static review. I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
B1 is fixed. R4 moves the root to `/mnt/storage/scratch/ga-XXXXXX`, and preflight refuses `/tmp`,
|
||||
anything under it, and roots over 30 bytes. D3's socket example uses that root (92 bytes). D4.2 adds
|
||||
a sandboxed launcher single with a base control and separate wrapper tests for both refusals. The
|
||||
r2 logs show the `bus start --pm:` single passing 1/1, no skips, on base and tree with 30-byte roots.
|
||||
That test starts the real sandboxed runner. R6 now cites sandbox.mjs:112,115.
|
||||
|
||||
My r1 example root was wrong: `/mnt/storage/scratch/mg-a-XXXXXX` is 32 bytes, as Herb measured.
|
||||
|
||||
N1-N5 are in: the bands in R8 with a startup refusal, HOME in R11 and the D4.2 sentinels, the
|
||||
reproducibility note in D3, the land-gate status test in D2, and keeping paired results after
|
||||
adoption in D4.
|
||||
|
||||
## Non-blocking, for the manifest
|
||||
|
||||
N1. The `~/.claude`, `~/.pi` and `~/.cache` sentinels will change during any gate, because other
|
||||
sessions on the host write there (every Claude Code session appends to `~/.claude/projects`). An
|
||||
"unchanged" assertion would fail for reasons that have nothing to do with the lanes. Compare only
|
||||
entries that a lane could have made: names that hold the lane nonce, the run's fixture prefixes, or
|
||||
paths under the lane root. Or record the listings as evidence without asserting on them.
|
||||
@@ -0,0 +1,66 @@
|
||||
# Webby review: row 197 design (#1695, two gate lanes)
|
||||
|
||||
Webby, 2026-10-20 (from `date -u`). Independent reviewer. Author: Herb.
|
||||
Subject: `agents/herb/work/queue-197/DESIGN.md` at 85a6eb0a9 on herb/row197, sha256 64a429a3
|
||||
(verified). Brief `docs/plans/2026-10-20_second-gate-lane.md`. Static review. I ran no suite and no
|
||||
test, and I read the three probe logs without rerunning them. Code read at the design's base
|
||||
c6f528a32. Host facts read with `/proc/sys/net/ipv4/ip_local_port_range` and `ss -Hltn`, read only.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
One blocker. The inventory is careful and most of it holds. The temp root it picks breaks the
|
||||
sandboxed runner tests on this host.
|
||||
|
||||
## Citations checked
|
||||
|
||||
These hold: common.sh:24-34 (load_release), test-release.sh:10-15 (RELEASE backup and restore),
|
||||
mosaic-task.mjs:321 (Compose inherits the whole environment, so `COMPOSE_PROJECT_NAME` reaches it),
|
||||
:395-397; hello.sh:24; agent.sh:208; release.sh:122; compose.yaml has no top-level `name` or
|
||||
`container_name`, and no script passes `-p` or `--name`, so R3's project isolation works.
|
||||
Containerfile:18-25 copies exactly the inputs D3 fingerprints. No `.dockerignore` exists.
|
||||
claim.mjs:50-55 and cohort.mjs:111 name scopes from the random claim; the impostor test
|
||||
(cohort.test.mjs:484) reuses its own recorded unit. No literal nonzero `listen()` in any src or test
|
||||
(R7). thread-launch.test.mjs runs its adapter inside bwrap (:4, :32), so the adapter's
|
||||
`$HOME/.claude/projects` write (fixtures/thread-adapter.mjs:14-20) lands in the run's home/.
|
||||
|
||||
One miss: R6 cites sandbox.mjs:118-120 for the private /tmp. At c6f528a32 the binds are :112
|
||||
(the run directory) and :115 (`<runDir>/tmp` onto `/tmp`).
|
||||
|
||||
## Blocker
|
||||
|
||||
B1. R4's temp root under `/tmp` hides every sandboxed run directory. sandbox.mjs:112 binds the run
|
||||
directory at its own path, then :115 binds `<runDir>/tmp` over `/tmp`. With `TMPDIR=/tmp/mg-a-XXXXXX`,
|
||||
fixture data roots and run directories sit under `/tmp`, so the second bind hides them inside the
|
||||
sandbox. bwrap then fails with `Can't chdir to /tmp/mosaic-launch-.../launches/acme/<run>`, and the
|
||||
launcher reports `runner ... did not start`. Seats have hit this three times: row 140 (9 of 18
|
||||
launcher tests), row 149 r3 (10 of 19) and a control on the base each time. D3's own socket example
|
||||
(`/tmp/mg-a-ABCDEF/mosaic-cli-ABCDEF/data/launches/...`) is one of these paths. R6's "Host TMPDIR
|
||||
must not be ... used as a socket path visible only on the host" names the risk but R4 walks into
|
||||
it. The length limit is real too: row 177 lost 24 of 396 cli tests with a 43-byte TMPDIR (session.mjs
|
||||
refuses `tools.sock` past 107 bytes), and passed at 30 bytes. Today's gates inherit
|
||||
`TMPDIR=/mnt/storage/scratch/tmp` (21 bytes), and Sage's gate template sets none.
|
||||
Fix: put each lane's temp root outside `/tmp` and at 30 bytes or less, for example
|
||||
`/mnt/storage/scratch/mg-a-XXXXXX` (30). Preflight refuses a temp root that resolves under `/tmp`
|
||||
or is longer than the measured limit. Redo D3's socket example with that root (the tools.sock path
|
||||
grows from 78 to 92 bytes), and add a sandboxed launcher test to the D4.2 single-file probes, run
|
||||
with the lane root, with a base control.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. Name R8's bands. Below 32768 the host listens on 3000, 3001, 3080, 3773 (T3), 5556, 6463, 6789,
|
||||
7332 and 7333 (Console and tester), 8001-8888, 9443, 20050 and 26400. Nothing listens above 60999,
|
||||
and `ip_local_reserved_ports` is empty. Use `61000-61127` for a and `61128-61255` for b, and have the
|
||||
startup check refuse any band that overlaps `ip_local_port_range` or a live listener.
|
||||
N2. R11 leaves HOME as the real home for both lanes. Nothing I found writes there outside a sandbox
|
||||
(claim.test.mjs:667-673 asserts a refusal, the stack-release stand-ins get a scratch HOME from the
|
||||
rehearsal, seat tests pass HOME to bwrap only). Say that in R11, and add `~/.claude`, `~/.pi` and
|
||||
`~/.cache` listings to D4.2's foreign sentinels so a new test that writes there shows up.
|
||||
N3. The Containerfile builds `FROM node:24-bookworm-slim` by tag and runs `apt-get update`, so two
|
||||
builds of identical files differ. D3 already relies on the pinned image ID. Say outright that
|
||||
"identical inputs" means identical files plus the same pinned ID, never a rebuild.
|
||||
N4. The production landing gate names its Compose network after the worktree (git-socket.mjs:239-246)
|
||||
and drops lane variables (landing.mjs:63-90). R17 treats it as LEGACY BUSY, which is right. Add a
|
||||
status test where a land gate runs beside a lane gate.
|
||||
N5. Three paired runs (six gates) is a small sample for flakes that show up one run in ten. Keep six
|
||||
as the bar for adoption, and record every later paired gate's result in the lane receipts so Sage
|
||||
sees the rate as it builds.
|
||||
@@ -0,0 +1,73 @@
|
||||
# Row 198 build early read (4b036e632), Webby
|
||||
|
||||
Request row198-r2-4b036e632 (Bushroot, 16:46Z 10-22). Commit 4b036e632 on bushroot/row198-r2, local, untested,
|
||||
parent b79ad5f2c. Read against the approved design (DESIGN.md rev 5, 3f71b359b, REVIEW-r5.md) and my last-fetched
|
||||
origin/refactor aca16404c. I can't fetch during Gander's slot, so the tip may be newer. Git and reading only. I ran
|
||||
nothing.
|
||||
|
||||
This isn't the manifest round. The row's gate is INVENTORY.md, then the manifest. This read is for the build.
|
||||
|
||||
## Verdict: CHANGES (B1-B5)
|
||||
|
||||
The source shape matches the design:
|
||||
- `createBoard` keeps the pinned order (write, bump, scan), and `reply()` bumps only after a real send.
|
||||
- The tmux checks and the reply run through `execFile` with a timeout. No path `createBoard` reaches calls
|
||||
`spawnSync`.
|
||||
- The Console's board writes need `--send` and the public Host, and run after the Host, origin, sign-in and tester
|
||||
gates. `body()` enforces `application/json` (serve.mjs:32).
|
||||
- `markSeen` validates its fields and refuses a `/` in `project`, so a null or odd body is a 400.
|
||||
|
||||
The gaps are base drift, one unbuilt design item, and the test plan.
|
||||
|
||||
## Required changes
|
||||
|
||||
B1. Rebase. b79ad5f2c is 188 commits behind aca16404c. `git merge-tree` reports conflicts in
|
||||
`packages/webui/src/cli.mjs` and `packages/webui/src/serve.mjs`. `stack-copy.sh`, `stack-copy.test.mjs`,
|
||||
`browser.test.mjs` and `chat.test.mjs` also changed on both sides, though they merge cleanly. Rows 192 and 197 are
|
||||
done, so the row note's "rebase onto 197's landing" applies now. I'll re-read both source files after the rebase.
|
||||
|
||||
B2. Design item 8 isn't built. The commit doesn't touch `scripts/stack-release.sh` or `stack-release.test.mjs`.
|
||||
There's no `parses_flag`, no `--board` drop in `console_for_target`, and no rollback keep, so M12 and M13 have
|
||||
nothing to kill. The new cli.mjs refuses `--board`. A pointer or rollback target that still carries the flag would
|
||||
stop the Console at re-pin. Build it as designed, or get Sage's ruling to drop it and say so in BUILD.md.
|
||||
|
||||
B3. Most of the approved new tests are missing, so most mutants have no killer:
|
||||
- `retired.test.mjs` checks (2) and (3) are missing: no `node:http` import or `listen(` in
|
||||
`packages/control-board/src`, and no `control-board serve` or 7331 starter in scripts, units, READMEs or
|
||||
package.json. These kill M2 and M9. Check (1) is board.test.mjs:76.
|
||||
- The `board-inprocess.test.mjs` cases are missing:
|
||||
- zero `fetch` calls (M3)
|
||||
- each board route giving 401 without the header and 403 with a foreign Host in preview mode (M6, M7)
|
||||
- no mark file after a refused `/api/seen` (M5)
|
||||
- the positive control, where `--send` and the public Host run `exec` once with the expected argv (M10)
|
||||
- `/healthz` answering within 200 ms while `/api/board` is pending (M14)
|
||||
The webui serve test covers only the no-`--send`, non-preview refusal.
|
||||
- The `pinned-copy.test.mjs` fresh-copy case, `/api/board` 200 from a copy, is missing (M11). The diff there only
|
||||
drops `--board` from the argv.
|
||||
- The design's markSeen join case says "The same check covers `reply()`". board.test.mjs has no `reply()` join case.
|
||||
|
||||
Write the tests the design names, or map each mutant to the test that kills it in BUILD.md, so the M1-M15 table
|
||||
holds.
|
||||
|
||||
B4. Keep the stale-registration reply refusal. It's a fail-closed check: a seat whose pid is dead gets a 409, and no
|
||||
text goes to its pane (old serve.test.mjs:811). Production keeps the check in `scan`. Add `isPidAlive` to
|
||||
`createBoard`'s options and pass it to `scanAsync`. It's for test injection only, and `scan` already takes it. Then
|
||||
port the case to the `reply` refusals test at serve.test.mjs:264. The bad-JSON case moves to the Console: POST
|
||||
`{not json` with `--send` and the public Host, expect 400 and zero captures. That's `boardSend`'s own `JSON.parse`
|
||||
branch. Dropping the page.html and proxy tests is fine.
|
||||
|
||||
B5. control-board serve.test.mjs:94-98, "serve rejects a non-numeric --port", now passes for the wrong reason.
|
||||
`parseArgs` throws RETIRED before it reads `--port`. Delete it, since board.test.mjs:76 covers the refusal, or assert
|
||||
the retired text.
|
||||
|
||||
## Notes (non-blocking)
|
||||
|
||||
N1. BUILD.md says "the tester socket runs with `board: null`". That's true only in proof.test.mjs. cli.mjs passes
|
||||
`board` to `startTesterSocket`, so the tester listener serves `/api/board` and the conversation reads in-process.
|
||||
That matches today, where the tester proxied to the same board. Writes still refuse at the tester gate before the
|
||||
board block. Correct the BUILD.md line.
|
||||
|
||||
N2. The docs (webui README, control-board README, CURRENT.md) aren't in the commit. The M9 starter grep from B3
|
||||
fails until the README stops telling people to run `control-board serve`, so the docs belong in the same candidate.
|
||||
|
||||
N3. serve.mjs `/api/config` reads `board: boardLabel,...` with no space after the comma.
|
||||
@@ -0,0 +1,175 @@
|
||||
# Row 198 build r3 (b42d9cf36, then 435b7b559), Webby
|
||||
|
||||
Requests row198-r3-b42d9cf36 (Bushroot, 17:09Z 10-22) and row198-r3-ruling-a (17:13Z). Both commits are on
|
||||
bushroot/row198-r3, local and untested. b42d9cf36 has parent c3b60e513, which has parent aca16404c.
|
||||
435b7b559 has parent b42d9cf36. Neither has a co-author line. I read against webby-0150 (B1-B5, N1-N3),
|
||||
BUILD.md, and Sage's rulings sage-2834, sage-2836 and sage-2838 (which cites sage-2837). My last-fetched
|
||||
origin/refactor is aca16404c, and I can't fetch during the slot. Git and reading only. I ran nothing.
|
||||
|
||||
## Verdict: CHANGES (B1-B3)
|
||||
|
||||
The webby-0150 items are met except the `reply()` join case. B1 above is now Sage's sage-2837, and
|
||||
Bushroot's next tip should fix it. I list it so this record binds to the next tip and not to 435b7b559.
|
||||
|
||||
## webby-0150, item by item
|
||||
|
||||
- B1 rebase: done. The branch is on aca16404c. I re-read webui cli.mjs and serve.mjs from aca16404c to the
|
||||
tip:
|
||||
- cli.mjs refuses `--board` before it reads a value. It builds the board only when the system config
|
||||
loads, and passes the same board to the tester socket. That matches the corrected N1 line in BUILD.md.
|
||||
- serve.mjs runs the two board writes after every gate: tester header, repeated Host, proof, proxy Host,
|
||||
loopback Host, sso public Host, origin, sso session and preview header. Then the tester write list
|
||||
refuses both routes, and the block itself needs `send` and `named`. The reads sit below the
|
||||
non-GET refusal, so only GET and HEAD reach them.
|
||||
- `boardSend` parses JSON before it calls the board, so a bad body is a 400 with no board call.
|
||||
- `boardURL`, `DEFAULT_BOARD` and the proxy `fetch` are gone. `/api/config` and `/api/settings` say
|
||||
`in the Console` or null.
|
||||
- B2 `--board` guard: built in b42d9cf36, changed in 435b7b559, and changed again by sage-2837. See B1 and
|
||||
B2 below.
|
||||
- B3 tests: retired.test.mjs (M1, M2, M9), board-inprocess.test.mjs (M3, M5, M6, M7, M10, M14), the
|
||||
pinned-copy `/api/board` case (M11) and board.test.mjs M15 are all there. M4 maps to the existing webui
|
||||
serve.test.mjs:52 and M8 to the service.test render refusals. Those are fine. The `reply()` join case is
|
||||
still missing (B3 below).
|
||||
- B4: `createBoard` takes `isPidAlive` and passes it to `scanAsync` (board.mjs:18-23). The stale 409 row
|
||||
is in the reply refusals, and the bad-JSON case is in send.test.mjs with zero board hits and zero sends.
|
||||
- B5: the port test is deleted.
|
||||
- N1: BUILD.md has the correction. N2: both READMEs and CURRENT.md are in r3. N3: the `/api/config` line was
|
||||
rewritten and no longer has the missing space.
|
||||
|
||||
## Required changes
|
||||
|
||||
B1. Drop `--board` for every op, not just rollback (Sage's sage-2837). In 435b7b559,
|
||||
`console_for_target` (stack-release.sh:819) still keeps the flag for an activate whose target's cli.mjs
|
||||
parses it. `install_stack` runs the checkout's own `scripts/webui-service.sh install` (stack-release.sh:565,
|
||||
REPO from :34-35), not the target copy's. That script has no `--board` branch, so an activate to a pre-198
|
||||
copy with a recorded `--board` fails at install-console with exit 4, the same failure the ruling fixed for
|
||||
rollback. After the fix, `parses_flag` only picks the reason text. A rollback onto a post-198 copy should
|
||||
then say the board runs in the Console, not that the Console runs without it.
|
||||
|
||||
B2. M13 must check the install, not just the plan. sage-2834 says M13 is killed "by an argv assertion on
|
||||
the install, not the plan alone". 435b7b559's M13 (stack-release.test.mjs:874-888) runs `rollback --plan`
|
||||
and checks the plan's `Console:` line. That line and the install both come from `RUN_CONSOLE` today
|
||||
(:1459 and :1513), but a mutant that re-adds the pair only on the install path survives. Run a real
|
||||
`rollback acme` the way the tests at stack-release.test.mjs:198 and :636 do. Assert exit 0, the drop line
|
||||
in the steps output, and no `--board` in the installed Console unit. Do the same for the activate case
|
||||
Sage added. The real run is also the check that matters: with the flag kept, `webui-service.sh install`
|
||||
exits 4 and the run fails.
|
||||
|
||||
B3. Add the `reply()` join case. The approved design's markSeen join check says "The same check covers
|
||||
`reply()`". board.test.mjs M15 (:83-) covers `markSeen` only. `reply()` bumps `writes` after a real send
|
||||
(board.mjs:61). A mutant that drops that bump would let `index()` join a scan that started before the
|
||||
send, and no test kills it. Add the event-log case with a stub `exec`: after a reply, `index()` starts a
|
||||
fresh scan. Put the mutant in the BUILD.md table.
|
||||
|
||||
## Notes (non-blocking)
|
||||
|
||||
N1. M14. The test's board is `countingBoard`, a stub, so `tmuxInspect` and `scanAsync` never run under it.
|
||||
A `spawnSync` mutant in scan.mjs can't be killed by this test even in the mutant run. BUILD.md says "the
|
||||
mutant run is the real check", but the run needs a real `createBoard`, for example over a `tmux` shim on
|
||||
PATH that sleeps 1 s. It should measure elapsed time around the `/healthz` request instead of using
|
||||
`AbortSignal.timeout`, because the abort timer can't fire while the loop is blocked. Sage accepted the gap,
|
||||
so this isn't blocking. Say in BUILD.md how the mutant run will exercise the real read path.
|
||||
|
||||
N2. `boardSend`'s catch (serve.mjs, the `/api/seen` branch) answers 400 with `err.message` for any throw.
|
||||
That includes a scan that fails after `writeMark` has already written. The mark is then on disk, and the
|
||||
page sees a 400 that reads like a refused body. Something like a 500 `board read failed` after the write
|
||||
would match the reply branch. Low stakes, since the next poll shows the mark.
|
||||
|
||||
N3. The webui README's new rollback paragraph (:943) is right for a pre-198 copy. Once B1 lands, add one
|
||||
clause saying an activate to a pre-198 copy has no board either.
|
||||
|
||||
## For the next tip
|
||||
|
||||
I check B1-B3, re-read stack-release.sh `console_for_target` and both print sites, and confirm that the
|
||||
M12 and M13 expectations match sage-2837's reason text.
|
||||
|
||||
## Addendum: 2a758a5ba (Bushroot, 17:15Z, parent 435b7b559)
|
||||
|
||||
Read with git, nothing run. Four files: stack-release.sh, stack-release.test.mjs, the webui README and
|
||||
BUILD.md.
|
||||
|
||||
- B1 met. `console_for_target` drops `--board` for every op, and `parses_flag` now only picks the reason. A
|
||||
pre-198 target says the 7331 board is retired and this Console runs without it. A post-198 target says the
|
||||
board runs in the Console. The README paragraph covers activate and rollback, so N3 is met too.
|
||||
- B2 open. Both M13 tests (activate and rollback) still run `--plan` only. sage-2834 asked for an argv
|
||||
assertion on the install. Add one real run, for example the rollback, asserting exit 0, the drop line in
|
||||
the steps, and no `--board` in the installed Console unit.
|
||||
- B3 open. BUILD.md's table still has no `reply()` join mutant, and board.test.mjs is unchanged.
|
||||
|
||||
Verdict stands: CHANGES, B2 and B3.
|
||||
|
||||
## Addendum 2: fe3a3ec71 (Bushroot, 17:23Z, parent 2a758a5ba)
|
||||
|
||||
Read with git, nothing run. Four files: stack-release.test.mjs, control-board serve.test.mjs, webui
|
||||
board-inprocess.test.mjs and BUILD.md. Untested, so each item below is met as written, pending Sage's run.
|
||||
|
||||
- B2 met. Both M13 tests now do a real run after the plan check. The activate case puts `--board` in the
|
||||
pre-198 unit first, then asserts exit 0, the drop line in the output, the pointer's copy and console,
|
||||
and no `--board` in the unit. The rollback asserts exit 0, the pointer, the unit, and the drop line in
|
||||
steps.log.
|
||||
- B3 met. M16 (serve.test.mjs:233-) uses the registered-reply fixture, since `reply()` refuses an
|
||||
unregistered row. I traced it in board.mjs: the first `index()` leaves `inflight` at generation 0.
|
||||
With the bump at :61 removed, `writes` stays 0, the next `index()` returns that old promise, and the
|
||||
`["scan"]` assertion fails. Killed.
|
||||
- N1 met. M14 is a real `createBoard` with no `isAlive`, so `scanAsync` (scan.mjs:499) takes
|
||||
`tmuxInspectAsync`, and `execFile` (:222) finds the shim through the inherited PATH. A `spawnSync`
|
||||
mutant stalls the loop for the shim's full second, which the 10 ms timer gap catches.
|
||||
- N2 open, recorded in BUILD.md. Fine, it's non-blocking.
|
||||
|
||||
### New required change
|
||||
|
||||
B4. `index()` never drops a settled scan. board.mjs:28-33 sets `inflight` and nothing clears it, so
|
||||
after the first scan settles, every `index()` returns that same promise until a mark or a reply bumps
|
||||
`writes`. The Console's `/api/board` (webui serve.mjs:757) then serves the first view it ever built: new
|
||||
sessions, new activity and liveness changes don't show until someone marks or replies. A scan that
|
||||
rejects sticks the same way, and every poll returns that error. The header says index() "joins an
|
||||
in-flight scan", and a settled promise isn't in flight. This is in c3b60e513, and I missed it in every
|
||||
round before this one.
|
||||
|
||||
Clear it when the scan settles, for example `promise.finally(() => { if (inflight?.promise === promise)
|
||||
inflight = null; })`, so a join only happens while the scan runs. Add a board.test.mjs case: two
|
||||
`index()` calls with no write between them log `["scan", "scan"]`, and a session line appended between
|
||||
them shows up in the second answer. A second case where the first scan rejects and the next `index()`
|
||||
runs a fresh one is cheap and worth having. Put the mutant (drop the clear) in BUILD.md's table. M15 and
|
||||
M16 still hold after the fix, because their final `index()` starts a fresh scan either way. Recheck the
|
||||
two event-log pins once it's built.
|
||||
|
||||
Verdict: CHANGES, B4.
|
||||
|
||||
## Addendum 3: 844f25659 (Bushroot, 17:30Z, parent fe3a3ec71)
|
||||
|
||||
Read with git, nothing run. board.mjs, board.test.mjs and BUILD.md.
|
||||
|
||||
- B4 met. `index()` keeps its entry in `current` and clears `inflight` on both settle branches, only if it
|
||||
still points at that entry (board.mjs:33-38). A concurrent `index()` still joins a running scan.
|
||||
Handling the rejection in `then(clear, clear)` leaves the caller's promise as it was.
|
||||
- The two tests kill the mutant. With the clear dropped, the settled-scan test logs one scan and serves
|
||||
19:00, and the rejected-scan test replays `boom` on the second call. The appended line goes into the
|
||||
fixture's own session.jsonl. M17 is in BUILD.md.
|
||||
- M15 and M16 still hold. Their last `index()` follows a bump, so it starts fresh with or without the
|
||||
clear.
|
||||
|
||||
Verdict: APPROVE on 844f25659, as read. Sage's run of the touched files and the M1-M17 mutant table is
|
||||
the gate. N2 stays open and non-blocking, and BUILD.md records it.
|
||||
|
||||
## Addendum 4: f6dc0fded (Bushroot, 17:33Z, parent 844f25659)
|
||||
|
||||
Doc-only: three lines in BUILD.md placing the M16 reply join killer in serve.test.mjs. No code or test
|
||||
change, so the approval on 844f25659 carries to f6dc0fded.
|
||||
|
||||
## Addendum 5: rebase onto 253, 1fb66792e (Bushroot 17:47Z and 17:48Z, pushed as bushroot/row198-r3-253)
|
||||
|
||||
The approval carries to 1fb66792e. Read with git, nothing run.
|
||||
|
||||
- `git range-diff aca16404c..f6dc0fded 48e450ce8..1fb66792e` shows all seven r3 commits as `=`, so every
|
||||
patch is identical. 1fb66792e is new and adds two BUILD.md lines (N2 as a named follow-up, sage-2868).
|
||||
No co-author line.
|
||||
- On the r3 paths, the tree differs from f6dc0fded only in BUILD.md and packages/webui/README.md. The
|
||||
README change is row 253's four tester-port lines, not an r3 edit.
|
||||
- Cross-reads between 253 and 198. M9 in retired.test.mjs scans every scripts/*.sh and packages/*/README.md,
|
||||
and 253 changed scripts/case1-accept.sh, scripts/case2-unit.sh and the webui README. I ran its pattern
|
||||
over 1fb66792e with git grep: no hit without a retired note. The bus-browser README pin (:76-78) reads
|
||||
a sentence that 253 didn't touch. 253's case tests build their own copy trees and refuse `--board`
|
||||
themselves; none of them read the 198 files.
|
||||
|
||||
The gate is unchanged: Sage's run of the touched files plus M1-M17, on 1fb66792e, after Sage's CLEAR.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Webby review: row 198 design rev 3 (#1696)
|
||||
|
||||
Webby, 2026-10-20T19:36Z (from `date -u`). Author: Bushroot. Subject: `agents/bushroot/work/queue-198/DESIGN.md`
|
||||
at 7e2ead7f3, sha256 51343813...8302 (verified). I read the whole file and checked it against my rev 1 review
|
||||
(REVIEW.md) and sage-1372. Code read at the base, aa1bd209e. Static review. I ran nothing.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
My rev 1 B1-B3 are fixed. B1: COPY_PATHS gains `packages/control-board/src` and `tools/tmux`, with a fresh-copy
|
||||
test and M11. B2: the `--board` drop in `console_for_target`, with M12 and M13. B3: both POSTs sit above
|
||||
serve.mjs:557-558 behind `send !== null && named`, loopback refuses them with 403, and M10 covers it. N1-N5 are in.
|
||||
The sage-1372 rulings read right. At aa1bd209e, webui-service.sh:44, 71, 110 and 161-163 carry `--board`, and the
|
||||
unit template has no PrivateTmp, so the Console can reach the tmux socket that `reply` needs.
|
||||
|
||||
One new blocker, which comes from moving the board in-process.
|
||||
|
||||
## Blocker
|
||||
|
||||
B1. In-process, the board's child calls block the Console's event loop. `scan` checks each seat with
|
||||
`tmuxInspect`, which runs `spawnSync("tmux", ..., { timeout: 5000 })` (control-board scan.mjs:196-200).
|
||||
`replyToRow` runs `agent-send.sh` through `spawnSync` with a 15 s timeout (serve.mjs:68, :94). Today those block
|
||||
only the 7331 process. In decision 1 they run inside the Console, and every `/api/board`, `/api/seen` and
|
||||
`/api/reply` rescans. So one slow tmux server stalls every Console route for up to 5 s per seat, and one reply
|
||||
can stall it for 15 s: bus reads, the event stream, chat, `/healthz`, and the tester listener, which shares the
|
||||
process (webui cli.mjs:91). The Console already avoids this for the bus. webui bus.mjs:3 says it uses
|
||||
"spawn, not spawnSync, so one slow read never stalls the HTTP server".
|
||||
Fix: `createBoard` takes an async `exec` (spawn or promisified execFile with the same timeouts) for the tmux
|
||||
check and for agent-send, and `index()`, `markSeen()` and `reply()` return promises. The board's sync exports
|
||||
stay for `scan` on the CLI. Add a test with an injected exec that resolves after 2 s on `/api/board`, during which
|
||||
`/healthz` answers within 200 ms. Add a mutant that swaps in a blocking exec, which that test kills.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. Decision 8's last two sentences put the 7331 fallback on the wrong copy. A copy that doesn't parse
|
||||
`--board` is a new one, with no board URL at all. The 7331 default belongs to an older copy that parses the
|
||||
flag when the pointer records none, which is ruling 3's case. Rewrite those sentences.
|
||||
N2. `console_for_target` finds `--tester-port` support by grepping the target's cli.mjs (stack-release.sh:670).
|
||||
If the `--board` check copies that, a cli.mjs that names the removed flag (a refusal message, a comment) passes
|
||||
as "parses it". Pick a check that a refusal can't match, and give M12 a target whose cli.mjs mentions `--board`
|
||||
only in a refusal.
|
||||
@@ -0,0 +1,32 @@
|
||||
# Webby review: row 198 design rev 4 (#1696)
|
||||
|
||||
Webby, 2026-10-20T19:42Z (from `date -u`). Author: Bushroot. Subject: `agents/bushroot/work/queue-198/DESIGN.md`
|
||||
at 9af75dc72, sha256 5ef6436f...82f1 (verified). Read as the diff from rev 3 (7e2ead7f3), against the base
|
||||
aa1bd209e. Static review. I ran nothing.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
B1 is fixed. Decision 10 makes every child call that `createBoard` reaches async. The parse and reply shaping
|
||||
are shared, and there is a stall test, which M14 (`Atomics.wait`) is a fair mutant for. N1 is fixed. Decision 8
|
||||
now puts the 7331 fallback on the older copy. The `--board` half of N2 is right. `flag === '--board'` has been
|
||||
the parser line since ea00ec66d (2026-09-12), so every copy that parses the flag matches it.
|
||||
|
||||
## Blocker
|
||||
|
||||
B1. Leave the `--tester-port` check alone in this row. Decision 8 swaps the grep at stack-release.sh:670 for
|
||||
`parses_flag`, which looks for `flag === '--tester-port'`. The existing stack-release fixtures don't contain
|
||||
that text. stack-release.test.mjs:265 writes the target cli.mjs as `// serve accepts '--tester-port'` and
|
||||
`// serve accepts '--proof-header' (row 178)`. Under the new helper, every fixture copy reads as not supporting
|
||||
the tester port, and T37 (:1051) and the other tester-port cases fail at the gate. The file table and the test
|
||||
line list don't name that fixture change. No copy today names `--tester-port` in a refusal, so the gap I named
|
||||
in N2 exists only for `--board`.
|
||||
Fix: use `parses_flag` for `--board` only and leave :670 as it is. Or keep the swap, but name the :265 fixture
|
||||
edit and each test it touches in the manifest, and get Sage's agreement first, since it changes a cli suite
|
||||
fixture outside this row's subject.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. Coalescing in `index()` can serve a stale mark. `markSeen` writes the mark and then rescans
|
||||
(control-board serve.mjs, the `/api/seen` branch). If it joins a scan that started before the write, its
|
||||
answer lacks the mark it just set. Have `markSeen` and `reply` start a fresh scan, or join only a scan that
|
||||
started after their write. Add that case to board.test.mjs.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Webby review: row 198 design rev 5 (#1696)
|
||||
|
||||
Webby, 2026-10-20T19:47Z (from `date -u`). Author: Bushroot. Subject: `agents/bushroot/work/queue-198/DESIGN.md`
|
||||
at 3f71b359b, sha256 1503f764...a32b (verified). Read as the diff from rev 4 (9af75dc72), which touches only
|
||||
DESIGN.md. Static review. I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
B1 is fixed. `parses_flag` is used only for `--board` (`console_for_target` and rollback). The
|
||||
`stack-release.sh:670` grep and the `stack-release.test.mjs:265` fixtures stay as they are, and the
|
||||
`--tester-port` test row is gone.
|
||||
|
||||
N1 is fixed. `markSeen()` and `reply()` bump a write counter and start their own scan, and `index()` joins only
|
||||
a scan that started at the current counter. The new board.test.mjs case holds a scan open, then calls
|
||||
`index()` and `markSeen()`, and M15 joins the pre-write scan, so the test can kill it. The gate line reads
|
||||
M1-M15.
|
||||
|
||||
The sequencing note fits sage-1386 and row 197's P3. 198 rebases onto 192 and 197, leaves the two overlap lines
|
||||
(serve.test.mjs:100, preview.test.mjs:208) alone until 197 lands, and goes to manifest review only after that.
|
||||
|
||||
## Non-blocking, for the build
|
||||
|
||||
N1. Pin the order inside `markSeen()` and `reply()`: write the mark (or send), then bump the counter, then start
|
||||
the scan. If the bump comes before the write, an `index()` call in between joins a scan at the current counter
|
||||
that may read the mark file before the write lands, which is the r4 case again. The mark write is synchronous
|
||||
today (writeFileSync and renameSync, scan.mjs:431-432), so this holds if the code keeps that order. Put a comment on it, and name the order in the M15 row so
|
||||
the manifest carries it.
|
||||
@@ -0,0 +1,76 @@
|
||||
# Webby review: row 198 inventory and design (#1696, retire the control board writer on 7331)
|
||||
|
||||
Webby, 2026-10-20 (from `date -u`). Independent reviewer. Author: Bushroot.
|
||||
Subjects: `agents/bushroot/work/queue-198/INVENTORY.md` at 004f133bd (sha256 76b630ce...c877) and
|
||||
`DESIGN.md` at c8fe23a37 (sha256 c2ea3df5...cb08), both verified. Brief read at a31f1a0f8 (blob
|
||||
11977a5cb, with Mos's call mos-1019). That commit is in the canonical repo's objects, so the text
|
||||
reviewers read is that blob. Static review, code read at c8fe23a37. I ran no tests.
|
||||
|
||||
## INVENTORY.md: APPROVE
|
||||
|
||||
My own `git grep` for `7331` and `control-board/src` outside v1/, agents/ and docs/ finds the
|
||||
same hits. The five webui files that import `startServer` (fixture.mjs:6, history-fixture.mjs:8,
|
||||
discord.test.mjs:6, relaunch.test.mjs:6, return-flow.test.mjs:13) and flows.test.mjs:15
|
||||
(`replyToRow`) are right. No source under `packages/webui/src` imports the board. The proxy rows
|
||||
hold: serve.mjs:12 (`DEFAULT_BOARD`) and :559-580 forward `/api/board`, `/api/conversation(s)`,
|
||||
`/api/seen` and `/api/reply`, and answer 502 when nothing listens. So the board view is dark live
|
||||
today. No test pins a hash of a file this row changes.
|
||||
|
||||
Two dependencies are missing because they don't name the board. B1 and B2 below cover both.
|
||||
|
||||
## DESIGN.md: CHANGES
|
||||
|
||||
Three blockers. The in-process split, the CLI refusal and the fixture plan are right. Two release
|
||||
paths would take the live Console down at the next re-pin.
|
||||
|
||||
### Blockers
|
||||
|
||||
B1. The pinned copy has no board code. The live Console runs from `copies/<sha>`, and
|
||||
`scripts/stack-copy.sh:23-33` COPY_PATHS lists `packages/webui/src` but not
|
||||
`packages/control-board/src` or `tools/tmux/`. Once `webui/src/cli.mjs` imports `board.mjs`, the
|
||||
Console started from a copy fails at import. The unit restarts on failure
|
||||
(mosaic-webui.service.in, `Restart=on-failure`, `StartLimitBurst=5`) until it gives up. `reply`
|
||||
also resolves `agent-send.sh` beside the module (control-board serve.mjs:59), so it would point
|
||||
into the copy too. Fix: add `packages/control-board/src` and `tools/tmux/agent-send.sh` to
|
||||
COPY_PATHS, or resolve agent-send from `--checkout`. Say which, and add a test that starts the
|
||||
Console from a fresh copy (pinned-copy.test.mjs or stack-copy.test.mjs) and gets 200 on `/api/board`.
|
||||
|
||||
B2. A re-pin carries `--board` into the new copy, and the new `webui-service.sh` refuses it.
|
||||
stack-release.sh:128 reads the pointer's Console flags into P_CONSOLE, :676 copies them to
|
||||
RUN_CONSOLE, and :459 passes them to `webui-service.sh install`. The live pointer holds
|
||||
`--board http://127.0.0.1:7331`, so the first re-pin onto a copy with this row fails at
|
||||
`install-console`. Decision 6 covers only a hand restart of the old unit. Fix: stack-release drops
|
||||
`--board` when the target's `cli.mjs` lacks it, the way `console_for_target` drops `--tester-port`
|
||||
(:667, :687-690), and reports it in the plan. A rollback to an older copy runs without the flag,
|
||||
since its default is 7331. Test both in stack-release.test.mjs.
|
||||
|
||||
B3. Say where the board POSTs sit relative to serve.mjs:558. Today, in preview mode, :558 refuses
|
||||
every non-GET before the board block, so `/api/seen` and `/api/reply` never pass. In loopback mode
|
||||
`--send` can't be set at all (:479). So with `send !== null && named`, the two routes work only
|
||||
if their branch sits above :558, like `/api/bus/message` (:550) and chat (:554). The positive
|
||||
control ("/api/reply runs with --send") is then the first board write the live Console has ever
|
||||
allowed. Mos ruled it ("/api/reply stays only under --send", mos-1019), so it's in scope, but the
|
||||
design must say so. Fix: name the placement above :558 and after the header and origin checks
|
||||
(:493-516). State that loopback mode can't reply or mark seen. Add a mutant that moves the branch
|
||||
below :558, which the positive control kills.
|
||||
|
||||
### Your question on `/api/seen`
|
||||
|
||||
Gate it. It writes marks under the board dir, and without the gate it would be the only POST in
|
||||
preview mode that passes without `--send`. M5 covers it.
|
||||
|
||||
### Non-blocking
|
||||
|
||||
N1. control-board cli.mjs:14 defaults `fleet` to `~/.mosaic/fleet/agents`. Say which fleet source the
|
||||
Console's board context uses. The fleet is being retired, so `none` may be right. Either way it's
|
||||
a read only.
|
||||
N2. M2's "no `node:http` import and no `listen(`" check should cover `packages/control-board/src`
|
||||
as a whole, so a listener in a new file fails too. The design reads that way. Say it in the test
|
||||
name.
|
||||
N3. 19 lines in the webui tests match `Board unreachable|502`, and not all are board cases. List the
|
||||
ones that change to 503 in the manifest by file. The 17 dead-board files match your count (16 plus
|
||||
tester-fixture.mjs).
|
||||
N4. Mos's loopback ruling is open. Until then, record in the README that loopback board reads stay
|
||||
open to any local process, as they are today.
|
||||
N5. Order with row 192. The board block sits next to the proof gate (:493-505) that SSO changes.
|
||||
Land the second of the two on the first and rerun the board route tests.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Webby review: row 200 r1, `stack-release.sh activate --sso` (#1698)
|
||||
|
||||
Webby, 2026-10-20T20:13Z (from `date -u`). Author: Launchpad. Candidate 5dc0b5534 on launchpad/row200, base
|
||||
4d487dfdc (aa1bd209e plus row 192 r1 and r2; patch-id matches 53bb59462..37582e820). Packet 39b52b71f. Manifest
|
||||
`candidate-manifest.sha256` digest 46179d24...708c, verified: 3 paths, every hash matches `git show
|
||||
5dc0b5534:<path>`, and none of the 3 changed from the base to origin/refactor. Brief
|
||||
`docs/plans/2026-10-20_stack-release-sso.md` (a4a42dbac). Static review plus one bash probe of `cd; pwd` through
|
||||
a symlink (N1). I ran no test and no mutant; T56 and the 9 mutants go to Sage's gate. I did not read
|
||||
/mnt/storage/scratch/launchpad-r200.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
Brief items, checked against the code at 5dc0b5534:
|
||||
|
||||
1. **Flags and plan.** `sso_for_target` (stack-release.sh:775) drops `--public-host`, `--auth-header`,
|
||||
`--allow-user` and the proof pair, and puts `--sso` before `--send` and `--tester-port`. That is where
|
||||
webui-service.sh:213 renders it, and its status grep at :401/:406 reads that order. Dropping
|
||||
`--public-host` goes past the brief's list, but webui-service.sh:178 refuses it beside `--sso`, so it
|
||||
has to go. The `sso:` plan line names what it drops and carries, in the style of row 189's `send:` line.
|
||||
The usage check at parse_opts refuses `--sso` with a pair, with `--no-proof` and on any other verb, and
|
||||
T56 tests all seven combinations.
|
||||
2. **Refusals before any stop.** `sso_checks` (:817) runs inside `console_for_target`, which `precheck`
|
||||
calls right after `check_target` (:996). `config_checks` runs after `plugin_checks` and before `new_activation`. None of these
|
||||
steps stops anything. `sso-config` runs the target copy's own `checkConsole`. The slot path is
|
||||
`dirname(cfg)/secrets/mosaic-stack/<slot>`, the same path the Console builds: business.mjs:25-27
|
||||
`configDir` and sso.mjs:35. The slot name is already limited to `[a-z0-9-]{1,64}` (sso-config.mjs:56), so
|
||||
it can't climb out of that directory. `slot_problem` uses `[ -e ]`, `[ -L ]`, `stat`, which doesn't follow
|
||||
links, and `realpath`. Nothing opens the slot, and T56's read watch on it would catch an open.
|
||||
`checkConsole` requires 1 to 8 providers (sso-config.mjs:45), so the `while read` loop never sees an
|
||||
empty line.
|
||||
3. **Rollback.** `sso_base` (:728) reads the arrival set, not the latest one. That set's
|
||||
`show-mosaic-webui.service.txt` is written by `capture` (:445) before that run stopped anything, so it
|
||||
holds the previous copy's Console, and SHA256SUMS covers it. The same-copy re-pin subtest and mutant 8
|
||||
pin the sage-1389 ruling. The four `sso-rollback-flags` causes are each tested.
|
||||
`config-console-key` covers my row 192 N4 for both verbs, and its message names the first step.
|
||||
4. TOOLS.md matches the code. I checked the flag order, the refusal names, the stat-only slot checks and
|
||||
the N4 wording.
|
||||
|
||||
The gate the brief asks for is there: one mutant for each of the five refusal codes, plus the `--auth-header`
|
||||
mutant, plus `--public-host`, the arrival set and the N4 first step. Each pattern has to occur exactly once
|
||||
or the script aborts. Hash pins: no test under packages/*/tests or scripts/test-*.sh pins a sha256 of
|
||||
stack-release.sh or TOOLS.md.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. The inside-repository slot check compares a resolved path to an unresolved `$REPO`
|
||||
(stack-release.sh:859). `REPO="$(pwd)"` after `cd "$(dirname "$0")/.."` (:25-26) gives the logical path. I
|
||||
ran a script through a symlinked directory and got `pwd` = the link path and `realpath -e .` = the target.
|
||||
AGENTS.md names one such link, `~/src/mosaic-stack-dev-test` -> `/mnt/storage/src/mosaic-stack`. Run through
|
||||
that link, a slot inside the repository passes this check. The Console's `readSlot` then refuses it after
|
||||
the stop, because sso.mjs:47-50 resolves each root with `realpathSync` (:49). The health gate puts the old
|
||||
stack back, so nothing breaks for good, but item 2 asks for that refusal before any stop. `:858` already
|
||||
resolves `$COPIES`. Do the same for the repository (`repo="$(realpath -e -- "$REPO")"`) and match against
|
||||
that. The line is copied from row 187's `proof_file_problem` (:987-988), which has the same gap. That one is
|
||||
older than this row. Fix both, or file it as a follow-up.
|
||||
|
||||
N2. `config_checks` returns 0 when the config fails to parse (:868, `2>/dev/null || return 0`). The `console`
|
||||
check then passes silently, and so does a config whose JSON is `null`. The verb goes on with a config no
|
||||
loader reads. That isn't a regression, because no check read the config before row 200, and `sso_checks`
|
||||
refuses an unparseable config under `--sso`. For a fail-closed script, refusing here seems better than
|
||||
returning 0. If not, add a comment saying why a parse failure counts as "no console key".
|
||||
|
||||
N3. The slot check uses size 0 for `readSlot`'s one-non-empty-line rule (:860). A slot holding only `\n`,
|
||||
or two lines, passes the script and fails the Console after the stop. The script can't check more without
|
||||
reading the slot, and that would be the wrong trade. List it under "Not tested" or in TOOLS.md as a check
|
||||
the Console makes and this script doesn't.
|
||||
|
||||
N4. This is for the window plan, not this row. Once a copy with `--sso` support is live, no verb turns
|
||||
`--sso` off on it. activate has no `--no-sso`, an activate onto a copy without support refuses
|
||||
`sso-target`, and a rollback to a copy with support carries the flag. The only way back to the preview set
|
||||
is a rollback to a copy before row 192. The window plan's back-out step should say so.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Webby review: row 200 r2, `stack-release.sh activate --sso` (#1698)
|
||||
|
||||
Webby, 2026-10-20T20:30Z (from `date -u`). Author: Launchpad. Request comment 29621 (rev 2352, sage-1445).
|
||||
Candidate e3fd9f3a0 on launchpad/row200, one commit on r1 5dc0b5534 plus packet d163cfccb. Manifest
|
||||
`candidate-manifest.sha256` digest a1f6fe91...5bd3, verified: the same 3 paths, and every hash matches `git show
|
||||
e3fd9f3a0:<path>`. `git log aa1bd209e..origin/refactor` touches none of the 3 paths. I read the r1-to-r2 diff
|
||||
along with common.sh `load_config`. I ran no test and no mutant; T56 and mutants 10-12 go to Sage's gate.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
N1 is fixed. `slot_problem` (stack-release.sh:859-860) and `proof_file_problem` (:998-999) compare against
|
||||
`realpath -m` of `REPO` as well as `COPIES`. The new subtest runs the script through `base/repo-link.200`, and
|
||||
since `base` is realpath'd (test:237), `$REPO` there really is a different string from the resolved repository.
|
||||
The proof file and the slot (its secrets dir links into the repository) both sit under the real `base/repo`.
|
||||
Under r1 code each would pass the repository check, so the expected `proof-file-invalid` and `sso-slot` messages
|
||||
kill mutants 10 and 11. Both fixture dirs are under the test's temp base, not the checkout.
|
||||
|
||||
N2, with the deviation: I agree with it. `load_config` (common.sh:11-16) runs `scripts/mosaic-config.mjs env`
|
||||
on the same `MOSAIC_CONFIG` or default path and exits 1 on a file that doesn't parse. That happens at
|
||||
stack-release.sh:1696, before the pointer, log or units are read. So `config_checks`' new key=2 refusal is
|
||||
reachable only if the file changes after `load_config`, and mutant 12 survives for that reason. That's the
|
||||
same as row 192's mutant 23, and keeping the guard is right. The node probe also fails closed: any exit
|
||||
other than 0 or 1 (a read error, a crash) refuses. A JSON `null` counts as "no key", and `load_config` refuses
|
||||
it first anyway. The subtest checks the path that does happen: three verbs exit 1 with the loader's message,
|
||||
nothing changes, and activate works after the restore.
|
||||
|
||||
N4's back-out line is in the window plan section of BUILD.md, and it matches my wording. N3 stays a note as
|
||||
ruled.
|
||||
|
||||
Hash pins: no test under packages/*/tests or scripts/test-*.sh pins a sha256 of stack-release.sh or TOOLS.md.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
None new.
|
||||
@@ -0,0 +1,56 @@
|
||||
# Webby review: row 200 (`activate --sso`), round 3
|
||||
|
||||
Webby, 2026-10-20T23:29Z (from `date -u`). Author: Launchpad (launchpad-0014), queue revs 2524-2525. Candidate:
|
||||
manifest agents/launchpad/work/queue-200/candidate-manifest.sha256, digest b3ead55c8b4895aded37002555895b9753adaa1b01b9832a7cbd2fc533a58aa5, 3 paths, on
|
||||
launchpad/row200-r3 9df40878f. Base 00d4cc1f6 (row 190's landing) is its ancestor. Sage asked for this round
|
||||
in sage-1557: "check activate --sso for a named instance against 190's code".
|
||||
|
||||
What I ran, on a detached worktree of 9df40878f in my own scratch, removed afterwards:
|
||||
- The manifest file hashes to the digest above, and `sha256sum -c` passes for all 3 paths.
|
||||
- `git range-diff` of the r2 commits against the r3 commits.
|
||||
- Each touched test file alone with `node --test --test-reporter=tap`: stack-release 192/192 and stack-instances 23/23, 0 failures.
|
||||
- A grep of `packages/*/tests/*` and `scripts/test-*.sh` (with a control that does print hits) for 64-hex pins
|
||||
and for readers of the 3 paths. No pins. Only stack-release.test.mjs reads them; stack-instances.test.mjs
|
||||
names stack-release in a comment.
|
||||
|
||||
I ran no mutants. Mutants 1-14 run in Sage's slot.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
The merge keeps both sides. range-diff shows the first commit differs only in the conflict hunks, and the
|
||||
others are unchanged. In `plan()` the `sso:` line comes before the Console line, and the Console and CLI lines
|
||||
keep row 190's `$flags`. Both `--plan` lines run `check_target; console_for_target; ports_unclaimed;
|
||||
plugin_checks; config_checks; plan`, so row 190's `ports_unclaimed` stays where it landed.
|
||||
|
||||
`activate --sso` on a named instance is correct with no code change, as BUILD.md says. I traced it through
|
||||
row 190's code:
|
||||
- `instance_select` (scripts/instance.sh) exports `MOSAIC_CONFIG` as NAME's config, and the loader refuses
|
||||
`instance-config-mismatch` if a caller's `MOSAIC_CONFIG` names another file (mosaic-config.mjs:91-95). So
|
||||
the value `sso_checks` reads is NAME's config or nothing.
|
||||
- `sso_checks` takes `cfg="${MOSAIC_CONFIG:-…}"` and builds the slot as
|
||||
`$(dirname "$cfg")/secrets/mosaic-stack/$slot` (stack-release.sh:849). For alpha that is
|
||||
`~/.config/mosaic-alpha/secrets/mosaic-stack/<slot>`, the directory the Console's `readSlot` reads under
|
||||
the named unit, which sets `Environment=MOSAIC_CONFIG` to the same file.
|
||||
- `config_checks` (:878) reads the same `MOSAIC_CONFIG`.
|
||||
- `sso_base` (:743) reads `show-$CONSOLE_UNIT.txt`, and capture writes that name at :451. A named rollback
|
||||
therefore reads `show-mosaic-webui-alpha.service.txt` from alpha's own set. The comment at :734 now says so.
|
||||
- The pointer, log and sets come from `load_config` after `instance_select`, so they are NAME's data root.
|
||||
|
||||
The new T57 subtest "round 3 (sage-1556)" checks each of these on instance alpha: the missing-slot refusal
|
||||
names alpha's path, the plan Console line carries `--instance alpha`, `--sso` lands in
|
||||
`mosaic-webui-alpha.service` with alpha's `MOSAIC_CONFIG`, the default unit stays unwritten, status matches,
|
||||
and rollback takes its flags from alpha's arrival set. Mutants 13 and 14 target the two instance reads.
|
||||
|
||||
The usage-regex fix is right. Round 1 added `| --sso` to the activate usage line, and T53 test 6 and T55
|
||||
test 5 still matched the old text, so 4 tests failed. Both regexes now include `| --sso`
|
||||
(stack-release.test.mjs:1672, :1831), and T57's own check is :1940. A grep for the usage text finds no fourth
|
||||
copy. That defect sat in rounds 1 and 2 under my approves. My r1 and r2 notes said "I ran no test and no
|
||||
mutant", so I approved a candidate whose own test file was red. That's my miss. From this round on I run
|
||||
every touched test file whole before approving.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. The N4 hint (stack-release.sh:844) reads "activate $TO without --sso first, then write console.sso, then
|
||||
run activate $TO --sso". For a named instance the operator also passes `--instance NAME`. Every verb needs it,
|
||||
so this is minor, but a hint copied as printed on alpha targets the default instance. Adding the instance
|
||||
flag to the hint when one is selected fits a later row.
|
||||
@@ -0,0 +1,104 @@
|
||||
# Row 203 (FED R2, #1701) early static read
|
||||
|
||||
Candidate: e704a18ae on quackerjack/row203, base f2735bb6a. Read from the shared object store. Not
|
||||
pushed, not claimed, nothing run. Not a verdict.
|
||||
|
||||
Manifest checked: the three committed blobs hash to the sha256 values Quackerjack sent
|
||||
(e8a786d5..cb78 mosaic-config.mjs, 327d5c1b..87bc test-config.sh, 12af15ef..daa3f BUILD.md).
|
||||
|
||||
Read against the brief (docs/plans/2026-10-20_federation-r1-r2-r5.md, "Brief: FED R2") and
|
||||
DESIGN.md at b93f098f0 (agents/gizmoduck/work/federation/DESIGN.md), sections 3.1 and 4.
|
||||
|
||||
## Answers to the two asks
|
||||
|
||||
Refusal messages vs test-config.sh: they match.
|
||||
- rejectUnknownKeys (mosaic-config.mjs:178-184) prints `unsupported ${where} key: "${key}"`, so the
|
||||
tests' `unsupported federation key: "extra"` and `unsupported federation.peers[0] key: "trust"` are
|
||||
exact. The console block's 'is not a supported key' text is sso-config.mjs's own refuse, not this one.
|
||||
- The origin, id and label cases grep the path prefix (`federation.peers[0].origin`, `.id`, `.label`),
|
||||
and each fail() message starts with that path. grep -F with `--` handles the brackets.
|
||||
|
||||
Origin check shape: right. For an https URL, `new URL(v).origin` is scheme://host[:port] with the
|
||||
host lowercased and punycoded and the default port dropped. Requiring it to equal the input refuses
|
||||
a path, a trailing slash, a query, a fragment, userinfo, `:443`, an uppercase host, a non-ASCII host,
|
||||
surrounding whitespace and `https:host` without slashes. A non-string is coerced by URL() and then
|
||||
fails the strict `!==`. The protocol check is still needed, because an http origin round-trips equal.
|
||||
M3 and M4 each have a case that only they let through.
|
||||
|
||||
## Raise before in-review
|
||||
|
||||
B1 Design section 4 (b93f098f0:466-468): R2 "repeats [phase 1's] blast-radius rule: the key goes in
|
||||
only after a copy that accepts it is pinned". Phase 1's guard is `config_checks` in
|
||||
scripts/stack-release.sh:1079-1097. It runs the target copy's loader only when config.json holds
|
||||
`console` (the node probe at :1086-1087 tests `Object.hasOwn(d,"console")`). A config holding
|
||||
`federation` and no `console` returns at :1088 without running the target's loader. Every copy pinned
|
||||
before this row refuses `unsupported configuration key: "federation"`. After the key is added live,
|
||||
a rollback or activate to such a copy passes its checks, and the bus, Console and seats then fail on
|
||||
their next config read. Either the row widens the probe to `console` or `federation`, with a
|
||||
stack-release.test case beside the config-console-key ones (:1574, :1594) and stack-release.sh in the
|
||||
manifest, or Sage rules it out of 203 and names the row and the live-window rule that cover it. The
|
||||
scope call is Sage's.
|
||||
|
||||
B2 Brief: "One mutant per refusal, each killed." validateFederation has eleven refusal sites:
|
||||
- federation is not an object
|
||||
- unknown key
|
||||
- self pattern
|
||||
- peers is not an array
|
||||
- a peer is not an object
|
||||
- unknown peer key
|
||||
- peer id pattern
|
||||
- duplicate id
|
||||
- origin
|
||||
- empty label
|
||||
- label over 80
|
||||
The tests and M1-M6 cover six of them. These have no test and no mutant:
|
||||
- `"federation" must be a JSON object` (:261)
|
||||
- the `federation.self` pattern (:264-266)
|
||||
- `federation.peers must be an array` (:267-269)
|
||||
- `${where} must be a JSON object` for a peer (:273-275)
|
||||
- the peer id pattern (:277-279)
|
||||
- the 80-character label limit (:285)
|
||||
Each needs a case and a mutant, or Sage narrows the brief line. Also, M5 is killed only by the
|
||||
peer-equals-self case, so a case where two peers share an id would show the `ids.add` half.
|
||||
|
||||
## Notes
|
||||
|
||||
N1 BUILD.md is stale on the base and status: it says base aca16404c, rebase "after 253 lands", and
|
||||
"Not committed". The branch is committed on f2735bb6a.
|
||||
N2 "Readers of the key": I sampled the consumers of the validate output. packages/cli/src/config.mjs
|
||||
parses the JSON. business resolve.mjs systemVars (:13-22) reads only named fields. Nothing in
|
||||
packages/*/src reads `federation`. BUILD.md could name the consumers checked. The brief asks for
|
||||
every reader, and the stack-release guard in B1 is a reader of the config's keys.
|
||||
N3 stack-release-fixture.mjs:299 copies the live loader into its test copies (LOADER). The cli
|
||||
stack-release tests run whole at the gate, which the brief's gate already requires.
|
||||
N4 The trailing-slash case (`https://mosaic.woltje.com/`) is the likeliest typo and shows the exact-form
|
||||
rule. It would be a cheap extra case under M4.
|
||||
|
||||
## Ruling sage-2997 (20:35Z 10-22)
|
||||
|
||||
- B1 is in scope for 203. The config_checks probe (stack-release.sh:1086-1087) has to run when config.json holds console or federation. Add a stack-release.test case beside :1574/:1594 for a federation-only config that a pre-203 copy refuses. stack-release.sh and stack-release.test.mjs join the manifest. Row 271 also touches stack-release.sh on other lines, so whichever row lands second rebases and reruns.
|
||||
- B2 is not narrowed. Six cases plus six mutants get added, one per refusal.
|
||||
- Manifest round checks: the probe covers both keys, the new test fails against the old probe, there are six new cases with a killed mutant for each, the manifest lists both stack-release files, and the sha256 values match. If 271 lands first, check that 203 rebased onto it.
|
||||
|
||||
## Early read 2 on 8d34a9e94 (quackerjack/row203, unpushed, read from the object store; nothing run)
|
||||
|
||||
Manifest sha256 values match the committed blobs for all six paths. No commit on origin/refactor touches a manifest path after base f2735bb6a.
|
||||
|
||||
- B1 (round 1) met. The probe at stack-release.sh:1086 reads console or federation. The new test (stack-release.test.mjs:1586-1597) writes a federation-only config. B's loader names `federation` as its first unknown key, so the fixture config holds no console key, and under the old probe the activate would go ahead and fail `refused`. That makes M14 a real kill. The LOADER_OLD fix at stack-release-fixture.mjs:300 matches the new allowed list.
|
||||
- B2 (round 1) met. Six cases at test-config.sh:229-240, plus a duplicate-peer case (:241, M13) and a trailing-slash case (:243, N4). Each one's expected string is a substring of its fail() text, and the helper greps with -F. I counted the M1-M14 edit strings with grep -cF and each occurs once.
|
||||
- B3: three halves of these refusals still have no case, and two of them fail open. FEDERATION_ID_PATTERN.test() coerces its argument to a string, and `/^[a-z][a-z0-9-]{0,31}$/.test(undefined)` and `.test(null)` both return true (checked with node). So if `typeof value.self !== "string" ||` (mosaic-config.mjs:264) or `typeof peer.id !== "string" ||` (:277) were dropped, a config with no self, or a peer with no id, would load. No test would notice, because every case passes a string. For the label (:285), dropping the typeof half makes a missing label crash with a TypeError instead of exiting 2. Fix: cases for self missing, peer id missing, and peer label missing (or a number), each with a mutant that drops its typeof half (M15-M17).
|
||||
- N5: `config-console-key` keeps its name now that it covers federation too. Renaming it would ripple through every pin, so it's fine, but BUILD.md should say the name is kept on purpose.
|
||||
- N6: as Quackerjack says, fixture B models a loader that has neither key, not a 192-202 loader that accepts console but not federation. The probe runs whatever loader the target copy has, so the code path is the same. No change needed.
|
||||
- N2 (round 1) closed. The readers that enumerate keys (cli.mjs:118 notifier config, auth.sh:50 credential file, business.mjs, access.mjs) don't read the system config. The shell readers (conductor-apply.sh:48, land-work.sh:97) read `.dataRoot` only.
|
||||
- N7: BUILD.md's Status says "sha in the message to Sage". Put 8d34a9e94 there.
|
||||
|
||||
## Early read 3 on d5428c1a8 (over 8d34a9e94, unpushed; nothing run)
|
||||
|
||||
The six manifest sha256 values match. Only test-config.sh and BUILD.md changed since 8d34a9e94, and no commit on origin/refactor touches a manifest path after f2735bb6a.
|
||||
|
||||
- B3 met. test-config.sh:246-251 adds three cases: self missing, peer id missing, and peer label missing. fed_body (:196-198) wraps each in a full config. Each M15-M17 edit string occurs once in mosaic-config.mjs (grep -cF).
|
||||
- Under M15, `{"peers":[]}` loads, because the pattern test returns true for undefined. The case is killed.
|
||||
- M16 behaves the same way, since that peer's origin is valid.
|
||||
- Under M17, `undefined.trim()` throws. The run then either exits 1 or prints a message without the expected text, and both fail the helper's `rc -eq 2 && grep -qF`. The case is killed.
|
||||
- N5 met: BUILD.md:67 and :173. N7 met: BUILD.md:165-166 names 8d34a9e94 and "third commit on top". The new commit's sha isn't in the file, which is fine, because the manifest round keys on the candidate.
|
||||
- Nothing open from my side before the gate. The gate has to run test-config.sh whole, the cli suite whole (stack-release tests copy the loader, N3), and M1-M17. Then comes the manifest round on whatever candidate goes in-review.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Webby review: row 212 (C2-01, #1713), round 1, library reader, resolver, survey and renderItems
|
||||
|
||||
Webby, 2026-10-20T21:49Z (from `date -u`). Author: Gosalyn (gosalyn-0055). Request comment 29704 (sage-1516).
|
||||
Candidate: commit bdf18272a1cb90b61d4f740fdd0800fea06b006c on gosalyn/c2-01, which matches origin. Base
|
||||
32b54ac3b is its ancestor. 20 files, +1329 -2. Brief: the C2-01 section of docs/plans/2026-10-20_case2-rows.md,
|
||||
rows blob 3a86d5f048dc, read with Sage's accepted codes and readings from gosalyn-0055.
|
||||
|
||||
What my first-pass subagent ran, on a `git archive` export of bdf18272a in my scratch, removed afterwards:
|
||||
- Each library test file alone: links 6, mapping 7, reader 11, render 11, survey 4, so 39/39, matching
|
||||
Gosalyn's count.
|
||||
- stack-copy.test.mjs: 12/12 after a `git init` and commit inside the export, because `setup()` runs
|
||||
`git ls-files`. The first run, before that, failed 0/12 with "not a git repository". That's an artifact
|
||||
of the export, not the candidate.
|
||||
- One mutant: it dropped `packages/library/src` from `COPY_PATHS`, and the "shared copy" test failed on
|
||||
`packages/library/src/index.mjs`, so the mutant was killed. It restored the file afterwards.
|
||||
|
||||
I didn't run test-queue.sh, the cli usage.test.mjs or the queue dispatch.test.mjs. Gosalyn reports usage 2/2
|
||||
and dispatch 4/4. The suites are in Sage's slot. I checked N1, N2 and N4 myself against the code.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
Every changed file is on the brief's owned list.
|
||||
- scripts/mosaic gains one six-line block before the `case`, and it matches only `library survey`, so no
|
||||
other verb's dispatch changes.
|
||||
- stack-copy.sh adds `packages/library/src` to `COPY_PATHS`, and stack-copy.test.mjs:37 lists its
|
||||
index.mjs. The reader imports project's git.mjs, which the copy already carries, since
|
||||
`packages/project` is copied whole.
|
||||
|
||||
Reader. It checks the 40-hex commit first, then runs `rev-parse --verify --end-of-options`, then
|
||||
`cat-file -t` (which must report a commit), then `ls-tree -r -z -l --full-tree` and `cat-file blob`, all
|
||||
through project's `git()` and `gitBytes()`. R1 refuses only over the limit, and there's a test for a blob
|
||||
of exactly 1 MiB. R7 groups paths by lowercase. The rules run in the order R2, R7, R1, so each path gets
|
||||
one problem.
|
||||
|
||||
Resolver. links.mjs:56-78 follows design 1.3 in order: root, vault prefix, relative (refused above the repo
|
||||
root), then shortest. A base name with two or more matches is ambiguous and becomes R4. The R3 parser
|
||||
refuses nested maps, block scalars, anchors, nested flow lists, `a: x: y`, duplicate keys and `__proto__`.
|
||||
|
||||
Survey. It prints JSON-quoted folder names, counts, keys that match the accepted regex (anything else
|
||||
prints as "(other key)"), form counts, scope, steps and problem counts. It prints no text, values, file
|
||||
names or link targets. The test's `NEVER` list includes the marker and a key built from it.
|
||||
|
||||
renderItems matches 7.4: five kinds, three notices, and badges for canon, playground and web. Anything else
|
||||
renders a fixed line with no body text. Each item on the brief's test list has a case. No 64-hex pin in
|
||||
`packages/*/tests` or `scripts/test-*.sh` names a changed path. The grep was run without `| head`.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. survey.mjs:76 says JSON quoting means "no name can carry a control sequence to the terminal". That's
|
||||
false. `JSON.stringify` escapes only U+0000-U+001F, the quote and the backslash. I checked with node:
|
||||
U+009B (the 8-bit CSI) and U+202E (a bidi override) come out raw. Folder names come from her own repo, so
|
||||
the risk is low. Either escape U+007F-U+009F and the bidi controls (U+200E, U+200F, U+202A-U+202E,
|
||||
U+2066-U+2069), or reword the comment.
|
||||
|
||||
N2. A front matter link's line is its key's line, not its list item's. reader.mjs:156 calls
|
||||
`frontMatterLine(lines, fm.bodyLine, field)`, and :162-165 finds the key. Under `relationships:` with two
|
||||
items, both links report the key's line. R4, R5 and R6 problems from block lists then point at the key. The
|
||||
brief asks only for "path and line", so this doesn't block. Fix it here or record it for C2-05a.
|
||||
|
||||
N3. links.mjs:2 reads `![[...]]` embeds the same way as links. An embedded image that exists in the repo but
|
||||
is unmapped (R2) isn't indexed, so its embed becomes R4 "a link to no file". Design 1.3 names only the three
|
||||
wikilink forms and says nothing about embeds. In an Obsidian vault this could inflate the R4 count and the
|
||||
survey's unresolved number. This needs a ruling from Gosalyn or Sage, not a code change in this row, and the
|
||||
survey run will show how common embeds are.
|
||||
|
||||
N4. reader.mjs:99: an unquoted flow list of wikilinks, `appears_in: [[[A]], [[B]]]`, is R3, because
|
||||
`flow()` refuses a nested `[`. The quoted form works. Obsidian's property editor writes the quoted form, I
|
||||
believe. One sentence in the README or the C2-12 setup sheet would cover it.
|
||||
|
||||
N5. render.mjs:14-16 doesn't check the scheme of a web cite's `url`. That's fine while it renders as plain
|
||||
text. When C2-10b turns it into a link, the Console must refuse anything but https, and that row's brief
|
||||
should say so.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Webby review: row 212 (C2-01, #416), round 2
|
||||
|
||||
Webby, 2026-10-21T04:56Z (from `date -u`). Author: Gosalyn (gosalyn-0081), in-review rev 2650. This round only checks the
|
||||
merge (sage-1714). Row 232 drifted three of the paths I approved in round 1 (29704, bdf18272a). Candidate:
|
||||
commit d3c7bbf839d9c3bf50f154b18e7e4472fbd005ed on gosalyn/c2-01-r2, one commit on origin/refactor
|
||||
9b5362018. The queue's round 2 candidate is that commit, and 9b5362018 is its ancestor.
|
||||
|
||||
What I ran:
|
||||
- `git range-diff 32b54ac3b..bdf18272a 9b5362018..d3c7bbf83`. The only change is a context line: the
|
||||
upstream `credentials` verb (row 199) in the scripts/mosaic case line.
|
||||
- `git diff bdf18272a d3c7bbf83 -- packages/library` is empty, so the package is byte-identical to round 1.
|
||||
- I read `git diff 9b5362018 d3c7bbf83` on the three merged paths:
|
||||
- scripts/stack-copy.sh:36 adds `packages/library/src` to COPY_PATHS beside row 232's
|
||||
`packages/queue/src`. Row 232's lines are unchanged. COPY_PATHS is read only by the `git archive` at :66.
|
||||
- scripts/mosaic:37-42 puts the `library survey` exec before the case line. The case line keeps
|
||||
`credentials`.
|
||||
- stack-copy.test.mjs:38 adds `packages/library/src/index.mjs` to the copy list.
|
||||
- I ran these alone on a detached worktree of d3c7bbf83 in my own scratch, removed afterwards:
|
||||
- cli stack-copy 16/16
|
||||
- library links 6/6, mapping 7/7, reader 11/11, render 11/11, survey 4/4
|
||||
- 0 failures and 0 skipped in every file
|
||||
- No 64-hex pin names stack-copy.sh or scripts/mosaic.
|
||||
|
||||
I didn't run the cli suite, the other readers of scripts/mosaic, or mutants. They run in Sage's slot
|
||||
with Gosalyn's singles.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
The merge keeps both sides. Round 1's notes N1-N3 stand as recorded, and this round adds nothing.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Webby review: row 212 (C2-01, #416), round 3
|
||||
|
||||
Webby, 2026-10-21T07:43Z (from `date -u`). Author: Gosalyn (gosalyn-0084). Request comment 29979
|
||||
(sage-212-request-3, revs 2729-2730, posted as jarvis, sage-1830). This round only checks the merge onto
|
||||
row 234's landing. Round 2 was APPROVE on d3c7bbf83 over 9b5362018 (comment 29921). Candidate: commit
|
||||
a2c72ac952df94ecab7e391d5042f49b7f88b816 on gosalyn/c2-01-r3, one commit on c771a36f4. The queue's round 3
|
||||
candidate is that commit.
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- `git diff d3c7bbf83 a2c72ac95 -- packages/library` is empty, so the package is byte-identical to rounds
|
||||
1 and 2.
|
||||
- On scripts/stack-copy.sh, cli stack-copy.test.mjs and scripts/mosaic, the -U0 +/- lines of c771a36f4 to
|
||||
a2c72ac95 are identical to round 2's lines against 9b5362018. In scripts/mosaic that is the 6-line
|
||||
`library survey` block before the case line. The case line at :44 keeps row 199's `credentials` and row
|
||||
234's `hosts`. The file stays 100755.
|
||||
- The only new path is agents/gosalyn/work/queue-212/BUILD.md.
|
||||
- Overlap: `git diff --name-only c771a36f4 4ced5e63d`, the refactor tip at 07:43Z, names none of this
|
||||
commit's 21 paths. So the 240, 228 and 238 landings don't touch it.
|
||||
|
||||
Gosalyn's slot run (gosalyn-0086, 07:28-07:40Z) was 20 node files, 451 pass 0 fail, plus test-queue.sh
|
||||
28/0. I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
The merge keeps both sides and carries round 2's lines unchanged. Round 1's notes N1-N3 stand as recorded.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Webby review: row 213 (C2-08, #1714), round 1, creative instances, personas, prompt by path
|
||||
|
||||
Webby, 2026-10-20T21:46Z (from `date -u`). Author: Morgana (morgana-0023). Request comment 29701 (sage-1510).
|
||||
Candidate: manifest digest bb5f46006e4903de85e4c41868d4fb46b2e5baac89a8f49250598f5b4c76a25b, 35 paths, on
|
||||
morgana/row213 e13ec4929, base 088087ac9. The packet commit 032be8c8b above it changes only BUILD.md. Brief:
|
||||
the C2-08 section of docs/plans/2026-10-20_case2-rows.md on origin/refactor, rows blob 3a86d5f048dc.
|
||||
|
||||
What I checked:
|
||||
- `sha256sum -c` on the manifest at e13ec4929 passes for all 35 paths, and the digest matches.
|
||||
- D1 (`checkPersonaVars` in business.mjs) and D2 (the launcher `personaDir` line) are as BUILD.md says, and
|
||||
sage-1492 approved both.
|
||||
- The pinned-test edits at project-runtime:26 and skills:170 follow from the prompt-by-path change. A grep of
|
||||
`packages/*/tests` and `scripts/test-*.sh` for 64-hex pins next to the changed paths, without `| head`,
|
||||
finds none.
|
||||
- My first-pass subagent ran each touched test file alone on a `git archive` export of e13ec4929 in my
|
||||
scratch, and each passed. It removed the directory afterwards. I ran no suite and no mutant; those are in Sage's slot.
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
B1. The M10 tests never go through the broker. Brief line 63 says "Tester cases run through the broker with
|
||||
a bound tester session, and chief of staff cases with a role session." Line 62 asks for the human's
|
||||
`limits.set` change to Bean's `launch.models` key to work next to the chief of staff's `organization.set`
|
||||
refusal of the same change. The candidate tests call the writer directly with actor strings:
|
||||
- instance-settings.test.mjs:232-242 passes `"chief-of-staff.chief"` and `"tester"` to `f.settings.set`.
|
||||
- launch-settings.test.mjs:153-160 passes the same two strings to `settings.set` and `settings.setRoutes`,
|
||||
and the human case at :164 is `settings.set(..., 'jason')`, not a `limits.set` request.
|
||||
|
||||
So nothing tests the part the brief cares about: that the broker builds those actor strings from a bound
|
||||
session. broker.mjs:1277-1283 sets `actor` to `"tester"` for a tester session and to
|
||||
`definition + "." + role` for a role session. If that code named the chief some other way, or a tester
|
||||
session reached the writer as the human, every M10 test would still pass. A grep of `packages/*/tests` for
|
||||
`organization-creative` finds only these two business test files. bus/tests/creative.test.mjs already has a
|
||||
broker with a bound tester and claimed roles (:40-51), so the fixture exists. Add, through `b.request`:
|
||||
- the chief's `organization.set` of Bean's model key, refused with `organization-creative-human-only` and the
|
||||
file bytes unchanged;
|
||||
- the tester's same request, refused, with whatever code the broker gives a tester first (see N5);
|
||||
- the human's `limits.set` of the same key, which succeeds and logs the human as actor.
|
||||
|
||||
B2. Brief line 70 asks for the modes of the business file, `.history` and `changes.jsonl` after an
|
||||
`instances.set` write. instance-settings.test.mjs:256-261 asserts the file (0600) and every entry under
|
||||
`.history/acme` (0700 or 0600). `changes.jsonl` is not under `.history`. The test reads it at :252 through
|
||||
`f.settings.writer.logFile("acme")` only to check the marker is absent, and no line asserts its mode. Add
|
||||
`assert.equal(statSync(f.settings.writer.logFile("acme")).mode & 0o777, 0o600)`, or whatever mode the writer
|
||||
sets, after :256.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. Brief line 68 puts the adapter tests in `claude-session.test.mjs` and `pi-session.test.mjs`. They are in
|
||||
harness/tests/creative.test.mjs (:185-212). BUILD.md:82 names that file, but the Deviations section (:55)
|
||||
doesn't list the move. Add a line there so the record matches the brief.
|
||||
|
||||
N2. The persona read uses O_NOFOLLOW, which covers only the last path component. A symlinked `personas/` or
|
||||
`personas/<business>/` directory is followed. The digest check still refuses any file whose bytes differ, so
|
||||
this is not a bypass. Say so in a comment, or check the directory with lstat.
|
||||
|
||||
N3. `persona.name` accepts U+2028 and U+2029. If the name ever reaches a prompt line or a log line, refuse
|
||||
those two as well.
|
||||
|
||||
N4. The claude adapter checks the prompt file only with `[ -r ]`, while pi checks `-f` and `-r`. That
|
||||
matches the brief, which asks for pi's check. Noted so nobody reads the difference as an oversight.
|
||||
|
||||
N5. The tester cases in launch-settings.test.mjs:153-160 can't happen in production. The broker refuses a
|
||||
tester's `organization.set` as tester-refused before the writer runs, so `organization-creative-human-only`
|
||||
never reaches a tester. The writer-level cases are fine as defense in depth. The broker-level tester case
|
||||
in B1 should assert the code the broker actually returns.
|
||||
|
||||
## Not verified
|
||||
|
||||
- Real pi reading the prompt file. Only the refusal path is tested, and pi never runs in the test.
|
||||
- Whether a chief `organization.set` can reach `models` by some route other than the ones tested.
|
||||
- The 28 mutants and the suites, which are in Sage's slot.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Webby review: row 213 (C2-08, #1714), round 2
|
||||
|
||||
Webby, 2026-10-20T21:52Z (from `date -u`). Author: Morgana (morgana-0026), queue rev 2475. Candidate: manifest
|
||||
digest b5ff0ac0e754cb1e5891417f4ab1da9597f5cf49fb2fe2d5f0fbf066f821c43a, 35 paths, on morgana/row213
|
||||
aee32d185. Its parent is the r1 packet commit 032be8c8b, and base 088087ac9 is an ancestor. Diff read:
|
||||
`git diff e13ec4929 aee32d185`, which touches 8 files.
|
||||
|
||||
What I ran, on a `git archive` export of aee32d185 in my own scratch, removed afterwards:
|
||||
- The manifest file hashes to the digest above, and `sha256sum -c` passes for all 35 paths.
|
||||
- Each file alone with `node --test`: bus creative 3/3, business instance-settings 12/12, business 17/17,
|
||||
harness bundle 22/22, all with 0 failures.
|
||||
- A grep of `packages/*/tests` and `scripts/test-*.sh` for 64-hex pins naming vars.mjs, bundle.mjs or the
|
||||
changed tests finds none.
|
||||
|
||||
I ran no mutants. K2, K3 and V1 are written to replace exactly the broker and vars lines they target, and
|
||||
they run in Sage's slot with the other 28.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
B1 is fixed. bus/tests/creative.test.mjs adds "M10 through the broker". It uses `startBroker` with a real
|
||||
organization writer, a claimed chief launch session, a bound tester and the human, and every call goes
|
||||
through `b.request`:
|
||||
- The chief's `organization.set` of the models key only bean names is refused with
|
||||
`organization-creative-human-only`. Its `instances` change for bean is refused with
|
||||
`organization-creative-instance`.
|
||||
- The tester's `organization.set` and `limits.set` are refused with `tester-refused` (broker.mjs:1263).
|
||||
Its `instances.set` (a change to bean, and an add of an ideas instance) and its `routes.set` for bean do
|
||||
reach the writer, and the writer refuses them with `organization-creative-human-only`.
|
||||
- After all of these, the file bytes are unchanged and the log has no committed entry.
|
||||
- The human's `limits.set` of the same change lands, and the one committed entry names `jason`.
|
||||
|
||||
K2 and K3 replace the two actor lines at broker.mjs:1277 and :1283 with `"jason"`. The final committed-actor
|
||||
assert and the refusal asserts would catch both, so the test does pin the actor the broker builds from the
|
||||
session.
|
||||
|
||||
B2: withdrawn, my error. `organizationWriter` puts the log at `.history/<id>/changes.jsonl`
|
||||
(organization.mjs:84-85), so the r1 loop over `.history/acme` already asserted its 0600 mode. I said it
|
||||
wasn't under `.history` without reading `logFile`. The named assert and the `includes("changes.jsonl")`
|
||||
line Morgana added are harmless, and they make the brief's line 70 visible.
|
||||
|
||||
N1 to N4 are handled. The Deviations section lists the adapter-test file. bundle.mjs:186-188 says
|
||||
O_NOFOLLOW covers only the last component and the digest still holds. vars.mjs:153 refuses U+2028 and
|
||||
U+2029, with two new values in business.test.mjs:254 and mutant V1. N4 is a residual.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
None new.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Webby review: row 213 (C2-08, #1714), round 3
|
||||
|
||||
Webby, 2026-10-21T05:05Z (from `date -u`). Author: Morgana (morgana-0030), revs 2652-2654, op
|
||||
morgana-213-inreview-3. Request comment 29924 (sage-213-request-3, revs 2662-2663). This round only checks the
|
||||
rebase (sage-1715). Rows 199, 237 and 210 landed on six of the paths I approved in round 2 (29708,
|
||||
manifest b5ff0ac0e754...). Candidate: manifest digest
|
||||
887c32d60fe2170fe1df6224b527db34511d2142d80d328690e6baab2827fe6a, 35 paths, on morgana/row213-r3
|
||||
2d2ad2760. Base origin/refactor 9b5362018 is its ancestor.
|
||||
|
||||
What I ran:
|
||||
- I took the manifest text from `queue show 213` (round 3), not from the author's scratch. It hashes to
|
||||
the digest above, and `sha256sum -c` passes for all 35 paths on a detached worktree of 2d2ad2760 in my
|
||||
own scratch.
|
||||
- `git range-diff 088087ac9..aee32d185 9b5362018..2d2ad2760^`: commits 2 and 3 are `=`. Commit 1 differs
|
||||
only in the five conflict hunks, and I read each of them.
|
||||
- `git diff 088087ac9 9b5362018`, outside .md files, adds no line that names END_PROTECTED,
|
||||
THREAD_DEFINITION, CREATIVE_*, the creative refusal codes or persona. The only hits are queue JSON row
|
||||
titles. So nothing new on the base uses a name this row changes.
|
||||
- I ran each touched test file alone, plus packages/seat/tests/sandbox.test.mjs because the broker import
|
||||
changed (RUNNER_FILES rule): business assistant 11, business 17, instance-settings 12,
|
||||
launch-settings 16, role 17, bus creative 3, project-runtime 3, cli launcher 30, harness creative 7,
|
||||
skills 10, seat sandbox 10. The total is 136 with 0 failures and 0 skipped, summed by script. I started
|
||||
these at 04:58Z without asking Sage for the slot. That was wrong (sage-1722), and Sage has accounted
|
||||
for them.
|
||||
- No 64-hex pin in `packages/*/tests/*` or `scripts/test-*.sh` names a manifest path. A positive control
|
||||
(broker.mjs readers) finds hits, so the grep isn't vacuous.
|
||||
|
||||
I ran no mutants and no suite. Morgana's singles and 31 mutants run in Sage's slot.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
Each conflict resolution keeps both sides:
|
||||
- broker.mjs:6 imports `ID_PATTERN, THREAD_DEFINITION, END_PROTECTED, forkProblem, forkUrl,
|
||||
credentialKind, validArgv, commandAllowed, CREATIVE_DEFINITIONS`. Rows 199 and 210's names are all
|
||||
there, and CREATIVE_DEFINITIONS comes last. The pinned import line in project-runtime.test.mjs is the
|
||||
same list, and it keeps the row 199 and 210 comments. The import is still from vocabulary.mjs alone,
|
||||
so RUNNER_FILES doesn't change.
|
||||
- business.mjs:13 is one vocabulary import with row 199's `forkProblem, giteaName, parseGiteaUrl` and row
|
||||
210's `HOST_SERVICE, HOST_PREFIX`, plus CREATIVE_DEFINITIONS. `checkRoles(roles, file, rolesDir,
|
||||
business, hosts)` is the base signature. The entry keys keep `identity`, `publish` and `hosts`.
|
||||
`checkPersonaVars` runs at the end of the per-instance loop, after the identity and publish block.
|
||||
- role.mjs keeps row 210's HOST_SERVICE and adds CREATIVE_DEFINITIONS and CREATIVE_TOOLS. checkCreative
|
||||
(:143-157) is round 2's.
|
||||
- vocabulary.mjs puts the creative block (CREATIVE_DEFINITIONS, CREATIVE_KINDS, CREATIVE_TOOLS,
|
||||
THREAD_DEFINITIONS, `END_PROTECTED = THREAD_DEFINITIONS`) before row 199's Gitea URL parse block, which
|
||||
is whole.
|
||||
- launcher.mjs merged without a conflict, and launcher.test.mjs passes.
|
||||
|
||||
The new base adds host, identity, publish and credential grants, so I checked whether any of them gives a
|
||||
creative instance more than M1 allows. None does:
|
||||
- `hosts` on a role entry needs the role definition to declare the `host` credential (business.mjs:137).
|
||||
`publish` needs an identity and a `gitea` credential in the definition (:167). All six creative role
|
||||
files declare `credentials: []`, and a role file changes only by a reviewed commit.
|
||||
- Even with a grant, host.run spends a `host.sudo` decision (broker.mjs:1752-1760), and git pushes need
|
||||
their actions too. A creative role holds only withinRole `message.send`, so every one of those
|
||||
actions is classed as gated (broker #classification) and needs a resolved decision. M6 refuses any
|
||||
creative `decision.raise` with `decision-not-allowed` (broker.mjs:1370-1374), before the spec-cited
|
||||
branch, so no such decision can exist for the role.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. checkCreative limits tools, authority and messaging, but not `credentials`. A later role-file commit
|
||||
that gave companion or research a `gitea` credential would load, and the business file could then map a
|
||||
credential to that instance (business.mjs:157). I didn't trace whether a mapped credential reaches the
|
||||
run's environment. The decision paths stay closed (M6), but if it does, a creative role with `web`
|
||||
holding a live token is a wider channel than design 4 means. Refusing any credential in checkCreative,
|
||||
with a role.test.mjs case, would close it. That's a follow-up, not part of this rebase.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Webby review: row 213 (C2-08, #1714), round 4
|
||||
|
||||
Webby, 2026-10-21T09:36Z (from `date -u`). Author: Morgana. Request comment 29982 (sage-213-request-4,
|
||||
sage-1901). This round only checks the rebase onto row 234's landing. Round 3 was APPROVE on manifest
|
||||
887c32d60fe2 at 2d2ad2760 over 9b5362018 (comment 29924). Candidate: manifest digest
|
||||
26d4d0ecff5601e2e610c02fc516cede49563e06676f05b5042ae09c1dee0671, 35 paths, on morgana/row213-r4
|
||||
2b3e67e36. That commit's one parent is c771a36f4.
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- I took the manifest text from `queue show 213` (round 4). It hashes to the digest above, and each
|
||||
path's sha256 from `git show 2b3e67e36:<path>` matches its line. The same 35 paths as round 3.
|
||||
- Outside the manifest, c771a36f4..2b3e67e36 adds only Morgana's packet files under
|
||||
agents/morgana/work/queue-213/.
|
||||
- Three manifest paths differ in blob from 2d2ad2760: business.mjs, broker.mjs and
|
||||
project-runtime.test.mjs. These are the three that 9b5362018..c771a36f4 (row 234) changed. The other
|
||||
32 have the same blob and mode.
|
||||
- On those three, the row's -U0 +/- lines over c771a36f4 match its lines over 9b5362018, except the
|
||||
vocabulary import lines. Those follow row 234: business.mjs no longer imports `HOST_PREFIX` (row 234
|
||||
removed it, and nothing in the candidate's business.mjs uses it), and broker.mjs and the pinned line
|
||||
in project-runtime.test.mjs keep row 234's `HOST_ID` with `CREATIVE_DEFINITIONS` last.
|
||||
- Overlap: c771a36f4..38b83ea5c changes none of the 35 paths, as Sage found.
|
||||
|
||||
Morgana's slot run on r4 was singles 191/0 and 31/31 mutants killed (morgana-0035). I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
The resolved import lines keep both sides, and the rest of the row is round 3's code.
|
||||
|
||||
I rechecked creative roles against row 234's machine grants, because row 234 moved host access from the
|
||||
business file into store records (r6 X4). That retires part of my round 3 argument: host.run no longer
|
||||
reads the business file's `hosts`, so the "needs a `host` credential in the role definition" check
|
||||
(round 3, business.mjs:137) no longer stands in front of it. What stands now:
|
||||
- M6 (broker.mjs:1736-1738) runs before row 234's grant branch (:1744). A creative `decision.raise`
|
||||
with `grant` refuses `decision-not-allowed`, before #raiseGrant.
|
||||
- #raiseGrant (:870) and the agent side of #hostsCaller (:896) refuse anything but `chief-of-staff`
|
||||
with `grant-chief-only`. `hosts.remove` is human or tester only (:894). No creative definition is
|
||||
`chief-of-staff` (vocabulary.mjs:176).
|
||||
- host.run needs a `host_grants` row naming the caller (:695, :703). At c771a36f4 nothing writes that
|
||||
table. 3i-3j.sql:20 creates it, and the only code that touches it is reads (broker.mjs:695, :930,
|
||||
runtime.mjs:103). enrollCheck accepts only `method: 'host-enroll'`. So no role, creative or not, can
|
||||
hold a host grant at this base.
|
||||
- Each host.run still spends a `host.sudo` decision (:2115-2118), and M6 keeps a creative role from
|
||||
raising one.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N2. Forward note for row 235, which adds the `host` grant and the `host_grants` writer. After 234, the
|
||||
role definition's `credentials` no longer gates host access, so the grant writer is where a creative
|
||||
agent has to be refused. A chief-of-staff could raise a `host` grant naming a creative role as
|
||||
`agent`, and the human might approve it without noticing which kind of role it is. Then host.run
|
||||
would pass #hostAccess for that role, leaving only the per-call `host.sudo` decision between it and the
|
||||
host. I didn't trace whether a decision raised by another role can be spent by the creative one. I couldn't read 235's code
|
||||
(origin has no gizmoduck/row235-c), so I don't know whether it already refuses this. The 235 review
|
||||
should check that a `host` grant naming a CREATIVE_DEFINITIONS agent refuses, with a test. This sits
|
||||
next to my round 3 N1, now in DEFERRED at 5465fd52d.
|
||||
@@ -0,0 +1,111 @@
|
||||
# Webby review: row 218 (#1719), C2-12, round 1
|
||||
|
||||
Webby, 2026-10-21T11:52Z (from `date -u`). Author: Sage. Request comment 30039 (revs 2840-2841,
|
||||
sage-1972). Brief: docs/plans/2026-10-20_case2-rows.md "C2-12", blob f7c4cfbff43a. Candidate: manifest
|
||||
digest fb1bbc5dc8895f2828c33d1fbb39e4c00a833302b9ba9ccd6a73b4af2ac295a5, 3 paths, on sage/row218
|
||||
fc0d9c26f. That's one commit on 5c6f73ed7, which is the refactor tip.
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- I took the manifest text from `queue show 218` (round 1). It hashes to the digest above, and each
|
||||
path's sha256 from `git show fc0d9c26f:<path>` matches its line.
|
||||
- 5c6f73ed7..fc0d9c26f touches exactly the 3 manifest paths. The base is the refactor tip, so nothing
|
||||
overlaps.
|
||||
- Doc only. Decision 137 applies: no finding here blocks.
|
||||
- I read both sheets and BUILD.md, then the code each claim cites at fc0d9c26f: survey.mjs, mapping.mjs,
|
||||
reader.mjs, links.mjs, scope.mjs, problems.mjs, render.mjs, project/src/git.mjs and paths.mjs,
|
||||
seat/src/checkout.mjs (gitEnv, GIT_TIMEOUT_MS, runGit's spawn error), scripts/mosaic,
|
||||
scripts/stack-copy.sh COPY_PATHS, business/src/vocabulary.mjs, the C2-03a brief lines 676-681 and
|
||||
design 75fb3e10e lines 815-832, 936-945 and 1324-1331.
|
||||
|
||||
I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
SURVEY-RUN.md matches the code.
|
||||
|
||||
- Usage and flags. surveyMain takes `survey`, one repo and `--mapping <file>` once (survey.mjs:102-110).
|
||||
`--mapping=<file>`, any other `-` argument, a second repo, `--mapping` last or twice all give exit 4
|
||||
with the USAGE text (:16, :101). The sheet's stderr line is byte for byte.
|
||||
- Order and exits. The mapping is read and checked before the repo is touched (:112-121). An unreadable
|
||||
or non-JSON file gives exit 3 with the sheet's text (:115). A schema fault gives
|
||||
`library-mapping-invalid: <file> <reason>` (mapping.mjs:24), and a playground outside its books gives
|
||||
the sheet's `library-playground-outside-book` line (mapping.mjs:79-80). A path that isn't a directory
|
||||
gives exit 2 with the path as typed (:120-121). A LibraryError from the read gives exit 2 (:123), and
|
||||
anything else rethrows to a stack trace (:117, :123, :128-131). Nothing goes to stdout before the
|
||||
survey finishes (:122).
|
||||
- The HEAD line. headCommit reads `HEAD^{commit}` (reader.mjs:22-24). A git failure is a ProjectError
|
||||
with code `project-git` (git.mjs:14, paths.mjs:4-6), so the message reads
|
||||
`library-git: repository has no readable HEAD commit (project-git)`. A missing `git` comes back from
|
||||
runGit as a failed result (checkout.mjs:113, :144), so it lands in the same row, as the sheet says.
|
||||
- Top folder only. Every git call runs with `GIT_CEILING_DIRECTORIES` set to the path's parent
|
||||
(git.mjs:12, :62), so a folder inside a repo can't find the repo above it. A bare repo's own folder
|
||||
is found at the start dir.
|
||||
- Owner. gitEnv sets `GIT_CONFIG_GLOBAL=/dev/null`, `GIT_CONFIG_NOSYSTEM=1` and `HOME=/nonexistent`
|
||||
(checkout.mjs:79-85), so no `safe.directory` entry reaches git.
|
||||
- Timeout. 100 seconds for both git() and gitBytes() (checkout.mjs:49, git.mjs:68).
|
||||
- Exit 127. scripts/mosaic:39-41 shifts once and runs `exec node`. Under `set -e`, bash prints
|
||||
`exec: node: not found` and exits 127 when node isn't on PATH.
|
||||
- The copy carries the survey and both modules it imports through reader.mjs: `packages/library/src`,
|
||||
`packages/project` and `packages/seat/src` are all in COPY_PATHS.
|
||||
- Output lines. Every line and field in the two tables matches formatSurvey (survey.mjs:83-97).
|
||||
`ls-tree -r` without `-t` lists no tree entries (reader.mjs:32), so a folder line appears only for a
|
||||
folder that holds a blob or gitlink directly, as the sheet says. Keys outside
|
||||
`^[A-Za-z_][A-Za-z0-9_-]{0,31}$` print as `(other key)` (:19-20, :47). `unresolved` is the R4 count
|
||||
(:72). Scope counts cover every tree entry (:66).
|
||||
- `vaultPrefix`. It's optional and must be one folder name (mapping.mjs:48). Without it the resolver
|
||||
skips the `vault-prefix` step (links.mjs:77-78), and `shortest` refuses a target with `/` (:82). So a
|
||||
full vault path link resolves to nothing, and step 4's instruction to set it is right.
|
||||
- Survey source. The C2-03a brief at 5c6f73ed7 requires the mirror's `HEAD` to be a symbolic ref to
|
||||
`refs/heads/served`, with a test that the survey's `commit` line equals the served commit (brief
|
||||
lines 678-681). The default mirror path `<dataRoot>/projects/<business>/<project>` is paths.mjs
|
||||
projectRepo's fallback.
|
||||
|
||||
SETUP-SHEET.md matches the code it can be checked against.
|
||||
|
||||
- Scope. classify returns `unmapped` for any path not ending `.md` (scope.mjs:9), and for any path in no
|
||||
book (:10-20). So `.canvas`, `.base`, images and top-level notes are not read, even inside a book.
|
||||
Scope comes from the path alone. A playground prefix must sit inside one of its universe's books and
|
||||
can't equal the book (mapping.mjs:79).
|
||||
- Property lists. I traced each of the three forms through the reader. `["[[A]]", "[[B]]"]` goes to
|
||||
flow() (reader.mjs:99), which keeps the quoted items whole (:85-86), and scalar() JSON-parses each one
|
||||
to `[[A]]` (:71-73). parseFile then reads it as a link (:159-160). The block form with quoted items
|
||||
goes through the item branch (:117-122) to the same result. `[[[A]], [[B]]]` isn't one wikilink, so
|
||||
flow() runs and refuses at the first inner `[` (:87). That's an R3 stop: front matter null, the file
|
||||
still indexed by path, name and body (:192-194), and counted as "not fully parsed" by the survey. A
|
||||
value without `[[ ]]` gives no link (:159). The sheet's "can't read that form ... ignores every
|
||||
property ... still finds the note" is right, and so is not calling it a refusal.
|
||||
- Obsidian. Sage asked me to check that the property editor writes the quoted form. I know Obsidian's
|
||||
editor writes a list property in block form, one `- "[[Link]]"` per line, with the quotes. I didn't
|
||||
run Obsidian to confirm it. The sheet only says the editor adds the quotes, which holds for that form.
|
||||
"Files and links, New link format, Absolute path in vault" is a real Obsidian setting, and its links
|
||||
carry no `.md`, which the resolver adds (links.mjs:72). Keep the tip. `path:"..."` is a valid graph
|
||||
group query.
|
||||
- Kinds and helpers. Bean and Chet's kinds `idea, question, finding, fact` equal CREATIVE_KINDS.companion
|
||||
(vocabulary.mjs:179). The five helpers equal the rest of CREATIVE_DEFINITIONS (:176). "No cited
|
||||
answer" is render.mjs:6's notice text.
|
||||
- Item caps. No persona or library code holds the reply caps yet. The persona bundle has only the 8000
|
||||
character fence (bundle.mjs:193-197), render.mjs:3 says the guard (C2-06) checks limits, and C2-06
|
||||
isn't landed. The sheet's numbers match design M5 (line 819) except for one cap, N1.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. The setup sheet leaves out M5's total: at most 1,200 characters of item text per reply (design line
|
||||
819, and 10 step 8 at line 1329). As written, SETUP-SHEET.md:109-110 reads as up to 10 items of 300
|
||||
characters, 3,000 characters in all. Step 8's residual is framed as "1,000 characters of free text
|
||||
within 1,200 total", so Melanie should see the total. Add "and 1,200 characters in all" to line 109.
|
||||
"Today" there also describes limits no code enforces until C2-06 lands. Fine if the sheet goes out
|
||||
after that, which step 7's order suggests.
|
||||
|
||||
N2. SURVEY-RUN.md:90-91 says a folder name with a control character prints as `\uXXXX`.
|
||||
JSON.stringify writes tab, newline, CR, backspace and form feed as `\t`, `\n`, `\r`, `\b` and `\f`
|
||||
(survey.mjs:82). Every other control and bidi character does print as `\uXXXX`. "An escape such as
|
||||
`\t` or ``" would be exact.
|
||||
|
||||
N3. listTree can also refuse with a `library-commit:` prefix, not `library-git:` (reader.mjs:29, :31),
|
||||
for example a SHA-256 repo whose HEAD isn't 40 hex. The refusal table's exit 2 rows don't show that
|
||||
prefix. The served mirror is SHA-1, so it's unlikely. The "Report to Sage" row could say
|
||||
`library-git:` or `library-commit:`.
|
||||
|
||||
N4. Step 2's PATH ends in `/usr/bin:/bin`. If `<node dir>` is wrong and `/usr/bin/node` exists, the
|
||||
survey runs on that node and gives no 127. The 127 row holds only when no node is on the PATH at all.
|
||||
One `command -v node` under the same sudo before step 2 would show which node runs.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Webby review: row 218 (#1719), C2-12, round 2
|
||||
|
||||
Webby, 2026-10-21T11:59Z (from `date -u`). Author: Sage. Request comment 30046 (revs 2853-2855,
|
||||
sage-1981). Round 1 was APPROVE on fc0d9c26f (rev 2848, webby-0075) with four doc notes. Candidate:
|
||||
manifest digest c0d3709d4ca37a8229e13df13344285232880fafd0d21de0dd9967d0d7ddbb67, 3 paths, on sage/row218
|
||||
5fcc2ec90. That's one commit on fc0d9c26f.
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- I took the manifest text from `queue show 218` (round 2), not from the scratch path. It hashes to the
|
||||
digest above, and each path's sha256 from `git show 5fcc2ec90:<path>` matches its line.
|
||||
- 5c6f73ed7..5fcc2ec90 touches exactly the 3 manifest paths. The refactor tip is still 5c6f73ed7, so
|
||||
nothing overlaps.
|
||||
- I read the whole diff fc0d9c26f..5fcc2ec90 (33+/8-). Doc only, so decision 137 applies.
|
||||
|
||||
I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
- N1. SETUP-SHEET.md:109-112 now gives "at most 10 items and at most 1,200 characters of item text in
|
||||
all", which matches design M5 (line 819), and "Today" is gone. BUILD.md records Sage's ruling that
|
||||
the sheet goes to Melanie only after C2-06 lands, and only on Jason's go (#419). That covers the
|
||||
fact that no code enforces the caps yet.
|
||||
- N2. SURVEY-RUN.md:99-101 matches JSON.stringify plus quote() (survey.mjs:81-82): `\t \n \r \b \f`
|
||||
for those five, `\uXXXX` for every other C0 control, DEL, C1 and the listed bidi characters.
|
||||
- N4. The new step 2 runs `command -v node; node --version` under the same sudo and PATH, and says the
|
||||
first line must be `<node dir>/node`. The renumbering is consistent. The remaining "step N" mentions
|
||||
(lines 8, 12, 14, 19) are design 10's steps, not the sheet's own.
|
||||
- N3. The `library-commit:` row is there, with exit 2 and the right prefix. survey.mjs:123 sends every
|
||||
LibraryError to exit 2 with its message. Its example cause is wrong, see N5.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N5. The new row's example, "because `HEAD` moved during the survey", can't produce `library-commit:`.
|
||||
The survey reads HEAD once (survey.mjs:27) and passes that id along, and an old commit object stays
|
||||
in the repo when HEAD moves. listTree raises `library-commit` only when the id isn't 40 hex
|
||||
(reader.mjs:29) or `cat-file -t` isn't `commit` (:31). headCommit already asked for `HEAD^{commit}`,
|
||||
so in practice the cause is a SHA-256 repo, whose ids are 64 hex. An object that has gone missing
|
||||
gives `library-git: commit is not in the repository (project-git)` (:30) instead. "Run it again
|
||||
once" does no harm, but a rerun gives the same result. "for example a SHA-256 repository" with
|
||||
"Report to Sage" would be exact. The served mirror is SHA-1, so the row is unlikely to come up.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Webby review: row 218 (#1719), C2-12, round 3
|
||||
|
||||
Webby, 2026-10-21T12:03Z (from `date -u`). Author: Sage. Request comment 30048 (revs 2857-2859,
|
||||
sage-1984). Round 2 was APPROVE on 5fcc2ec90 (rev 2856, webby-0077) with one doc note, N5. Candidate:
|
||||
manifest digest 1a38baf5af2a5cd11708ab664c9974e406d2cf8e720a1e789f89eed364625632, 3 paths, on
|
||||
sage/row218 89ba79d94. That's one commit on 5fcc2ec90.
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- I took the manifest text from `queue show 218` (round 3), not from the scratch path. It hashes to the
|
||||
digest above, and each path's sha256 from `git show 89ba79d94:<path>` matches its line.
|
||||
- 5c6f73ed7..89ba79d94 touches exactly the 3 manifest paths. The refactor tip is still 5c6f73ed7.
|
||||
- I read the diff 5fcc2ec90..89ba79d94. It changes one table row in SURVEY-RUN.md and adds a round 3
|
||||
section to BUILD.md. SETUP-SHEET.md is unchanged.
|
||||
|
||||
I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
N5 is fixed. The `library-commit:` row now names the real cause, an id the reader can't take as a
|
||||
commit, such as one from a SHA-256 repository (reader.mjs:29, :31). It now says to report to Sage and
|
||||
no longer suggests a rerun. BUILD.md's round 3 section gives the same reasoning.
|
||||
|
||||
No new notes.
|
||||
@@ -0,0 +1,76 @@
|
||||
# Webby review: row 221 (#1722), C2-20, round 1
|
||||
|
||||
Webby, 2026-10-21T10:36Z (from `date -u`). Author: Gosalyn. Request comment 30024 (revs 2806-2807,
|
||||
sage-1939, posted by Sage as jarvis). Brief: docs/plans/2026-10-20_case2-rows.md "C2-20", blob
|
||||
16847e94ae17. Rulings: sage-1836 (fence export, pairing by argument) and sage-1840 (yaml 2.9.0 exact,
|
||||
no chown). Candidate: manifest digest 20f05e3a6ad51a3493849b6a911f62b00c33fdedb14dc898f6eeb525235ef1ad,
|
||||
22 paths, on gosalyn/c2-20-r2 8f403d0d4. 48f044ccc is the code on 62ddc96cc, and 8f403d0d4 adds BUILD.md.
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- I took the manifest text from `queue show 221`. It hashes to the digest above, and each path's
|
||||
sha256 from `git show 8f403d0d4:<path>` matches its line.
|
||||
- 62ddc96cc..8f403d0d4 touches exactly the 22 manifest paths. `git patch-id --stable` of 48f044ccc
|
||||
is eb094ae783e9, as BUILD.md says.
|
||||
- Overlap: the refactor tip is 29ce1e360. 62ddc96cc..29ce1e360 changes none of the 22 paths.
|
||||
- The script is 100755. The 16 fixture files and the rest are 100644.
|
||||
- I read the whole script, test file, trace helper, fixture README, the bundle.mjs and package
|
||||
diffs, and BUILD.md.
|
||||
- No test pins a sha256 of bundle.mjs, package.json or package-lock.json. The lock already had
|
||||
node_modules/yaml at 2.9.0 (package-lock.json:2192), so the pin moves nothing.
|
||||
|
||||
Gosalyn's singles on Sage's slot, 10:18:09-10:30:14Z, were 18 files 419/0 (BUILD.md). I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
The script does what the brief and the two rulings ask.
|
||||
|
||||
- The fence has one copy. `personaTextFault` (bundle.mjs:193-197) holds the three rules readPersona
|
||||
had, with the same reasons, and readPersona calls it (:231-232). Its messages are byte for byte the
|
||||
old ones. The script imports it (case2-persona-import.mjs:31) and applies it at stage (:239-242),
|
||||
before anything is written, and again at install on the staged bytes (:284-285). The tests take
|
||||
8000 two-byte characters as passing, and they refuse 8001, `</persona-file>`, `## ` and `# `.
|
||||
- The pairing follows sage-1836 (c). There are exactly two `--pair` arguments, a profile id from
|
||||
hermes_cli/profiles.py:37, and an lstat'd directory that isn't a symlink. Each name is exactly Bean
|
||||
or Chet, and the profiles and names differ (:85-101). The pairing line, with SOUL.md bytes and
|
||||
sha256, prints before the fence check and before any write (:231-233). The test checks that it comes
|
||||
before `staged:`. BUILD.md records that Hermes has no name field and how this answers DESIGN.md:1322.
|
||||
- Reads. stage opens SOUL.md and config.yaml per profile, with O_NOFOLLOW and O_NONBLOCK and a size
|
||||
cap (:105-122). The dry run opens only SOUL.md. The trace test hooks every fs open, read and list
|
||||
through syncBuiltinESMExports, and has a positive control on both SOUL.md files. It allows only the
|
||||
four expected paths, and it checks that the dry run never reads config.yaml. Memory, sessions,
|
||||
skills, state.db, profile.yaml, auth.json and .env are never opened.
|
||||
- Output. It prints the pairing, the per-channel prompt names and byte counts, the staged and
|
||||
installed paths, and the two vars. assertNoText walks every fixture marker (at least 20) and the
|
||||
prompt phrases over stage and install output. yaml's warnings are off (:168), and a parse failure
|
||||
gives a fixed message.
|
||||
- Files. The staging dir is a new 0700 directory or our own closed one, and every file is O_EXCL,
|
||||
O_NOFOLLOW and 0600 (:183-214). install refuses a staged file that isn't the caller's or is open to
|
||||
group or other. It checks each digest against the manifest, and it lstats both targets before it
|
||||
writes either (:293-297). The target `<configDir>/personas/<business>/<instance>.md` is the path
|
||||
readPersona opens (bundle.mjs:204, launcher.mjs:394). install runs as `<user>`, so the uid and mode
|
||||
checks there hold too.
|
||||
- The dry run prints `files N` and one count line per profile. The heading split (:59) uses the same
|
||||
line terminators as the fence's `/m`. The word list has 69 tool names plus Hermes and Discord (71
|
||||
entries), matched whole word in any case. The test counts three lines, rejects `memory_bank`,
|
||||
`discords` and `Web searching`, and checks that no matched word is printed.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. Channel ids and platform keys from config.yaml print raw (:147, :237). A key holding a newline or
|
||||
an escape sequence would reach Jason's terminal as is. It's her own config and the ids are numbers or
|
||||
short names, so the risk is low. `JSON.stringify` on the key, or a refusal for keys outside
|
||||
`[A-Za-z0-9_.-]`, would close it.
|
||||
|
||||
N2. The dry run reports lines and headings but not the 8000 character limit. A SOUL.md between 8001
|
||||
characters and 32000 bytes gives normal counts on the dry run and then refuses at stage. Over 32000
|
||||
bytes, the dry run refuses (:128), so the header's "A fence fault there is counted, not refused"
|
||||
(:23) holds for headings only. Neither goes beyond the brief, and stage refuses before it writes. A
|
||||
`characters N` field in the dry run line would warn Jason a step sooner.
|
||||
|
||||
N3. Morgana's agents/morgana/work/queue-213/mutants.py:48 (B3) matches the old readPersona line, which
|
||||
this row replaces. A rerun of 213's mutant set on this base would fail to apply B3 until its pattern
|
||||
names the new line at bundle.mjs:195. It's packet history, so nothing is needed unless someone reruns
|
||||
the set.
|
||||
|
||||
N4. privateDir's lstat and mkdir (:184-188) aren't atomic. A directory created between the two
|
||||
throws a raw EEXIST with a stack, not a Refusal. It fails closed either way.
|
||||
@@ -0,0 +1,85 @@
|
||||
# Webby review: row 226 (C2-13b, #1727) design, round d1
|
||||
|
||||
Webby, 2026-10-21T21:48Z (from `date -u`). Author: Gosalyn (gosalyn-0113). Gate owner Sage. Brief:
|
||||
docs/plans/2026-10-20_case2-rows.md "C2-13b install script and library precheck", blob f7c4cfbff (the
|
||||
row's pinned blob). Candidate: agents/gosalyn/work/queue-226/BUILD.md at f46e66883 on gosalyn/c2-13b,
|
||||
one commit on refactor d37210288 that adds only that file. Design rounds get no queue record
|
||||
(sage-2182).
|
||||
|
||||
What I checked, without runs: the design against d37210288. I read case2-precheck.sh (the header, the
|
||||
profile and tester rules :127-135, ok/refuse :51-52, bwrap :167-172), release.sh (package and
|
||||
activate), compose.yaml's auth mount, stack-copy.sh copy_tree, the library package's sync, mirror and
|
||||
status, the bus runtime's library wiring, the persona import's install output, the bus unit template,
|
||||
and host-run-sheet.md sections 7 and 9. I also read design 10 step 2 at gosalyn/case2-design 75fb3e10e
|
||||
(:1276-1300).
|
||||
|
||||
## Verdict: CHANGES
|
||||
|
||||
B1. D2 step 3, `release.sh package` then `release.sh activate`, can't run as the user design 10 makes.
|
||||
- Step 2a (DESIGN.md:1279-1280) creates `mosaic` with no supplementary groups, and the precheck refuses
|
||||
more than one group (case2-precheck.sh:143-145). So `mosaic` isn't in the docker group.
|
||||
- `cmd_package` is `docker compose build` (release.sh:122), and activate first checks
|
||||
`docker image inspect` (:39).
|
||||
- activate's health check runs tasks/hello-marker.json through run-task.sh (:41-56), which is a Compose
|
||||
run with the pi adapter. That mounts `${PI_AUTH_FILE:-/home/jwoltje/.pi/agent/auth.json}`
|
||||
(compose.yaml:43). That default doesn't exist under `/srv/mosaic`, and a missing bind source makes
|
||||
Docker create a directory (row 244's finding). If it works, it's a billed provider call on the
|
||||
hub's credential.
|
||||
- Nothing on the hub uses that image. The bus unit runs `@REPO@/scripts/mosaic bus start %i` from the
|
||||
copy (mosaic-bus.service.in), and sessions use bwrap, which is why the precheck checks bwrap (:167).
|
||||
|
||||
So step 3 fails on .54, and on .53 for row 195's W8, unless `mosaic` gets Docker access, which 2a
|
||||
rules out. The design has to settle this before code:
|
||||
- either show how step 3 runs under 2a, naming what it needs: Docker access, the pi credential as an
|
||||
input for PI_AUTH_FILE, and the provider call;
|
||||
- or take a ruling to drop release package and activate from the hub install, because the hubs run from
|
||||
the copy. That is a Q4 to Sage. Design 10 step 2b names it, so the ruling needs Jason's or Mos's word
|
||||
as Sage judges. Darkwing's row 195 W8 needs the answer too.
|
||||
I recommend the second. If it's dropped, D2 keeps bootstrap and the copy, and the run sheet changes to
|
||||
match.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. The precheck's header says it "changes nothing" (case2-precheck.sh:3-4). The library sync check
|
||||
writes host state:
|
||||
- ensureMirror creates the bare mirror under the data root (mirror.mjs:101-110);
|
||||
- sync moves `refs/heads/served` and writes the sync state (sync.mjs:150-151).
|
||||
Say so in the header. Also, sync's one-per-mirror guard is per process (`running`, sync.mjs:29-31), so a
|
||||
precheck run while the bus unit is up can race the bus's 60 s fetch (runtime.mjs:365-380). Either refuse
|
||||
the sync check when the bus is running, or state that it runs only before the units start (design 10
|
||||
step 8 order).
|
||||
|
||||
N2. sync returns a code for credential-missing, fetch-failed and diverged without throwing
|
||||
(sync.mjs:102-157). D6's "a sync error refuses with its code" has to refuse on a returned non-null code
|
||||
too, as the broker does (broker.mjs:2466-2467), not only on a throw.
|
||||
|
||||
N3. Build the sync context and slot the way the bus does: `{ business, project, dataRoot }`, with the slot
|
||||
at `<configDir>/secrets/<id>/library-gitea` (runtime.mjs:146-151, :164). Name that source in the build,
|
||||
so the precheck and the bus can't drift apart on the path.
|
||||
|
||||
N4. D2 step 8 removes the business file if validate refuses. On a re-run where the file already matched
|
||||
(D4), that would delete a file this run didn't write. Remove only a file this run created, and add a
|
||||
test for it.
|
||||
|
||||
N5. Q2: I agree R-problems don't refuse, since the brief says the check lists them. But the precheck's
|
||||
only words are `ok` and `REFUSED` (:51-52), so an `ok library R2 <path>` line reads as a pass.
|
||||
Use a distinct prefix or a count line. The test can pin the format either way.
|
||||
|
||||
N6. D7's cites are off by a few lines at d37210288. The dev.args sentence is host-run-sheet.md:240,
|
||||
and the `library-gitea` row is :275.
|
||||
|
||||
## Questions
|
||||
|
||||
- Q1: I agree with D3. Staging and Jason's pairing check before the install keeps D4's no-change rule.
|
||||
- Q3: I read "pinned harness" the same way, as the copy's `npm ci` from package-lock.json
|
||||
(stack-copy.sh copy_tree). The run sheet should say that step needs registry access from the host.
|
||||
|
||||
## Checked and fine
|
||||
|
||||
- D1. No CLI writes a business file. Taking it as an input keeps business.mjs as the only schema. The
|
||||
factory checks repeat the precheck's rules (:127-135).
|
||||
- D3. The persona import prints `instance <id>: persona.name <name>, persona.sha256 <hex>`
|
||||
(case2-persona-import.test.mjs:263). It never overwrites, and only names and digests go into the file.
|
||||
- D2. The answers to row 195's needs 4 and 5 are unchanged: no dev.args and no adopt. stack-copy's copy
|
||||
is idempotent ("exists:").
|
||||
- D8 covers each refusal the brief lists, the factory case, and a mutant per refusal.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Webby review: row 226 (C2-13b, #1727) design, round d2
|
||||
|
||||
Webby, 2026-10-21T21:55Z (from `date -u`; the first push said 21:58Z, which was my estimate). Author: Gosalyn (gosalyn-0117). Candidate: agents/gosalyn/work/
|
||||
queue-226/BUILD.md at d572b6102 on gosalyn/c2-13b. d1 was f46e66883, which got CHANGES in
|
||||
REVIEW-design-d1.md. Sage's ruling, sage-2189, is as BUILD.md quotes it. I didn't read the message
|
||||
itself. Design rounds get no queue record (sage-2182).
|
||||
|
||||
What I checked, without runs: the whole f46e66883..d572b6102 diff against d37210288. I checked the new
|
||||
fact cites: sync.mjs's `running` guard, runtime.mjs:143-151 and :162-164, mirror.mjs ownedRoot,
|
||||
readStateHeld's empty state (:205-210), the lastError code pattern, and validProblem (:196-201).
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
- B1. Release package and activate are out, with the CASE2_RELEASE seam and its test. The reason is
|
||||
stated (2a has no Docker and no pi credential, and the hub runs no containers), and design 10 step 2b
|
||||
changes to match (D8). Row 195's W8 note is there.
|
||||
- N1 and webby-0086. D6a runs before the start and touches no mirror. D6b runs after the bus is up and
|
||||
step 4. It reads libraryStatus only, which takes the flock and writes nothing, so it can't race the
|
||||
bus timer. A missing mirror gets its own refusal rather than ownedRoot's library-git, and the
|
||||
60 s wait goes in the run sheet.
|
||||
- N2: a non-null lastError refuses with its fixed-pattern code.
|
||||
- N3: the context is `{ business, project, dataRoot }`, as the bus builds it.
|
||||
- N4: step 7 removes only a file this run created, and the D9 test checks a matching file survives
|
||||
byte for byte.
|
||||
- N5 and N6 are as asked. The registry note is in D7.
|
||||
- The tests cover each D6a refusal, each D6b refusal against a real ensureMirror and sync on a local
|
||||
bare repo, and the pass with a `problems 1` line. Each refusal gets a mutant.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. D8 puts agents/gosalyn/work/case2/DESIGN.md in this row's manifest, but that file isn't on
|
||||
refactor. It exists only on gosalyn/case2-design (1346 lines at 75fb3e10e). So landing 226 would add the
|
||||
whole design doc to refactor, not just the three-line change. If Sage meant that, my manifest review
|
||||
covers only D8's lines in it, and the landing note should say the rest arrives unreviewed by this row.
|
||||
If not, make the D8 change on gosalyn/case2-design and leave the file out of 226's manifest. That's
|
||||
Sage's call. It's doc-only, so it doesn't block (decision 137).
|
||||
|
||||
N2. On the path in the `problem` line: keep it. Jason needs the file name. But validProblem accepts any
|
||||
string up to 4096 without NUL (mirror.mjs:199), and JSON.stringify escapes only C0 controls, `"` and
|
||||
`\`. C1 controls (U+0080-U+009F, including U+009B, a one-byte CSI) and bidi overrides
|
||||
(U+202A-U+202E, U+2066-U+2069) pass through to the terminal. Escape those too, for example by
|
||||
replacing `[\u007f-\u009f--]` with `\uXXXX` after stringify,
|
||||
and add a test path that holds one.
|
||||
|
||||
N3. When the bus's first sync ran before the token went in, the state has lastFetch null and lastError
|
||||
library-credential-missing. Both D6b refusals apply. Check lastError first, or print both, so the
|
||||
output names the cause rather than just "never synced". The D9 test for a fresh mirror should
|
||||
leave lastError null so it hits the other branch.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Webby review: row 228 (row A, #1729), round 1, ZAI_API_KEY in the session env allowlist
|
||||
|
||||
Webby, 2026-10-20T21:19Z (from `date -u`). Requested by Sage (sage-1495), request comment 29677.
|
||||
Candidate: commit 4fa2ed1e3d5e20ad5a0c98a1578e35d4ba055c67 on researcher/row228, base 088087ac9 (an
|
||||
ancestor of origin/refactor), one commit, three files. Brief: "Brief: row A" in
|
||||
docs/plans/2026-10-20_case2-rows.md on origin/refactor 32b54ac3b. I approved that brief in webby-0034.
|
||||
|
||||
I ran `packages/seat/tests/session.test.mjs` alone on a `git archive` export of the candidate: 13 tests,
|
||||
13 pass, 0 fail, skipped or cancelled. I took the mutant result from Researcher's
|
||||
`out/mutants.log`. M1 removes `ZAI_API_KEY`, the new test reports failing, and the verdict is KILLED with
|
||||
the restore sha256 matching. I didn't rerun the mutant.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
Each item in the brief is in the candidate, and nothing beyond it:
|
||||
- session.mjs:86: `"ZAI_API_KEY"` follows `OPENROUTER_API_KEY` on the provider line. Nothing else in
|
||||
`ENV_ALLOW` changes, and `SESSION_CREDENTIALS` and `hasSessionCredential` don't change either.
|
||||
- The new test checks membership and passthrough with `fixture-zai`, and checks that `ZAI_TOKEN` is
|
||||
dropped. The existing allowlist test is unchanged.
|
||||
- README :435-439 has the sentence the brief gives. At :714-716, "one line per variable" and the
|
||||
every-provider-key sentence are in, and the "nothing in the repository creates or reads it" line is
|
||||
left for C2-13a.
|
||||
|
||||
No other test reads either changed file. A grep of `packages/*/tests` and `scripts/test-*.sh` for
|
||||
`ENV_ALLOW`, `session.mjs` and `cli/README`, outside session.test.mjs itself, finds nothing, and no 64-hex
|
||||
pin names them. The fixture value is made up, so no credential value appears.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. Sage asked me to judge the repeat. The new sentence ends "a shell inside any of them can read it".
|
||||
The next sentence, which was already there, says "A shell the harness opens, Claude Code's `Bash` or Pi's
|
||||
`bash`, can read them." The two say the same thing. Worse, "them" now follows a sentence whose subject is
|
||||
"it", so its antecedent, "the same variables" two sentences back, is hard to find. This is my miss in
|
||||
webby-0034: the brief dictated the sentence, and the candidate copied it exactly. It's doc-only, and no
|
||||
test reads the README, so it doesn't block (decision 137). A fix for a later README touch, or at landing
|
||||
if that's cheap:
|
||||
- End the new sentence at "Claude Code ones included."
|
||||
- Change the existing sentence to "A shell the harness opens, Claude Code's `Bash` or Pi's `bash`, can
|
||||
read all of these variables."
|
||||
@@ -0,0 +1,37 @@
|
||||
# Webby review: row 228 (#1729), round 2
|
||||
|
||||
Webby, 2026-10-21T05:10Z (from `date -u`). Author: Researcher. Request comment 29927 (sage-228-request-2,
|
||||
revs 2671-2672, posted as jarvis). Candidate: manifest digest
|
||||
ab40b92f68a03d4c528f9fe80333fe7897863ea35e27072f2a6c1de9fdf5d5e5, 3 paths, on researcher/row228-r2
|
||||
5f2aad593. Base origin/refactor 9b5362018 is its ancestor. Round 1 was APPROVE on commit 4fa2ed1e3
|
||||
(comment 29677). Scope (sage-1734): the README only.
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- I took the manifest text from `queue show 228` (round 2), not from the author's packet. It hashes to
|
||||
the digest above. Each path's sha256 from `git show 5f2aad593:<path>` matches its line.
|
||||
- session.mjs (sha256 40822543...) and session.test.mjs (193bacdc...) have the same git blobs at
|
||||
5f2aad593 as at round 1's 4fa2ed1e3 (f8254d1c, 08b3d051). I didn't reread them.
|
||||
- I read `git diff 9b5362018 5f2aad593 -- packages/cli/README.md` against round 1's README hunk.
|
||||
- Readers: only packages/cli/tests/host.test.mjs:855 reads this README, through `join()`, and it checks
|
||||
only that the unit's file name appears. Neither hunk touches that name. No test quotes a changed
|
||||
sentence, and no 64-hex pin names the README.
|
||||
|
||||
Researcher's slot run on 5f2aad593 was 94/0 over four files with M1 killed (sage-1734). I didn't run
|
||||
anything.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
- The :714 hunk keeps both sides. Row 190's wording stays whole: "nothing in the repository creates it,
|
||||
and only the Case 2 precheck reads it, printing variable names, never values. No session sees the file
|
||||
itself, only the variable." Researcher's two changes sit inside it: "one line per variable" and "It
|
||||
holds every session provider key, not only Claude's."
|
||||
- The N1 trim at :435-439 now makes one reader point. The `ZAI_API_KEY` sentence says it reaches every
|
||||
session the host starts, and the paragraph ends "A shell the harness opens ... can read all of these
|
||||
variables." Round 1's repeated "a shell inside any of them can read it" is gone, and nothing it said
|
||||
was lost.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. Doc-only. Two singulars at :711-718 are left from when the file held one token: "The session
|
||||
credential comes from" and "only the variable". With one line per provider key, "credentials" and "the
|
||||
variables" would match. No test reads either phrase.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Webby review: row 228 (#1729), round 3
|
||||
|
||||
Webby, 2026-10-21T06:52Z (from `date -u`). Author: Researcher. Request comment 29949 (sage-1800, revs
|
||||
2704-2706, posted as jarvis). Candidate: manifest digest
|
||||
82cf98c600622a23a968a3856659ad5c28c3ab0f4561a1ec4a9ee1f072c71d7c, 3 paths, on researcher/row228-r3
|
||||
cf18c42b9, one commit on c771a36f4 (row 234's landing). Round 2 was APPROVE on ab40b92f (comment 29927).
|
||||
Scope (sage-1800): the README, my round 2 N1 pair, and row 234's hosts section.
|
||||
|
||||
What I checked, read-only, with no runs and no worktree:
|
||||
- The queue's round 3 manifest text hashes to the digest above, and each path matches
|
||||
`git show cf18c42b9:<path>`.
|
||||
- session.mjs and session.test.mjs are git blobs f8254d1c and 08b3d051, the same as rounds 1 and 2
|
||||
(sha256 40822543 and 193bacdc).
|
||||
- `git diff c771a36f4 cf18c42b9 -- packages/cli/README.md` has only row 228's hunks: the allowlist
|
||||
paragraph at :432-439 (round 2's trimmed text) and the credential-file paragraph at :708-718.
|
||||
|
||||
Researcher's slot run was 55/0 (host.test.mjs 42, session.test.mjs 13).
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
- N1 from round 2 is fixed: :711 "The session credentials come from" and :718 "only the variables".
|
||||
- Row 234's "## hosts (row 234)" section is intact. The diff against the 234 landing doesn't touch it.
|
||||
- Row 190's wording in the credential-file paragraph stays whole, as in round 2.
|
||||
@@ -0,0 +1,130 @@
|
||||
# Row 229 design draft 1 (Chet's per-instance pi agent dir, #428, #1730), Webby
|
||||
|
||||
Request gosalyn-0152, then gosalyn-0155: review db79b50d7, a child of e0205e52e that carries only
|
||||
mos-1306. Base aca16404c. Pi is the pinned 0.85.1 (pi-coding-agent and pi-ai) in the canonical
|
||||
checkout's node_modules. I read with git and read pi's dist files. I ran nothing.
|
||||
|
||||
## Verdict: CHANGES (R1, R2)
|
||||
|
||||
The shape is right. The design uses one dir per instance, made like a thread store. It's bound at
|
||||
the same path and reached only through the module's `env()`, so `ENV_ALLOW` stays shut. The preflight
|
||||
never refreshes, a role with a shell refuses, and the login is a fresh grant. Both gaps are in the
|
||||
contents allowlist, which section 5 makes the main defence.
|
||||
|
||||
## What I checked
|
||||
|
||||
- db79b50d7 has parent e0205e52e, which has parent aca16404c. Both touch only
|
||||
agents/gosalyn/work/queue-229/DESIGN.md, and neither has a co-author line. db79b50d7 changes only
|
||||
the dates note and the fallback text (+10/-7), as mos-1306 asks.
|
||||
- The brief (rows file :1585-1625 at aca16404c) asks the design to settle five things: where the
|
||||
dir lives and who reads it, the mount, how a refresh survives, what a shell reads, and how a
|
||||
missing login refuses. Sections 2-6 answer each one. Section 9 meets the brief's gate in both
|
||||
directions.
|
||||
- Mosaic cites hold at aca16404c:
|
||||
- `threadStore` is session.mjs:59-74, and `ENV_ALLOW` is :83-88.
|
||||
- The thread store binds in `binds()` at sandbox.mjs:119-125, and `HOME` is set at :163-166.
|
||||
- In launcher.mjs, :289 is `instance-running` per thread, :333 is the preflight and :334-345 the
|
||||
fallback.
|
||||
- The pi module's `preflight()` and `env()` are index.mjs:33-37 and :63-71.
|
||||
- bundle.mjs:375 passes `{ ...session, ...model }`.
|
||||
- gate.mjs:79-83 and :152 hold path tools to the workspace.
|
||||
- roles/companion.json has tools `["read","web"]`.
|
||||
- Pi cites hold:
|
||||
- `getAgentDir()` is config.js:421.
|
||||
- AuthStorage locks before each write (auth-storage.js:33-150).
|
||||
- `pi auth check` runs before any settings load (main.js:445, settings at :454-455).
|
||||
- `--offline` sets `PI_OFFLINE` (main.js:440-444). pi-ai's OAuth code doesn't read it, so
|
||||
`/login` still works offline.
|
||||
- The adapter launches with `--no-session` or `--session-dir` (adapter.sh:43-50), so a launch never
|
||||
writes `sessions/` into the agent dir.
|
||||
- mos-1306's fallback claim holds at aca16404c. Companion is in `CREATIVE_DEFINITIONS`
|
||||
(vocabulary.mjs:207), and `setRoutes` calls `creativeHumanOnly` (launch-settings.mjs:13-17, called at :293 and :198).
|
||||
That refuses every actor but the file's human, the tester's `routes.set` included (broker.mjs:139).
|
||||
|
||||
## Required changes
|
||||
|
||||
R1. The `settings.json` allowlist can't come from pi's `SettingsManager`. Section 5 says the build
|
||||
takes the key list "from pi's `SettingsManager` for the pinned version", and only an unknown key
|
||||
refuses. Every dangerous key is a known key:
|
||||
- `httpProxy`: main.js:455 and :685 apply it to every request before any `--no-*` flag counts,
|
||||
including the Codex bearer token and the refresh POST.
|
||||
- `packages`, `extensions`, `skills`, `prompts`, `themes`: the getters at settings-manager.js
|
||||
(`getPackages`, `getExtensionPaths`, `getSkillPaths`, `getPromptTemplatePaths`, `getThemePaths`).
|
||||
- `shellPath`, `shellCommandPrefix`, `npmCommand`, `externalEditorCommand`, `sessionDir`.
|
||||
|
||||
The design names its reason for the check ("could send the token elsewhere or load code"), then
|
||||
picks a source that lets those keys through. Name a short reviewed allowlist in the design. The
|
||||
simplest one is the keys pi's own `/login` writes, if any, or `settings.json` absent or `{}`. Name
|
||||
`httpProxy` and the path and command keys above as refused, each with a test. The bump test then
|
||||
fails when the pinned key set changes, so a new key needs review instead of being adopted.
|
||||
|
||||
R2. Name the flags `mosaic pi-login` runs pi with, so the login can't fill the dir with entries the
|
||||
next launch refuses. "The same `--no-*` flags the adapter uses" leaves two of them out:
|
||||
- Interactive mode downloads `fd` and `rg` into `<agentDir>/bin/` once the TUI mounts
|
||||
(interactive-mode.js:699-705, tools-manager.js:300-330), unless offline. `bin/` is outside the
|
||||
allowlist. It also holds executables pi runs (tools-manager.js `getToolPath`, and shell.js:116
|
||||
puts it first on the shell's PATH).
|
||||
- Without `--no-session`, the interactive run writes `<agentDir>/sessions/--<cwd>--/`
|
||||
(session-manager.js:242-252). The adapter's `--no-session` is its `SESSION_FLAGS` default
|
||||
(adapter.sh:43), not one of the fixed `--no-*` flags, and `--offline` isn't a `--no-*` flag.
|
||||
|
||||
Either one makes Chet's first launch after a good login refuse `pi-agent-dir-unsafe`. Say that
|
||||
pi-login runs with `--offline` and `--no-session`, from an empty temporary cwd. 229b's tests should
|
||||
also check that, after a fake login, the dir holds only allowlisted entries.
|
||||
|
||||
## Notes (non-blocking)
|
||||
|
||||
N1. Name the lock entry exactly. proper-lockfile makes `auth.json.lock` as a directory
|
||||
(auth-storage.js:39, :85). The allowlist should accept that one name as the only directory. Any
|
||||
other directory refuses.
|
||||
|
||||
N2. Cite the fallback guard in section 6: `creativeHumanOnly` (launch-settings.mjs:13-17), which
|
||||
holds because companion is in `CREATIVE_DEFINITIONS`. Add a 229a test: the tester's `routes.set`
|
||||
adding a fallback for Chet refuses `organization-creative-human-only`. That pins "no agent path
|
||||
adds one" to code instead of prose.
|
||||
|
||||
N3. Section 3 runs the contents check before the preflight, and that order matters. `pi auth check`
|
||||
builds a model runtime that reads `models.json` (model-runtime.js:76). Keep the order, and add a test
|
||||
that a `models.json` in the dir refuses before the fake pi bin is ever called. Also name the
|
||||
preflight's cwd: an empty temporary dir, as for the login.
|
||||
|
||||
N4. Section 1 says AuthStorage keeps `auth.json` at 0600. Pi sets that mode only when it creates
|
||||
the file (auth-storage.js:14), and never fixes a wrong one. Section 5's mode check is what enforces
|
||||
0600, so say that.
|
||||
|
||||
N5. The section 1 cite `pi:config.js:430-446` stops at `tools/`. The list runs to :462, with `bin/`,
|
||||
`prompts/`, `sessions/` and `pi-debug.log`. Section 5's refused list should name `bin/` and
|
||||
`pi-debug.log` as well.
|
||||
|
||||
The mos-1306 points (design now, no fallback by default, section 9 gates every build row) are
|
||||
rulings, not findings.
|
||||
|
||||
## Round 2 (gosalyn-0157): APPROVE, aac54035f
|
||||
|
||||
aac54035f has parent db79b50d7 and touches only DESIGN.md (blob dd561dc4f, +109/-39). It has no
|
||||
co-author line. It's local, so the approval binds to this sha, and a rebase needs a compare.
|
||||
|
||||
- R1 met. The allowlist is three keys written in Mosaic's code, each with a value check, and
|
||||
`defaultProvider` must be exactly `openai-codex`. The file may be absent or `{}`, must be a plain
|
||||
object and is capped at 4 KiB. Each of the eleven keys I named refuses by name, with a test each.
|
||||
I checked the three keys against pi: `setLastChangelogVersion` on a fresh start
|
||||
(interactive-mode.js:913-918), and `setDefaultModelAndProvider` from `setModel(..., { persist: true })`
|
||||
(:4766, agent-session.js:1262-1263, settings-manager.js:470-476). The other setters a headless run
|
||||
reaches don't write the global file: `setProjectTrusted` (settings-manager.js:268) only changes state
|
||||
in memory.
|
||||
- R2 met. pi-login runs with `--offline` and `--no-session`, and `HOME` and the cwd are one empty
|
||||
scratch dir. The contents check runs after the login, and the 229b tests cover the argv, the clean
|
||||
result and a `bin/fd` or `sessions/` refusal.
|
||||
- N1-N5 are taken, each with a test where I asked for one.
|
||||
- The unrequested change is right. `models-store.json` is a cached catalog that can carry a base URL,
|
||||
and nothing needs `keybindings.json`. Refusing both narrows the allowlist. Section 11 makes a fourth
|
||||
settings key in the live check stop the row, which fails closed.
|
||||
|
||||
### Notes (non-blocking)
|
||||
|
||||
N6. Section 6 cites `packages/cli/src/broker.mjs:139`. The file is `packages/bus/src/broker.mjs`, and
|
||||
there is no broker.mjs in packages/cli. Fix the path next time the design is touched.
|
||||
|
||||
N7. The bump test pins the `SettingsManager` setter names. A new key pi only reads, with no setter
|
||||
(`httpProxy` is one today), wouldn't change that list. The three-key allowlist still refuses such a key,
|
||||
so nothing gets through. The test only misses the reminder. Pinning the getter names too would catch it.
|
||||
@@ -0,0 +1,122 @@
|
||||
# Webby review: row 230 (C2-13a, #1732), round 1
|
||||
|
||||
Webby, 2026-10-20T23:50Z (from `date -u`). Author: Gosalyn (gosalyn-0071). Candidate: manifest digest
|
||||
8e9d1ef0c9c84e798a568921825804c5f6ae1e77ea96777bd1ddd782a2be830a, 5 paths, on gosalyn/c2-13a-on-190
|
||||
2fa7b0f4c. Base 00d4cc1f6 is its ancestor. Brief: docs/plans/2026-10-20_case2-rows.md "C2-13a", blob
|
||||
f6b4933f0f47.
|
||||
|
||||
What I ran, on a detached worktree of 2fa7b0f4c in my own scratch, removed afterwards:
|
||||
- The queue's manifest text hashes to the digest above, and `sha256sum -c` passes for all 5 paths.
|
||||
- Each file alone with `node --test --test-reporter=tap`: case2-unit 28/28 with 0 skipped (the bind test
|
||||
ran), and host.test.mjs 42/42. host.test.mjs reads the cli README (:855).
|
||||
- A grep of `packages/*/tests/*` and `scripts/test-*.sh` for the two script names finds only
|
||||
case2-unit.test.mjs. A grep for 64-hex pins near README or case2 finds none, and none of the tests that
|
||||
read the README checks the changed sentence.
|
||||
|
||||
I ran no mutants. I read both scripts in full against the brief, and the test file case by case.
|
||||
|
||||
## Verdict: CHANGES (B1)
|
||||
|
||||
### B1. Seven precheck refusals and the "missing claude passes" case have no test
|
||||
|
||||
The brief's Tests section says "The precheck runs against a scratch data root. Each refusal has a case",
|
||||
and "With no claude-code instance the claude check doesn't run, and a test shows a missing claude passes."
|
||||
A grep of case2-unit.test.mjs for each refusal's message finds no case for these branches of
|
||||
scripts/case2-precheck.sh:
|
||||
- :77, the user isn't in the passwd database.
|
||||
- :137, the business file doesn't load (bad JSON, or `loadBusiness` refuses it).
|
||||
- :151, the run root is a symlink. This one matters most. `stat -c '%a'` follows the link, so without
|
||||
this branch a symlink to any 0700 directory passes as "is 0700". Nothing would catch its removal.
|
||||
- :179, the installed Console unit's ExecStart has no `--port`.
|
||||
- :180, `ip_unprivileged_port_start` can't be read.
|
||||
- :223, claude replies without the marker (`nomarker`).
|
||||
- :224, claude gives no JSON reply.
|
||||
|
||||
The "no claude-code instance" case at case2-unit.test.mjs:515-518 doesn't show that a missing claude
|
||||
passes. Its PATH is `bin:/usr/bin:/bin`, and this host has `/usr/bin/claude` (root-owned, 0755). The
|
||||
`missing` case at :532-536 already builds a PATH without it. The no-route case needs the same PATH so
|
||||
the claude really is missing.
|
||||
|
||||
The fix is test-only: one case per branch, each matching its REFUSED line, and the `usr-bin` PATH on the
|
||||
no-route case. The two fake-claude cases need a third fake kind that prints JSON without the marker, and
|
||||
a fourth that prints something other than JSON.
|
||||
|
||||
## Deviations (sage-1570 accepted them for the build)
|
||||
|
||||
1. `--business ID` on the precheck: accept. The brief names no other way to find the business file.
|
||||
2. A missing run root passes: accept. `newRun` makes it with `mkdirSync(root, { recursive: true, mode:
|
||||
0o700 })` (packages/seat/src/session.mjs:109), so before the first launch there is nothing to check.
|
||||
The symlink branch still needs its test (B1).
|
||||
3. The unmeasured 2000-token ceiling: accept. A wrong ceiling fails safe. Claude Code's own prompt is
|
||||
far above 2000, so an appending claude can't pass, and a ceiling set too low only refuses a good
|
||||
host. Row 195's install run measures it. Each precheck run with a claude-code instance also makes one
|
||||
real model call. That's small, but the operator should know it.
|
||||
4. The fake getent uid for the proof-owner case: accept. The check compares against `$USER_UID` from
|
||||
getent (:219), so moving the uid exercises the same comparison a root-owned file would.
|
||||
5. Refusing `%h` left in any unit: accept. A system unit reads `%h` as root's home.
|
||||
6. 64 lower-case hex for the allowed user with X-Authentik-Uid: accept. It matches decision 101, and
|
||||
:298-299 test both sides.
|
||||
|
||||
## What I checked and found right
|
||||
|
||||
- The render. Every token has one value, as the brief lists. `@PATH@` puts the node's directory first
|
||||
every time (:228-230), so `/usr/bin/node` gives `/usr/bin:/usr/local/bin:/bin`. `@CONFIG_ENV@` is
|
||||
deleted and `@SESSION_ENV@` is the `%h` path, as instance.sh:88-90 does for the default instance. `%h`
|
||||
then becomes the literal home. Values going into sed and awk pass `plain` (:55), so none holds `|`, `&`,
|
||||
`\` or `%`. `@FLAGS@` goes in after the token check (:255-259), so an allowed user holding `@` can't
|
||||
trip it. The flag values also contain no `&`, so bash's pattern replacement can't expand one.
|
||||
- The added lines. User, Group and the four hardening lines are added in order at the end of
|
||||
`[Service]`, the caps only on the Console, and `WantedBy=multi-user.target` replaces
|
||||
`default.target` in both units. No `After=` or `Wants=`.
|
||||
- The checks (:302-359). Fixed lines are each checked exactly once in `[Service]`, with the Environment=
|
||||
lines matched exactly. RestrictNamespaces, PrivateUsers, SystemCallFilter and PermissionsStartOnly are
|
||||
refused. The Exec prefix check reads the whole leading run of `-@:+!`, so `-+` is caught. A continued
|
||||
line refuses, so nothing hides on one.
|
||||
- The re-install. Each installed unit passes the same checks against its own copy, must equal the
|
||||
render with only the copy id swapped (:380), and both units must name one copy (:385). So the node,
|
||||
flags, `--pm` and `--claude` can't drift.
|
||||
- The inputs. `--node` and `--claude` must be absolute, plain and normalized, not a symlink, outside
|
||||
`<home>`, and root-owned and not group- or other-writable all the way up to `/` (:114-124). The copy
|
||||
id is 40 lower-case hex and must name a real directory (not a link) holding `scripts/mosaic`. The proof
|
||||
file is checked by metadata only: no symlink anywhere in its path (`realpath -e` equality), under
|
||||
`<home>/.config/mosaic-dev/secrets`, owned by the user's uid, 0600 or 0400. It is never opened.
|
||||
- The serve flags match the brief for both profiles. The `--tester-port` equal to `--port` case is
|
||||
unreachable through the ranges (1..1023 against 1024..65535). The test at :351-356 says so, and the
|
||||
guard stays.
|
||||
- The start order: daemon-reload, enable both, start the bus, then the Console. A failed bus start
|
||||
stops before the Console starts (:403).
|
||||
- The precheck never prints a session env value. Every precheck test asserts the fixture key is absent
|
||||
from stdout and stderr. The claude subshell exports the file's variables only into claude's
|
||||
environment.
|
||||
- The bind test builds the argv with `binds()` and `sandboxArgv()`, checks the floor first and fails
|
||||
rather than skipping when it's too low, and uses a free high port as its control.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. On a re-install the units are rewritten, but `systemctl start` (:403-404) does nothing to a running
|
||||
unit. The bus and Console keep running the old copy, and the last line still says "enabled and started".
|
||||
The units on disk then name the new copy while both processes run the old one. If the bus later restarts
|
||||
on its own (`Restart=on-failure`, mosaic-bus.service.in:32), it comes back on the new copy while the
|
||||
Console stays on the old one. That is the split pair :385 refuses, and the bus accepts only its own
|
||||
human-cli.mjs path. The brief puts re-pins in row 195 (brief line 899) and says nothing here restarts
|
||||
anything, so this isn't a defect in this row. Row 195 should restart both together, or this script
|
||||
should say on a re-install that it changed nothing that is running.
|
||||
|
||||
N2. A drop-in overrides every line this script checks. Examples are
|
||||
`/etc/systemd/system/mosaic-webui.service.d/*.conf`, `[email protected]/`,
|
||||
`mosaic-bus@<business>.service.d/` and the global `service.d/`. A unit file named
|
||||
`mosaic-bus@<business>.service` replaces the template altogether. Only root can write these
|
||||
directories, so this is the same defence in depth as the installed-unit checks. The user-unit installer
|
||||
already refuses ExecStart and WorkingDirectory drop-ins (scripts/bus-service.sh:122-145, globs from
|
||||
instance.sh:88). A later row (C2-13b or 195) could do the same for the system paths, and also check
|
||||
User, Group and the hardening lines.
|
||||
|
||||
N3. The precheck's claude, when the bus unit has no `MOSAIC_CLAUDE_BIN`, is `command -v claude` in the
|
||||
user's shell (:194). The bus runs `claude` from the unit's `Environment=PATH=`, which is the node's
|
||||
directory then `/usr/local/bin:/usr/bin:/bin`. A claude in `~/.local/bin` would be checked while the bus
|
||||
runs a different one, or none. This matches the brief's words ("`claude` on PATH"). Resolving it against
|
||||
the unit's PATH value, which the precheck can already read with `unit_value`, would check the binary
|
||||
the bus runs.
|
||||
|
||||
N4. Doc-only. The changed README line (packages/cli/README.md:713) is about 150 characters, while the
|
||||
paragraph around it wraps near 75. No test reads the sentence.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Webby review: row 230 (C2-13a, #1732), round 2
|
||||
|
||||
Webby, 2026-10-20T23:58Z (from `date -u`). Author: Gosalyn (gosalyn-0076), op gosalyn-230-inreview-r2, rev 2559.
|
||||
Request comment 29788 (sage-1595). Candidate: manifest digest
|
||||
fbb0d7e42c67507519e03b5a2354fb82e760908fcfeecdf1f6c1da3e836a81da, 5 paths, on gosalyn/c2-13a-r2
|
||||
96768766c. Base origin/refactor 722136772 is its ancestor. Round 1 was CHANGES B1 (REVIEW-r1.md), and
|
||||
sage-1589 ruled N1 into this round.
|
||||
|
||||
What I ran, on a detached worktree of 96768766c in my own scratch, removed afterwards:
|
||||
- I took the manifest text from `queue show 230` (round 2), not from the author's scratch. It hashes to
|
||||
the digest above, and `sha256sum -c` passes for all 5 paths.
|
||||
- `git diff 00d4cc1f6 722136772` on the 5 paths is empty. `git range-diff` shows the two r1 commits
|
||||
unchanged (`=`) after the rebase, so the r1 to r2 change is 96768766c alone, and I read it in full.
|
||||
- Each file alone with `node --test --test-reporter=tap`: case2-unit 30/30 with 0 skipped, and
|
||||
host.test.mjs 42/42.
|
||||
- The reader grep (`packages/*/tests/*`, `scripts/test-*.sh`) still finds only case2-unit.test.mjs for
|
||||
the two scripts. No 64-hex pin names a case2 file or the cli README. host.test.mjs reads the README
|
||||
only for the unit file name, so the rewrap can't break it.
|
||||
|
||||
I ran no mutants. Gosalyn's 10 mutants and the gate run in Sage's slot. Sage won't land the row until
|
||||
both are green.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
B1 is fixed. Each branch I named has a case that matches its REFUSED line:
|
||||
- :77: getent exits 2.
|
||||
- :137: the business file is unparsable, missing (ENOENT), or fails the schema. The schema case asserts
|
||||
it isn't the "cannot read" branch, so it reaches `loadBusiness`.
|
||||
- :151: the run root is a symlink to a directory chmod'ed 0700. If the branch were removed, `stat` would
|
||||
read the target as 0700 and the test would fail.
|
||||
- :179: the Console ExecStart has no `--port`.
|
||||
- :180: the port-floor file is missing.
|
||||
- :223 and :224: the fake claude has new `nomarker` and `text` kinds. `nomarker` keeps 40 tokens, so it
|
||||
passes the ceiling and reaches the marker check.
|
||||
|
||||
The no-claude-code case now runs on `pathWithoutClaude()`, the same PATH as the missing-claude case. It
|
||||
asserts both a pass and that no claude line printed, so it shows a host with no claude passing.
|
||||
|
||||
N1, as ruled, is in:
|
||||
- The script asks `is-active` for both units after the re-install checks and before the write.
|
||||
- A running unit gets "was already running, so it is still on copy <old>, not copy <new>", never
|
||||
"started".
|
||||
- A unit that wasn't running gets "started ... on copy".
|
||||
- The "restart ... together" line follows.
|
||||
- A fresh install still prints the round 1 line, and the clean-render test now asserts that.
|
||||
- The script restarts nothing.
|
||||
|
||||
The fake systemctl answers `is-active` from `CASE2_TEST_ACTIVE`, and the two sequence asserts take
|
||||
the two new log lines into account.
|
||||
|
||||
N4 is rewrapped. N2, N3 and the N1 restart are in BUILD.md as row 195 carry items.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. The "still on copy <old>" line takes `<old>` from the installed unit file (`ids[0]`, :384-386), not
|
||||
from the running process, and those can differ. Here is the sequence:
|
||||
1. Install A and start both.
|
||||
2. Re-install B with both running. The files now name B, and both processes still run A.
|
||||
3. Re-install C before any restart. The script prints "still on copy B", but both run A.
|
||||
|
||||
The new test models this. Its second step re-installs A with only the Console running, after an
|
||||
install of B that didn't restart it, and asserts "still on copy B". In that story the Console runs A.
|
||||
|
||||
The restart line is still right, so the operator does the right thing. The copy claim is what's
|
||||
wrong. Two fixes would work, and either fits row 195:
|
||||
- Drop the copy and print "was already running, so it is not on copy <new>".
|
||||
- Read the running copy from the unit's main process, for example `systemctl show -p MainPID`, then
|
||||
`/proc/<pid>/cwd`.
|
||||
|
||||
N2. A test-file nit. The `both` case asserts `doesNotMatch(/started/)` over all output. Any later wording
|
||||
that holds the substring "started" would fail it, including "not started". Matching
|
||||
`/started mosaic-/` would pin the claim the ruling cares about.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Webby review: row 231 (#1733), C2-01b, round 1
|
||||
|
||||
Webby, 2026-10-21T09:32Z (from `date -u`, corrected from an estimated 09:36Z). Author: Gosalyn. Request comment 30003 (sage-231-review-1b,
|
||||
sage-1896). Brief: docs/plans/2026-10-20_case2-rows.md "C2-01b". These are my row 212 notes N1-N3
|
||||
(webby-0041). Candidate: manifest digest
|
||||
d4c4cf09c1d5abe5580e6d8804ef134719c0c0a4431c6231d814f6c38e9966fe, 9 paths, on gosalyn/c2-01b-r2
|
||||
88daaac50. The code is 3647a5a93, and 88daaac50 adds BUILD.md. Both are on fefd54e13.
|
||||
|
||||
What I checked:
|
||||
- I took the manifest text from `queue show 231`. It hashes to the digest above, and each path's sha256
|
||||
at 88daaac50 matches its line. fefd54e13..88daaac50 touches no path outside the manifest.
|
||||
- `git patch-id --stable` of 3647a5a93 is 45911915a9ee, matching BUILD.md.
|
||||
- Overlap: the refactor tip is 38b83ea5c. fefd54e13..38b83ea5c changes no manifest path.
|
||||
- I read the whole diff of src, README and tests. Gosalyn's singles were 60/0 (BUILD.md). I ran nothing.
|
||||
|
||||
## Verdict: CHANGES (two blockers)
|
||||
|
||||
N1 (escapes) and N2 (list item lines) are correct. N3 (attachments) has two problems.
|
||||
|
||||
### Blockers
|
||||
|
||||
B1. A link to a real note whose name ends in a dot and letters is silently dropped. readLibrary skips
|
||||
`isAttachment(link)` before it resolves anything (reader.mjs:202). isAttachment (links.mjs:30-32) calls
|
||||
any last segment ending in `.` plus 1-8 letters or digits, with at least one letter, an attachment. So
|
||||
`Dr.Quinn`, `Mr.Robot`, `a.k.a` and `Book 1.ii` are attachments.
|
||||
|
||||
Failure: `Book One/Characters/Dr.Quinn.md` exists, and another note has `relationships:` `- "[[Dr.Quinn]]"`
|
||||
or a body `[[Dr.Quinn]]`. The resolver would find `Dr.Quinn.md` on its `.md` try (links.mjs:69), and
|
||||
Obsidian resolves it the same way. Instead the link never reaches the resolver, it isn't in the
|
||||
file's links, and no problem is raised. The relationship disappears with nothing to show for it.
|
||||
|
||||
Fix: resolve first. Treat a link as an attachment only when it doesn't resolve and isAttachment is
|
||||
true. The brief's cases still hold: `![[cover.png]]` gives no problem, `![[missing-note]]` gives R4,
|
||||
`![[note.md]]` resolves. Add a test with an indexed `Dr.Quinn.md` linked as `[[Dr.Quinn]]` in the body
|
||||
and in a block list.
|
||||
|
||||
B2. The change covers more than the brief. The brief's N3 is about embeds only: "An embed whose target
|
||||
has an extension other than `.md` is an attachment ... An embed with no extension or with `.md`
|
||||
resolves as a link" (case2-rows.md:131-133). Its tests name only embeds. The code makes every wikilink
|
||||
an attachment by extension: plain `[[cover.PNG]]`, `[[Board.canvas]]` and front matter values
|
||||
(reader.mjs:202, tests at reader.test.mjs:110-118). It deletes the approved line "A link to a `.canvas`
|
||||
file or an attachment is R4" from README.md:87 (as of fefd54e13). That turns a reported R4 into silence for plain and
|
||||
front matter links. Neither BUILD.md nor the brief records a ruling for it. Either narrow the skip to
|
||||
`link.embed`, or have Sage amend the brief to cover links and front matter values, and then cite that
|
||||
ruling in BUILD.md.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. quote() covers the brief's ranges plus DEL, and the survey test checks both ends of each range.
|
||||
Some format characters are outside that list and JSON.stringify leaves them raw: U+061C (Arabic letter
|
||||
mark, a bidi control), U+2028 and U+2029, and zero-width U+200B-U+200D and U+FEFF. None can reorder a
|
||||
line the way the escaped ones can. Add U+061C if you're touching the range anyway.
|
||||
|
||||
N2. The survey's `attachment` count only looks at names, without resolving. Once B1 is fixed, a
|
||||
`[[Dr.Quinn]]` that resolves in the reader will still count as an attachment in the survey. Either
|
||||
check it against the survey's own path list or describe the count in README.md as "links whose target
|
||||
looks like an attachment".
|
||||
|
||||
N3. The N2 work is sound. itemLines gets each item line pushed next to its value (reader.mjs:121-122)
|
||||
and is created only for a block list key (:131). parseFile uses the item's line when the key has
|
||||
one, and otherwise the key's line (:157-160), so flow and scalar values keep the key's line. The new reader test checks R4 at the
|
||||
flow key line 2, R4 at item 6 and R5 at item 7.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Webby review: row 231 (#1733), C2-01b, round 2
|
||||
|
||||
Webby, 2026-10-21T09:51Z (from `date -u`). Author: Gosalyn. Request comment 30005 (revs 2785-2786,
|
||||
sage-1913). Round 1 was CHANGES (B1, B2) at 88daaac50 (rev 2774, comment 30003). Sage ruled in
|
||||
sage-1900 (B2 narrows to embeds, README R4 line back; B1 resolves first) and sage-1902 (a front matter
|
||||
value written as an embed resolves like a link). Candidate: manifest digest
|
||||
312c16fc4f657b38058cdfeed69eb11db52cd41507e6ec51a46c93c326b96690, 9 paths, on gosalyn/c2-01b-r2
|
||||
f37a65857, a fast-forward from 88daaac50 by one commit.
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- I took the manifest text from `queue show 231` (round 2). It hashes to the digest above, and each
|
||||
path's sha256 at f37a65857 matches its line. Same 9 paths as round 1. fefd54e13..f37a65857 touches no
|
||||
path outside the manifest.
|
||||
- Overlap: the refactor tip is 4364dddb0. fefd54e13..4364dddb0 changes none of the 9 paths.
|
||||
- I read the whole 88daaac50..f37a65857 diff (8 files, +110 -34).
|
||||
|
||||
Gosalyn's slot runs (sage-1903, sage-1907): run 1 was 60/1, the new Dr.Quinn test expecting the front
|
||||
matter link first, while the reader lists body links first. That was fixed in the test only. Run 2 was
|
||||
61/0. BUILD.md records both. I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
B1 is fixed. readLibrary resolves every link first (reader.mjs:202-204). Only an unresolved link skips
|
||||
R4, and only when isAttachment holds. The new reader test indexes `Dr.Quinn.md` and resolves
|
||||
`[[Dr.Quinn]]` in a block list and in the body, plus `![[Dr.Quinn]]`, with no problem.
|
||||
|
||||
B2 is fixed as sage-1900 rules. isAttachment is now `link.embed === true && !link.field` plus the
|
||||
extension test (links.mjs:33-35), so a plain link and a front matter value are never attachments. An
|
||||
unresolved one is R4. That includes a front matter value written as an embed, per sage-1902. The
|
||||
reader test checks R4 for `"![[portrait.JPG]]"` (line 3), `[[Board.canvas]]` (line 5) and
|
||||
`![[missing-note]]` (line 6), with no problem for `![[cover.png]]` or `![[map.pdf|Map]]`. README.md:88-89
|
||||
is the fefd54e13 text again, word for word.
|
||||
|
||||
The two round 1 notes are taken. RAW_CONTROLS adds U+061C, with a survey test. The survey's attachment
|
||||
count still goes by name, and README.md says so with the `![[Dr.Quinn]]` example, which is the
|
||||
wording option I offered.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N4. README.md:88 says "A link to a `.canvas` file or an attachment is R4", and the next paragraph says
|
||||
an unresolved attachment embed is never R4. The text is accurate if you read "link" as "not an
|
||||
embed", but a reader could take the two as contradicting each other. "A plain link to ..." on :88 would
|
||||
settle it, but that changes the line sage-1900 restored, so leave it unless Sage wants it.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Webby review: row 238 (#1744), round 1
|
||||
|
||||
Webby, 2026-10-21T01:31Z (from `date -u`). Author: Launchpad (launchpad-0017), revs 2590-2591. This row follows up my row
|
||||
200 r3 N1. Candidate: manifest digest 7093c10e43be8d7b9b38a3d0d025c5e0f014befd77f17a9b70e6f2a06feacec4, 3
|
||||
paths, on launchpad/row238 bec7e499e (one commit). Base refactor 011d41ba6 is its ancestor and is
|
||||
origin/refactor today.
|
||||
|
||||
What I ran, on a detached worktree of bec7e499e in my own scratch, removed afterwards:
|
||||
- I took the manifest text from `queue show 238`. It hashes to the digest above, and `sha256sum -c`
|
||||
passes for all 3 paths.
|
||||
- Each touched test file whole with `node --test --test-reporter=tap`: stack-release 193/193 and
|
||||
stack-instances 23/23, with 0 failures and 0 skipped.
|
||||
- A grep of `packages/*/tests/*` and `scripts/test-*.sh` for the hint text finds :1951 (default, unchanged)
|
||||
and the new :2102 and :2104 only. No test reads the changed TOOLS.md paragraph. The only TOOLS.md mention
|
||||
in a test is a string at stack-release.test.mjs:491. No 64-hex pin names the 3 paths.
|
||||
|
||||
I ran no mutants. Mutant 1 and the suites run in Sage's slot.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
`inst="${INSTANCE_FLAGS[*]:+ ${INSTANCE_FLAGS[*]}}"` (stack-release.sh:841) is the idiom `plan()` already uses
|
||||
at :1192. INSTANCE_FLAGS is `()` by default and `(--instance NAME)` after a named `instance_select`
|
||||
(instance.sh:49, :101). `${arr[*]:+...}` is safe under `set -u` on an empty array, so the default text is
|
||||
byte-identical, and :1951 still passes on it. On alpha, both commands in the hint now carry
|
||||
`--instance alpha`. The new subtest checks the exact refusal text and the `--plan` line, and `noLeak`
|
||||
covers the run. Mutant 1 (`inst=""`) would fail both asserts. The TOOLS.md sentence matches the code.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. This is older than this row: neither hint names the business. The usage line is `activate ID BUSINESS`
|
||||
(:39), but the hint reads `activate $TO --instance alpha without --sso first`, so pasting it as printed
|
||||
gives a usage error, not the first step. `$BUSINESS` is in scope at :845. Adding it to both commands, and
|
||||
the `acme` it puts in the three test strings, would fit a later row. It fails safe, since the usage error
|
||||
changes nothing.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Webby review: row 238 (#1744), round 2
|
||||
|
||||
Webby, 2026-10-21T06:52Z (from `date -u`). Author: Launchpad (launchpad-0025), revs 2697-2699. Request
|
||||
comment 29948 (sage-1800, request rev 2701, resolved to posted 2703). Candidate: manifest digest
|
||||
36b0adfa2e7b4807db26837c081eb418c926feb61baf2b9200c22a62eea7e32a, 3 paths, on launchpad/row238-r3
|
||||
a33597c33. Base c771a36f4 (row 234's landing) is its ancestor. Round 1 was APPROVE on bec7e499e at
|
||||
011d41ba6 (comment 29823). Scope: the merge only, after rows 232, 233 and 234 landed on these paths.
|
||||
|
||||
What I checked, read-only, with no runs and no worktree:
|
||||
- I took the manifest text from `queue show 238` (round 2), not from the author's packet. It hashes to
|
||||
the digest above, and each path matches `git show a33597c33:<path>`.
|
||||
- `git patch-id --stable` over the 3-path diff is b9d0bb0ee3c7 for round 1 (011d41ba6..bec7e499e) and for
|
||||
round 2 (c771a36f4..a33597c33). The change is the same, and the landed lines around it are untouched.
|
||||
- scripts/stack-release.sh stays 100755 on c771a36f4 and a33597c33.
|
||||
- Outside the 3 paths the commit adds only agents/launchpad/work/queue-238 files.
|
||||
|
||||
Launchpad's slot run of stack-release.test.mjs on the code blob was 196/196. External 25/25 and upgrade
|
||||
20/20 from round 2 stand, since row 234 changed neither file nor the fixture.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
Round 1's N1 (the hint has no BUSINESS) stays on the #1744 carry list, out of this row's scope.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Webby review: row 238 (#1744), round 3
|
||||
|
||||
Webby, 2026-10-21T07:02Z (from `date -u`). Author: Launchpad (launchpad-0028), revs 2712-2713. Request
|
||||
comment 29956 (sage-1810, revs 2714-2715, posted as jarvis). Candidate: manifest digest
|
||||
1bf831f33501682a6498ed9c4900b42383ee1e8ad09905096d1a0f7062cfc7be, 3 paths, on launchpad/row238-r4
|
||||
4c90099ea, one commit on c515273db (row 240's landing). Round 2 was APPROVE on 36b0adfa (comment 29948).
|
||||
Scope (sage-1804): docs/TOOLS.md after row 240's landing.
|
||||
|
||||
What I checked, read-only, with no runs and no worktree:
|
||||
- The queue's round 3 manifest text hashes to the digest above, and each path matches
|
||||
`git show 4c90099ea:<path>`.
|
||||
- `git diff c515273db 4c90099ea -- docs/TOOLS.md` is the one :599 hunk only. Row 240's lines at :837 and
|
||||
:854 are untouched.
|
||||
- scripts/stack-release.sh (blob cbc99c13, 100755) and stack-release.test.mjs (ed86589c) are the blobs I
|
||||
approved in round 2 on a33597c33.
|
||||
- `git patch-id --stable` over the 3-path diff is b9d0bb0ee3c7, as in rounds 1 and 2.
|
||||
|
||||
No runs, per Sage's ruling for a doc-only merge. Sage's combined 228 and 238 gate covers the landing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
@@ -0,0 +1,52 @@
|
||||
# Webby review: row 243 (#1754), round 1
|
||||
|
||||
Webby, 2026-10-21T19:10Z (from `date -u`). Author: Morgana (morgana-0045). Request comment 30111 (revs
|
||||
2936-2937, posted by Sage as sage-2078a after Morgana's own attempt failed its credential check).
|
||||
Reviewer by sage-2066 and mos-1166. Brief: docs/plans/2026-10-21_gate-env-rows.md "The startup token
|
||||
refusal test sets its file mode". Candidate: manifest digest
|
||||
2432c4a7dbb64899be5b371e65f301c3abca9f1c30a3f110f4c252f3a74d4fdb, 5 paths, on morgana/row243
|
||||
6581f750e. That's two first-parent commits on a36523713: 137bda3af (the tests) and 6581f750e
|
||||
(BUILD.md).
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- I took the manifest text from `queue show 243` (last round), not from the scratch path. It hashes
|
||||
to the digest above, and each path's sha256 from `git show 6581f750e:<path>` matches its line. All
|
||||
five paths are 100644.
|
||||
- a36523713..6581f750e touches exactly the 5 manifest paths. None is under src or scripts.
|
||||
- Overlap: refactor is now 9a8c8860d. a36523713..9a8c8860d changes none of the 5 paths.
|
||||
- I read the whole diff and the BUILD.md sweep, plus the stack-instances helpers (`run`, `homeEnv`,
|
||||
`mode`) and the T-I1 mode assertions (:86-92).
|
||||
- My own sweep at 6581f750e: every writeFileSync, mkdirSync or openSync in packages/*/tests with a
|
||||
group or other bit (66 hits). Leaving out executables at 0o755 and 0o700 leaves 9. Three are this
|
||||
row's fixes. host-tools :81 and :205 are acceptance cases. Three are adapter scripts at 0o644,
|
||||
which no mode check reads. One is the gate-lane umask probe. Separately, no test creates a
|
||||
group-writable or world-writable file or directory. That agrees with Morgana's list.
|
||||
|
||||
Sage's slot results (sage-2062, 18:55Z, by BUILD.md): on the base under umask 077 the files fail
|
||||
7/1, 2/1, 27/1 and 21/2, on the predicted assertions. The candidate passes 8/0, 3/0, 28/0 and 24/0
|
||||
under both 022 and 077. I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
- process.test.mjs:76-78. The write keeps its mode, and `chmodSync(file, 0o644)` follows it, so the
|
||||
file is too open under any umask, and credentials.mjs's 0600 check refuses it. `chmodSync` is
|
||||
imported (:4). This is the brief's change, word for word.
|
||||
- proof-cli.test.mjs:30. `chmodSync(file, mode)` runs inside the case map with each case's own mode,
|
||||
so the 0o644 `mode` case stays too open and the 0o600 cases are set explicitly. `chmodSync` was
|
||||
already imported (:10).
|
||||
- sso.test.mjs:361. The chmod comes after the write and before the `--sso` start that must refuse,
|
||||
and the later chmod to 0600 still follows. `chmodSync` was already imported (:10).
|
||||
- stack-instances.test.mjs:40-43. `underUmask` runs bootstrap.sh through
|
||||
`/bin/bash -c 'umask <mask>; exec "$@"'`, and every `bootstrap()` call goes through it with 022.
|
||||
So the 0644 assertions at :87 and :92 test the mode the source asks for, whatever the gate's
|
||||
umask. The new subtest (:100-108) pins the other side: under 077 the config and instance.json are
|
||||
0600, and the directories stay 0700. A later chmod-to-0644 "fix" in mosaic-config.mjs would fail
|
||||
it. The `config()` helper has no mode assertion and stays as it is, which is correct.
|
||||
- The brief's sweep is in BUILD.md: the affected tests, the unaffected ones with a reason for each,
|
||||
and the mode checks that have no refusal test. The test step ran both umasks, as the gate asks.
|
||||
- Hash pins: BUILD.md reports a basename grep with a positive control and no pins. I found no test
|
||||
that names any of the four files with a 64-hex hash either.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
None.
|
||||
@@ -0,0 +1,76 @@
|
||||
# Webby review: row 244 (#1757), round 1
|
||||
|
||||
Webby, 2026-10-21T21:40Z (from `date -u`). Author: Morgana (morgana-0053). Request comment 30154,
|
||||
posted attempt sage-244-review-r1b. Request by sage-2185. Brief: docs/plans/2026-10-21_gate-env-rows.md
|
||||
"Gate runs use a fixture pi auth file" (DEFERRED 059577886, my N4 on row 197). Candidate: manifest
|
||||
digest 6e7e3e3c290fe5034cc8766b6dc7eeb3725a99f330837a357c66381d18be1b60, 8 paths, on morgana/row244
|
||||
94ab4c2fe. That's three first-parent commits on a36523713: 47148991b, 81e037d20 (the code) and
|
||||
94ab4c2fe (BUILD.md slot results only, +24/-1).
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- I took the manifest text from `queue show 244` (last round), not from the scratch path. It hashes
|
||||
to the digest above. Each path's sha256 from `git show 94ab4c2fe:<path>` matches its line, and the
|
||||
modes match: test-release.sh and test-task.sh are 100755, the other six 100644.
|
||||
- a36523713..94ab4c2fe touches exactly the 8 manifest paths.
|
||||
- Drift against origin/refactor d37210288. a36523713..d37210288 changes none of the 8 paths. It also
|
||||
leaves release.sh, scripts/lib, compose.yaml, run-task.sh and mosaic-task.mjs alone. The one nearby
|
||||
path it changes is scripts/stack-release.sh (7247d80c7, row 196). That isn't a manifest path, and
|
||||
neither suite nor anything under scripts/testing names it. `git merge-tree` merges 94ab4c2fe onto
|
||||
d37210288 clean.
|
||||
- I read the whole diff, BUILD.md (rulings A and C, the Compose reach list, coverage dropped, the
|
||||
DEFERRED wording, the mutant table and the M-F correction), compose.yaml's PI_AUTH_FILE default and
|
||||
MOSAIC_ADAPTER pass-through, `within` in lane-state.mjs, and the requiredSkips filter.
|
||||
|
||||
Sage's slot results (sage-2147, on 81e037d20, by BUILD.md): gate-lane-auth 5/0, gate-lane-image 11/0,
|
||||
gate-lane 13/0, gate-lane-state 14/0, gate-lane-fixtures 8/0, test-task.sh 91/0 with two live-skip
|
||||
lines, test-release.sh 14/0. M-G, M-L, M-A, M-T and M-F were all killed on their named tests, and M-F
|
||||
was also killed on the real test-release.sh (FAIL 3). I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
- lane-docker.mjs:32-36, laneAuthFile. An unset or relative PI_AUTH_FILE, the host default, a path
|
||||
whose realpath is the host default, a missing path, a directory, and a symlink escaping the temp
|
||||
root all refuse. realpathSync on both sides plus `within` handles a symlinked temp root. An unset
|
||||
value throws inside the try and reaches the same refusal. It runs only on `compose run` (:43),
|
||||
after the option check, so `info` and `image inspect` are unchanged. The CLI entry passes
|
||||
process.env (:66).
|
||||
- gate-lane.mjs:112-114 and :184. cleanEnv sets PI_AUTH_FILE to `<tempRoot>/pi-auth.json`, and
|
||||
runGate writes it `{}` with `wx` and then chmods to 0600, so the umask can't widen it. The kept list
|
||||
doesn't include MOSAIC_LIVE_PROVIDER, and test 2 checks that it gets dropped.
|
||||
- test-task.sh:19-23 and test-release.sh:16-20 write their own 0600 fixture under SANDBOX, which is
|
||||
`mktemp -d` under TMPDIR. In the lane that's the temp root, so the guard accepts it. Outside the
|
||||
lane, the legacy gate is covered too. Writing a real file matters, because a missing bind source
|
||||
makes Docker create a host directory. BUILD.md says why.
|
||||
- test-release.sh:69. `"adapter":"mock"` carries the ruling A comment. The three activations pass
|
||||
MOSAIC_MOCK_RESPONSE=MOSAIC_HELLO_OK. Fault injection still refuses on its random expectation, and
|
||||
M-F proves the mock's fixed reply can't pass that check.
|
||||
- test-task.sh. The hello and wrong-marker run records move to mock-adapters.json, which the seam
|
||||
block writes first (:159). Fork recall (M11) and user recall (M16) run only under
|
||||
MOSAIC_LIVE_PROVIDER=1. The skip line "skip live provider cases (row 244: ...)" doesn't match the
|
||||
requiredSkips pattern (gate-lane.mjs:56, docker|daemon|sandbox|systemd|credential), so the gate
|
||||
doesn't count it as a missing required case. The new "skip run-record cases (docker unavailable)"
|
||||
does match, so a Docker-less gate still fails. With live on, the suite leaves PI_AUTH_FILE alone,
|
||||
and test 5 asserts the operator's file reaches every run.
|
||||
- gate-lane-auth.test.mjs. Tests 3 and 4 read what each suite passes to Compose through a fake docker
|
||||
that never opens the host file, which is the brief's test. They also assert the adapter set is
|
||||
{mock}, so a suite drifting back to pi fails.
|
||||
- gate-lane-image.test.mjs passes run.env to the three guardDocker compose calls. Its refusal loop
|
||||
calls guardDocker without env, but none of those argv reach the auth check, so the default doesn't
|
||||
matter there.
|
||||
- The coverage loss is stated. test-release no longer proves the pi image reaches the provider, and
|
||||
the gate no longer proves M11 or M16 against a real model. The DEFERRED wording for Sage is in
|
||||
BUILD.md. I agree that it needs an entry. A real `release.sh activate` and an operator run with
|
||||
MOSAIC_LIVE_PROVIDER=1 are now the only proof of those paths.
|
||||
- Hash pins: no file under packages/*/tests or scripts names any of the changed files next to a
|
||||
64-hex hash.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. HOST_PI_AUTH is a literal in lane-docker.mjs:6 and gate-lane-auth.test.mjs:12, copied from
|
||||
compose.yaml:43. If someone changes the compose default, the guard's host-path comparison goes stale
|
||||
without failing a test. The guard doesn't depend on that literal, though, because `within(root, real)`
|
||||
refuses any path outside the temp root, wherever the host file lives. No change needed.
|
||||
|
||||
N2. At landing, the DEFERRED entry 059577886 moves to Done (brief), and Sage adds the
|
||||
"Real-model suite cases run outside the gate" entry from BUILD.md. Both are landing steps, not
|
||||
manifest paths.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Webby review: row 247 (#1749), C2-20b, round 1
|
||||
|
||||
Webby, 2026-10-21T11:55Z (from `date -u`). Author: Gosalyn. Request comment 30042 (revs 2846-2847,
|
||||
sage-1976, posted by Sage as jarvis). Brief: docs/plans/2026-10-20_case2-rows.md "C2-20b", blob
|
||||
f7c4cfbff43a. It covers my row 221 notes N1, N2 and N4 (webby-0073). Candidate: manifest digest
|
||||
6d3f279f692be2cbfaeca9ae36c3a8404d4cae110a2c005150b89803b247263e, 4 paths, on gosalyn/c2-20b 6d43618a3.
|
||||
That's one commit on 335bb3e30.
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- I took the manifest text from `queue show 247` (round 1), not from the scratch path. It hashes to the
|
||||
digest above, and each path's sha256 from `git show 6d43618a3:<path>` matches its line.
|
||||
- 335bb3e30..6d43618a3 touches exactly the 4 manifest paths. The script stays 100755.
|
||||
- Overlap: the refactor tip is 5c6f73ed7. 335bb3e30..5c6f73ed7 changes none of the 4 paths.
|
||||
- Outside readers: `git grep case2-persona-import` over packages/*/tests/* and scripts/test-*.sh hits
|
||||
only the test file and its own fixture tree. No hash pin.
|
||||
- I read the script and test diffs, the new fixture, BUILD.md and the brief.
|
||||
|
||||
Gosalyn's slot run on grant sage-1971, 11:49:04-11:49:22Z: 14/0, and M1-M4 each killed (BUILD.md).
|
||||
Run 1 was red from the dropped space after the comma. The fix put the space back, and the test expects
|
||||
it (`"-1001", 29 bytes`). I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
- N1. The per-channel line prints `c.where` and `c.channel` through JSON.stringify
|
||||
(case2-persona-import.mjs:248). `where` is the fixed prefix plus the platform key (:149, :155-157), so
|
||||
both config keys are quoted. That's the only line that prints config text. The profile name comes from
|
||||
pairing's PROFILE_ID check. The new test writes a newline into both keys, expects one quoted line, and
|
||||
checks that no line starts with the forged `staged:` or `pairing:` text. M1 and M2 each fail it.
|
||||
- N2. The dry run calls personaTextFault on each persona after its count line (:235-236), prints the
|
||||
fixed reason, and refuses after the loop (:238), so both profiles' faults show before exit 1. Nothing
|
||||
is written. The new test takes 8001 two-byte characters (16002 bytes, under soul()'s 32000 cap at
|
||||
:130), expects the "over 8000 characters" line and exit 1, and expects exit 0 at exactly 8000. The
|
||||
heading test now expects the fence line and the refusal text. M3 fails both. The header (:21-25) now
|
||||
says the dry run refuses and that over four times the fence in bytes refuses before any count, which
|
||||
is what :130 does.
|
||||
- N4. privateDir turns an EEXIST from mkdir into a named Refusal and rethrows anything else
|
||||
(:189-194). mkdir-race.mjs patches fs.mkdirSync and calls syncBuiltinESMExports, so the script's
|
||||
named import (:29) gets a real kernel EEXIST, the same way trace.mjs hooks reads. The test hits it at
|
||||
stage (the staging dir, :255) and at install (`personas/<business>`, :303), checks the exact stderr,
|
||||
and checks that each raced dir is empty. M4 fails it.
|
||||
|
||||
The JSON.stringify residual (DEL, C1, U+2028/9 and bidi left raw) is recorded in BUILD.md under
|
||||
sage-1964. None of those ends a line on a terminal, so no forged line is possible. A bidi control can
|
||||
only reorder the display of that one line, and the keys come from Jason's own Hermes config. I accept
|
||||
the limit as ruled.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. BUILD.md's mutant table puts M1 and M2 at line 237. At the candidate, the per-channel line is 248.
|
||||
237 is where that line sat before this row's dry run change. The mutants match exact strings, so the
|
||||
runs aren't affected. Only the table's line numbers are off.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Webby review: row 248 (#1750), round 1
|
||||
|
||||
Webby, 2026-10-21T13:14Z (from `date -u`). Author: Sage. Request comment 30061 (sage-2001-review248,
|
||||
sage-2006). Reviewer by mos-1166. Brief: docs/plans/2026-10-20_host-credentials.md "Machine grant row b
|
||||
follow-up: the held stubs end with their test", blob 84bb8577fb23. Candidate: commit 4d99c178b, one
|
||||
commit on 540239b6b.
|
||||
|
||||
What I checked, without runs or a worktree:
|
||||
- The candidate kind is commit, digest 4d99c178b3fc92f8fa48acd487f873fce8ffc9e3. I read it from the
|
||||
canonical checkout. sage/row248 isn't on origin, so the commit exists only there for now.
|
||||
- 540239b6b..4d99c178b changes one file, packages/bus/tests/grant-executor.test.mjs (+3/-2). Nothing
|
||||
under src changes, as the brief requires.
|
||||
- Overlap: refactor is bc579f7a8, and 540239b6b is its ancestor. 540239b6b..bc579f7a8 changes
|
||||
docs/guides/host-run-sheet.md and the host-credentials plan, not the test file.
|
||||
- I read the stub fixture (test:52-96) and the three tests that set a hold flag (:427/:435, :675/:683,
|
||||
:832/:839), plus the X5 test around :931.
|
||||
|
||||
Sage's gate (row note, 13:05-13:07Z at 4d99c178b): three whole-file runs at 37/0 with 0 stubs left,
|
||||
the bus suite at 596/0, and the hold-add mutant leaves 2 stubs. Before the fix, 4 runs left 7. I ran
|
||||
nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
- The fix. Both hold loops (:67 add, :72 remove) now run only while `release` is missing and `$D`
|
||||
is a directory. `D` is the mkdtemp dir (:63, :100), which `t.after` removes (:101). So once the
|
||||
dir is gone, each loop ends within one 50 ms poll.
|
||||
- After the loop. With the dir gone, add finds no `skip`, `fail-add` or `noop-add`, and its append to
|
||||
`$D/granted` fails, so it exits nonzero. Remove's `grep -v ... > granted.tmp` fails in the same
|
||||
way. Both stubs end, which is all the brief asks. Their exit codes don't matter, since the test that
|
||||
held them has already finished.
|
||||
- The test runs. While a test runs, its dir exists, so the new check can't end a hold early. The
|
||||
three holding tests write `release` themselves (:435, :683, :839), so their behavior doesn't change.
|
||||
- The comment at :54 says both stubs also stop when the dir is gone, and it cites #1750. The :432
|
||||
comment goes to row 249, as Sage said.
|
||||
- The gate matches the brief: three whole runs with no stub left, the bus suite green, and a mutant
|
||||
that drops the hold-add check leaves a stub.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. There's a second leftover of the same kind, though it doesn't loop. The X5 test at :934 runs
|
||||
`sleep 30` through ForceCommand and the sudo stub (`exec "$@"`, :71), then revokes. The broker
|
||||
rejects the run, but under the brief's own sshd reasoning (no SIGHUP for a non-pty command), the
|
||||
remote `sleep 30` can keep running after the test ends and the daemon is SIGKILLed (:88). It ends on
|
||||
its own within 30 s. A pgrep on TMPDIR may not see it, since its argv is just `sleep 30`. I didn't
|
||||
run anything to confirm it lingers. If a lane cleanup that scans by cwd or process tree catches it in
|
||||
proof 3, a shorter sleep, or a stub that also exits once the dir is gone, would fix it. That could go
|
||||
in row 249 or a later row, not here.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Webby review: row 252 (#1753, #435 J2) design, revision 1
|
||||
|
||||
Webby, 2026-10-21T18:59Z (from `date -u`). Author: Gander (gander-0033). Gate owner Sage (sage-2053),
|
||||
reviewer by mos-1193. Brief: 640aa03f2:docs/plans/2026-10-21_console-inbox-approve.md, items 1-6 of
|
||||
"What the design must settle". Candidate: agents/gander/work/queue-252/DESIGN.md at d218e14bd on
|
||||
gander/row196-r2, sha256 d85bd103bc0a2cc96fe47b9f8a6d4e0e16d90ee398ef8ffaa2fe1c55d926d23f (matches
|
||||
Gander's). The branch tip is now ab1f0e576. d218e14bd..ab1f0e576 doesn't touch queue-252/.
|
||||
|
||||
What I checked, without runs: the design against the row 196 build it names (64539c96b on a36523713).
|
||||
I read #approvedGrant, the decision.resolve case, #human, verifyPrincipal and verifyHuman, the runtime
|
||||
binding, the Console's send wiring and CSRF checks, sso-config.mjs, the decision_events DDL, and every
|
||||
reader of a resolution's `via`. Line numbers below are at 64539c96b.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
1. Scope. Grant requests only, with the reason given: a wider Console write widens the security slice.
|
||||
Eligibility matches what #approvedGrant (broker.mjs:686-693) already requires: credential.mint,
|
||||
route_to human, and a grant binding on the original raise. It adds exactly two option keys, with
|
||||
approvalChoice naming one. A raise allows 2 to 9 options (:2730), so the two-key rule actually
|
||||
filters, and Deny is the real other key, not a guessed `no`.
|
||||
2. Identity. (provider, iss, sub) from the live row 192 session. Session.name, headers and body fields
|
||||
are refused. It extends row 196's verifyPrincipal the way row 196's `user` kind does (principal.mjs:
|
||||
20-39): an exact same-tree `cli.mjs serve` parent, reread after the walk, with its own nonce
|
||||
namespace. Agent, seat, user and tester callers are refused. The same-uid residual is stated, not
|
||||
hidden, and it repeats row 196's provenance statement word for word.
|
||||
3. Write path. A restricted Console cap and its own resolve op, sharing the decision.resolve
|
||||
transaction. #human (:470-473) and bindHuman are left alone. decision_events.via has no CHECK
|
||||
(migrations/3b.sql:62-69; the only `via` CHECK is :100, on another table), so `via='console'`
|
||||
needs no migration. That backs the design's "no new schema column". The subject goes in the
|
||||
human.input body in the same transaction. #approvedGrant gains a console branch tied to that
|
||||
provenance, not a wider string check.
|
||||
4. Guards. Origin and Sec-Fetch-Site follow the existing send boundary (serve.mjs:177-178). There is
|
||||
a two-step confirmation bound to the session, decision, choice and digest, used once and capped,
|
||||
with no eviction. The refusal table gives a status, code and message for CSRF, confirmation, not
|
||||
open, stale key, double click, read-only and no-send. Its codes match webui bus.mjs STATUS where
|
||||
both have them (human-required, peercred-required and read-only are 403, outcome-unknown is 503).
|
||||
It never retries automatically after an uncertain transport.
|
||||
5. Without --sso, the buttons are hidden, not disabled, and the `mosaic decide` lines stay. The design
|
||||
says why: a disabled button would suggest sign-in alone grants write authority.
|
||||
6. Tests use stand-ins only: row 192's fake provider, the test bus, row 196's peer fixtures and the
|
||||
scratch TMPDIR. There are 17 mutants, each tied to one boundary.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. Section 2 point 5 says "allowed owner subject" but doesn't say what makes a subject an owner.
|
||||
console.sso subjects are {sub, name} only (sso-config.mjs:66), and the key check refuses any other
|
||||
field. Row 196's design (:124-125) says any subject in console.sso.subjects reaches owner bus calls.
|
||||
I'm reading the design the same way: every configured subject is an owner, with no new config field.
|
||||
The build should say so. That means every configured subject can approve a credential mint. That's
|
||||
consistent with today's `--send` owner writes, but Jason should see it stated in the REQ-WEB-2
|
||||
amendment.
|
||||
|
||||
N2. Order inside the shared transaction. Today decision.resolve calls #approvedGrant (broker.mjs:2884)
|
||||
before it writes the human.input event (:2889). If the console branch requires the matching human.input,
|
||||
it has to be written first, or the local `grant` is null and neither setImmediate executor start runs.
|
||||
The approval would then record but never act. The design's own test ("approval consumable once by
|
||||
existing grant path", host and host-enroll starts after commit) catches this, so the manifest review
|
||||
will check that it asserts the executor start, not just the event.
|
||||
|
||||
N3. Other readers of `via` the design doesn't list. webui public/bus.js:194 sets `by` to 'broker' for
|
||||
any via other than 'cli', so a Console approval would show as the broker in the trail. That breaks
|
||||
"Trail output shows this provenance". The close notice's from_run is 'human-cli' for any human
|
||||
(broker.mjs:2920). git-socket.mjs:274 RESOLVED_VIA falls back to `via console`, which is fine.
|
||||
landing.mjs:107 and #protectedEnd (:1427) keep requiring 'cli'. That is correct, since neither a merge
|
||||
decision nor a role.revoke decision is eligible. The build should name each of these.
|
||||
|
||||
N4. Today's `--sso --send` owner writes already reach the bus through human-cli and record via=cli
|
||||
(webui cli.mjs:80, bus.mjs:32, runtime.mjs:226). So #approvedGrant's `via === 'cli'` check doesn't by
|
||||
itself tell a terminal from the Console. That's acceptable, because the Console wires only a fixed verb
|
||||
list. The build should keep decision.resolve off that list, so a Console grant answer can only take
|
||||
the restricted path. A test or mutant that checks the list would hold that.
|
||||
|
||||
N5. readProcess keeps only an allowlist of environment keys (human.mjs:34). The Console nonce and the
|
||||
provider, iss and sub keys need adding there. The J5 mutant (other nonce namespace) depends on that
|
||||
list, so the build needs both.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Row 256 b1 early look (webby-0124)
|
||||
|
||||
Candidate: ec8262474 on honker/row256, base be2b008cc (refactor after 194). Honker's note: honker-0141.
|
||||
This is an early look. No tests have run, so it isn't the manifest round.
|
||||
|
||||
## Verdict: no blockers in the code
|
||||
|
||||
The code matches design d2 (8c50481d2) and the BUILD.md correction.
|
||||
|
||||
- protectedRefusal at instance-settings.mjs:32-38 refuses the non-creative END_PROTECTED definition for every
|
||||
actor before the human check, so M2 has a real target (T3 on an assistant instance).
|
||||
- deleteRefusal, planDelete, checkDelete and set op delete all take the actor. commit's and fire's
|
||||
listings read with the writer's actor.
|
||||
- runtime.mjs passes the actor at `instances.read(id,actor)` and `instances.checkDelete(id,args.instance,actor)`.
|
||||
The broker is untouched. runtime.mjs:566-567 exposes lifecycleTake and lifecycleDelete, so T6's calls resolve.
|
||||
- T4 asserts the last-listed BusinessError (R1 from d1). T5 includes the undefined actor. T6 runs on the real
|
||||
startBroker runtime and pins human-required for the chief and tester-refused for the tester (N1 from d1).
|
||||
- agents.js changes a comment only.
|
||||
- The persona-file note in BUILD.md is sound: add refuses a retired name, so no later instance reads it.
|
||||
|
||||
## N1: two comments name the wrong definition (fix before the manifest round)
|
||||
|
||||
- instance-settings.mjs:30 says "The chief of staff is refused for every actor, the human included."
|
||||
- agents.js:46 says "true for the chief of staff and on the tester's".
|
||||
|
||||
The non-creative END_PROTECTED definition is `assistant` (vocabulary.mjs:203, THREAD_DEFINITION; END_PROTECTED =
|
||||
THREAD_DEFINITIONS). The chief-of-staff definition isn't END_PROTECTED, so its endProtected is false and the page
|
||||
offers it Delete, which then refuses organization-instance-referenced because the chief is launch.by. BUILD.md
|
||||
already records this; the two comments still carry the brief's old wording. Both should say the assistant.
|
||||
|
||||
## Manifest round checks
|
||||
|
||||
- Broker diff empty; runtime.mjs diff is the two actor arguments only.
|
||||
- T6 green on the real runtime.
|
||||
- M1, M2, M3, M4a and M4b killed on a green base, with the exact strings from BUILD.md.
|
||||
- N1 comment fix present.
|
||||
- Touched test files run whole.
|
||||
|
||||
## b2 aed679663 (honker-0143), webby-0125
|
||||
|
||||
The diff from ec8262474 is one commit and 3 files. instance-settings.mjs:30 and agents.js:46 each change one comment line,
|
||||
now naming the assistant, and BUILD.md gains 7 lines. There are no code lines and no co-author line. N1 is closed.
|
||||
The manifest round comes after the 236 rebase and the gate.
|
||||
|
||||
## r2 1e761962a on bd5f11ded (honker-0145), webby-0126
|
||||
|
||||
I carry-checked the rebase onto 236's landing using git only. No tests ran.
|
||||
- bd5f11ded is an ancestor of the tip, and the tip has 4 commits. Range-diff be2b008cc..aed679663 against bd5f11ded..1e761962a gives 4/4 `=`.
|
||||
- The series patch-id is the same on both bases (b28e63746f8c). So the runtime.mjs hunks, the only path both 236 and this row touch, are byte-identical.
|
||||
- The path list is unchanged at 10 paths. I rebuilt the manifest from the branch and it matches Honker's manifest-r2.txt (sha256 7435f0c4). There are no co-author lines.
|
||||
- 236 also changed broker.mjs, which creative.test's runtime() starts. Honker's code read says T6's paths are untouched. The T6 run at the gate is what confirms that.
|
||||
The carry holds. The manifest round runs on the gate results.
|
||||
|
||||
## r2 early signal (honker-0148), webby-0127
|
||||
|
||||
I read Honker's logs at /mnt/storage/scratch/honker-t/r256/run-20261022T094804Z, which Honker pointed me to.
|
||||
- head.txt names candidate 1e761962a. The base runs pass 16+9+4+2 = 31, with 0 fail and 0 skipped, and skips.txt is empty.
|
||||
- T6 is green on the real runtime on bd5f11ded. That closes the 236 runtime() question from webby-0126.
|
||||
- All 5 mutants are KILLED. Each restore blob matches the head blob. The failing tests match Honker's report:
|
||||
- M1: T1-T3,T5 and T4.
|
||||
- M2: T1-T3,T5.
|
||||
- M3: T1-T3,T5 and T6.
|
||||
- M4a: T6.
|
||||
- M4b: T1-T3,T5, T4 and T6.
|
||||
This is an early signal, not gate evidence. The manifest round runs on the post-274 rebase and its full gate.
|
||||
|
||||
## r3 f12441d4f on 0165be266 (honker-0150), webby-0128
|
||||
|
||||
I carry-checked the rebase onto 274's landing using git only. The gate is running in Honker's slot.
|
||||
- 0165be266 is an ancestor of the tip, and the tip has 4 commits. Range-diff bd5f11ded..1e761962a against 0165be266..f12441d4f gives 4/4 `=`.
|
||||
- With `git diff BASE TIP | git patch-id --stable` I get b28e63746f8c on both bases, the same value as r1 and r2. Honker's c09943e0f3d2 must come from another command. It doesn't matter, because the ids match within each pair.
|
||||
- Four paths overlap with 274: DEFERRED.md, instance-settings.mjs, instance-settings.test.mjs and runtime.mjs. On each, the md5 of this row's +/- lines is the same on both bases, so 274's hunks sit outside them.
|
||||
- The path list is unchanged at 10 paths. I rebuilt the manifest from the branch and it matches manifest-r3.txt (sha256 01eb75aa). There are no co-author lines.
|
||||
- USER_VERBS (broker.mjs:132) has no instances verb, and :3590 refuses any other verb access-refused. So a 274 user session can't reach delete.
|
||||
The carry holds. The manifest round runs on this gate's results.
|
||||
@@ -0,0 +1,109 @@
|
||||
# Row 256 (#1761) design draft 1 review (Webby, webby-0122)
|
||||
|
||||
Candidate: agents/honker/work/queue-256/DESIGN.md at c3e0bd9db on honker/row256 (sha256 62f93ce9),
|
||||
base be2b008cc. Brief: docs/plans/2026-10-21_creative-delete-human.md. Line numbers are from be2b008cc.
|
||||
I ran nothing. This was a read against the code at the base.
|
||||
|
||||
Verdict: the shape is accepted with one required change (R1). The answers to the two questions are below.
|
||||
|
||||
## What I checked
|
||||
|
||||
- `protectedRefusal` matches the brief's three cases.
|
||||
- A creative definition passes for `document.human` only.
|
||||
- Any other actor gets `organization-creative-human-only`, the code `creativeHumanOnly` uses
|
||||
(launch-settings.mjs:13-17).
|
||||
- A non-creative END_PROTECTED definition gets `organization-definition-refused` for every actor.
|
||||
- It mirrors `closed` at instance-settings.mjs:80.
|
||||
- The actor reaches every path the design lists.
|
||||
- At the verb, broker.mjs:2843 passes `s.human` to `instances.check`. For a human session
|
||||
`s.human` is `b.human` (broker.mjs:904). It is `false` for role and tester sessions
|
||||
(:834, :912).
|
||||
- The launcher pass, lifecycleDelete at broker.mjs:2327, passes `r.by`. That is `#owner(s)`
|
||||
(:603, :708-710), which returns `s.human` for the human.
|
||||
- runtime.mjs:283 is the only place that drops the actor today. runtime.mjs:279 `instances` drops
|
||||
it too, which matters for the listing. The broker's read already sets `"tester"` or `s.human`
|
||||
(broker.mjs:2627).
|
||||
- So the broker needs no logic change, as the design says. lifecycle-verbs.test.mjs:335 already
|
||||
pins `'jason'` as the launcher pass's actor.
|
||||
- No delete bypasses the bus.
|
||||
- The Console's POST /api/instances goes through `instancesWrite` (serve.mjs:677-680).
|
||||
- The tester's op delete is refused `tester-refused` at the verb (broker.mjs:2595) before
|
||||
`#deleteRequest`.
|
||||
- The chief's session has `human: false` and no organization.set path to instances.set
|
||||
(:2585, :2589-2590).
|
||||
- `endProtected` has one consumer in src, agents.js:46. Every internal `read()` has the actor to
|
||||
hand.
|
||||
- commit's listing at :405 gets `{ actor }`.
|
||||
- fire's listing at :338 has the chief's actor.
|
||||
- A `read(business)` with no actor gives `true` for a creative entry, so the default refuses.
|
||||
- The persona choice holds. A retired name can't be added again (instance-settings.mjs:219), so a
|
||||
later add can't inherit the file.
|
||||
|
||||
## R1 (required): T4 builds a business file the validator refuses
|
||||
|
||||
T4 puts a creative instance in `launch.keep`. business.mjs:208 refuses that file: "keep names X,
|
||||
whose definition ... can't be kept". The veto writer refuses it too (instance-settings.mjs:284).
|
||||
|
||||
The test would pass, because set op delete runs `planDelete` before `commit` validates. But it
|
||||
pins a state no valid file reaches.
|
||||
|
||||
The other named references can't hold a creative instance either:
|
||||
- An arbiter can't declare messaging lists (business.mjs:251-255).
|
||||
- `launch.by` needs `role.launch` (:181-183).
|
||||
|
||||
The later check a creative delete can really reach is deletedDocument's last-listed refusal
|
||||
(instance-settings.mjs:64-66). Use that: bean as the only `launch.instances` name. The human's
|
||||
delete then refuses the BusinessError rather than `organization-creative-human-only`. That still
|
||||
proves protectedRefusal passed and the later checks ran. Keep T1 for the success path.
|
||||
|
||||
## Answers
|
||||
|
||||
1. **Keep the `endProtected` name.** agents.js:46 is its only reader, and that reader means "don't
|
||||
offer Delete", which is exactly the per-viewer value. A rename churns five fixtures for no change
|
||||
in behavior.
|
||||
- Rewrite the comment at instance-settings.mjs:28-31: "no one deletes one yet (sage-2164,
|
||||
DEFERRED)" stops being true.
|
||||
- Rewrite the agents.js:45 comment too.
|
||||
- T5 should also pin `read(business)` with no actor as `true`.
|
||||
2. **Put the bus test in creative.test.mjs.** Its `runtime()` (creative.test.mjs:93-127) is a real
|
||||
`startBroker` with the business writer, the human, the tester, the chief and bean.
|
||||
- instances-verb.test.mjs uses a recording stub writer (:29, :107), apart from one coder-only
|
||||
runtime test (:62-68).
|
||||
- lifecycle-verbs.test.mjs's writer is a stub too.
|
||||
- A stub can't show the actor reaching protectedRefusal, so T6 has to run on the real writer.
|
||||
|
||||
## Notes (non-blocking)
|
||||
|
||||
- **N1.** The design says M1 is "killed by T2 and T6". Through the bus, no non-human actor reaches
|
||||
the writer's delete.
|
||||
- The tester is refused at broker.mjs:2595.
|
||||
- The chief gets `human-required` at :2585.
|
||||
- So only T2 kills M1. Correct the claim, and pin the chief's code in T6 as `human-required`, not
|
||||
the creative code.
|
||||
- **N2.** Neither listed mutant covers the threading itself. I recommend an M4: runtime.mjs:283
|
||||
drops the actor, or `set` op delete stops passing it to `planDelete`. The human's real-writer
|
||||
delete then refuses `organization-creative-human-only`, and T6 kills it. That's cheap, and it
|
||||
guards the part of the change the brief names first.
|
||||
- **N3.** The DEFERRED.md:387 closing line is fine. Fire stays refused by REQ-COS-3, per the brief's
|
||||
item 2.
|
||||
|
||||
## At the manifest round
|
||||
|
||||
- R1 and the answers are applied.
|
||||
- T5 pins the no-actor read.
|
||||
- T6 runs on creative.test's real runtime: the take, then lifecycleDelete commits with `r.by`.
|
||||
- M1-M3 are killed on a green base, plus M4 if Honker takes N2.
|
||||
- The broker diff is comments only.
|
||||
|
||||
## Draft 2 8c50481d2 (honker-0139), webby-0123
|
||||
|
||||
DESIGN.md sha256 is 141a59e1. The only file in the diff from c3e0bd9db is DESIGN.md (+28/-12). I read every changed line.
|
||||
|
||||
- R1 is met. T4 uses bean as the only launch.instances name and expects the last-listed BusinessError. The invalid keep, by and arbiter forms are gone.
|
||||
- Q1 and Q2 are applied: the name is kept, both comments are rewritten, T5 pins the no-actor read as `true`, and the bus test is in creative.test.mjs.
|
||||
- N1 is applied. M1 is killed by T2 only, and T6 pins `human-required` (:2585) and `tester-refused` (:2595).
|
||||
- N2 is applied. M4 comes in two variants:
|
||||
- (a) runtime.mjs:283 drops the actor, and T6 kills it.
|
||||
- (b) set op delete doesn't pass the actor to planDelete. T1 kills it, because the human's business-level delete then refuses, and so does T6's launcher pass.
|
||||
|
||||
The design is accepted as written. Nothing is open before code.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Row 256 manifest round 1 (webby-0129): APPROVE
|
||||
|
||||
Candidate: manifest-r3, digest 01eb75aa9d51367337f4eda855ad5271c5e22b14650aa5a2130333d275a7ab90, for f12441d4f on
|
||||
honker/row256-r3. Base 0165be266. Issue #1761, queue rev 3181. Honker's request: honker-0154.
|
||||
|
||||
## Bytes
|
||||
- The queue round's manifest text lists 10 paths and matches the manifest I rebuilt from the branch with
|
||||
`git show f12441d4f:<path> | sha256sum` (webby-0128). broker.mjs isn't among them; the bus change is the two
|
||||
runtime.mjs actor arguments.
|
||||
- Range-diff against the reviewed b2 aed679663 is 4/4 `=` across both rebases (webby-0126, webby-0128). The landing
|
||||
patch-id is b28e63746f8c (`git diff BASE TIP | git patch-id --stable`).
|
||||
- origin/refactor is still 0165be266, so there's no base drift on the manifest paths or on anything the tests read.
|
||||
- No co-author lines.
|
||||
|
||||
## Gate (Honker's logs, read by me)
|
||||
- gate-20261022T103414Z merged f12441d4f --no-ff onto 0165be266. The merge commit ced6bf5f has the candidate's tree
|
||||
(4e4798438). The DEFERRED keep-both fallback didn't fire.
|
||||
- 18 package suites: 3757 pass, 0 fail (awk sum over pkg-*.txt).
|
||||
- All 10 scripts/test-*.sh are rc=0. I counted 10 scripts in scripts/.
|
||||
- 4 skips, none on a touched path:
|
||||
- the webui editor rebuild needs esbuild 0.28.2, and 0.28.1 is installed;
|
||||
- test-queue verify/render runs on a non-canonical root;
|
||||
- the test-task live provider cases (row 244 rule), counted twice.
|
||||
- No leftovers.
|
||||
- run-20261022T103355Z on f12441d4f:
|
||||
- The four touched files run whole: 16+9+4+2 = 31/0/0.
|
||||
- T6 is green on the real startBroker runtime with 236 and 274 in the base.
|
||||
- M1, M2, M3, M4a and M4b are all KILLED, and every restore blob equals the head blob.
|
||||
- The failing tests per mutant are: M1 T1-T3,T5 + T4; M2 T1-T3,T5; M3 T1-T3,T5 + T6; M4a T6; M4b T1-T3,T5 + T4 + T6.
|
||||
|
||||
## Design checks carried
|
||||
- d1 R1: T4 uses the last-listed refusal.
|
||||
- d1 N1: T6 pins human-required for the chief and tester-refused for the tester.
|
||||
- d2 N2: M4 runs in two variants (M4a, M4b).
|
||||
- b1 N1: the comments name the assistant (b2).
|
||||
- The persona file stays on delete. The retired name blocks reuse, per the BUILD.md note.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
No blockers. I record on the round once the Gitea request is posted (Honker's attempt failed pre-send; Sage posts it).
|
||||
|
||||
## Recorded
|
||||
|
||||
Sage posted the verdict on #1761 as jarvis, comment 30519 (sage-2595). The request is comment 30518. I recorded `queue review record 256 --verdict approve --comment 30519 --candidate 01eb75aa...` with op webby-256-verdict-r1, rev 3185. Messages webby-0130 and webby-0131.
|
||||
@@ -0,0 +1,91 @@
|
||||
# Webby review: row 268 (C2-01c playground levels, #1773, Vikunja 464), round 1, static
|
||||
|
||||
Webby, 2026-10-21T23:15Z (from `date -u`). Author: Gizmoduck (gizmoduck-0151). Candidate 9d9ee12c3 on
|
||||
gizmoduck/row268, one commit on refactor db1e7732a, 9 paths. The brief is
|
||||
docs/plans/2026-10-20_case2-rows.md "C2-01c" (blob a6ac627c8), which I approved at d2 (88f7ebe0b).
|
||||
|
||||
So far this is a static review. Another seat's slot was running the cli tests at 2026-10-21T23:15Z, so I didn't run
|
||||
repo node, `queue show` included. I haven't run tests or checked the manifest against the queue
|
||||
record. Both wait for Sage's CLEAR and the posted request. I record nothing until then.
|
||||
|
||||
## Static verdict: clean, no blockers
|
||||
|
||||
### Source
|
||||
|
||||
- mapping.mjs:71 refuses a `kind` key other than `"hybrid"` or `"draft"`. A draft refuses `books`
|
||||
before the key check, takes `kind`, `name` and `playground`, needs a prefix, and comes back as
|
||||
`{kind: 'draft', name, books: [], playground}` inside the deep freeze.
|
||||
- mapping.mjs:117-120. A source with any `kind` refuses, which covers hybrids and drafts. The plain
|
||||
universe `out` never carries `kind`, so `hasOwn` is the right test.
|
||||
- mapping.mjs:127-140, the level check, runs after the overlap checks. For each prefix and book:
|
||||
equal to any book gives `library-mapping-invalid`; inside or holding another entry's book gives
|
||||
`library-playground-other-universe`, and the message says which and names the owner; holding its
|
||||
own book gives `library-mapping-invalid`. Inside its own book is book level, and touching no book is
|
||||
universe level. This matches the ruling and the brief, including a hybrid with `books: []`. A
|
||||
draft has no books, so any book its prefix touches is another entry's.
|
||||
- scope.mjs:12-24 checks every entry's prefixes first, sets `book` to the entry's book that holds
|
||||
the prefix or null, then checks canon. `place` keeps the old type and folder rule. Since prefixes
|
||||
can't overlap, at most one matches, as the comment says.
|
||||
- Readers. reader.mjs:206-209 reads `sources ?? []`, so a draft target gives R6, and a draft can't
|
||||
be a canon file's universe because it has no books. Nothing in the reader reads `book`.
|
||||
survey.mjs:66 and mirror.mjs:246 read only `scope`. My grep over packages/*/src finds no other
|
||||
caller of this `classify` and no other reader of `.book`.
|
||||
|
||||
### Tests against the brief's list
|
||||
|
||||
All present. The levels test covers a plain universe, a hybrid with `books: []`, and a draft, plus
|
||||
`.canvas` and near-miss paths (`Harbor Ideasx/a.md`, `Drafts/New World.md`) staying unmapped. The
|
||||
refusals cover inside another's book, holding another's, a draft prefix inside a book, equal to own
|
||||
and to another's book, holding own, a draft with `books` (and `books: []`), with none or empty
|
||||
prefixes, an extra key, five bad `kind` values, a draft source, and a cross-entry hold and duplicate.
|
||||
The draft shape test checks freezing. The prefixes-first test includes `Book One/Playground.md` as
|
||||
canon beside the `Playground/` prefix. The reader test gives R5 for a universe-level target and R5 plus
|
||||
R6 for a draft target. business.test.mjs loads `Elsewhere/Playground/` and refuses
|
||||
`Second World/Playground/` with the new code.
|
||||
|
||||
### Docs and gate
|
||||
|
||||
- `git grep library-playground-outside-book 9d9ee12c3 -- packages docs/case2` gives no hit (rc 1).
|
||||
- README, SETUP-SHEET "Your playground" and SURVEY-RUN:114 match the code and the messages.
|
||||
|
||||
## Non-blocking
|
||||
|
||||
N1. mapping.mjs:3, the edited header comment line, runs past the file's wrap width. Cosmetic.
|
||||
|
||||
## On CLEAR
|
||||
|
||||
1. `queue show 268`: manifest sha256 and paths against `git diff --name-only db1e7732a 9d9ee12c3`
|
||||
(9 paths), plus the request.
|
||||
2. Whole files: packages/library/tests/mapping.test.mjs, scope.test.mjs, reader.test.mjs, and
|
||||
packages/business/tests/business.test.mjs.
|
||||
3. Two mutants, restored by blob check: (a) scope.mjs canon pass before the prefix pass, which the
|
||||
prefixes-first test must kill; (b) drop the holding-own-book `bad`, which the `Shelf/` case must kill.
|
||||
|
||||
## Runs on Sage's CLEAR (sage-2230), 2026-10-21T23:33Z
|
||||
|
||||
Request sage-268-review-1, posted as jarvis, #1773 comment 30192 (revs 3063 and 3064). The candidate in
|
||||
the round is commit 9d9ee12c3c04c5d39fbee23f7f10a0e5e4b5689d. That matches gizmoduck/row268.
|
||||
`git diff --name-only db1e7732a 9d9ee12c3` gives the same 9 paths Gizmoduck listed.
|
||||
|
||||
Slot 23:26:48Z to 23:27:06Z, in a detached worktree at 9d9ee12c3, with TMPDIR under /mnt/storage/scratch/tmp:
|
||||
|
||||
| File | tests | pass | fail |
|
||||
|---|---|---|---|
|
||||
| packages/library/tests/mapping.test.mjs | 11 | 11 | 0 |
|
||||
| packages/library/tests/scope.test.mjs | 3 | 3 | 0 |
|
||||
| packages/library/tests/reader.test.mjs | 14 | 14 | 0 |
|
||||
| packages/business/tests/business.test.mjs | 19 | 19 | 0 |
|
||||
|
||||
Mutants, each restored with git checkout and checked by blob hash:
|
||||
- canon-first: scope.mjs runs the canon pass before the prefix pass. Killed, 2 fails: "each level gives
|
||||
its universe, and its book or null" and "prefixes come first...". Restored.
|
||||
- no-own-hold: mapping.mjs drops the holding-own-book `bad`. Killed by "a prefix equal to a book
|
||||
folder, or holding its own, refuses library-mapping-invalid". Restored.
|
||||
- The final tree was clean.
|
||||
|
||||
The gate grep `git grep library-playground-outside-book 9d9ee12c3 -- packages docs/case2` finds no hit
|
||||
(rc 1). As a positive control, it hits 3 files at db1e7732a.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
N1 (cosmetic) stands. The library suite and the other touched suites run on Sage's gate, as the row's gate says.
|
||||
@@ -0,0 +1,30 @@
|
||||
# Webby review: row 268 r2 (C2-01c, #1773), the rebase onto dcd8d4dda, static
|
||||
|
||||
Webby, 2026-10-22T00:00Z (from `date -u`). Author: Gizmoduck (gizmoduck-0159).
|
||||
I sent this ahead of any request, per sage-2252.
|
||||
|
||||
- **Candidate.** origin/gizmoduck/row268-r2 at ecd9b35eb. It is one commit on dcd8d4dda, and dcd8d4dda
|
||||
is origin/refactor's tip.
|
||||
- **Approved round.** r1 9d9ee12c3 on db1e7732a, APPROVED and recorded (webby-0093, rev 3070).
|
||||
|
||||
## Verdict: APPROVE, carried from r1
|
||||
|
||||
What I checked, git only, with no runs:
|
||||
- **The patch.** `git range-diff db1e7732a..9d9ee12c3 dcd8d4dda..ecd9b35eb` gives `1: = 1:`.
|
||||
- **The paths.** The manifest paths match r1's. Every path has the same blob as at 9d9ee12c3 except
|
||||
packages/library/README.md.
|
||||
- **The README.** It differs from r1 only by row 254's 7-line paragraph at :176-181, in the `library.sync`
|
||||
section. Row 268's hunks are at :34-60 and don't change.
|
||||
- **The base.** db1e7732a..dcd8d4dda changes nothing under packages/library or packages/business except
|
||||
that README.
|
||||
- The other paths it changes are bus human-cli, channel-proof, library-sync, webui bus and
|
||||
library-status.
|
||||
- None of the four files I ran in r1 imports them: mapping, scope, reader and business.test.
|
||||
- **Tests reading the README.** `git grep` over packages/*/tests at ecd9b35eb finds no test that reads
|
||||
packages/library/README.md and no hash pin on it. The README.md hits are project-files and
|
||||
spec-review fixtures for `docs/README.md`.
|
||||
|
||||
So r1's runs still cover r2: the four whole files, the two mutants and the gate grep. I see no reason
|
||||
to rerun them. If Sage's merge-scoped gate wants its own runs, they belong to the gate, not this review.
|
||||
|
||||
I record against Sage's merge-scoped request once it's posted, with r2's candidate digest.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Webby carry check: row 268 r3 aca53c4f0 (gizmoduck-0174, sage-2455)
|
||||
|
||||
Read at 2026-10-22T04:51Z (from `date -u`), git only. Nothing has run.
|
||||
|
||||
**Verdict: APPROVE as a carry of r2 ecd9b35eb (webby-0096, rev 3070). The r3 rerun counts are still owed.**
|
||||
|
||||
- **Base.** origin/gizmoduck/row268-r3 = aca53c4f0dfc4950711669c1177b1e29f1a04a7e. Its parent is
|
||||
0fa7bf421, which is also the current origin/refactor tip.
|
||||
- **One commit.** The author is Jason Woltje (the push identity). It has no attribution line.
|
||||
- **range-diff.** `1: ecd9b35eb = 1: aca53c4f0`.
|
||||
- **patch-id --stable.** 2942acc8e6e2b0171b96ebbf033ab5c08c0f0eac for both commits.
|
||||
- **Paths.** The same 9 paths in both, with name-list sha256 caa0fa4e...adeb. `git diff ecd9b35eb aca53c4f0`
|
||||
is empty on all 9.
|
||||
- **Drift from r2's base (dcd8d4dda) to 0fa7bf421.** 105 files changed. None of them are under
|
||||
packages/library or packages/business. The scripts/ changes include scripts/mosaic: 191 added
|
||||
`halt|resume|processing` and `access` to the cli dispatch line (:44). The `library survey` branch at
|
||||
:40-42 sits before that case line and doesn't change. So survey.test.mjs:94-99 (spawns
|
||||
`scripts/mosaic library survey`) still reaches survey.mjs the same way.
|
||||
- **Imports.** The library and business tests may import packages that did change in the move. I didn't
|
||||
trace each import. The rerun Gizmoduck has planned covers the brief files, library and business, so it
|
||||
settles that.
|
||||
- **Before landing.** Gizmoduck sends the r3 rerun counts, and they match r2 (library 83/83, business
|
||||
209/209, brief files green). The r2 mutants M1-M3 don't need a rerun, because the 9 blobs are
|
||||
identical.
|
||||
|
||||
**Rerun (gizmoduck-0177, on CLEAR sage-2461, 04:50:29-04:50:38Z), as Gizmoduck reported it.** I haven't re-read
|
||||
the logs, since gizmoduck-0177 named none.
|
||||
- brief files 14/14;
|
||||
- library 83/83, survey.test included;
|
||||
- business 209/209;
|
||||
- 0 skipped;
|
||||
- worktree clean afterwards.
|
||||
|
||||
These match r2's library and business counts. The carry is complete, and I have no blockers on r3.
|
||||
|
||||
## r4 c27207f6e carry check (gizmoduck-0179, sage-2480)
|
||||
|
||||
r4 is a carry of r3 with no blockers. Its parent is ce6a96ad6, the refactor tip after 205 landed.
|
||||
- range-diff shows `1: aca53c4f0 = 1: c27207f6e`.
|
||||
- patch-id 2942acc8 is the same on both sides.
|
||||
- The path list sha256 is caa0fa4e…adeb. The 9 blobs diff to 0 lines.
|
||||
- There is one commit, and it has no attribution line.
|
||||
- Drift 0fa7bf421..ce6a96ad6 covers four files: broker.mjs, message-filter.test and Morgana's 205 packet. None of them is a 268 path. Some business tests mention `broker`, but the rerun covers those suites on the new base.
|
||||
- Rerun counts, as reported: 14/14, 83/83, 209/209, 0 skipped.
|
||||
@@ -0,0 +1,118 @@
|
||||
# Row 278 (#1783) early source read, Webby
|
||||
|
||||
Candidate: gander/row278-b source commit e7d21b91b (parent 2c350a466, base 6af85df4b),
|
||||
packet tip 0011fe357. Manifest digest d6e1476c8c37f41e. Messages gander-0090, gander-0091.
|
||||
Git and reading only. No node, test or browser ran on my side.
|
||||
|
||||
## Verdict: no blockers in the source read
|
||||
|
||||
## What I checked
|
||||
|
||||
- 6af85df4b is an ancestor of origin/refactor. 0011fe357 changes only packet files
|
||||
(BUILD.md, MANIFEST.md, SOURCE.json, manifest-check.txt).
|
||||
- Rebuilt all six manifest hashes from `git show e7d21b91b:<path> | sha256sum`. All six match.
|
||||
- The change lines of `git diff 2c350a466 e7d21b91b` equal the frozen CANDIDATE.patch lines
|
||||
once both are sorted. Only the file order differs, so a plain patch-id compare fails. No byte moved.
|
||||
- No co-author lines in 6af85df4b..0011fe357.
|
||||
- serve.mjs: the tester guard sits in the /api/bus/ branch after the 404 allowlist check
|
||||
and the 405 method check, and before `busRead`. It refuses with 403 `tester-refused`
|
||||
before any bus callback runs (R2). The 400 for a query string sits in busRead, so a tester
|
||||
gets 403 even with a query. That is the right order.
|
||||
- bus.js:475-491 `threadClaimsSection`. Its catch rethrows only `signed-out` and evicts only
|
||||
`thread.claims` from `last` (R1). The `get` helper (:65-73) has no refusal side effect of its
|
||||
own, so a 403 never reaches `forget()` or the global refusal path. A malformed row throws
|
||||
`invalid-response` after `last.set`, and the catch deletes that entry.
|
||||
- Projection is four fields through `txt()` and `when()`. `instance` and the other extras are
|
||||
never read. The hostile-field browser test asserts they are absent and the row has 4 cells.
|
||||
- bus.js:492-500 `agentsView`. The section goes after the unchanged Held roles section. The
|
||||
role count is still `claims.length` of the `agents` read, so the brief's M2 failure mode
|
||||
(claims counted into role totals) shows as a change in the byte-equal role section. Gander's
|
||||
M2 (merge thread rows into roles) maps to it.
|
||||
- cli.mjs:86 adds the argument-free adapter. The adapter test pins `{verb, args: [], tester: false}`.
|
||||
|
||||
## Notes (non-blocking)
|
||||
|
||||
N1. `configRead` ends in `.catch(() => {})`, so a failed /api/config leaves `isTester` false. A
|
||||
tester browser in that state requests the read and shows the local 403 banner. The server guard
|
||||
still holds, so this only affects display. No change needed.
|
||||
|
||||
N2. `agentsView` awaits the claims read after the agents read, so each Agents render makes two
|
||||
round trips in sequence. Fine at this size. Worth a `Promise.all` only if the view gets slow.
|
||||
|
||||
N3. gander/row278-b is not on origin. The manifest round reviews the pushed first-parent tip,
|
||||
so push before the request. Gander's "no push until green" is consistent with that.
|
||||
|
||||
## For the manifest round
|
||||
|
||||
- Intended RED cases observed on the base, then GREEN, with byte-identical restores.
|
||||
- Both touched test files run whole. Bus whole, webui whole, all ten scripts/test-*.sh.
|
||||
- Kill table for M1-M6: M3 (403 rethrow) killed by the byte-equal role test, M4 (tester guard)
|
||||
by the socket test's status and call count, M6 (cache kept on refusal) by the stale repaint.
|
||||
A timeout counts as a stop, not a kill.
|
||||
- Record only after the request is posted, with my verdict comment id.
|
||||
|
||||
## Run 1 stop (gander-0092): fixture repair read
|
||||
|
||||
Red: thread-claims.test.mjs:54. The loader fake of webui bus.mjs exports only `busCaller`, and
|
||||
chat.mjs:7 imports `BusReadError` from it. I traced the static import graph at e7d21b91b with git:
|
||||
|
||||
- bus.mjs is imported by cli.mjs:5 (`busCaller`), chat.mjs:7 (`BusReadError`), serve.mjs:7 and
|
||||
chat-http.mjs:2. serve.mjs is faked, and chat-http.mjs is reached only through serve.mjs, so
|
||||
with the fake in place the only names needed are `busCaller` and `BusReadError`.
|
||||
- chat.mjs:15 uses `BusReadError` only inside a catch at call time, so a plain
|
||||
`class BusReadError extends Error` stand-in is enough.
|
||||
- The tester socket starts only under `testerSocket`, which the test argv doesn't set, so
|
||||
nothing else loads the faked serve.mjs.
|
||||
|
||||
So the proposed repair (add an exported `BusReadError` stand-in, keep every assertion) is
|
||||
complete for the import graph and doesn't loosen the test. It changes the test blob, so the
|
||||
manifest digest changes and the run restarts from the top. That ruling is Sage's
|
||||
(mos-1272: first-run red in a new test).
|
||||
|
||||
## Fix check (gander-0093)
|
||||
|
||||
964b6b184 (parent 0011fe357) changes one line in thread-claims.test.mjs: the loader fake now
|
||||
starts with `export class BusReadError extends Error{};`. Nothing else in packages/ moves, and
|
||||
no assertion changes. Record tip 42abda031 touches only BUILD-LOG.md and the queue-278 packet.
|
||||
All six hashes in the new manifest (digest 188071d851e1f682) match the 42abda031 blobs. No
|
||||
co-author lines. Run 1 is void, so run 2 restarts from syntax with the full scope.
|
||||
|
||||
## Run 2 stop (gander-0094): shell.test.mjs read
|
||||
|
||||
Red: shell.test.mjs:463, `count('#bus-view .banner') === 0` on #/agents. stubBus (:96-101)
|
||||
supplies inbox, tasks, agents and trail only, so the new section's read fails and shows its
|
||||
local banner. That is the designed R1 behavior. The old test just doesn't know the verb exists.
|
||||
Mos rules on this one (an existing refactor test, mos-1272).
|
||||
|
||||
I read the rest of the five-states loop (:438-485) for the effect of adding an empty
|
||||
`thread.claims` list and callback to the stub:
|
||||
|
||||
- The rows selector is `table.s1-agents tbody tr`. The claims table is `s1-thread-claims`, so
|
||||
claim rows never add to `n`.
|
||||
- Stale, refusal and recovery all run `s.fail(code)` across every stub verb. agentsView reads
|
||||
`agents` first, so a failing read throws there before the claims section runs, and the view-
|
||||
level stale or refused banner is the one asserted. The `.empty` match on refusal hits the view's
|
||||
own empty state because the section never renders.
|
||||
- The recovery wait `!#bus-view .banner` holds once the stubbed claims read returns [].
|
||||
- Normal state: an empty claims list renders the section's empty line, which has no banner
|
||||
class, so :463 passes.
|
||||
|
||||
So the proposed repair (add `thread.claims: []` and its callback, keep every assertion) is
|
||||
complete for this test and doesn't loosen it. board-held.test.mjs and bus-browser.test.mjs
|
||||
also render #/agents with buses that lack the verb. They passed in run 2's whole WebUI, so they
|
||||
need nothing.
|
||||
|
||||
## Stub fix check (gander-0095)
|
||||
|
||||
9696d1fcb (parent f99c39a3f, which voids run 2) changes two lines in shell.test.mjs:96-101. They
|
||||
add `'thread.claims': []` to the stub's rows and `'thread.claims': verb('thread.claims')` to its
|
||||
bus. That is the same fail/hold path as the other four verbs. No assertion moves. Across
|
||||
42abda031..3e4c77ac6, nothing else in packages/ changes, and scripts/ is byte-identical to
|
||||
6af85df4b. The seven-entry manifest (digest 8fa859a0c617defe) matches every blob at 3e4c77ac6.
|
||||
No co-author lines.
|
||||
|
||||
Shell timeouts: sage-2644 ruled `timeout -k 10 900` per script. Gander's window.py uses tighter
|
||||
per-script limits from SHELL-LIMITS.json (126-240 s, 3x the recorded max plus 120 s, 900 when no
|
||||
record exists). That's stricter than the ruling. A slow but healthy shell could stop on a
|
||||
timeout that 900 would have passed. Sage decides whether that stands. At the manifest round I
|
||||
check that the logs show the wrapper actually used, and that no timeout fired.
|
||||
@@ -0,0 +1,52 @@
|
||||
# Row 278 (#1783) design note review, gander-0087
|
||||
|
||||
The brief is in docs/plans/2026-10-22_owner-thread-claims.md, in the section "Brief: the Console shows thread claims". I read the code at refactor 23c3d6218. This is a design look only. No code exists yet, and the row chains on 277.
|
||||
|
||||
Gander's shape is the right one, and I accept it. The role section stays as is, and a separate table shows four fields: thread id, holder_run, harness and at. It doesn't use the s1-agents selector, empty shows an empty-state line, and the adapter is Console-owned. There are two required changes before code and one note.
|
||||
|
||||
## R1. The local catch must keep 403 local
|
||||
|
||||
There are two problems in the code today.
|
||||
- bus.js:1028 defines `refusal` as a 403 or one of not-configured, no-bus-host or queue-refused.
|
||||
- The existing local-catch idiom (bus.js:815-822) rethrows `err.status === 403`. A rethrow reaches the page handler at :1134-1139. For a refusal, that handler renders failedView and calls forget(), so the role rows are lost.
|
||||
|
||||
If 278 copies the idiom, a 403 from thread.claims wipes the role rows. The brief's item 4 forbids that.
|
||||
|
||||
Required behaviour:
|
||||
- The thread.claims catch rethrows only `err.code === 'signed-out'`.
|
||||
- Every other error, 403 included, renders a section-local reason and drops only the claims cache.
|
||||
|
||||
Required test: the role read succeeds while thread.claims returns HTTP 403. The role rows and count must be byte-equal to a run without claims.
|
||||
|
||||
Required mutant: the catch also rethrows 403. A named test must kill it.
|
||||
|
||||
## R2. The tester listener must refuse with a real refusal, not a missing method
|
||||
|
||||
Two facts about the code:
|
||||
- serve.mjs:619 checks only the BUS_VERBS list.
|
||||
- The generic busRead (serve.mjs:70 onward) then calls `bus[verb](arg)`.
|
||||
|
||||
Suppose 278 adds `'thread.claims'` to BUS_VERBS and to `bus` in webui cli.mjs:78, but not to `testerBus` at :108. Then a tester GET /api/bus/thread.claims throws a TypeError. The handler turns that into 'invalid-response', which is a server-fault answer. It is not a refusal.
|
||||
|
||||
Required behaviour: the tester listener answers thread.claims with an explicit 403 refusal code. It can do this with a tester-only guard, or with a testerBus entry that throws a BusReadError the bus already uses, for example tester-required. It must not answer through a missing function.
|
||||
|
||||
Required test: a tester-socket GET of /api/bus/thread.claims gets 403 with that code, and the bus is never called.
|
||||
|
||||
## N1. Let 277's refusal decide the reader case
|
||||
|
||||
The brief says "a human or reader session". The client shouldn't decide eligibility. It shows whatever the bus answers, and R1 keeps any refusal local. If 277's r2 refuses a reader, the section shows that refusal, and that's correct.
|
||||
|
||||
## Checks at the manifest round
|
||||
- The role rows at bus.js:447, :462 and :474 keep their shape (I'll re-pin the line numbers on the candidate's base). Byte-equal before and after a claims refresh.
|
||||
- The section lists only the four fields, through txt/esc. A test with hostile extra fields (name, body, instance, address) shows that none of them render.
|
||||
- A released claim drops on the next refresh.
|
||||
- M1 (no section) and M2 (claims counted into the role total) are each killed by a named test, and so is the R1 mutant.
|
||||
- A signed-out read still takes the global sign-out path, and no claims data stays cached afterwards.
|
||||
|
||||
## gander-0088 update: R1 and R2 met in design
|
||||
|
||||
- R1: the catch rethrows only `signed-out`, so a 403 drops only the claims cache and renders in the section. A named browser test compares the role section byte for byte with a claims 403, and a new mutant M3 adds the 403 rethrow. That test must kill M3. On sign-out the claims cache is cleared. Met.
|
||||
- R2: a guard on the tester listener, placed before busRead, returns 403 `{error:'tester-refused'}`. The bus already uses that code for verbs a tester can't call (packages/bus/README.md:626). The test uses a counting bus stand-in and requires zero calls. Met.
|
||||
- N1: the client doesn't gate eligibility, and tester mode alone hides the section. Accepted.
|
||||
|
||||
Morgana says 277's r2 rows are `{thread,instance,holder_run,harness,at}`, and the section renders four of those fields. A manifest-round test must show that `instance` is never rendered. No further design changes are needed. I'll review the code at the manifest round, after 277 lands.
|
||||
@@ -0,0 +1,74 @@
|
||||
# Row 278 (#1783) manifest round 1, Webby
|
||||
|
||||
Candidate: origin/gander/row278-b 385a7194c (first-parent, no merges), base 6af85df4b.
|
||||
Runtime e7d21b91b, test fixes 964b6b184 and 9696d1fcb. Manifest digest
|
||||
8fa859a0c617defe42e50028b057a603c6634f6db47ac2e3f7c01dfb290228a3, seven paths.
|
||||
Run 3 records under agents/gander/work/queue-278/run3/ (gander-0096). I read them with git;
|
||||
I ran nothing.
|
||||
|
||||
## Verdict: APPROVE
|
||||
|
||||
## Bytes
|
||||
|
||||
- All seven manifest hashes match `git show 385a7194c:<path> | sha256sum`.
|
||||
- Outside agents/gander/, 6af85df4b..385a7194c changes only the seven manifest paths plus
|
||||
BUILD-LOG.md.
|
||||
- origin/refactor is still 6af85df4b, so there is no base drift.
|
||||
- No co-author lines in 6af85df4b..385a7194c.
|
||||
|
||||
## Gate (run 3, runs 1 and 2 void)
|
||||
|
||||
My own sums of the TAP and selftest lines in the raw logs:
|
||||
|
||||
- Syntax 6 files, no output.
|
||||
- Intended RED: red-0 and red-1 each 0/1 on the base source, then restored.
|
||||
- Focused whole files: thread-claims 4/0/0, thread-claims-browser 6/0/0, shell 5/0/0.
|
||||
- Bus whole 793/0/0. WebUI whole 345/0/1.
|
||||
- Ten shells, 425/0 in total: auth 15, conductor 17, config 31, discord 81,
|
||||
extension-package 18, foundation 44, land-work 86, queue 28, release 14, task 91.
|
||||
- The WebUI skip is the pinned-esbuild rebuild test (0.28.1 installed, 0.28.2 needed). That's an
|
||||
environment skip.
|
||||
- Three shell skips the report doesn't name: test-queue's verify and render --check (the seat
|
||||
worktree isn't the queue's canonical root), and test-task's two live-provider cases (row 244,
|
||||
no provider call in the gate). Both are existing refactor behavior, not this row.
|
||||
|
||||
Timeouts (mos-1275, sage-2650): every shell command.json is `timeout -k 10 LIMIT bash
|
||||
scripts/test-X.sh`, with LIMIT taken from SHELL-LIMITS.json. Each limit is ceil(3x the
|
||||
receipt) + 120 s. Every observed run is well under its limit; the longest is foundation at
|
||||
39.9 s against 240 s. results.json shows `timeout: false` for every change.
|
||||
|
||||
## Changes (changes.json, run3/changes)
|
||||
|
||||
| Id | Change | Killer | Failure |
|
||||
|---|---|---|---|
|
||||
| M1 | section returns '' | held/release browser test | wait for #b-thread-claims never met |
|
||||
| M2 | thread rows pushed into the role `claims` before rows and count | same | AssertionError |
|
||||
| M3 | 403 rethrown with signed-out | 403-stays-local test | local `human-required` code never shown |
|
||||
| M4 | tester guard removed | tester socket test | AssertionError on status or calls |
|
||||
| M5 | adapter calls `agents` | adapter test | AssertionError on verb |
|
||||
| M6 | cache kept on refusal | 403-stays-local test | stale repaint `not-read` never shown |
|
||||
|
||||
Each change has a passing base (M2 shares M1's, M6 shares M3's), and every changed run fails in its
|
||||
named test on an assertion or a never-met browser condition. None is a harness timeout or a
|
||||
syntax error. M2 is the brief's "claims counted into role totals". M3 is R1, and M4 plus the
|
||||
tester browser test are R2.
|
||||
|
||||
## Design checks carried
|
||||
|
||||
R1 (only signed-out leaves the section; a 403 evicts only the claims cache) and R2 (tester 403
|
||||
before the bus, and the tester browser skips the section) are in the source and are killed as above.
|
||||
The hostile-field test covers `instance`. The role section stays byte-equal across held, release
|
||||
and refusal.
|
||||
|
||||
## Notes (non-blocking)
|
||||
|
||||
N1. Name the three shell skips (queue canonical-root, task live-provider x2) in the landing
|
||||
record next to the esbuild skip.
|
||||
|
||||
N2. Early-read N1 (a failed /api/config shows the local 403 banner in a tester browser) and N2
|
||||
(two sequential reads on Agents) still stand. Neither needs a change.
|
||||
|
||||
## Recorded
|
||||
|
||||
Request c30587 (revs 3198/3199). Verdict posted on #1783 as jarvis, comment 30588 (sage-2655).
|
||||
Recorded rev 3200, op webby-278-verdict-r1, candidate 8fa859a0c617defe42e50028b057a603c6634f6db47ac2e3f7c01dfb290228a3.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Row 283 (#1788), C2-01d design draft 1, Webby
|
||||
|
||||
Honker's draft is agents/honker/work/queue-283/DESIGN.md at 0761e603c (parent 94c33a43c, doc only,
|
||||
local). I checked it against the C2-01d brief (docs/plans/2026-10-20_case2-rows.md at 94c33a43c), design
|
||||
b68d79663 (the ruling after r11 :207-222, M12 :300-308, 1.1 and 1.2 :800-832), mapping.mjs:120-150 and
|
||||
reader.mjs:14-45. Read with git, nothing run.
|
||||
|
||||
## Verdict: CHANGES (R1)
|
||||
|
||||
The rest matches the brief and the design, and I'd approve it with R1 folded.
|
||||
|
||||
R1. `editor` (section 1, Q5): refuse `<` and `>` in both values here, as `library-mapping-invalid`, and
|
||||
add them to T3 and M6. Git doesn't refuse an ident containing them. It drops them and commits under a
|
||||
different name than the mapping holds, so C2-02 would never see a failure. The mapping is where Jason
|
||||
edits the value, and the brief's "Other malformed values refuse `library-mapping-invalid`" covers it.
|
||||
|
||||
## Answers
|
||||
|
||||
Q2, the cross-list rules: accepted.
|
||||
- A same-entry equal prefix being writable is design :216, :804. A playground holding a same-entry
|
||||
supporting prefix follows from supporting being checked first (1.2).
|
||||
- Refusing a supporting prefix that holds a playground prefix goes past the brief, but it's the
|
||||
fail-closed reading. Without it the inner playground folder would turn writable without being listed
|
||||
as supporting. It uses an existing code, so Sage needn't rule on a new one. Put the tradeoff in BUILD.md.
|
||||
- The cross-entry overlap refusal is needed, because one path can't have two universes. Supporting
|
||||
against supporting across entries is already covered by "holding another, or listed twice".
|
||||
|
||||
Q3, `tree` as the `listTree` array: accepted. listTree runs `ls-tree -r` and keeps every mode
|
||||
(reader.mjs:14 takes `blob|tree|commit`), so a 120000 blob and a 160000 commit entry both reach
|
||||
check 7. `-r` lists no directory entries, so check 8 deriving folders from paths is correct.
|
||||
|
||||
Q4, the delete's 100644 check: it can stay out of `supportingPath`, whose signature has no operation.
|
||||
But the C2-02 brief (rows.md, the C2-02 write steps) never names it. The 100644 rule is only in design
|
||||
:308. Put it under Handoffs in BUILD.md as a carry to C2-02, and Sage adds it to C2-02's brief. Otherwise
|
||||
it gets lost.
|
||||
|
||||
Q5: see R1.
|
||||
|
||||
## Notes (non-blocking)
|
||||
|
||||
N1. Check 8 compares with `toLowerCase()`. Her vault is on a Mac, and APFS also treats NFC and NFD forms
|
||||
of the same name as one file. Compare `normalize('NFC').toLowerCase()` on both sides and add an NFD
|
||||
variant to T6. If you keep plain case, say why in BUILD.md.
|
||||
N2. Section 5, survey (Q1, for Sage): per mos-1330, the manifest round will check that survey.test:73's
|
||||
pin gains only the supporting key and that the new count test is the only addition.
|
||||
N3. Output carries `supporting: []` always. I found no test outside packages/library that deepEquals
|
||||
the mapping output (library-projects-protected.test:15 passes raw input). Grep again on the build base.
|
||||
|
||||
## Draft 2, 16e809457 (parent 0761e603c, doc only): APPROVE
|
||||
|
||||
The diff touches only DESIGN.md (+28/-7) and has no co-author line.
|
||||
- R1 met. `editor` refuses `<` and `>` in either value (section 1), T3 has both cases, and M6b drops the check.
|
||||
- N1 met. Check 8 normalizes both sides with `normalize('NFC').toLowerCase()`. The exact-equality
|
||||
exception stays raw, which is right. T6 has an NFD variant of an NFC tree name, and M4b drops the
|
||||
normalize. That mutant survives only if T6 checks no NFD case, so the kill rule holds.
|
||||
- Check 2 keeps the control class alone. `<` and `>` are legal in a path, and the right call is to leave them.
|
||||
- Q2's tradeoff and Q4's handoff go in BUILD.md (section 10). N2 and N3 are on record for the manifest
|
||||
round. Sage still needs to answer Q1 (survey) and the C2-02 brief edit for the 100644 rule.
|
||||
@@ -0,0 +1,106 @@
|
||||
# Row 285 early read (C2-08b, #1790)
|
||||
|
||||
Candidate: scrooge/row285 5f81d0da0 on 94c33a43c (0e369895b, 8c529fb4d, 5f81d0da0). 33 files.
|
||||
Read only. Nothing run (sage-2934: no runs without a named CLEAR). Not a verdict: no review request
|
||||
is posted yet. Labels below are what I'd raise at the manifest round.
|
||||
|
||||
## Checked and fine
|
||||
|
||||
- mos-1330 item 1. helpers.mjs:71-74 changes the chief fixture only, and the comment above it now says
|
||||
the chief's one reference is gitea. chief-of-staff.test :24, :90-93 and :157-163 change expected
|
||||
values to the role file's exact values, each citing a ruling key. The old-test edits in business.test
|
||||
(persona vars) and instance-settings.test (:335-336, :381) add `credentials` and change nothing else.
|
||||
No assertion is removed or loosened in the diffs I read.
|
||||
- Named tests for a missing ref: business.test "row 285: a chief or creative instance without
|
||||
credentials.gitea refuses at load" covers the chief and all six CREATIVE_DEFINITIONS, with a load
|
||||
check beside each refusal.
|
||||
- mos-1330 item 2. BUILD.md:63-77 has the exact store-form JSON, the file form, says the reference
|
||||
"creates no account, mints no token and grants nothing by itself", and names the store-root
|
||||
precondition (credentials.mjs:212).
|
||||
- mos-1332 item 3. The fence sits at business.mjs:164-169, inside `publish !== undefined` and before
|
||||
N10, clear of :283-291. Named tests: business.test (creative with identity loads, publish refuses
|
||||
with the fence text) and agent-identity.test (assistant publish refuses with the plain N10 text,
|
||||
anchored with `$`).
|
||||
- Mutants traced by reading, not run:
|
||||
- M7 (drop chief): agent-identity.test:48-49 gives the chief identity and publish. The chief now
|
||||
declares gitea, so N10 passes and the file loads. The refuses() fails. Killed.
|
||||
- M8 (drop creatives): the creative test's publish case loads (identity set, gitea declared). Killed.
|
||||
- M9 (drop `!services.includes("gitea")`): the assistant case loads. Killed.
|
||||
- mos-1332 item 4. instance-settings.test:289-290 is `["gitea"]` with the ruling key. BUILD.md:127
|
||||
says the :298 value goes into BUILD.md if it changes at the run. I'll check that at the manifest round.
|
||||
- role.mjs checkCreative refuses crossRole, any withinRole action outside CREATIVE_ACTIONS and any
|
||||
service other than gitea.
|
||||
|
||||
## Raise before in-review
|
||||
|
||||
B1 `packages/business/tests/business.test.mjs:311-314`. The new test carries two stacked comments.
|
||||
The first (C2-08b brief) says a creative instance "may carry identity and publish". The test asserts
|
||||
the opposite. Drop the first comment, or rewrite it to the mos-1332 item 3 text.
|
||||
|
||||
B2 `agents/scrooge/work/queue-285/BUILD.md:83` says no test reads `mosaic-stack.example.json`. Two do:
|
||||
`packages/business/tests/business.test.mjs:256` ("the shipped example refuses as written and
|
||||
validates once filled in") and `packages/cli/tests/stack-release-fixture.mjs:351` (EXAMPLE, used by
|
||||
stack-release.test, stack-release-external.test and stack-upgrade.test). The cli fixture keeps the
|
||||
`/home/you/...` token path as written, so any of those tests that reads the chief's credential reads
|
||||
a file that doesn't exist. Correct the line and put all four test files in the gate run.
|
||||
|
||||
## For Mos and Sage (a ruling question, not Scrooge's to fix)
|
||||
|
||||
Q1. The mos-1332 item 3 fence collides with C2-21 (row 287) as briefed in
|
||||
`docs/plans/2026-10-20_case2-rows.md` at 94c33a43c:
|
||||
- C2-21 has `scripts/case2-install.sh` give the chief `identity` and `publish: ["books"]` so the chief
|
||||
can hold the read grant. Under the fence that chief entry refuses at load.
|
||||
- C2-21 lets a creative role pass #giteaRole for library projects, with "the other checks" staying.
|
||||
Those checks include a nonempty `role.publish` (broker.mjs #giteaRole) and #giteaTargets reads each
|
||||
target project from publish. A creative instance can't carry publish under the fence, so it can
|
||||
never reach a grant.
|
||||
BUILD.md:56-61 ("Binding note for row 287") covers the chief's scopes and the #giteaRole refusal but
|
||||
not the fence. Either the binding note adds "row 287 lifts or reshapes the business.mjs publish fence
|
||||
for library projects, with its own ruling", or the C2-21 brief moves the library grant off `publish`.
|
||||
This doesn't block 285 as ruled. It does need to be written down before 287 starts.
|
||||
|
||||
## Notes
|
||||
|
||||
N1 `role.mjs` checkCreative checks the service but not the scopes. A creative role file declaring
|
||||
gitea with wider scopes loads. sage-2915 holds declared scopes equal to minted ones, and role files
|
||||
change only by reviewed commit, so this is a gap in the fence rather than a hole. If role.test pins
|
||||
each creative file's credentials exactly, say so in BUILD.md and this note closes.
|
||||
N2 `business.mjs:167` uses the literal "chief-of-staff". broker.mjs has STAFF_TO. A shared constant in
|
||||
vocabulary.mjs would keep the two fences on one name. Not required.
|
||||
N3 BUILD.md:135-137: the branch stays red until the ROUTED commit, since every creative role file
|
||||
names library.write, which isn't in ACTIONS. The gate can only run after Jason's yes on 1.16 and
|
||||
commit 3. Sage's slot order should allow for that.
|
||||
|
||||
## Early read 2: d79cfe674 on 5f81d0da0 (2026-10-22T19:04Z, read only)
|
||||
|
||||
- B1 met: business.test drops the stale comment, and only the mos-1332 item 3 comment remains above the test.
|
||||
- B2 met: BUILD.md names both example readers and the gate runs all four test files whole.
|
||||
- N1 closed: role.test:238 deepEquals each creative file's credentials to gitea with
|
||||
["read:user","write:repository"], so a widened scope is red.
|
||||
- N2 closed: the literal stays, and BUILD.md gives the reason.
|
||||
- Open: Q1 (publish fence vs C2-21) with Mos and Sage. The manifest round waits on the review request.
|
||||
|
||||
## Q1 ruled: mos-1333 (2026-10-22T19:05Z)
|
||||
|
||||
Mos withdrew mos-1332 item 3. It contradicted the brief at case2-rows.md:1825, :1829 and :1839 (285)
|
||||
and :1998 (287); I read those lines at 94c33a43c and they say so. Checks for the manifest round:
|
||||
- business.mjs fence removed in its own commit, and business.mjs out of the manifest.
|
||||
- business.test back to "a creative instance with identity and publish loads".
|
||||
- N10 (a): the second case uses an instance whose definition has no gitea. Only the instance and the
|
||||
name in the regex change.
|
||||
- New test: a chief with gitea, identity and publish loads. X-G1 covers the refusal at raise.
|
||||
- A services.includes("gitea") mutant, killed by the new N10 case. M7 and M8 fall away.
|
||||
- mos-1332 items 1, 2, 4 and 5 stand. The 287 binding note needs no fence line.
|
||||
|
||||
## Early read 3: 34ee4df7d on d79cfe674 (2026-10-22T19:08Z, read only)
|
||||
|
||||
mos-1333 met as far as reading shows:
|
||||
- business.mjs equals 94c33a43c (empty diff) and BUILD.md:25 drops it from the files owned.
|
||||
- business.test "row 285: a creative instance with identity and publish loads" equals its 8c529fb4d form.
|
||||
- N10 at agent-identity.test:49-52: only the instance (helper, assistant) and the regex name change.
|
||||
The anchored standalone test is gone.
|
||||
- New chief test loads identity, publish ["stack"] and credentials keys exactly ["gitea"].
|
||||
- M9 traced: without the gitea check the helper case loads, so N10's refuses() fails. Killed.
|
||||
- The restored creative comment cites business.mjs:155-166, which still covers the identity and
|
||||
publish block (:160-168) closely enough.
|
||||
Nothing open. Next is the manifest round once the request is posted.
|
||||
@@ -0,0 +1,68 @@
|
||||
# Row 290 (#1795), C2-12b setup sheet rewrite, Webby
|
||||
|
||||
Louie's draft is louie/row290 3df213e4b (parent aca16404c, local, unclaimed). The manifest is only
|
||||
docs/case2/SETUP-SHEET.md, sha256 e05e8d141b4f4f65ee90b03a37f2f5ae2b284ec960d962807cb921c1c16b3b47, which
|
||||
I recomputed from the blob. There's no co-author line. I reviewed it against the C2-12b brief
|
||||
(docs/plans/2026-10-20_case2-rows.md:2178 at 94c33a43c), design f6e3e7f20 (the ruling after r11, 5.2
|
||||
and the setting table at :236), and Q223 in DECISIONS.md:2208. Read with git, nothing run. At 94c33a43c,
|
||||
no file in packages/*/tests or scripts/ reads the sheet.
|
||||
|
||||
## Verdict: CHANGES (B1, B2)
|
||||
|
||||
The draft covers every point in the brief's "What ships": free chat, cites shown in the Console, the two
|
||||
settings and their defaults, supporting folders, agent write commits with a notice and Revert, Obsidian's
|
||||
next pull, saves under `editor` and the moved-file refusal, the residual, Bean and Chet, and the setup
|
||||
questions. The old item limits and the "No cited answer" text are gone. Two facts need fixing.
|
||||
|
||||
B1. The draft names the wrong place for `supporting` (:51-52 "the `supporting` list in your library
|
||||
settings"). In the design, `supporting` is a list in each universe entry of the business file's
|
||||
`projects` entry, beside `playground` (:216, :803). The sheet already names that entry at :21-23. Say so
|
||||
here, and say Jason edits it by hand as he does the playground. Louie says this is being fixed.
|
||||
|
||||
B2. Keep story out of chat in the sheet. Louie's reply says chat "may provide free-text story text, as
|
||||
the row requires." The brief doesn't say that. It says "Chat is free text of any length". Jason's Q223
|
||||
is "They are NOT allowed to actually write any of the story" and "The creative work is handled by
|
||||
Melanie" (DECISIONS.md:2212). Mos's reading of TARGET.md:101 limits "prose" to story text, so agents
|
||||
answer at length, but nothing rules that they write story passages on request in chat. The draft's :143
|
||||
("They still cannot write story into your book files") is accurate as it stands. Don't add a line telling
|
||||
her an agent will write story for her in chat. If that should be allowed, it's a question for Mos to put
|
||||
to Jason, not a sheet edit.
|
||||
|
||||
## Notes (non-blocking)
|
||||
|
||||
N1. "Colour the playground in your graph" (:64) is now an h3 under "Saves and agent notes". Move it back
|
||||
under "Your playground".
|
||||
N2. :138 `requireCitation` "each answer needs a citation". Add that Mosaic's own notices, such as a
|
||||
failed turn or an answer too long to send, pass without one. That's PRD 1.15 REQ-LIB-6 and design 5.2.
|
||||
N3. :60-61. Say each agent commit is made under that agent's own name (REQ-LIB-6), so she can tell
|
||||
those notes from her own in the history.
|
||||
N4. :4-5 "Mosaic does not change files on your machine" sits next to :61 "Obsidian sees the change on its
|
||||
next pull". Say agent notes and Console saves reach her vault on the next pull. Louie says this is being
|
||||
fixed.
|
||||
N5. Landing: the sheet describes agent writes, which work only after the 1.16 routing (mos-1328) and
|
||||
C2-08b/C2-02 land, and it names settings that C2-06 builds. The brief's After is C2-12 and C2-06. Sage
|
||||
should check the order holds at landing.
|
||||
|
||||
## Round 2 on d3e8d7520 (parents 6450343cd, 3df213e4b, aca16404c): APPROVE
|
||||
|
||||
Manifest docs/case2/SETUP-SHEET.md only, sha256 26dbe432d962f74caf288bda8adb82055363f74897eb804ce92d10fce7af7925,
|
||||
recomputed from the blob. No co-author line in the three commits. Read with git, nothing run.
|
||||
|
||||
- B1 met. :51-53 put the `supporting` list in the `projects` entry of the business file, edited by Jason
|
||||
by hand. :57-58 name `editor` in the same entry.
|
||||
- B2 met. 6450343cd briefly said "including story text in chat". d3e8d7520 drops it, and :145-146 now say
|
||||
free-text answers that cite her books, with repository writes limited to supporting `.md` notes. The
|
||||
sentence about book files is gone, but :46-48 still say unmarked folders are story and agents leave
|
||||
them alone, so nothing is lost.
|
||||
- N4 met. :4-6 say Mosaic does not write directly to her vault and obsidian-git pulls the commits.
|
||||
- I checked one more point. :136 "You or Jason can change these settings". Design :150 has only the
|
||||
business file's `human` run `library.limits.set`. The brief (C2-12b, "she or Jason can change them")
|
||||
asks for this wording, so it stands.
|
||||
- N1, N2, N3 and N5 are still open and still non-blocking.
|
||||
|
||||
## Round 3 on 42b75e484 (parent d3e8d7520), N1-N3 folded: approval carries
|
||||
|
||||
sha256 b3b00732f2b5e9c87ad86caa94caf98fe222da8a15aa85abbea15b8fa9fa4e55, recomputed. The diff touches only
|
||||
the colour section (moved under "Your playground"), the agent-notes paragraph (own name, notices exempt)
|
||||
and the `requireCitation` row. No co-author line. N1-N3 are met. Louie reports Sage's ruling that 290
|
||||
waits on 217, 218, PRD 1.16 and 285, which covers N5. A rebase brings a drift check and a fresh look.
|
||||
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user