diff --git a/agents/researcher/work/queue-102/bidi-count.mjs b/agents/researcher/work/queue-102/bidi-count.mjs new file mode 100644 index 000000000..cba1bd176 --- /dev/null +++ b/agents/researcher/work/queue-102/bidi-count.mjs @@ -0,0 +1,7 @@ +import { readFileSync } from 'node:fs'; +const BIDI = /[؜‎‏‪-‮⁦-⁩]/gu; +for (const f of process.argv.slice(2)) { + const t = readFileSync(f, 'utf8'); + const hits = [...t.matchAll(BIDI)].map((m) => `${t.slice(0, m.index).split('\n').length}:U+${m[0].codePointAt(0).toString(16).toUpperCase().padStart(4, '0')}`); + console.log(f, hits.length, hits.join(' ')); +} diff --git a/agents/researcher/work/queue-102/bidi-counts.txt b/agents/researcher/work/queue-102/bidi-counts.txt new file mode 100644 index 000000000..99e8ab130 --- /dev/null +++ b/agents/researcher/work/queue-102/bidi-counts.txt @@ -0,0 +1,6 @@ +packages/webui/src/public/bus.js HEAD: 7 now: 0 +packages/webui/tests/bus.test.mjs HEAD: 1 now: 0 +packages/conversation/tests/flows.test.mjs HEAD: 1 now: 0 +packages/webui/tests/bus-browser.test.mjs HEAD: 0 now: 0 +packages/tasks/src/fields.mjs HEAD: 0 now: 0 +packages/tasks/tests/fields.test.mjs HEAD: 0 now: 0 diff --git a/agents/researcher/work/queue-102/build.md b/agents/researcher/work/queue-102/build.md new file mode 100644 index 000000000..9f93c9ca3 --- /dev/null +++ b/agents/researcher/work/queue-102/build.md @@ -0,0 +1,59 @@ +# Row 102 (#1596): row 88 follow-up, build notes + +Built 2026-10-13T21:12:10Z by the Researcher seat. Base: 587e64a27 (refactor, unpushed), in the worktree +/mnt/storage/scratch/researcher/q102 (detached). The change is row102.patch (sha256 1d1a343cddd4c66a8c4b6dfbb38abf38edc1b784222ebd52c9a0ab4ac49d45d8): +6 files, +10 -6. Apply with `git apply row102.patch` at the repository root. + +## What changed + +- N1, packages/tasks/src/fields.mjs: the header comment gains one sentence naming the check. The + test reads the rows of the Field table and requires that every field name in a row is a label in + fields.json and that the task.* verbs a row names are exactly the verbs fields.json lists for that + field (fields.test.mjs lines 91-117). +- N2, packages/tasks/tests/fields.test.mjs: the 146-character line 13 is split in two. No other reflow. +- N3, three files, each literal bidi character replaced by its lowercase \uXXXX escape: + - packages/webui/src/public/bus.js:13, the UNSEEN pattern + - packages/webui/tests/bus.test.mjs:20, HOSTILE + - packages/conversation/tests/flows.test.mjs:495 +- N4, packages/webui/tests/bus-browser.test.mjs, inertCheck only: U+200E and U+200F added to the + refused pattern, beside U+202A-U+202E and U+2066-U+2069. + +No behaviour change in bus.js beyond the escape. + +## Evidence (all in this directory) + +- bidi-counts.txt: literal bidi characters per file, HEAD then now. bus.js 7 to 0, bus.test.mjs 1 to 0, + flows.test.mjs 1 to 0; the other three files 0 and 0. A text-file scan of packages, scripts, adapters, + roles and contracts at the worktree finds none left. Counter: bidi-count.mjs (U+061C, U+200E, U+200F, + U+202A-U+202E, U+2066-U+2069). Escaper used: escape-bidi.mjs. +- same-code-points.out (script same-code-points.mjs, run from the worktree root against HEAD): + the old and new UNSEEN regexes agree on all 65536 code units (44 match, 0 differ); the HOSTILE + string in bus.test.mjs:20 and the string in flows.test.mjs:495 are identical before and after. +- N4, n4-evidence.sh with output in n4-out/, running only bus-browser.test.mjs three times: + - control: HOSTILE also carries U+200E U+200F, real bus.js, new inertCheck: 2 of 2 pass. + - mutant: same HOSTILE, bus.js UNSEEN no longer strips U+200E U+200F, new inertCheck: exit 1, + 1 of 2 fails at inertCheck (the inbox page, bus-browser.test.mjs:111, doesNotMatch). The mutant + is the injection: the character reaches shown text and the new pattern refuses it. + - gap: same mutant with inertCheck as it was at HEAD: 2 of 2 pass. That is the hole N4 closes. + The script restores every file it edits; the worktree diff is the same after it as before. +- test-out/: the four covering files, each run alone with node --test from the worktree, on the final + tree: fields.test.mjs 5 of 5, bus.test.mjs 10 of 10, flows.test.mjs 28 of 28, bus-browser.test.mjs 2 of 2. + +## What was not run + +The full tasks, webui and conversation suites and scripts/test-*.sh. Darkwing held the test slot for +row 103 and Sage ruled that phase A runs no full suite; those are for Sage's gate rerun. + +## Environment notes + +- flows.test.mjs refuses without node_modules/.package-lock.json at the tree root (the pi pin check, + pi-pin.mjs). The worktree has no node_modules, so the first run failed 16 of 28 with the + pin-check refusal. I linked the canonical checkout's node_modules into the worktree (read only), + reran, and removed the link afterwards. The 16 failures were that, not the change. +- An earlier loop of mine launched node --test through setsid without -w, so its first exit codes were + not the tests' own. The numbers above are from the rerun with setsid -w; the n4 script uses -w too. + +## Landing + +packages/webui/src/public/bus.js is under public/: it needs Mos's go (decision 123) and the decision +109 notices before and after. Nothing here is pushed. diff --git a/agents/researcher/work/queue-102/candidate-manifest.sha256 b/agents/researcher/work/queue-102/candidate-manifest.sha256 new file mode 100644 index 000000000..1ee960b20 --- /dev/null +++ b/agents/researcher/work/queue-102/candidate-manifest.sha256 @@ -0,0 +1,18 @@ +8f635ce5d4d831f577a71e1ac008ef61addc611effdca65719da3fe472bb4a94 agents/researcher/work/queue-102/bidi-count.mjs +436b4027f7cdaa7b137683cc0df37d086c175df9772ffc9f32ae1807a3210b4c agents/researcher/work/queue-102/bidi-counts.txt +18bdcce101ed088be039d14b5c72b568b9b0d1199e0f65bb3c38f141ae0b2be6 agents/researcher/work/queue-102/build.md +9a16aad451073620df5fa77bacd0b17bc9f02cfae6ea5ffbe6bbb17b819ac1dd agents/researcher/work/queue-102/escape-bidi.mjs +e96df5f216bdd50b2ee1ef4b6891c86d1f3553100d3881733431f3527ce1136f agents/researcher/work/queue-102/n4-evidence.sh +fe0b880929c1545958a4a6e62359edec7eb0f39c1799eb75fda1cb2d2604abb5 agents/researcher/work/queue-102/n4-out/control.out +9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa agents/researcher/work/queue-102/n4-out/control.rc +f6524214bfd82630244339bf0250c9bb727b4b1ebac4af31eeddf09d9940d90e agents/researcher/work/queue-102/n4-out/gap.out +9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa agents/researcher/work/queue-102/n4-out/gap.rc +dfaa9dfecbc67f28cc1a55444c2e3e6d98452137da8b108c40a5ae64a388f6ea agents/researcher/work/queue-102/n4-out/mutant.out +4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 agents/researcher/work/queue-102/n4-out/mutant.rc +1d1a343cddd4c66a8c4b6dfbb38abf38edc1b784222ebd52c9a0ab4ac49d45d8 agents/researcher/work/queue-102/row102.patch +1ac26ddd849e6705d69e537d70b2150aa9f8aee11a6521bf4ad93fa765b04c6b agents/researcher/work/queue-102/same-code-points.mjs +e85f81733f717e4e4b9f37cc20aaff3b33a60671974e9e4a755ec5bd55e1f106 agents/researcher/work/queue-102/same-code-points.out +75e0d4d8f3de54cdada686a99863e3e999d7ea35c57104958c928c43f62ffd74 agents/researcher/work/queue-102/test-out/bus-browser.out +998c71967664d4f4c3d083c8dcf641e00497bcfe49c242147cb65dc191fcc3da agents/researcher/work/queue-102/test-out/bus.out +684c3d21aeccea1f6d7a6ce22a8632594505ad7365e6d4b91951828135763125 agents/researcher/work/queue-102/test-out/fields.out +81ee7eb7b36290a177bc11a6b312e550d0e66696dc41a205f4afec85e6d25ff2 agents/researcher/work/queue-102/test-out/flows.out diff --git a/agents/researcher/work/queue-102/escape-bidi.mjs b/agents/researcher/work/queue-102/escape-bidi.mjs new file mode 100644 index 000000000..2b10509e9 --- /dev/null +++ b/agents/researcher/work/queue-102/escape-bidi.mjs @@ -0,0 +1,11 @@ +// Row 102 (#1596) N3: replace each literal bidi control character on the named line of the +// named file with its \u escape. Usage: node escape-bidi.mjs +import { readFileSync, writeFileSync } from 'node:fs'; +const [file, lineNo] = process.argv.slice(2); +const BIDI = /[؜‎‏‪-‮⁦-⁩]/gu; +const lines = readFileSync(file, 'utf8').split('\n'); +const i = Number(lineNo) - 1; +const before = lines[i]; +lines[i] = before.replace(BIDI, (c) => '\\u' + c.codePointAt(0).toString(16).padStart(4, '0')); +if (lines[i] === before) throw new Error(`${file}:${lineNo}: no bidi character`); +writeFileSync(file, lines.join('\n')); diff --git a/agents/researcher/work/queue-102/n4-evidence.sh b/agents/researcher/work/queue-102/n4-evidence.sh new file mode 100644 index 000000000..4c9fbac1e --- /dev/null +++ b/agents/researcher/work/queue-102/n4-evidence.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Row 102 (#1596) N4: inertCheck in bus-browser.test.mjs refuses U+200E and U+200F in shown text. +# Runs only that one test file, three times, from the row 102 worktree: +# control real bus.js, HOSTILE also carries U+200E U+200F, new inertCheck -> passes +# mutant bus.js UNSEEN no longer strips U+200E U+200F, same HOSTILE, new inertCheck -> fails in inertCheck +# gap same mutant, inertCheck as it was at HEAD -> passes (the hole N4 closes) +# Usage: n4-evidence.sh Restores every file it touches. +set -u +WT=$(cd "$1" && pwd); OUT=$2; mkdir -p "$OUT" +T=packages/webui/tests/bus-browser.test.mjs; B=packages/webui/src/public/bus.js +cd "$WT" || exit 2 +BAK=$(mktemp -d "$OUT/.bak.XXXXXX") +cp "$T" "$BAK/test"; cp "$B" "$BAK/bus" +restore() { cp "$BAK/test" "$T"; cp "$BAK/bus" "$B"; rm -rf "$BAK"; } +trap restore EXIT +hostile() { node -e 'const f=process.argv[1],s=require("fs").readFileSync(f,"utf8");const o="evil\x27;";if(!s.includes(o))process.exit(3);require("fs").writeFileSync(f,s.replace(o,"evil\\u200e\\u200f\x27;"))' "$T"; } +strip() { node -e 'const f=process.argv[1],s=require("fs").readFileSync(f,"utf8");const o="\\u200e\\u200f\\u202a";if(!s.includes(o))process.exit(3);require("fs").writeFileSync(f,s.replace(o,"\\u202a"))' "$B"; } +oldcheck() { node -e 'const f=process.argv[1],s=require("fs").readFileSync(f,"utf8");const o="\\u200e\\u200f\\u202a";if(!s.includes(o))process.exit(3);require("fs").writeFileSync(f,s.replace(o,"\\u202a"))' "$T"; } +run() { # name + setsid -w timeout -k 10 240 node --test "$T" > "$OUT/$1.out" 2>&1; echo "$?" > "$OUT/$1.rc" + echo "$1: exit $(cat "$OUT/$1.rc"); $(grep -E '^(ℹ|#) (tests|pass|fail)' "$OUT/$1.out" | tr '\n' ' ')" +} +hostile || { echo "HOSTILE line not found"; exit 3; } +run control +strip || { echo "UNSEEN edit failed"; exit 3; } +run mutant +oldcheck || { echo "inertCheck edit failed"; exit 3; } +run gap diff --git a/agents/researcher/work/queue-102/n4-out/control.out b/agents/researcher/work/queue-102/n4-out/control.out new file mode 100644 index 000000000..2bde26fdf --- /dev/null +++ b/agents/researcher/work/queue-102/n4-out/control.out @@ -0,0 +1,10 @@ +✔ the README says a refused field table names the file or the key at fault, never a path (0.758545ms) +✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (2753.999042ms) +ℹ tests 2 +ℹ suites 0 +ℹ pass 2 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2852.161637 diff --git a/agents/researcher/work/queue-102/n4-out/control.rc b/agents/researcher/work/queue-102/n4-out/control.rc new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/agents/researcher/work/queue-102/n4-out/control.rc @@ -0,0 +1 @@ +0 diff --git a/agents/researcher/work/queue-102/n4-out/gap.out b/agents/researcher/work/queue-102/n4-out/gap.out new file mode 100644 index 000000000..a6516a36b --- /dev/null +++ b/agents/researcher/work/queue-102/n4-out/gap.out @@ -0,0 +1,10 @@ +✔ the README says a refused field table names the file or the key at fault, never a path (0.743157ms) +✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (2752.067506ms) +ℹ tests 2 +ℹ suites 0 +ℹ pass 2 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2841.706475 diff --git a/agents/researcher/work/queue-102/n4-out/gap.rc b/agents/researcher/work/queue-102/n4-out/gap.rc new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/agents/researcher/work/queue-102/n4-out/gap.rc @@ -0,0 +1 @@ +0 diff --git a/agents/researcher/work/queue-102/n4-out/mutant.out b/agents/researcher/work/queue-102/n4-out/mutant.out new file mode 100644 index 000000000..0fd1aac8f --- /dev/null +++ b/agents/researcher/work/queue-102/n4-out/mutant.out @@ -0,0 +1,28 @@ +✔ the README says a refused field table names the file or the key at fault, never a path (0.701619ms) +✖ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (566.272543ms) +ℹ tests 2 +ℹ suites 0 +ℹ pass 1 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 660.741695 + +✖ failing tests: + +test at packages/webui/tests/bus-browser.test.mjs:81:1 +✖ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (566.272543ms) + AssertionError [ERR_ASSERTION]: inbox + at inertCheck (file:///mnt/storage/scratch/researcher/q102/packages/webui/tests/bus-browser.test.mjs:111:12) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async TestContext. (file:///mnt/storage/scratch/researcher/q102/packages/webui/tests/bus-browser.test.mjs:148: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: 'InboxRead from the bus never. Decisions are answered in a terminal with mosaic decide; Console only shows them.\n \n For you 1Push the release? ␛[31mred [U+202E]evil‎‏\n blockinggated · git.push.protected\n Choosing “yes” approves git.push.protected on refactor.\n Raised by coder 0 s ago · task #7\n Not shown here yet: Decisions routed to roles: not in the Q1 module Closed decisions: not in the Q1 module Seen and DM state per row: not in the Q1 module Open a decision for its seen, DM and closing history.', + expected: /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/, + operator: 'doesNotMatch', + diff: 'simple' + } diff --git a/agents/researcher/work/queue-102/n4-out/mutant.rc b/agents/researcher/work/queue-102/n4-out/mutant.rc new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/agents/researcher/work/queue-102/n4-out/mutant.rc @@ -0,0 +1 @@ +1 diff --git a/agents/researcher/work/queue-102/row102.patch b/agents/researcher/work/queue-102/row102.patch new file mode 100644 index 000000000..03afbd386 --- /dev/null +++ b/agents/researcher/work/queue-102/row102.patch @@ -0,0 +1,82 @@ +diff --git a/packages/conversation/tests/flows.test.mjs b/packages/conversation/tests/flows.test.mjs +index acd84aa35..e1bbce8fa 100644 +--- a/packages/conversation/tests/flows.test.mjs ++++ b/packages/conversation/tests/flows.test.mjs +@@ -492,7 +492,7 @@ test("terminal: engine control characters are made visible; a lost connection re + const h = await started(); + try { + const term = new Terminal({ client: h.client }); +- h.engine.script([{ text: "\x1b]52;c;ZXZpbA==\x07 bell‮", stop: "stop" }]); ++ h.engine.script([{ text: "\x1b]52;c;ZXZpbA==\x07 bell\u202e", stop: "stop" }]); + await term.key("go\r"); + await receiptState(h.client, term.lastReceipt.id, "finished"); + await until(() => term.transcript.lines().some((l) => l.includes("bell")), 2000, "the reply"); +diff --git a/packages/tasks/src/fields.mjs b/packages/tasks/src/fields.mjs +index 1c1d61fbc..e790430f0 100644 +--- a/packages/tasks/src/fields.mjs ++++ b/packages/tasks/src/fields.mjs +@@ -2,7 +2,10 @@ import { readFileSync } from 'node:fs'; + import { TASK_VERBS } from '../../bus/src/broker.mjs'; + // Row 61 (#1550): the slice 1 field table as data, in ../fields.json. The Field table in + // docs/plans/2026-10-04_slice-1.md is the reviewed prose it must match, and tests/fields.test.mjs +-// checks that prose and this table against what the verbs do. Two layers, as lead decision 113 rules: ++// checks that prose and this table against what the verbs do. For the prose, it reads the rows of ++// the Field table and requires that every field name in a row is a label here and that the ++// task verbs a row names are exactly the verbs this table lists for that field. ++// Two layers, as lead decision 113 rules: + // - `verbs`: the writer verbs.mjs checks before anything else. `pm` is the pm definition + // (field-writer otherwise), `assigned` a role whose bot is an assignee (not-assigned + // otherwise), `any` no writer check. +diff --git a/packages/tasks/tests/fields.test.mjs b/packages/tasks/tests/fields.test.mjs +index ef81f3385..1c2e396d4 100644 +--- a/packages/tasks/tests/fields.test.mjs ++++ b/packages/tasks/tests/fields.test.mjs +@@ -10,7 +10,8 @@ import { loadFields, checkFields, FieldTableError, FIELDS_FILE } from '../src/fi + import { TASK_VERBS } from '../../bus/src/broker.mjs'; + import { loadRole } from '../../business/src/index.mjs'; + // Row 61 (#1550): fields.json, and the Field table in docs/plans/2026-10-04_slice-1.md, against what +-// the verbs in src/verbs.mjs do, with the role files as given. The writer layer is read from the table; whether a write needs a decision, and who ++// the verbs in src/verbs.mjs do, with the role files as given. The writer layer is read from the ++// table; whether a write needs a decision, and who + // resolves it, from roles/*.json as the business loader reads them (lead decision 113). A version 1 + // file, the researcher's, grants nothing. Neither expectation is written out here, so a change to + // the table, the prose, a role file or the verbs that leaves them disagreeing fails this file. +diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js +index 108df3b98..0cc841bbc 100644 +--- a/packages/webui/src/public/bus.js ++++ b/packages/webui/src/public/bus.js +@@ -10,7 +10,7 @@ + const esc = v => String(v ?? '').replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); + // The CHAT-02 rules: C0 controls show as control pictures, C1 and bidi controls as + // [U+XXXX]. Newlines and tabs stay. Markdown stays as its source. +- const CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f]/g, UNSEEN = /[\u0080-\u009f؜‎‏‪-‮⁦-⁩]/g; ++ const CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f]/g, UNSEEN = /[\u0080-\u009f\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g; + const inert = v => String(v ?? '').replace(CONTROL, c => c === '\u007f' ? '␡' : String.fromCharCode(0x2400 + c.charCodeAt(0))).replace(UNSEEN, c => `[U+${c.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')}]`); + const txt = v => esc(inert(v)); // every agent-, tracker- or bus-authored string goes through this + const announce = text => { $('announce').textContent = text; }; +diff --git a/packages/webui/tests/bus-browser.test.mjs b/packages/webui/tests/bus-browser.test.mjs +index 106a8dd26..bf09119f1 100644 +--- a/packages/webui/tests/bus-browser.test.mjs ++++ b/packages/webui/tests/bus-browser.test.mjs +@@ -108,7 +108,7 @@ test('S5 pages in a browser: real broker rows, inert text, Copy, routes, failure + assert.equal(await b.evaluate('!!window.injected'), false, where); + assert.equal(await b.evaluate('document.querySelectorAll("#bus-view script, #bus-view img, #bus-view iframe, #bus-view object, #bus-view embed, #bus-view svg, #bus-view style, #bus-view link").length'), 0, where); + const shown = await text('#bus-view'); +- assert.doesNotMatch(shown, /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/, where); ++ assert.doesNotMatch(shown, /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/, where); + }; + const noOverflow = async where => { + for (const width of [320, 1440]) { +diff --git a/packages/webui/tests/bus.test.mjs b/packages/webui/tests/bus.test.mjs +index 6fb74ccec..d1bb55e42 100644 +--- a/packages/webui/tests/bus.test.mjs ++++ b/packages/webui/tests/bus.test.mjs +@@ -17,7 +17,7 @@ import { startServer } from '../src/serve.mjs'; + import { BusReadError, busReader, humanCall } from '../src/bus.mjs'; + import { close } from './fixture.mjs'; + +-const HOSTILE = ' ‮evil'; ++const HOSTILE = ' \u202eevil'; + const at = '2026-10-08T12:00:00.000Z'; + const businesses = { + demo: { diff --git a/agents/researcher/work/queue-102/same-code-points.mjs b/agents/researcher/work/queue-102/same-code-points.mjs new file mode 100644 index 000000000..cb3ada129 --- /dev/null +++ b/agents/researcher/work/queue-102/same-code-points.mjs @@ -0,0 +1,23 @@ +// Row 102 (#1596) N3: the escaped lines mean what the literal lines meant. Run from the repo +// root of a tree whose HEAD still has the literals: node same-code-points.mjs +import { execFileSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; +const head = (f) => execFileSync('git', ['show', `HEAD:${f}`], { encoding: 'utf8' }); +const line = (text, n) => text.split('\n')[n - 1]; +const BUS = 'packages/webui/src/public/bus.js'; +const unseen = (l) => new Function(`return ${/UNSEEN = (\/.*?\/g)/.exec(l)[1]}`)(); +const oldRe = unseen(line(head(BUS), 13)); +const newRe = unseen(line(readFileSync(BUS, 'utf8'), 13)); +let matched = 0, differ = 0; +for (let u = 0; u <= 0xffff; u++) { + const c = String.fromCharCode(u); + const a = (oldRe.lastIndex = 0, oldRe.test(c)), b = (newRe.lastIndex = 0, newRe.test(c)); + if (a) matched++; + if (a !== b) differ++; +} +console.log(`${BUS}:13 UNSEEN: old matches ${matched} of 65536 code units, new differs on ${differ}`); +for (const [f, n] of [['packages/webui/tests/bus.test.mjs', 20], ['packages/conversation/tests/flows.test.mjs', 495]]) { + const lit = (l) => new Function(`return ${/(['"])(?:(?!\1).|\\.)*\1/.exec(l.replace(/^.*?=\s*/, '').replace(/^.*?text:\s*/, ''))[0]}`)(); + const a = lit(line(head(f), n)), b = lit(line(readFileSync(f, 'utf8'), n)); + console.log(`${f}:${n}: string ${a === b ? 'identical' : 'DIFFERENT'} (${a.length} code units)`); +} diff --git a/agents/researcher/work/queue-102/same-code-points.out b/agents/researcher/work/queue-102/same-code-points.out new file mode 100644 index 000000000..676a2d55c --- /dev/null +++ b/agents/researcher/work/queue-102/same-code-points.out @@ -0,0 +1,3 @@ +packages/webui/src/public/bus.js:13 UNSEEN: old matches 44 of 65536 code units, new differs on 0 +packages/webui/tests/bus.test.mjs:20: string identical (48 code units) +packages/conversation/tests/flows.test.mjs:495: string identical (8 code units) diff --git a/agents/researcher/work/queue-102/test-out/bus-browser.out b/agents/researcher/work/queue-102/test-out/bus-browser.out new file mode 100644 index 000000000..085674c75 --- /dev/null +++ b/agents/researcher/work/queue-102/test-out/bus-browser.out @@ -0,0 +1,10 @@ +✔ the README says a refused field table names the file or the key at fault, never a path (0.725401ms) +✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (2789.709807ms) +ℹ tests 2 +ℹ suites 0 +ℹ pass 2 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2879.992892 diff --git a/agents/researcher/work/queue-102/test-out/bus.out b/agents/researcher/work/queue-102/test-out/bus.out new file mode 100644 index 000000000..713adfe55 --- /dev/null +++ b/agents/researcher/work/queue-102/test-out/bus.out @@ -0,0 +1,18 @@ +✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (54.268191ms) +✔ decisions route serves the roles and recent lists from the reader view (26.476402ms) +✔ credentials route serves the broker credential status from the reader view (7.636065ms) +✔ replies route serves the replies to one message from the reader view (26.287359ms) +✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (46.288585ms) +✔ bus refusals map to statuses and never carry a path or a stack (23.64501ms) +✔ humanCall speaks the human transport protocol without blocking the server (1639.760639ms) +✔ busReader takes --business, else the live bus host, else refuses (147.313209ms) +✔ humanCall kills a transport that runs past its timeout (527.716516ms) +✔ serve refuses a --business value that is not a business id (213.748295ms) +ℹ tests 10 +ℹ suites 0 +ℹ pass 10 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2807.201207 diff --git a/agents/researcher/work/queue-102/test-out/fields.out b/agents/researcher/work/queue-102/test-out/fields.out new file mode 100644 index 000000000..25615fe23 --- /dev/null +++ b/agents/researcher/work/queue-102/test-out/fields.out @@ -0,0 +1,13 @@ +✔ the committed table loads frozen; its verbs are the broker task verbs (1.243019ms) +✔ a write names a verb the table has as its own key, not one it inherits (0.439281ms) +✔ the schema refuses any other shape (2.463284ms) +✔ the Field table in the slice 1 plan names the verbs each field is written by (0.539528ms) +✔ each verb refuses, asks for a decision and writes as the table and the role files say (126.98207ms) +ℹ tests 5 +ℹ suites 0 +ℹ pass 5 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 204.597479 diff --git a/agents/researcher/work/queue-102/test-out/flows.out b/agents/researcher/work/queue-102/test-out/flows.out new file mode 100644 index 000000000..eb03332f8 --- /dev/null +++ b/agents/researcher/work/queue-102/test-out/flows.out @@ -0,0 +1,36 @@ +✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (27.653093ms) +✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (19.870659ms) +✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (13.928605ms) +✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (25.619289ms) +✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (13.457877ms) +✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.426276ms) +✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (19.510677ms) +✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (124.140672ms) +✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (30.560467ms) +✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (14.034126ms) +✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (25.026727ms) +✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (24.936416ms) +✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.419815ms) +✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (23.165412ms) +✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (38.885433ms) +✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (35.446877ms) +✔ terminal: engine control characters are made visible; a lost connection refuses submit (18.65797ms) +✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.441355ms) +✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.28447ms) +✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.324686ms) +✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.494799ms) +✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.085386ms) +✔ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (0.57979ms) +✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.850133ms) +✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.446367ms) +✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.349162ms) +✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.103951ms) +✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (221.414486ms) +ℹ tests 28 +ℹ suites 0 +ℹ pass 28 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2535.684165