Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd5720662e |
No files matched your search
@@ -5585,3 +5585,7 @@ claim. Receipts and limitations in agents/lena/work/queue-296/BUILD.md and run-r
|
||||
2026-10-24T23:37:46Z | Sage | Row 329 (#1827, P1 pinned model proxy, Magica) LANDED 61ebf2c34, pushed 23:31:27Z 10-24. Before: refactor at 669cdf42c. After: Magica's 4 commits (94cde633e, c65fb34ee, d1f6f2b19, 61ebf2c34) cherry-picked onto 669cdf42c, diff vs the gated tree limited to drift paths, range-diff in scratch. Gate: one unchanged full landing gate in /mnt/storage/scratch/sage-r329l/wt (gate.sh 8b4978887 c8a9bcf94), started 23:08:12Z at load 14.35, GATE GREEN 23:29:14Z at load 8.67, no rerun, no test or timeout edit (mos-1634). Host note: two Codex workers cleaned T3 worktrees from about 23:14Z (mos-1639, Vikunja 632). Approvals: Dewey r2 c31643 rev 4047, Boyd r2 c31642 rev 4048. #1827 comment 31650, closed. Vikunja 237 comment 1109 (stays open for P2-P7); C4 P10 red filed as Vikunja 631. Queue: note rev 4063, done rev 4064, committed b24d917ec. DEFERRED: N2 and R2-N1 bound to row 331, N1 bound to P7. Overlap: row 284 (packages/bus/src/broker.mjs) and row 157 (scripts/mosaic-config.mjs) rebase onto 61ebf2c34 before their gates.
|
||||
2026-10-25T00:15:48Z | Sage | Row 284 (#1789, C2-05c library.get scope, Della) LANDED ce0b6ed41, pushed 00:09:52Z 10-25. Before: refactor at 9e2689dff (row 329 landing docs). After: fast-forward to Della's 7 rebased commits 78b932feb..ce0b6ed41; 8 paths, manifest-r4 de505cfd matches the landed tree. Gate: one unchanged full landing gate in /mnt/storage/scratch/sage-r284l/wt (the 329 gate.sh with D and the compose project renamed), started 23:51:19Z at load 1.62, GATE GREEN 00:09:04Z at load 5.20, no rerun, no test or timeout edit. Counts: bus 1020/0, business 283/0, cli 999 pass 1 allowed skip (FED R7), harness 177/0, library 156/0, seat 218/0, webui 458/0, every scripts/test-*.sh rc 0; queue 34 and task 87/93 skips are the standing ones. Della's whole-file runs on ce0b6ed41: library-lookups 12/0, library-tools 5/0, query 34/0. Approval: Webby round 2 delta c31654 rev 4070 (round 1 rev 4062). Overlap sweep: rows 13, 157, 309, gyro/row310 and fethry/row332-p1 clean. #1789 comment 31655, closed. No Vikunja task for the row. Queue: note rev 4071, done rev 4072, committed 7c8dc2033. DEFERRED: Webby N1 (whole-tree listing per get) bound to the next library read-path row; N2 needs nothing; N3 is this gate.
|
||||
2026-10-25T00:18:35Z | Sage | Follow-up tasks for mos-1647 item 3 (Q109): Vikunja 633 (P7 cutover, CLIProxyAPI 8.0.42 startup Grok-npm and antigravity version refreshes, from row 332 isolated runs) and 634 (row 284 Webby N1, library get lists the whole tree). DEFERRED gains the 633 entry and the 634 number for row 284 N1. Filing script and receipt in agents/sage/work/vikunja-setup/.
|
||||
|
||||
2026-10-25 — Rockerduck row334 (#1832), before: Case 2 renders bus/Console only. Plan: reuse P1 proxy template/runner under --proxy, enforce three-unit re-install checks and proxy→bus→Console starts, cover refusals and both named mutants. Tests held for Sage CLEAR; design in agents/rockerduck/work/queue-334/BUILD.md.
|
||||
|
||||
2026-10-25 — Rockerduck row334 (#1832), RED evidence: whole case2-unit.test.mjs against baseline has 48 pass/33 expected new fail; whole case2-install.test.mjs has 27 pass/1 expected new fail. All 74 older tests pass. Implementations restored. Sage sage-4390 authorizes seat-branch candidate commit/push before GREEN; GREEN/mutants held until separate CLEAR after row331. Per-failure evidence: agents/rockerduck/work/queue-334/red-results.txt.
|
||||
@@ -1,628 +0,0 @@
|
||||
# Row 198 r5: assertion diff
|
||||
|
||||
Status: uncommitted working-tree review file. Nothing here has been committed or pushed, and no test has been run (CLEAR pending).
|
||||
|
||||
- Base: `78b69e1e6` (refactor). Rebase paused on pick 1 (`4288b7b16`). Commit 1 is staged; later edits are in the working tree.
|
||||
- Groups: A = tests that existed on refactor before row 198 (Mos reads these, mos-1272). B = tests row 198 adds (Sage rules).
|
||||
- Hunks: 24 group A, 3 group B. Changed assert lines: 230 removed, 61 added (hunk sums; the file-level diff includes non-assert lines).
|
||||
- Retired test names: 36 (list below).
|
||||
|
||||
Labels are from line-range attribution. A removed block can span several old tests, so the enclosing-test label is the first match only; the reason and the diff text are authoritative.
|
||||
|
||||
## Group A (existed on refactor before row 198)
|
||||
|
||||
### A1: control-board/tests/discord.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): server rescans connector discovery and refuses HTTP reply without transport
|
||||
|
||||
Reason: Port: the HTTP /api/board fetch becomes board.index(); the 409 is the status board.reply returns in-process. Kept: state, connector.alive, registered null, forged task not used.
|
||||
|
||||
```diff
|
||||
- assert.equal(board.sessions[0].state, 'idle'); assert.equal(board.sessions[0].connector.alive, true);
|
||||
- assert.equal(board.sessions[0].registered, null); assert.notEqual(board.sessions[0].task, 'forged');
|
||||
- assert.equal(response.status, 409);
|
||||
+ assert.equal(payload.sessions[0].state, 'idle'); assert.equal(payload.sessions[0].connector.alive, true);
|
||||
+ assert.equal(payload.sessions[0].registered, null); assert.notEqual(payload.sessions[0].task, 'forged');
|
||||
+ assert.equal(status, 409);
|
||||
```
|
||||
|
||||
### A2: control-board/tests/discord.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): server rescans connector discovery and refuses HTTP reply without transport
|
||||
|
||||
Reason: Port: the HTTP /api/board fetch becomes board.index(); zero-sessions assert unchanged.
|
||||
|
||||
```diff
|
||||
- assert.equal((await (await fetch(base + '/api/board')).json()).sessions.length, 0);
|
||||
+ assert.equal((await board.index()).sessions.length, 0);
|
||||
```
|
||||
|
||||
### A4: control-board/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): isLoopbackHost: rejects non-loopback hosts
|
||||
|
||||
Reason: Retired writer HTTP asserts: page content-type and cache-control, healthz body, 404/405 and the 500 envelope. The writer opens no listener now. index.json and per-agent write asserts are not re-asserted here (gap).
|
||||
|
||||
```diff
|
||||
- assert.ok(caught instanceof ConfigError, "expected a ConfigError");
|
||||
- assert.equal(res.status, 200, path);
|
||||
- assert.equal(res.headers.get("content-type"), "text/html; charset=utf-8", path);
|
||||
- assert.equal(res.headers.get("cache-control"), "no-store", path);
|
||||
- assert.equal(await res.text(), page, path);
|
||||
- assert.equal(health.status, 200);
|
||||
- assert.deepEqual(await health.json(), { ok: true });
|
||||
- assert.equal(first.status, 200);
|
||||
- assert.equal(first.headers.get("content-type"), "application/json");
|
||||
- assert.equal(first.headers.get("cache-control"), "no-store");
|
||||
- assert.equal(firstBody.sessions[0].state, "idle");
|
||||
- assert.ok(!firstBody.waitingOnYou.includes("proj/agent1"));
|
||||
- assert.ok(existsSync(join(boardDir, "index.json")), "index.json must be written by the scan");
|
||||
- assert.ok(existsSync(join(boardDir, "sessions", "proj", "agent1.json")), "per-agent file must be written by the scan");
|
||||
- assert.equal(second.status, 200);
|
||||
- assert.equal(secondBody.sessions[0].state, "working");
|
||||
- assert.equal(missing.status, 404);
|
||||
- assert.equal(posted.status, 405);
|
||||
- assert.equal(res.status, 200);
|
||||
- assert.equal(body.sessions[0].taskSource, "registration");
|
||||
- assert.equal(body.sessions[0].task, "registered task");
|
||||
- assert.deepEqual(body.registered, ["proj/agent1"]);
|
||||
- assert.deepEqual(body.registrationErrors, []);
|
||||
- assert.equal(res.status, 500);
|
||||
- assert.equal(res.headers.get("content-type"), "application/json");
|
||||
- assert.equal(typeof body.error, "string");
|
||||
- assert.ok(body.error.length > 0);
|
||||
```
|
||||
|
||||
### A5: control-board/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): CLI: serve refuses a non-loopback host with exit 2 and a refused: message
|
||||
|
||||
Reason: Retired with serve: CLI refusal now asserted by cli.mjs:14 via board.test.mjs (group B hunk 0).
|
||||
|
||||
```diff
|
||||
- assert.equal(r.status, 2);
|
||||
- assert.match(r.stderr, /^refused:/);
|
||||
```
|
||||
|
||||
### A6: control-board/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): CLI: live serve prints its URL and answers /healthz
|
||||
|
||||
Reason: Retired page.html source checks (esc, seen POST, healthz) and HTTP seen round-trip. page.html is deleted; markSeen order is in board.test.mjs. The esc() asserts have no in-process target (open gap).
|
||||
|
||||
```diff
|
||||
- assert.equal(res.status, 200);
|
||||
- assert.deepEqual(await res.json(), { ok: true });
|
||||
- assert.ok(m, "esc() must exist in page.html");
|
||||
- assert.equal(esc('<script>alert("x")</script>&\''), "<script>alert("x")</script>&'");
|
||||
- assert.equal(esc(null), "");
|
||||
- assert.equal(esc(undefined), "");
|
||||
- assert.equal(esc(42), "42");
|
||||
- assert.deepEqual(rawJoins, [], `API values concatenated into HTML without esc(): ${rawJoins.join(" | ")}`);
|
||||
- assert.ok(escCalls >= 15, `expected many esc() calls, saw ${escCalls}`);
|
||||
- assert.equal(postRes.status, 200);
|
||||
- assert.ok(postBody.seen.includes("proj/agent1"));
|
||||
- assert.ok(!postBody.waitingOnYou.includes("proj/agent1"));
|
||||
- assert.equal(boardRes.status, 200);
|
||||
- assert.ok(boardBody.seen.includes("proj/agent1"));
|
||||
- assert.ok(!boardBody.waitingOnYou.includes("proj/agent1"));
|
||||
- assert.equal(clearRes.status, 200);
|
||||
- assert.ok(!clearBody.seen.includes("proj/agent1"));
|
||||
- assert.ok(clearBody.waitingOnYou.includes("proj/agent1"));
|
||||
- assert.equal(res.status, 400);
|
||||
- assert.ok(!existsSync(join(boardDir, "seen.json")), "seen.json must not be written on a rejected content-type");
|
||||
- assert.equal(res.status, 400);
|
||||
- assert.ok(!existsSync(join(boardDir, "seen.json")));
|
||||
- if (status !== null) assert.equal(status, 400);
|
||||
- assert.ok(!existsSync(join(boardDir, "seen.json")), "seen.json must not be written for an oversized body");
|
||||
- assert.equal(res.status, 400);
|
||||
- assert.ok(!existsSync(join(boardDir, "seen.json")));
|
||||
- assert.equal(board.status, 405);
|
||||
- assert.equal(put.status, 405);
|
||||
```
|
||||
|
||||
### A7: control-board/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)'
|
||||
|
||||
Reason: Retired page.html source checks only (seenControl, Seen section, Hide seen, sourceTag escapes). No verified Console equivalent in this pass (open gap).
|
||||
|
||||
```diff
|
||||
- assert.ok(m, "seenControl() must exist in page.html");
|
||||
- assert.match(body, /esc\(rec\.project\)/);
|
||||
- assert.match(body, /esc\(rec\.agent\)/);
|
||||
- assert.match(body, /esc\(rec\.lastActivity\)/);
|
||||
- assert.match(html, /headers:\s*\{\s*"content-type":\s*"application\/json"\s*\}/, "the seen POST must send content-type: application/json");
|
||||
- assert.match(html, /<details id="seenDetails">(?![^>]*\sopen)/, "the Seen section must start collapsed");
|
||||
- assert.match(html, /<h2 id="seen-h">Seen<\/h2>/);
|
||||
- assert.ok(m, "renderSeen() must exist in page.html");
|
||||
- assert.match(m[0], /filter\(function \(r\) \{ return r\.seen; \}\)/, "the Seen section lists exactly the rows the API marks seen");
|
||||
- assert.match(m[0], /buildRowPair\(r, true\)/, "seen rows reuse the escaped row builder, project column included");
|
||||
- assert.match(html, /renderWaiting\(lastData\);\n\s*renderSeen\(lastData\);/, "renderAll must render the Seen section on every refresh");
|
||||
- assert.ok(m, "renderProjects() must exist in page.html");
|
||||
- assert.match(body, /if \(!\(project in hideSeenState\)\) hideSeenState\[project\] = true/, "Hide seen defaults to on and the choice survives re-render");
|
||||
- assert.match(body, /class="hide-seen-toggle" data-project="' \+ esc\(project\)/, "the checkbox carries the escaped project name");
|
||||
- assert.match(body, /hideSeen && r\.seen/, "seen rows are filtered when the box is ticked");
|
||||
- assert.match(body, /" seen hidden"/, "the note reports how many seen rows are hidden");
|
||||
- assert.match(html, /closest\("\.hide-offline-toggle, \.hide-seen-toggle"\)/, "one change handler serves both checkboxes");
|
||||
- assert.ok(m, "renderProjects() must exist in page.html");
|
||||
- assert.match(
|
||||
- assert.match(body, /<span>\(' \+ headCount \+ "\)<\/span>/, "the header uses headCount, not the raw group length");
|
||||
- assert.doesNotMatch(body, /<span>\(' \+ group\.length/, "the raw group length must no longer be rendered in the header");
|
||||
- assert.ok(m, "buildRowPair() must exist in page.html");
|
||||
- assert.match(body, /rec\.task\s*\?[\s\S]*esc\(rec\.task\)[\s\S]*:\s*'<span class="unknown">unknown<\/span>'/, "task is escaped, or reads unknown when null");
|
||||
- assert.match(body, /rec\.activeProject\s*\?[\s\S]*esc\(rec\.activeProject\)[\s\S]*unknown/, "active project is escaped, or reads unknown when null");
|
||||
- assert.match(body, /<dt>Task<\/dt>/, "detail lists the task");
|
||||
- assert.match(body, /<dt>Active project<\/dt>/, "detail lists the active project");
|
||||
- assert.match(body, /<dt>Workspace<\/dt><dd>" \+ \(rec\.workspace \? esc\(rec\.workspace\) : "unknown"\)/, "detail lists the workspace, escaped, or unknown");
|
||||
- assert.match(body, /var span = showProject \? 7 : 6/, "the detail row spans the two new columns");
|
||||
- assert.equal(heads.length, 3, "all three tables carry the two new headers");
|
||||
- assert.match(html, /<td colspan="6" class="empty">No agents\.<\/td>/, "the empty project row spans every column");
|
||||
- assert.ok(rowPair, "buildRowPair() must exist in page.html");
|
||||
- assert.match(rowPair[0], /sourceTag\(rec\.taskSource\)/, "the task cell shows its source");
|
||||
- assert.match(rowPair[0], /sourceTag\(rec\.activeProjectSource\)/, "the active project cell shows its source");
|
||||
- assert.match(
|
||||
- assert.match(rowPair[0], /esc\(rec\.agent\) \+ "<\/button>" \+ modelTag\(rec\)/, "the agent cell shows the model under the name");
|
||||
- assert.match(rowPair[0], /<dt>Model<\/dt><dd>" \+ modelText\(rec\) \+ "<\/dd>/, "the detail list has a Model row");
|
||||
- assert.ok(line, `${name}() must exist in page.html`);
|
||||
- assert.match(line, /esc\(rec\.model\)/, `${name}() escapes the model`);
|
||||
- if (name === "modelText") assert.match(line, /esc\(rec\.provider\)/, "modelText() escapes the provider");
|
||||
- assert.ok(sourceLabelMatch, "SOURCE_LABEL must exist in page.html");
|
||||
- assert.equal(sourceLabel.registration, "registered", 'SOURCE_LABEL must map "registration" to "registered"');
|
||||
- assert.ok(sourceTagLine, "sourceTag() must exist in page.html");
|
||||
- assert.match(sourceTagLine, /esc\(source\)/, "sourceTag() escapes the raw source value (used in the title)");
|
||||
- assert.match(sourceTagLine, /esc\(sourceLabel\(source\)\)/, "sourceTag() escapes the label text it displays");
|
||||
- assert.ok(fromSourceLine, "fromSource() must exist in page.html");
|
||||
- assert.match(fromSourceLine, /esc\(sourceLabel\(source\)\)/, "fromSource() escapes the label text it displays");
|
||||
- assert.ok(registeredTextFn, "registeredText() must exist in page.html");
|
||||
- assert.match(line, /esc\(/, `every dynamic value read off reg must be escaped: ${line.trim()}`);
|
||||
```
|
||||
|
||||
### A8: control-board/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): every refusal code the reader can raise has an HTTP status
|
||||
|
||||
Reason: Moved in, not weakened: REFUSAL_STATUS table and code-count asserts from the retired conversation test (hunk 15), plus the DEFAULT_AGENT_SEND path asserts (hunk 12).
|
||||
|
||||
```diff
|
||||
+ assert.equal(DEFAULT_AGENT_SEND, resolve(pkgRoot, "..", "..", "tools", "tmux", "agent-send.sh"));
|
||||
+ assert.ok(existsSync(DEFAULT_AGENT_SEND));
|
||||
+ assert.ok(statSync(DEFAULT_AGENT_SEND).mode & 0o111);
|
||||
+ assert.ok(codes.size >= 15, [...codes].join(" "));
|
||||
+ assert.deepEqual([...codes].filter((c) => !(c in REFUSAL_STATUS)), []);
|
||||
+ assert.deepEqual(Object.keys(REFUSAL_STATUS).filter((c) => !codes.has(c)), [], "no stale entries");
|
||||
+ assert.deepEqual(REFUSAL_STATUS, {
|
||||
```
|
||||
|
||||
### A9: control-board/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): POST /api/reply: a registration with a tmux socket adds -L <socket>
|
||||
|
||||
Reason: Re-indent only: the same -L socket asserts move into the board.reply closure (identical text after trim).
|
||||
|
||||
```diff
|
||||
- assert.equal(status, 200);
|
||||
- assert.equal(body.socket, "mosaic-fleet");
|
||||
- assert.equal(argv[argv.indexOf("-L") + 1], "mosaic-fleet");
|
||||
+ assert.equal(status, 200);
|
||||
+ assert.equal(body.socket, "mosaic-fleet");
|
||||
+ assert.equal(argv[argv.indexOf("-L") + 1], "mosaic-fleet");
|
||||
```
|
||||
|
||||
### A10: control-board/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): POST /api/reply: refusals before the tool runs: empty or blank or long text 400, unknown row 404, no registration 409, stale registration 409, no tmux session 409, bad JSON 400; the tool is never called
|
||||
|
||||
Reason: Re-indent only: the refusal asserts move into the ported reply test; the tool-not-run check is unchanged.
|
||||
|
||||
```diff
|
||||
- assert.equal(status, c.status, `${JSON.stringify(c.body).slice(0, 60)}: ${JSON.stringify(body)}`);
|
||||
- assert.match(body.error, c.error);
|
||||
- assert.equal(existsSync(f.capture), false, "the tool must not run on a refusal");
|
||||
+ assert.equal(status, c.status, `${JSON.stringify(c.body).slice(0, 60)}: ${JSON.stringify(body)}`);
|
||||
+ assert.match(body.error, c.error);
|
||||
+ assert.equal(existsSync(f.capture), false, "the tool must not run on a refusal");
|
||||
```
|
||||
|
||||
### A11: control-board/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash
|
||||
|
||||
Reason: Re-indent only for the 500 and path asserts. The healthz status line is dropped with the HTTP server.
|
||||
|
||||
```diff
|
||||
- assert.equal(status, 500);
|
||||
- assert.match(body.error, /missing-agent-send\.sh/);
|
||||
- assert.equal(health.status, 200);
|
||||
+ assert.equal(status, 500);
|
||||
+ assert.match(body.error, /missing-agent-send\.sh/);
|
||||
```
|
||||
|
||||
### A12: control-board/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable
|
||||
|
||||
Reason: Moved, not weakened: DEFAULT_AGENT_SEND asserts move to the refusal-table test (hunk 8).
|
||||
|
||||
```diff
|
||||
- assert.equal(DEFAULT_AGENT_SEND, resolve(pkgRoot, "..", "..", "tools", "tmux", "agent-send.sh"));
|
||||
- assert.ok(existsSync(DEFAULT_AGENT_SEND));
|
||||
- assert.ok(statSync(DEFAULT_AGENT_SEND).mode & 0o111);
|
||||
```
|
||||
|
||||
### A13: control-board/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): page.html: the reply box appears only where canReply() holds (live registration with a tmux session), the detail has a Reply row, the submit posts JSON to /api/reply, receipts and drafts survive a refresh, and every receipt value is escaped
|
||||
|
||||
Reason: Retired page.html source checks for the reply box (canReply, replyControl, postReply, receipts). Reply gate behaviour is covered in the Console by browser-edge.test (reply needs a registered seat, line 61; in-flight reply).
|
||||
|
||||
```diff
|
||||
- assert.ok(canReplyFn, "canReply() must exist");
|
||||
- assert.match(canReplyFn[0], /reg\.alive !== false/, "a stale registration gets no box");
|
||||
- assert.match(canReplyFn[0], /reg\.tmux && reg\.tmux\.session/, "a registration without a tmux session gets no box");
|
||||
- assert.ok(replyControlFn, "replyControl() must exist");
|
||||
- assert.match(replyControlFn[0], /if \(!canReply\(rec\)\) return '<span class="unknown">reply needs a registered seat<\/span>'/);
|
||||
- assert.match(replyControlFn[0], /class="reply-form" data-id="' \+ esc\(id\)/);
|
||||
- assert.match(replyControlFn[0], /value="' \+ esc\(replyDrafts\[id\] \|\| ""\)/, "an unsent draft is put back after a refresh");
|
||||
- assert.match(rowPair[0], /<dt>Reply<\/dt><dd>" \+ replyControl\(rec\) \+ "<\/dd>/);
|
||||
- assert.ok(receiptFn, "replyReceipt() must exist");
|
||||
- assert.match(line, /esc\(/, `every receipt value must be escaped: ${line.trim()}`);
|
||||
- assert.match(receiptFn[0], /esc\(r\.stderr \|\| r\.error/, "a failure shows the tool's stderr");
|
||||
- assert.ok(postReplyFn, "postReply() must exist");
|
||||
- assert.match(postReplyFn[0], /fetch\("\/api\/reply", \{\s*method: "POST",\s*headers: \{ "content-type": "application\/json" \}/);
|
||||
- assert.match(postReplyFn[0], /JSON\.stringify\(\{ agent: id, text: text \}\)/);
|
||||
- assert.match(postReplyFn[0], /replyReceipts\[id\] = body/);
|
||||
- assert.ok(lines.some((l) => l.includes('main.addEventListener("submit"')), "the form submits through one delegated handler");
|
||||
- assert.ok(lines.some((l) => l.includes('main.addEventListener("input"')), "drafts are captured as they are typed");
|
||||
- assert.match(html, /function withReplyFocus\(render\)/, "a refresh gives the caret back to the reply box");
|
||||
- assert.doesNotMatch(html, /send-keys/, "the page never talks tmux");
|
||||
+ // Ported from the writer's HTTP test (refactor 78b69e1e6): the same assertions over board.index().
|
||||
```
|
||||
|
||||
### A14: control-board/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): startServer: /api/board carries taskSetBy from a live registration and null for the derived rows
|
||||
|
||||
Reason: Ported: byAgent taskSource, taskSetBy and registrationErrors asserts kept on board.index() (test renamed 'index: carries taskSetBy...'). Retired: page.html tag and detail checks and the HTTP Host/Origin fences (no listener).
|
||||
|
||||
```diff
|
||||
- assert.equal(byAgent.agent1.taskSource, "registration");
|
||||
- assert.equal(byAgent.agent1.taskSetBy, "darkwing");
|
||||
- assert.equal(byAgent.agent2.taskSource, "first-user-message");
|
||||
- assert.equal(byAgent.agent2.taskSetBy, null);
|
||||
- assert.deepEqual(body.registrationErrors, []);
|
||||
- assert.ok(rowPair);
|
||||
- assert.match(rowPair[0], /sourceTag\(rec\.taskSource\) \+ setByTag\(rec\)/, "the set-by tag follows the task's source tag on the row");
|
||||
- assert.match(rowPair[0], /<dt>Task set by<\/dt><dd>" \+ setByText\(rec\) \+ "<\/dd>/, "the detail has a Task set by row");
|
||||
- assert.ok(tagLine);
|
||||
- assert.match(tagLine, /rec\.taskSetBy \?/, "nothing is shown without a value");
|
||||
- assert.equal((tagLine.match(/esc\(rec\.taskSetBy\)/g) || []).length, 2, "the title and the text are both escaped");
|
||||
- assert.match(tagLine, /not verified/);
|
||||
- assert.ok(textLine);
|
||||
- assert.match(textLine, /esc\(rec\.taskSetBy\)/);
|
||||
- assert.match(textLine, /not verified/);
|
||||
- assert.ok(canReplyFn);
|
||||
- assert.equal(canReplyFn[0].includes("taskSetBy"), false);
|
||||
- assert.equal(html.match(/function replyControl\(rec\) \{[\s\S]*?\n \}/)[0].includes("taskSetBy"), false);
|
||||
- assert.equal(board.status, 403, `GET /api/board, ${label}`);
|
||||
- assert.deepEqual(JSON.parse(board.text), { error }, `GET /api/board, ${label}`);
|
||||
- assert.equal(board.headers["access-control-allow-origin"], undefined);
|
||||
- assert.equal(posted.status, 403, `POST /api/reply, ${label}`);
|
||||
- assert.deepEqual(JSON.parse(posted.text), { error }, `POST /api/reply, ${label}`);
|
||||
- assert.equal(posted.headers["access-control-allow-origin"], undefined);
|
||||
- assert.equal((await rawRequest(port, {})).status, 400, "GET /api/board, missing Host");
|
||||
- assert.equal((await rawRequest(port, { method: "POST", path: "/api/reply", headers: { "content-type": "application/json" }, body: reply })).status, 400, "POST /api/reply, missing Host");
|
||||
- assert.equal(existsSync(f.capture), false, "agent-send was never run");
|
||||
- assert.equal(existsSync(join(f.boardDir, "index.json")), false, "no refused request rescanned the board");
|
||||
- assert.equal((await rawRequest(port, { path, headers: { host: `rebind.example:${port}` } })).status, 403, path);
|
||||
- assert.equal((await rawRequest(port, { method: "POST", path: "/api/seen", headers: { host: `rebind.example:${port}`, "content-type": "application/json" }, body: "{}" })).status, 403);
|
||||
- assert.equal((await rawRequest(port, { headers: { host } })).status, 200, host);
|
||||
- assert.equal((await rawRequest(port, { headers: { host, origin: `http://${host}` } })).status, 200, `${host} with its own Origin`);
|
||||
- assert.equal(posted.status, 200);
|
||||
- assert.equal(JSON.parse(posted.text).delivered, true);
|
||||
- assert.equal((await fetch(`http://${own}/api/board`)).status, 200, "fetch without Origin");
|
||||
- assert.equal(r.status, 403, `${path}, ${label}`);
|
||||
- assert.deepEqual(JSON.parse(r.text), { error }, `${path}, ${label}`);
|
||||
- for (const h of Object.keys(r.headers)) assert.ok(!h.startsWith("access-control-"), `${path}, ${label}: ${h}`);
|
||||
- assert.deepEqual(calls, [], "the reader never ran for a refused request");
|
||||
- assert.equal((await rawRequest(port, { path: "/api/conversations", headers: { host: own, origin: `http://${own}` } })).status, 200, "same-origin passes");
|
||||
- assert.deepEqual(calls, ["catalogue"]);
|
||||
+ assert.equal(byAgent.agent1.taskSource, "registration");
|
||||
+ assert.equal(byAgent.agent1.taskSetBy, "darkwing");
|
||||
+ assert.equal(byAgent.agent2.taskSource, "first-user-message");
|
||||
+ assert.equal(byAgent.agent2.taskSetBy, null);
|
||||
+ assert.deepEqual(body.registrationErrors, []);
|
||||
```
|
||||
|
||||
### A15: control-board/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): conversation routes: a reader listing that refuses gets its refusal status and body, not a 200
|
||||
|
||||
Reason: Ported: status and refusal body kept on board.conversation (renamed 'conversation listing...'). Retired: HTTP content-type, nosniff, no-store and no-CORS asserts, now in the Console test (group B hunk 25). Retired: pi and claude catalogue and paging asserts, HTTP-only (gap).
|
||||
|
||||
```diff
|
||||
- assert.equal(res.status, 503);
|
||||
- assert.deepEqual(await res.json(), { error: refusal.message, refusal: { code: refusal.code, reconcile: refusal.reconcile } });
|
||||
- assert.ok(codes.size >= 15, [...codes].join(" "));
|
||||
- assert.deepEqual([...codes].filter((c) => !(c in REFUSAL_STATUS)), []);
|
||||
- assert.deepEqual(Object.keys(REFUSAL_STATUS).filter((c) => !codes.has(c)), [], "no stale entries");
|
||||
- assert.deepEqual(REFUSAL_STATUS, {
|
||||
- assert.equal(res.headers.get("content-type"), "application/json", path);
|
||||
- assert.equal(res.headers.get("x-content-type-options"), "nosniff", path);
|
||||
- assert.equal(res.headers.get("cache-control"), "no-store", path);
|
||||
- assert.equal(res.headers.get("access-control-allow-origin"), null, path);
|
||||
- assert.equal(cat.status, 200);
|
||||
- assert.deepEqual([pi.seat, pi.title, pi.readOnly], ["agent1", "m0", true]);
|
||||
- assert.deepEqual([link.refusal, claude.seat, claude.unsupportedReason], ["unsafe-path", "rocko", "unsupported-harness"]);
|
||||
- assert.equal(first.status, 200);
|
||||
- assert.deepEqual([first.body.page.kind, first.body.page.entries.length, first.body.page.hasMore, first.body.page.branch], ["page", 100, true, "main"]);
|
||||
- assert.equal(second.status, 200);
|
||||
- assert.deepEqual([second.body.page.entries.length, second.body.page.hasMore], [20, false]);
|
||||
- assert.equal(second.body.page.entries.at(-1).content[0].text, "m119");
|
||||
- assert.deepEqual([follow.status, follow.body.page.entries.length], [200, 0]);
|
||||
- assert.equal(branch.status, 200);
|
||||
- assert.equal(r.status, status, path);
|
||||
- assert.deepEqual(r.body.refusal, { code, reconcile }, path);
|
||||
- assert.equal(typeof r.body.error, "string");
|
||||
- assert.equal(r.status, 400, path);
|
||||
- assert.deepEqual(treePrint(f.sessionsDir), before, "the sessions directory is unchanged");
|
||||
- assert.equal(existsSync(join(f.proj, ".pi", "state", "rocko")), false, "the Claude seat's directory was never created");
|
||||
+ assert.equal(out.status, 503);
|
||||
+ assert.deepEqual(out.body, { error: refusal.message, refusal: { code: refusal.code, reconcile: refusal.reconcile } });
|
||||
```
|
||||
|
||||
### A16: webui/tests/browser.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures
|
||||
|
||||
Reason: CHANGED ASSERT: the error text must contain 'in the Console' instead of the board URL, since there is no board URL in-process. Not run; verify on CLEAR.
|
||||
|
||||
```diff
|
||||
- assert.ok(error.includes(f.boardURL)); assert.match(error, /Showing last known data/);
|
||||
+ assert.ok(error.includes('in the Console')); assert.match(error, /Showing last known data/);
|
||||
```
|
||||
|
||||
### A17: webui/tests/preview.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): loopback requests are unchanged in preview mode, and the bus views work with the board down
|
||||
|
||||
Reason: CHANGED ASSERT: a dead board read now returns error 'invalid-response' at 502 (consoleRead, webui/src/serve.mjs:230-234). Status kept; the board echo field is gone.
|
||||
|
||||
```diff
|
||||
- assert.equal(board.status, 502); assert.equal(board.body.board, dead);
|
||||
+ assert.equal(board.status, 502); assert.equal(board.body.error, 'invalid-response');
|
||||
```
|
||||
|
||||
### A18: webui/tests/reads.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): settings: release, board, loopback address, notifier binding name only
|
||||
|
||||
Reason: CHANGED ASSERT: settings.board is null, since the board is in-process (webui/src/serve.mjs:874, boardLabel).
|
||||
|
||||
```diff
|
||||
- assert.deepEqual({ ...body, launch: undefined, at: undefined }, { release: '0.0.99', business: 'acme', notifier: { binding: 'jason-dm' }, board: 'http://127.0.0.1:7331', address: `${base}/`, launch: undefined, at: undefined });
|
||||
+ assert.deepEqual({ ...body, launch: undefined, at: undefined }, { release: '0.0.99', business: 'acme', notifier: { binding: 'jason-dm' }, board: null, address: `${base}/`, launch: undefined, at: undefined });
|
||||
```
|
||||
|
||||
### A19: webui/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): loopback host and board origin fail closed
|
||||
|
||||
Reason: Retired: boardURL() is removed with the HTTP board address, so its asserts go with it.
|
||||
|
||||
```diff
|
||||
- for (const u of ['https://127.0.0.1', 'http://example.com', 'http://127.0.0.1/path', 'http://user:[email protected]', 'http://127.0.0.1?x', 'http://127.0.0.1#x']) assert.throws(() => boardURL(u));
|
||||
- assert.equal(boardURL('http://localhost:7331'), 'http://127.0.0.1:7331');
|
||||
- assert.equal(boardURL('http://[::1]:7331'), 'http://[::1]:7331');
|
||||
```
|
||||
|
||||
### A20: webui/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect
|
||||
|
||||
Reason: BEHAVIOUR CHANGE, not a re-expression: the refactor forwarded this request and returned the board's 409. The Console now answers 403 when send is off or the seat is unnamed (webui/src/serve.mjs ~843-846). The oversized-body 400 is also dropped here; no Console test covers the 4096 limit (open gap).
|
||||
|
||||
```diff
|
||||
- assert.equal(res.status, 409); assert.equal(await res.text(), '{"error":"fixture refusal <unsafe>"}');
|
||||
- assert.deepEqual(requests.at(-1), { url: path, method: 'POST', body: raw });
|
||||
- try { assert.equal((await post(base, '/api/reply', raw)).status, 400); } catch (err) { if (raw.length < 4096) throw err; }
|
||||
+ assert.equal(res.status, 403, path); assert.equal((await res.json()).sent, false);
|
||||
```
|
||||
|
||||
### A21: webui/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect
|
||||
|
||||
Reason: Dropped: text/plain 400 (Console body() at serve.mjs:58 refuses non-JSON, mapped to 400 at :191; no test, open gap), the request-count checks (no HTTP hop) and the board-down 502 (board is in-process). Added: 405 for GET /api/reply and OPTIONS /api/board; 'the board never ran a write' check on f.captures.
|
||||
|
||||
```diff
|
||||
- assert.equal((await post(base, '/api/reply', '{}', { 'content-type': 'text/plain' })).status, 400);
|
||||
- assert.equal(requests.length, 2);
|
||||
- assert.equal((await fetch(base + '/api/board')).status, 502);
|
||||
- assert.equal(requests.length, 3);
|
||||
+ assert.equal(f.captures.length, 0, 'the board never ran a write');
|
||||
+ assert.equal((await fetch(base + '/api/reply')).status, 405);
|
||||
+ assert.equal((await fetch(base + '/api/board', { method: 'OPTIONS' })).status, 405);
|
||||
```
|
||||
|
||||
### A22: webui/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect
|
||||
|
||||
Reason: CHANGED ASSERT: the exact 404 body and refusal object become a want.status and want.body table. The table must match the same body (check in the file, not run).
|
||||
|
||||
```diff
|
||||
- assert.equal(res.status, 404, path);
|
||||
- assert.equal(await res.text(), '{"error":"fixture unknown <x>","refusal":{"code":"unknown-branch","reconcile":true}}');
|
||||
- assert.deepEqual(requests.at(-1), { url: path, method: 'GET', body: '' });
|
||||
+ assert.equal(res.status, want.status, path);
|
||||
+ assert.deepEqual(await res.json(), want.body, path);
|
||||
```
|
||||
|
||||
### A23: webui/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect
|
||||
|
||||
Reason: WEAKENED: the exact 'refused before the board' check (request count unchanged, last url /api/board) becomes a status >= 400 check. This drops the proof the board was not reached. Flagged for Mos.
|
||||
|
||||
```diff
|
||||
- assert.equal((await post(base, '/api/conversation?id=x', '{}')).status, 405);
|
||||
- assert.equal((await post(base, '/api/conversations', '{}')).status, 405);
|
||||
- assert.equal((await fetch(base + '/api/conversation?id=x', { headers: { origin: 'https://evil.example' } })).status, 403);
|
||||
- assert.equal(requests.length, before, 'refused before the board');
|
||||
- assert.equal(requests.at(-1).url, '/api/board');
|
||||
+ assert.ok((await fetch(base + '/api/board')).status >= 400);
|
||||
```
|
||||
|
||||
### A24: webui/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): unreachable board reports URL; CLI rejects unsupported options
|
||||
|
||||
Reason: CHANGED ASSERT: the 502 with the board URL becomes 503 not-configured for /api/board and /api/conversations. No board URL appears in any response now.
|
||||
|
||||
```diff
|
||||
- assert.equal(res.status, 502); const result = await res.json(); assert.equal(result.board, url); assert.ok(result.error.includes(url));
|
||||
+ assert.equal((await fetch(base + '/api/board')).status, 503);
|
||||
+ assert.equal((await fetch(base + '/api/conversations')).status, 503);
|
||||
```
|
||||
|
||||
### A26: webui/tests/service.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): render carries every serve flag, the preview set included
|
||||
|
||||
Reason: CHANGED ASSERT: the expected ExecStart drops the --board flag (in-process board; webui/src/cli.mjs and scripts/webui-service.sh changed in commit 1).
|
||||
|
||||
```diff
|
||||
- assert.equal(execStart(r.stdout), `ExecStart=${process.execPath} ${REPO}/packages/webui/src/cli.mjs serve --port 7340 --business mosaic.dev_1 --board http://127.0.0.1:7331 --public-host console.example.com --auth-header X-authentik-uid --allow-user ${UID}`);
|
||||
+ assert.equal(execStart(r.stdout), `ExecStart=${process.execPath} ${REPO}/packages/webui/src/cli.mjs serve --port 7340 --business mosaic.dev_1 --public-host console.example.com --auth-header X-authentik-uid --allow-user ${UID}`);
|
||||
```
|
||||
|
||||
## Group B (added by row 198; Sage rules)
|
||||
|
||||
### B0: control-board/tests/board.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): cli serve refuses and points to the Console
|
||||
|
||||
Reason: New in row 198: cli serve refusal (cli.mjs:14) and the 'serve' retirement message, plus the markSeen order and answer asserts.
|
||||
|
||||
```diff
|
||||
+ import assert from "node:assert/strict";
|
||||
+ assert.deepEqual(events, ["write", "bump", "scan"]);
|
||||
+ assert.deepEqual(before.seen, []);
|
||||
+ assert.deepEqual(index.seen, ["proj/agent1"]);
|
||||
+ const board = createBoard({ specs, boardDir, isAlive, onEvent: (name) => events.push(name), exec: async () => assert.fail("no send on refusal") });
|
||||
+ assert.equal(out.status, 409);
|
||||
+ assert.equal(events.includes("send"), false);
|
||||
+ assert.equal(events.includes("bump"), false);
|
||||
+ assert.equal(r.status, 2);
|
||||
+ assert.match(r.stderr, /retired on 7331/);
|
||||
+ assert.match(r.stderr, /webui/);
|
||||
```
|
||||
|
||||
### B3: control-board/tests/relaunch.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): without the pid seam, a null-pid registration is undecided: kept, alive null, no relaunch
|
||||
|
||||
Reason: New in row 198: null-pid relaunch test. Correction: a null pid makes registeredAlive undecided (scan.mjs:372, 377, 381), not dead. The seam only affects relaunch; the default stays () => true.
|
||||
|
||||
```diff
|
||||
+ assert.notEqual(row.registered, null);
|
||||
+ assert.equal(row.registered.alive, null);
|
||||
+ assert.equal(row.relaunchedAt, null);
|
||||
```
|
||||
|
||||
### B25: webui/tests/serve.test.mjs
|
||||
|
||||
Enclosing test (first match, old or new side): conversation routes: nosniff, no-store, no CORS header, and every board refusal keeps its status
|
||||
|
||||
Reason: New in row 198: Console conversation headers test (nosniff, no-store, no CORS header) and the REFUSAL_STATUS table through the Console route.
|
||||
|
||||
```diff
|
||||
+ assert.equal(hostile, 403);
|
||||
+ assert.equal((await fetch(f.base + path, { headers: { origin: 'https://evil.example' } })).status, 403);
|
||||
+ assert.equal(res.status, status, `${code} ${path}`);
|
||||
+ assert.deepEqual(await res.json(), { error: code }, `${code} ${path}`);
|
||||
+ assert.equal(res.headers.get('x-content-type-options'), 'nosniff', path);
|
||||
+ assert.equal(res.headers.get('cache-control'), 'no-store', path);
|
||||
+ assert.equal(res.headers.get('access-control-allow-origin'), null, path);
|
||||
```
|
||||
|
||||
## Retired, not ported
|
||||
|
||||
One line per test name that exists on refactor and not in the current tree. Name-based check: `comm` on test names in the three files.
|
||||
|
||||
- `control-board/tests/serve.test.mjs`: startServer: refuses a non-loopback host with ConfigError, never opens a socket — Listener refusal. No listener remains in control-board.
|
||||
- `control-board/tests/serve.test.mjs`: startServer: serves page, healthz, and a rescanning /api/board — HTTP-only page, healthz and /api/board. No listener remains; page content is not re-asserted (open gap).
|
||||
- `control-board/tests/serve.test.mjs`: startServer: a seatsDir registration overrides the row and index.registered reflects it — HTTP-only seatsDir override check. Not re-asserted in this port (open gap).
|
||||
- `control-board/tests/serve.test.mjs`: startServer: /api/board returns 500 JSON with an error field when scan throws — HTTP error envelope. A throwing board read is covered by the preview dead-board test (502 invalid-response, hunk 17); the 500 shape is not re-asserted.
|
||||
- `control-board/tests/serve.test.mjs`: CLI: serve refuses a non-loopback host with exit 2 and a refused: message — serve is retired; the refusal is asserted by board.test.mjs 'cli serve refuses and points to the Console' (group B hunk 0).
|
||||
- `control-board/tests/serve.test.mjs`: CLI: live serve prints its URL and answers /healthz — serve is retired (cli.mjs:14). The writer's HTTP healthz and page are gone; no listener remains (see Board listeners).
|
||||
- `control-board/tests/serve.test.mjs`: page.html: esc() escapes every HTML-significant character — page.html is deleted. Source-shape asserts (esc, Hide seen, reply gate, source tags) have no in-process target. Console app.js coverage NOT verified in this pass (open gap).
|
||||
- `control-board/tests/serve.test.mjs`: POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it — Seen POST HTTP test removed, no port (no hunk, no phrase). markSeen write, bump, scan order is covered by board.test.mjs. Body-parsing gap below.
|
||||
- `control-board/tests/serve.test.mjs`: POST /api/seen without a JSON content-type returns 400 and does not write a mark — Body-parsing gap. Console body() refuses non-JSON content-type (webui/src/serve.mjs:58) then 400 at :191. No Console test (open gap).
|
||||
- `control-board/tests/serve.test.mjs`: POST /api/seen with invalid JSON returns 400 — Body-parsing gap. Console answers 400 (webui/src/serve.mjs:191). No Console test (open gap).
|
||||
- `control-board/tests/serve.test.mjs`: POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark — Body-parsing gap. Console body() default max 4096 (webui/src/serve.mjs:56) answers 400 via :191. No Console test (open gap).
|
||||
- `control-board/tests/serve.test.mjs`: POST /api/seen with a missing agent returns 400 — Body-parsing gap. Console answers 400 with the error message (webui/src/serve.mjs:197). No Console test (open gap).
|
||||
- `control-board/tests/serve.test.mjs`: POST /api/board returns 405; PUT /api/seen returns 405 — Writer HTTP method refusal. Console 405s are asserted by group A hunk 21 (GET /api/reply, OPTIONS /api/board).
|
||||
- `control-board/tests/serve.test.mjs`: page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type — page.html is deleted. Source-shape asserts (esc, Hide seen, reply gate, source tags) have no in-process target. Console app.js coverage NOT verified in this pass (open gap).
|
||||
- `control-board/tests/serve.test.mjs`: page.html: has a collapsed Seen section that lists seen rows with the shared row builder — page.html is deleted. Source-shape asserts (esc, Hide seen, reply gate, source tags) have no in-process target. Console app.js coverage NOT verified in this pass (open gap).
|
||||
- `control-board/tests/serve.test.mjs`: page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note — page.html is deleted. Source-shape asserts (esc, Hide seen, reply gate, source tags) have no in-process target. Console app.js coverage NOT verified in this pass (open gap).
|
||||
- `control-board/tests/serve.test.mjs`: page.html: a project header reads \ — page.html is deleted. Source-shape asserts (esc, Hide seen, reply gate, source tags) have no in-process target. Console app.js coverage NOT verified in this pass (open gap).
|
||||
- `control-board/tests/serve.test.mjs`: page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail — page.html is deleted. Source-shape asserts (esc, Hide seen, reply gate, source tags) have no in-process target. Console app.js coverage NOT verified in this pass (open gap).
|
||||
- `control-board/tests/serve.test.mjs`: page.html: task and active project cells show their source via sourceTag(); the detail has a Registered row via registeredText(); SOURCE_LABEL maps registration to registered; every dynamic value in sourceTag/fromSource/registeredText is escaped — page.html is deleted. Source-shape asserts (esc, Hide seen, reply gate, source tags) have no in-process target. Console app.js coverage NOT verified in this pass (open gap).
|
||||
- `control-board/tests/serve.test.mjs`: POST /api/reply: runs agent-send.sh with -s from the registration, -S <host>:control-board, -m text plus the fixed trailer, no -L on the default socket, MOSAIC_TMUX_SOCKET stripped; answers delivered with the exit code and both streams — Renamed; the phrase is still present in the current tree (rename only, no assert hunk).
|
||||
- `control-board/tests/serve.test.mjs`: POST /api/reply: a registration with a tmux socket adds -L <socket> — Name not carried over; the -L socket asserts stay (hunk 9, re-indent only).
|
||||
- `control-board/tests/serve.test.mjs`: POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim — Renamed; the phrase is still present in the current tree (rename only, no assert hunk).
|
||||
- `control-board/tests/serve.test.mjs`: POST /api/reply: refusals before the tool runs: empty or blank or long text 400, unknown row 404, no registration 409, stale registration 409, no tmux session 409, bad JSON 400; the tool is never called — Name not carried over; the refusal table stays (hunk 10, re-indent only).
|
||||
- `control-board/tests/serve.test.mjs`: POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash — Name not carried over; the 500 and path asserts stay in the ported reply test (hunk 11).
|
||||
- `control-board/tests/serve.test.mjs`: page.html: the reply box appears only where canReply() holds (live registration with a tmux session), the detail has a Reply row, the submit posts JSON to /api/reply, receipts and drafts survive a refresh, and every receipt value is escaped — page.html is deleted. Source-shape asserts (esc, Hide seen, reply gate, source tags) have no in-process target. Console app.js coverage NOT verified in this pass (open gap).
|
||||
- `control-board/tests/serve.test.mjs`: startServer: /api/board carries taskSetBy from a live registration and null for the derived rows — Renamed 'index: carries taskSetBy...'; asserts ported (hunk 14).
|
||||
- `control-board/tests/serve.test.mjs`: page.html: the task cell and detail show who set a registered task via setByTag()/setByText(), both escaped, only from rec.taskSetBy; the reply gate does not read it — page.html is deleted. Source-shape asserts (esc, Hide seen, reply gate, source tags) have no in-process target. Console app.js coverage NOT verified in this pass (open gap).
|
||||
- `control-board/tests/serve.test.mjs`: Host/Origin guard: GET /api/board and POST /api/reply refuse a foreign Host, a wrong port and a cross-origin Origin with 403 JSON, before any scan or send, and never send CORS headers — Writer HTTP fence. control-board opens no listener (see Board listeners). Console fences at webui/src/serve.mjs:713-717 and :726 covered by the F16 test.
|
||||
- `control-board/tests/serve.test.mjs`: Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin — Writer HTTP fence; same reason as the other Host/Origin guard test.
|
||||
- `control-board/tests/serve.test.mjs`: conversation routes (F16): a foreign Host, a wrong port and a cross-origin Origin get 403 before the reader runs, with no CORS headers — Writer HTTP fence. Replaced by the Console F16 test (webui serve.test.mjs, 'conversation route: a non-local Host or a foreign Origin is refused 403').
|
||||
- `control-board/tests/serve.test.mjs`: conversation routes: a reader listing that refuses gets its refusal status and body, not a 200 — Renamed 'conversation listing: ...' and ported to board.conversation (hunk 15). HTTP header asserts moved to the Console test (group B hunk 25).
|
||||
- `control-board/tests/serve.test.mjs`: conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written — HTTP-only catalogue and paging. No in-process port; page-content asserts are not re-asserted (open gap).
|
||||
- `control-board/tests/discord.test.mjs`: server rescans connector discovery and refuses HTTP reply without transport — Renamed in place to 'board rescans connector discovery and refuses reply without transport'; HTTP fetch and 409 ported to board.index()/board.reply() (hunks 1-2).
|
||||
- `webui/tests/serve.test.mjs`: loopback host and board origin fail closed — boardURL() removed with the HTTP board address (hunk 19).
|
||||
- `webui/tests/serve.test.mjs`: proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect — Writer HTTP proxy test; the Console has no proxy hop in-process. Changed and weakened asserts in hunks 20-23 (see Weakened).
|
||||
- `webui/tests/serve.test.mjs`: unreachable board reports URL; CLI rejects unsupported options — No board URL to report in-process; replaced by 503 not-configured asserts (hunk 24).
|
||||
|
||||
## Weakened and changed asserts (for Mos)
|
||||
|
||||
- Hunk 20: 409 becomes 403 (behaviour change, see below); the oversized-body 400 is dropped.
|
||||
- Hunk 21: the text/plain 400 and the request-count and board-502 checks are dropped.
|
||||
- Hunk 22: exact 404 body text becomes a table comparison.
|
||||
- Hunk 23: exact 'refused before the board' check becomes status >= 400.
|
||||
|
||||
## Behaviour changes to report
|
||||
|
||||
1. POST /api/reply in the Console: 403 when send is off or the seat is unnamed (webui/src/serve.mjs ~843-846). The refactor forwarded the request unconditionally (hunk 20).
|
||||
2. /api/settings reports `board: null` in-process (hunk 18).
|
||||
3. A dead board read returns 502 `invalid-response` (hunk 17).
|
||||
4. Null-pid correction: a null pid is undecided (scan.mjs:372, 377, 381), not dead. The default `() => true` only matters for relaunch, and the new relaunch test (group B hunk 3) covers that.
|
||||
5. creative-hidden.test.mjs: ported from `startServer` to `createBoard` and `board.conversation`. Assertions unchanged. `startServer` is no longer exported by control-board/src/serve.mjs.
|
||||
|
||||
## Board listeners after 198
|
||||
|
||||
Command: `grep -rn "createServer\|\.listen(\|startServer\|startBoard" packages/control-board/src`
|
||||
|
||||
Result: no matches (exit 1). control-board/src opens no listener.
|
||||
|
||||
CLI entry: packages/control-board/src/cli.mjs:14 refuses `serve` with `ConfigError` (RETIRED message, line 11). No listener.
|
||||
|
||||
Not in the board's code, for completeness: the Console (packages/webui/src/serve.mjs:1 imports `createServer`; :684 creates the server; :909 listens) hosts the board in-process. packages/webui/src/tester-socket.mjs:24 listens on a separate tester socket, not the board.
|
||||
|
||||
So the Host/Origin tests in control-board are retired on this basis. The Console's Host and Origin fences are covered by the F16 test in webui/tests/serve.test.mjs.
|
||||
|
||||
## Open items (not closed in this pass)
|
||||
|
||||
- page.html source checks are retired with no verified Console equivalent in app.js (open gap).
|
||||
- Seen POST body parsing (400 for invalid JSON, missing agent, 4096 limit, non-JSON content-type) has no board test and no Console test. Gap recorded; no new test added (sage-4373).
|
||||
- Catalogue and paging over HTTP, seatsDir registration override, and the 500 shape are not re-asserted (see retire list).
|
||||
- Not run: chat-replies-browser (line 71 `startServer({ port: 0, board: f.board })`) and the shell 'board unavailable' text (ReadError message path, serve.mjs:230-234). Verify on CLEAR.
|
||||
- The webui F16 test (serve.test.mjs:93) is a new test; the hunk map does not isolate it as its own hunk. Confirm its add lines are in group B hunk 25's range.
|
||||
- webui/tests/discord.test.mjs needs no isPidAlive seam: its registration uses pid `process.pid`.
|
||||
- conversation/tests/flows.test.mjs imports replyToRow from control-board/src/serve.mjs; the export still exists. Not run.
|
||||
- Syntax: `node --check` passes for all touched JS files (no tests run).
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# Row 334 — Case 2 proxy unit (#1832)
|
||||
|
||||
## Design at 78b69e1e6 (2026-10-25)
|
||||
|
||||
Scope: mos-1616 ruling 1, P1 item 7 only. No template, runner, PIN,
|
||||
user-unit path, config/secret access, or live unit changes.
|
||||
|
||||
Current references: case2-unit.sh template seams 44-45, defaults 54-57,
|
||||
render 435, check_unit 489, render checks 548-551, re-install 553-573,
|
||||
active-state capture 576-580, writes/start/report 583-610.
|
||||
case2-install.sh Next: 231. P1 instance.sh PROXY_UNIT 86;
|
||||
bus-service.sh dependency insertion 93. P1 template ExecStart 13;
|
||||
run.mjs validates config 11-13 and calls startVerifiedProxy 14;
|
||||
runner.mjs refuses proxy-not-configured 8, verifyInstall 9;
|
||||
settings.mjs verifyInstall 23-30 refuses proxy-install-invalid for a hash
|
||||
mismatch. run.mjs catches that and exits 3. P1 item 7 is rows doc 86-90;
|
||||
row209 entry check 2 remains 32-36.
|
||||
|
||||
Add bare --proxy and CASE2_PROXY_TEMPLATE. Render P1's unmodified template
|
||||
with copy, PATH, node and deleted CONFIG_ENV; add User/Group plus bus's four
|
||||
hardening lines, no capabilities or EnvironmentFile. check_unit proxy uses
|
||||
exact node + copy/packages/model-proxy/src/run.mjs, NoNewPrivileges=yes,
|
||||
and every common refusal. Restrict CLAUDE_ENV expectation and capabilities
|
||||
to their existing bus/webui kinds. Insert bus Wants/After immediately after
|
||||
Description exactly as P1 does, only with --proxy. No-proxy output remains
|
||||
byte identical.
|
||||
|
||||
Re-install checks all selected units against render modulo copy id and
|
||||
compares every installed copy id. Existing proxy without --proxy refuses
|
||||
before writes, including a dangling symlink. No deletion. Enable all
|
||||
selected units, then start proxy, bus, Console. Capture/report already-running
|
||||
proxy with the others and name all three in restart-together guidance.
|
||||
Change only case2-install.sh's closing Next: to tell root to add --proxy
|
||||
when the config has a proxy section.
|
||||
|
||||
Tests first, unrun until Sage CLEAR: byte-identical non-proxy renders;
|
||||
proxy content and exact dependencies; each common/proxy check_unit refusal
|
||||
by message; rendered ExecStart wrong-PIN refusal proxy-install-invalid rc 3;
|
||||
three-unit re-install and mixed copies including proxy-only mismatch;
|
||||
installed proxy without flag preserves files; start order and failures;
|
||||
already-running report; install Next: guidance. Named mutants: remove bus
|
||||
After=; direct binary ExecStart bypassing run.mjs. Whole-file run list will
|
||||
be derived from static imports before any run; case1-accept stays off.
|
||||
|
||||
P2 is not present on this base (row331 still in progress); inspect landed
|
||||
P2 refusal code before final handoff. No flag/config false-positive check:
|
||||
P1 startVerifiedProxy refuses proxy-not-configured, run.mjs exits 3.
|
||||
|
||||
## Slot / session
|
||||
|
||||
No tests run. CLEAR pending from Sage. Canonical queue start initially
|
||||
refused: lock held by magica-331-start since 03:57:01.839Z. Same operation
|
||||
will be retried after a minute; no lock manipulation.
|
||||
|
||||
Correction to line references above: row209 entry check 2 is 29-33;
|
||||
run.mjs config validation 8-10, runner call 11, refusal exit status 18.
|
||||
case2-unit.sh is byte-identical to brief base 58b2bbb3a (sha256
|
||||
4ca8769134d83ee391bcc73af5bd042b9e2b161cac22821d29fcca710a9afad4).
|
||||
P1 paths named above have no diff between 86cb5c221 and 78b69e1e6.
|
||||
Own-worktree root npm ci --ignore-scripts --no-audit --no-fund: rc 0,
|
||||
128 packages. No tests invoked.
|
||||
|
||||
Sage sage-4346 accepts design, authorizes writing RED tests then code while
|
||||
holding all runs until CLEAR. Tests were written first, then implementation;
|
||||
no RED run has occurred. Installed proxy without --proxy explicitly refuses
|
||||
and preserves bytes; dangling symlink also refuses, never deletes. P2 code
|
||||
remains pending until row331 lands. Both wrong-flag P1 runner paths covered.
|
||||
|
||||
Import-manifest correction: expanded traversal to include multiline import
|
||||
and export-from edges (the business/library barrel modules). Closure is
|
||||
53 local modules; only the same two test entrypoints occur. The run list
|
||||
is unchanged; this is source inspection, no tests executed.
|
||||
|
||||
Queue start completed: rockerduck-334-start rev4079, row334
|
||||
briefed→in-progress. git diff --check passed. Implementation, tests and
|
||||
import manifest prepared; no tests, commits or pushes performed. Verification
|
||||
awaits Sage's CLEAR; no live host/instance access occurred.
|
||||
|
||||
## RED run authorized by sage-4387, 2026-10-25 04:21Z
|
||||
|
||||
Whole files, sequential, with each corresponding script temporarily restored
|
||||
from 78b69e1e6 and its implementation restored by EXIT trap afterwards:
|
||||
- case2-unit.test.mjs: rc1, 81 tests, 48 pass, 33 fail, 0 skips;
|
||||
TMPDIR=/mnt/storage/scratch/tmp/QD3vp5, nice=19, 77995.928687 ms.
|
||||
All 47 old tests pass; new no-proxy byte equality passes. 32 new failures
|
||||
encounter baseline unsupported --proxy usage rc4; dangling installed proxy
|
||||
symlink case gets rc0 rather than refusal because baseline ignores it.
|
||||
- case2-install.test.mjs: rc1, 28 tests, 27 pass, 1 fail, 0 skips;
|
||||
TMPDIR=/mnt/storage/scratch/tmp/YAGfKJ, nice=19, 10016.247032 ms.
|
||||
All old tests pass. New Next: guidance assertion fails because baseline
|
||||
lacks add --proxy when the config has a proxy section.
|
||||
|
||||
Every failure and cause is listed in red-results.txt; full plain-text
|
||||
outputs in red-unit.log and red-install.log. Absolute node/test paths used.
|
||||
Implementation restored, git diff --check clean. No old-test red, rerun,
|
||||
assertion change, GREEN or mutant run. P2 check still pending row331.
|
||||
Slot returned to Sage; GREEN and mutants await separate CLEAR.
|
||||
|
||||
Sage sage-4390 accepts clean RED and explicitly authorizes committing and
|
||||
pushing this candidate to rockerduck/row334 before GREEN. No force push.
|
||||
GREEN and mutants remain held for a separate CLEAR after row331 lands;
|
||||
then rebase onto landed P2 and check its missing-flag refusal code.
|
||||
@@ -0,0 +1,69 @@
|
||||
# Row334 whole-file run list (UNRUN; awaiting CLEAR)
|
||||
|
||||
Entrypoints are the brief-owned tests, which spawn the changed shell scripts.
|
||||
Their import graph is recorded below; no imported module is itself a test entrypoint.
|
||||
case1-accept.test.mjs is excluded (mos-1612).
|
||||
|
||||
packages/cli/tests/case2-unit.test.mjs -> packages/business/tests/helpers.mjs, packages/seat/src/sandbox.mjs, packages/cli/tests/helpers.mjs, packages/cli/tests/case-sudo-fixture.mjs
|
||||
packages/business/tests/helpers.mjs -> (builtins/external only)
|
||||
packages/seat/src/sandbox.mjs -> packages/business/src/vocabulary.mjs
|
||||
packages/business/src/vocabulary.mjs -> (builtins/external only)
|
||||
packages/cli/tests/helpers.mjs -> packages/bus/src/broker.mjs, packages/bus/src/store.mjs, packages/business/tests/helpers.mjs, packages/harness/tests/helpers.mjs, packages/plugins/tests/helpers.mjs, packages/cli/src/transport.mjs
|
||||
packages/bus/src/broker.mjs -> packages/bus/src/address.mjs, packages/processing-control/src/state.mjs, packages/plugins/src/kinds.mjs, packages/tasks/src/requirements.mjs, packages/bus/src/landing.mjs, packages/business/src/vocabulary.mjs
|
||||
packages/bus/src/address.mjs -> (builtins/external only)
|
||||
packages/processing-control/src/state.mjs -> (builtins/external only)
|
||||
packages/plugins/src/kinds.mjs -> (builtins/external only)
|
||||
packages/tasks/src/requirements.mjs -> (builtins/external only)
|
||||
packages/bus/src/landing.mjs -> (builtins/external only)
|
||||
packages/bus/src/store.mjs -> (builtins/external only)
|
||||
packages/harness/tests/helpers.mjs -> packages/business/src/index.mjs, packages/business/tests/helpers.mjs, packages/plugins/src/loader.mjs
|
||||
packages/business/src/index.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs, packages/business/src/role.mjs, packages/business/src/vars.mjs, packages/business/src/credentials.mjs, packages/business/src/business.mjs, packages/business/src/access.mjs, packages/business/src/project.mjs, packages/business/src/resolve.mjs, packages/business/src/library-seats.mjs
|
||||
packages/business/src/errors.mjs -> (builtins/external only)
|
||||
packages/business/src/role.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs, packages/business/src/util.mjs
|
||||
packages/business/src/util.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs
|
||||
packages/business/src/vars.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs, packages/business/src/util.mjs, packages/plugins/src/kinds.mjs
|
||||
packages/business/src/credentials.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs, packages/business/src/util.mjs
|
||||
packages/business/src/business.mjs -> packages/business/src/errors.mjs, packages/business/src/launch.mjs, packages/business/src/vars.mjs, packages/business/src/credentials.mjs, packages/business/src/role.mjs, packages/business/src/vocabulary.mjs, packages/plugins/src/kinds.mjs, packages/library/src/mapping.mjs, packages/business/src/util.mjs
|
||||
packages/business/src/launch.mjs -> packages/business/src/util.mjs, packages/business/src/errors.mjs
|
||||
packages/library/src/mapping.mjs -> packages/library/src/problems.mjs
|
||||
packages/library/src/problems.mjs -> (builtins/external only)
|
||||
packages/business/src/access.mjs -> packages/business/src/errors.mjs, packages/business/src/business.mjs, packages/business/src/util.mjs, packages/business/src/vocabulary.mjs
|
||||
packages/business/src/project.mjs -> packages/business/src/errors.mjs, packages/business/src/vars.mjs, packages/business/src/util.mjs
|
||||
packages/business/src/resolve.mjs -> packages/business/src/errors.mjs, packages/business/src/vocabulary.mjs, packages/business/src/vars.mjs, packages/business/src/util.mjs, packages/business/src/project.mjs
|
||||
packages/business/src/library-seats.mjs -> packages/business/src/errors.mjs, packages/business/src/business.mjs, packages/business/src/vocabulary.mjs
|
||||
packages/plugins/src/loader.mjs -> packages/plugins/src/kinds.mjs
|
||||
packages/plugins/tests/helpers.mjs -> (builtins/external only)
|
||||
packages/cli/src/transport.mjs -> packages/cli/src/errors.mjs
|
||||
packages/cli/src/errors.mjs -> (builtins/external only)
|
||||
packages/cli/tests/case-sudo-fixture.mjs -> (builtins/external only)
|
||||
packages/cli/tests/case2-install.test.mjs -> packages/business/tests/helpers.mjs, packages/library/tests/helpers.mjs, packages/library/src/index.mjs, packages/seat/src/checkout.mjs, packages/project/src/git.mjs, packages/cli/tests/helpers.mjs
|
||||
packages/library/tests/helpers.mjs -> packages/library/src/mapping.mjs
|
||||
packages/library/src/index.mjs -> packages/library/src/problems.mjs, packages/library/src/mapping.mjs, packages/library/src/scope.mjs, packages/library/src/supporting.mjs, packages/library/src/links.mjs, packages/library/src/reader.mjs, packages/library/src/render.mjs, packages/library/src/survey.mjs, packages/library/src/mirror.mjs, packages/library/src/sync.mjs, packages/library/src/index-build.mjs, packages/library/src/query.mjs, packages/library/src/limits.mjs, packages/library/src/guard.mjs
|
||||
packages/library/src/scope.mjs -> (builtins/external only)
|
||||
packages/library/src/supporting.mjs -> packages/library/src/problems.mjs, packages/library/src/scope.mjs
|
||||
packages/library/src/links.mjs -> (builtins/external only)
|
||||
packages/library/src/reader.mjs -> packages/project/src/git.mjs, packages/library/src/scope.mjs, packages/library/src/links.mjs, packages/library/src/problems.mjs
|
||||
packages/project/src/git.mjs -> packages/seat/src/checkout.mjs, packages/project/src/paths.mjs, packages/processing-control/src/effects.mjs
|
||||
packages/seat/src/checkout.mjs -> packages/processing-control/src/effects.mjs
|
||||
packages/processing-control/src/effects.mjs -> packages/processing-control/src/state.mjs, packages/processing-control/src/runtimes.mjs
|
||||
packages/processing-control/src/runtimes.mjs -> packages/processing-control/src/state.mjs
|
||||
packages/project/src/paths.mjs -> (builtins/external only)
|
||||
packages/library/src/render.mjs -> (builtins/external only)
|
||||
packages/library/src/survey.mjs -> packages/library/src/mapping.mjs, packages/library/src/scope.mjs, packages/library/src/links.mjs, packages/library/src/reader.mjs, packages/library/src/problems.mjs
|
||||
packages/library/src/mirror.mjs -> packages/project/src/paths.mjs, packages/project/src/git.mjs, packages/library/src/mapping.mjs, packages/library/src/scope.mjs, packages/library/src/problems.mjs, packages/library/src/reader.mjs
|
||||
packages/library/src/sync.mjs -> packages/seat/src/checkout.mjs, packages/project/src/git.mjs, packages/library/src/mapping.mjs, packages/library/src/problems.mjs, packages/library/src/reader.mjs, packages/library/src/mirror.mjs
|
||||
packages/library/src/index-build.mjs -> packages/library/src/reader.mjs, packages/library/src/problems.mjs
|
||||
packages/library/src/query.mjs -> packages/library/src/problems.mjs, packages/library/src/mapping.mjs, packages/library/src/index-build.mjs, packages/library/src/reader.mjs
|
||||
packages/library/src/limits.mjs -> (builtins/external only)
|
||||
packages/library/src/guard.mjs -> packages/project/src/git.mjs, packages/library/src/scope.mjs, packages/library/src/reader.mjs, packages/library/src/problems.mjs, packages/library/src/mirror.mjs, packages/library/src/limits.mjs, packages/library/src/reply-protocol.mjs
|
||||
packages/library/src/reply-protocol.mjs -> (builtins/external only)
|
||||
|
||||
Whole-file commands after CLEAR (one fresh shell-mktemp TMPDIR each; nice=19):
|
||||
node --test packages/cli/tests/case2-unit.test.mjs
|
||||
node --test packages/cli/tests/case2-install.test.mjs
|
||||
|
||||
Before these: new row334 tests only on saved 78b69e1e6 scripts for RED,
|
||||
then restored implementation. After GREEN: named mutants individually,
|
||||
each with its directly relevant row334 test. No scripts/test-*.sh builder run planned.
|
||||
|
||||
Includes multiline imports and export-from module edges: 53 local modules.
|
||||
@@ -0,0 +1,68 @@
|
||||
TMPDIR=/mnt/storage/scratch/tmp/YAGfKJ nice=19
|
||||
✔ the library install writes the business file, personas and slot dir, makes the copy and starts nothing (386.113555ms)
|
||||
✔ a library re-run passes and leaves every file as it was (496.225906ms)
|
||||
✔ a re-run refuses business-file-differs and leaves the file unchanged (376.395513ms)
|
||||
✔ a re-run refuses persona-differs when an installed persona isn't the staged one, and writes nothing (398.216221ms)
|
||||
✔ the factory install writes no library mapping, persona or slot dir (247.905208ms)
|
||||
✔ the factory install refuses a library project and bus.tester off, before anything is written (105.176208ms)
|
||||
✔ the library install refuses bus.tester on and a persona var in the input (104.503599ms)
|
||||
✔ the install refuses root, a business id mismatch, another --home and a config dir with group bits (80.144703ms)
|
||||
✔ the library install needs --personas (7.831929ms)
|
||||
✔ a validate refusal removes the business file this run wrote (311.701322ms)
|
||||
✔ N4: a validate refusal on a re-run leaves the matching business file in place, byte for byte (537.848604ms)
|
||||
✔ a clean library host passes the precheck with no mirror yet (509.009214ms)
|
||||
✔ the library precheck refuses a persona file at 0644 (506.697677ms)
|
||||
✔ the library precheck refuses a persona whose digest isn't persona.sha256 (495.113108ms)
|
||||
✔ the library precheck refuses a persona dir with group bits (481.900638ms)
|
||||
✔ the library precheck refuses a business file at 0644 (462.626038ms)
|
||||
✔ the library precheck refuses a changes.jsonl at 0644 (687.490332ms)
|
||||
✔ the library precheck refuses a Discord binding (499.568905ms)
|
||||
✔ --library-status refuses library-mirror-missing before the bus has made the mirror (377.717622ms)
|
||||
✔ --library-status refuses library-never-synced on a fresh mirror (410.956236ms)
|
||||
✔ --library-status names a recorded lastError before the null lastFetch it leaves (N3) (417.888767ms)
|
||||
✔ --library-status prints each R-problem's path as an escaped JSON string, then the count, and passes (481.141014ms)
|
||||
✔ --library-status takes no --profile (6.246241ms)
|
||||
✔ Vikunja 614: install preserves the explicit notifier decision bytes and mode and creates none when absent (560.431267ms)
|
||||
✔ row 322 F8: library personas install from the copy with no clone node_modules (183.399079ms)
|
||||
✔ row 322 F9: importer refusal keeps the error line rather than the Node banner (118.731885ms)
|
||||
✔ C2-21: library install supplies chief identity and books publish for its real read grant (330.48101ms)
|
||||
✖ row334 install Next tells root to select proxy only when configured (302.965779ms)
|
||||
ℹ tests 28
|
||||
ℹ suites 0
|
||||
ℹ pass 27
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 10016.247032
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/case2-install.test.mjs:495:1
|
||||
✖ row334 install Next tells root to select proxy only when configured (302.965779ms)
|
||||
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /Next: root runs case2-unit.sh[^\n]+add --proxy when the config has a proxy section/. Input:
|
||||
|
||||
'case2-install: config /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/config.json\n' +
|
||||
'case2-install: copy /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc\n' +
|
||||
'case2-install: personas installed under /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/personas/acme\n' +
|
||||
'case2-install: business file /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/businesses/acme.json written\n' +
|
||||
'case2-install: slot dir /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/secrets/acme\n' +
|
||||
'case2-install: business acme validates\n' +
|
||||
'case2-install: done. Next: root runs case2-unit.sh --copy cccccccccccccccccccccccccccccccccccccccc --business acme (design 10 step 2c), then 2d and 3.\n' +
|
||||
'case2-install: Then Jason places the library token in /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/secrets/acme (step 4), and <user> runs case2-precheck.sh --business acme --library-status (step 4a).\n'
|
||||
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-install.test.mjs:498:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'case2-install: config /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/config.json\ncase2-install: copy /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc\ncase2-install: personas installed under /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/personas/acme\ncase2-install: business file /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/businesses/acme.json written\ncase2-install: slot dir /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/secrets/acme\ncase2-install: business acme validates\ncase2-install: done. Next: root runs case2-unit.sh --copy cccccccccccccccccccccccccccccccccccccccc --business acme (design 10 step 2c), then 2d and 3.\ncase2-install: Then Jason places the library token in /mnt/storage/scratch/tmp/YAGfKJ/case2-install-lXA9Qd/home/.config/mosaic-dev/secrets/acme (step 4), and <user> runs case2-precheck.sh --business acme --library-status (step 4a).\n',
|
||||
expected: /Next: root runs case2-unit.sh[^\n]+add --proxy when the config has a proxy section/,
|
||||
operator: 'match',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
# RED results — sage-4387 (2026-10-25)
|
||||
|
||||
Baseline scripts: 78b69e1e6. Current row334 tests. Whole files, sequential.
|
||||
Both implementation scripts restored via EXIT traps after their run.
|
||||
No GREEN or mutant run. No old-test failure. No assertion changed.
|
||||
|
||||
case2-unit.test.mjs: exit 1; 81 tests, 48 pass, 33 fail, 0 skip.
|
||||
47 existing tests plus new byte-identical no-proxy test pass.
|
||||
TMPDIR=/mnt/storage/scratch/tmp/QD3vp5; nice=19; duration 77995.928687 ms.
|
||||
|
||||
Every unit failure and its observed reason:
|
||||
- row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses continuation by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses not-setting by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses RestrictNamespaces by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses PrivateUsers by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses SystemCallFilter by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses PermissionsStartOnly by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses EnvironmentFile by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses AmbientCapabilities by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses CapabilityBoundingSet by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses privileged by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses direct-binary by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses nnp by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses cwd by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses environment by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses install by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses user by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses group by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses ProtectSystem by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses ReadWritePaths by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses ProtectHome by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses PrivateTmp by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy refuses token by message before writing: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy template missing or symlink refuses: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 rendered ExecStart refuses wrong PIN and missing proxy with P1 status: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 reinstall repins all three and refuses a proxy copy mismatch: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 installed proxy without --proxy refuses and preserves all bytes: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 dangling proxy symlink without flag refuses without deleting: baseline ignores installed proxy symlink without flag, returns 0 instead of refusal 1.
|
||||
- row334 active proxy reports old copy and all three must restart together: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 failed mosaic-proxy.service start leaves later units unstarted: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 failed [email protected] start leaves later units unstarted: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 installed proxy enforces hardening order and remaining common refusals: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
- row334 proxy installed path rejects symlink and non-regular file: baseline does not parse --proxy: usage exit 4, before the proxy-specific assertion/setup can be reached.
|
||||
|
||||
case2-install.test.mjs: exit 1; 28 tests, 27 pass, 1 fail, 0 skip.
|
||||
TMPDIR=/mnt/storage/scratch/tmp/YAGfKJ; nice=19; duration 10016.247032 ms.
|
||||
- row334 install Next tells root to select proxy only when configured: baseline Next: lacks add --proxy when the config has a proxy section.
|
||||
|
||||
Commands used absolute /usr/bin/node --test and absolute test-file paths.
|
||||
Full plain-text outputs: red-unit.log and red-install.log beside this file.
|
||||
P2 refusal-code check remains pending row331. Slot returned to Sage.
|
||||
@@ -0,0 +1,873 @@
|
||||
TMPDIR=/mnt/storage/scratch/tmp/QD3vp5 nice=19
|
||||
✔ row253 factory socket uses actual config and target-uid query without widening hardening (351.144059ms)
|
||||
✔ row253 installer refuses unsafe location and query facts before unit writes (3621.94316ms)
|
||||
✔ row253 query timeout kills its process group and leaves no running child (7054.318085ms)
|
||||
✔ row253 protected homes refuse before any filesystem access (9.291102ms)
|
||||
✔ row253 C4 sleep query has exact inner reason and cleans all members (10796.380077ms)
|
||||
✔ row253 C4 signals query has exact inner reason and cleans all members (10819.227773ms)
|
||||
✔ row253 C4 member query has exact inner reason and cleans all members (10897.328548ms)
|
||||
✔ row253 C4 overflow is output-limit in both sudo modes (554.12811ms)
|
||||
✔ row253 C4 stalled supervisor uses the outer reason and explicit residual cleanup (14201.797743ms)
|
||||
✔ row253 C4 inline supervisor is byte-identical in both host scripts (0.888234ms)
|
||||
✔ row253 C4 cleanup budget starts after inventory pass one (46.615614ms)
|
||||
✔ row253 query reaps its group even when a child closes stdout before the query returns (152.118314ms)
|
||||
✔ row253 config parser is strict (11.560624ms)
|
||||
✔ a clean library render writes both units 0644 with the fixed lines, and enables both then starts the bus then the Console (120.676804ms)
|
||||
✔ PATH starts with the --node directory, for a node outside the default list and for /usr/bin/node (131.630623ms)
|
||||
✔ a failed bus start refuses with the Console not started (123.177864ms)
|
||||
✔ the script never links a unit file (0.18683ms)
|
||||
✔ factory renders --pm once and one MOSAIC_CLAUDE_BIN line, and without them both tokens render empty (746.075081ms)
|
||||
✔ a re-install over running units says they are still on the old copy and must restart together, never that it started them (494.63679ms)
|
||||
✔ a re-install to another copy re-renders both units, and an edited installed unit refuses (1175.719345ms)
|
||||
✔ a template with a namespace, user or system call restriction refuses (335.625113ms)
|
||||
✔ the bus template's EnvironmentFile line must be there and point at the session env, and the Console template must have none (214.600182ms)
|
||||
✔ a template holding an unknown token, PermissionsStartOnly or a privileged Exec prefix refuses (151.081813ms)
|
||||
✔ a symlink at either unit path refuses (203.555513ms)
|
||||
✔ a --node that is relative, not root-owned or under <home> refuses (27.297759ms)
|
||||
✔ a re-install with a different --node or different serve flags refuses (599.873393ms)
|
||||
✔ an installed pair whose units name different copies refuses (328.321083ms)
|
||||
✔ a copy id that is short, uppercase, holds / or .., or names no directory refuses (46.028703ms)
|
||||
✔ library refuses a privileged-floor miss, a bad proof pair and every flag outside its set (344.833107ms)
|
||||
✔ library refuses a proof file not owned by <user>'s uid (18.013172ms)
|
||||
✔ factory refuses duplicate socket, a missing --port or --business, and each proxy flag (209.757808ms)
|
||||
✔ row253 factory refuses retired TCP and a value after the socket marker (59.218628ms)
|
||||
✔ factory refuses a --claude that is relative, not root-owned or under <home>, and a re-install with another --pm or --claude (1686.680735ms)
|
||||
✔ the precheck passes on a good host and prints free memory, without the key (226.963557ms)
|
||||
✔ the precheck refuses each failed host check (1765.731095ms)
|
||||
✔ the precheck refuses a user missing from passwd, a business file that does not load, and a symlinked run root (927.260864ms)
|
||||
✔ a bwrap that fails refuses and prints the sysctl and where bwrap is (200.731617ms)
|
||||
✔ an unprivileged port floor at or below the Console's port refuses, and so does a missing Console unit (1055.360802ms)
|
||||
✔ bus.tester: library refuses it on, factory needs it on, and factory refuses a library business (927.574539ms)
|
||||
✔ the claude check runs only for a claude-code instance, and refuses a claude that appends or answers wrong (1694.074713ms)
|
||||
✔ the claude check runs the bus unit's MOSAIC_CLAUDE_BIN over claude on PATH (498.707509ms)
|
||||
✔ the session env file refuses when missing, open to others, a symlink, empty, or emptied by a later line (1155.959127ms)
|
||||
✔ inside a session's sandbox a bind below 1024 fails with EACCES, and a bind on a free high port succeeds (106.466066ms)
|
||||
✔ Vikunja 614 factory: scoped @anthropic-ai Claude path renders and passes installed read-back (1024.721235ms)
|
||||
✔ Vikunja 614 factory: plain paths still refuse every named unsafe character (117.946806ms)
|
||||
✔ Vikunja 614 precheck: explicit null and named notifier decisions pass (384.090317ms)
|
||||
✔ Vikunja 614 precheck: notifier config refuses missing, public, malformed and extra-key decisions (1596.303308ms)
|
||||
✔ row334 no-proxy renders stay byte-identical to the pre-row renderer (316.417263ms)
|
||||
✖ row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order (6.436602ms)
|
||||
✖ row334 proxy refuses continuation by message before writing (6.291734ms)
|
||||
✖ row334 proxy refuses not-setting by message before writing (5.588449ms)
|
||||
✖ row334 proxy refuses RestrictNamespaces by message before writing (5.71389ms)
|
||||
✖ row334 proxy refuses PrivateUsers by message before writing (5.34285ms)
|
||||
✖ row334 proxy refuses SystemCallFilter by message before writing (5.400994ms)
|
||||
✖ row334 proxy refuses PermissionsStartOnly by message before writing (5.443023ms)
|
||||
✖ row334 proxy refuses EnvironmentFile by message before writing (5.052075ms)
|
||||
✖ row334 proxy refuses AmbientCapabilities by message before writing (5.027453ms)
|
||||
✖ row334 proxy refuses CapabilityBoundingSet by message before writing (5.604853ms)
|
||||
✖ row334 proxy refuses privileged by message before writing (5.176325ms)
|
||||
✖ row334 proxy refuses direct-binary by message before writing (5.301058ms)
|
||||
✖ row334 proxy refuses nnp by message before writing (4.978268ms)
|
||||
✖ row334 proxy refuses cwd by message before writing (4.903056ms)
|
||||
✖ row334 proxy refuses environment by message before writing (4.928207ms)
|
||||
✖ row334 proxy refuses install by message before writing (4.935225ms)
|
||||
✖ row334 proxy refuses user by message before writing (4.996881ms)
|
||||
✖ row334 proxy refuses group by message before writing (4.740792ms)
|
||||
✖ row334 proxy refuses ProtectSystem by message before writing (7.412574ms)
|
||||
✖ row334 proxy refuses ReadWritePaths by message before writing (5.039647ms)
|
||||
✖ row334 proxy refuses ProtectHome by message before writing (4.948113ms)
|
||||
✖ row334 proxy refuses PrivateTmp by message before writing (5.049564ms)
|
||||
✖ row334 proxy refuses token by message before writing (5.291145ms)
|
||||
✖ row334 proxy template missing or symlink refuses (4.77612ms)
|
||||
✖ row334 rendered ExecStart refuses wrong PIN and missing proxy with P1 status (5.907949ms)
|
||||
✖ row334 reinstall repins all three and refuses a proxy copy mismatch (5.706372ms)
|
||||
✖ row334 installed proxy without --proxy refuses and preserves all bytes (4.919106ms)
|
||||
✖ row334 dangling proxy symlink without flag refuses without deleting (127.81539ms)
|
||||
✖ row334 active proxy reports old copy and all three must restart together (5.181396ms)
|
||||
✖ row334 failed mosaic-proxy.service start leaves later units unstarted (4.827066ms)
|
||||
✖ row334 failed [email protected] start leaves later units unstarted (4.558348ms)
|
||||
✖ row334 installed proxy enforces hardening order and remaining common refusals (5.170683ms)
|
||||
✖ row334 proxy installed path rejects symlink and non-regular file (4.685001ms)
|
||||
ℹ tests 81
|
||||
ℹ suites 0
|
||||
ℹ pass 48
|
||||
ℹ fail 33
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 77995.928687
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:966:1
|
||||
✖ row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order (6.436602ms)
|
||||
AssertionError [ERR_ASSERTION]: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:969:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses continuation by message before writing (6.291734ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses not-setting by message before writing (5.588449ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses RestrictNamespaces by message before writing (5.71389ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses PrivateUsers by message before writing (5.34285ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses SystemCallFilter by message before writing (5.400994ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses PermissionsStartOnly by message before writing (5.443023ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses EnvironmentFile by message before writing (5.052075ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses AmbientCapabilities by message before writing (5.027453ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses CapabilityBoundingSet by message before writing (5.604853ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses privileged by message before writing (5.176325ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses direct-binary by message before writing (5.301058ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses nnp by message before writing (4.978268ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses cwd by message before writing (4.903056ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses environment by message before writing (4.928207ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses install by message before writing (4.935225ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses user by message before writing (4.996881ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses group by message before writing (4.740792ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses ProtectSystem by message before writing (7.412574ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses ReadWritePaths by message before writing (5.039647ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses ProtectHome by message before writing (4.948113ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses PrivateTmp by message before writing (5.049564ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1004:52
|
||||
✖ row334 proxy refuses token by message before writing (5.291145ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1007:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1012:1
|
||||
✖ row334 proxy template missing or symlink refuses (4.77612ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1014:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1019:1
|
||||
✖ row334 rendered ExecStart refuses wrong PIN and missing proxy with P1 status (5.907949ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
|
||||
4 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1021:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1044:1
|
||||
✖ row334 reinstall repins all three and refuses a proxy copy mismatch (5.706372ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
|
||||
4 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1046:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1055:1
|
||||
✖ row334 installed proxy without --proxy refuses and preserves all bytes (4.919106ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
|
||||
4 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1057:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1065:1
|
||||
✖ row334 dangling proxy symlink without flag refuses without deleting (127.81539ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 0: case2-unit: wrote /mnt/storage/scratch/tmp/QD3vp5/case2-unit-h4V4Zn/units/[email protected] and /mnt/storage/scratch/tmp/QD3vp5/case2-unit-h4V4Zn/units/mosaic-webui.service from copy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; enabled and started [email protected], then mosaic-webui.service
|
||||
|
||||
|
||||
0 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1068:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 0,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1072:1
|
||||
✖ row334 active proxy reports old copy and all three must restart together (5.181396ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
|
||||
4 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1074:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1080:73
|
||||
✖ row334 failed mosaic-proxy.service start leaves later units unstarted (4.827066ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1082:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1080:73
|
||||
✖ row334 failed [email protected] start leaves later units unstarted (4.558348ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1082:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1088:1
|
||||
✖ row334 installed proxy enforces hardening order and remaining common refusals (5.170683ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
|
||||
4 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1090:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/case2-unit.test.mjs:1103:1
|
||||
✖ row334 proxy installed path rejects symlink and non-regular file (4.685001ms)
|
||||
AssertionError [ERR_ASSERTION]: expected a refusal, got 4: usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS
|
||||
|
||||
|
||||
4 !== 1
|
||||
|
||||
at refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:292:10)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/rockerduck-row334/packages/cli/tests/case2-unit.test.mjs:1106:3)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 4,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -1694,3 +1694,6 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-10-24 | magica | row329 R2 #1827 | ASCII management-key fix and docs verified: package66/66,bus readers261/261,CLI readers726/726,config55/55; no required failures/skips, slot returned. Preparing authorized ff review candidate; old tests unchanged.
|
||||
2026-10-24 | Sage 1ef1e4f8 | row329 landing #1827 (mos-1634) | Gate GREEN 23:29:14Z unchanged, landed 61ebf2c34 pushed 23:31:27Z; #1827 closed c31650; queue note 4063, done 4064 (b24d917ec); DEFERRED N2/R2-N1 to 331, N1 to P7; Vikunja 631 filed, 237 c1109; 284 and 157 must rebase.
|
||||
2026-10-25 | Sage 1ef1e4f8 | row284 landing #1789 | Gate GREEN 00:09:04Z unchanged, landed ce0b6ed41 pushed 00:09:52Z; #1789 closed c31655; queue note 4071, done 4072 (7c8dc2033); DEFERRED Webby N1 to the next library read-path row; row 332 PROXY_CODEX_API ruled openai-responses (sage-4331), P3 holds for row 331.
|
||||
2026-10-25 — rockerduck — row334 (#1832), Case 2 proxy system unit — design/read phase; tests held for Sage CLEAR; queue start lock diagnosed.
|
||||
2026-10-25 — rockerduck — row334 (#1832), Case 2 proxy system unit — queue start rev4079; tests then implementation written, design accepted by Sage; verification held for CLEAR, P2 refusal-code check pending row331.
|
||||
2026-10-25 — rockerduck — row334 RED-only slot sage-4387 — both whole files against pre-change scripts: 74 older tests pass, 34 new expected failures; implementations restored, slot returned; GREEN/mutants held.
|
||||
@@ -490,3 +490,10 @@ test('C2-21: library install supplies chief identity and books publish for its r
|
||||
assert.ok(doc.roles.chief.identity?.gitea?.user);
|
||||
assert.notEqual(doc.roles.chief.identity?.gitea?.user, 'writer');
|
||||
});
|
||||
|
||||
|
||||
test('row334 install Next tells root to select proxy only when configured', t => {
|
||||
const x = world(t), r = install(x);
|
||||
assert.equal(r.code, 0, r.out);
|
||||
assert.match(r.out, /Next: root runs case2-unit.sh[^\n]+add --proxy when the config has a proxy section/);
|
||||
});
|
||||
@@ -7,10 +7,10 @@ import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { createServer } from "node:net";
|
||||
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
||||
import { chmodSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { businessDoc, writeJson } from "../../business/tests/helpers.mjs";
|
||||
import { businessDoc, systemConfig, writeJson } from "../../business/tests/helpers.mjs";
|
||||
import { binds, prepareRun, sandboxArgv, sandboxProblem } from "../../seat/src/sandbox.mjs";
|
||||
import { tmp } from "./helpers.mjs";
|
||||
import { CASE_SUDO_SESSION, recordedLivePids, killRecordedPids, fixtureGuard, cleanupFixture, pollFixtureGone, blockingQuerySource } from "./case-sudo-fixture.mjs";
|
||||
@@ -940,3 +940,171 @@ test("Vikunja 614 precheck: notifier config refuses missing, public, malformed a
|
||||
assert.match(r.out, /REFUSED notifier config:/, `${fault}: ${r.out}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Row334: render only into the scratch hub; fake systemctl never touches the host.
|
||||
const PROXY_TEMPLATE = join(REPO, 'packages/cli/systemd/mosaic-proxy.service.in');
|
||||
const proxyFile = h => join(h.units, 'mosaic-proxy.service');
|
||||
const proxyUnit = (h, options = {}) => unit(h, { ...options, extra: ['--proxy', ...(options.extra ?? [])] });
|
||||
|
||||
test('row334 no-proxy renders stay byte-identical to the pre-row renderer', t => {
|
||||
const h = hub(t);
|
||||
// Immutable committed baseline, executed only with the same scratch seams.
|
||||
const baseline = spawnSync('git', ['show', '78b69e1e6:scripts/case2-unit.sh'], { cwd: REPO, encoding: 'utf8' });
|
||||
assert.equal(baseline.status, 0, baseline.stderr);
|
||||
const old = script(join(h.root, 'baseline/scripts/case2-unit.sh'), baseline.stdout);
|
||||
const args = ['--user', 'mosaic', '--home', h.home, '--business', 'acme', '--copy', COPY_A, '--node', USR_NODE, '--', ...libraryFlags(h)];
|
||||
const r = spawnSync(old, args, { encoding: 'utf8', env: { PATH: `${h.bin}:${process.env.PATH}`, CASE2_UNIT_DIR: h.units,
|
||||
CASE2_SYSTEMCTL: join(h.bin, 'systemctl'), CASE2_GETENT: join(h.bin, 'getent'), CASE2_BUS_TEMPLATE: BUS_TEMPLATE, CASE2_WEBUI_TEMPLATE: WEBUI_TEMPLATE } });
|
||||
assert.equal(r.status, 0, r.stderr);
|
||||
const before = [read(busFile(h)), read(webuiFile(h))];
|
||||
assert.equal(unit(h).code, 0);
|
||||
assert.deepEqual([read(busFile(h)), read(webuiFile(h))], before);
|
||||
assert.equal(existsSync(proxyFile(h)), false);
|
||||
});
|
||||
|
||||
test('row334 proxy uses P1 runner, bus dependencies and proxy bus Console start order', t => {
|
||||
const h = hub(t);
|
||||
const r = proxyUnit(h, { profile: 'factory', flags: ['--port', '443', '--business', 'acme'], extra: ['--claude', TRUE_BIN] });
|
||||
assert.equal(r.code, 0, r.err);
|
||||
const proxy = read(proxyFile(h));
|
||||
const expected = read(PROXY_TEMPLATE).replaceAll('@REPO@', copyDir(h)).replaceAll('@PATH@', '/usr/bin:/usr/local/bin:/bin')
|
||||
.replaceAll('@NODE@', USR_NODE).replace(/^@CONFIG_ENV@\n/m, '').replace('WantedBy=default.target', 'WantedBy=multi-user.target')
|
||||
.replace('TimeoutStopSec=60', `TimeoutStopSec=60\nUser=mosaic\nGroup=mosaic\n${HARDENING(h).join('\n')}`);
|
||||
assert.equal(proxy, expected);
|
||||
assert.equal(statSync(proxyFile(h)).mode & 0o777, 0o644);
|
||||
assert.match(read(busFile(h)), /Description=[^\n]+\nWants=mosaic-proxy.service\nAfter=mosaic-proxy.service\n/);
|
||||
assert.deepEqual(lines(proxy, 'ExecStart'), [`${USR_NODE} ${copyDir(h)}/packages/model-proxy/src/run.mjs`]);
|
||||
assert.doesNotMatch(proxy, /EnvironmentFile=|Capabilities=|CapabilityBoundingSet=|MOSAIC_CLAUDE_BIN/);
|
||||
assert.doesNotMatch(read(webuiFile(h)), /Wants=|After=/);
|
||||
assert.deepEqual(read(h.log).trim().split('\n'), [
|
||||
'is-active --quiet mosaic-proxy.service', 'is-active --quiet [email protected]', 'is-active --quiet mosaic-webui.service',
|
||||
'daemon-reload', 'enable mosaic-proxy.service [email protected] mosaic-webui.service',
|
||||
'start mosaic-proxy.service', 'start [email protected]', 'start mosaic-webui.service',
|
||||
]);
|
||||
});
|
||||
|
||||
const proxyRefusals = [
|
||||
['continuation', s => s.replace('Type=simple', 'Type=simple' + String.fromCharCode(92)), /a line continues onto the next/],
|
||||
['not-setting', s => s.replace('Type=simple', 'not a setting'), /neither a section/],
|
||||
...['RestrictNamespaces', 'PrivateUsers', 'SystemCallFilter', 'PermissionsStartOnly', 'EnvironmentFile', 'AmbientCapabilities', 'CapabilityBoundingSet'].map(k =>
|
||||
[k, s => s.replace('Type=simple', `Type=simple\n${k}=yes`), new RegExp(`refusing ${k}=`)]),
|
||||
['privileged', s => s.replace('ExecStart=', 'ExecStart=+'), /Exec line that runs with full privileges/],
|
||||
['direct-binary', s => s.replace('packages/model-proxy/src/run.mjs', 'opt/cli-proxy-api'), /ExecStart= must be exactly once/],
|
||||
['nnp', s => s.replace('NoNewPrivileges=yes', 'NoNewPrivileges=no'), /NoNewPrivileges= must be exactly once/],
|
||||
['cwd', s => s.replace('WorkingDirectory=@REPO@', 'WorkingDirectory=/elsewhere'), /WorkingDirectory= must be exactly once/],
|
||||
['environment', s => s.replace('Environment=PATH=@PATH@', 'Environment=PATH=/elsewhere'), /Environment= lines must be exactly/],
|
||||
['install', s => s.replace('WantedBy=default.target', 'WantedBy=other.target'), /WantedBy= must be exactly once/],
|
||||
['user', s => s.replace('Type=simple', 'Type=simple\nUser=other'), /User= must be exactly once/],
|
||||
['group', s => s.replace('Type=simple', 'Type=simple\nGroup=other'), /Group= must be exactly once/],
|
||||
...['ProtectSystem', 'ReadWritePaths', 'ProtectHome', 'PrivateTmp'].map(k =>
|
||||
[k, s => s.replace('Type=simple', `Type=simple\n${k}=other`), new RegExp(`${k}= must be exactly once`)]),
|
||||
['token', s => s.replace('Type=simple', 'Type=simple\nX-Note=@UNKNOWN@'), /a token is left after the render/],
|
||||
];
|
||||
for (const [name, edit, message] of proxyRefusals) test(`row334 proxy refuses ${name} by message before writing`, t => {
|
||||
const h = hub(t);
|
||||
const file = template(h, PROXY_TEMPLATE, `${name}.in`, edit);
|
||||
refuses(proxyUnit(h, { env: { CASE2_PROXY_TEMPLATE: file } }), message);
|
||||
assert.deepEqual(readdirSync(h.units), []);
|
||||
assert.equal(existsSync(h.log), false);
|
||||
});
|
||||
|
||||
test('row334 proxy template missing or symlink refuses', t => {
|
||||
const h = hub(t), missing = join(h.root, 'missing.in');
|
||||
refuses(proxyUnit(h, { env: { CASE2_PROXY_TEMPLATE: missing } }), /template missing or not a regular file/);
|
||||
symlinkSync(PROXY_TEMPLATE, missing);
|
||||
refuses(proxyUnit(h, { env: { CASE2_PROXY_TEMPLATE: missing } }), /template missing or not a regular file/);
|
||||
});
|
||||
|
||||
test('row334 rendered ExecStart refuses wrong PIN and missing proxy with P1 status', t => {
|
||||
const h = hub(t);
|
||||
assert.equal(proxyUnit(h).code, 0);
|
||||
const copy = copyDir(h);
|
||||
cpSync(join(REPO, 'packages/model-proxy'), join(copy, 'packages/model-proxy'), { recursive: true });
|
||||
cpSync(join(REPO, 'scripts/mosaic-config.mjs'), join(copy, 'scripts/mosaic-config.mjs'));
|
||||
const config = systemConfig(join(h.root, 'runner'));
|
||||
const doc = JSON.parse(read(config));
|
||||
mkdirSync(doc.dataRoot, { recursive: true, mode: 0o700 });
|
||||
const pin = JSON.parse(read(join(REPO, 'packages/model-proxy/PIN')));
|
||||
const binary = join(doc.dataRoot, 'opt/cliproxyapi', pin.version, 'cli-proxy-api');
|
||||
script(binary, `echo unexpected-spawn > '${h.root}/spawned'`);
|
||||
writeJson(join(dirname(config), 'proxy.json'), { port: 18541, binary });
|
||||
const argv = lines(read(proxyFile(h)), 'ExecStart')[0].split(' ');
|
||||
const run = () => spawnSync(argv[0], argv.slice(1), { encoding: 'utf8', env: { PATH: '/usr/bin:/bin', HOME: h.home, MOSAIC_CONFIG: config } });
|
||||
const bad = run();
|
||||
assert.equal(bad.status, 3, bad.stderr);
|
||||
assert.equal(bad.stderr, 'proxy-install-invalid\n');
|
||||
assert.equal(existsSync(join(h.root, 'spawned')), false);
|
||||
rmSync(join(dirname(config), 'proxy.json'));
|
||||
const absent = run();
|
||||
assert.equal(absent.status, 3, absent.stderr);
|
||||
assert.equal(absent.stderr, 'proxy-not-configured\n');
|
||||
});
|
||||
|
||||
test('row334 reinstall repins all three and refuses a proxy copy mismatch', t => {
|
||||
const h = hub(t);
|
||||
assert.equal(proxyUnit(h).code, 0);
|
||||
assert.equal(proxyUnit(h, { copy: COPY_B }).code, 0);
|
||||
for (const file of [proxyFile(h), busFile(h), webuiFile(h)]) assert.match(read(file), new RegExp(copyDir(h, COPY_B)));
|
||||
writeFileSync(proxyFile(h), read(proxyFile(h)).replaceAll(copyDir(h, COPY_B), copyDir(h)));
|
||||
const before = [proxyFile(h), busFile(h), webuiFile(h)].map(read);
|
||||
refuses(proxyUnit(h), /installed units name different copies/);
|
||||
assert.deepEqual([proxyFile(h), busFile(h), webuiFile(h)].map(read), before);
|
||||
});
|
||||
|
||||
test('row334 installed proxy without --proxy refuses and preserves all bytes', t => {
|
||||
const h = hub(t);
|
||||
assert.equal(proxyUnit(h).code, 0);
|
||||
const files = [proxyFile(h), busFile(h), webuiFile(h)], before = files.map(read);
|
||||
writeFileSync(h.log, '');
|
||||
refuses(unit(h), /installed mosaic-proxy.service requires --proxy/);
|
||||
assert.deepEqual(files.map(read), before);
|
||||
assert.equal(read(h.log), '');
|
||||
});
|
||||
|
||||
test('row334 dangling proxy symlink without flag refuses without deleting', t => {
|
||||
const h = hub(t);
|
||||
symlinkSync(join(h.root, 'absent'), proxyFile(h));
|
||||
refuses(unit(h), /installed mosaic-proxy.service requires --proxy/);
|
||||
assert.equal(readdirSync(h.units).includes('mosaic-proxy.service'), true);
|
||||
});
|
||||
|
||||
test('row334 active proxy reports old copy and all three must restart together', t => {
|
||||
const h = hub(t);
|
||||
assert.equal(proxyUnit(h).code, 0);
|
||||
const r = proxyUnit(h, { copy: COPY_B, env: { CASE2_TEST_ACTIVE: 'mosaic-proxy.service [email protected] mosaic-webui.service' } });
|
||||
assert.equal(r.code, 0, r.err);
|
||||
assert.match(r.out, /mosaic-proxy.service was already running/);
|
||||
assert.match(r.out, /restart mosaic-proxy.service, [email protected] and mosaic-webui.service together/);
|
||||
});
|
||||
for (const name of ['mosaic-proxy.service', '[email protected]']) test(`row334 failed ${name} start leaves later units unstarted`, t => {
|
||||
const h = hub(t);
|
||||
refuses(proxyUnit(h, { env: { CASE2_TEST_FAIL: `start ${name}` } }), /systemctl start .* failed/);
|
||||
assert.doesNotMatch(read(h.log), /start mosaic-webui.service/);
|
||||
if (name === 'mosaic-proxy.service') assert.doesNotMatch(read(h.log), /start [email protected]/);
|
||||
});
|
||||
|
||||
|
||||
test('row334 installed proxy enforces hardening order and remaining common refusals', t => {
|
||||
const h = hub(t);
|
||||
assert.equal(proxyUnit(h).code, 0);
|
||||
const original = read(proxyFile(h));
|
||||
for (const [edit, message] of [
|
||||
[s => s.replace('ProtectSystem=strict\nReadWritePaths=' + h.home, 'ReadWritePaths=' + h.home + '\nProtectSystem=strict'), /hardening lines must be in the order/],
|
||||
[s => s.replace('WorkingDirectory=' + copyDir(h), 'WorkingDirectory=%h'), /names no copy/],
|
||||
[s => s.replace('Type=simple', 'Type=simple\nX-Note=%h'), /%h is left; a system unit/],
|
||||
[s => s.replace('RestartSec=15', 'RestartSec=16'), /differs from the render in more than the copy id/],
|
||||
]) {
|
||||
writeFileSync(proxyFile(h), edit(original));
|
||||
refuses(proxyUnit(h), message);
|
||||
}
|
||||
});
|
||||
|
||||
test('row334 proxy installed path rejects symlink and non-regular file', t => {
|
||||
const h = hub(t);
|
||||
symlinkSync(join(h.root, 'absent'), proxyFile(h));
|
||||
refuses(proxyUnit(h), /is a symlink/);
|
||||
rmSync(proxyFile(h));
|
||||
mkdirSync(proxyFile(h));
|
||||
refuses(proxyUnit(h), /is not a regular file/);
|
||||
});
|
||||
@@ -228,7 +228,7 @@ fi
|
||||
say "business $BUSINESS validates"
|
||||
|
||||
# 8. What comes next. Root's steps run from root's own checkout.
|
||||
say "done. Next: root runs case2-unit.sh --copy $COMMIT --business $BUSINESS (design 10 step 2c), then 2d and 3."
|
||||
say "done. Next: root runs case2-unit.sh --copy $COMMIT --business $BUSINESS (add --proxy when the config has a proxy section; design 10 step 2c), then 2d and 3."
|
||||
if [ "$PROFILE" = library ]; then
|
||||
say "Then Jason places the library token in $SLOT (step 4), and <user> runs case2-precheck.sh --business $BUSINESS --library-status (step 4a)."
|
||||
fi
|
||||
|
||||
+65
-22
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Case 2 row C2-13a (design 9.3, 10 step 2c): render and install the two
|
||||
# system units a hub runs as, [email protected] (enabled as
|
||||
# system units a hub runs as (plus P1's proxy with --proxy), [email protected] (enabled as
|
||||
# mosaic-bus@<business>.service) and mosaic-webui.service, from the existing
|
||||
# templates, which it doesn't change.
|
||||
#
|
||||
# case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH
|
||||
# [--profile factory|library] [--pm] [--claude PATH]
|
||||
# [--profile factory|library] [--proxy] [--pm] [--claude PATH]
|
||||
# -- SERVE FLAGS
|
||||
#
|
||||
# Root runs it from a root-owned checkout of the reviewed commit outside
|
||||
@@ -31,8 +31,8 @@
|
||||
# --pm and --claude.
|
||||
#
|
||||
# On a re-install the installed units must equal the render in everything
|
||||
# but the copy id, and both must name one copy, so a re-pin or rollback
|
||||
# re-renders the pair together. A unit that was already running stays on its
|
||||
# but the copy id, and all selected units must name one copy, so a re-pin or rollback
|
||||
# re-renders them together. A unit that was already running stays on its
|
||||
# old copy, since `systemctl start` leaves it alone; the script says so and
|
||||
# that the bus and Console must restart together, and restarts nothing.
|
||||
#
|
||||
@@ -41,6 +41,7 @@
|
||||
# the root and root-owned checks apply only there
|
||||
# CASE2_SYSTEMCTL the systemctl command, default systemctl
|
||||
# CASE2_GETENT the getent command, default getent
|
||||
# CASE2_PROXY_TEMPLATE default packages/cli/systemd/mosaic-proxy.service.in
|
||||
# CASE2_BUS_TEMPLATE default packages/cli/systemd/mosaic-bus.service.in
|
||||
# CASE2_WEBUI_TEMPLATE default packages/webui/systemd/mosaic-webui.service.in
|
||||
#
|
||||
@@ -53,10 +54,12 @@ SYSTEMCTL="${CASE2_SYSTEMCTL:-systemctl}"
|
||||
GETENT="${CASE2_GETENT:-getent}"
|
||||
BUS_TEMPLATE="${CASE2_BUS_TEMPLATE:-$REPO/packages/cli/systemd/mosaic-bus.service.in}"
|
||||
WEBUI_TEMPLATE="${CASE2_WEBUI_TEMPLATE:-$REPO/packages/webui/systemd/mosaic-webui.service.in}"
|
||||
PROXY_TEMPLATE="${CASE2_PROXY_TEMPLATE:-$REPO/packages/cli/systemd/mosaic-proxy.service.in}"
|
||||
PROXY_UNIT=mosaic-proxy.service
|
||||
BUS_UNIT=[email protected]
|
||||
WEBUI_UNIT=mosaic-webui.service
|
||||
|
||||
usage() { echo 'usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS' >&2; exit 4; }
|
||||
usage() { echo 'usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--proxy] [--pm] [--claude PATH] -- SERVE FLAGS' >&2; exit 4; }
|
||||
die() { echo "case2-unit: $*" >&2; exit 1; }
|
||||
# A value the unit carries as itself: no space, %, $, quote, backslash, & or |.
|
||||
plain() { case "$1" in *[!A-Za-z0-9/._+@-]*) return 1 ;; esac; }
|
||||
@@ -67,7 +70,7 @@ abs_plain() {
|
||||
[ "$1" = / ] || [ "${1%/}" = "$1" ] || die "$2 must not end in /: $1"
|
||||
}
|
||||
|
||||
PROFILE=library USER_NAME='' HOME_DIR='' BUSINESS='' COPY_ID='' NODE='' PM=0 CLAUDE='' CLAUDE_SET=0
|
||||
PROFILE=library USER_NAME='' HOME_DIR='' BUSINESS='' COPY_ID='' NODE='' PROXY=0 PM=0 CLAUDE='' CLAUDE_SET=0
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--profile|--user|--home|--business|--copy|--node|--claude)
|
||||
@@ -78,6 +81,7 @@ while [ $# -gt 0 ]; do
|
||||
--claude) CLAUDE="$2"; CLAUDE_SET=1 ;;
|
||||
esac
|
||||
shift 2 ;;
|
||||
--proxy) PROXY=1; shift ;;
|
||||
--pm) PM=1; shift ;;
|
||||
--) shift; break ;;
|
||||
*) usage ;;
|
||||
@@ -150,6 +154,7 @@ fi
|
||||
# The checkout the templates come from, when installing for real.
|
||||
if [ "$UNIT_DIR" = "$DEFAULT_UNIT_DIR" ]; then
|
||||
case "$REPO/" in "$HOME_DIR"/*) die "run this from a root-owned checkout outside $HOME_DIR, not $REPO" ;; esac
|
||||
[ "$PROXY" = 0 ] || root_only "$PROXY_TEMPLATE" 'proxy template'
|
||||
root_only "$BUS_TEMPLATE" 'bus template'
|
||||
root_only "$WEBUI_TEMPLATE" 'Console template'
|
||||
fi
|
||||
@@ -449,7 +454,10 @@ render() {
|
||||
fi
|
||||
if printf '%s\n' "$text" | grep -q '%h'; then die "$template: %h is left after the render"; fi
|
||||
local add="User=$USER_NAME\nGroup=$GROUP\n${HARDENING//$'\n'/\\n}\n"
|
||||
[ "$kind" = bus ] || add="$add${CAPS//$'\n'/\\n}\n"
|
||||
[ "$kind" != webui ] || add="$add${CAPS//$'\n'/\\n}\n"
|
||||
if [ "$kind" = bus ] && [ "$PROXY" = 1 ]; then
|
||||
text="$(printf '%s\n' "$text" | sed "/^Description=/a Wants=$PROXY_UNIT\\nAfter=$PROXY_UNIT")"
|
||||
fi
|
||||
# The added lines go at the end of [Service], before its trailing blank
|
||||
# lines. In [Install], WantedBy=multi-user.target replaces
|
||||
# WantedBy=default.target in both units, so both start at boot (Q189).
|
||||
@@ -517,7 +525,7 @@ check_unit() {
|
||||
one WorkingDirectory "$copy"
|
||||
# Environment= lines: PATH, and MOSAIC_CLAUDE_BIN only with --claude.
|
||||
local envs want_envs="[Service] PATH=$UNIT_PATH"
|
||||
[ "$kind" = webui ] || [ -z "$CLAUDE_ENV" ] || want_envs="$want_envs
|
||||
[ "$kind" != bus ] || [ -z "$CLAUDE_ENV" ] || want_envs="$want_envs
|
||||
[Service] MOSAIC_CLAUDE_BIN=$CLAUDE"
|
||||
envs="$(printf '%s\n' "$s" | awk -F'\t' '$2 == "Environment" { print $1 "\t" $3 }')"
|
||||
[ "$envs" = "$want_envs" ] || die "$what: the Environment= lines must be exactly: $(printf '%s' "$want_envs" | cut -f2 | tr '\n' ' ')"
|
||||
@@ -537,6 +545,11 @@ check_unit() {
|
||||
one EnvironmentFile "$ENV_FILE"
|
||||
none AmbientCapabilities
|
||||
none CapabilityBoundingSet
|
||||
elif [ "$kind" = proxy ]; then
|
||||
one ExecStart "$NODE $copy/packages/model-proxy/src/run.mjs"
|
||||
none EnvironmentFile
|
||||
none AmbientCapabilities
|
||||
none CapabilityBoundingSet
|
||||
else
|
||||
one ExecStart "$NODE $copy/packages/webui/src/cli.mjs serve $FLAGS"
|
||||
none EnvironmentFile
|
||||
@@ -554,7 +567,16 @@ check_unit webui "$WEBUI_TEXT" "rendered $WEBUI_UNIT" "$COPY"
|
||||
# copy, and each equals the render once its copy id is the new one.
|
||||
[ -d "$UNIT_DIR" ] && [ ! -L "$UNIT_DIR" ] || die "no unit directory $UNIT_DIR"
|
||||
installed_ids=''
|
||||
for pair in "bus:$BUS_UNIT:BUS_TEXT" "webui:$WEBUI_UNIT:WEBUI_TEXT"; do
|
||||
PAIRS=("bus:$BUS_UNIT:BUS_TEXT" "webui:$WEBUI_UNIT:WEBUI_TEXT")
|
||||
if [ "$PROXY" = 1 ]; then
|
||||
PROXY_TEXT="$(render proxy "$PROXY_TEMPLATE")" || exit 1
|
||||
check_unit proxy "$PROXY_TEXT" "rendered $PROXY_UNIT" "$COPY"
|
||||
PAIRS=("proxy:$PROXY_UNIT:PROXY_TEXT" "${PAIRS[@]}")
|
||||
else
|
||||
[ ! -e "$UNIT_DIR/$PROXY_UNIT" ] && [ ! -L "$UNIT_DIR/$PROXY_UNIT" ] \
|
||||
|| die "installed $PROXY_UNIT requires --proxy; nothing was changed"
|
||||
fi
|
||||
for pair in "${PAIRS[@]}"; do
|
||||
IFS=: read -r kind name var <<<"$pair"
|
||||
path="$UNIT_DIR/$name"
|
||||
[ ! -L "$path" ] || die "refusing: $path is a symlink"
|
||||
@@ -569,13 +591,17 @@ for pair in "bus:$BUS_UNIT:BUS_TEXT" "webui:$WEBUI_UNIT:WEBUI_TEXT"; do
|
||||
installed_ids="$installed_ids $old"
|
||||
done
|
||||
read -r -a ids <<<"$installed_ids"
|
||||
[ "${#ids[@]}" -lt 2 ] || [ "${ids[0]}" = "${ids[1]}" ] || die "the installed units name different copies, ${ids[0]} and ${ids[1]}"
|
||||
for old in "${ids[@]}"; do
|
||||
[ "$old" = "${ids[0]}" ] || die "the installed units name different copies, ${ids[0]} and $old"
|
||||
done
|
||||
|
||||
# `systemctl start` leaves a running unit on the copy it started from, so
|
||||
# note which units run before the write (row 195 owns the restart).
|
||||
BUS_NAME="mosaic-bus@$BUSINESS.service"
|
||||
NAMES=("$BUS_NAME" "$WEBUI_UNIT")
|
||||
[ "$PROXY" = 0 ] || NAMES=("$PROXY_UNIT" "${NAMES[@]}")
|
||||
running=''
|
||||
for name in "$BUS_NAME" "$WEBUI_UNIT"; do
|
||||
for name in "${NAMES[@]}"; do
|
||||
if "$SYSTEMCTL" is-active --quiet "$name"; then running="$running $name"; fi
|
||||
done
|
||||
|
||||
@@ -588,23 +614,40 @@ write_unit() {
|
||||
[ "$(id -u)" != 0 ] || chown root:root "$tmp"
|
||||
mv -fT -- "$tmp" "$UNIT_DIR/$name"
|
||||
}
|
||||
write_unit "$BUS_UNIT" "$BUS_TEXT"
|
||||
write_unit "$WEBUI_UNIT" "$WEBUI_TEXT"
|
||||
# Enable both, then start the bus, then the Console (design 10 step 2c).
|
||||
# serve calls the bus per request, so the units carry no After= or Wants=.
|
||||
for pair in "${PAIRS[@]}"; do
|
||||
IFS=: read -r kind name var <<<"$pair"
|
||||
write_unit "$name" "${!var}"
|
||||
done
|
||||
# Enable all selected units before starting proxy (if selected), bus, Console.
|
||||
# Only the bus depends on the proxy; the Console calls the bus per request.
|
||||
"$SYSTEMCTL" daemon-reload || die "systemctl daemon-reload failed; the units are written, nothing is enabled or started"
|
||||
"$SYSTEMCTL" enable "$BUS_NAME" "$WEBUI_UNIT" || die "systemctl enable $BUS_NAME $WEBUI_UNIT failed; nothing is started"
|
||||
"$SYSTEMCTL" enable "${NAMES[@]}" || die "systemctl enable ${NAMES[*]} failed; nothing is started"
|
||||
if [ "$PROXY" = 1 ]; then
|
||||
"$SYSTEMCTL" start "$PROXY_UNIT" || die "systemctl start $PROXY_UNIT failed; the bus and Console are not started"
|
||||
fi
|
||||
"$SYSTEMCTL" start "$BUS_NAME" || die "systemctl start $BUS_NAME failed; the Console is not started"
|
||||
"$SYSTEMCTL" start "$WEBUI_UNIT" || die "systemctl start $WEBUI_UNIT failed; the bus is running"
|
||||
if [ -z "$running" ]; then
|
||||
echo "case2-unit: wrote $UNIT_DIR/$BUS_UNIT and $UNIT_DIR/$WEBUI_UNIT from copy $COPY_ID; enabled and started $BUS_NAME, then $WEBUI_UNIT"
|
||||
exit 0
|
||||
if [ "$PROXY" = 1 ]; then
|
||||
echo "case2-unit: wrote $UNIT_DIR/$PROXY_UNIT, $UNIT_DIR/$BUS_UNIT and $UNIT_DIR/$WEBUI_UNIT from copy $COPY_ID and enabled all three"
|
||||
if [ -z "$running" ]; then
|
||||
echo "case2-unit: enabled and started $PROXY_UNIT, then $BUS_NAME, then $WEBUI_UNIT"
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
if [ -z "$running" ]; then
|
||||
echo "case2-unit: wrote $UNIT_DIR/$BUS_UNIT and $UNIT_DIR/$WEBUI_UNIT from copy $COPY_ID; enabled and started $BUS_NAME, then $WEBUI_UNIT"
|
||||
exit 0
|
||||
fi
|
||||
echo "case2-unit: wrote $UNIT_DIR/$BUS_UNIT and $UNIT_DIR/$WEBUI_UNIT from copy $COPY_ID and enabled both"
|
||||
fi
|
||||
echo "case2-unit: wrote $UNIT_DIR/$BUS_UNIT and $UNIT_DIR/$WEBUI_UNIT from copy $COPY_ID and enabled both"
|
||||
for name in "$BUS_NAME" "$WEBUI_UNIT"; do
|
||||
for name in "${NAMES[@]}"; do
|
||||
case " $running " in
|
||||
*" $name "*) echo "case2-unit: $name was already running, so it is still on ${ids[0]:+copy }${ids[0]:-the copy it started from}, not copy $COPY_ID" ;;
|
||||
*) echo "case2-unit: started $name on copy $COPY_ID" ;;
|
||||
esac
|
||||
done
|
||||
echo "case2-unit: restart $BUS_NAME and $WEBUI_UNIT together to put both on copy $COPY_ID; until then they may run different copies"
|
||||
if [ "$PROXY" = 1 ]; then
|
||||
echo "case2-unit: restart $PROXY_UNIT, $BUS_NAME and $WEBUI_UNIT together to put all three on copy $COPY_ID; until then they may run different copies"
|
||||
else
|
||||
echo "case2-unit: restart $BUS_NAME and $WEBUI_UNIT together to put both on copy $COPY_ID; until then they may run different copies"
|
||||
fi
|
||||
Reference in new issue
Block a user