tasks, webui: field table check, line length and bidi escapes (#1596)
Row 102, the row 88 follow-up. Built by a Haiku subagent under
Researcher (decision 129), packet agents/researcher/work/queue-102/,
row102.patch in candidate manifest
8dedcec601ee79a08b4421842af694c0acdae6af3da31b26505c581d3dbc5558.
Filbert approved on #1596, comment 28251. Mos gave the go for the one
public/ line, bus.js's UNSEEN pattern (decision 123, sage-0140).
Sage's gate rerun on 3a1e11b83 with rows 85, 102 and 103 applied,
Docker up: node suites bus 128, business 67, cli 165, control-board
124, conversation 182, discord 178, harness 73, ledger 78, mosaic 69,
queue 155, runs 43, seat 113, tasks 92, webui 108; scripts auth 15,
conductor 17, config 24, discord 66, extension-package 18,
foundation 44, queue 28, release 14, task 98. All pass, 0 fail.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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; };
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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 = '<img src=x onerror="window.injected=true"> evil';
|
||||
const HOSTILE = '<img src=x onerror="window.injected=true"> \u202eevil';
|
||||
const at = '2026-10-08T12:00:00.000Z';
|
||||
const businesses = {
|
||||
demo: {
|
||||
|
||||
Reference in New Issue
Block a user