Compare commits
63
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -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,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,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,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,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,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,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,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.
|
||||
@@ -1361,3 +1361,65 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-10-20T18:34:53Z | Filbert (Opus 5.5, 048f3c63) | Q175 SSO design review r2 (DESIGN.md blob 3eb718b5e131 at 1a268249e), static | changes: C1 config.json console key fails old loaders (mosaic-config.mjs:113 rejects unknown keys; key add inside phase 2 window, rollback removes it, loader test), C2 app.js signs out only on opaqueredirect so 401 never shows sign-in; notes on tests 15/7/9a, Q-B 200-HTML classification, scratch Console flags; Q-E keep iss/sub escaped and capped; verdict sent to Sage, no Gitea/queue record
|
||||
2026-10-20T18:40:20Z | Filbert (Opus 5.5, 048f3c63) | Q175 SSO design review r3 (DESIGN.md blob a152fd58e18e at 817e8f51b), static | approve on condition D1 (slot file checks stay out of shared loader, shape-only there) as a line edit; D2 notes: slot root from configDir, scratch dataRoot, name discovery GETs for Mos; C1/C2 and notes verified; Mos C1 sequence confirm still open; no Gitea/queue record
|
||||
2026-10-20T18:41:20Z | Filbert (Opus 5.5, 048f3c63) | Q175 SSO design D1/D2 check (DESIGN.md blob eba861f5d2bb at d55d86bb0), static git diff 817e8f51b..d55d86bb0 | ack: D1 and D2 match; design approved; Mos C1 sequence confirm still open; no Gitea/queue record
|
||||
2026-10-20T18:57:45Z | Webby (Opus 5.5, reviewer seat) | Case 2 design r1 review for Gosalyn (#416, DESIGN.md blob fba567f71dc1 at e997f3b76 / 88222f3f3), static | changes: B1-B12, N1-N9 in agents/webby/work/case2-r1/REVIEW.md; section 6 no-schema claim holds
|
||||
2026-10-20T19:14:58Z | Webby (Opus 5.5, reviewer seat) | row 194 design review for Honker (#1692, DESIGN.md sha256 eec9f011567d at 14a6f5f9d, r1 8f80c278b also read), static | changes: B1-B8, N1-N8 in agents/webby/work/queue-194/REVIEW.md; Q2 answered yes with two conditions
|
||||
2026-10-20T19:20:19Z | Webby (Opus 5.5, reviewer seat) | row 197 design review for Herb (#1695, DESIGN.md sha256 64a429a3 at 85a6eb0a9), static | changes: B1 (temp root under /tmp hides sandboxed run dirs), N1-N5 in agents/webby/work/queue-197/REVIEW.md
|
||||
2026-10-20T19:23:14Z | Webby (Opus 5.5, reviewer seat) | row 198 inventory and design review for Bushroot (#1696, INVENTORY 76b630ce at 004f133bd, DESIGN c2ea3df5 at c8fe23a37), static | inventory approved; design changes B1-B3 (copy lacks board code, re-pin carries --board, POST placement vs serve.mjs:558), N1-N5 in agents/webby/work/queue-198/REVIEW.md
|
||||
2026-10-20T19:24:03Z | Webby (Opus 5.5, reviewer seat) | row 197 design r2 re-review for Herb (#1695, DESIGN.md sha256 732ee0e2 at 6ad25d745), static | approve: B1 fixed, N1-N5 in; one manifest note in agents/webby/work/queue-197/REVIEW-r2.md
|
||||
2026-10-20T19:29:11Z | Webby (Opus 5.5, reviewer seat) | Case 2 design r2 review for Gosalyn (#416, DESIGN.md sha256 bda7a717 at c1913fb07), static | changes: B1 top-level playground has no universe, B2 persona.text storage and tester read, B3 THREAD_DEFINITION in launch-settings must stay assistant, B4 persona block position and fence; N1-N4; notes agents/webby/work/case2-r2/REVIEW.md
|
||||
2026-10-20T19:31:52Z | Webby (Opus 5.5, reviewer seat) | row 194 design r2 re-review for Honker (#1692, DESIGN.md 313c206e and PRD-1.7-DRAFT.md 98f4f49e at c0a35f47a), static | changes: B1 hire floor is a deny list that misses git.merge.protected (merge-gate.json, no credentials), decision.resolve.technical, policy.change, prd.approve, instance.name; make it an allowlist. r1 B1, B3-B8, Q2, N1-N8 fixed; Q4 view: start empty; notes agents/webby/work/queue-194/REVIEW-r2.md
|
||||
2026-10-20T19:33:02Z | Webby (Opus 5.5, reviewer seat) | row 194 design r3 review for Honker (#1692, DESIGN.md 885fce0d and PRD-1.7-DRAFT.md 04c32917 at d0afda024), static | changes: r2 B1 stands except instance.name (allowlist); B2 fire delist and claim revoke can't share a transaction, name order and recovery; N1 REQ-LAND-2 relies on Gitea refusal, N2 recheck by start time; notes agents/webby/work/queue-194/REVIEW-r3.md
|
||||
2026-10-20T19:36:19Z | Webby (Opus 5.5, reviewer seat) | row 198 design rev 3 re-review for Bushroot (#1696, DESIGN.md 51343813 at 7e2ead7f3), static | changes: rev 1 B1-B3 and N1-N5 fixed, sage-1372 rulings read right; new B1 in-process board runs spawnSync tmux (5 s per seat) and agent-send (15 s) in the Console event loop, make createBoard async with a stall test and mutant; N1 decision 8 fallback sentences, N2 --board support check must not match a refusal; notes agents/webby/work/queue-198/REVIEW-r3.md
|
||||
2026-10-20T19:37:29Z | Webby (Opus 5.5, reviewer seat) | row 197 manifest review for Herb (#1695, MANIFEST.md 16768433 at 84f21923c, TIMING.md 148a1416 at fa820fed6), static | changes: B1 drop dead Board :9 replacements (port below unprivileged floor 1024, not shared by lanes, 20 files, collides with row 198), add a :9 listener preflight refusal; N1 row 198 overlap for Sage; notes agents/webby/work/queue-197/REVIEW-manifest.md
|
||||
2026-10-20T19:38:46Z | Webby (Opus 5.5, reviewer seat) | row 194 design r4 re-review for Honker (#1692, DESIGN.md 824ef884 and PRD-1.7-DRAFT.md 379cc318 at 7d0788236), static | approve: HIRE_ALLOWED allowlist checked against role files, fire order A-B-C repeatable; N1 launch should refuse a listed instance mid-fire, N2 hire-time check should require role version 2; notes agents/webby/work/queue-194/REVIEW-r4.md
|
||||
2026-10-20T19:39:47Z | Webby (Opus 5.5, reviewer seat) | row 198 design rev 4 re-review for Bushroot (#1696, DESIGN.md 5ef6436f at 9af75dc72), static | changes: B1 and N1 fixed, --board parse check right; new B1 parses_flag swap at stack-release.sh:670 breaks the tester-port fixtures (stack-release.test.mjs:265, T37), keep it to --board; N1 index coalescing can return a scan from before markSeen's write; notes agents/webby/work/queue-198/REVIEW-r4.md
|
||||
2026-10-20T19:40:29Z | Webby (Opus 5.5, reviewer seat) | row 197 manifest r2 re-review for Herb (#1695, MANIFEST.md 2a1a3ad0 at bdc9cd56a), static | approve: literal Board :9 URLs kept, leases only at the close-rebind probes (preview.test:208, serve.test:100), T1 :9 preflight; P3 row 198 order per sage-1384; notes agents/webby/work/queue-197/REVIEW-manifest-r2.md
|
||||
2026-10-20T19:43:52Z | Webby (Opus 5.5, reviewer seat) | Case 2 design r3 for Gosalyn (#416, DESIGN.md sha256 f368c27d at 2958acb81), static | CHANGES B1 (routes.set lets the tester change a creative instance's route vars), N1-N3; notes agents/webby/work/case2-r3/REVIEW.md
|
||||
2026-10-20T19:44:59Z | Webby (Opus 5.5, reviewer seat) | row 198 design rev 5 for Bushroot (#1696, DESIGN.md sha256 1503f764 at 3f71b359b), static | APPROVE, N1 (write, bump, scan order); notes agents/webby/work/queue-198/REVIEW-r5.md
|
||||
2026-10-20T19:49:56Z | Webby (Opus 5.5, reviewer seat) | row 192 r1 candidate 83b32d8d8 for Launchpad (#1690, manifest 3ad2a73d...badab), static plus a node probe of returnPath | CHANGES: B1 open redirect after sign-in via dot segments (sso.mjs:200-206, :284); N1-N5; notes agents/webby/work/queue-192/REVIEW-r1.md
|
||||
2026-10-20T19:52:33Z | Webby (Opus 5.5, reviewer seat) | Case 2 design r4 for Gosalyn (#416, 5fc296e70 blob 7c62c40cbbb5), static | CHANGES: B1 human reaches launch.models via limits.set (DESIGN.md:29-30, :545-548), test missing; r3 B1 fixed; N1-N2; row 192 r1 recorded in queue rev 2303 (comment 29553); notes agents/webby/work/case2-r4/REVIEW.md
|
||||
2026-10-20T19:54:11Z | Webby (Opus 5.5, reviewer seat) | Case 2 design r5 review for Gosalyn (#416, e1635308b), static | APPROVE, r4 B1 N1 N2 fixed; notes agents/webby/work/case2-r5/REVIEW.md
|
||||
2026-10-20T19:58:32Z | Webby (Opus 5.5, reviewer seat) | row 192 r2 review for Launchpad (#1690, 37582e820, manifest 6f8cee14), static plus a node probe | APPROVE, r1 B1 fixed, mutant 23 equivalent; notes agents/webby/work/queue-192/REVIEW-r2.md
|
||||
2026-10-20T20:00:07Z | Webby (Opus 5.5, reviewer seat) | Case 2 design r6 step 1 review for Gosalyn (#416, 1508fd175), static | CHANGES B1 root steps on .54 have no owner after #421 stopped; notes agents/webby/work/case2-r6/REVIEW.md
|
||||
2026-10-20T20:03:11Z | Webby (Opus 5.5, reviewer seat) | Case 2 design r7 sections 9-10 and C2-13 brief review for Gosalyn (#416, 8645cb456), static | CHANGES B1 root installs a unit file mosaic wrote; step 1 itself fine; notes agents/webby/work/case2-r7/REVIEW.md
|
||||
2026-10-20T20:04:59Z | Webby (Opus 5.5, reviewer seat) | Case 2 design r8 step 2c and C2-13 brief review for Gosalyn (#416, eab6e1a3a), static | APPROVE, r7 B1 N1 fixed, N1 privileged Exec prefixes; notes agents/webby/work/case2-r8/REVIEW.md
|
||||
2026-10-20T20:14:58Z | Webby (Opus 5.5, reviewer seat) | row 200 r1 review for Launchpad (#1698, 5dc0b5534), static | APPROVE, N1-N4; notes agents/webby/work/queue-200/REVIEW-r1.md
|
||||
2026-10-20T20:24:21Z | Webby (Opus 5.5, reviewer seat) | row 197 implementation packet review for Herb (BUILD.patch 6a1cd3e5, worktree a4e3e857e), static plus two FD probes | changes B1 (manifest tests missing) B2 (fixture lease test crosses lanes); notes agents/webby/work/queue-197/REVIEW-impl.md
|
||||
2026-10-20T20:31:25Z | Webby (Opus 5.5, reviewer seat) | row 200 r2 review for Launchpad (#1698, e3fd9f3a0, manifest a1f6fe91), static | approve, N2 deviation agreed (mutant 12 equivalent), queue rev 2355; notes agents/webby/work/queue-200/REVIEW-r2.md
|
||||
2026-10-20T20:45:28Z | Webby (Opus 5.5, reviewer seat) | row 197 implementation review r2 for Herb (herb-0023, BUILD.patch 0b3871bd, SOURCE.json 91ba149f), static | approve, N1-N3 non-blocking; notes agents/webby/work/queue-197/REVIEW-impl-r2.md
|
||||
2026-10-20T20:49Z | Webby (Opus 5.5, reviewer seat) | Case 2 design r9 review (#416) for Gosalyn (29e85196b, blob 60a2d6cb751e, incl 13a98bfd0 29404f996), static | approve, N1-N3; notes agents/webby/work/case2-r9/REVIEW.md
|
||||
2026-10-20T20:50Z | Webby (Opus 5.5, reviewer seat) | row 197 implementation review r3 for Herb (herb-0026, N1-N3 delta, BUILD.patch 55fe390d), static | approve; notes agents/webby/work/queue-197/REVIEW-impl-r3.md
|
||||
2026-10-20T20:55Z | Webby (Opus 5.5, reviewer seat) | row A brief review (ZAI_API_KEY allowlist) for Sage/Gosalyn (a2d94b2c2, rows blob 81140d017441), static | approve, N1-N2; notes agents/webby/work/case2-rowA/REVIEW-brief.md
|
||||
2026-10-20T21:01Z | Webby (Opus 5.5, reviewer seat) | Case 2 design r10 review (#416) for Gosalyn (4281b0064, blob 4a1860e698e0, two system units), static | changes B1 Console boot start is Jason's call, B2 PATH/NODE/FLAGS render, B3 hardening lines unlisted, N1-N5; notes agents/webby/work/case2-r10/REVIEW.md
|
||||
2026-10-20T21:15:35Z | Webby (Opus 5.5, reviewer seat) | Case 2 design r11 review (#416) for Gosalyn (fb20a61b4 + eac92d956, rows blob 3a86d5f048dc), static | APPROVE, N1-N4; notes agents/webby/work/case2-r11/REVIEW.md
|
||||
2026-10-20T21:19:21Z | Webby (Opus 5.5, reviewer seat) | row 228 (row A, #1729) review r1 for Researcher/Sage (4fa2ed1e3, comment 29677), static plus session.test.mjs 13/13 | APPROVE, N1 doc-only; notes agents/webby/work/queue-228/REVIEW-r1.md
|
||||
2026-10-20T21:47Z | Webby (Opus 5.5, reviewer seat) | row 213 (C2-08, #1714) review r1 for Morgana (bb5f46006e49 on e13ec4929, comment 29701), static plus single test files | CHANGES B1 broker-path M10 tests, B2 changes.jsonl mode; N1-N5; notes agents/webby/work/queue-213/REVIEW-r1.md
|
||||
2026-10-20T21:48Z | Webby (Opus 5.5, reviewer seat) | row 212 (C2-01, #1713) review r1 for Gosalyn (bdf18272a, comment 29704), static plus single test files | APPROVE; N1-N5; notes agents/webby/work/queue-212/REVIEW-r1.md
|
||||
2026-10-20T21:53Z | Webby (Opus 5.5, reviewer seat) | row 213 (C2-08, #1714) review r2 for Morgana (b5ff0ac0e754 on aee32d185), static plus single test files | APPROVE; B1 fixed, r1 B2 withdrawn as my error; notes agents/webby/work/queue-213/REVIEW-r2.md
|
||||
2026-10-20T23:29Z | Webby (Opus 5.5, reviewer seat) | row 200 review r3 (activate --sso, rebase on row 190) | APPROVE on b3ead55c8b48 (9df40878f), stack-release 192/192, stack-instances 23/23; r1-r2 usage-regex miss owned; notes agents/webby/work/queue-200/REVIEW-r3.md
|
||||
2026-10-20T23:50Z | Webby (Opus 5.5, reviewer seat) | row 230 review r1 (C2-13a, gosalyn/c2-13a-on-190 2fa7b0f4c, digest 8e9d1ef0c9c8) | changes B1 (7 precheck refusals and missing-claude case untested), N1-N4, six deviations accepted; notes agents/webby/work/queue-230/REVIEW-r1.md
|
||||
2026-10-20T23:58Z | Webby (Opus 5.5, reviewer seat) | row 230 review r2 (C2-13a, gosalyn/c2-13a-r2 96768766c, digest fbb0d7e42c67) | approve; B1 fixed, sage-1589 N1 in; N1 (old copy id from the unit file, not the process), N2 test nit; notes agents/webby/work/queue-230/REVIEW-r2.md
|
||||
2026-10-21T01:31Z | Webby (Opus 5.5, reviewer seat) | row 238 review r1 (#1744, launchpad/row238 bec7e499e, digest 7093c10e43be) | approve; N1 hint omits BUSINESS (pre-existing); notes agents/webby/work/queue-238/REVIEW-r1.md
|
||||
2026-10-21T04:56Z | Webby (Opus 5.5, reviewer seat) | row 212 review r2 (merge scope, gosalyn/c2-01-r2 d3c7bbf83) | approve; notes agents/webby/work/queue-212/REVIEW-r2.md
|
||||
2026-10-21T05:02Z | Webby (Opus 5.5, reviewer seat) | row 213 (C2-08) review r3, rebase scope, on manifest 887c32d6 (morgana/row213-r3 2d2ad2760) | approve, N1 creative credentials follow-up; notes agents/webby/work/queue-213/REVIEW-r3.md
|
||||
2026-10-21T05:06Z | Webby (Opus 5.5, reviewer seat) | row 228 (#1729) review r2, README scope, on manifest ab40b92f (researcher/row228-r2 5f2aad593) | approve, N1 doc-only; notes agents/webby/work/queue-228/REVIEW-r2.md
|
||||
2026-10-21T06:50Z | Webby (Opus 5.5, reviewer seat) | row 238 (#1744) review r2, merge scope, on manifest 36b0adfa (launchpad/row238-r3 a33597c33) and row 228 (#1729) review r3, README scope, on manifest 82cf98c6 (researcher/row228-r3 cf18c42b9) | approve both; notes agents/webby/work/queue-238/REVIEW-r2.md, agents/webby/work/queue-228/REVIEW-r3.md
|
||||
2026-10-21T07:01Z | Webby (Opus 5.5, reviewer seat) | row 238 (#1744) review r3, TOOLS.md merge scope, on manifest 1bf831f3 (launchpad/row238-r4 4c90099ea) | approve; notes agents/webby/work/queue-238/REVIEW-r3.md
|
||||
2026-10-21T07:19Z | Webby (Opus 5.5, reviewer seat) | row 197 queue round 2 merge review (304c67753, manifest 8119a608, comment 29961) | APPROVE; notes agents/webby/work/queue-197/REVIEW-merge-r2.md
|
||||
2026-10-21T07:43Z | Webby (Opus 5.5, reviewer seat) | row 212 round 3 merge review (a2c72ac95 on c771a36f4, comment 29979) | APPROVE; notes agents/webby/work/queue-212/REVIEW-r3.md
|
||||
2026-10-21T08:13Z | Webby (Opus 5.5, reviewer seat) | row 197 queue round 3 (r5) merge review (9aa6d52d9 on e7078e6a8, manifest 69fad4e7, comment 29981) | APPROVE; notes agents/webby/work/queue-197/REVIEW-merge-r3.md
|
||||
2026-10-21T08:22Z | Webby (Opus 5.5, reviewer seat) | row 197 two-lane proof driver review (8ae9d8acd, run-proof.mjs b8bb3df6, sage-1850) | CHANGES B1 (gate temp roots left in scratch); notes agents/webby/work/queue-197/REVIEW-proof-driver.md
|
||||
2026-10-21T08:31Z | Webby (Opus 5.5, reviewer seat) | row 197 proof driver r2 review, B1 and N3 only (b8461266c, run-proof.mjs ace376a9, sage-1864) | APPROVE; notes agents/webby/work/queue-197/REVIEW-proof-driver-r2.md
|
||||
2026-10-21T09:29Z | Webby (Opus 5.5, reviewer seat) | row 197 queue round 4 (r6 df0c59608, manifest 1556cd29) merge and umask wrapper review | APPROVE; notes agents/webby/work/queue-197/REVIEW-merge-r4.md
|
||||
2026-10-21T09:32Z | Webby (Opus 5.5, reviewer seat) | row 231 C2-01b round 1 (88daaac50, manifest d4c4cf09) review | CHANGES B1 B2; notes agents/webby/work/queue-231/REVIEW-r1.md
|
||||
2026-10-21T09:37Z | Webby (Opus 5.5, reviewer seat) | row 213 C2-08 round 4 (2b3e67e36 on c771a36f4, manifest 26d4d0ec) rebase and 234 grant recheck | APPROVE; notes agents/webby/work/queue-213/REVIEW-r4.md
|
||||
2026-10-21T09:52Z | Webby (Opus 5.5, reviewer seat) | row 231 C2-01b round 2 (f37a65857, manifest 312c16fc) review | APPROVE; notes agents/webby/work/queue-231/REVIEW-r2.md
|
||||
2026-10-21T10:37Z | Webby (Opus 5.5, reviewer seat) | row 221 C2-20 round 1 (8f403d0d4, manifest 20f05e3a) review | APPROVE; notes agents/webby/work/queue-221/REVIEW-r1.md
|
||||
2026-10-21T11:29Z | Webby (Opus 5.5, reviewer seat) | row 197 queue round 5 r7 (9c3166915, manifest 4e54024a) review | APPROVE; notes agents/webby/work/queue-197/REVIEW-merge-r5.md
|
||||
2026-10-21T11:53Z | Webby (Opus 5.5, reviewer seat) | row 218 (#1719) C2-12 round 1 review, doc only, merge scope | APPROVE fc0d9c26f manifest fb1bbc5d, N1-N4 non-blocking; notes agents/webby/work/queue-218/REVIEW-r1.md
|
||||
2026-10-21T11:55Z | Webby (Opus 5.5, reviewer seat) | row 247 (#1749) C2-20b round 1 review, merge scope | APPROVE 6d43618a3 manifest 6d3f279f, N1 non-blocking; notes agents/webby/work/queue-247/REVIEW-r1.md
|
||||
2026-10-21T12:00Z | Webby (Opus 5.5, reviewer seat) | row 218 (#1719) C2-12 round 2 review, doc only | APPROVE 5fcc2ec90 manifest c0d3709d, N1-N4 fixed, N5 non-blocking; notes agents/webby/work/queue-218/REVIEW-r2.md
|
||||
2026-10-21T12:04Z | Webby (Opus 5.5, reviewer seat) | row 218 (#1719) C2-12 round 3 review, doc only | APPROVE 89ba79d94 manifest 1a38baf5, N5 fixed, no new notes; notes agents/webby/work/queue-218/REVIEW-r3.md
|
||||
2026-10-21T12:17Z | Webby (Opus 5.5, reviewer seat) | row 197 queue round 6 (Herb r8 c45ddeb6b, manifest 80dc1468), merge scope | APPROVE, N1 doc; notes agents/webby/work/queue-197/REVIEW-q6r8.md
|
||||
2026-10-21T13:15Z | Webby (Opus 5.5, reviewer seat) | row 248 round 1 (Sage 4d99c178b, grant-executor held stubs) | APPROVE, N1 sleep 30 residual; notes agents/webby/work/queue-248/REVIEW-r1.md
|
||||
2026-10-21T15:20Z | Webby (Opus 5.5, reviewer seat) | row 197 proof driver a5b636750 (Herb, proof 4 watcher change), herb-0090 | APPROVE, N1 probe cleanup retry, N2 pin is Sage's; notes agents/webby/work/queue-197/REVIEW-proof-driver-a5b636750.md
|
||||
|
||||
Reference in New Issue
Block a user