review(row54): round 1 packet, changes (darkwing)

Review of #1543 candidate dba2429e: gate green with Docker, 31 mutants
(26 killed), Node, import and Chromium probes. Posted as comment 27186.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-10 15:47:54 -05:00
co-authored by Claude Opus 5.5
parent 712baf49ba
commit 8a3ded5335
100 changed files with 4409 additions and 0 deletions
@@ -0,0 +1,14 @@
3db6ee2a4d4b05de6768709da0c21c6815c0d7899fb9389d659444828264275d packages/queue/README.md
352d96c5cc02d01e5d524c1a9106985c0f0a12b7312d7de157d9bf733dead4d5 packages/queue/src/store.mjs
b44f6f8b07cad4f35dfc92bee2c762845052b2b79f990f7cd5f590efd2b05544 packages/queue/tests/write.test.mjs
cd146196c98d8492a2b7f0c9bc046db0a69bd8c4539de8454563bd91cda24d9b packages/webui/README.md
07d2c06523dd89f82c9ea14757852b16434f7207df57d7dfcbc58425e6e75c1d packages/webui/src/cli.mjs
5a611e2339fa9e31c489d24460f82852ca2bc892f07265ffdc5eca03b579541b packages/webui/src/public/app.js
0e1305c1cfb27617c8df66c77a5bf0fca822d65b9da8f75aadc27e11fdf7c176 packages/webui/src/public/bus.js
4b10c9522f7d2061853abbc98b1d8425791ebde0f9a3af2ef7f19119c23c5cb6 packages/webui/src/queue-read.mjs
1e7505a9cb2509d97dd671399aca8bb5f68ea72320c8f6cb0648c8f0eed9db31 packages/webui/src/reads.mjs
8121915343ad5000cf66d474d9e4a994ce6c44ff1977822a067ad5ffd2fbbf29 packages/webui/src/serve.mjs
f1896e52c29ab4d211a946a70c0d94cecedfb4a0099f8dc37b42e10f55377bc8 packages/webui/tests/reads-fixture.mjs
9eaad7bdf3159e68715b2f63ba3a7d03746ea87ff9ae42cb36b2205639b39f44 packages/webui/tests/reads.test.mjs
85f3bd247e6f99da717a51d71db13b65d511fffa8f0232ecbafa6c5000430c4e packages/webui/tests/shell.test.mjs
c3b23f81dfc5a88e1a0865dbabfc8168ba4a0749cc171bb21615e2c113b46e57 packages/webui/tests/views.test.mjs
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (7419.429981ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (7680.637771ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (13386.420409ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (1345.265196ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (218.103567ms)
✔ bus refusals map to statuses and never carry a path or a stack (97.857933ms)
✔ humanCall speaks the human transport protocol without blocking the server (1828.410005ms)
✔ busReader takes --business, else the live bus host, else refuses (1012.495009ms)
✔ humanCall kills a transport that runs past its timeout (531.174294ms)
✔ serve refuses a --business value that is not a business id (186.842156ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (7047.334736ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (6246.922456ms)
✔ conversation view: a newer session with no readable history keeps the marker (4389.254684ms)
✔ conversation view: seats without history say so and offer no reply (828.95027ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (7468.513781ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (57162.25451ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.999965ms)
✔ credential state from the date alone, as the bus states it (0.448513ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2070.482221ms)
✔ queue: a refused read fails closed with the store's text, redacted (3676.131063ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (9.417875ms)
✔ business: missing or invalid file refuses with the module's text, redacted (36.892944ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (12.873755ms)
✔ settings: release, board, loopback address, notifier binding name only (1249.123547ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (7322.498686ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (24203.485317ms)
✔ loopback host and board origin fail closed (9.007024ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (160.476511ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (192.38851ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1444.12178ms)
✔ tokens.css and icons.svg are byte copies of docs/design (18.747124ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (27.71902ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.299374ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (10524.470842ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (6111.154323ms)
✔ Queue, Business and Settings in their five states, with no move button (7650.945522ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (6902.541038ms)
✔ the server log names no config path when the system config refuses (212.908855ms)
ℹ tests 38
ℹ suites 0
ℹ pass 38
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 57595.214628
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5839.746242ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (7325.85568ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7831.155187ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (347.66314ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (173.94975ms)
✔ bus refusals map to statuses and never carry a path or a stack (55.724887ms)
✔ humanCall speaks the human transport protocol without blocking the server (2498.033109ms)
✔ busReader takes --business, else the live bus host, else refuses (207.637636ms)
✔ humanCall kills a transport that runs past its timeout (529.179874ms)
✔ serve refuses a --business value that is not a business id (209.950815ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (6068.495613ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (4283.951068ms)
✔ conversation view: a newer session with no readable history keeps the marker (11919.124163ms)
✔ conversation view: seats without history say so and offer no reply (5634.883862ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5434.844012ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56209.30624ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.036858ms)
✔ credential state from the date alone, as the bus states it (0.290985ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1592.371831ms)
✔ queue: a refused read fails closed with the store's text, redacted (1299.104554ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (15.730037ms)
✔ business: missing or invalid file refuses with the module's text, redacted (126.356697ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (7.833237ms)
✔ settings: release, board, loopback address, notifier binding name only (1264.596881ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (6807.764916ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (27296.723771ms)
✔ loopback host and board origin fail closed (9.197514ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (133.750629ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (132.277584ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1378.640156ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.363383ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.500626ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.773941ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (10675.98392ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (11032.844508ms)
✔ Queue, Business and Settings in their five states, with no move button (7573.153409ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (10210.286256ms)
✔ the server log names no config path when the system config refuses (231.763576ms)
ℹ tests 38
ℹ suites 0
ℹ pass 38
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56651.663371
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4286.355057ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4252.126397ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6854.737148ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (397.293903ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (892.224846ms)
✔ bus refusals map to statuses and never carry a path or a stack (305.290964ms)
✔ humanCall speaks the human transport protocol without blocking the server (2499.061199ms)
✔ busReader takes --business, else the live bus host, else refuses (229.570056ms)
✔ humanCall kills a transport that runs past its timeout (530.950102ms)
✔ serve refuses a --business value that is not a business id (202.430622ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4080.000342ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2902.704951ms)
✔ conversation view: a newer session with no readable history keeps the marker (1725.712723ms)
✔ conversation view: seats without history say so and offer no reply (1039.767926ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4243.701844ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55322.312724ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.003187ms)
✔ credential state from the date alone, as the bus states it (0.266493ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2653.500347ms)
✔ queue: a refused read fails closed with the store's text, redacted (1455.707416ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (15.527602ms)
✔ business: missing or invalid file refuses with the module's text, redacted (16.109401ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (20.501815ms)
✔ settings: release, board, loopback address, notifier binding name only (918.620874ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4178.952002ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22508.319055ms)
✔ loopback host and board origin fail closed (8.615004ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (223.229573ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (162.65381ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1995.402535ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.228437ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.745638ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.218172ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7533.989319ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3083.283406ms)
✔ Queue, Business and Settings in their five states, with no move button (5067.081944ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2526.450229ms)
✔ the server log names no config path when the system config refuses (319.428027ms)
ℹ tests 38
ℹ suites 0
ℹ pass 38
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55822.82058
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4724.290753ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4722.707574ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6690.234269ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (447.173015ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (132.577517ms)
✔ bus refusals map to statuses and never carry a path or a stack (60.410292ms)
✔ humanCall speaks the human transport protocol without blocking the server (2308.918023ms)
✔ busReader takes --business, else the live bus host, else refuses (492.048171ms)
✔ humanCall kills a transport that runs past its timeout (530.459549ms)
✔ serve refuses a --business value that is not a business id (298.626669ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4817.249057ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2453.444874ms)
✔ conversation view: a newer session with no readable history keeps the marker (1271.507ms)
✔ conversation view: seats without history say so and offer no reply (893.14175ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4719.168513ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55313.079262ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.890544ms)
✔ credential state from the date alone, as the bus states it (0.298797ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1374.358329ms)
✔ queue: a refused read fails closed with the store's text, redacted (2442.351205ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (12.412173ms)
✔ business: missing or invalid file refuses with the module's text, redacted (27.071614ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (13.071699ms)
✔ settings: release, board, loopback address, notifier binding name only (1005.23027ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5012.615125ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (24057.460238ms)
✔ loopback host and board origin fail closed (7.811045ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (117.698074ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (216.386573ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1189.511684ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.333266ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.447183ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.816484ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8807.266513ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2729.494701ms)
✔ Queue, Business and Settings in their five states, with no move button (5756.971116ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1679.248145ms)
✔ the server log names no config path when the system config refuses (265.944172ms)
ℹ tests 38
ℹ suites 0
ℹ pass 38
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55866.804231
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5394.913107ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5618.203116ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5584.792046ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (306.257569ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (83.303554ms)
✔ bus refusals map to statuses and never carry a path or a stack (49.559503ms)
✔ humanCall speaks the human transport protocol without blocking the server (2194.013714ms)
✔ busReader takes --business, else the live bus host, else refuses (221.629238ms)
✔ humanCall kills a transport that runs past its timeout (688.121733ms)
✔ serve refuses a --business value that is not a business id (173.917439ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5420.340201ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2013.951367ms)
✔ conversation view: a newer session with no readable history keeps the marker (1761.145701ms)
✔ conversation view: seats without history say so and offer no reply (985.9ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5585.269091ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53598.657341ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.745622ms)
✔ credential state from the date alone, as the bus states it (0.213752ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1200.796259ms)
✔ queue: a refused read fails closed with the store's text, redacted (1035.157402ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (27.175479ms)
✔ business: missing or invalid file refuses with the module's text, redacted (67.165167ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (24.32399ms)
✔ settings: release, board, loopback address, notifier binding name only (2165.271905ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5432.822289ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22935.878861ms)
✔ loopback host and board origin fail closed (7.111826ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (113.133151ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (86.62178ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1032.056695ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.108678ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.079698ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.572088ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8084.586836ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3050.371572ms)
✔ Queue, Business and Settings in their five states, with no move button (5506.865857ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2213.02114ms)
✔ the server log names no config path when the system config refuses (323.726956ms)
ℹ tests 38
ℹ suites 0
ℹ pass 38
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54013.508053
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4967.867343ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5054.379982ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7767.995099ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (351.660239ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (229.866655ms)
✔ bus refusals map to statuses and never carry a path or a stack (248.529647ms)
✔ humanCall speaks the human transport protocol without blocking the server (2443.280612ms)
✔ busReader takes --business, else the live bus host, else refuses (754.62238ms)
✔ humanCall kills a transport that runs past its timeout (555.937246ms)
✔ serve refuses a --business value that is not a business id (203.046485ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4962.873181ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2255.494312ms)
✔ conversation view: a newer session with no readable history keeps the marker (1835.688725ms)
✔ conversation view: seats without history say so and offer no reply (874.471396ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5057.229424ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55917.71064ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.578516ms)
✔ credential state from the date alone, as the bus states it (0.208763ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1807.02286ms)
✔ queue: a refused read fails closed with the store's text, redacted (2925.352176ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (10.321829ms)
✔ business: missing or invalid file refuses with the module's text, redacted (68.195812ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (7.821254ms)
✔ settings: release, board, loopback address, notifier binding name only (430.255959ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4966.190023ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23030.997892ms)
✔ loopback host and board origin fail closed (8.231493ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (145.679402ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (192.789916ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1468.72478ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.927827ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.066366ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.585746ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7706.035404ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3229.754636ms)
✔ Queue, Business and Settings in their five states, with no move button (5263.502407ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1949.682779ms)
✔ the server log names no config path when the system config refuses (490.821529ms)
ℹ tests 38
ℹ suites 0
ℹ pass 38
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56682.506308
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5281.957391ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5241.65014ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7840.135378ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (270.540486ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (208.09862ms)
✔ bus refusals map to statuses and never carry a path or a stack (318.694936ms)
✔ humanCall speaks the human transport protocol without blocking the server (2758.51859ms)
✔ busReader takes --business, else the live bus host, else refuses (147.192096ms)
✔ humanCall kills a transport that runs past its timeout (550.440101ms)
✔ serve refuses a --business value that is not a business id (178.662668ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5118.818508ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3077.747047ms)
✔ conversation view: a newer session with no readable history keeps the marker (1627.474771ms)
✔ conversation view: seats without history say so and offer no reply (1029.062199ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5290.335757ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56468.112243ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.166496ms)
✔ credential state from the date alone, as the bus states it (0.372441ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2103.086677ms)
✔ queue: a refused read fails closed with the store's text, redacted (2703.057076ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (7.65459ms)
✔ business: missing or invalid file refuses with the module's text, redacted (41.611595ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (23.322504ms)
✔ settings: release, board, loopback address, notifier binding name only (856.256666ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5115.288055ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23151.949075ms)
✔ loopback host and board origin fail closed (9.284164ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (129.004745ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (89.098167ms)
✔ unreachable board reports URL; CLI rejects unsupported options (2105.449159ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.570082ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.761389ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.906565ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7747.877498ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2783.880721ms)
✔ Queue, Business and Settings in their five states, with no move button (5730.815205ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (3355.333275ms)
✔ the server log names no config path when the system config refuses (216.073819ms)
ℹ tests 38
ℹ suites 0
ℹ pass 38
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56933.604909
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (6327.766018ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (6587.561462ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7164.072836ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (246.078084ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (139.195353ms)
✔ bus refusals map to statuses and never carry a path or a stack (95.893839ms)
✔ humanCall speaks the human transport protocol without blocking the server (3105.093089ms)
✔ busReader takes --business, else the live bus host, else refuses (213.372392ms)
✔ humanCall kills a transport that runs past its timeout (529.299281ms)
✔ serve refuses a --business value that is not a business id (232.303138ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4227.223124ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3532.965806ms)
✔ conversation view: a newer session with no readable history keeps the marker (2264.28452ms)
✔ conversation view: seats without history say so and offer no reply (1354.462899ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5789.719539ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55222.570505ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.328296ms)
✔ credential state from the date alone, as the bus states it (0.284846ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2212.985953ms)
✔ queue: a refused read fails closed with the store's text, redacted (1389.946627ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (10.094467ms)
✔ business: missing or invalid file refuses with the module's text, redacted (60.349154ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (6.569104ms)
✔ settings: release, board, loopback address, notifier binding name only (2564.866182ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4188.479774ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23166.897511ms)
✔ loopback host and board origin fail closed (8.473148ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (124.260907ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (90.028219ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1862.766827ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.430597ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.440465ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.909832ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7536.091806ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3049.744251ms)
✔ Queue, Business and Settings in their five states, with no move button (6628.913916ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2676.092518ms)
✔ the server log names no config path when the system config refuses (212.437536ms)
ℹ tests 38
ℹ suites 0
ℹ pass 38
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55715.675637
@@ -0,0 +1,8 @@
full-1 exit=0 ℹ pass 38 ℹ fail 0
full-2 exit=0 ℹ pass 38 ℹ fail 0
full-3 exit=0 ℹ pass 38 ℹ fail 0
full-4 exit=0 ℹ pass 38 ℹ fail 0
full-5 exit=0 ℹ pass 38 ℹ fail 0
full-6 exit=0 ℹ pass 38 ℹ fail 0
full-7 exit=0 ℹ pass 38 ℹ fail 0
full-8 exit=0 ℹ pass 38 ℹ fail 0
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# Row 54 gate: the webui, queue and business node suites, build-tokens --check, then every scripts/test-*.sh with Docker unreachable.
export TMPDIR=~/darkwing-scratch/r54a/tmp DOCKER_HOST=unix:///nonexistent.sock
cd ~/darkwing-scratch/r54a/papply
O=~/darkwing-scratch/r54a/r1/out
: > $O/summary.txt
echo "start $(date -u +%FT%TZ)" >> $O/summary.txt
for p in webui queue business; do
(cd packages/$p && env -u NODE_TEST_CONTEXT node --test 'tests/*.test.mjs') > $O/node-$p.txt 2>&1; e=$?
echo "node-$p exit=$e $(grep -E '^ℹ (pass|fail)' $O/node-$p.txt | tr '\n' ' ')" >> $O/summary.txt
done
node docs/design/tools/build-tokens.mjs --check > $O/build-tokens.txt 2>&1; e=$?
echo "build-tokens --check exit=$e $(tail -1 $O/build-tokens.txt)" >> $O/summary.txt
for f in scripts/test-*.sh; do
s=$(basename $f .sh)
$f > $O/$s.txt 2>&1; e=$?
echo "$s exit=$e $(grep -E 'passed, [0-9]+ failed' $O/$s.txt | tail -1)" >> $O/summary.txt
done
echo "end $(date -u +%FT%TZ)" >> $O/summary.txt
echo DONE >> $O/summary.txt
@@ -0,0 +1,12 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index ef13c793..0a20da6b 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -542,7 +542,6 @@
}
async function render(navigated) {
// A refresh due while this render reads would replace it: a navigation would lose its heading focus.
- clearTimeout(timer);
const route = routeOf(), [path, query = ''] = route.split('?'), board = path === '/' || path === '';
const g = ++gen;
document.body.classList.toggle('on-bus', !board);
@@ -0,0 +1,67 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3553.895836ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4891.433739ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6956.718189ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (389.653748ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (360.721408ms)
✔ bus refusals map to statuses and never carry a path or a stack (259.738493ms)
✔ humanCall speaks the human transport protocol without blocking the server (2914.51749ms)
✔ busReader takes --business, else the live bus host, else refuses (218.470323ms)
✔ humanCall kills a transport that runs past its timeout (528.920229ms)
✔ serve refuses a --business value that is not a business id (206.73927ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4420.305151ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2686.360689ms)
✔ conversation view: a newer session with no readable history keeps the marker (2011.768104ms)
✔ conversation view: seats without history say so and offer no reply (1442.624355ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3680.914258ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54948.713021ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.936711ms)
✔ credential state from the date alone, as the bus states it (0.285537ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2664.55476ms)
✔ queue: a refused read fails closed with the store's text, redacted (1076.031935ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (45.708991ms)
✔ business: missing or invalid file refuses with the module's text, redacted (49.193628ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (10.462066ms)
✔ settings: release, board, loopback address, notifier binding name only (1145.745393ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3665.782998ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22693.262252ms)
✔ loopback host and board origin fail closed (8.121504ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (147.592865ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (296.909995ms)
✔ unreachable board reports URL; CLI rejects unsupported options (2227.652587ms)
✔ tokens.css and icons.svg are byte copies of docs/design (5.246242ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (7.875649ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.008695ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7278.461772ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3423.807951ms)
✖ Queue, Business and Settings in their five states, with no move button (4781.975041ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (3724.530622ms)
✔ the server log names no config path when the system config refuses (213.795611ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55507.970242
✖ failing tests:
test at tests/views.test.mjs:63:1
✖ Queue, Business and Settings in their five states, with no move button (4781.975041ms)
AssertionError [ERR_ASSERTION]: the navigation cleared the due refresh
1 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:150:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 1,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index ef13c793..5908e8bd 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -529,7 +529,7 @@
function keep() {
const a = document.activeElement, view = $('bus-view');
return {
- href: view.contains(a) ? a.getAttribute('href') : null, copy: view.contains(a) ? a.dataset?.copy : null, retry: view.contains(a) && a.hasAttribute?.('data-retry'), mirror: view.contains(a) ? a.dataset?.mirror : null, h1: view.contains(a) && a.tagName === 'H1',
+ href: view.contains(a) ? a.getAttribute('href') : null, copy: view.contains(a) ? a.dataset?.copy : null, retry: view.contains(a) && a.hasAttribute?.('data-retry'), mirror: view.contains(a) ? a.dataset?.mirror : null,
open: [...view.querySelectorAll('details[id]')].filter(d => d.open).map(d => d.id), status: $('copy-status')?.textContent || '',
};
}
@@ -0,0 +1,67 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3968.619637ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5047.49366ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6459.792237ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (314.357486ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (101.425172ms)
✔ bus refusals map to statuses and never carry a path or a stack (119.06869ms)
✔ humanCall speaks the human transport protocol without blocking the server (2076.637029ms)
✔ busReader takes --business, else the live bus host, else refuses (220.20531ms)
✔ humanCall kills a transport that runs past its timeout (530.69311ms)
✔ serve refuses a --business value that is not a business id (210.542338ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4061.401789ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2753.455344ms)
✔ conversation view: a newer session with no readable history keeps the marker (1393.137759ms)
✔ conversation view: seats without history say so and offer no reply (1437.780504ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4679.185454ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54300.189462ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.701029ms)
✔ credential state from the date alone, as the bus states it (0.253638ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1325.561302ms)
✔ queue: a refused read fails closed with the store's text, redacted (1458.997085ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (14.260186ms)
✔ business: missing or invalid file refuses with the module's text, redacted (32.239843ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (32.032266ms)
✔ settings: release, board, loopback address, notifier binding name only (880.22299ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4525.040771ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22860.897185ms)
✔ loopback host and board origin fail closed (9.145343ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (135.226621ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (112.161171ms)
✔ unreachable board reports URL; CLI rejects unsupported options (947.30215ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.167311ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.417363ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.745934ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7682.084505ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2736.481331ms)
✖ Queue, Business and Settings in their five states, with no move button (4385.276082ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2773.495323ms)
✔ the server log names no config path when the system config refuses (211.527649ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54686.891864
✖ failing tests:
test at tests/views.test.mjs:63:1
✖ Queue, Business and Settings in their five states, with no move button (4385.276082ms)
AssertionError [ERR_ASSERTION]: #/queue keeps heading focus
false !== true
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:95:14)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,12 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index ef13c793..ed7d83b1 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -538,7 +538,6 @@
if ($('copy-status')) $('copy-status').textContent = k.status;
const el = [...$('bus-view').querySelectorAll('a[href],[data-copy],[data-retry],[data-mirror]')].find(e => k.href ? e.getAttribute('href') === k.href : k.copy ? e.dataset.copy === k.copy : k.mirror ? e.dataset.mirror === k.mirror : k.retry && e.hasAttribute('data-retry'));
if (el && (k.href || k.copy || k.retry || k.mirror)) el.focus({ preventScroll: true });
- else if (k.h1) $('bus-view').querySelector('h1')?.focus({ preventScroll: true });
}
async function render(navigated) {
// A refresh due while this render reads would replace it: a navigation would lose its heading focus.
@@ -0,0 +1,67 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4959.135819ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5474.161015ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5830.919645ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (245.718215ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (110.347818ms)
✔ bus refusals map to statuses and never carry a path or a stack (55.071907ms)
✔ humanCall speaks the human transport protocol without blocking the server (2216.361773ms)
✔ busReader takes --business, else the live bus host, else refuses (261.823267ms)
✔ humanCall kills a transport that runs past its timeout (534.849259ms)
✔ serve refuses a --business value that is not a business id (209.384483ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4258.679146ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2859.495657ms)
✔ conversation view: a newer session with no readable history keeps the marker (1210.012664ms)
✔ conversation view: seats without history say so and offer no reply (1270.574713ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5212.633077ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54375.108638ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.008832ms)
✔ credential state from the date alone, as the bus states it (0.261643ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1408.723324ms)
✔ queue: a refused read fails closed with the store's text, redacted (958.62181ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (50.388295ms)
✔ business: missing or invalid file refuses with the module's text, redacted (79.938741ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (26.881936ms)
✔ settings: release, board, loopback address, notifier binding name only (1300.646496ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4240.6632ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (24038.570056ms)
✔ loopback host and board origin fail closed (11.500921ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (151.330163ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (147.370812ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1278.214507ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.441027ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.553354ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.098882ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8038.81786ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2561.130024ms)
✖ Queue, Business and Settings in their five states, with no move button (5077.605025ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2121.25286ms)
✔ the server log names no config path when the system config refuses (318.053875ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54807.670851
✖ failing tests:
test at tests/views.test.mjs:63:1
✖ Queue, Business and Settings in their five states, with no move button (5077.605025ms)
AssertionError [ERR_ASSERTION]: #/queue keeps heading focus
false !== true
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:95:14)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index d321b483..c0491091 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -191,7 +191,7 @@
conv = { row: rowKey, id: null, branch: null, follow: null, messages: new Map(), markers: new Map(), sessionId: null, loading: true, polling: false, returnFocus: returnTo };
selected = null; render(); showConversation(true);
$('conv-title').textContent = r ? `${r.agent} conversation` : 'Conversation';
- $('conv-meta').textContent = ''; $('conv-pick').replaceChildren(); $('conv-log').replaceChildren(); drawMarkers(); convForm();
+ $('conv-meta').textContent = ''; $('conv-log').replaceChildren(); drawMarkers(); convForm();
if (!sameRow) $('conv-reply').value = drafts.get(rowKey) || '';
convStatus('Loading history…');
const slash = rowKey.lastIndexOf('/'), project = r?.project ?? rowKey.slice(0, slash), agent = r?.agent ?? rowKey.slice(slash + 1);
@@ -0,0 +1,67 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5416.256047ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5630.022554ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7285.022751ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (251.074506ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (96.863255ms)
✔ bus refusals map to statuses and never carry a path or a stack (198.437726ms)
✔ humanCall speaks the human transport protocol without blocking the server (2880.248817ms)
✔ busReader takes --business, else the live bus host, else refuses (162.972556ms)
✔ humanCall kills a transport that runs past its timeout (726.021417ms)
✔ serve refuses a --business value that is not a business id (179.377266ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5291.097952ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3133.676466ms)
✔ conversation view: a newer session with no readable history keeps the marker (1361.246225ms)
✔ conversation view: seats without history say so and offer no reply (808.287257ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5531.156192ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54931.370367ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.988747ms)
✔ credential state from the date alone, as the bus states it (0.268064ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1865.443672ms)
✔ queue: a refused read fails closed with the store's text, redacted (1787.964736ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (9.768333ms)
✔ business: missing or invalid file refuses with the module's text, redacted (48.559844ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (13.790316ms)
✔ settings: release, board, loopback address, notifier binding name only (1553.642343ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5277.71839ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (24077.83055ms)
✔ loopback host and board origin fail closed (8.965747ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (130.838291ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (94.321597ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1719.091845ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.239607ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.415696ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.843268ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5348.746927ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (4085.043051ms)
✔ Queue, Business and Settings in their five states, with no move button (5597.170405ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2798.86353ms)
✔ the server log names no config path when the system config refuses (319.853437ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55378.019619
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5348.746927ms)
AssertionError [ERR_ASSERTION]: no stale session in the picker
1 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/shell.test.mjs:220:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 1,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,12 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index d321b483..fab77574 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -325,7 +325,6 @@
: `<p><b>${esc(err.message)}</b> Board: ${esc(board)}. ${data ? `Showing last known data.${data.generatedAt ? ` Scanned ${esc(data.generatedAt)}.` : ''}` : 'No board data loaded.'} Use Refresh to try again.</p>`;
fresh();
// Clearing the board or closing a conversation can remove what had focus: give it to the page.
- if (focused && document.activeElement === document.body) $('main').focus();
}
function schedule() { clearTimeout(timer); if (!paused) timer = setTimeout(refresh, 10000); }
async function refresh() {
@@ -0,0 +1,67 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4615.719375ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4585.514852ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7271.250051ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (242.896934ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (220.669235ms)
✔ bus refusals map to statuses and never carry a path or a stack (141.578121ms)
✔ humanCall speaks the human transport protocol without blocking the server (2988.599285ms)
✔ busReader takes --business, else the live bus host, else refuses (126.433258ms)
✔ humanCall kills a transport that runs past its timeout (527.917971ms)
✔ serve refuses a --business value that is not a business id (195.271174ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4479.853577ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3174.276657ms)
✔ conversation view: a newer session with no readable history keeps the marker (1640.838159ms)
✔ conversation view: seats without history say so and offer no reply (775.790906ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4595.451589ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55470.169655ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.614121ms)
✔ credential state from the date alone, as the bus states it (0.223081ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2109.365496ms)
✔ queue: a refused read fails closed with the store's text, redacted (2184.175929ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (8.080197ms)
✔ business: missing or invalid file refuses with the module's text, redacted (25.283905ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (17.008779ms)
✔ settings: release, board, loopback address, notifier binding name only (335.452271ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4597.129667ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22498.542313ms)
✔ loopback host and board origin fail closed (9.263759ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (136.050609ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (128.243855ms)
✔ unreachable board reports URL; CLI rejects unsupported options (2210.233039ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.315777ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.867949ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.844346ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4472.420123ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (4783.30299ms)
✔ Queue, Business and Settings in their five states, with no move button (5506.470634ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2600.39773ms)
✔ the server log names no config path when the system config refuses (260.593252ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56411.185578
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4472.420123ms)
AssertionError [ERR_ASSERTION]: focus after a refusal closes the conversation
'' !== 'main'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/shell.test.mjs:231:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: '',
expected: 'main',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index d321b483..0f4fec95 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -325,7 +325,7 @@
: `<p><b>${esc(err.message)}</b> Board: ${esc(board)}. ${data ? `Showing last known data.${data.generatedAt ? ` Scanned ${esc(data.generatedAt)}.` : ''}` : 'No board data loaded.'} Use Refresh to try again.</p>`;
fresh();
// Clearing the board or closing a conversation can remove what had focus: give it to the page.
- if (focused && document.activeElement === document.body) $('main').focus();
+ if (document.activeElement === document.body) $('main').focus();
}
function schedule() { clearTimeout(timer); if (!paused) timer = setTimeout(refresh, 10000); }
async function refresh() {
@@ -0,0 +1,76 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3613.786889ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4240.276866ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5973.742507ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (466.99216ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (105.247532ms)
✔ bus refusals map to statuses and never carry a path or a stack (130.073438ms)
✔ humanCall speaks the human transport protocol without blocking the server (3065.220086ms)
✔ busReader takes --business, else the live bus host, else refuses (230.729041ms)
✔ humanCall kills a transport that runs past its timeout (558.243876ms)
✔ serve refuses a --business value that is not a business id (184.29222ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4047.318596ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2916.465219ms)
✔ conversation view: a newer session with no readable history keeps the marker (1192.341421ms)
✔ conversation view: seats without history say so and offer no reply (870.513825ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3063.102811ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54285.29228ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.060755ms)
✔ credential state from the date alone, as the bus states it (0.282176ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2246.692501ms)
✔ queue: a refused read fails closed with the store's text, redacted (793.260067ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (14.965494ms)
✔ business: missing or invalid file refuses with the module's text, redacted (11.07886ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (14.771348ms)
✔ settings: release, board, loopback address, notifier binding name only (1090.883299ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3131.077537ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22611.758067ms)
✔ loopback host and board origin fail closed (8.416311ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (128.240361ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (152.805559ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1934.709995ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.240145ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.239792ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.470294ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (3230.457287ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3242.981076ms)
✖ Queue, Business and Settings in their five states, with no move button (14732.61445ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1262.366048ms)
✔ the server log names no config path when the system config refuses (211.323102ms)
ℹ tests 38
ℹ suites 0
ℹ pass 36
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54920.869466
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (3230.457287ms)
AssertionError [ERR_ASSERTION]: a failure with nothing focused leaves focus alone
false !== true
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/shell.test.mjs:191:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: 'strictEqual',
diff: 'simple'
}
test at tests/views.test.mjs:63:1
✖ Queue, Business and Settings in their five states, with no move button (14732.61445ms)
Error: Uncaught (in promise) Error: Condition timed out: [...document.querySelectorAll("#cmdk-list [data-href]")].map(e=>e.dataset.href).includes("#/queue/7"): undefined
at Object.evaluate (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/browser.mjs:44:35)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:167:34)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/reads.mjs b/packages/webui/src/reads.mjs
index 0443a7c9..3c6717c0 100644
--- a/packages/webui/src/reads.mjs
+++ b/packages/webui/src/reads.mjs
@@ -41,7 +41,7 @@ export function redactor({ configDir = null, dataRoot = null } = {}) {
return text => {
let s = unquote(String(text));
for (const [re, label] of known) s = s.replace(re, label);
- return s.replace(/(^|[\s('"`=,[{])\/[^\s'"`),;\]}]+/g, '$1<path>').replace(/(?<![\w.-])\d{17,20}(?![\w.-])/g, '<id>');
+ return s.replace(/(?<![\w.-])\d{17,20}(?![\w.-])/g, '<id>');
};
}
// Every string in a value, deep. Keys are left alone: they are fixed names or ids.
@@ -0,0 +1,120 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4650.582262ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4664.085582ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5754.318128ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (279.717506ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (114.210506ms)
✔ bus refusals map to statuses and never carry a path or a stack (53.435062ms)
✔ humanCall speaks the human transport protocol without blocking the server (2221.997841ms)
✔ busReader takes --business, else the live bus host, else refuses (218.985409ms)
✔ humanCall kills a transport that runs past its timeout (528.312794ms)
✔ serve refuses a --business value that is not a business id (179.869563ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4508.168686ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2293.264018ms)
✔ conversation view: a newer session with no readable history keeps the marker (1319.817439ms)
✔ conversation view: seats without history say so and offer no reply (1205.060938ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4581.550733ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53751.394015ms)
✖ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.762145ms)
✔ credential state from the date alone, as the bus states it (0.322334ms)
✖ queue: rows, one row, ids and methods, notes; no path or id from a row (730.976411ms)
✔ queue: a refused read fails closed with the store's text, redacted (783.263853ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (36.53287ms)
✔ business: missing or invalid file refuses with the module's text, redacted (55.40206ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (21.82087ms)
✔ settings: release, board, loopback address, notifier binding name only (729.334788ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4574.89748ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23349.130426ms)
✔ loopback host and board origin fail closed (7.012201ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (122.031329ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (110.378886ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1061.933973ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.30578ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.451548ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.782154ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7613.991981ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2495.761305ms)
✖ Queue, Business and Settings in their five states, with no move button (15029.35374ms)
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1029.333274ms)
✔ the server log names no config path when the system config refuses (212.197114ms)
ℹ tests 38
ℹ suites 0
ℹ pass 34
ℹ fail 4
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54179.015502
✖ failing tests:
test at tests/reads.test.mjs:19:1
✖ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.762145ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'file /run/x.token, see https://git.example/a'
- 'file <path>, see https://git.example/a'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:23:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'file /run/x.token, see https://git.example/a',
expected: 'file <path>, see https://git.example/a',
operator: 'strictEqual',
diff: 'simple'
}
test at tests/reads.test.mjs:40:1
✖ queue: rows, one row, ids and methods, notes; no path or id from a row (730.976411ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'token at /srv/secret/x.token for <id>'
- 'token at <path> for <id>'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:49:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'token at /srv/secret/x.token for <id>',
expected: 'token at <path> for <id>',
operator: 'strictEqual',
diff: 'simple'
}
test at tests/views.test.mjs:63:1
✖ Queue, Business and Settings in their five states, with no move button (15029.35374ms)
Error: Uncaught (in promise) Error: Condition timed out: [...document.querySelectorAll("#cmdk-list [data-href]")].map(e=>e.dataset.href).includes("#/queue/7"): undefined
at Object.evaluate (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/browser.mjs:44:35)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:167:34)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
test at tests/views.test.mjs:185:1
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1029.333274ms)
AssertionError [ERR_ASSERTION]: response carries /srv/secret
true !== false
at clean (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads-fixture.mjs:54:185)
at seen (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:191:30)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:192:35)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: true,
expected: false,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/reads.mjs b/packages/webui/src/reads.mjs
index 0443a7c9..00fa848c 100644
--- a/packages/webui/src/reads.mjs
+++ b/packages/webui/src/reads.mjs
@@ -41,7 +41,7 @@ export function redactor({ configDir = null, dataRoot = null } = {}) {
return text => {
let s = unquote(String(text));
for (const [re, label] of known) s = s.replace(re, label);
- return s.replace(/(^|[\s('"`=,[{])\/[^\s'"`),;\]}]+/g, '$1<path>').replace(/(?<![\w.-])\d{17,20}(?![\w.-])/g, '<id>');
+ return s.replace(/(^|[\s('"`=,[{])\/[^\s'"`),;\]}]+/g, '$1<path>');
};
}
// Every string in a value, deep. Keys are left alone: they are fixed names or ids.
@@ -0,0 +1,112 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5688.449075ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5694.95127ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7107.443888ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (343.91401ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (89.658458ms)
✔ bus refusals map to statuses and never carry a path or a stack (83.906383ms)
✔ humanCall speaks the human transport protocol without blocking the server (2290.543537ms)
✔ busReader takes --business, else the live bus host, else refuses (367.513325ms)
✔ humanCall kills a transport that runs past its timeout (529.358747ms)
✔ serve refuses a --business value that is not a business id (186.786281ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5610.619897ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1942.713594ms)
✔ conversation view: a newer session with no readable history keeps the marker (1204.744965ms)
✔ conversation view: seats without history say so and offer no reply (1072.867685ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5631.191537ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54777.514235ms)
✖ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (4.211688ms)
✔ credential state from the date alone, as the bus states it (0.340018ms)
✖ queue: rows, one row, ids and methods, notes; no path or id from a row (685.64992ms)
✔ queue: a refused read fails closed with the store's text, redacted (1303.125605ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (24.025165ms)
✔ business: missing or invalid file refuses with the module's text, redacted (77.22646ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (14.685298ms)
✔ settings: release, board, loopback address, notifier binding name only (2276.993614ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5653.991032ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (24629.657301ms)
✔ loopback host and board origin fail closed (8.698518ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (117.702268ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (99.809366ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1022.425725ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.31903ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.399609ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.81104ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8241.020683ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2564.16633ms)
✔ Queue, Business and Settings in their five states, with no move button (5781.32492ms)
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1711.303273ms)
✔ the server log names no config path when the system config refuses (215.065833ms)
ℹ tests 38
ℹ suites 0
ℹ pass 35
ℹ fail 3
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55203.672143
✖ failing tests:
test at tests/reads.test.mjs:19:1
✖ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (4.211688ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'user 123456789012345678 ok'
- 'user <id> ok'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:25:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'user 123456789012345678 ok',
expected: 'user <id> ok',
operator: 'strictEqual',
diff: 'simple'
}
test at tests/reads.test.mjs:40:1
✖ queue: rows, one row, ids and methods, notes; no path or id from a row (685.64992ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'token at <path> for 523456789012345678'
- 'token at <path> for <id>'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:49:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'token at <path> for 523456789012345678',
expected: 'token at <path> for <id>',
operator: 'strictEqual',
diff: 'simple'
}
test at tests/views.test.mjs:185:1
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1711.303273ms)
AssertionError [ERR_ASSERTION]: response carries id 523456789012345678
true !== false
at clean (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads-fixture.mjs:53:38)
at seen (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:191:30)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:192:35)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: true,
expected: false,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/reads.mjs b/packages/webui/src/reads.mjs
index 0443a7c9..2e411db5 100644
--- a/packages/webui/src/reads.mjs
+++ b/packages/webui/src/reads.mjs
@@ -39,7 +39,7 @@ export function redactor({ configDir = null, dataRoot = null } = {}) {
.sort((a, b) => b[0].length - a[0].length)
.map(([p, label]) => [new RegExp(`${escape(p.replace(/\/+$/, ''))}(?![\\w.-])`, 'g'), label]);
return text => {
- let s = unquote(String(text));
+ let s = String(text);
for (const [re, label] of known) s = s.replace(re, label);
return s.replace(/(^|[\s('"`=,[{])\/[^\s'"`),;\]}]+/g, '$1<path>').replace(/(?<![\w.-])\d{17,20}(?![\w.-])/g, '<id>');
};
@@ -0,0 +1,77 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5175.312212ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5665.118462ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5853.659579ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (250.913924ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (137.674136ms)
✔ bus refusals map to statuses and never carry a path or a stack (110.570377ms)
✔ humanCall speaks the human transport protocol without blocking the server (2323.48959ms)
✔ busReader takes --business, else the live bus host, else refuses (236.080781ms)
✔ humanCall kills a transport that runs past its timeout (557.134857ms)
✔ serve refuses a --business value that is not a business id (240.965551ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5286.630849ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1924.346256ms)
✔ conversation view: a newer session with no readable history keeps the marker (1138.222866ms)
✔ conversation view: seats without history say so and offer no reply (1192.055963ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5513.038574ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54364.222627ms)
✖ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (3.151142ms)
✔ credential state from the date alone, as the bus states it (0.336056ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1454.573847ms)
✔ queue: a refused read fails closed with the store's text, redacted (829.865022ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (65.075001ms)
✔ business: missing or invalid file refuses with the module's text, redacted (47.810417ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (66.425429ms)
✔ settings: release, board, loopback address, notifier binding name only (1679.554601ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5084.063744ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (24647.259793ms)
✔ loopback host and board origin fail closed (9.974437ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (132.543531ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (95.015033ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1069.560663ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.103773ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.524ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.950677ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8179.983322ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2679.061779ms)
✖ Queue, Business and Settings in their five states, with no move button (15617.419591ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1174.356514ms)
✔ the server log names no config path when the system config refuses (211.037161ms)
ℹ tests 38
ℹ suites 0
ℹ pass 36
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54769.880455
✖ failing tests:
test at tests/reads.test.mjs:19:1
✖ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (3.151142ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
true !== false
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:27:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: true,
expected: false,
operator: 'strictEqual',
diff: 'simple'
}
test at tests/views.test.mjs:63:1
✖ Queue, Business and Settings in their five states, with no move button (15617.419591ms)
Error: Uncaught (in promise) Error: Condition timed out: [...document.querySelectorAll("#cmdk-list [data-href]")].map(e=>e.dataset.href).includes("#/queue/7"): undefined
at Object.evaluate (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/browser.mjs:44:35)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:167:34)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/reads.mjs b/packages/webui/src/reads.mjs
index 0443a7c9..f4c19dcb 100644
--- a/packages/webui/src/reads.mjs
+++ b/packages/webui/src/reads.mjs
@@ -40,7 +40,7 @@ export function redactor({ configDir = null, dataRoot = null } = {}) {
.map(([p, label]) => [new RegExp(`${escape(p.replace(/\/+$/, ''))}(?![\\w.-])`, 'g'), label]);
return text => {
let s = unquote(String(text));
- for (const [re, label] of known) s = s.replace(re, label);
+
return s.replace(/(^|[\s('"`=,[{])\/[^\s'"`),;\]}]+/g, '$1<path>').replace(/(?<![\w.-])\d{17,20}(?![\w.-])/g, '<id>');
};
}
@@ -0,0 +1,131 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4821.548654ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4712.402677ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5063.478583ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (333.96204ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (95.492452ms)
✔ bus refusals map to statuses and never carry a path or a stack (146.016361ms)
✔ humanCall speaks the human transport protocol without blocking the server (2132.328901ms)
✔ busReader takes --business, else the live bus host, else refuses (179.840217ms)
✔ humanCall kills a transport that runs past its timeout (528.852334ms)
✔ serve refuses a --business value that is not a business id (183.777864ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4691.507317ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1782.431855ms)
✔ conversation view: a newer session with no readable history keeps the marker (1083.690064ms)
✔ conversation view: seats without history say so and offer no reply (905.133089ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4774.780471ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54058.753561ms)
✖ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (3.109248ms)
✔ credential state from the date alone, as the bus states it (0.312537ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1348.652243ms)
✔ queue: a refused read fails closed with the store's text, redacted (911.277874ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (53.102001ms)
✖ business: missing or invalid file refuses with the module's text, redacted (13.446815ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (58.631252ms)
✖ settings: release, board, loopback address, notifier binding name only (1725.215019ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4710.06304ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (24419.701727ms)
✔ loopback host and board origin fail closed (7.568747ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (127.869708ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (109.641548ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1109.060295ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.037359ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.80034ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.352675ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7673.066897ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2596.719124ms)
✔ Queue, Business and Settings in their five states, with no move button (4979.78513ms)
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1436.423592ms)
✔ the server log names no config path when the system config refuses (216.073464ms)
ℹ tests 38
ℹ suites 0
ℹ pass 34
ℹ fail 4
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54464.117032
✖ failing tests:
test at tests/reads.test.mjs:19:1
✖ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (3.109248ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'business file not found: <path>'
- 'business file not found: <config>/businesses/a.json'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:21:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'business file not found: <path>',
expected: 'business file not found: <config>/businesses/a.json',
operator: 'strictEqual',
diff: 'simple'
}
test at tests/reads.test.mjs:104:1
✖ business: missing or invalid file refuses with the module's text, redacted (13.446815ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
{
error: 'not-configured',
+ message: 'business file not found: <path>'
- message: 'business file not found: <config>/businesses/acme.json'
}
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:110:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: { error: 'not-configured', message: 'business file not found: <path>' },
expected: { error: 'not-configured', message: 'business file not found: <config>/businesses/acme.json' },
operator: 'deepStrictEqual',
diff: 'simple'
}
test at tests/reads.test.mjs:139:1
✖ settings: release, board, loopback address, notifier binding name only (1725.215019ms)
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /^no notifier config at <dataRoot>\/notify\/acme\/notify\.json/. Input:
'no notifier config at <path>; write {"notifyVersion": 1, "binding": "<discord binding>"} there, mode 0600, or "binding": null to run without DMs'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:155:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'no notifier config at <path>; write {"notifyVersion": 1, "binding": "<discord binding>"} there, mode 0600, or "binding": null to run without DMs',
expected: /^no notifier config at <dataRoot>\/notify\/acme\/notify\.json/,
operator: 'match',
diff: 'simple'
}
test at tests/views.test.mjs:185:1
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1436.423592ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'business file not found: <path>'
- 'business file not found: <config>/businesses/acme.json'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:208:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'business file not found: <path>',
expected: 'business file not found: <config>/businesses/acme.json',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/reads.mjs b/packages/webui/src/reads.mjs
index 0443a7c9..f58e295c 100644
--- a/packages/webui/src/reads.mjs
+++ b/packages/webui/src/reads.mjs
@@ -175,7 +175,7 @@ export function consoleReads({ root, system = null, configDir = null, rolesDir,
return {
async queue() {
const doc = await readQueue();
- return scrub({ rows: doc.rows.map(queueSummary), notes: doc.notes, at: new Date(now()).toISOString() }, redact);
+ return { rows: doc.rows.map(queueSummary), notes: doc.notes, at: new Date(now()).toISOString() };
},
async row(id) {
const doc = await readQueue();
@@ -0,0 +1,90 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4200.011825ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4287.337505ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6767.800204ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (279.383429ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (127.629469ms)
✔ bus refusals map to statuses and never carry a path or a stack (177.380594ms)
✔ humanCall speaks the human transport protocol without blocking the server (3110.092035ms)
✔ busReader takes --business, else the live bus host, else refuses (216.322083ms)
✔ humanCall kills a transport that runs past its timeout (527.688862ms)
✔ serve refuses a --business value that is not a business id (257.348378ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4187.488658ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2597.873871ms)
✔ conversation view: a newer session with no readable history keeps the marker (1319.75687ms)
✔ conversation view: seats without history say so and offer no reply (1524.467611ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4247.752749ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55468.239155ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.993404ms)
✔ credential state from the date alone, as the bus states it (0.237632ms)
✖ queue: rows, one row, ids and methods, notes; no path or id from a row (1448.356706ms)
✔ queue: a refused read fails closed with the store's text, redacted (1243.781784ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (29.010056ms)
✔ business: missing or invalid file refuses with the module's text, redacted (87.088738ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (47.891683ms)
✔ settings: release, board, loopback address, notifier binding name only (1289.868745ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4184.66885ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23528.884292ms)
✔ loopback host and board origin fail closed (9.164883ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (139.400051ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (269.611326ms)
✔ unreachable board reports URL; CLI rejects unsupported options (2210.316145ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.225827ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.306369ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.912981ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7919.078486ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2726.485488ms)
✔ Queue, Business and Settings in their five states, with no move button (5182.080981ms)
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1701.612976ms)
✔ the server log names no config path when the system config refuses (262.023568ms)
ℹ tests 38
ℹ suites 0
ℹ pass 36
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55954.757552
✖ failing tests:
test at tests/reads.test.mjs:40:1
✖ queue: rows, one row, ids and methods, notes; no path or id from a row (1448.356706ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'token at /srv/secret/x.token for 523456789012345678'
- 'token at <path> for <id>'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:49:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'token at /srv/secret/x.token for 523456789012345678',
expected: 'token at <path> for <id>',
operator: 'strictEqual',
diff: 'simple'
}
test at tests/views.test.mjs:185:1
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1701.612976ms)
AssertionError [ERR_ASSERTION]: response carries id 523456789012345678
true !== false
at clean (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads-fixture.mjs:53:38)
at seen (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:191:30)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:192:35)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: true,
expected: false,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/reads.mjs b/packages/webui/src/reads.mjs
index 0443a7c9..bb2c4125 100644
--- a/packages/webui/src/reads.mjs
+++ b/packages/webui/src/reads.mjs
@@ -181,7 +181,7 @@ export function consoleReads({ root, system = null, configDir = null, rolesDir,
const doc = await readQueue();
const row = doc.rows.find(r => r.id === id);
if (!row) throw fail('not-found', `no row ${id}`);
- return scrub({ row, notes: doc.notes, at: new Date(now()).toISOString() }, redact);
+ return { row, notes: doc.notes, at: new Date(now()).toISOString() };
},
async business() {
const b = loaded();
@@ -0,0 +1,70 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4570.193723ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4956.198068ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7107.58317ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (769.948386ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (494.675929ms)
✔ bus refusals map to statuses and never carry a path or a stack (136.265847ms)
✔ humanCall speaks the human transport protocol without blocking the server (2443.327434ms)
✔ busReader takes --business, else the live bus host, else refuses (258.531965ms)
✔ humanCall kills a transport that runs past its timeout (530.502105ms)
✔ serve refuses a --business value that is not a business id (212.783705ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4993.067816ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2996.990459ms)
✔ conversation view: a newer session with no readable history keeps the marker (1631.875166ms)
✔ conversation view: seats without history say so and offer no reply (1186.796127ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4696.661508ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55729.545624ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.903061ms)
✔ credential state from the date alone, as the bus states it (0.268462ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2515.470013ms)
✔ queue: a refused read fails closed with the store's text, redacted (2087.981999ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (9.103344ms)
✔ business: missing or invalid file refuses with the module's text, redacted (7.270486ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (12.478573ms)
✔ settings: release, board, loopback address, notifier binding name only (450.947921ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4766.405351ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22539.695656ms)
✔ loopback host and board origin fail closed (9.137815ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (159.152836ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (138.985494ms)
✔ unreachable board reports URL; CLI rejects unsupported options (2048.036231ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.242791ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.275387ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.737133ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7486.985895ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2934.077185ms)
✔ Queue, Business and Settings in their five states, with no move button (4760.262064ms)
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2859.762634ms)
✔ the server log names no config path when the system config refuses (315.570758ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56412.306795
✖ failing tests:
test at tests/views.test.mjs:185:1
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2859.762634ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'token at /srv/secret/x.token for 523456789012345678'
- 'token at <path> for <id>'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:194:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'token at /srv/secret/x.token for 523456789012345678',
expected: 'token at <path> for <id>',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/reads.mjs b/packages/webui/src/reads.mjs
index 0443a7c9..3b684c08 100644
--- a/packages/webui/src/reads.mjs
+++ b/packages/webui/src/reads.mjs
@@ -147,7 +147,7 @@ export function businessView(b, { system = null, now = Date.now() } = {}) {
for (const [service, ref] of Object.entries(b.tracker.sync.credentials)) credentials.push(credential(service, ref, b.tracker.sync.bot, 'tracker sync', now));
return {
id: b.id, human: b.human, arbiters: b.arbiters, projects: Object.keys(b.projects), actions: [...ACTIONS],
- vars: pick(b.vars, VAR_KEYS), instances, launch: b.launch, credentials,
+ vars: b.vars, instances, launch: b.launch, credentials,
};
}
@@ -0,0 +1,88 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4709.018453ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4973.594263ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5094.682703ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (221.296591ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (85.930486ms)
✔ bus refusals map to statuses and never carry a path or a stack (42.082383ms)
✔ humanCall speaks the human transport protocol without blocking the server (2307.10047ms)
✔ busReader takes --business, else the live bus host, else refuses (199.49397ms)
✔ humanCall kills a transport that runs past its timeout (528.962067ms)
✔ serve refuses a --business value that is not a business id (185.701252ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4615.784771ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1847.441235ms)
✔ conversation view: a newer session with no readable history keeps the marker (1159.459305ms)
✔ conversation view: seats without history say so and offer no reply (1340.222303ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4778.356902ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53978.621659ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.659993ms)
✔ credential state from the date alone, as the bus states it (0.223706ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1277.587507ms)
✔ queue: a refused read fails closed with the store's text, redacted (913.900924ms)
✖ business: names, vars on the allowlist, authority, credential metadata only (37.45811ms)
✔ business: missing or invalid file refuses with the module's text, redacted (31.583646ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (31.72574ms)
✔ settings: release, board, loopback address, notifier binding name only (1476.970439ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4756.782144ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (24948.382349ms)
✔ loopback host and board origin fail closed (7.539573ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (119.828281ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (92.075561ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1064.918332ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.288778ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.412184ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.387512ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7389.030034ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2512.855214ms)
✔ Queue, Business and Settings in their five states, with no move button (5019.358561ms)
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1469.499886ms)
✔ the server log names no config path when the system config refuses (264.214792ms)
ℹ tests 38
ℹ suites 0
ℹ pass 36
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54385.798876
✖ failing tests:
test at tests/reads.test.mjs:78:1
✖ business: names, vars on the allowlist, authority, credential metadata only (37.45811ms)
AssertionError [ERR_ASSERTION]: response carries discordUserId
true !== false
at clean (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads-fixture.mjs:54:185)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:83:3)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: true,
expected: false,
operator: 'strictEqual',
diff: 'simple'
}
test at tests/views.test.mjs:185:1
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1469.499886ms)
AssertionError [ERR_ASSERTION]: response carries discordUserId
true !== false
at clean (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads-fixture.mjs:54:185)
at seen (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:191:30)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:198:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: true,
expected: false,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/reads.mjs b/packages/webui/src/reads.mjs
index 0443a7c9..8d7820f1 100644
--- a/packages/webui/src/reads.mjs
+++ b/packages/webui/src/reads.mjs
@@ -138,7 +138,7 @@ export function businessView(b, { system = null, now = Date.now() } = {}) {
refused = err.message;
}
}
- return { instance, definition: r.definition, holder: r.holder, vars: pick(r.vars, AGENT_KEYS), authority, refused };
+ return { instance, definition: r.definition, holder: r.holder, vars: r.vars, authority, refused };
});
const credentials = [];
for (const [instance, r] of Object.entries(b.roles)) {
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4004.937464ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3917.400112ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5170.572708ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (287.407303ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (166.167279ms)
✔ bus refusals map to statuses and never carry a path or a stack (199.702392ms)
✔ humanCall speaks the human transport protocol without blocking the server (3138.725814ms)
✔ busReader takes --business, else the live bus host, else refuses (115.537443ms)
✔ humanCall kills a transport that runs past its timeout (527.728165ms)
✔ serve refuses a --business value that is not a business id (238.234487ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3957.452635ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2434.993651ms)
✔ conversation view: a newer session with no readable history keeps the marker (1218.814393ms)
✔ conversation view: seats without history say so and offer no reply (1584.178275ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3973.306995ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55015.00821ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.86278ms)
✔ credential state from the date alone, as the bus states it (0.292161ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2322.265964ms)
✔ queue: a refused read fails closed with the store's text, redacted (1114.018863ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (9.728956ms)
✔ business: missing or invalid file refuses with the module's text, redacted (15.795049ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (41.263849ms)
✔ settings: release, board, loopback address, notifier binding name only (476.586387ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3957.622785ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22809.958545ms)
✔ loopback host and board origin fail closed (9.47624ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (129.714901ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (123.675161ms)
✔ unreachable board reports URL; CLI rejects unsupported options (2294.81738ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.880216ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.110731ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.724916ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7477.226767ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2740.634144ms)
✔ Queue, Business and Settings in their five states, with no move button (5141.920321ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1610.387559ms)
✔ the server log names no config path when the system config refuses (212.181681ms)
ℹ tests 38
ℹ suites 0
ℹ pass 38
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55625.222535
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/reads.mjs b/packages/webui/src/reads.mjs
index 0443a7c9..4357ec46 100644
--- a/packages/webui/src/reads.mjs
+++ b/packages/webui/src/reads.mjs
@@ -124,7 +124,7 @@ export function credentialState(service, date, now = Date.now()) {
}
const credential = (service, ref, account, role, now) => {
const dateKind = ref.expires ? 'expires' : 'rotateBy', date = ref[dateKind];
- return { service, account, role, dateKind, date, state: credentialState(service, date, now) };
+ return { ...ref, service, account, role, dateKind, date, state: credentialState(service, date, now) };
};
export function businessView(b, { system = null, now = Date.now() } = {}) {
const instances = Object.entries(b.roles).map(([instance, r]) => {
@@ -0,0 +1,88 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4320.342173ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4861.262698ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6014.528276ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (240.14141ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (109.720314ms)
✔ bus refusals map to statuses and never carry a path or a stack (58.088948ms)
✔ humanCall speaks the human transport protocol without blocking the server (2366.690803ms)
✔ busReader takes --business, else the live bus host, else refuses (347.421521ms)
✔ humanCall kills a transport that runs past its timeout (536.340319ms)
✔ serve refuses a --business value that is not a business id (244.651155ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4525.098097ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2537.083859ms)
✔ conversation view: a newer session with no readable history keeps the marker (1372.734646ms)
✔ conversation view: seats without history say so and offer no reply (1514.772455ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4156.771288ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54196.838627ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (3.046309ms)
✔ credential state from the date alone, as the bus states it (0.502866ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1253.34351ms)
✔ queue: a refused read fails closed with the store's text, redacted (851.611323ms)
✖ business: names, vars on the allowlist, authority, credential metadata only (30.771802ms)
✔ business: missing or invalid file refuses with the module's text, redacted (17.083256ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (52.61554ms)
✔ settings: release, board, loopback address, notifier binding name only (907.907753ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4003.394ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (24408.169579ms)
✔ loopback host and board origin fail closed (9.25948ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (142.502118ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (112.15472ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1004.917225ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.425495ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.576602ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.512941ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8275.616778ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2614.697551ms)
✔ Queue, Business and Settings in their five states, with no move button (5923.260745ms)
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1654.074503ms)
✔ the server log names no config path when the system config refuses (213.349984ms)
ℹ tests 38
ℹ suites 0
ℹ pass 36
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54636.24251
✖ failing tests:
test at tests/reads.test.mjs:78:1
✖ business: names, vars on the allowlist, authority, credential metadata only (30.771802ms)
AssertionError [ERR_ASSERTION]: response carries CODER_GITEA_SECRET_ENV
true !== false
at clean (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads-fixture.mjs:54:185)
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:83:3)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: true,
expected: false,
operator: 'strictEqual',
diff: 'simple'
}
test at tests/views.test.mjs:185:1
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1654.074503ms)
AssertionError [ERR_ASSERTION]: response carries CODER_GITEA_SECRET_ENV
true !== false
at clean (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads-fixture.mjs:54:185)
at seen (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:191:30)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:198:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: true,
expected: false,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/reads.mjs b/packages/webui/src/reads.mjs
index 0443a7c9..2f03f3f5 100644
--- a/packages/webui/src/reads.mjs
+++ b/packages/webui/src/reads.mjs
@@ -158,7 +158,7 @@ export function businessView(b, { system = null, now = Date.now() } = {}) {
export function consoleReads({ root, system = null, configDir = null, rolesDir, business = null, env = process.env, queueTimeoutMs = 30000, now = () => Date.now() }) {
const dataRoot = system?.dataRoot ?? null;
const redact = redactor({ configDir, dataRoot });
- const fail = (code, message) => new ReadError(code, redact(message));
+ const fail = (code, message) => new ReadError(code, message);
const queue = queueReader({ root, env, timeoutMs: queueTimeoutMs });
const readQueue = async () => {
try { return await queue(); } catch (err) { throw err instanceof ReadError ? fail(err.code, err.message) : fail('read-failed', 'the queue read failed'); }
@@ -0,0 +1,92 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3650.247354ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3826.974177ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6474.565325ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (327.782299ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (205.8335ms)
✔ bus refusals map to statuses and never carry a path or a stack (131.381401ms)
✔ humanCall speaks the human transport protocol without blocking the server (3012.738672ms)
✔ busReader takes --business, else the live bus host, else refuses (180.000411ms)
✔ humanCall kills a transport that runs past its timeout (528.983485ms)
✔ serve refuses a --business value that is not a business id (186.706417ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3645.611452ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3438.823899ms)
✔ conversation view: a newer session with no readable history keeps the marker (1683.525095ms)
✔ conversation view: seats without history say so and offer no reply (1386.921952ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3688.668232ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54782.584998ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.958002ms)
✔ credential state from the date alone, as the bus states it (0.281795ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2451.051865ms)
✔ queue: a refused read fails closed with the store's text, redacted (1187.512117ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (14.308173ms)
✖ business: missing or invalid file refuses with the module's text, redacted (9.423229ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (17.044179ms)
✔ settings: release, board, loopback address, notifier binding name only (1785.282944ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3698.089316ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22352.671379ms)
✔ loopback host and board origin fail closed (8.954892ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (148.083539ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (375.111628ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1949.171088ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.987791ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.17167ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.739585ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7374.265867ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3370.774154ms)
✔ Queue, Business and Settings in their five states, with no move button (5497.49223ms)
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2167.33082ms)
✔ the server log names no config path when the system config refuses (266.89509ms)
ℹ tests 38
ℹ suites 0
ℹ pass 36
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55318.229797
✖ failing tests:
test at tests/reads.test.mjs:104:1
✖ business: missing or invalid file refuses with the module's text, redacted (9.423229ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
{
error: 'not-configured',
+ message: 'business file not found: /home/jwoltje/darkwing-scratch/r54a/tmp/mosaic-webui-reads-WGaPEJ/config/businesses/acme.json'
- message: 'business file not found: <config>/businesses/acme.json'
}
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:110:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: { error: 'not-configured', message: 'business file not found: /home/jwoltje/darkwing-scratch/r54a/tmp/mosaic-webui-reads-WGaPEJ/config/businesses/acme.json' },
expected: { error: 'not-configured', message: 'business file not found: <config>/businesses/acme.json' },
operator: 'deepStrictEqual',
diff: 'simple'
}
test at tests/views.test.mjs:185:1
✖ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2167.33082ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'business file not found: /home/jwoltje/darkwing-scratch/r54a/tmp/mosaic-webui-reads-XOYkdN/config/businesses/acme.json'
- 'business file not found: <config>/businesses/acme.json'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:208:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'business file not found: /home/jwoltje/darkwing-scratch/r54a/tmp/mosaic-webui-reads-XOYkdN/config/businesses/acme.json',
expected: 'business file not found: <config>/businesses/acme.json',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/reads.mjs b/packages/webui/src/reads.mjs
index 0443a7c9..50bfc986 100644
--- a/packages/webui/src/reads.mjs
+++ b/packages/webui/src/reads.mjs
@@ -197,7 +197,7 @@ export function consoleReads({ root, system = null, configDir = null, rolesDir,
} catch (err) {
notifier = { refused: err instanceof ReadError ? err.code : 'not-configured', message: err.message };
}
- return scrub({ release, business: id, notifier, at: new Date(now()).toISOString() }, redact);
+ return { release, business: id, notifier, at: new Date(now()).toISOString() };
},
};
}
@@ -0,0 +1,67 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4817.858569ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4831.116292ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6438.845053ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (261.893139ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (128.527526ms)
✔ bus refusals map to statuses and never carry a path or a stack (357.436891ms)
✔ humanCall speaks the human transport protocol without blocking the server (3111.192772ms)
✔ busReader takes --business, else the live bus host, else refuses (151.434433ms)
✔ humanCall kills a transport that runs past its timeout (529.197179ms)
✔ serve refuses a --business value that is not a business id (178.261532ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3487.673599ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3146.74374ms)
✔ conversation view: a newer session with no readable history keeps the marker (1816.87862ms)
✔ conversation view: seats without history say so and offer no reply (1113.619555ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3498.498318ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54569.122714ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.696016ms)
✔ credential state from the date alone, as the bus states it (0.252845ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2326.020362ms)
✔ queue: a refused read fails closed with the store's text, redacted (1045.331492ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (31.836555ms)
✔ business: missing or invalid file refuses with the module's text, redacted (51.675294ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (22.849902ms)
✖ settings: release, board, loopback address, notifier binding name only (1275.177424ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3243.06729ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22482.941715ms)
✔ loopback host and board origin fail closed (9.017601ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (141.486246ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (102.08935ms)
✔ unreachable board reports URL; CLI rejects unsupported options (2474.838466ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.186829ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.396715ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.697522ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7569.493062ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2691.59571ms)
✔ Queue, Business and Settings in their five states, with no move button (5186.847432ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2597.52527ms)
✔ the server log names no config path when the system config refuses (283.944681ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55034.437097
✖ failing tests:
test at tests/reads.test.mjs:139:1
✖ settings: release, board, loopback address, notifier binding name only (1275.177424ms)
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /^no notifier config at <dataRoot>\/notify\/acme\/notify\.json/. Input:
'no notifier config at /home/jwoltje/darkwing-scratch/r54a/tmp/mosaic-webui-reads-AgJNR1/data/notify/acme/notify.json; write {"notifyVersion": 1, "binding": "<discord binding>"} there, mode 0600, or "binding": null to run without DMs'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:155:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'no notifier config at /home/jwoltje/darkwing-scratch/r54a/tmp/mosaic-webui-reads-AgJNR1/data/notify/acme/notify.json; write {"notifyVersion": 1, "binding": "<discord binding>"} there, mode 0600, or "binding": null to run without DMs',
expected: /^no notifier config at <dataRoot>\/notify\/acme\/notify\.json/,
operator: 'match',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/cli.mjs b/packages/webui/src/cli.mjs
index 64b3e7b4..ccbe6780 100644
--- a/packages/webui/src/cli.mjs
+++ b/packages/webui/src/cli.mjs
@@ -35,7 +35,7 @@ try {
const { dataRoot } = system;
bus = busReader({ dataRoot, socket: socketPath(dataRoot), business: options.business ?? null });
busLine = `Bus: ${options.business ?? 'the running bus host\'s business'}`;
- } catch (err) { busLine = `Bus: not configured (${redactor({ configDir: configDir() })(err.message)})`; }
+ } catch (err) { busLine = `Bus: not configured (${err.message})`; }
const reads = consoleReads({ root: REPO, system, configDir: configDir(), rolesDir: rolesDir(), business: options.business ?? null });
const server = await startServer({ board: options.board, port: options.port, bus, reads });
console.log(`Mosaic Console: http://127.0.0.1:${server.address().port}/\nBoard: ${options.board}\n${busLine}\nCtrl-C stops this server.`);
@@ -0,0 +1,71 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3817.703923ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5659.498256ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6439.043369ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (414.481885ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (196.26774ms)
✔ bus refusals map to statuses and never carry a path or a stack (433.236761ms)
✔ humanCall speaks the human transport protocol without blocking the server (2853.910625ms)
✔ busReader takes --business, else the live bus host, else refuses (196.312458ms)
✔ humanCall kills a transport that runs past its timeout (529.937494ms)
✔ serve refuses a --business value that is not a business id (186.136437ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3702.914845ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2862.774682ms)
✔ conversation view: a newer session with no readable history keeps the marker (1422.952668ms)
✔ conversation view: seats without history say so and offer no reply (1164.889844ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3732.745577ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55078.235119ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.963655ms)
✔ credential state from the date alone, as the bus states it (0.272451ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2325.989519ms)
✔ queue: a refused read fails closed with the store's text, redacted (1363.790238ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (14.618212ms)
✔ business: missing or invalid file refuses with the module's text, redacted (11.854573ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (22.083734ms)
✔ settings: release, board, loopback address, notifier binding name only (1153.399222ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3884.944785ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22223.74894ms)
✔ loopback host and board origin fail closed (8.621355ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (143.327015ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (213.530601ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1953.585562ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.062193ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.793912ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.43552ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7690.504357ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2529.808928ms)
✔ Queue, Business and Settings in their five states, with no move button (4899.427222ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2906.015306ms)
✖ the server log names no config path when the system config refuses (158.108662ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55517.92932
✖ failing tests:
test at tests/views.test.mjs:220:1
✖ the server log names no config path when the system config refuses (158.108662ms)
AssertionError [ERR_ASSERTION]: missing: the log names the temporary directory: Mosaic Console: http://127.0.0.1:45003/
Board: http://127.0.0.1:7331
Bus: not configured (system config problem (mosaic-config exit 3): mosaic-config: configuration not found: /home/jwoltje/darkwing-scratch/r54a/tmp/mosaic-webui-log-AVrgLO/cfg/config.json (run scripts/bootstrap.sh to create it))
Ctrl-C stops this server.
true !== false
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:232:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: true,
expected: false,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/reads.mjs b/packages/webui/src/reads.mjs
index 0443a7c9..ab908629 100644
--- a/packages/webui/src/reads.mjs
+++ b/packages/webui/src/reads.mjs
@@ -77,7 +77,7 @@ export function queueReader({ root, env = process.env, timeoutMs = 30000, maxByt
if (status === null) return done(reject, new ReadError('read-failed', 'the queue read did not finish'));
// 2 is the store's "invalid data or refused": fail closed. Anything
// else failed to run; the view keeps its last read.
- if (status === 2) return done(reject, new ReadError('queue-refused', said || 'the queue refused the read'));
+ if (status === 3) return done(reject, new ReadError('queue-refused', said || 'the queue refused the read'));
if (status !== 0) return done(reject, new ReadError('read-failed', said || `the queue read exited ${status}`));
try {
const doc = JSON.parse(Buffer.concat(out).toString('utf8'));
@@ -0,0 +1,69 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5000.920903ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (6101.070206ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5149.705568ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (202.168278ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (118.869951ms)
✔ bus refusals map to statuses and never carry a path or a stack (56.799173ms)
✔ humanCall speaks the human transport protocol without blocking the server (2185.591574ms)
✔ busReader takes --business, else the live bus host, else refuses (228.176327ms)
✔ humanCall kills a transport that runs past its timeout (530.144008ms)
✔ serve refuses a --business value that is not a business id (179.885403ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4383.570339ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2385.381128ms)
✔ conversation view: a newer session with no readable history keeps the marker (1251.275827ms)
✔ conversation view: seats without history say so and offer no reply (1299.472932ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4954.067159ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54150.717577ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.875352ms)
✔ credential state from the date alone, as the bus states it (1.110811ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1080.450373ms)
✖ queue: a refused read fails closed with the store's text, redacted (865.486429ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (33.648624ms)
✔ business: missing or invalid file refuses with the module's text, redacted (43.174739ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (55.195688ms)
✔ settings: release, board, loopback address, notifier binding name only (1735.767622ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4953.621555ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22780.557902ms)
✔ loopback host and board origin fail closed (7.991927ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (132.502511ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (103.336956ms)
✔ unreachable board reports URL; CLI rejects unsupported options (923.407023ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.142534ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.34311ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.607216ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7916.296577ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2821.242953ms)
✔ Queue, Business and Settings in their five states, with no move button (5077.761226ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2307.244753ms)
✔ the server log names no config path when the system config refuses (212.086109ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54580.470005
✖ failing tests:
test at tests/reads.test.mjs:66:1
✖ queue: a refused read fails closed with the store's text, redacted (865.486429ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'read-failed'
- 'queue-refused'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:72:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'read-failed',
expected: 'queue-refused',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/reads.mjs b/packages/webui/src/reads.mjs
index 0443a7c9..738eeabd 100644
--- a/packages/webui/src/reads.mjs
+++ b/packages/webui/src/reads.mjs
@@ -67,7 +67,7 @@ export function queueReader({ root, env = process.env, timeoutMs = 30000, maxByt
const timer = setTimeout(() => { child.kill('SIGKILL'); done(reject, new ReadError('read-failed', 'the queue read did not finish in time')); }, timeoutMs);
child.stdout.on('data', chunk => {
size += chunk.length;
- if (size > maxBytes) { child.kill('SIGKILL'); return done(reject, new ReadError('invalid-response', 'the queue read printed too much')); }
+ if (false) { child.kill('SIGKILL'); return done(reject, new ReadError('invalid-response', 'the queue read printed too much')); }
out.push(chunk);
});
child.stderr.on('data', chunk => { if (err.length < 64) err.push(chunk); });
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3866.059382ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4419.937987ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5405.701111ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (519.485737ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (113.885362ms)
✔ bus refusals map to statuses and never carry a path or a stack (81.818732ms)
✔ humanCall speaks the human transport protocol without blocking the server (2598.472555ms)
✔ busReader takes --business, else the live bus host, else refuses (284.401929ms)
✔ humanCall kills a transport that runs past its timeout (528.464522ms)
✔ serve refuses a --business value that is not a business id (176.819738ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3360.350258ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2475.164929ms)
✔ conversation view: a newer session with no readable history keeps the marker (1548.551466ms)
✔ conversation view: seats without history say so and offer no reply (1159.760586ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4398.513141ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53904.389573ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.905984ms)
✔ credential state from the date alone, as the bus states it (0.466905ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1877.649139ms)
✔ queue: a refused read fails closed with the store's text, redacted (1017.101102ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (14.700461ms)
✔ business: missing or invalid file refuses with the module's text, redacted (42.691838ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (11.231969ms)
✔ settings: release, board, loopback address, notifier binding name only (985.174883ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5045.616406ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21943.942895ms)
✔ loopback host and board origin fail closed (7.980215ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (152.92514ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (108.918606ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1508.292551ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.226657ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.213199ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.727394ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7770.048483ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2575.003059ms)
✔ Queue, Business and Settings in their five states, with no move button (5342.218834ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1475.424402ms)
✔ the server log names no config path when the system config refuses (211.232239ms)
ℹ tests 38
ℹ suites 0
ℹ pass 38
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54372.574594
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/reads.mjs b/packages/webui/src/reads.mjs
index 0443a7c9..be748ef1 100644
--- a/packages/webui/src/reads.mjs
+++ b/packages/webui/src/reads.mjs
@@ -64,7 +64,7 @@ export function queueReader({ root, env = process.env, timeoutMs = 30000, maxByt
const out = [], err = [];
let size = 0, settled = false;
const done = (fn, value) => { if (!settled) { settled = true; clearTimeout(timer); fn(value); } };
- const timer = setTimeout(() => { child.kill('SIGKILL'); done(reject, new ReadError('read-failed', 'the queue read did not finish in time')); }, timeoutMs);
+ const timer = setTimeout(() => { child.kill('SIGKILL'); done(reject, new ReadError('read-failed', 'the queue read did not finish in time')); }, timeoutMs * 1000);
child.stdout.on('data', chunk => {
size += chunk.length;
if (size > maxBytes) { child.kill('SIGKILL'); return done(reject, new ReadError('invalid-response', 'the queue read printed too much')); }
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4065.728473ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4748.411013ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5432.64066ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (251.094089ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (152.1088ms)
✔ bus refusals map to statuses and never carry a path or a stack (70.226629ms)
✔ humanCall speaks the human transport protocol without blocking the server (2389.336743ms)
✔ busReader takes --business, else the live bus host, else refuses (150.170513ms)
✔ humanCall kills a transport that runs past its timeout (528.673989ms)
✔ serve refuses a --business value that is not a business id (205.809698ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4456.794936ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2219.534804ms)
✔ conversation view: a newer session with no readable history keeps the marker (1520.224118ms)
✔ conversation view: seats without history say so and offer no reply (1156.358866ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4544.113781ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53836.532458ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.528531ms)
✔ credential state from the date alone, as the bus states it (0.226672ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1277.199973ms)
✔ queue: a refused read fails closed with the store's text, redacted (1030.143575ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (92.670531ms)
✔ business: missing or invalid file refuses with the module's text, redacted (46.184315ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (23.83569ms)
✔ settings: release, board, loopback address, notifier binding name only (790.819227ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4452.918008ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22738.303689ms)
✔ loopback host and board origin fail closed (7.888341ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (130.305992ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (100.057231ms)
✔ unreachable board reports URL; CLI rejects unsupported options (989.45021ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.110478ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.307697ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.667219ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7737.043908ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2539.24466ms)
✔ Queue, Business and Settings in their five states, with no move button (5385.6383ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2107.620417ms)
✔ the server log names no config path when the system config refuses (215.432638ms)
ℹ tests 38
ℹ suites 0
ℹ pass 38
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54219.239493
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/reads.mjs b/packages/webui/src/reads.mjs
index 0443a7c9..17fd8dca 100644
--- a/packages/webui/src/reads.mjs
+++ b/packages/webui/src/reads.mjs
@@ -108,7 +108,7 @@ export function businessId({ business = null, dataRoot = null }) {
if (!dataRoot) throw new ReadError('not-configured', 'the Console has no system config, so it knows no business');
let state;
try { state = readHostState(dataRoot); } catch { throw new ReadError('no-bus-host', 'the bus host state file is unreadable'); }
- if (!state?.live) throw new ReadError('no-bus-host', 'no bus host is running and no --business was given');
+ if (!state) throw new ReadError('no-bus-host', 'no bus host is running and no --business was given');
return state.business;
}
const VAR_KEYS = ['tracker.kind', 'tracker.baseUrl', 'tracker.reconcileMinutes', 'tracker.pollSeconds', 'gitea.baseUrl', 'limits.tools', 'limits.network', 'limits.authority'];
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3985.688283ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4447.990495ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6526.818377ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (231.270755ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (89.655118ms)
✔ bus refusals map to statuses and never carry a path or a stack (238.493594ms)
✔ humanCall speaks the human transport protocol without blocking the server (3141.608774ms)
✔ busReader takes --business, else the live bus host, else refuses (107.72097ms)
✔ humanCall kills a transport that runs past its timeout (528.390433ms)
✔ serve refuses a --business value that is not a business id (269.837021ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4330.192204ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2278.387967ms)
✔ conversation view: a newer session with no readable history keeps the marker (1301.091785ms)
✔ conversation view: seats without history say so and offer no reply (1173.992175ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4291.22648ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53396.828902ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.899748ms)
✔ credential state from the date alone, as the bus states it (0.26954ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2349.273168ms)
✔ queue: a refused read fails closed with the store's text, redacted (1447.280241ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (12.839479ms)
✔ business: missing or invalid file refuses with the module's text, redacted (14.813239ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (8.441831ms)
✔ settings: release, board, loopback address, notifier binding name only (572.775828ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4348.935418ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22560.65201ms)
✔ loopback host and board origin fail closed (8.401405ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (115.287665ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (91.329521ms)
✔ unreachable board reports URL; CLI rejects unsupported options (2293.702044ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.2854ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.42369ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.836279ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7773.688678ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2735.667567ms)
✔ Queue, Business and Settings in their five states, with no move button (5064.701774ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2616.434555ms)
✔ the server log names no config path when the system config refuses (215.619506ms)
ℹ tests 38
ℹ suites 0
ℹ pass 38
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53826.308905
@@ -0,0 +1,12 @@
diff --git a/packages/webui/src/serve.mjs b/packages/webui/src/serve.mjs
index 8e89e3ed..96f47c8c 100644
--- a/packages/webui/src/serve.mjs
+++ b/packages/webui/src/serve.mjs
@@ -117,7 +117,6 @@ export async function startServer({ host = '127.0.0.1', port = 7330, board = DEF
return busRead(res, bus, verb, search);
}
if (path === '/api/queue' || path.startsWith('/api/queue/') || path === '/api/business' || path === '/api/settings') {
- if (req.method !== 'GET') return json(res, 405, { error: 'method not allowed' });
const a = server.address(), address = `http://${a.family === 'IPv6' ? `[${a.address}]` : a.address}:${a.port}/`;
if (path === '/api/settings') return consoleRead(res, async () => ({ ...(reads ? await reads.settings() : { release: null, business: null, notifier: { refused: 'not-configured', message: 'the Console has no reads configured' } }), board: upstream, address }));
if (!reads) return json(res, 503, { error: 'not-configured', message: 'the Console has no reads configured' });
@@ -0,0 +1,67 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3815.689527ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4120.862703ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6446.050679ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (346.888376ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (165.142536ms)
✔ bus refusals map to statuses and never carry a path or a stack (197.93156ms)
✔ humanCall speaks the human transport protocol without blocking the server (2844.510877ms)
✔ busReader takes --business, else the live bus host, else refuses (176.820627ms)
✔ humanCall kills a transport that runs past its timeout (529.116722ms)
✔ serve refuses a --business value that is not a business id (281.267378ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3811.106505ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2720.620433ms)
✔ conversation view: a newer session with no readable history keeps the marker (1522.129375ms)
✔ conversation view: seats without history say so and offer no reply (792.600848ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3909.957477ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55212.888172ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.906873ms)
✔ credential state from the date alone, as the bus states it (0.302761ms)
✖ queue: rows, one row, ids and methods, notes; no path or id from a row (2388.033224ms)
✔ queue: a refused read fails closed with the store's text, redacted (1416.198428ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (10.872099ms)
✔ business: missing or invalid file refuses with the module's text, redacted (11.147121ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (11.425321ms)
✔ settings: release, board, loopback address, notifier binding name only (836.630321ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3928.415346ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22243.032316ms)
✔ loopback host and board origin fail closed (7.301129ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (119.905625ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (162.644707ms)
✔ unreachable board reports URL; CLI rejects unsupported options (2049.828497ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.46393ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.928379ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.39511ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7056.354698ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2889.413893ms)
✔ Queue, Business and Settings in their five states, with no move button (4816.099964ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (2441.977189ms)
✔ the server log names no config path when the system config refuses (275.214472ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55832.586028
✖ failing tests:
test at tests/reads.test.mjs:40:1
✖ queue: rows, one row, ids and methods, notes; no path or id from a row (2388.033224ms)
AssertionError [ERR_ASSERTION]: /api/queue
200 !== 405
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:61:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 200,
expected: 405,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/serve.mjs b/packages/webui/src/serve.mjs
index 8e89e3ed..869ac527 100644
--- a/packages/webui/src/serve.mjs
+++ b/packages/webui/src/serve.mjs
@@ -65,7 +65,7 @@ async function busRead(res, bus, verb, search) {
}
// Row 54 (#1543): GET only. `reads` is consoleReads() from reads.mjs; its
// bodies and messages arrive redacted. No route here writes or calls out.
-const QUEUE_ID = /^[1-9]\d{0,5}$/;
+const QUEUE_ID = /^\d+$/;
async function consoleRead(res, read) {
try { return json(res, 200, await read()); } catch (err) {
const code = err instanceof ReadError ? err.code : 'invalid-response';
@@ -0,0 +1,67 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4334.088061ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4563.597523ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6981.519232ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (301.49155ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (121.989525ms)
✔ bus refusals map to statuses and never carry a path or a stack (85.758336ms)
✔ humanCall speaks the human transport protocol without blocking the server (3239.318814ms)
✔ busReader takes --business, else the live bus host, else refuses (159.127953ms)
✔ humanCall kills a transport that runs past its timeout (529.138059ms)
✔ serve refuses a --business value that is not a business id (280.268697ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4407.36187ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2733.008688ms)
✔ conversation view: a newer session with no readable history keeps the marker (2070.930421ms)
✔ conversation view: seats without history say so and offer no reply (1565.67244ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4194.007266ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55288.825077ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (3.066497ms)
✔ credential state from the date alone, as the bus states it (0.285937ms)
✖ queue: rows, one row, ids and methods, notes; no path or id from a row (2508.769179ms)
✔ queue: a refused read fails closed with the store's text, redacted (1702.717269ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (10.897652ms)
✔ business: missing or invalid file refuses with the module's text, redacted (13.918497ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (19.626303ms)
✔ settings: release, board, loopback address, notifier binding name only (421.789955ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4360.92173ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22414.895262ms)
✔ loopback host and board origin fail closed (8.368719ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (129.667475ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (133.870046ms)
✔ unreachable board reports URL; CLI rejects unsupported options (2069.33384ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.35725ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (3.160257ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.057927ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7383.171842ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3592.513343ms)
✔ Queue, Business and Settings in their five states, with no move button (4883.523192ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (3931.282217ms)
✔ the server log names no config path when the system config refuses (211.720622ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55939.200557
✖ failing tests:
test at tests/reads.test.mjs:40:1
✖ queue: rows, one row, ids and methods, notes; no path or id from a row (2508.769179ms)
AssertionError [ERR_ASSERTION]: 0
404 !== 400
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:59:72)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 404,
expected: 400,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,12 @@
diff --git a/packages/webui/src/serve.mjs b/packages/webui/src/serve.mjs
index 8e89e3ed..abb97b7f 100644
--- a/packages/webui/src/serve.mjs
+++ b/packages/webui/src/serve.mjs
@@ -120,7 +120,6 @@ export async function startServer({ host = '127.0.0.1', port = 7330, board = DEF
if (req.method !== 'GET') return json(res, 405, { error: 'method not allowed' });
const a = server.address(), address = `http://${a.family === 'IPv6' ? `[${a.address}]` : a.address}:${a.port}/`;
if (path === '/api/settings') return consoleRead(res, async () => ({ ...(reads ? await reads.settings() : { release: null, business: null, notifier: { refused: 'not-configured', message: 'the Console has no reads configured' } }), board: upstream, address }));
- if (!reads) return json(res, 503, { error: 'not-configured', message: 'the Console has no reads configured' });
if (path === '/api/business') return consoleRead(res, () => reads.business());
if (path === '/api/queue') return consoleRead(res, () => reads.queue());
const id = path.slice('/api/queue/'.length);
@@ -0,0 +1,69 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4935.828828ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5556.981854ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5798.803355ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (243.736845ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (80.028636ms)
✔ bus refusals map to statuses and never carry a path or a stack (73.396795ms)
✔ humanCall speaks the human transport protocol without blocking the server (2140.799295ms)
✔ busReader takes --business, else the live bus host, else refuses (223.160338ms)
✔ humanCall kills a transport that runs past its timeout (546.378813ms)
✔ serve refuses a --business value that is not a business id (187.221128ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4872.852956ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2048.103464ms)
✔ conversation view: a newer session with no readable history keeps the marker (1540.691642ms)
✔ conversation view: seats without history say so and offer no reply (1200.708193ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5182.555675ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54000.807918ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.045458ms)
✔ credential state from the date alone, as the bus states it (0.279761ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1118.151989ms)
✔ queue: a refused read fails closed with the store's text, redacted (1143.076623ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (21.877954ms)
✔ business: missing or invalid file refuses with the module's text, redacted (21.429199ms)
✖ business without --business: the live bus host, else no-bus-host; no system config: not-configured (36.321892ms)
✔ settings: release, board, loopback address, notifier binding name only (1138.749147ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4776.151142ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22916.479064ms)
✔ loopback host and board origin fail closed (9.799039ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (125.929636ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (90.292384ms)
✔ unreachable board reports URL; CLI rejects unsupported options (971.714773ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.033591ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.367726ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.902784ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7534.182322ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2843.620239ms)
✔ Queue, Business and Settings in their five states, with no move button (5608.585604ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1701.527913ms)
✔ the server log names no config path when the system config refuses (324.537747ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54511.288835
✖ failing tests:
test at tests/reads.test.mjs:126:1
✖ business without --business: the live bus host, else no-bus-host; no system config: not-configured (36.321892ms)
AssertionError [ERR_ASSERTION]: /api/business
+ actual - expected
+ 'invalid-response'
- 'not-configured'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/reads.test.mjs:136:78)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 'invalid-response',
expected: 'not-configured',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index ef13c793..20f2c620 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -488,7 +488,7 @@
}
const failTitle = err => err.code === 'queue-refused' ? 'Queue refused the read' : err.status === 403 ? 'Bus refused the read' : ['not-configured', 'no-bus-host'].includes(err.code) ? (err.module ? 'Nothing to read' : 'No bus to read') : 'The read failed';
// A refusal fails closed: no kept data, the code in mono. Any other failure keeps the last read, labelled with its time.
- const refusal = err => err.status === 403 || ['not-configured', 'no-bus-host', 'queue-refused'].includes(err.code);
+ const refusal = err => err.status === 403 || ['not-configured', 'no-bus-host'].includes(err.code);
// After a refusal nothing read before is shown anywhere: not in a view, the palette or the Inbox count.
function forget() { last.clear(); inboxCount = null; }
const failedView = err => `${head(failTitle(err))}<div class="banner ${refusal(err) ? 'ref' : 'err'}" role="alert"><p>${refusal(err) ? `<b>refused: <code>${txt(err.code)}</code>.</b> ` : ''}${why(err)} Console shows nothing rather than a guess. Nothing was written.</p>${retry}</div>${empty('Nothing to show.', refusal(err) ? 'The view stays empty until the problem above is fixed.' : err.module ? 'Read again.' : 'Read again, or come back when the bus answers.')}`;
@@ -0,0 +1,58 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5437.41818ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5425.885595ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5405.715959ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (250.174144ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (101.189068ms)
✔ bus refusals map to statuses and never carry a path or a stack (84.11427ms)
✔ humanCall speaks the human transport protocol without blocking the server (2250.76167ms)
✔ busReader takes --business, else the live bus host, else refuses (180.702079ms)
✔ humanCall kills a transport that runs past its timeout (529.226821ms)
✔ serve refuses a --business value that is not a business id (172.053203ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4797.019702ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1812.125303ms)
✔ conversation view: a newer session with no readable history keeps the marker (1072.161095ms)
✔ conversation view: seats without history say so and offer no reply (977.720549ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5360.236268ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54038.945666ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (1.900312ms)
✔ credential state from the date alone, as the bus states it (0.329717ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1084.161379ms)
✔ queue: a refused read fails closed with the store's text, redacted (813.368263ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (23.033284ms)
✔ business: missing or invalid file refuses with the module's text, redacted (39.320861ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (89.915802ms)
✔ settings: release, board, loopback address, notifier binding name only (824.973907ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5421.603398ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23092.309356ms)
✔ loopback host and board origin fail closed (9.341519ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (161.875401ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (129.189318ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1115.164199ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.273333ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.491156ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.870097ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8029.584162ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2562.190099ms)
✖ Queue, Business and Settings in their five states, with no move button (13061.411383ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1195.131788ms)
✔ the server log names no config path when the system config refuses (211.973308ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54452.502364
✖ failing tests:
test at tests/views.test.mjs:63:1
✖ Queue, Business and Settings in their five states, with no move button (13061.411383ms)
Error: Uncaught (in promise) Error: Condition timed out: !!document.querySelector("#bus-view .banner.ref"): undefined
at Object.evaluate (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/browser.mjs:44:35)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:107:9)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index ef13c793..3d33625f 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -563,7 +563,7 @@
catch (err) {
const kept = reader(false);
try { if (refusal(err)) throw err; html = await view(kept, path, new URLSearchParams(query)); readAt = kept.oldest(); html = staleBanner(err) + html; state = 'stale'; }
- catch { html = failedView(err) + (path === '/settings' ? appearance() : ''); state = refusal(err) ? 'refused' : 'failed'; }
+ catch { html = failedView(err); state = refusal(err) ? 'refused' : 'failed'; }
if (refusal(err)) forget();
}
if (g !== gen) return;
@@ -0,0 +1,67 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5359.138834ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5574.687643ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6394.275362ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (400.569442ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (335.69022ms)
✔ bus refusals map to statuses and never carry a path or a stack (170.26143ms)
✔ humanCall speaks the human transport protocol without blocking the server (2948.747983ms)
✔ busReader takes --business, else the live bus host, else refuses (559.020686ms)
✔ humanCall kills a transport that runs past its timeout (655.789471ms)
✔ serve refuses a --business value that is not a business id (175.846302ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5298.811007ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1890.466511ms)
✔ conversation view: a newer session with no readable history keeps the marker (1275.488352ms)
✔ conversation view: seats without history say so and offer no reply (821.434352ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4194.197709ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55288.083268ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.005061ms)
✔ credential state from the date alone, as the bus states it (0.303587ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2465.438021ms)
✔ queue: a refused read fails closed with the store's text, redacted (1303.981495ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (11.316386ms)
✔ business: missing or invalid file refuses with the module's text, redacted (16.125405ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (12.424481ms)
✔ settings: release, board, loopback address, notifier binding name only (1625.559026ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4050.738206ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22337.434477ms)
✔ loopback host and board origin fail closed (8.803767ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (117.180827ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (520.6679ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1881.24091ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.537959ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.453414ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.732381ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7317.414925ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2843.296748ms)
✖ Queue, Business and Settings in their five states, with no move button (5449.283824ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1747.082479ms)
✔ the server log names no config path when the system config refuses (267.75453ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55852.086444
✖ failing tests:
test at tests/views.test.mjs:63:1
✖ Queue, Business and Settings in their five states, with no move button (5449.283824ms)
AssertionError [ERR_ASSERTION]: #/settings
0 !== 2
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:114:16)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 0,
expected: 2,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index ef13c793..5eae9ad7 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -624,7 +624,7 @@
const decisions = (last.get('inbox')?.rows || []).map(d => ({ href: `#/inbox/${enc(d.id)}`, label: first(d.question), group: `decision ${short(d.id)}` }));
const tasks = (last.get('tasks')?.rows || []).filter(r => TASK.test(String(r.task_ref))).map(r => { const t = task(r); return { href: `#/tasks/${enc(t.ref)}`, label: `#${t.id} ${t.title ?? '(no title in the current read)'}`, group: 'task' }; });
const rows = (last.get('api:queue')?.rows || []).filter(r => Number.isInteger(r.id)).map(r => ({ href: `#/queue/${r.id}`, label: `${r.id} ${r.piece ?? ''}`, group: `queue row, ${r.state}` }));
- pkAll = [...VIEWS.map(([href, label]) => ({ href, label, group: 'view' })), ...decisions, ...tasks, ...rows];
+ pkAll = [...VIEWS.map(([href, label]) => ({ href, label, group: 'view' })), ...decisions, ...tasks];
}
function pkFill() {
const q = $('cmdk-input').value.trim().toLowerCase();
@@ -0,0 +1,58 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5084.508493ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5458.239348ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5740.696949ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (386.426731ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (97.982497ms)
✔ bus refusals map to statuses and never carry a path or a stack (74.793341ms)
✔ humanCall speaks the human transport protocol without blocking the server (2334.988593ms)
✔ busReader takes --business, else the live bus host, else refuses (271.395983ms)
✔ humanCall kills a transport that runs past its timeout (538.048771ms)
✔ serve refuses a --business value that is not a business id (235.772279ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4998.478789ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1842.085999ms)
✔ conversation view: a newer session with no readable history keeps the marker (1126.877575ms)
✔ conversation view: seats without history say so and offer no reply (1030.515436ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4090.593159ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (44422.737955ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.404832ms)
✔ credential state from the date alone, as the bus states it (3.015192ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1584.305698ms)
✔ queue: a refused read fails closed with the store's text, redacted (1044.686503ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (32.366721ms)
✔ business: missing or invalid file refuses with the module's text, redacted (44.982778ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (62.791652ms)
✔ settings: release, board, loopback address, notifier binding name only (1038.015137ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4742.884676ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23522.751503ms)
✔ loopback host and board origin fail closed (11.47803ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (142.545877ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (98.818306ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1215.200025ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.712102ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (3.277194ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.035858ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8498.730424ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2527.827632ms)
✖ Queue, Business and Settings in their five states, with no move button (15632.85447ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1206.864572ms)
✔ the server log names no config path when the system config refuses (211.188726ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 44945.766974
✖ failing tests:
test at tests/views.test.mjs:63:1
✖ Queue, Business and Settings in their five states, with no move button (15632.85447ms)
Error: Uncaught (in promise) Error: Condition timed out: [...document.querySelectorAll("#cmdk-list [data-href]")].map(e=>e.dataset.href).includes("#/queue/7"): undefined
at Object.evaluate (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/browser.mjs:44:35)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:167:34)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index ef13c793..9cc485c7 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -529,7 +529,7 @@
function keep() {
const a = document.activeElement, view = $('bus-view');
return {
- href: view.contains(a) ? a.getAttribute('href') : null, copy: view.contains(a) ? a.dataset?.copy : null, retry: view.contains(a) && a.hasAttribute?.('data-retry'), mirror: view.contains(a) ? a.dataset?.mirror : null, h1: view.contains(a) && a.tagName === 'H1',
+ href: view.contains(a) ? a.getAttribute('href') : null, copy: view.contains(a) ? a.dataset?.copy : null, retry: view.contains(a) && a.hasAttribute?.('data-retry'), h1: view.contains(a) && a.tagName === 'H1',
open: [...view.querySelectorAll('details[id]')].filter(d => d.open).map(d => d.id), status: $('copy-status')?.textContent || '',
};
}
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3208.32569ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4897.875723ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5614.816105ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (310.158448ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (468.360126ms)
✔ bus refusals map to statuses and never carry a path or a stack (72.552986ms)
✔ humanCall speaks the human transport protocol without blocking the server (2650.653791ms)
✔ busReader takes --business, else the live bus host, else refuses (157.502233ms)
✔ humanCall kills a transport that runs past its timeout (530.96326ms)
✔ serve refuses a --business value that is not a business id (194.43599ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3091.904095ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2969.867887ms)
✔ conversation view: a newer session with no readable history keeps the marker (1296.565486ms)
✔ conversation view: seats without history say so and offer no reply (1173.616968ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4948.108811ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53770.941289ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.081629ms)
✔ credential state from the date alone, as the bus states it (0.257325ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2362.77099ms)
✔ queue: a refused read fails closed with the store's text, redacted (799.047262ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (33.870027ms)
✔ business: missing or invalid file refuses with the module's text, redacted (39.487155ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (23.133068ms)
✔ settings: release, board, loopback address, notifier binding name only (1099.603118ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4997.9674ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22093.780558ms)
✔ loopback host and board origin fail closed (8.135877ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (167.400008ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (223.31773ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1919.691821ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.379393ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.515625ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.892738ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7173.088852ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2568.806432ms)
✔ Queue, Business and Settings in their five states, with no move button (4733.117204ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1919.786753ms)
✔ the server log names no config path when the system config refuses (212.79365ms)
ℹ tests 38
ℹ suites 0
ℹ pass 38
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54905.986885
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index ef13c793..86d63a83 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -667,7 +667,7 @@
// Settings' appearance selects drive the command bar's, and follow them back.
document.addEventListener('change', e => {
const m = e.target.closest?.('#bus-view select[data-mirror]');
- if (m) { const src = $(m.dataset.mirror); src.value = m.value; src.dispatchEvent(new Event('change')); return; }
+ if (m) return;
if (e.target.id === 'palette' || e.target.id === 'mode') for (const s of document.querySelectorAll(`#bus-view select[data-mirror="${e.target.id}"]`)) s.value = e.target.value;
});
window.addEventListener('hashchange', () => render(true));
@@ -0,0 +1,58 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4120.956867ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4824.216209ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6289.244588ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (206.539641ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (305.656532ms)
✔ bus refusals map to statuses and never carry a path or a stack (364.895813ms)
✔ humanCall speaks the human transport protocol without blocking the server (3091.114734ms)
✔ busReader takes --business, else the live bus host, else refuses (377.459739ms)
✔ humanCall kills a transport that runs past its timeout (530.188869ms)
✔ serve refuses a --business value that is not a business id (181.986594ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3680.756898ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2693.823091ms)
✔ conversation view: a newer session with no readable history keeps the marker (1130.580172ms)
✔ conversation view: seats without history say so and offer no reply (1135.919719ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3711.979432ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55493.606219ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (2.108104ms)
✔ credential state from the date alone, as the bus states it (2.57012ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (2317.660531ms)
✔ queue: a refused read fails closed with the store's text, redacted (1326.760192ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (14.91271ms)
✔ business: missing or invalid file refuses with the module's text, redacted (13.924507ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (34.544246ms)
✔ settings: release, board, loopback address, notifier binding name only (958.427757ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3741.282376ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22397.184221ms)
✔ loopback host and board origin fail closed (8.88274ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (119.436011ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (78.584529ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1912.139073ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.29965ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.467233ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.940463ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7505.57196ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2711.484056ms)
✖ Queue, Business and Settings in their five states, with no move button (16338.942804ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (5582.819377ms)
✔ the server log names no config path when the system config refuses (326.046882ms)
ℹ tests 38
ℹ suites 0
ℹ pass 37
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55934.909432
✖ failing tests:
test at tests/views.test.mjs:63:1
✖ Queue, Business and Settings in their five states, with no move button (16338.942804ms)
Error: Uncaught (in promise) Error: Condition timed out: document.querySelector("#mode").value==="dark": undefined
at Object.evaluate (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/browser.mjs:44:35)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/webui/tests/views.test.mjs:177:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
@@ -0,0 +1,13 @@
diff --git a/packages/queue/src/store.mjs b/packages/queue/src/store.mjs
index 237d3525..ca6c1837 100644
--- a/packages/queue/src/store.mjs
+++ b/packages/queue/src/store.mjs
@@ -815,7 +815,7 @@ export function list(opts) {
// lead decision 83). Same unlocked read as list; writes nothing.
export function rows(opts) {
const r = readState(opts);
- return { rows: rowsArray(r.state), err: r.notes, code: 0 };
+ return { rows: rowsArray(r.state), err: [], code: 0 };
}
export function show(opts, id) {
@@ -0,0 +1,182 @@
✔ F1: an ordinary commit after update-ref is refused until step 8; then it commits and the queue stays at C (1213.513287ms)
✔ F1: a plain `commit -e` whose guard ran before update-ref fails at its own HEAD update (1069.107357ms)
ℹ git commit -e: index.lock free during the editor
✔ F1: a `commit -e -- path` whose guard ran before update-ref fails at its own HEAD update (974.487379ms)
ℹ git commit -e -- src.txt: index.lock held during the editor
✔ F1: step 8 with index.lock held exits 3, and ordinary commits stay refused until the printed command runs (1116.046618ms)
✔ F1: HEAD moving after the step-7 guard check and before update-ref: refused, nothing published (1026.511233ms)
✔ F1: H is recorded before the canary, so HEAD moving during the step-1 canary is refused at step 7 (1006.880591ms)
✔ F1: a queue commit landing after H is recorded: step 1 says HEAD moved, not the guard (1385.680207ms)
✔ F1: a queue commit landing between the HEAD check and the canary: the failed clean run is reported as HEAD moved (1026.268035ms)
✔ F1: a shared-index change during the procedure is not committed (1107.139632ms)
✔ F1: a queue path staged after update-ref: step 8 stops and touches nothing (1064.514494ms)
✔ F1: a missing or a different hook refuses (794.853144ms)
✔ F1: same bytes without the exec bit, a symlinked hook, and core.hooksPath in the local or global scope each refuse before update-ref (1241.997413ms)
✔ F1: the canary refuses a hook that git would not run (733.099779ms)
✔ F1: the guard deactivated after step 1 is refused at the step-7 recheck (1139.731222ms)
✔ bootstrap: implementation-only HEAD, the guard, genesis, the --genesis commit, then an extending commit (903.361989ms)
✔ bootstrap: --genesis with a base present, no base without --genesis, an op before the first commit, a changed map, another branch (926.505232ms)
✔ bootstrap: the archived tests and validator run outside any repository (907.817519ms)
✔ general: an unrelated staged file stays staged, and the committed blobs are the snapshot bytes (921.470664ms)
✔ general: a queue write after the snapshot is not committed (1352.817484ms)
✔ general: a snapshot whose log does not extend the base refuses (1111.754128ms)
✔ general: install-hook privilege, repair of a same-bytes hook, and its refusals (162.258953ms)
✔ general: environment overrides, a linked worktree and usage (667.207031ms)
✔ general: a queue path staged before the run refuses at step 1 (682.311835ms)
✔ general: HEAD's queue tests failing in the archive refuse (894.163937ms)
✔ genesis document serializes deterministically and replays (3.72264ms)
✔ a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization (2.261016ms)
✔ a tampered result, receipt or viewSha fails replay (2.069395ms)
✔ op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries (0.156196ms)
✔ add: defaults for an ordinary seat, privileged extras, refusals (3.698885ms)
✔ matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied (4.833452ms)
✔ matrix: release, review round, changes requested and waiting-on-jason (13.459417ms)
✔ matrix J5: in-review→done by the gate owner with evidence naming the current round (8.120048ms)
✔ review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it (18.90493ms)
✔ the row schema refuses a round with a null issue, and the A1 review shape (P2) (2.21006ms)
✔ matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not (1311.201423ms)
✔ matrix: blocked keeps the claim and returns only to previousState (3.468986ms)
✔ matrix J4: parking is Jason's, refused while required; unpark returns to queued (2.267256ms)
✔ field edits: who may change what (4.188321ms)
✔ set issues keeps a logged narrowing of closes (N10) (2.572373ms)
✔ text the table shows refuses \ and <, everywhere it enters (N8) (1.587104ms)
✔ every accepted text renders to nine cells on every row (N8) (19.691247ms)
✔ genesis: the map refuses an owner among its row's reviewers; replay doesn't (2026-10-04) (0.486984ms)
✔ times and dates must be calendar values, not just the shape (2026-10-04) (1.40233ms)
✔ replay holds every op id to the caller's rule (N11) (4.722269ms)
✔ note: owner, listed reviewer or privileged; empty clears (1.00409ms)
✔ assign moves the claim with the owner; done clears it (2.164224ms)
✔ render is byte-stable and escapes pipes (0.458507ms)
✔ view classification: current, genuine stale, edited stale marker, changed current body, markers (0.520792ms)
✔ next: resume, then review, then start, then wait, then nothing; lowest id first (15.736859ms)
✔ canonical args make a retry's identity independent of list order (0.294223ms)
✔ manifests, headings and blob ids (0.416495ms)
✔ the migration map: one queue-map block, exact keys (0.528395ms)
✔ every call but `queue` reaches the seat CLI exactly as before A2 (558.04744ms)
✔ `queue` reaches the queue CLI with the rest of the arguments (181.075919ms)
✔ the pre-A2 fixture is the script A2 changed (0.458276ms)
✔ acquire publishes the record by link; release removes only its own lock (10.631557ms)
✔ a kill between the temp write and the link leaves no lock (54.98358ms)
✔ a short or failed temp write refuses and leaves no lock and no temp (9.046649ms)
✔ a link error other than EEXIST refuses (6.03194ms)
✔ an error after the link releases the lock: unreadable gate, failing temp stat (25.058842ms)
✔ a release that fails on a gate path is reported, never a stack trace (P1) (37.873526ms)
✔ a paused holder: another writer waits 10 s, then refuses naming it live (10097.430167ms)
✔ two concurrent unlockers: the second refuses on the gate (32.774663ms)
✔ a writer publishing during an unlock, lock first: unlock sees it live and refuses (9.917845ms)
✔ a writer publishing during an unlock, gate first: the writer releases and refuses (11.895493ms)
✔ a gate swapped while held is left in place and reported, on success and on refusal (N1) (25.265671ms)
✔ a reused pid within one boot is mismatch; unlock removes the lock and never signals the process (22.564019ms)
✔ the same pid and start on a different boot is mismatch (0.467676ms)
✔ a foreign host is unknown whatever the local pid says; unlock refuses (70.360828ms)
✔ unreadable /proc: classification is unknown and acquire refuses (0.652675ms)
✔ invalid records: empty, unparsable, wrong keys, bad start or boot (0.540424ms)
✔ a stale gate blocks writers; --check-gate says mismatch for a reused pid (24.872192ms)
✔ a delayed release by a dead owner, after unlock and a new owner: the inode check keeps the new lock (32.477596ms)
✔ release checks the inode too: a byte-identical lock file with a new inode is left in place (13.653148ms)
✔ unlock refuses a live, unknown or invalid lock, and does nothing without one (60.577196ms)
✔ the migration map validates and renders the golden genesis table (3.280246ms)
✔ the marked QUEUE.md holds every row and parked item between its markers (1.587567ms)
✔ map-check reports each kind of drift (5.023352ms)
✔ a request posts once as the requester; a retry sends nothing (776.579847ms)
✔ each transport answer maps to posted, failed or uncertain (8.9 step 3) (4441.347113ms)
✔ the pre-send checks: GET user must name the requester, under the deadline (1570.572909ms)
✔ the lead's request refuses a token for login sage (1060.099198ms)
✔ the credential file: the seat's own, 0600, no symlink, never the shared default (974.653704ms)
✔ an unresolved request blocks a new request, a new round, waiting-on-jason and done (2565.280814ms)
✔ a same-op retry after a kill sends nothing, even with a stale view (3502.362113ms)
✔ a held lock at the outcome exits 3 and names what the transport said (772.010489ms)
✔ late outcomes: after an abandon, and after a resolve with the same or another id (2455.150991ms)
✔ resolve checks the comment: issue, markers, round, candidate and author (1780.398496ms)
✔ the lead resolves a seat's request: the comment's author is the requester, fetched with the lead's token (811.227356ms)
✔ validateRow checks a request round's shape, which every replayed entry must keep (518.426737ms)
✔ request, changes, a new candidate, approval: every round pinned; no review files (1835.462759ms)
✔ a row with no reviewers opens a round that posts nothing (1232.619181ms)
✔ verify-commit: a prospective tree must hold exactly the candidate's paths (1362.828077ms)
✔ semantics: v1 entries replay as before; review entries need v2 (505.983018ms)
✔ set reviewers refuses the row's owner (2026-09-28) (381.747755ms)
✔ the owner records no verdict, even as a listed reviewer (535.840723ms)
✔ a request comment over the length limit is not sent (633.418807ms)
✔ a late POST on a closed row leaves a conflict nothing can resolve, and resolve asks nothing (695.947063ms)
✔ a Jason-gated row reaches waiting-on-jason only on every reviewer's approval (2665.868291ms)
✔ genesis: refusals before anything is written (399.556692ms)
✔ genesis: the map must be committed, well formed, with committed briefs and seats (593.065097ms)
✔ genesis: markers, a stray witness, once only; a retry returns the receipt (565.934383ms)
✔ genesis: a file holding genesis alone with no witness is confirmed by sync or a retry (381.577139ms)
✔ canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works (644.239906ms)
✔ --by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12) (769.483708ms)
✔ op ids: missing, too long, reserved; a retry answers; another payload refuses (762.62937ms)
✔ a retried add returns the id it first allocated, after reassignment and after done (962.158705ms)
✔ Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round (703.083043ms)
✔ the review issue and the evidence round through the CLI (lead decision 23, 8.7) (1260.087325ms)
✔ claims and add defaults through the CLI; candidates are manifests or reachable commits (926.025142ms)
✔ add, set reviewers and assign refuse the row's owner as a reviewer (611.721552ms)
✔ the working-brief check: a changed working copy refuses the start and flags next (688.832413ms)
✔ next: resume first, then nothing for an idle seat; needs a seat (302.049161ms)
✔ view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes (755.381882ms)
✔ view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers (1320.096998ms)
✔ a hand edit to queue.json refuses every verb, reads included (623.985474ms)
✔ verify and render --check leave bytes and mtimes unchanged (456.693689ms)
✔ render is byte-stable across runs and repositories (323.632045ms)
✔ snapshot and verify --snapshot (853.89672ms)
✔ usage errors exit 4 (578.558636ms)
✔ a short write, ENOSPC or a file fsync failure: nothing visible, temp removed (287.804763ms)
✔ a rename failure: nothing visible, temp removed (160.964047ms)
✔ a directory fsync failure: uncertain, exit 3, no receipt; the tail refuses new ops; a retry confirms (221.921063ms)
✔ a directory fsync failure, then sync names the op (384.379841ms)
✔ a witness write failure: uncertain, durable, exit 3; the view is untouched (229.818869ms)
✔ the .git fsync after the witness rename fails: uncertain, exit 3, the witness says so (206.280197ms)
✔ confirming a tail fsyncs queue.json and docs/plans before the witness; either failure changes nothing (237.081979ms)
✔ the docs/plans fsync after the view rename fails: the op stands, the view is written, a warning says so (192.959497ms)
✔ a lock swapped while held is left in place and reported, on a receipt and on a refusal (201.519196ms)
✔ a lock that cannot be released after an op is reported, on a receipt and on a refusal (192.012053ms)
✔ unlock prints a swapped gate's warning on stderr, the result on stdout (152.234406ms)
✔ a view write that fails keeps the op and reports a stale view (181.103512ms)
✔ SIGKILL before the rename: nothing recorded; the retry removes the leftover temp (676.48917ms)
✔ SIGKILL after the rename, before the witness: the tail refuses new ops and sync names the op (709.803644ms)
✔ SIGKILL after the witness, before the view: the stale refusal names the op (627.947435ms)
✔ SIGKILL after the view, before the receipt: the retry returns the receipt (676.296653ms)
✔ git checkout between steps 1 and 7: step 7 refuses and nothing is written (217.160751ms)
✔ git stash restoring an older valid pair: history lost; accept-history needs privilege, a reason and --yes (1043.283064ms)
✔ a deleted witness: refused after the locked recheck; accept-history records it absent (333.644697ms)
✔ a header edit during a write: the op stands, the view write is skipped with a warning (227.120825ms)
✔ a reader paused between the witness and the file while a writer finishes: no lost-history report (222.117391ms)
✖ rows: every row as show prints it, in list order; writes nothing; same notes and refusals as list (286.928512ms)
✔ file-then-witness order forced by a hook: the locked recheck prevents a false report (282.274709ms)
✔ a writer paused before and after the witness rename: readers see a tail, then a match (211.829398ms)
✔ a true rollback is reported only after the locked recheck; a held lock names its holder instead (609.130001ms)
✔ an accept-history in progress: an unlocked reader waits on the lock and never reports lost history (653.178306ms)
✔ the platform check refuses other filesystems (131.913302ms)
✔ tmpfs passes only a test layer that allows it (N5) (318.184031ms)
✔ unlock keeps a multi-line lock record on stdout (P3) (250.143797ms)
ℹ tests 149
ℹ suites 0
ℹ pass 148
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 31150.650186
✖ failing tests:
test at tests/write.test.mjs:337:1
✖ rows: every row as show prints it, in list order; writes nothing; same notes and refusals as list (286.928512ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
+ []
- [
- 'rev 1 visible, not confirmed durable'
- ]
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r54a/mutwt/packages/queue/tests/write.test.mjs:349:10)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: [],
expected: [ 'rev 1 visible, not confirmed durable' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,14 @@
packages/queue/README.md: OK
packages/queue/src/store.mjs: OK
packages/queue/tests/write.test.mjs: OK
packages/webui/README.md: OK
packages/webui/src/cli.mjs: OK
packages/webui/src/public/app.js: OK
packages/webui/src/public/bus.js: OK
packages/webui/src/queue-read.mjs: OK
packages/webui/src/reads.mjs: OK
packages/webui/src/serve.mjs: OK
packages/webui/tests/reads-fixture.mjs: OK
packages/webui/tests/reads.test.mjs: OK
packages/webui/tests/shell.test.mjs: OK
packages/webui/tests/views.test.mjs: OK
@@ -0,0 +1,53 @@
# Row 54 round 1 mutants: exact text, must match once. Run from the worktree root.
import sys
A = "packages/webui/src/public/app.js"
B = "packages/webui/src/public/bus.js"
R = "packages/webui/src/reads.mjs"
V = "packages/webui/src/serve.mjs"
C = "packages/webui/src/cli.mjs"
Q = "packages/queue/src/store.mjs"
M = {
# my four items
"D01": [(B, " clearTimeout(timer);\n const route = routeOf()", " const route = routeOf()")],
"D02": [(B, ", h1: view.contains(a) && a.tagName === 'H1'", "")],
"D03": [(B, " else if (k.h1) $('bus-view').querySelector('h1')?.focus({ preventScroll: true });\n", "")],
"D04": [(A, " $('conv-pick').replaceChildren();", "")],
"D05": [(A, " if (focused && document.activeElement === document.body) $('main').focus();\n", "")],
"D06": [(A, "if (focused && document.activeElement === document.body)", "if (document.activeElement === document.body)")],
# redaction and projection
"D07": [(R, r"""return s.replace(/(^|[\s('"`=,[{])\/[^\s'"`),;\]}]+/g, '$1<path>').replace(""", "return s.replace(")],
"D08": [(R, r""".replace(/(?<![\w.-])\d{17,20}(?![\w.-])/g, '<id>')""", "")],
"D09": [(R, "let s = unquote(String(text));", "let s = String(text);")],
"D10": [(R, "for (const [re, label] of known) s = s.replace(re, label);", "")],
"D11": [(R, "return scrub({ rows: doc.rows.map(queueSummary), notes: doc.notes, at: new Date(now()).toISOString() }, redact);", "return { rows: doc.rows.map(queueSummary), notes: doc.notes, at: new Date(now()).toISOString() };")],
"D12": [(R, "return scrub({ row, notes: doc.notes, at: new Date(now()).toISOString() }, redact);", "return { row, notes: doc.notes, at: new Date(now()).toISOString() };")],
"D13": [(R, "vars: pick(b.vars, VAR_KEYS)", "vars: b.vars")],
"D14": [(R, "vars: pick(r.vars, AGENT_KEYS)", "vars: r.vars")],
"D15": [(R, "return { service, account, role, dateKind, date, state: credentialState(service, date, now) };", "return { ...ref, service, account, role, dateKind, date, state: credentialState(service, date, now) };")],
"D16": [(R, "const fail = (code, message) => new ReadError(code, redact(message));", "const fail = (code, message) => new ReadError(code, message);")],
"D17": [(R, "return scrub({ release, business: id, notifier, at: new Date(now()).toISOString() }, redact);", "return { release, business: id, notifier, at: new Date(now()).toISOString() };")],
"D18": [(C, "(${redactor({ configDir: configDir() })(err.message)})", "(${err.message})")],
# queue child
"D19": [(R, "if (status === 2) return", "if (status === 3) return")],
"D20": [(R, "if (size > maxBytes) {", "if (false) {")],
"D21": [(R, "}, timeoutMs);", "}, timeoutMs * 1000);")],
"D22": [(R, "if (!state?.live) throw", "if (!state) throw")],
# routes
"D23": [(V, " if (req.method !== 'GET') return json(res, 405, { error: 'method not allowed' });\n const a = server.address()", " const a = server.address()")],
"D24": [(V, r"const QUEUE_ID = /^[1-9]\d{0,5}$/;", r"const QUEUE_ID = /^\d+$/;")],
"D25": [(V, " if (!reads) return json(res, 503, { error: 'not-configured', message: 'the Console has no reads configured' });\n", "")],
# views
"D26": [(B, "['not-configured', 'no-bus-host', 'queue-refused'].includes(err.code)", "['not-configured', 'no-bus-host'].includes(err.code)")],
"D27": [(B, "html = failedView(err) + (path === '/settings' ? appearance() : '');", "html = failedView(err);")],
"D28": [(B, "...decisions, ...tasks, ...rows];", "...decisions, ...tasks];")],
"D29": [(B, ", mirror: view.contains(a) ? a.dataset?.mirror : null", "")],
"D30": [(B, "if (m) { const src = $(m.dataset.mirror); src.value = m.value; src.dispatchEvent(new Event('change')); return; }", "if (m) return;")],
# store
"D31": [(Q, "return { rows: rowsArray(r.state), err: r.notes, code: 0 };", "return { rows: rowsArray(r.state), err: [], code: 0 };")],
}
for f, old, new in M[sys.argv[1]]:
s = open(f).read()
n = s.count(old)
if n != 1: sys.exit(f"{sys.argv[1]}: {n} matches in {f}")
open(f, "w").write(s.replace(old, new))
print(sys.argv[1], "applied")
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
# Each mutant against its package suite in a candidate worktree, then restores from the index.
export TMPDIR=~/darkwing-scratch/r54a/tmp
S=~/darkwing-scratch/r54a; M=$S/r1/mut; W=$S/mutwt
: > $M/summary.txt
echo "start $(date -u +%FT%TZ)" >> $M/summary.txt
for v in $(python3 -c 'import re;print(" ".join(sorted(set(re.findall(r"\"(D\d\d)\"", open("'$M'/mutate.py").read())))))'); do
(cd $W && git checkout -q -- packages/webui packages/queue && python3 $M/mutate.py $v > /dev/null && git diff > $M/$v.diff) || { echo "$v APPLY-FAILED" >> $M/summary.txt; continue; }
pkg=webui; [ $v = D31 ] && pkg=queue
(cd $W/packages/$pkg && env -u NODE_TEST_CONTEXT node --test 'tests/*.test.mjs' > $M/$v.txt 2>&1); e=$?
r=killed; [ $e = 0 ] && r=SURVIVED
echo "$v $r exit=$e $(grep -E '^ℹ (pass|fail)' $M/$v.txt | tr '\n' ' ')" >> $M/summary.txt
done
(cd $W && git checkout -q -- packages/webui packages/queue && sha256sum -c $S/pkt/candidate-manifest.sha256) > $M/manifest-after.txt 2>&1
echo "end $(date -u +%FT%TZ)" >> $M/summary.txt
echo DONE >> $M/summary.txt
@@ -0,0 +1,34 @@
start 2026-10-10T20:09:04Z
D01 killed exit=1 ℹ pass 37 ℹ fail 1
D02 killed exit=1 ℹ pass 37 ℹ fail 1
D03 killed exit=1 ℹ pass 37 ℹ fail 1
D04 killed exit=1 ℹ pass 37 ℹ fail 1
D05 killed exit=1 ℹ pass 37 ℹ fail 1
D06 killed exit=1 ℹ pass 36 ℹ fail 2
D07 killed exit=1 ℹ pass 34 ℹ fail 4
D08 killed exit=1 ℹ pass 35 ℹ fail 3
D09 killed exit=1 ℹ pass 36 ℹ fail 2
D10 killed exit=1 ℹ pass 34 ℹ fail 4
D11 killed exit=1 ℹ pass 36 ℹ fail 2
D12 killed exit=1 ℹ pass 37 ℹ fail 1
D13 killed exit=1 ℹ pass 36 ℹ fail 2
D14 SURVIVED exit=0 ℹ pass 38 ℹ fail 0
D15 killed exit=1 ℹ pass 36 ℹ fail 2
D16 killed exit=1 ℹ pass 36 ℹ fail 2
D17 killed exit=1 ℹ pass 37 ℹ fail 1
D18 killed exit=1 ℹ pass 37 ℹ fail 1
D19 killed exit=1 ℹ pass 37 ℹ fail 1
D20 SURVIVED exit=0 ℹ pass 38 ℹ fail 0
D21 SURVIVED exit=0 ℹ pass 38 ℹ fail 0
D22 SURVIVED exit=0 ℹ pass 38 ℹ fail 0
D23 killed exit=1 ℹ pass 37 ℹ fail 1
D24 killed exit=1 ℹ pass 37 ℹ fail 1
D25 killed exit=1 ℹ pass 37 ℹ fail 1
D26 killed exit=1 ℹ pass 37 ℹ fail 1
D27 killed exit=1 ℹ pass 37 ℹ fail 1
D28 killed exit=1 ℹ pass 37 ℹ fail 1
D29 SURVIVED exit=0 ℹ pass 38 ℹ fail 0
D30 killed exit=1 ℹ pass 37 ℹ fail 1
D31 killed exit=1 ℹ pass 148 ℹ fail 1
end 2026-10-10T20:37:00Z
DONE
@@ -0,0 +1 @@
tokens.css is current
@@ -0,0 +1,68 @@
✔ config directory and file path follow MOSAIC_CONFIG (2.249977ms)
✔ the fixture business validates and comes back frozen (4.532959ms)
✔ two instances may share a definition (1.765642ms)
✔ top-level refusals (4.302103ms)
✔ arbiters and projects (7.247986ms)
✔ role instances (3.272868ms)
✔ Vikunja bots (10.504188ms)
✔ a role without Vikunja takes no tracker block (2.672516ms)
✔ credential references match the definition's services (3.118138ms)
✔ launch (11.412474ms)
✔ loadBusiness: file checks (2.813378ms)
✔ loadBusiness: not a regular file (44.568346ms)
✔ loading writes nothing (1.108792ms)
✔ names that are Object.prototype properties don't count as declared (2.75627ms)
✔ the shipped example refuses as written and validates once filled in (0.698687ms)
✔ usage errors exit 4 (309.603048ms)
✔ validate: a good business exits 0 and prints instance digests (66.400709ms)
✔ validate: project files (320.30546ms)
✔ validate: missing files and a broken system config (249.861145ms)
✔ validate: credential reference problems exit 2 and name each one (87.767791ms)
✔ validate: a token file inside the repository is refused (117.810768ms)
✔ validate: role definitions come from MOSAIC_ROLES_DIR (243.797067ms)
✔ resolve: prints one instance's record (268.027885ms)
✔ resolve: refusals (504.503503ms)
✔ parse: exactly one of file or env, plus the service's date (3.825708ms)
✔ check: a good file has no problems (0.837736ms)
✔ check never opens the file: a write-only token passes (0.329552ms)
✔ check: file problems (0.839735ms)
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (0.828104ms)
✔ check: dates and environment references (0.405191ms)
✔ path and load (3.281625ms)
✔ refusals (1.710463ms)
✔ systemVars flattens the validated config (2.908558ms)
✔ precedence: system, business, project, project role, agent (7.104187ms)
✔ limits narrow the definition and never widen it (2.116209ms)
✔ role.launch stays within-role only for the instance the launch block names (5.209865ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (1.785323ms)
✔ limits.authority narrows cross-role actions too (1.147422ms)
✔ classify (1.173259ms)
✔ the record carries what the broker and launcher need (0.983132ms)
✔ digest: key order doesn't matter, any value change does (6.76918ms)
✔ refusals (2.209809ms)
✔ the four shipped version 2 roles load (4.29083ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.426069ms)
✔ shipped authority follows the note's table (0.727498ms)
✔ version 1 files keep loading with no authority (1.343343ms)
✔ the conductor policy isn't a role (0.279846ms)
✔ a missing role file is exit 4, a symbolic link too (0.526959ms)
✔ version 2 refusals (1.714782ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (2.951528ms)
✔ credentials: Gitea scopes (1.137161ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.59683ms)
✔ credentials: services (0.90866ms)
✔ contract: a non-empty regular Markdown file beside the role file (0.945123ms)
✔ every key names known layers and a merge rule (0.953922ms)
✔ unknown keys and wrong layers refuse (0.737586ms)
✔ types (1.882153ms)
✔ merge: defaults, then the most specific layer wins (0.298593ms)
✔ merge: limits only narrow, and provenance lists each source (0.390772ms)
✔ merge doesn't change its inputs (0.149627ms)
ℹ tests 60
ℹ suites 0
ℹ pass 60
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2240.424879
@@ -0,0 +1,159 @@
✔ F1: an ordinary commit after update-ref is refused until step 8; then it commits and the queue stays at C (1582.217933ms)
✔ F1: a plain `commit -e` whose guard ran before update-ref fails at its own HEAD update (1243.634971ms)
ℹ git commit -e: index.lock free during the editor
✔ F1: a `commit -e -- path` whose guard ran before update-ref fails at its own HEAD update (1307.636454ms)
ℹ git commit -e -- src.txt: index.lock held during the editor
✔ F1: step 8 with index.lock held exits 3, and ordinary commits stay refused until the printed command runs (1466.728544ms)
✔ F1: HEAD moving after the step-7 guard check and before update-ref: refused, nothing published (1449.863825ms)
✔ F1: H is recorded before the canary, so HEAD moving during the step-1 canary is refused at step 7 (1331.734065ms)
✔ F1: a queue commit landing after H is recorded: step 1 says HEAD moved, not the guard (1769.141616ms)
✔ F1: a queue commit landing between the HEAD check and the canary: the failed clean run is reported as HEAD moved (1318.314026ms)
✔ F1: a shared-index change during the procedure is not committed (1332.788746ms)
✔ F1: a queue path staged after update-ref: step 8 stops and touches nothing (1539.398665ms)
✔ F1: a missing or a different hook refuses (1044.492118ms)
✔ F1: same bytes without the exec bit, a symlinked hook, and core.hooksPath in the local or global scope each refuse before update-ref (1475.985527ms)
✔ F1: the canary refuses a hook that git would not run (845.399413ms)
✔ F1: the guard deactivated after step 1 is refused at the step-7 recheck (1133.113106ms)
✔ bootstrap: implementation-only HEAD, the guard, genesis, the --genesis commit, then an extending commit (1009.29169ms)
✔ bootstrap: --genesis with a base present, no base without --genesis, an op before the first commit, a changed map, another branch (1206.049891ms)
✔ bootstrap: the archived tests and validator run outside any repository (1101.54087ms)
✔ general: an unrelated staged file stays staged, and the committed blobs are the snapshot bytes (1114.500626ms)
✔ general: a queue write after the snapshot is not committed (1578.962692ms)
✔ general: a snapshot whose log does not extend the base refuses (1178.914348ms)
✔ general: install-hook privilege, repair of a same-bytes hook, and its refusals (188.536771ms)
✔ general: environment overrides, a linked worktree and usage (789.753089ms)
✔ general: a queue path staged before the run refuses at step 1 (720.416708ms)
✔ general: HEAD's queue tests failing in the archive refuse (1049.480391ms)
✔ genesis document serializes deterministically and replays (5.850242ms)
✔ a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization (4.131412ms)
✔ a tampered result, receipt or viewSha fails replay (3.479977ms)
✔ op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries (0.289694ms)
✔ add: defaults for an ordinary seat, privileged extras, refusals (6.060088ms)
✔ matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied (7.697249ms)
✔ matrix: release, review round, changes requested and waiting-on-jason (19.830649ms)
✔ matrix J5: in-review→done by the gate owner with evidence naming the current round (10.176003ms)
✔ review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it (22.710341ms)
✔ the row schema refuses a round with a null issue, and the A1 review shape (P2) (2.561966ms)
✔ matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not (1715.290938ms)
✔ matrix: blocked keeps the claim and returns only to previousState (3.688012ms)
✔ matrix J4: parking is Jason's, refused while required; unpark returns to queued (2.471631ms)
✔ field edits: who may change what (5.919518ms)
✔ set issues keeps a logged narrowing of closes (N10) (2.745535ms)
✔ text the table shows refuses \ and <, everywhere it enters (N8) (1.834375ms)
✔ every accepted text renders to nine cells on every row (N8) (23.225795ms)
✔ genesis: the map refuses an owner among its row's reviewers; replay doesn't (2026-10-04) (0.521975ms)
✔ times and dates must be calendar values, not just the shape (2026-10-04) (1.656362ms)
✔ replay holds every op id to the caller's rule (N11) (5.116922ms)
✔ note: owner, listed reviewer or privileged; empty clears (1.171954ms)
✔ assign moves the claim with the owner; done clears it (2.580929ms)
✔ render is byte-stable and escapes pipes (0.767339ms)
✔ view classification: current, genuine stale, edited stale marker, changed current body, markers (0.619389ms)
✔ next: resume, then review, then start, then wait, then nothing; lowest id first (19.292108ms)
✔ canonical args make a retry's identity independent of list order (0.334897ms)
✔ manifests, headings and blob ids (0.512792ms)
✔ the migration map: one queue-map block, exact keys (0.72184ms)
✔ every call but `queue` reaches the seat CLI exactly as before A2 (755.581607ms)
✔ `queue` reaches the queue CLI with the rest of the arguments (239.070561ms)
✔ the pre-A2 fixture is the script A2 changed (0.463753ms)
✔ acquire publishes the record by link; release removes only its own lock (13.725696ms)
✔ a kill between the temp write and the link leaves no lock (63.786428ms)
✔ a short or failed temp write refuses and leaves no lock and no temp (7.248293ms)
✔ a link error other than EEXIST refuses (9.332931ms)
✔ an error after the link releases the lock: unreadable gate, failing temp stat (19.805095ms)
✔ a release that fails on a gate path is reported, never a stack trace (P1) (59.716098ms)
✔ a paused holder: another writer waits 10 s, then refuses naming it live (10129.786417ms)
✔ two concurrent unlockers: the second refuses on the gate (70.378457ms)
✔ a writer publishing during an unlock, lock first: unlock sees it live and refuses (33.78472ms)
✔ a writer publishing during an unlock, gate first: the writer releases and refuses (44.762526ms)
✔ a gate swapped while held is left in place and reported, on success and on refusal (N1) (41.270041ms)
✔ a reused pid within one boot is mismatch; unlock removes the lock and never signals the process (21.309492ms)
✔ the same pid and start on a different boot is mismatch (1.974589ms)
✔ a foreign host is unknown whatever the local pid says; unlock refuses (113.259974ms)
✔ unreadable /proc: classification is unknown and acquire refuses (1.031732ms)
✔ invalid records: empty, unparsable, wrong keys, bad start or boot (0.30246ms)
✔ a stale gate blocks writers; --check-gate says mismatch for a reused pid (28.056769ms)
✔ a delayed release by a dead owner, after unlock and a new owner: the inode check keeps the new lock (54.661063ms)
✔ release checks the inode too: a byte-identical lock file with a new inode is left in place (9.802189ms)
✔ unlock refuses a live, unknown or invalid lock, and does nothing without one (48.357976ms)
✔ the migration map validates and renders the golden genesis table (4.149216ms)
✔ the marked QUEUE.md holds every row and parked item between its markers (2.255405ms)
✔ map-check reports each kind of drift (5.936848ms)
✔ a request posts once as the requester; a retry sends nothing (1091.194918ms)
✔ each transport answer maps to posted, failed or uncertain (8.9 step 3) (5607.310149ms)
✔ the pre-send checks: GET user must name the requester, under the deadline (2230.578378ms)
✔ the lead's request refuses a token for login sage (1332.927063ms)
✔ the credential file: the seat's own, 0600, no symlink, never the shared default (1325.273979ms)
✔ an unresolved request blocks a new request, a new round, waiting-on-jason and done (3417.324146ms)
✔ a same-op retry after a kill sends nothing, even with a stale view (3985.134768ms)
✔ a held lock at the outcome exits 3 and names what the transport said (795.443994ms)
✔ late outcomes: after an abandon, and after a resolve with the same or another id (3149.311768ms)
✔ resolve checks the comment: issue, markers, round, candidate and author (2019.64599ms)
✔ the lead resolves a seat's request: the comment's author is the requester, fetched with the lead's token (855.267378ms)
✔ validateRow checks a request round's shape, which every replayed entry must keep (577.49109ms)
✔ request, changes, a new candidate, approval: every round pinned; no review files (2055.811499ms)
✔ a row with no reviewers opens a round that posts nothing (1539.080552ms)
✔ verify-commit: a prospective tree must hold exactly the candidate's paths (1569.317305ms)
✔ semantics: v1 entries replay as before; review entries need v2 (629.911317ms)
✔ set reviewers refuses the row's owner (2026-09-28) (549.913259ms)
✔ the owner records no verdict, even as a listed reviewer (579.334645ms)
✔ a request comment over the length limit is not sent (668.113143ms)
✔ a late POST on a closed row leaves a conflict nothing can resolve, and resolve asks nothing (691.457831ms)
✔ a Jason-gated row reaches waiting-on-jason only on every reviewer's approval (3083.330206ms)
✔ genesis: refusals before anything is written (503.286821ms)
✔ genesis: the map must be committed, well formed, with committed briefs and seats (807.224701ms)
✔ genesis: markers, a stray witness, once only; a retry returns the receipt (642.697502ms)
✔ genesis: a file holding genesis alone with no witness is confirmed by sync or a retry (496.925873ms)
✔ canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works (751.509475ms)
✔ --by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12) (1104.107929ms)
✔ op ids: missing, too long, reserved; a retry answers; another payload refuses (1032.843906ms)
✔ a retried add returns the id it first allocated, after reassignment and after done (1280.03922ms)
✔ Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round (1013.810301ms)
✔ the review issue and the evidence round through the CLI (lead decision 23, 8.7) (1770.540186ms)
✔ claims and add defaults through the CLI; candidates are manifests or reachable commits (1107.169211ms)
✔ add, set reviewers and assign refuse the row's owner as a reviewer (743.959607ms)
✔ the working-brief check: a changed working copy refuses the start and flags next (771.199602ms)
✔ next: resume first, then nothing for an idle seat; needs a seat (349.575374ms)
✔ view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes (1041.998524ms)
✔ view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers (1637.483676ms)
✔ a hand edit to queue.json refuses every verb, reads included (825.767395ms)
✔ verify and render --check leave bytes and mtimes unchanged (520.027665ms)
✔ render is byte-stable across runs and repositories (449.181744ms)
✔ snapshot and verify --snapshot (968.801434ms)
✔ usage errors exit 4 (598.943632ms)
✔ a short write, ENOSPC or a file fsync failure: nothing visible, temp removed (415.053673ms)
✔ a rename failure: nothing visible, temp removed (228.783561ms)
✔ a directory fsync failure: uncertain, exit 3, no receipt; the tail refuses new ops; a retry confirms (281.646422ms)
✔ a directory fsync failure, then sync names the op (465.133245ms)
✔ a witness write failure: uncertain, durable, exit 3; the view is untouched (249.137788ms)
✔ the .git fsync after the witness rename fails: uncertain, exit 3, the witness says so (240.58536ms)
✔ confirming a tail fsyncs queue.json and docs/plans before the witness; either failure changes nothing (253.025834ms)
✔ the docs/plans fsync after the view rename fails: the op stands, the view is written, a warning says so (252.049306ms)
✔ a lock swapped while held is left in place and reported, on a receipt and on a refusal (264.865376ms)
✔ a lock that cannot be released after an op is reported, on a receipt and on a refusal (267.401478ms)
✔ unlock prints a swapped gate's warning on stderr, the result on stdout (185.671145ms)
✔ a view write that fails keeps the op and reports a stale view (247.337462ms)
✔ SIGKILL before the rename: nothing recorded; the retry removes the leftover temp (801.261529ms)
✔ SIGKILL after the rename, before the witness: the tail refuses new ops and sync names the op (910.629511ms)
✔ SIGKILL after the witness, before the view: the stale refusal names the op (732.124673ms)
✔ SIGKILL after the view, before the receipt: the retry returns the receipt (806.39669ms)
✔ git checkout between steps 1 and 7: step 7 refuses and nothing is written (366.39408ms)
✔ git stash restoring an older valid pair: history lost; accept-history needs privilege, a reason and --yes (1462.024959ms)
✔ a deleted witness: refused after the locked recheck; accept-history records it absent (671.590028ms)
✔ a header edit during a write: the op stands, the view write is skipped with a warning (320.642346ms)
✔ a reader paused between the witness and the file while a writer finishes: no lost-history report (293.522218ms)
✔ rows: every row as show prints it, in list order; writes nothing; same notes and refusals as list (338.132281ms)
✔ file-then-witness order forced by a hook: the locked recheck prevents a false report (323.527408ms)
✔ a writer paused before and after the witness rename: readers see a tail, then a match (346.577018ms)
✔ a true rollback is reported only after the locked recheck; a held lock names its holder instead (675.109699ms)
✔ an accept-history in progress: an unlocked reader waits on the lock and never reports lost history (681.300682ms)
✔ the platform check refuses other filesystems (207.116173ms)
✔ tmpfs passes only a test layer that allows it (N5) (432.118831ms)
✔ unlock keeps a multi-line lock record on stdout (P3) (252.882648ms)
ℹ tests 149
ℹ suites 0
ℹ pass 149
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 37844.810913
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4195.060555ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5313.351014ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5832.509445ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (262.208263ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (96.137203ms)
✔ bus refusals map to statuses and never carry a path or a stack (75.617629ms)
✔ humanCall speaks the human transport protocol without blocking the server (2402.204951ms)
✔ busReader takes --business, else the live bus host, else refuses (261.004396ms)
✔ humanCall kills a transport that runs past its timeout (531.06761ms)
✔ serve refuses a --business value that is not a business id (219.496891ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4264.028355ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2539.115102ms)
✔ conversation view: a newer session with no readable history keeps the marker (1134.530704ms)
✔ conversation view: seats without history say so and offer no reply (1038.970715ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4723.291602ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54477.701456ms)
✔ redactor: config, dataRoot, any absolute path, Discord ids and the JSON parser quote (5.867895ms)
✔ credential state from the date alone, as the bus states it (4.11703ms)
✔ queue: rows, one row, ids and methods, notes; no path or id from a row (1464.3776ms)
✔ queue: a refused read fails closed with the store's text, redacted (767.146119ms)
✔ business: names, vars on the allowlist, authority, credential metadata only (18.424716ms)
✔ business: missing or invalid file refuses with the module's text, redacted (61.182638ms)
✔ business without --business: the live bus host, else no-bus-host; no system config: not-configured (22.557023ms)
✔ settings: release, board, loopback address, notifier binding name only (1067.013458ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4212.447048ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23264.718506ms)
✔ loopback host and board origin fail closed (35.569514ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (172.362614ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (94.071947ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1133.747713ms)
✔ tokens.css and icons.svg are byte copies of docs/design (2.210876ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.976132ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.086146ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8126.602491ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2741.9107ms)
✔ Queue, Business and Settings in their five states, with no move button (5682.218013ms)
✔ seeded secrets, Discord ids and paths reach no response and no page; business refusals in the module's words (1554.959822ms)
✔ the server log names no config path when the system config refuses (265.736239ms)
ℹ tests 38
ℹ suites 0
ℹ pass 38
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55123.86591
@@ -0,0 +1,16 @@
start 2026-10-10T20:05:27Z
node-webui exit=0 ℹ pass 38 ℹ fail 0
node-queue exit=0 ℹ pass 149 ℹ fail 0
node-business exit=0 ℹ pass 60 ℹ fail 0
build-tokens --check exit=0 tokens.css is current
test-auth exit=0 selftest: 15 passed, 0 failed
test-conductor exit=0 selftest: 17 passed, 0 failed
test-config exit=0 selftest: 24 passed, 0 failed
test-discord exit=0 discord suite: 66 passed, 0 failed
test-extension-package exit=0 extension package selftest: 18 passed, 0 failed
test-foundation exit=0 selftest: 44 passed, 0 failed
test-queue exit=0 queue suite: 27 passed, 0 failed
test-release exit=0 selftest: 4 passed, 0 failed
test-task exit=0 selftest: 26 passed, 0 failed
end 2026-10-10T20:08:51Z
DONE
@@ -0,0 +1,17 @@
OK status with missing harness credential exits 3 and still lists accounts
OK status reports harness credential (read-only) + mosaic accounts
OK api key material never reaches output
OK oauth token material never reaches output
OK unparseable credential file exits 2
OK symlinked credential file exits 4
OK env-side credential names reported
OK env var values never reach output
OK accounts without an accounts dir reports none and creates nothing
OK accounts lists files and marks the active one
OK loose account perms flagged in listing
OK agent --auth with missing account file refuses (exit 4)
OK agent --auth with non-0600 account file refuses
OK agent --auth with invalid account name refuses
OK auth.sh without valid config refuses
selftest: 15 passed, 0 failed
@@ -0,0 +1,23 @@
On branch refactor
Your branch is up to date with 'origin/refactor'.
nothing to commit, working tree clean
OK dry-run: allowed change, exit 0, nothing committed (exit 0)
OK dry-run committed nothing
OK apply: allowed change exits 0 (exit 0)
OK apply: attribution in commit subject
OK apply: target tree clean after commit
OK disallowed path refused (exit 1)
OK disallowed path: target untouched
OK syntax gate refused broken .mjs (exit 1)
OK syntax gate: target untouched
OK suite failure refused (exit 1)
OK suite failure: target reverted to clean
OK disabled policy refused (exit 2)
OK disabled policy: target untouched
OK failed run refused (exit 1)
OK failed run: target untouched
OK missing run exits 4 (exit 4)
OK invalid policy exits 2 (exit 2)
selftest: 17 passed, 0 failed
@@ -0,0 +1,26 @@
OK absent adapter defaults to pi
OK adapter mock validates (exit 0)
OK unsupported adapter exits 2 (exit 2)
OK env exports adapter
OK bootstrap creates default when absent (exit 0)
OK bootstrap wrote config file
OK bootstrap is idempotent on existing config (exit 0)
OK bootstrap did not rewrite existing config
OK validate missing config exits 3 (exit 3)
OK malformed JSON exits 2 (exit 2)
OK unsupported configVersion exits 2 (exit 2)
OK unknown top-level key exits 2 (exit 2)
OK unknown execution key exits 2 (exit 2)
OK unsupported backend exits 2 (exit 2)
OK unsupported environment exits 2 (exit 2)
OK relative dataRoot exits 2 (exit 2)
OK non-canonical dataRoot exits 2 (exit 2)
OK filesystem root dataRoot exits 2 (exit 2)
OK home directory dataRoot exits 2 (exit 2)
OK dataRoot containing config dir exits 2 (exit 2)
OK control character in provider exits 2 (exit 2)
OK symlinked config file exits 2 (exit 2)
OK env exports resolve correctly
OK failed validation modified nothing
selftest: 24 passed, 0 failed
@@ -0,0 +1,70 @@
toolchain: node v26.8.1
OK syntax: packages/discord/src/approvals.mjs
OK syntax: packages/discord/src/authorize.mjs
OK syntax: packages/discord/src/binding.mjs
OK syntax: packages/discord/src/cli.mjs
OK syntax: packages/discord/src/connector.mjs
OK syntax: packages/discord/src/context.mjs
OK syntax: packages/discord/src/engine-pi.mjs
OK syntax: packages/discord/src/errors.mjs
OK syntax: packages/discord/src/gateway.mjs
OK syntax: packages/discord/src/git.mjs
OK syntax: packages/discord/src/journal.mjs
OK syntax: packages/discord/src/notify.mjs
OK syntax: packages/discord/src/rest.mjs
OK syntax: packages/discord/src/setspark.mjs
OK syntax: packages/discord/src/tools.mjs
OK syntax: packages/discord/src/web.mjs
OK syntax: packages/discord/bin/git-credential.mjs
OK syntax: packages/discord/extension/tools.mjs
OK syntax: packages/discord/tests/approvals.test.mjs
OK syntax: packages/discord/tests/authorize.test.mjs
OK syntax: packages/discord/tests/binding.test.mjs
OK syntax: packages/discord/tests/connector.test.mjs
OK syntax: packages/discord/tests/context.test.mjs
OK syntax: packages/discord/tests/engine.test.mjs
OK syntax: packages/discord/tests/fake-pi.mjs
OK syntax: packages/discord/tests/gateway.test.mjs
OK syntax: packages/discord/tests/git.test.mjs
OK syntax: packages/discord/tests/helpers.mjs
OK syntax: packages/discord/tests/journal.test.mjs
OK syntax: packages/discord/tests/notify.test.mjs
OK syntax: packages/discord/tests/recover.test.mjs
OK syntax: packages/discord/tests/rest.test.mjs
OK syntax: packages/discord/tests/setspark.test.mjs
OK syntax: packages/discord/tests/tools.test.mjs
OK syntax: packages/discord/tests/web.test.mjs
OK syntax: packages/discord/fixtures/claim-worker.mjs
OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs
OK syntax: scripts/discord.sh
OK syntax: scripts/discord-service.sh
OK packages/discord declares no dependencies
OK no bot-token-shaped string in packages/discord
OK fixture binding uses placeholder ids only
OK fixture binding validates
OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool
OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start
OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root
OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token
OK real pi with protocol vault adds reserve_id to the git verbs
OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key
OK real pi refuses a git key on a read-only root (fail closed)
OK real pi with the pilot flags (--no-tools) exposes no tool at all
OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test packages/discord/tests/ (ℹ pass 178)
OK scripts/discord.sh --help exits 0
OK scripts/discord.sh check without a binding exits 4
OK scripts/discord.sh recover without a binding exits 4
OK scripts/discord.sh reload without a binding exits 4
OK scripts/discord-service.sh without a command exits 4
OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP
OK service install writes the rendered unit (0644) and leaves no temp file
OK service install a second time reports unchanged
OK systemd-analyze verify accepts the rendered unit
OK service uninstall removes the unit file
OK service install with an unknown flag exits 4
OK service install with USER unset finishes and names the account for lingering
discord suite: 66 passed, 0 failed
@@ -0,0 +1,21 @@
OK initial ordinary-file install
OK installed tree matches canonical source
OK installed tree has no symlinks
OK check detects installation drift
OK sync refuses to overwrite installation drift
OK check detects an extra destination file
OK check detects an extra destination directory
OK check rejects a destination symlink
OK sync accepts a canonical source update
OK updated installation matches canonical source
scripts/test-extension-package.sh: line 14: 602290 Killed "$@" > /dev/null 2>&1
OK forced interruption kills the replacing process
OK next invocation recovers old consistent installation
OK interrupted replacement rolled back
OK sync succeeds after interruption recovery
OK unlocked stale lock file does not block
OK active lock refuses a concurrent sync
OK source symlink fails closed
OK nested second entrypoint fails closed
extension package selftest: 18 passed, 0 failed
@@ -0,0 +1,53 @@
toolchain: node v26.8.1, python 3.12.8, jsonschema 4.26.0
OK syntax: scripts/foundation-inspect.mjs
OK syntax: scripts/foundation/strict-json.mjs
OK syntax: scripts/foundation/canonical.mjs
OK syntax: scripts/foundation/resolve.mjs
OK syntax: scripts/foundation/validate-record.mjs
OK syntax: scripts/foundation/fixtures/build-fixtures.mjs
OK syntax: scripts/foundation/canonical.test.mjs
OK syntax: scripts/foundation/cli.test.mjs
OK syntax: scripts/foundation/fixtures.test.mjs
OK syntax: scripts/foundation/resolve.test.mjs
OK syntax: scripts/foundation/strict-json.test.mjs
OK syntax: scripts/foundation/verify-schema.py (ast only; no bytecode written)
OK fixture generator runs
OK checked-in fixtures/bundles equal a fresh generation
OK checked-in fixtures/raw equal a fresh generation
OK checked-in fixtures/index.json equal a fresh generation
OK checked-in demo bundles equal a fresh generation
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test scripts/foundation/ (ℹ pass 80)
OK differential schema oracle: PASS: differential schema oracle (finite corpus; compatibility evidence, not equivalence proof)
platform witness: strftime('%Y') for year 999 -> '999' (pinned checker refuses years 0001..0999)
node v26.8.1; corpus 1568 records (38 pinned fixtures, 478 unique bundle records, 1052 typeCase/mutation/lexical cases)
schema column: agree-valid 540, agree-invalid 991, DISAGREEMENTS 0; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10
profile column (schema-valid records only): profile-valid 510, profile-invalid 30
profile refusals asserted: 30 schema-agreed-valid records refused only by the strict typed-string profile (rule profile-pattern-mismatch), 12 declared by name; 73 named probes verified against declared schema/profile columns
OK oracle: zero schema-column disagreements with the pinned checker
OK oracle: strict-only profile refusals are counted and asserted
OK demo: permitted read preview exits 0 (exit 0)
OK demo: permitted file.change preview exits 0 (exit 0)
OK demo: assignment.change proposal is unresolved (exit 3) (exit 3)
OK demo: revoked registration is refused (exit 3) (exit 3)
OK demo: message is not authority (exit 3) (exit 3)
OK usage: no arguments exits 2 (exit 2)
OK io: missing file exits 4 (exit 4)
OK io: directory exits 4 (exit 4)
OK io: symlink exits 4 (O_NOFOLLOW) (exit 4)
OK bound: oversize fixture exits 2 (exit 2)
OK profile: one final LF in a typed selection id is refused before admission (exit 2) (exit 2)
OK profile: two final LFs fail the schema pattern itself (exit 2) (exit 2)
OK profile: escaped newlines in free-form text stay allowed (exit 0) (exit 0)
OK profile refusal is invalid-request/profile-pattern-mismatch with selection and operation withheld, value not echoed
OK text output starts with the disclaimer
OK json output is valid JSON with result allowed and exactly the charter §7 fields
OK json golden matches byte-for-byte
OK sandboxed bundle run (env -i, PATH=/nonexistent) produced the unresolved proposal
OK sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs
OK canary never printed (bundle run and credential-file run)
OK a non-bundle JSON file is refused at the shape gate, not read into output
OK no field of the non-bundle file is echoed
selftest: 44 passed, 0 failed
@@ -0,0 +1,35 @@
toolchain: node v26.8.1, git version 2.55.0
OK syntax: packages/queue/src/cli.mjs
OK syntax: packages/queue/src/errors.mjs
OK syntax: packages/queue/src/io.mjs
OK syntax: packages/queue/src/lock.mjs
OK syntax: packages/queue/src/queue.mjs
OK syntax: packages/queue/src/review.mjs
OK syntax: packages/queue/src/store.mjs
OK syntax: packages/queue/tests/commit.test.mjs
OK syntax: packages/queue/tests/data.test.mjs
OK syntax: packages/queue/tests/dispatch.test.mjs
OK syntax: packages/queue/tests/helpers.mjs
OK syntax: packages/queue/tests/lock.test.mjs
OK syntax: packages/queue/tests/migration.test.mjs
OK syntax: packages/queue/tests/review.test.mjs
OK syntax: packages/queue/tests/store.test.mjs
OK syntax: packages/queue/tests/write.test.mjs
OK syntax: packages/queue/tests/fixtures/fake-gitea.mjs
OK syntax: packages/queue/tests/fixtures/kill-at.mjs
OK syntax: packages/queue/tests/fixtures/lock-child.mjs
OK syntax: packages/queue/tests/fixtures/mosaic-pre-a2.sh
OK syntax: scripts/queue-commit.sh
OK syntax: scripts/git-hooks/pre-commit
OK syntax: scripts/mosaic
OK queue-commit.sh, the guard and scripts/mosaic are executable
OK packages/queue declares no dependencies
ℹ tests 149
ℹ pass 149
ℹ fail 0
OK node --test packages/queue/tests/
OK scripts/mosaic queue help
skip queue verify and render --check: this checkout (/home/jwoltje/darkwing-scratch/r54a/papply) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
queue suite: 27 passed, 0 failed
@@ -0,0 +1,17 @@
OK valid RELEASE resolves (exit 0)
OK invalid RELEASE exits 1 (exit 1)
OK missing RELEASE exits 1 (exit 1)
OK valid RELEASE leaves image tag consistent with version
OK status safe on empty state (exit 0)
OK status created no pointer
OK fault-injected activation refuses (exit 1)
OK refused activation wrote no pointer
OK refusal logged exactly once with valid fields
OK healthy activation succeeds (exit 0)
OK pointer written with valid fields
OK repeat activation succeeds (log grows) (exit 0)
OK log is append-only across activations
OK rollback without previous refuses (exit 1)
selftest: 14 passed, 0 failed
exit=0
@@ -0,0 +1,7 @@
OK valid RELEASE resolves (exit 0)
OK invalid RELEASE exits 1 (exit 1)
OK missing RELEASE exits 1 (exit 1)
OK valid RELEASE leaves image tag consistent with version
skip state-machine cases (docker daemon unavailable)
selftest: 4 passed, 0 failed
@@ -0,0 +1,32 @@
OK valid task validates (exit 0)
OK unknown task key exits 2 (exit 2)
OK unsupported taskVersion exits 2 (exit 2)
OK invalid task id exits 2 (exit 2)
OK empty prompt exits 2 (exit 2)
OK NUL in expectExact exits 2 (exit 2)
OK out-of-range timeout exits 2 (exit 2)
OK missing mission file exits 4 (exit 4)
OK task with valid mission validates (exit 0)
OK invalid mission exits 2 (exit 2)
OK validate missing task exits 4 (exit 4)
OK validation does not modify the task file
OK prune dry-run exits 0 (exit 0)
OK dry-run deleted nothing
OK prune --keep=2 --yes removes oldest (exit 0)
OK kept exactly 2 newest runs
OK newest run kept, oldest pruned
OK append-only receipt written (3 entries)
OK sessions/workspaces untouched by prune
OK prune with invalid keep exits 4 (exit 4)
skip adapter seam cases (docker daemon unavailable)
skip workspace/capability cases (docker daemon unavailable)
skip live task cases (docker unavailable)
OK onboard without name exits 4 (non-interactive) (exit 4)
OK onboard --name renders profile (exit 0)
OK profile written
OK canon structure: required filled, optional placeholdered
OK canon sections present
skip live recall pair: user recall run, recalled user name (docker daemon unavailable)
OK no agent identity on headless run
selftest: 26 passed, 0 failed
@@ -0,0 +1,28 @@
fixture arbiters: {"delivery":"pm","technical":"cto"}
B1 api arbiters: {"delivery":"pm","technical":"cto"}
B1 Arbiters dd: "none"
B2 mirrors: ["palette","mode"]
B2 palette before: ["SELECT","palette"]
B2 palette due refreshes: 1
B2 palette fired: 1
B2 palette after: ["BODY",""]
B2 mode before: ["SELECT","mode"]
B2 mode due refreshes: 1
B2 mode fired: 1
B2 mode after: ["BODY",""]
B2b before: ["harbor",["harbor","carmine","atlantic","terracotta","aubergine","mineral","cobalt","rosewood","graphite","grove"]]
B2b after: ["carmine","carmine"]
B3 before: "H1"
B3 fired: 1
B3 after: ["H1","Queue"]
B4 due on queue: 1
B4 due after nav: 1
✔ probes, seeded fixture (2076.585529ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2167.431061
@@ -0,0 +1,73 @@
// Darkwing row 54 round 1 browser probes on the seeded fixture with the real reads.
// B1: the Business view's Arbiters line. B2: focus on a Settings mirror select across the 10 s refresh.
// B3: focus on the H1 across the 10 s refresh. B4: navigation clears the due refresh.
// Run from a worktree's packages/webui: node --test <this file> with WT set to that worktree.
import { test, after } from 'node:test';
import { pathToFileURL } from 'node:url';
const WT = process.env.WT;
const u = p => pathToFileURL(`${WT}/packages/webui/${p}`).href;
const { browser } = await import(u('tests/browser.mjs'));
const { close } = await import(u('tests/fixture.mjs'));
const { startServer } = await import(u('src/serve.mjs'));
const { consoleReads } = await import(u('src/reads.mjs'));
const { REPO_ROLES } = await import(pathToFileURL(`${WT}/packages/business/tests/helpers.mjs`).href);
const { seeded, queueRepo } = await import(u('tests/reads-fixture.mjs'));
const out = [];
const note = (k, v) => { out.push(`${k}: ${JSON.stringify(v)}`); };
after(() => console.log(out.join('\n')));
const PROBE = '(()=>{const st=setTimeout,ct=clearTimeout;window.refreshes=new Map();window.setTimeout=(fn,ms,...a)=>{if(ms!==10000||!String(fn).includes("render(false)"))return st(fn,ms,...a);const id=st(()=>{refreshes.delete(id);fn()},ms);refreshes.set(id,fn);return id};window.clearTimeout=id=>{refreshes.delete(id);return ct(id)};window.fireRefresh=()=>{const due=[...refreshes];refreshes.clear();for(const[id,fn]of due){ct(id);fn()}return due.length}})();';
test('probes, seeded fixture', { timeout: 120000 }, async t => {
const s = seeded(t), repo = queueRepo(t);
note('fixture arbiters', s.doc.arbiters);
const web = await startServer({ port: 0, board: 'http://127.0.0.1:9', reads: consoleReads({ root: repo.root, env: repo.env, system: s.system, configDir: s.configDir, rolesDir: REPO_ROLES, business: 'acme' }) });
const b = await browser();
try {
const wait = expr => b.evaluate(`(async()=>{for(let i=0;i<200;i++){if(${expr})return true;await new Promise(r=>setTimeout(r,50))}throw new Error('Condition timed out: '+${JSON.stringify(expr)})})()`);
const go = async (hash, h1) => { await b.evaluate(`location.hash=${JSON.stringify(hash)}`); await wait(`document.querySelector("#bus-view h1")?.textContent.startsWith(${JSON.stringify(h1)}) && !document.querySelector("#bus-view [aria-busy]")`); };
await b.call('Page.addScriptToEvaluateOnNewDocument', { source: PROBE });
await b.viewport(1200, 900);
await b.navigate(`http://127.0.0.1:${web.address().port}/`);
await wait('document.querySelectorAll("#sections a").length===7');
// B1
await go('#/business', 'Business acme');
note('B1 api arbiters', await b.evaluate('fetch("/api/business").then(r=>r.json()).then(j=>j.business.arbiters)'));
note('B1 Arbiters dd', await b.evaluate('(()=>{const dt=[...document.querySelectorAll("#bus-view dt")].find(e=>e.textContent==="Arbiters");return dt?.nextElementSibling?.textContent})()'));
// B2
await go('#/settings', 'Settings');
const sels = await b.evaluate('[...document.querySelectorAll("#bus-view select[data-mirror]")].map(e=>e.dataset.mirror)');
note('B2 mirrors', sels);
for (const m of sels) {
await b.evaluate(`document.querySelector('#bus-view select[data-mirror="${m}"]').focus()`);
note(`B2 ${m} before`, await b.evaluate('[document.activeElement.tagName, document.activeElement.dataset?.mirror]'));
note(`B2 ${m} due refreshes`, await b.evaluate('window.refreshes.size'));
note(`B2 ${m} fired`, await b.evaluate('window.fireRefresh()'));
await wait('!document.querySelector("#bus-view [aria-busy]")');
await new Promise(r => setTimeout(r, 300));
note(`B2 ${m} after`, await b.evaluate('[document.activeElement.tagName, document.activeElement.dataset?.mirror ?? document.activeElement.id]'));
}
// B2b: change a mirror and see the command bar select follow
const m0 = sels[0];
note('B2b before', await b.evaluate(`[document.getElementById("${m0}").value, [...document.getElementById("${m0}").options].map(o=>o.value)]`));
await b.evaluate(`(()=>{const s=document.querySelector('#bus-view select[data-mirror="${m0}"]');const v=[...s.options].map(o=>o.value).find(v=>v!==s.value);s.value=v;s.dispatchEvent(new Event('change',{bubbles:true}));})()`);
note('B2b after', await b.evaluate(`[document.getElementById("${m0}").value, document.querySelector('#bus-view select[data-mirror="${m0}"]').value]`));
// B3
await go('#/queue', 'Queue');
await b.evaluate('(()=>{const h=document.querySelector("#bus-view h1");h.tabIndex=-1;h.focus()})()');
note('B3 before', await b.evaluate('document.activeElement.tagName'));
note('B3 fired', await b.evaluate('window.fireRefresh()'));
await wait('!document.querySelector("#bus-view [aria-busy]")');
await new Promise(r => setTimeout(r, 300));
note('B3 after', await b.evaluate('[document.activeElement.tagName, document.activeElement.textContent.slice(0,20)]'));
// B4
await go('#/queue', 'Queue');
note('B4 due on queue', await b.evaluate('window.refreshes.size'));
await go('#/business', 'Business acme');
note('B4 due after nav', await b.evaluate('window.refreshes.size'));
} finally { await b.close(); await close(web); }
});
@@ -0,0 +1,28 @@
fixture arbiters: {"delivery":"pm","technical":"cto"}
B1 api arbiters: {"delivery":"pm","technical":"cto"}
B1 Arbiters dd: "none"
B2 mirrors: ["palette","mode"]
B2 palette before: ["SELECT","palette"]
B2 palette due refreshes: 1
B2 palette fired: 1
B2 palette after: ["SELECT","palette"]
B2 mode before: ["SELECT","mode"]
B2 mode due refreshes: 1
B2 mode fired: 1
B2 mode after: ["SELECT","mode"]
B2b before: ["harbor",["harbor","carmine","atlantic","terracotta","aubergine","mineral","cobalt","rosewood","graphite","grove"]]
B2b after: ["carmine","carmine"]
B3 before: "H1"
B3 fired: 1
B3 after: ["H1","Queue"]
B4 due on queue: 1
B4 due after nav: 1
✔ probes, seeded fixture (2002.571259ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2077.350586
@@ -0,0 +1,18 @@
// P4f: the real queue-read.mjs when its store import fails at load (a broken
// or partial checkout). What reaches the read's message?
import { mkdtempSync, writeFileSync, mkdirSync, realpathSync, rmSync, copyFileSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
const WT = process.env.WT;
const { consoleReads } = await import(`${WT}/packages/webui/src/reads.mjs`);
const base = realpathSync(mkdtempSync(join(tmpdir(), 'dw-r54-imp-')));
for (const [name, store] of [['syntax', 'export function rows( {'], ['missing', null]]) {
const root = join(base, name);
mkdirSync(join(root, 'packages/webui/src'), { recursive: true }); mkdirSync(join(root, 'packages/queue/src'), { recursive: true });
copyFileSync(join(WT, 'packages/webui/src/queue-read.mjs'), join(root, 'packages/webui/src/queue-read.mjs'));
writeFileSync(join(root, 'packages/queue/src/errors.mjs'), 'export class QueueError extends Error {}');
if (store) writeFileSync(join(root, 'packages/queue/src/store.mjs'), store);
try { await consoleReads({ root }).queue(); console.log(`${name}: resolved`); }
catch (e) { console.log(`${name}: ${e.code}\n${e.message.replaceAll(base, '<BASE>')}\nleaks base: ${e.message.includes(base)}\n`); }
}
rmSync(base, { recursive: true, force: true });
@@ -0,0 +1,43 @@
syntax: read-failed
file://<BASE>/syntax/packages/queue/src/store.mjs:1
export function rows( {
SyntaxError: Unexpected end of input
at compileSourceTextModule (node:internal/modules/esm/utils:355:16)
at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:93:18)
at #translate (node:internal/modules/esm/loader:441:20)
at afterLoad (node:internal/modules/esm/loader:509:29)
at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:514:12)
at #getOrCreateModuleJobAfterResolve (node:internal/modules/esm/loader:567:36)
at afterResolve (node:internal/modules/esm/loader:614:52)
at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:620:12)
at ModuleJob.syncLink (node:internal/modules/esm/module_job:277:33)
at ModuleJob.link (node:internal/modules/esm/module_job:389:17)
Node.js v26.8.1
leaks base: true
missing: read-failed
node:internal/modules/esm/resolve:272
throw new ERR_MODULE_NOT_FOUND(
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '<path>' imported from <path>
at finalizeResolution (node:internal/modules/esm/resolve:272:11)
at moduleResolve (node:internal/modules/esm/resolve:879:10)
at defaultResolve (node:internal/modules/esm/resolve:1006:11)
at #cachedDefaultResolve (node:internal/modules/esm/loader:708:20)
at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:728:38)
at ModuleLoader.resolveSync (node:internal/modules/esm/loader:766:56)
at #resolve (node:internal/modules/esm/loader:690:17)
at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:610:35)
at ModuleJob.syncLink (node:internal/modules/esm/module_job:277:33)
at ModuleJob.link (node:internal/modules/esm/module_job:389:17) {
code: 'ERR_MODULE_NOT_FOUND',
url: 'file://<BASE>/missing/packages/queue/src/store.mjs'
}
Node.js v26.8.1
leaks base: true
@@ -0,0 +1,57 @@
// Row 54 round 1 node probes (Darkwing). Run with WT=<candidate worktree>.
import { mkdtempSync, writeFileSync, mkdirSync, realpathSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
const WT = process.env.WT;
const { redactor, queueReader, consoleReads, businessId } = await import(`${WT}/packages/webui/src/reads.mjs`);
const { startServer } = await import(`${WT}/packages/webui/src/serve.mjs`);
const { startTimeOf, hostFile } = await import(`${WT}/packages/cli/src/host.mjs`);
const out = (k, v) => console.log(`${k}: ${JSON.stringify(v)}`);
// P2: the redactor on shapes its tests don't use.
const r = redactor({ configDir: '/home/u/.config/mosaic-dev', dataRoot: '/home/u/.mosaic-dev' });
for (const s of [
'config:/home/u/secret/x.token', 'open "/home/u/secret/x.token"', 'path</home/u/x>', 'see file:///home/u/x.token', 'at ~/secret/x.token',
'in ./secret/x.token', 'C:\\Users\\u\\x.token', 'url https://discord.com/channels/123456789012345678/223456789012345678',
'id:123456789012345678', 'id_123456789012345678', 'n=1234567890123456', 'a 21-digit 123456789012345678901 run',
'tab\t/home/u/x', '/home/u/.config/mosaic-dev', '/home/u/.config/mosaic-dev/', '/home/u/.mosaic-dev.bak/x', '[/home/u/x]', '{/home/u/x}',
]) out(`P2 ${JSON.stringify(s)}`, r(s));
// P4: the queue child's timeout and output cap, with a stand-in reader script.
const base = realpathSync(mkdtempSync(join(tmpdir(), 'dw-r54-')));
const fake = (name, body) => { const root = join(base, name); mkdirSync(join(root, 'packages/webui/src'), { recursive: true }); writeFileSync(join(root, 'packages/webui/src/queue-read.mjs'), body); return root; };
const settle = async (p) => { const t = Date.now(); try { const v = await p; return { ok: true, ms: Date.now() - t, rows: v.rows?.length }; } catch (e) { return { ok: false, ms: Date.now() - t, code: e.code, message: e.message }; } };
out('P4a hang, timeout 500ms', await settle(queueReader({ root: fake('hang', 'setInterval(()=>{},1000)'), timeoutMs: 500 })()));
out('P4b 64 KiB out, cap 1 KiB', await settle(queueReader({ root: fake('big', 'process.stdout.write("x".repeat(65536))'), maxBytes: 1024 })()));
out('P4c exit 2 with a path on stderr', await settle(consoleReads({ root: fake('two', `process.stderr.write("refused at ${base}/q.json");process.exit(2)`) }).queue()));
out('P4d exit 0, bad shape', await settle(queueReader({ root: fake('shape', 'console.log(JSON.stringify({rows:{}}))') })()));
out('P4e killed by a signal', await settle(queueReader({ root: fake('sig', 'process.kill(process.pid,"SIGKILL")') })()));
// P5: the business id from the bus host state: none, stale, live, malformed.
const dataRoot = join(base, 'data'); mkdirSync(join(dataRoot, 'bus-host'), { recursive: true });
const id = () => { try { return businessId({ dataRoot }); } catch (e) { return `${e.code}: ${e.message}`; } };
out('P5a no host file', id());
writeFileSync(hostFile(dataRoot), JSON.stringify({ pid: process.pid, startTime: 'not-the-start', business: 'stale-biz' }));
out('P5b stale host file', id());
writeFileSync(hostFile(dataRoot), JSON.stringify({ pid: process.pid, startTime: startTimeOf(process.pid), business: 'live-biz' }));
out('P5c live host file', id());
writeFileSync(hostFile(dataRoot), '{');
out('P5d malformed host file', id());
// P3: Host and Origin on the new routes, and HEAD.
const server = await startServer({ port: 0, reads: consoleReads({ root: fake('two2', 'process.exit(2)') }) });
const port = server.address().port;
const { request } = await import('node:http');
const call = (path, method = 'GET', headers = {}) => new Promise(res => { const q = request({ host: '127.0.0.1', port, path, method, headers }, s => { let b = ''; s.on('data', c => b += c); s.on('end', () => res(`${s.statusCode} ${b.slice(0, 80)}`)); }); q.end(); });
for (const p of ['/api/queue', '/api/queue/1', '/api/business', '/api/settings']) {
out(`P3 ${p} Host evil`, await call(p, 'GET', { host: 'evil.example' }));
out(`P3 ${p} Host 127.0.0.1:other`, await call(p, 'GET', { host: '127.0.0.1:1' }));
out(`P3 ${p} Origin evil`, await call(p, 'GET', { origin: 'http://evil.example' }));
out(`P3 ${p} HEAD`, await call(p, 'HEAD'));
out(`P3 ${p} OPTIONS`, await call(p, 'OPTIONS'));
}
out('P3 /api/queue/ empty id', await call('/api/queue/'));
out('P3 /api/queue/1?x', await call('/api/queue/1?x=1'));
out('P3 /api/queue%2F1', await call('/api/queue%2F1'));
server.close();
rmSync(base, { recursive: true, force: true });
@@ -0,0 +1,50 @@
P2 "config:/home/u/secret/x.token": "config:/home/u/secret/x.token"
P2 "open \"/home/u/secret/x.token\"": "open \"<path>\""
P2 "path</home/u/x>": "path</home/u/x>"
P2 "see file:///home/u/x.token": "see file:///home/u/x.token"
P2 "at ~/secret/x.token": "at ~/secret/x.token"
P2 "in ./secret/x.token": "in ./secret/x.token"
P2 "C:\\Users\\u\\x.token": "C:\\Users\\u\\x.token"
P2 "url https://discord.com/channels/123456789012345678/223456789012345678": "url https://discord.com/channels/<id>/<id>"
P2 "id:123456789012345678": "id:<id>"
P2 "id_123456789012345678": "id_123456789012345678"
P2 "n=1234567890123456": "n=1234567890123456"
P2 "a 21-digit 123456789012345678901 run": "a 21-digit 123456789012345678901 run"
P2 "tab\t/home/u/x": "tab\t<path>"
P2 "/home/u/.config/mosaic-dev": "<config>"
P2 "/home/u/.config/mosaic-dev/": "<config>/"
P2 "/home/u/.mosaic-dev.bak/x": "<path>"
P2 "[/home/u/x]": "[<path>]"
P2 "{/home/u/x}": "{<path>}"
P4a hang, timeout 500ms: {"ok":false,"ms":500,"code":"read-failed","message":"the queue read did not finish in time"}
P4b 64 KiB out, cap 1 KiB: {"ok":false,"ms":22,"code":"invalid-response","message":"the queue read printed too much"}
P4c exit 2 with a path on stderr: {"ok":false,"ms":26,"code":"queue-refused","message":"refused at <path>"}
P4d exit 0, bad shape: {"ok":false,"ms":29,"code":"invalid-response","message":"the queue read printed something that is not its JSON"}
P4e killed by a signal: {"ok":false,"ms":22,"code":"read-failed","message":"the queue read did not finish"}
P5a no host file: "no-bus-host: no bus host is running and no --business was given"
P5b stale host file: "no-bus-host: no bus host is running and no --business was given"
P5c live host file: "live-biz"
P5d malformed host file: "no-bus-host: the bus host state file is unreadable"
P3 /api/queue Host evil: "403 {\"error\":\"non-local Host refused\"}"
P3 /api/queue Host 127.0.0.1:other: "403 {\"error\":\"non-local Host refused\"}"
P3 /api/queue Origin evil: "403 {\"error\":\"cross-origin request refused\"}"
P3 /api/queue HEAD: "405 "
P3 /api/queue OPTIONS: "405 {\"error\":\"method not allowed\"}"
P3 /api/queue/1 Host evil: "403 {\"error\":\"non-local Host refused\"}"
P3 /api/queue/1 Host 127.0.0.1:other: "403 {\"error\":\"non-local Host refused\"}"
P3 /api/queue/1 Origin evil: "403 {\"error\":\"cross-origin request refused\"}"
P3 /api/queue/1 HEAD: "405 "
P3 /api/queue/1 OPTIONS: "405 {\"error\":\"method not allowed\"}"
P3 /api/business Host evil: "403 {\"error\":\"non-local Host refused\"}"
P3 /api/business Host 127.0.0.1:other: "403 {\"error\":\"non-local Host refused\"}"
P3 /api/business Origin evil: "403 {\"error\":\"cross-origin request refused\"}"
P3 /api/business HEAD: "405 "
P3 /api/business OPTIONS: "405 {\"error\":\"method not allowed\"}"
P3 /api/settings Host evil: "403 {\"error\":\"non-local Host refused\"}"
P3 /api/settings Host 127.0.0.1:other: "403 {\"error\":\"non-local Host refused\"}"
P3 /api/settings Origin evil: "403 {\"error\":\"cross-origin request refused\"}"
P3 /api/settings HEAD: "405 "
P3 /api/settings OPTIONS: "405 {\"error\":\"method not allowed\"}"
P3 /api/queue/ empty id: "400 {\"error\":\"invalid-request\",\"message\":\"a queue row id is a positive integer\"}"
P3 /api/queue/1?x: "503 {\"error\":\"queue-refused\",\"message\":\"the queue refused the read\"}"
P3 /api/queue%2F1: "404 {\"error\":\"not found\"}"
@@ -0,0 +1,240 @@
# Row 54, read-only console views, round 1 review (Darkwing)
Issue #1543, request comment 27182, packet comment 27183. Base
`d64f434f`. Candidate manifest sha256
`dba2429e411ae22b1e939344247e9f3857a48e488dc7da11c578d1092288ea8a`, 14
files. The packet manifest and my snapshot of the checkout both check 14
OK, and `row54.patch` applied at `c6b214e5` gives the same 14 files.
`c6b214e5` is `d64f434f` plus queue revs 373 to 375, which touch only
`docs/plans/queue.json` and `QUEUE.md`.
Verdict: **changes**. The four fixes Sage asked me to check are right, and
the tests pin each one. Every suite is green. Two things block. The
Business view shows "none" for Arbiters on every real business file, and a
queue child that fails at import sends an absolute `file://` path and a
stack trace to the browser.
## Method
- Detached worktrees `papply` and `mutwt` at `c6b214e5` with
`row54.patch` applied and staged. Both check 14 OK.
`mutwt` still checks 14 OK after the mutant runs
(`r1/mut/manifest-after.txt`).
- `r1/gate.sh`: the `packages/webui`, `packages/queue` and
`packages/business` node suites, `build-tokens.mjs --check`, then every
`scripts/test-*.sh` with `DOCKER_HOST=unix:///nonexistent.sock`,
20:05:27Z to 20:08:51Z. I skipped the `packages/cli` node suite:
`packages/cli` doesn't change and is frozen.
- 31 mutants (`r1/mut/mutate.py`, `r1/mut/run.sh`), each against the full
`packages/webui` suite, except D31 against `packages/queue`, 20:09:04Z
to 20:37:00Z.
- `test-release` with Docker.
- Node probes (`r1/probes/probe-node.mjs`, output `probe-node.txt`): the
redactor on hostile strings (P2), the four routes under bad Host, bad
Origin, HEAD, OPTIONS and odd ids (P3), the queue child against a fake
script that hangs, floods, refuses, prints the wrong shape or dies (P4),
and the bus host state file (P5).
- An import probe (`r1/probes/probe-import.mjs`, output
`probe-import.txt`): the real `queue-read.mjs` beside a `store.mjs` with
a syntax error, and beside none (P4f).
- Browser probes in Chromium through the candidate's `tests/browser.mjs`
on the seeded fixture with the real reads
(`r1/probes/probe-browser.test.mjs`, output `probe-browser.txt`): B1 to
B4.
- Read every changed file against the brief, decision 83 and my row 53
notes.
Node v26.8.1, `TMPDIR=~/darkwing-scratch/r54a/tmp`. No tracker request.
## Suites
| Suite | Result |
|---|---|
| packages/webui (node) | 38/0 |
| packages/queue (node) | 149/0 |
| packages/business (node) | 60/0 |
| build-tokens --check | rc 0, "tokens.css is current" |
| test-auth | 15/0 |
| test-conductor | 17/0 |
| test-config | 24/0 |
| test-discord | 66/0 |
| test-extension-package | 18/0 |
| test-foundation | 44/0 |
| test-queue | 27/0 |
| test-release | 4/0 without Docker, 14/0 with it |
| test-task, Docker unreachable | 26/0 |
I didn't run test-task with real Docker, because it makes live model calls.
## The four fixes
| Item | Result |
|---|---|
| Refresh timer during a navigation | Fixed. `clearTimeout(timer)` is the first line of `render()`. D01 drops it and dies at "the navigation cleared the due refresh" (`views.test.mjs:150`) |
| H1 focus on a same-page refresh | Fixed. B3: focus on the Queue H1, fire the 10 s refresh, focus is still on the new H1. D02 and D03 die (table below) |
| Stale `#conv-pick` | Fixed. `openConversation()` empties it beside `#conv-meta`. D04 dies at "no stale session in the picker" |
| Focus after `error()` closes a conversation | Fixed, with Dewey's check on prior focus. D05 and D06 both die |
On the `error()` deviation: I agree with Dewey. My note asked for the
fallback without the check, and that would pull focus to `<main>` and
scroll on every failed ten-second refresh when nothing had focus. The
check is better than what I asked for.
B2 also covers the mirror selects, which `keep()` and `restore()` now
carry: focus on each Settings mirror (`palette`, `mode`) survives the 10 s
refresh, and a change on the mirror moves the command bar's select.
## Required
1. **Arbiters always reads "none".** `reads.mjs:149` passes
`b.arbiters` through as the business file has it, an object
(`{ delivery, technical }`, which `packages/bus` requires). `bus.js:404`
renders it with `names()`, and `names()` (`bus.js:348`) returns `none`
for anything but a non-empty array. B1 on the seeded fixture: the API
answers `{"delivery":"pm","technical":"cto"}` and the page shows "none".
The views.test stub has `arbiters: { delivery: 'pm' }` and nothing
asserts the Arbiters line, so no test sees it. Render the object as
pairs (for example "pm (delivery), cto (technical)"), and assert the
line on the seeded fixture, not only on the stub.
2. **An import failure in the queue child sends an absolute path to the
browser.** `queue-read.mjs` imports `store.mjs` and `errors.mjs`
statically, so a failure at load time happens before its `try`. Node
prints the error and a stack to stderr and exits 1. `reads.mjs:81`
passes up to 2000 characters of that stderr as the `read-failed`
message, and the redactor doesn't match a path after `file://`
(the character before the `/` is `:`, which isn't in its prefix set).
P4f, with the real `queue-read.mjs`:
- `store.mjs` with a syntax error: the message starts
`file://<BASE>/syntax/packages/queue/src/store.mjs:1`, then the
broken source line, then the stack.
- no `store.mjs`: the message ends with
`url: 'file://<BASE>/missing/packages/queue/src/store.mjs'`.
`<BASE>` is my substitution in the probe output; the real message
carries the absolute temporary path. This checkout is edited in place
by several seats, so a half-written `store.mjs` is a real state, not a
contrived one. The brief says no file path appears in any response.
Any one of these closes it, and I'd do the first two:
- import the store inside the `try` (a dynamic `import()`), so a load
failure prints "the queue read failed";
- forward stderr only for exit 2, with a fixed message for any other
exit;
- add `:` (or `file:`) to the redactor's prefix set.
A test with a broken `store.mjs` in the `queueRepo` scratch copy should
assert that the message carries no base path.
## Notes (not blocking)
1. Redactor gaps from P2. These pass through unchanged:
`config:/home/u/x.token`, `path</home/u/x>`, `~/secret/x.token`,
`./secret/x.token`, a Windows path, `id_123456789012345678`, and
digit runs of 16 or 21. None of the sources I traced produces the
first two today. `~/` paths do show up in real queue notes (`~/.mosaic`
in three), and the Queue row page shows them as written. They name no
user and no secret, so I'd leave them. The README's "any other absolute
path" describes this correctly.
2. Five mutants survive (table below). D14 is equivalent. D20 to D22 are
paths my probes show working with no test. D29 is a test gap for
mirror focus across a refresh. I'd add the D29 test with the round 2
fixes; the others can wait.
3. `cli.mjs`'s outer catch prints its error unredacted. Dewey lists it
under "Not done"; I agree its inputs carry no path today.
4. `bus.js:428` lost a space: `const GROUPS =[[`. Cosmetic.
5. B4 is weaker than Dewey's test: after a navigation one refresh is due,
and that is the new view's timer. Dewey's PROBE checks the count with
the reads held, which is the right shape. D01 confirms it.
6. views.test test 1 failed at line 167 (the palette's queue rows) in two
mutant runs that can't reach it, and passed in eight clean runs. Worth
a look before it fails someone's gate. Two guesses, neither checked: the
Ctrl+K press lands before the palette's handler sees the last queue
read, or a real 10 s refresh fires during the test (it runs about 5 s,
but slower under load).
## Probes
| Probe | Candidate |
|---|---|
| P2 redactor | quoted, bracketed, braced and tab-led paths become `<path>`; the config dir is `<config>`; `.mosaic-dev.bak` is `<path>`; Discord URL ids and `id:` ids are `<id>`. Gaps in note 1 |
| P3 routes | on all four: bad Host 403, bad Origin 403, HEAD and OPTIONS 405. `/api/queue/` 400, `/api/queue%2F1` 404, `/api/queue/1?x=1` reaches the read |
| P4a child hangs, 500 ms timeout | `read-failed` "did not finish in time" at 500 ms |
| P4b 64 KiB out, 1 KiB cap | `invalid-response` "printed too much" |
| P4c exit 2 with a path | `queue-refused` "refused at `<path>`" |
| P4d wrong shape | `invalid-response` |
| P4e killed by a signal | `read-failed` "did not finish" |
| **P4f** import failure | absolute `file://` path and stack in the message. Required 2 |
| P5 bus host state | no file and a stale file give `no-bus-host`; a live file gives its business; a malformed file gives `no-bus-host` "unreadable" |
| **B1** Business, seeded | API `{"delivery":"pm","technical":"cto"}`, Arbiters "none". Required 1 |
| B2 mirror focus across the refresh | kept on `palette` and on `mode`; a mirror change moves the command bar |
| B3 H1 focus across the refresh | kept |
| B4 navigation | one refresh due after it. See note 5 |
## Mutants
26 of 31 killed (`r1/mut/summary.txt`). I checked each kill site in its
output against its diff.
| Mutant | Change | Result |
|---|---|---|
| D01 | no `clearTimeout(timer)` at the top of `render()` | killed, "the navigation cleared the due refresh", `views.test.mjs:150` |
| D02 | `keep()` doesn't record an H1 | killed, "#/queue keeps heading focus", `views.test.mjs:95` |
| D03 | `restore()` doesn't refocus the H1 | killed, same line |
| D04 | no `#conv-pick` clear | killed, "no stale session in the picker", `shell.test.mjs:220` |
| D05 | no focus fallback in `error()` | killed, "focus after a refusal closes the conversation", `shell.test.mjs:231` |
| D06 | the fallback without `focused &&` | killed, "a failure with nothing focused leaves focus alone", `shell.test.mjs:191` |
| D07 | no generic path rule in the redactor | killed, redactor test `reads.test.mjs:23` and "response carries /srv/secret" |
| D08 | no Discord id rule | killed, `reads.test.mjs:25` and "response carries id 523456789012345678" |
| D09 | no unquote | killed, `reads.test.mjs:27` |
| D10 | no config and dataRoot labels | killed, `reads.test.mjs:21`, `:110` and `:155` |
| D11 | queue list not scrubbed | killed, `reads.test.mjs:49` and "response carries id" in views.test |
| D12 | queue row not scrubbed | killed, the row note text, `views.test.mjs:194` |
| D13 | business vars not allowlisted | killed, "response carries discordUserId" |
| D14 | role vars not allowlisted | **survived**, equivalent today. See below |
| D15 | credential spreads its ref | killed, "response carries CODER_GITEA_SECRET_ENV" |
| D16 | `fail()` doesn't redact | killed, `reads.test.mjs:110` and `views.test.mjs:208` |
| D17 | settings not scrubbed | killed, `reads.test.mjs:155` |
| D18 | cli bus line not redacted | killed, "the log names the temporary directory", `views.test.mjs:232` |
| D19 | refusal on exit 3, not 2 | killed, `read-failed` for `queue-refused`, `reads.test.mjs:72` |
| D20 | no output cap | **survived** |
| D21 | timeout times 1000 | **survived** |
| D22 | host state without `.live` | **survived** |
| D23 | no 405 on the new routes | killed, `reads.test.mjs:61` |
| D24 | any digits as a row id | killed, 404 for 400, `reads.test.mjs:59` |
| D25 | no 503 without reads | killed, `reads.test.mjs:136` |
| D26 | `queue-refused` not a refusal | killed, no refusal banner, `views.test.mjs:107` |
| D27 | settings refusal without appearance | killed, 0 mirror selects for 2, `views.test.mjs:114` |
| D28 | palette without queue rows | killed, `views.test.mjs:167` |
| D29 | `keep()` without the mirror | **survived**. See below |
| D30 | mirror change ignored | killed, `#mode` never "dark", `views.test.mjs:177` |
| D31 | `rows()` drops its notes | killed, `write.test.mjs:349` |
D14 is equivalent. The business var registry (`packages/business/src/vars.mjs`)
allows only `harness`, `model`, `thinking` and the three `limits.*` keys at
the agent layer, which is the allowlist. It stays as defense in depth.
D20, D21 and D22 are untested paths that work. P4a and P4b show the
timeout and the cap behave, and P5c shows a live host file is read. A
test for each would be short, with the reader pointed at a fake script.
D29 is a real gap. With the mutant applied, B2 shows focus on either
mirror select falls to `<body>` on the 10 s refresh
(`r1/probes/probe-browser-D29.txt`). B2 passes on the candidate, so the
code is right and only the test is missing.
One more thing showed up in the mutant outputs. views.test test 1 timed
out at line 167, where Ctrl+K should list `#/queue/7`, under D06, D07 and
D09 as well as D28. D07 and D09 change only the redactor, and test 1's
stub reads never call it, so those two failures aren't the mutants'
doing. D06, D07 and D09 each die at their own assertions as well. Eight clean
runs of the webui suite on the candidate passed (`r1/flake/summary.txt`),
so it's intermittent and I don't know the cause. See note 6.
## Files
- `r1/candidate-manifest.sha256`: copy of Dewey's.
- `r1/gate.sh`, `r1/out/`: suite runs, `summary.txt` and
`test-release-docker.txt`.
- `r1/mut/`: mutant definitions, runner, diffs, outputs, `summary.txt` and
the manifest check after the runs.
- `r1/probes/`: the three probes and their output, plus B2 under D29.
- `r1/flake/`: eight clean runs of the webui suite.