diff --git a/agents/rocko/work/queue-219/BUILD.md b/agents/rocko/work/queue-219/BUILD.md index 73d043bc9..c352659a3 100644 --- a/agents/rocko/work/queue-219/BUILD.md +++ b/agents/rocko/work/queue-219/BUILD.md @@ -260,3 +260,27 @@ legacy store assertions stay unchanged. Only the specified comment changes. No product bytes change. If inference is wrong, report red, no third edit. Dewey must inspect this test diff before the next full restart. Local commit only while294 holds; push waits for Sage's named gap. No runs after this edit. + +## Third edit and new manifest round r2: mos-1420 / sage-3243 + +Full restart on7cbd839d0 began06:09:43.706Z and stopped06:10:30.695Z: +creative4/0/0, threads19/0/0, user-principal23/0/0, launcher34/0/0, +thread-launch13/1/0, total93 pass/1 fail/0 skip. Persona's exact two-row pin +passed. The new creative-history test failed ENOENT on its obsolete legacy +history path, after its real send/reply checks. Always-running Bean refusal +passed; sandbox-dependent Bean assertion followed the failed read and was +not reached. No coverage claim for that assertion. Slot returned after empty +cwd sweep and14 hashes checked. Original earlier red output was archived +byte-identically before this restart; all later stages unrun. + +Mos-1420 authorizes exactly the two history-path corrections in that new +test: obtain each default session from the real runtime threadsState and +read the literal threads/acme/T/sessions//session/history path. +Contents remain exactly Bean input\nBean again\n and Chet input\n. +Add one literal legacy-path nonexistence assertion per thread, no mkdir +helper. Every other file line stays unchanged. No product change. +The new r2/ manifest/patch snapshot is this third-edit round; root manifest +is refreshed to the same digest for the next claimant/review handoff. +Local commit only; Dewey checks mos1420 items1-3 before push/restart, and +Sage names a gap/CLEAR after294 round4. No runs after this edit. Any further +red, including later lines in this test, returns to Mos through Sage. diff --git a/agents/rocko/work/queue-219/archive-before-mos1418.sha256 b/agents/rocko/work/queue-219/archive-before-mos1418.sha256 new file mode 100644 index 000000000..c80c20651 --- /dev/null +++ b/agents/rocko/work/queue-219/archive-before-mos1418.sha256 @@ -0,0 +1,9 @@ +4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945 agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/cwd-sweep-final.json +4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945 agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/cwd-sweep.json +4c7009747c39843d6666ca54bb56d0f2bf991ab81bb514fa6d45053b8f6e4b92 agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/head.json +e86cfe9d1e28b8b42e9ed47c1a5836e4f9f09e700dc892935d55092785ece429 agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/network-cleanup.log +4fd1875a14b5da6e55effa090416b39529e5e00eb54660c2574727624d97ec4d agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/results.jsonl +4898d4ece63c8dcad3346c0a661087531b8b9c5471b6c49a023c4eede65726cb agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-bus-creative.test.tap +0d7e7b8a83c2d6441fe76403615839b26c0863b09e536ee77bcdafa62dd43948 agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-bus-threads.test.tap +1d4bc4439cfd51773a66efd88d22fe1d6bb826c7511a2b1b92e5040ebc293448 agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-bus-user-principal.test.tap +ededb8288ffdb04001aa39bc77d29f7f7ea9ff6cf57f814ea2bd743b4f9fbc8d agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-cli-launcher.test.tap diff --git a/agents/rocko/work/queue-219/build.patch b/agents/rocko/work/queue-219/build.patch index 5cfa4fa2d..b34097ed7 100644 --- a/agents/rocko/work/queue-219/build.patch +++ b/agents/rocko/work/queue-219/build.patch @@ -314,7 +314,7 @@ index 2202c7753..baabd843e 100644 test("row 64 on Claude Code a role skill that isn't enabled refuses the same way, before anything starts", { timeout: 30000 }, async (t) => { diff --git a/packages/cli/tests/thread-launch.test.mjs b/packages/cli/tests/thread-launch.test.mjs -index 0d207a0a0..84f8ed088 100644 +index 0d207a0a0..fa3adb866 100644 --- a/packages/cli/tests/thread-launch.test.mjs +++ b/packages/cli/tests/thread-launch.test.mjs @@ -396,6 +396,22 @@ test("REQ-THREAD-1 REQ-LAUNCH-1 the chief can't launch the assistant outside a t @@ -340,7 +340,7 @@ index 0d207a0a0..84f8ed088 100644 // Row 125 (#1619, REQ-LIMIT-3, REQ-LIMIT-4): the token-limit check switched on, with the // assistant's limit at 0, so it is at its limit before any turn. The real check runs through the // bus's plug-in loader. -@@ -443,3 +459,71 @@ test("REQ-LIMIT-4 a thread run at its token limit stops at the end of its turn: +@@ -443,3 +459,73 @@ test("REQ-LIMIT-4 a thread run at its token limit stops at the end of its turn: assert.deepEqual(threadRuns(f, A), []); assert.equal(host.runtime.threadsState("acme").find((x) => x.thread === A).holder, null); }); @@ -371,8 +371,10 @@ index 0d207a0a0..84f8ed088 100644 + assert.deepEqual((await send(bean, 'bean', 'Bean input')).before, []); + assert.deepEqual((await send(chet, 'chet', 'Chet input')).before, []); + assert.deepEqual((await send(bean, 'bean', 'Bean again')).before, ['Bean input']); -+ assert.equal(readFileSync(join(f.dataRoot, 'threads', 'acme', bean, 'session', 'history'), 'utf8'), 'Bean input\nBean again\n'); -+ assert.equal(readFileSync(join(f.dataRoot, 'threads', 'acme', chet, 'session', 'history'), 'utf8'), 'Chet input\n'); ++ assert.equal(readFileSync(join(f.dataRoot, 'threads', 'acme', bean, 'sessions', host.runtime.threadsState('acme').find(x => x.thread === bean).session, 'session', 'history'), 'utf8'), 'Bean input\nBean again\n'); ++ assert.equal(readFileSync(join(f.dataRoot, 'threads', 'acme', chet, 'sessions', host.runtime.threadsState('acme').find(x => x.thread === chet).session, 'session', 'history'), 'utf8'), 'Chet input\n'); ++ assert.equal(existsSync(join(f.dataRoot, 'threads', 'acme', bean, 'session')), false); ++ assert.equal(existsSync(join(f.dataRoot, 'threads', 'acme', chet, 'session')), false); + const launches = launchLog(f).filter(row => row.event === 'launch'); + assert.ok(launches.some(row => row.instance === 'bean' && row.thread === bean)); + assert.ok(launches.some(row => row.instance === 'chet' && row.thread === chet)); diff --git a/agents/rocko/work/queue-219/candidate-manifest.sha256 b/agents/rocko/work/queue-219/candidate-manifest.sha256 index b2ef5c5a0..841f95bd5 100644 --- a/agents/rocko/work/queue-219/candidate-manifest.sha256 +++ b/agents/rocko/work/queue-219/candidate-manifest.sha256 @@ -4,7 +4,7 @@ de4913f50801a11fd172e3f2c2b3cb41ab04e84d32a6650d1c8662b5e227ea3d packages/bus/t 8f7d6745fffb2a0898ac80c012084ac48964748da6439a8c56facc6529ffb19b packages/bus/tests/user-principal.test.mjs b74275ce43249bafc97edc87a6a3aa0431c62fe1a28cd9fa265246e6d6da219b packages/cli/src/launcher.mjs 2c3f120dfdc0b52bf8ab38a88e86251be75d4b0d218e23df489dcaf815d6bc15 packages/cli/tests/launcher.test.mjs -a219036737419870e0aa9d0da20843759a66be5bafff44d3292593833f8fcb9a packages/cli/tests/thread-launch.test.mjs +7f308fdf601e839194f026beb3fea279eba1e356ce2756aa4a42fa4873b79b30 packages/cli/tests/thread-launch.test.mjs cbfd54e6b375561d1e4679ee8d78a3285f970d9a7198cec9d22a06510a36afba packages/webui/src/chat-http.mjs f49a49c6ec69783190d3f5eaf34316cb4d2c44d10d2ee9336fe1383207afefff packages/webui/src/chat.mjs 8e89df5fe8a9da35de41b5968afc6efcd92503e92ef28c6f557bb5371d10d1a4 packages/webui/src/public/chat.js diff --git a/agents/rocko/work/queue-219/r2/BUILD.md b/agents/rocko/work/queue-219/r2/BUILD.md new file mode 100644 index 000000000..7454edd03 --- /dev/null +++ b/agents/rocko/work/queue-219/r2/BUILD.md @@ -0,0 +1,6 @@ +# Row219 r2 / mos1420 third edit + +See ../BUILD.md for the full unchanged scope and preserved red chronology. +This round changes only the new thread-launch native-history paths and adds +the two legacy-path absence assertions approved by mos1420. No run yet. +Dewey pre-restart diff check and Sage push/run CLEAR are required. diff --git a/agents/rocko/work/queue-219/r2/base.txt b/agents/rocko/work/queue-219/r2/base.txt new file mode 100644 index 000000000..1d519a39a --- /dev/null +++ b/agents/rocko/work/queue-219/r2/base.txt @@ -0,0 +1 @@ +4a9c536e861f1d9f9a018019b3ac4519d65eccf0 diff --git a/agents/rocko/work/queue-219/r2/build.patch b/agents/rocko/work/queue-219/r2/build.patch new file mode 100644 index 000000000..b34097ed7 --- /dev/null +++ b/agents/rocko/work/queue-219/r2/build.patch @@ -0,0 +1,788 @@ +diff --git a/packages/bus/src/broker.mjs b/packages/bus/src/broker.mjs +index d76085739..11061c778 100644 +--- a/packages/bus/src/broker.mjs ++++ b/packages/bus/src/broker.mjs +@@ -4,7 +4,7 @@ import { initializeControl, processingStatus } from '../../processing-control/sr + import { NAME as HARNESS_NAME } from '../../plugins/src/kinds.mjs'; + import { loadRequirements } from '../../tasks/src/requirements.mjs'; + import { MERGE, LandingRefusal, readLanding } from './landing.mjs'; +-import { ID_PATTERN, THREAD_DEFINITION, END_PROTECTED, forkProblem, forkUrl, credentialKind, validArgv, commandAllowed, HOST_ID, CREATIVE_DEFINITIONS, unhireableAction, giteaName, parseGiteaUrl, covers } from '../../business/src/vocabulary.mjs'; ++import { ID_PATTERN, THREAD_DEFINITIONS, END_PROTECTED, forkProblem, forkUrl, credentialKind, validArgv, commandAllowed, HOST_ID, CREATIVE_DEFINITIONS, unhireableAction, giteaName, parseGiteaUrl, covers } from '../../business/src/vocabulary.mjs'; + + // Kept closed while S1 lands; its vocabulary is the integration authority. + export const ACTIONS = Object.freeze([ +@@ -314,7 +314,7 @@ const enrollBinding = (enroll) => ({ + }); + // r6 E3: the installer line's comment, then the tag's 32 hex digits. + const INSTALLER_COMMENT = 'mosaic-installer-'; +-// Row 121 (#1615): an instance of THREAD_DEFINITION (vocabulary.mjs since row 155) talks with the ++// Row 121 (#1615): an instance of THREAD_DEFINITIONS (vocabulary.mjs since row 213) talks with the + // user in threads. Its runs claim a thread, not the role, and every message to or from it names its + // thread (REQ-MSG-1). + // Row 146 (#1640): the sender of the bus's notice to launch.by. A role instance name matches the +@@ -598,7 +598,7 @@ export class Broker { + ); + } + #threaded(business, role) { +- return this.#business(business).roles[role]?.definition === THREAD_DEFINITION; ++ return THREAD_DEFINITIONS.includes(this.#business(business).roles[role]?.definition); + } + // Row 264a (#1769): a claim holds one session of a thread, and each session has its own holder. + #sessionHolder(session) { +@@ -6068,24 +6068,27 @@ export class Broker { + .all('SELECT id,at,name,repo FROM projects WHERE business=? ORDER BY seq', s.business) + .filter((p) => !s.user || this.#granted(s, 'project.list', p.id, null)); + } +- // A thread belongs to the user who made it and to the business's one thread instance. ++ // A thread belongs to the user who made it and to one declared thread instance. + // Row 177 (#1675): a tester's thread is owned by `tester`, so neither reaches the other's. + case 'thread.create': { + if (!s.tester && !s.user) this.#human(s); +- keys(a, ['project', 'name'], ['project', 'name']); ++ keys(a, ['project', 'name', 'instance'], ['project', 'name']); + if (!name(a.name)) fail('invalid-request'); ++ const instances = Object.keys(b.roles).filter((r) => this.#threaded(s.business, r)); ++ const instance = a.instance === undefined ++ ? (instances.length === 1 ? instances[0] : null) ++ : identifier(a.instance); + // FED R3 (design 3.5): a user's grant must cover the project and the thread instance, checked + // before the project is looked up, so an ungranted id reads the same whether it exists or not. + if (s.user) { +- const threaded = Object.keys(b.roles).filter((r) => this.#threaded(s.business, r)); +- if (threaded.length !== 1 || !this.#granted(s, 'thread.create', identifier(a.project), threaded[0])) fail('access-refused'); ++ if (!instance || !instances.includes(instance) || !this.#granted(s, 'thread.create', identifier(a.project), instance)) ++ fail('access-refused'); + } + if (!this.#store.get('SELECT 1 FROM projects WHERE id=? AND business=?', identifier(a.project), s.business)) + fail('project-not-found'); +- // The bus never picks between instances; the business file declares exactly one. +- const instances = Object.keys(b.roles).filter((r) => this.#threaded(s.business, r)); +- if (!instances.length) fail('thread-instance-missing'); +- if (instances.length > 1) fail('thread-instance-ambiguous'); ++ // The human selects an instance; omission is compatible only with one candidate. ++ if (!instances.length || (a.instance !== undefined && !instances.includes(instance))) fail('thread-instance-missing'); ++ if (a.instance === undefined && instances.length > 1) fail('thread-instance-ambiguous'); + if (this.#store.get('SELECT 1 FROM threads WHERE project=? AND name=?', a.project, a.name)) fail('thread-exists'); + const tid = randomUUID(); + this.#store.run( +@@ -6094,13 +6097,13 @@ export class Broker { + this.#now(), + s.business, + a.project, +- instances[0], ++ instance, + this.#owner(s), + a.name, + ); + // Row 264a (#1769, D3): every thread has a default session from the start. +- this.#newSession(s, { id: tid, business: s.business, instance: instances[0], owner: this.#owner(s) }, 'default', 'default', true); +- return { id: tid, instance: instances[0] }; ++ this.#newSession(s, { id: tid, business: s.business, instance, owner: this.#owner(s) }, 'default', 'default', true); ++ return { id: tid, instance }; + } + // Row 177 (#1675): each lists its own threads. The human's list leaves out the tester's, + // and the tester's lists only its own (row 175 listed it the business human's). +diff --git a/packages/bus/tests/creative.test.mjs b/packages/bus/tests/creative.test.mjs +index 6cfbf1cd6..6d421ba19 100644 +--- a/packages/bus/tests/creative.test.mjs ++++ b/packages/bus/tests/creative.test.mjs +@@ -44,15 +44,18 @@ function setup(t) { + const caps = {}; + caps.human = b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true }); + const project = b.request(caps.human, { verb: 'project.create', args: { name: 'demo' } }).id; +- const thread = b.request(caps.human, { verb: 'thread.create', args: { project, name: 'main' } }).id; ++ const thread = b.request(caps.human, { verb: 'thread.create', args: { project, name: 'main', instance: 'assistant' } }).id; ++ const threads = { assistant: thread }; + for (const role of Object.keys(roles)) { +- caps[role] = b.bindLaunch({ business: 'demo', role, run: `${role}-run`, harness: 'pi', ...(role === 'assistant' ? { thread } : {}) }); ++ if (['bean', 'quill'].includes(role)) ++ threads[role] = b.request(caps.human, { verb: 'thread.create', args: { project, name: role, instance: role } }).id; ++ caps[role] = b.bindLaunch({ business: 'demo', role, run: `${role}-run`, harness: 'pi', ...(threads[role] ? { thread: threads[role] } : {}) }); + b.request(caps[role], { verb: 'role.claim' }); + } + caps.tester = b.bindTester({ business: 'demo' }); + const call = (who, verb, args = {}) => b.request(caps[who], { verb, args }); + const count = (sql) => store.get(sql).n; +- return { call, count, store }; ++ return { call, count, store, threads }; + } + + const raise = { action: 'policy.change', question: 'Change?', options: [{ key: 'yes', text: 'Yes' }, { key: 'no', text: 'No' }], recommendation: 'yes', blocking: false }; +@@ -72,7 +75,7 @@ test('M6: a creative raise refuses decision-not-allowed, cited or not, and no de + }); + + test('a creative instance sends only to the user and hears only from the user', (t) => { +- const { call, count } = setup(t); ++ const { call, count, threads } = setup(t); + for (const who of ['bean', 'quill']) { + call(who, 'message.send', { to: 'human', body: 'hello' }); + for (const to of ['chief', 'assistant', 'pm', 'bean', 'quill'].filter((n) => n !== who)) { +@@ -81,7 +84,7 @@ test('a creative instance sends only to the user and hears only from the user', + for (const from of ['chief', 'pm']) { + assert.throws(() => call(from, 'message.send', { to: who, body: 'hello' }), { code: 'message-sender-refused' }, `${from} to ${who}`); + } +- call('human', 'message.send', { to: who, body: 'hello' }); ++ call('human', 'thread.send', { thread: threads[who], body: 'hello' }); + } + assert.equal(count("SELECT count(*) n FROM messages WHERE from_role IN ('bean','quill')"), 2); + assert.equal(count("SELECT count(*) n FROM messages WHERE to_role IN ('bean','quill')"), 2); +diff --git a/packages/bus/tests/threads.test.mjs b/packages/bus/tests/threads.test.mjs +index 8b31da9f9..1410ec3fa 100644 +--- a/packages/bus/tests/threads.test.mjs ++++ b/packages/bus/tests/threads.test.mjs +@@ -601,3 +601,42 @@ test('project.create keeps unrelated names with and without a repository', t => + { id: linked.id, name: 'work', repo: 'stack' }, + ]); + }); ++ ++test('C2-09a: explicit creative instances bind independent threads, default sessions and holders', t => { ++ const creative = ['companion', 'ideas', 'research', 'proofreader', 'fact-checker', 'continuity']; ++ const extra = Object.fromEntries(creative.map((definition, i) => [`writer${i}`, { definition, authority: { withinRole: ['message.send'], crossRole: [] } }])); ++ const ctx = setup(t, demo({ roles: { ...roles, ...extra }, definitions: { ++ ...definitions, ...Object.fromEntries(creative.map(definition => [definition, { messaging: shipped(definition).messaging }])), ++ } })); ++ const project = ctx.call('human', 'project.create', { name: 'Writing' }).id; ++ assert.throws(() => ctx.call('human', 'thread.create', { project, name: 'ambiguous' }), { code: 'thread-instance-ambiguous' }); ++ for (const [instance, role] of Object.entries(extra)) { ++ const receipt = ctx.call('human', 'thread.create', { project, name: instance, instance }); ++ assert.equal(receipt.instance, instance); ++ assert.equal(ctx.store.get('SELECT instance FROM threads WHERE id=?', receipt.id).instance, instance); ++ assert.equal(ctx.store.get('SELECT instance FROM sessions WHERE thread=?', receipt.id).instance, instance); ++ ctx.launch(instance, instance, { thread: receipt.id }); ++ assert.equal(ctx.claim(instance).thread, receipt.id); ++ const sent = ctx.call('human', 'thread.send', { thread: receipt.id, body: `For ${instance}` }); ++ const read = ctx.call(instance, 'message.receive'); ++ assert.deepEqual(read.map(m => m.id), [sent.id], role.definition); ++ assert.equal(ctx.threadOf(sent.id), receipt.id); ++ assert.equal(ctx.store.get('SELECT count(*) n FROM role_claims WHERE role=?', instance).n, 0); ++ assert.throws(() => ctx.call('human', 'message.send', { to: instance, body: 'No implicit thread' }), ++ { code: 'thread-required-use-thread-send' }); ++ } ++ assert.equal(ctx.count('SELECT count(*) n FROM threads'), creative.length); ++}); ++ ++test('C2-09a: explicit missing, non-thread and malformed selectors create no thread or session', t => { ++ const ctx = setup(t); ++ const project = ctx.call('human', 'project.create', { name: 'Writing' }).id; ++ for (const instance of ['not-declared', 'pm', 'coder']) ++ assert.throws(() => ctx.call('human', 'thread.create', { project, name: instance, instance }), { code: 'thread-instance-missing' }); ++ for (const instance of [null, 7, {}, [], '', 'bad name']) ++ assert.throws(() => ctx.call('human', 'thread.create', { project, name: 'bad', instance }), { code: 'invalid-request' }); ++ assert.equal(ctx.count('SELECT count(*) n FROM threads'), 0); ++ assert.equal(ctx.count('SELECT count(*) n FROM sessions'), 0); ++ const legacy = ctx.call('human', 'thread.create', { project, name: 'Default' }); ++ assert.equal(legacy.instance, 'assistant'); ++}); +diff --git a/packages/bus/tests/user-principal.test.mjs b/packages/bus/tests/user-principal.test.mjs +index 4dcd1ebeb..8471ddf85 100644 +--- a/packages/bus/tests/user-principal.test.mjs ++++ b/packages/bus/tests/user-principal.test.mjs +@@ -23,7 +23,7 @@ const grant = (over = {}) => ({ + }); + + // `processing` stands in for the halt switch, as lifecycle-verbs.test.mjs's T15 does. +-function setup(t, { grants = [grant()], processing = null } = {}) { ++function setup(t, { grants = [grant()], processing = null, threadRoles = {} } = {}) { + const root = mkdtempSync(join(tmpdir(), 'bus-user-principal-')); + const store = new Store(root); + t.after(() => { +@@ -40,6 +40,7 @@ function setup(t, { grants = [grant()], processing = null } = {}) { + roles: { + chief: { definition: 'chief-of-staff', authority: { withinRole: ['message.send'], crossRole: [] } }, + assistant: { definition: 'assistant', authority: { withinRole: ['message.send'], crossRole: [] } }, ++ ...threadRoles, + }, + definitions: { 'chief-of-staff': {}, assistant: { messaging: shipped('assistant').messaging } }, + }, +@@ -394,3 +395,21 @@ test('R3 r4: the chief\'s hire refuses an instance named user with the code it g + test('R3 r4: a non-function access throws before the store is read', () => { + assert.throws(() => new Broker({ store: null, businesses: { demo: {} }, access: 'access.json' }), /access must be a function/); + }); ++ ++test('C2-09a: naming an instance cannot bypass its collaborator grant or disclose an ungranted project', t => { ++ const ctx = setup(t, { threadRoles: { bean: { definition: 'companion', authority: { withinRole: ['message.send'], crossRole: [] } } } }); ++ const project = ctx.call('human', 'project.create', { name: 'shared' }).id; ++ const privateProject = ctx.call('human', 'project.create', { name: 'private' }).id; ++ ctx.access.doc.grants = [grant({ projects: [project] })]; ++ ctx.user('alice'); ++ assert.equal(ctx.code('alice', 'thread.create', { project, name: 'blocked bean', instance: 'bean' }), 'access-refused'); ++ for (const id of [privateProject, '00000000-0000-4000-8000-000000000000']) ++ assert.equal(ctx.code('alice', 'thread.create', { project: id, name: 'blocked project', instance: 'assistant' }), 'access-refused'); ++ assert.equal(ctx.store.get('SELECT count(*) n FROM threads').n, 0); ++ const allowed = ctx.call('alice', 'thread.create', { project, name: 'assistant work', instance: 'assistant' }); ++ assert.equal(allowed.instance, 'assistant'); ++ ctx.access.doc.grants = [grant({ projects: [project], instances: ['bean'] })]; ++ const bean = ctx.call('alice', 'thread.create', { project, name: 'bean work', instance: 'bean' }); ++ assert.equal(bean.instance, 'bean'); ++ assert.equal(ctx.code('alice', 'thread.create', { project, name: 'old instance', instance: 'assistant' }), 'access-refused'); ++}); +diff --git a/packages/cli/src/launcher.mjs b/packages/cli/src/launcher.mjs +index bfe55b61e..d8248284c 100644 +--- a/packages/cli/src/launcher.mjs ++++ b/packages/cli/src/launcher.mjs +@@ -53,7 +53,7 @@ + // session.json names it as gitSocket. + // + // Thread sessions (row 121, #1615). A thread instance (an instance whose +-// role definition is the assistant) runs once per thread, never on its own: ++// role definition is in THREAD_DEFINITIONS) runs once per thread, never on its own: + // a launch from a session or --pm refuses thread-required. Every tick + // (threadTickMs) the host asks the broker for the business's threads + // (launch op threadsState) and: +@@ -139,6 +139,7 @@ import { createServer } from "node:net"; + import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync } from "node:fs"; + import { dirname, join, relative } from "node:path"; + import { configDir, loadBusiness, resolveInstance, systemVars } from "../../business/src/index.mjs"; ++import { THREAD_DEFINITIONS } from "../../business/src/vocabulary.mjs"; + import { limitHit } from "../../business/src/launch.mjs"; + import { BundleError, buildBundle, hasFallback, readSkills, sessionModel } from "../../harness/src/bundle.mjs"; + import { createLoader } from "../../plugins/src/loader.mjs"; +@@ -176,8 +177,6 @@ const STOP_TIMEOUT_MS = 30000; + const LINE_MAX = 4096; + // How often the host asks for the threads' state; a poll period, not a limit. + const THREAD_TICK_MS = 2000; +-// The broker's thread instance definition (packages/bus/src/broker.mjs). +-const THREAD_DEFINITION = "assistant"; + // Row 194: the broker's LIFECYCLE_REQUEST_MS. A regenerate's old run must be gone by then. + const LIFECYCLE_REQUEST_MS = 60000; + +@@ -331,7 +330,7 @@ export function createLauncher({ + try { b=loadBusiness(host.business,{dir:configDir(env),rolesDir:rolesDir(env)}); } catch(e){throw new Refusal("organization-refused",e.message);} + if (!b.launch) throw new Refusal("launch-not-configured", `business ${b.id} has no launch block`); + if (!(pm ? instance === b.launch.by : b.launch.instances.includes(instance) || (human && Object.hasOwn(b.roles, instance)))) throw new Refusal("instance-not-listed"); +- const threaded = b.roles[instance]?.definition === THREAD_DEFINITION; ++ const threaded = THREAD_DEFINITIONS.includes(b.roles[instance]?.definition); + if (threaded && !thread) throw new Refusal("thread-required", `${instance} runs only for a thread`); + if (!threaded && thread) throw new Refusal("thread-not-allowed", `${instance} isn't a thread instance`); + if (threaded && !session) throw new Refusal("session-required", `${instance} runs for one session of a thread`); +@@ -997,7 +996,7 @@ export function createLauncher({ + function ending(r) { + const runs = r.runs ?? []; + const at = Date.parse(r.at); +- const threaded = b.roles[r.instance]?.definition === THREAD_DEFINITION; ++ const threaded = THREAD_DEFINITIONS.includes(b.roles[r.instance]?.definition); + return registry.sessions.filter((s) => s.business === b.id && (runs.includes(s.run) || + (!threaded && s.instance === r.instance && (spawnedAt.get(s.run) ?? Date.parse(s.startedAt)) <= at))); + } +diff --git a/packages/cli/tests/launcher.test.mjs b/packages/cli/tests/launcher.test.mjs +index 2202c7753..baabd843e 100644 +--- a/packages/cli/tests/launcher.test.mjs ++++ b/packages/cli/tests/launcher.test.mjs +@@ -773,21 +773,34 @@ test("row 213 a creative instance's persona is read from /personas: o + const dir = join(given.root, "config", "personas", "acme"); + mkdirSync(dir, { recursive: true, mode: 0o700 }); + writeFileSync(join(dir, "bean.md"), "Not the pinned text.\n", { mode: 0o600 }); +- const { f, host, launcher } = await setup(t, undefined, given); +- // The chief of staff holds role.launch; a test chief session bound to this process asks. +- const chief = await host.bindLaunch({ business: "acme", role: "chief", run: "chief-test", harness: "pi", pid: process.pid, startTime: startTimeOf(process.pid) }); +- await new Client({ path: host.path, cap: chief.cap }).call("role.claim"); +- // The bundle's refusal comes back as launch-failed, as webPlan's does, with its reason in the log. +- assert.deepEqual(await launcher.fromSession(chief.cap, "bean"), { ok: false, error: "launch-failed" }); +- const [e] = log(f); +- assert.deepEqual([e.event, e.code, log(f).length], ["refused", "launch-failed", 1]); ++ // Row 219: creative instances launch from their thread, not from a chief's standalone request. ++ // The mismatch occurs before bindLaunch, so only the host's thread-state snapshot is a fixture. ++ const thread = 'persona-thread'; ++ const SESSION = 'persona-session'; ++ const { f, launcher } = await setup(t, undefined, given, { wrap: host => ({ ++ ...host, ++ op: message => message.op === 'threadsState' ++ ? Promise.resolve([{ thread, session: SESSION, instance: 'bean', holder: null, pending: 1 }]) ++ : host.op(message), ++ }) }); ++ // The thread path records row 270's run-start count before the bundle's persona refusal. ++ await launcher.tick(); ++ const runs = readdirSync(join(f.dataRoot, "launches", "acme")); ++ const entries = log(f); ++ assert.equal(entries.length, 2); ++ const { at, ...compactions } = entries[0]; ++ assert.deepEqual(compactions, { event: 'compactions', instance: 'bean', thread, session: SESSION, run: runs[0], count: 0, known: true, reason: null }); ++ const e = entries[1]; ++ assert.deepEqual([e.event, e.code], ["refused", "launch-failed"]); ++ assert.equal(e.thread, thread); + assert.equal(e.why, `instance bean: persona file ${join(dir, "bean.md")} isn't the file persona.sha256 names`); + assert.ok(!JSON.stringify(log(f)).includes("PERSONA-MARKER-d2")); + assert.deepEqual(readSessions(f.dataRoot), []); + // The run directory exists by then (BUILD.md residual); the persona is read before any bundle file. +- const runs = readdirSync(join(f.dataRoot, "launches", "acme")); + assert.equal(runs.length, 1); +- assert.deepEqual(readdirSync(join(f.dataRoot, "launches", "acme", runs[0])), ["session"]); ++ assert.deepEqual(readdirSync(join(f.dataRoot, "launches", "acme", runs[0])), []); ++ assert.equal(existsSync(join(f.dataRoot, "threads", "acme", thread, "sessions", SESSION, "session")), true); ++ assert.equal(existsSync(join(f.dataRoot, "threads", "acme", thread, "session")), false); + }); + + test("row 64 on Claude Code a role skill that isn't enabled refuses the same way, before anything starts", { timeout: 30000 }, async (t) => { +diff --git a/packages/cli/tests/thread-launch.test.mjs b/packages/cli/tests/thread-launch.test.mjs +index 0d207a0a0..fa3adb866 100644 +--- a/packages/cli/tests/thread-launch.test.mjs ++++ b/packages/cli/tests/thread-launch.test.mjs +@@ -396,6 +396,22 @@ test("REQ-THREAD-1 REQ-LAUNCH-1 the chief can't launch the assistant outside a t + assert.deepEqual(launchLog(f).map((e) => [e.event, e.instance, e.code]), [["refused", "assistant", "thread-required"]]); + }); + ++// Admission refuses before bundle construction or a sandbox is needed. ++test('C2-09a: fromSession refuses a creative instance outside a thread without a sandbox', { timeout: 30000 }, async t => { ++ const prepared = prepare(t, { edit: doc => { ++ doc.roles.bean = { ...doc.roles.assistant, definition: 'companion' }; ++ doc.launch.instances.push('bean'); ++ return doc; ++ } }); ++ const { f } = prepared; ++ const host = await start(t, prepared); ++ const chief = await host.host.bindLaunch({ business: 'acme', role: 'chief', run: 'chief-bean-refusal', harness: 'pi', pid: process.pid, startTime: startTimeOf(process.pid) }); ++ await new Client({ path: host.host.path, cap: chief.cap }).call('role.claim'); ++ assert.deepEqual(await host.launcher.fromSession(chief.cap, 'bean'), { ok: false, error: 'thread-required' }); ++ assert.deepEqual(readSessions(f.dataRoot), []); ++ assert.deepEqual(launchLog(f).map(e => [e.event, e.instance, e.code]), [['refused', 'bean', 'thread-required']]); ++}); ++ + // Row 125 (#1619, REQ-LIMIT-3, REQ-LIMIT-4): the token-limit check switched on, with the + // assistant's limit at 0, so it is at its limit before any turn. The real check runs through the + // bus's plug-in loader. +@@ -443,3 +459,73 @@ test("REQ-LIMIT-4 a thread run at its token limit stops at the end of its turn: + assert.deepEqual(threadRuns(f, A), []); + assert.equal(host.runtime.threadsState("acme").find((x) => x.thread === A).holder, null); + }); ++ ++test('C2-09a: creative thread messages start distinct runs and keep each instance history separate', { skip, timeout: 90000 }, async t => { ++ const prepared = prepare(t, { sonnet: 2, idle: 60, edit: doc => { ++ const vars = { ...doc.roles.assistant.vars }; ++ delete doc.roles.assistant; ++ doc.roles.bean = { definition: 'companion', vars: { ...vars } }; ++ doc.roles.chet = { definition: 'continuity', vars: { ...vars } }; ++ doc.launch.instances = ['coder', 'bean', 'chet']; ++ return doc; ++ } }); ++ const host = await start(t, prepared), { f } = prepared; ++ const project = host.call('project.create', { name: 'Writing' }).id; ++ const bean = host.call('thread.create', { project, name: 'Bean', instance: 'bean' }).id; ++ const chet = host.call('thread.create', { project, name: 'Chet', instance: 'chet' }).id; ++ const send = async (thread, instance, body) => { ++ const message = host.call('thread.send', { thread, body }); ++ const outcome = await until(() => host.call('thread.messages', { thread }).find(m => m.in_reply_to === message.id) || ++ launchLog(f).find(row => row.thread === thread && row.event === 'refused')); ++ assert.notEqual(outcome.event, 'refused', `creative thread launch refused: ${outcome.code}`); ++ const reply = outcome; ++ assert.equal(reply.from_role, instance); ++ assert.equal(reply.to_role, 'human'); ++ return JSON.parse(reply.body.match(/THREAD (\{.*\})/)[1]); ++ }; ++ assert.deepEqual((await send(bean, 'bean', 'Bean input')).before, []); ++ assert.deepEqual((await send(chet, 'chet', 'Chet input')).before, []); ++ assert.deepEqual((await send(bean, 'bean', 'Bean again')).before, ['Bean input']); ++ assert.equal(readFileSync(join(f.dataRoot, 'threads', 'acme', bean, 'sessions', host.runtime.threadsState('acme').find(x => x.thread === bean).session, 'session', 'history'), 'utf8'), 'Bean input\nBean again\n'); ++ assert.equal(readFileSync(join(f.dataRoot, 'threads', 'acme', chet, 'sessions', host.runtime.threadsState('acme').find(x => x.thread === chet).session, 'session', 'history'), 'utf8'), 'Chet input\n'); ++ assert.equal(existsSync(join(f.dataRoot, 'threads', 'acme', bean, 'session')), false); ++ assert.equal(existsSync(join(f.dataRoot, 'threads', 'acme', chet, 'session')), false); ++ const launches = launchLog(f).filter(row => row.event === 'launch'); ++ assert.ok(launches.some(row => row.instance === 'bean' && row.thread === bean)); ++ assert.ok(launches.some(row => row.instance === 'chet' && row.thread === chet)); ++ const chief = await host.host.bindLaunch({ business: 'acme', role: 'chief', run: 'chief-creative-test', harness: 'pi', pid: process.pid, startTime: startTimeOf(process.pid) }); ++ await new Client({ path: host.host.path, cap: chief.cap }).call('role.claim'); ++ assert.deepEqual(await host.launcher.fromSession(chief.cap, 'bean'), { ok: false, error: 'thread-required' }); ++}); ++ ++test('C2-09a: terminating one creative thread does not signal an unselected peer of the same instance', { skip, timeout: 90000 }, async t => { ++ const prepared = prepare(t, { sonnet: 2, idle: 60, edit: doc => { ++ doc.roles.bean = { ...doc.roles.assistant, definition: 'companion' }; ++ delete doc.roles.assistant; ++ doc.launch.instances = ['coder', 'bean']; ++ return doc; ++ } }); ++ let requests = []; ++ const host = await start(t, prepared, { ops: () => ({ ++ lifecycleRequests: () => requests, ++ lifecycleTake: () => ({ ...requests[0], go: true }), ++ lifecycleDone: () => { requests = []; return {}; }, ++ }) }); ++ const { f } = prepared; ++ const project = host.call('project.create', { name: 'Writing' }).id; ++ const threads = ['one', 'two'].map(name => host.call('thread.create', { project, name, instance: 'bean' }).id); ++ for (const thread of threads) { ++ const message = host.call('thread.send', { thread, body: 'Stay in this thread' }); ++ const outcome = await until(() => host.call('thread.messages', { thread }).find(row => row.in_reply_to === message.id) || ++ launchLog(f).find(row => row.thread === thread && row.event === 'refused')); ++ assert.notEqual(outcome.event, 'refused', `creative thread launch refused: ${outcome.code}`); ++ } ++ const runs = threads.map(thread => launchLog(f).find(row => row.thread === thread && row.event === 'launch').run); ++ requests = [{ request: 'one-thread-end', state: 'open', business: 'acme', action: 'lifecycle.end', ++ instance: 'bean', runs: [runs[0]], by: f.doc.human, at: new Date(Date.now() + 1000).toISOString() }]; ++ await host.launcher.tick(); ++ await until(() => launchLog(f).some(row => row.event === 'lifecycle-end' && row.run === runs[0])); ++ assert.deepEqual(launchLog(f).filter(row => row.event === 'lifecycle-end').map(row => row.run), [runs[0]]); ++ await until(() => !readSessions(f.dataRoot).some(row => row.run === runs[0])); ++ assert.ok(readSessions(f.dataRoot).some(row => row.run === runs[1])); ++}); +diff --git a/packages/webui/src/chat-http.mjs b/packages/webui/src/chat-http.mjs +index c7770726d..396b42f3c 100644 +--- a/packages/webui/src/chat-http.mjs ++++ b/packages/webui/src/chat-http.mjs +@@ -78,7 +78,8 @@ export async function chatHTTP(req, res, { path, search, chat, named, send, prev + (Object.hasOwn(v, 'repo') && (typeof v.repo !== 'string' || !KEY.test(v.repo)))) throw Error(); + action = 'createProject'; args = v; + } else if (path === '/api/chat/threads') { +- if (!exact(v, ['project', 'name']) || !id(v.project) || !name(v.name)) throw Error(); ++ if (!exact(v, ['project', 'name', 'instance'], ['project', 'name']) || !id(v.project) || !name(v.name) || ++ (Object.hasOwn(v, 'instance') && (typeof v.instance !== 'string' || !KEY.test(v.instance)))) throw Error(); + action = 'createThread'; args = v; + } else if (match?.[2] === 'messages') { + if (!exact(v, ['body']) || typeof v.body !== 'string' || !v.body.trim() || v.body.length > 32768) throw Error(); +diff --git a/packages/webui/src/chat.mjs b/packages/webui/src/chat.mjs +index 018574ad4..65b89dab3 100644 +--- a/packages/webui/src/chat.mjs ++++ b/packages/webui/src/chat.mjs +@@ -20,7 +20,7 @@ export function createChat({ call }) { + projects: () => channel('project.list', {}), + createProject: ({ name, repo }) => channel('project.create', { name, ...(repo === undefined ? {} : { repo }) }), + threads: ({ project } = {}) => channel('thread.list', project === undefined ? {} : { project }), +- createThread: ({ project, name }) => channel('thread.create', { project, name }), ++ createThread: ({ project, name, instance }) => channel('thread.create', { project, name, ...(instance === undefined ? {} : { instance }) }), + messages: ({ thread, after, limit }) => channel('thread.messages', { thread, ...(after ? { after } : {}), limit }), + send: ({ thread, body }) => channel('thread.send', { thread, body, class: 'REQUEST' }), + open: ({ thread }) => channel('thread.open', { thread }), +diff --git a/packages/webui/src/public/chat.js b/packages/webui/src/public/chat.js +index a6b805c1b..15066bdc6 100644 +--- a/packages/webui/src/public/chat.js ++++ b/packages/webui/src/public/chat.js +@@ -14,7 +14,7 @@ + return states.get(id); + }; + const form = key => { +- if (!forms.has(key)) forms.set(key, { name: '', repo: '', receipt: '', pending: false }); ++ if (!forms.has(key)) forms.set(key, { name: '', repo: '', instance: '', instanceChosen: false, receipt: '', pending: false }); + return forms.get(key); + }; + class ChatError extends Error { +@@ -58,7 +58,11 @@ + function creation(kind, key) { + const f = form(key), project = kind === 'project'; + const repo = project && current.repos.length ? `` : ''; +- return `

New ${kind}

${repo}

${txt(f.receipt)}

`; ++ const choices = current.instances ?? []; ++ const picker = project ? '' : ``; ++ const unavailable = !project && (!!current.instanceError || !choices.some(row => row.instance === f.instance)); ++ const status = !project && current.instanceError ? current.instanceError : f.receipt || (!project && !choices.length ? 'No thread instances are configured.' : ''); ++ return `

New ${kind}

${repo}${picker}

${txt(status)}

`; + } + function threads(rows) { + return rows.length ? `` : '

No threads yet.

'; +@@ -106,7 +110,7 @@ + const f = document.getElementById(keep.field.id); + if (f && !f.disabled) { + f.focus({ preventScroll: true }); +- if (keep.field.start !== null) f.setSelectionRange(keep.field.start, keep.field.end); ++ if (keep.field.start != null && typeof f.setSelectionRange === 'function') f.setSelectionRange(keep.field.start, keep.field.end); + } + } else if (navigated) c.container.querySelector('h1')?.focus({ preventScroll: true }); + } +@@ -126,7 +130,7 @@ + paint(); return; + } + const old = current; +- current = { container, kind, id, canSend, projects: old?.projects ?? [], threads: old?.threads ?? [], repos: old?.repos ?? [], error: '', refused: false }; ++ current = { container, kind, id, canSend, projects: old?.projects ?? [], threads: old?.threads ?? [], repos: old?.repos ?? [], instances: old?.instances ?? [], instanceError: kind === 'project' ? 'Reading thread instances…' : '', error: '', refused: false }; + if (navigated || !old) container.innerHTML = '

Reading chat…

'; + try { + const s = kind === 'thread' ? state(id) : null; +@@ -147,6 +151,25 @@ + s.receipt = 'Message shown in this conversation.'; s.receiptId = null; + } + } ++ if (kind === 'project') { ++ try { ++ const listed = await request('instances', undefined, signal); ++ if (g !== generation || out()) return; ++ if (!listed.rows.every(row => typeof row?.instance === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,159}$/.test(row.instance) && typeof row.name === 'string')) ++ throw new ChatError('invalid-response'); ++ current.instances = listed.rows; current.instanceError = ''; ++ const f = form(id); ++ if (f.instance && !listed.rows.some(row => row.instance === f.instance)) { ++ f.instance = ''; f.instanceChosen = true; ++ f.receipt = 'The selected instance is no longer available. Choose who this thread is with.'; ++ } ++ if (!f.instanceChosen && !f.instance && listed.rows.length === 1) f.instance = listed.rows[0].instance; ++ } catch (err) { ++ if (err.name === 'AbortError' || out() || g !== generation) return; ++ current.instances = []; ++ current.instanceError = "Couldn't read the thread instances; reload to try again"; ++ } ++ } + // Repository choices are optional metadata; an unavailable read must not block unlinked chat. + if (kind === 'list') { + try { +@@ -179,7 +202,11 @@ + if (!current || out()) return; + if (event.target.id === 'chat-body') state(current.id).draft = event.target.value; + const f = event.target.closest('.chat-create'); +- if (f && event.target.dataset.chatField) form(f.dataset.key)[event.target.dataset.chatField] = event.target.value; ++ if (f && event.target.dataset.chatField) { ++ const draft = form(f.dataset.key), field = event.target.dataset.chatField; ++ draft[field] = event.target.value; ++ if (field === 'instance') { draft.instanceChosen = true; draft.receipt = ''; paint(); } ++ } + }); + document.addEventListener('scroll', event => { + if (event.target.matches?.('.chat-log') && current?.kind === 'thread') { +@@ -232,7 +259,8 @@ + void write('send', id, { body: state(id).draft }, `threads/${id}/messages`); + } else if (event.target.classList.contains('chat-create')) { + const key = event.target.dataset.key, f = form(key), project = key === 'projects'; +- const value = project ? { name: f.name, ...(f.repo ? { repo: f.repo } : {}) } : { project: key, name: f.name }; ++ if (!project && (current.instanceError || !(current.instances ?? []).some(row => row.instance === f.instance))) return; ++ const value = project ? { name: f.name, ...(f.repo ? { repo: f.repo } : {}) } : { project: key, name: f.name, instance: f.instance }; + void write(project ? 'createProject' : 'createThread', key, value, project ? 'projects' : 'threads'); + } + }); +diff --git a/packages/webui/src/serve.mjs b/packages/webui/src/serve.mjs +index 288871d0c..d9d27a062 100644 +--- a/packages/webui/src/serve.mjs ++++ b/packages/webui/src/serve.mjs +@@ -8,6 +8,7 @@ import { BusReadError, busStatus as coreBusStatus, subjectOk, sendOutcome } from + // Row 176, sage-1111: HTTP-only tester refusals; the row-164 transport pin stays untouched. + const busStatus = code => ['tester-off', 'tester-required'].includes(code) ? 403 : coreBusStatus(code); + import { ReadError, readStatus, fieldTable } from './reads.mjs'; ++import { THREAD_DEFINITIONS } from '../../business/src/vocabulary.mjs'; + import { chatHTTP } from './chat-http.mjs'; + import { processingHTTP } from './processing-http.mjs'; + +@@ -705,6 +706,25 @@ export async function startServer({ host = '127.0.0.1', port = 7330, board = DEF + if (req.method !== 'POST') return json(res, 405, { error: 'method not allowed' }); + return busSend(req, res, { send, preview: signer, log }); + } ++ // Row 219: labels only, from this listener's own human or tester bus, after auth above. ++ if (path === '/api/chat/instances') { ++ if (req.method !== 'GET') return json(res, 405, { error: 'method-not-allowed' }); ++ if (search) return json(res, 400, { error: 'invalid-request' }); ++ if (!bus?.instances) return json(res, 503, { error: 'not-configured' }); ++ try { ++ const value = await bus.instances(); ++ if (!value || !value.instances || typeof value.instances !== 'object' || Array.isArray(value.instances)) ++ throw new BusReadError('invalid-response'); ++ const rows = Object.entries(value.instances) ++ .filter(([, entry]) => THREAD_DEFINITIONS.includes(entry?.definition)) ++ .map(([instance, entry]) => ({ instance, name: entry.vars?.['persona.name'] || instance })); ++ if (rows.some(row => typeof row.name !== 'string')) throw new BusReadError('invalid-response'); ++ return json(res, 200, { rows, at: new Date().toISOString() }); ++ } catch (err) { ++ const code = err instanceof BusReadError ? err.code : 'invalid-response'; ++ return json(res, busStatus(code), { error: code }); ++ } ++ } + if (path === '/api/chat' || path.startsWith('/api/chat/')) { + return chatHTTP(req, res, { path, search, chat, named, send, preview: writer, log }); + } +diff --git a/packages/webui/tests/chat-browser.test.mjs b/packages/webui/tests/chat-browser.test.mjs +index f3eeeca96..cc3ec093d 100644 +--- a/packages/webui/tests/chat-browser.test.mjs ++++ b/packages/webui/tests/chat-browser.test.mjs +@@ -12,6 +12,7 @@ const HOSTILE = '\u001b[31m\u202eevil'; + const stub = `(() => { + const real = fetch.bind(window); + window.__writes=[];window.__reads=[];window.__answers=[];window.__pages=[]; ++ window.__instances=[{instance:'assistant',name:'assistant'}]; + window.__projects=[{id:'${P}', name:${JSON.stringify(HOSTILE)}, repo:null},{id:'\">',name:'Invalid record'}]; + window.__threads=[{id:'${A}',project:'${P}',name:'alpha',instance:'assistant'}, {id:'${B}',project:'${P}',name:'beta',instance:'assistant'}]; + window.fetch=async (url,init={})=>{ +@@ -33,6 +34,7 @@ const stub = `(() => { + window.__reads.push(path); + if(window.__holdRead && path.includes(window.__holdRead)){window.__heldSignal=init.signal;await new Promise(r=>window.__releaseRead=r);} + if(window.__readFailure)return new Response(JSON.stringify({error:window.__readFailure}),{status:window.__readFailure==='thread-not-owned'?403:503}); ++ if(path==='/api/chat/instances')return window.__instanceFailure ? new Response(JSON.stringify({error:'not-configured'}),{status:503}) : data({rows:window.__instances}); + if(path==='/api/chat/projects')return data({rows:window.__projects}); + if(path.startsWith('/api/chat/threads')&&!path.includes('/messages'))return data({rows:window.__threads}); + if(window.__pages.length)return data(window.__pages.shift()); +@@ -82,7 +84,7 @@ test('chat browser: list/create named projects and threads, hostile labels inert + await b.evaluate(`window.__answers.push({body:{id:'${A}',instance:'assistant'}}),true`); + await click('#chat-thread-form button[type=submit]'); + await wait('document.querySelector("#chat-send-form")'); +- assert.deepEqual(await b.evaluate('window.__writes[1].body'), { project: P, name: 'named thread' }); ++ assert.deepEqual(await b.evaluate('window.__writes[1].body'), { project: P, name: 'named thread', instance: 'assistant' }); + assert.equal(await b.evaluate('window.__writes[1].path'), '/api/chat/threads'); + assert.equal(await b.evaluate('window.__writes.length'), 2); + }); +@@ -264,3 +266,65 @@ test('chat browser: a late project creation receipt cannot navigate away from an + assert.equal(await b.evaluate('location.hash'), '#/chat/threads/' + A); + assert.equal(await b.evaluate('document.querySelector("#chat-body").value'), 'stay in alpha'); + }); ++ ++test('C2-09a picker: no silent choice, duplicate hostile labels stay inert, selection survives refresh and reaches POST', async t => { ++ const { b, wait, set, click, refresh } = await setup(t); ++ await b.evaluate(`window.__instances=[{instance:'bean',name:${JSON.stringify(HOSTILE)}},{instance:'chet',name:${JSON.stringify(HOSTILE)}}];location.hash='#/chat/projects/${P}';true`); ++ await wait('document.querySelectorAll("#chat-instance option").length===3'); ++ assert.equal(await b.evaluate('document.querySelector("#chat-instance").value'), ''); ++ assert.equal(await b.evaluate('document.querySelector("#chat-instance").required'), true); ++ assert.equal(await b.evaluate('document.querySelector("#chat-thread-form button").disabled'), true); ++ const labels = await b.evaluate('[...document.querySelectorAll("#chat-instance option")].map(o=>o.textContent)'); ++ assert.match(labels[1], /\(bean\)/); ++ assert.match(labels[2], /\(chet\)/); ++ assert.doesNotMatch(labels.join(''), /[\u202a-\u202e\u2066-\u2069]/u); ++ assert.equal(await b.evaluate('!!document.querySelector("#chat-thread-form img")'), false); ++ assert.equal(await b.evaluate('window.injected'), undefined); ++ await set('#chat-thread-name', 'Bean discussion'); ++ await click('#chat-thread-form button'); ++ assert.equal(await b.evaluate('window.__writes.length'), 0); ++ await set('#chat-instance', 'bean'); ++ await refresh(); ++ assert.equal(await b.evaluate('document.querySelector("#chat-instance").value'), 'bean'); ++ assert.equal(await b.evaluate('document.querySelector("#chat-thread-name").value'), 'Bean discussion'); ++ assert.equal(await b.evaluate('document.querySelector("#chat-thread-form button").disabled'), false); ++ await b.evaluate(`window.__answers.push({body:{id:'${A}',instance:'bean'}});true`); ++ await click('#chat-thread-form button'); ++ await wait('window.__writes.length===1'); ++ assert.deepEqual(await b.evaluate('window.__writes[0]'), { path: '/api/chat/threads', body: { project: P, name: 'Bean discussion', instance: 'bean' }, redirect: 'manual' }); ++}); ++ ++test('C2-09a picker: metadata failure, empty set and removed pick explain disabled state and recover without switching', async t => { ++ const { b, wait, set, refresh } = await setup(t); ++ await b.evaluate(`window.__instanceFailure=true;location.hash='#/chat/projects/${P}';true`); ++ await wait('document.querySelector("#chat-thread-form") && !document.querySelector(".chat-loading")'); ++ assert.match(await b.evaluate('document.querySelector("#chat-thread-form [role=status]").textContent'), /read the thread instances/); ++ assert.equal(await b.evaluate('document.querySelector("#chat-thread-form button").disabled'), true); ++ await b.evaluate('window.__instanceFailure=false;window.__instances=[];true'); ++ await refresh(); ++ assert.match(await b.evaluate('document.querySelector("#chat-thread-form [role=status]").textContent'), /No thread instances/); ++ assert.equal(await b.evaluate('document.querySelector("#chat-thread-form button").disabled'), true); ++ await b.evaluate("window.__instances=[{instance:'bean',name:'Bean'},{instance:'chet',name:'Chet'}];true"); ++ await refresh(); ++ await set('#chat-instance', 'bean'); ++ await b.evaluate("window.__instances=[{instance:'chet',name:'Chet'}];true"); ++ await refresh(); ++ assert.equal(await b.evaluate('document.querySelector("#chat-instance").value'), ''); ++ assert.match(await b.evaluate('document.querySelector("#chat-thread-form [role=status]").textContent'), /no longer available/); ++ assert.equal(await b.evaluate('document.querySelector("#chat-thread-form button").disabled'), true); ++ await refresh(); ++ assert.equal(await b.evaluate('document.querySelector("#chat-instance").value'), '', 'another refresh must not silently select the one remaining instance'); ++ await set('#chat-instance', 'chet'); ++ assert.equal(await b.evaluate('document.querySelector("#chat-thread-form button").disabled'), false); ++}); ++ ++test('C2-09a picker: a single instance is chosen explicitly but read-only mode cannot create a thread', async t => { ++ const { b, wait } = await setup(t, '?readonly'); ++ await b.evaluate(`location.hash='#/chat/projects/${P}';true`); ++ await wait('document.querySelectorAll("#chat-instance option").length===2'); ++ assert.equal(await b.evaluate('document.querySelector("#chat-instance").value'), 'assistant'); ++ assert.equal(await b.evaluate('document.querySelector("#chat-instance").disabled'), true); ++ assert.equal(await b.evaluate('document.querySelector("#chat-thread-form button").disabled'), true); ++ assert.match(await b.evaluate('document.querySelector(".chat-area").textContent'), /only reads/); ++ assert.equal(await b.evaluate('window.__writes.length'), 0); ++}); +diff --git a/packages/webui/tests/chat-instances.test.mjs b/packages/webui/tests/chat-instances.test.mjs +new file mode 100644 +index 000000000..893c38f80 +--- /dev/null ++++ b/packages/webui/tests/chat-instances.test.mjs +@@ -0,0 +1,87 @@ ++import test from 'node:test'; ++import assert from 'node:assert/strict'; ++import { request } from 'node:http'; ++import { mkdtempSync, rmSync } from 'node:fs'; ++import { join } from 'node:path'; ++import { tmpdir } from 'node:os'; ++import { startServer } from '../src/serve.mjs'; ++import { startTesterSocket, TESTER_ORIGIN } from '../src/tester-socket.mjs'; ++import { close } from './fixture.mjs'; ++ ++const path = '/api/chat/instances'; ++const preview = { host: 'chat.example', header: 'x-user', user: 'jason' }; ++const snapshot = { ++ revision: 'a'.repeat(64), plugins: { private: 'do-not-project' }, ++ instances: { ++ assistant: { definition: 'assistant', vars: { model: 'private-model' } }, ++ bean: { definition: 'companion', vars: { 'persona.name': 'Bean', 'persona.sha256': 'private-hash' } }, ++ chet: { definition: 'continuity', vars: { 'persona.name': 'Bean', file: '/private/path' } }, ++ pm: { definition: 'pm', vars: { 'persona.name': 'Hidden PM' } }, ++ }, ++}; ++function get(server, { route = path, method = 'GET', headers = {} } = {}) { ++ const address = server.address(), unix = typeof address === 'string'; ++ return new Promise((resolve, reject) => { ++ const req = request({ ...(unix ? { socketPath: address } : { host: '127.0.0.1', port: address.port }), ++ path: route, method, headers: { host: unix ? new URL(TESTER_ORIGIN).host : preview.host, ...headers } }, res => { ++ let text = ''; ++ res.on('data', chunk => text += chunk); ++ res.on('end', () => resolve({ status: res.statusCode, text, value: JSON.parse(text) })); ++ }); ++ req.on('error', reject); req.end(); ++ }); ++} ++test('C2-09a metadata uses the human listener bus, projects labels only and refuses unauthenticated reads', async t => { ++ let calls = 0; ++ const server = await startServer({ port: 0, preview, bus: { instances: async () => { calls++; return snapshot; } } }); ++ t.after(() => close(server)); ++ const refused = await get(server); ++ assert.equal(refused.status, 401); ++ assert.equal(calls, 0); ++ const answer = await get(server, { headers: { 'x-user': 'jason' } }); ++ assert.equal(answer.status, 200); ++ assert.deepEqual(answer.value.rows, [{ instance: 'assistant', name: 'assistant' }, { instance: 'bean', name: 'Bean' }, { instance: 'chet', name: 'Bean' }]); ++ for (const row of answer.value.rows) assert.deepEqual(Object.keys(row).sort(), ['instance', 'name']); ++ assert.doesNotMatch(answer.text, /private|Hidden PM/); ++ assert.equal(calls, 1); ++ for (const options of [{ route: path + '?extra=1' }, { method: 'POST' }]) { ++ const result = await get(server, { ...options, headers: { 'x-user': 'jason' } }); ++ assert.ok([400, 405].includes(result.status)); ++ } ++ assert.equal(calls, 1, 'invalid metadata requests never call a bus'); ++}); ++test('C2-09a metadata uses the private tester listener own bus and no human transport', async t => { ++ const root = mkdtempSync(join(tmpdir(), 'chat219-')); t.after(() => rmSync(root, { recursive: true, force: true })); ++ let reads = 0; ++ const server = await startTesterSocket({ dataRoot: root, board: 'http://127.0.0.1:9', ++ bus: { instances: async () => { reads++; return { instances: { quill: { definition: 'proofreader', vars: { 'persona.name': 'Quill' } } } }; } } }); ++ t.after(() => close(server)); ++ const answer = await get(server); ++ assert.equal(answer.status, 200); ++ assert.deepEqual(answer.value.rows, [{ instance: 'quill', name: 'Quill' }]); ++ assert.equal(reads, 1); ++}); ++test('C2-09a metadata refuses missing or malformed reads and never echoes untrusted fields', async t => { ++ for (const bus of [null, { instances: async () => ({ instances: [] }) }, { instances: async () => { throw new Error('private-path'); } }]) { ++ const server = await startServer({ port: 0, preview, bus }); t.after(() => close(server)); ++ const answer = await get(server, { headers: { 'x-user': 'jason' } }); ++ assert.ok(answer.status >= 400); ++ assert.doesNotMatch(answer.text, /private-path/); ++ } ++}); ++ ++test('C2-09a metadata stays behind the SSO session check and a GET never refreshes session activity', async t => { ++ let session = null, reads = 0; ++ const touches = []; ++ const sso = { publicHost: 'sso-chat.example', publicOrigin: 'https://sso-chat.example', formTargets: () => [], ++ session: (_req, { touch }) => { touches.push(touch); return session; } }; ++ const server = await startServer({ port: 0, sso, bus: { instances: async () => { reads++; return snapshot; } } }); ++ t.after(() => close(server)); ++ const headers = { host: sso.publicHost }; ++ assert.equal((await get(server, { headers })).status, 401); ++ assert.equal(reads, 0); ++ session = { name: 'jason' }; ++ assert.equal((await get(server, { headers })).status, 200); ++ assert.equal(reads, 1); ++ assert.deepEqual(touches, [false, false]); ++}); +diff --git a/packages/webui/tests/chat.test.mjs b/packages/webui/tests/chat.test.mjs +index 8c11611cf..b47ecf1a7 100644 +--- a/packages/webui/tests/chat.test.mjs ++++ b/packages/webui/tests/chat.test.mjs +@@ -335,3 +335,21 @@ test('the chat seam names the transport it is given: the human one and the teste + assert.throws(() => createChat({}), { name: 'TypeError', message: 'chat needs a bus transport function' }); + assert.equal(typeof createChat({ call: async () => ({}) }).projects, 'function'); + }); ++ ++test('C2-09a createThread forwards an explicit instance through the existing channel', async t => { ++ const c = await setup(t); ++ c.state.answer = { id: T, instance: 'bean' }; ++ const answer = await c.get('/api/chat/threads', { method: 'POST', value: { project: P, name: 'Bean work', instance: 'bean' } }); ++ assert.equal(answer.status, 200); ++ assert.deepEqual(c.calls, [['thread.create', { project: P, name: 'Bean work', instance: 'bean' }]]); ++ assert.equal(answer.data.instance, 'bean'); ++}); ++test('C2-09a malformed instance and extra thread fields refuse before the channel write', async t => { ++ const c = await setup(t); ++ for (const value of [null, 7, {}, [], '', 'bad name', 'bean/../../other']) { ++ const answer = await c.get('/api/chat/threads', { method: 'POST', value: { project: P, name: 'work', instance: value } }); ++ assert.equal(answer.status, 400, JSON.stringify(value)); ++ } ++ assert.equal((await c.get('/api/chat/threads', { method: 'POST', value: { project: P, name: 'work', instance: 'bean', extra: true } })).status, 400); ++ assert.deepEqual(c.calls, []); ++}); diff --git a/agents/rocko/work/queue-219/r2/candidate-manifest.sha256 b/agents/rocko/work/queue-219/r2/candidate-manifest.sha256 new file mode 100644 index 000000000..841f95bd5 --- /dev/null +++ b/agents/rocko/work/queue-219/r2/candidate-manifest.sha256 @@ -0,0 +1,14 @@ +142a210882535108159e045c61be288bc8703910a8ee0169137bc1eb80baff64 packages/bus/src/broker.mjs +b0c66c8999b567896923f6c876911dba936c6eca1ccc7aaeada51369fd613594 packages/bus/tests/creative.test.mjs +de4913f50801a11fd172e3f2c2b3cb41ab04e84d32a6650d1c8662b5e227ea3d packages/bus/tests/threads.test.mjs +8f7d6745fffb2a0898ac80c012084ac48964748da6439a8c56facc6529ffb19b packages/bus/tests/user-principal.test.mjs +b74275ce43249bafc97edc87a6a3aa0431c62fe1a28cd9fa265246e6d6da219b packages/cli/src/launcher.mjs +2c3f120dfdc0b52bf8ab38a88e86251be75d4b0d218e23df489dcaf815d6bc15 packages/cli/tests/launcher.test.mjs +7f308fdf601e839194f026beb3fea279eba1e356ce2756aa4a42fa4873b79b30 packages/cli/tests/thread-launch.test.mjs +cbfd54e6b375561d1e4679ee8d78a3285f970d9a7198cec9d22a06510a36afba packages/webui/src/chat-http.mjs +f49a49c6ec69783190d3f5eaf34316cb4d2c44d10d2ee9336fe1383207afefff packages/webui/src/chat.mjs +8e89df5fe8a9da35de41b5968afc6efcd92503e92ef28c6f557bb5371d10d1a4 packages/webui/src/public/chat.js +a597cc4902a873fe15650dacfce14dbc897db26310a7b6a24c293287add946c8 packages/webui/src/serve.mjs +d918e5b061ced9db28337a9b02c7042f9ddafaf09f32affce9f51190c0cba93f packages/webui/tests/chat-browser.test.mjs +2bad14535372488de326b82b791b79a61e1a4ab839458535132958ef85b746eb packages/webui/tests/chat-instances.test.mjs +7779d33d5b1dd3fe183722d0f91ff1f71e741e113cf2d5ee407f3d26428fc162 packages/webui/tests/chat.test.mjs diff --git a/agents/rocko/work/queue-219/r2/changed-paths.txt b/agents/rocko/work/queue-219/r2/changed-paths.txt new file mode 100644 index 000000000..689c1cf8e --- /dev/null +++ b/agents/rocko/work/queue-219/r2/changed-paths.txt @@ -0,0 +1,14 @@ +packages/bus/src/broker.mjs +packages/bus/tests/creative.test.mjs +packages/bus/tests/threads.test.mjs +packages/bus/tests/user-principal.test.mjs +packages/cli/src/launcher.mjs +packages/cli/tests/launcher.test.mjs +packages/cli/tests/thread-launch.test.mjs +packages/webui/src/chat-http.mjs +packages/webui/src/chat.mjs +packages/webui/src/public/chat.js +packages/webui/src/serve.mjs +packages/webui/tests/chat-browser.test.mjs +packages/webui/tests/chat-instances.test.mjs +packages/webui/tests/chat.test.mjs diff --git a/agents/rocko/work/queue-219/r2/mos1420-test.diff b/agents/rocko/work/queue-219/r2/mos1420-test.diff new file mode 100644 index 000000000..9c5d0fc6a --- /dev/null +++ b/agents/rocko/work/queue-219/r2/mos1420-test.diff @@ -0,0 +1,17 @@ +diff --git a/packages/cli/tests/thread-launch.test.mjs b/packages/cli/tests/thread-launch.test.mjs +index 84f8ed088..fa3adb866 100644 +--- a/packages/cli/tests/thread-launch.test.mjs ++++ b/packages/cli/tests/thread-launch.test.mjs +@@ -486,8 +486,10 @@ test('C2-09a: creative thread messages start distinct runs and keep each instanc + assert.deepEqual((await send(bean, 'bean', 'Bean input')).before, []); + assert.deepEqual((await send(chet, 'chet', 'Chet input')).before, []); + assert.deepEqual((await send(bean, 'bean', 'Bean again')).before, ['Bean input']); +- assert.equal(readFileSync(join(f.dataRoot, 'threads', 'acme', bean, 'session', 'history'), 'utf8'), 'Bean input\nBean again\n'); +- assert.equal(readFileSync(join(f.dataRoot, 'threads', 'acme', chet, 'session', 'history'), 'utf8'), 'Chet input\n'); ++ assert.equal(readFileSync(join(f.dataRoot, 'threads', 'acme', bean, 'sessions', host.runtime.threadsState('acme').find(x => x.thread === bean).session, 'session', 'history'), 'utf8'), 'Bean input\nBean again\n'); ++ assert.equal(readFileSync(join(f.dataRoot, 'threads', 'acme', chet, 'sessions', host.runtime.threadsState('acme').find(x => x.thread === chet).session, 'session', 'history'), 'utf8'), 'Chet input\n'); ++ assert.equal(existsSync(join(f.dataRoot, 'threads', 'acme', bean, 'session')), false); ++ assert.equal(existsSync(join(f.dataRoot, 'threads', 'acme', chet, 'session')), false); + const launches = launchLog(f).filter(row => row.event === 'launch'); + assert.ok(launches.some(row => row.instance === 'bean' && row.thread === bean)); + assert.ok(launches.some(row => row.instance === 'chet' && row.thread === chet)); diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/cwd-sweep-final.json b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/cwd-sweep-final.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/cwd-sweep-final.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/cwd-sweep.json b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/cwd-sweep.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/cwd-sweep.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/head.json b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/head.json new file mode 100644 index 000000000..07dd958dd --- /dev/null +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/head.json @@ -0,0 +1 @@ +{"head": "b97a3ff9757ec2ac39d9ab4cebb9769e471df932", "manifest": "a973912fe2fb9c64606cd99a385305b8f51498a096511b3a980bca6b0a5b98aa", "start": "2026-10-23T05:48:57.416807+00:00"} \ No newline at end of file diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/network-cleanup.log b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/network-cleanup.log new file mode 100644 index 000000000..5f00a1b72 --- /dev/null +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/network-cleanup.log @@ -0,0 +1,2 @@ +Error response from daemon: network rocko-r219-gate_default not found +exit status 1 diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/results.jsonl b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/results.jsonl new file mode 100644 index 000000000..550ae9ec5 --- /dev/null +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/results.jsonl @@ -0,0 +1,4 @@ +{"label": "single-bus-creative.test", "args": ["timeout", "-k", "10", "300", "node", "--test-reporter=tap", "--test", "packages/bus/tests/creative.test.mjs"], "start": "2026-10-23T05:48:57.418927+00:00", "end": "2026-10-23T05:48:57.834246+00:00", "seconds": 0.415, "exit": 0, "timeout": false, "counts": {"tests": 4, "pass": 4, "fail": 0, "cancelled": 0, "skipped": 0}, "skips": ["# skipped 0"], "log": "agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-creative.test.tap", "tmpdir": "/mnt/storage/scratch/tmp/rs156uowe"} +{"label": "single-bus-threads.test", "args": ["timeout", "-k", "10", "300", "node", "--test-reporter=tap", "--test", "packages/bus/tests/threads.test.mjs"], "start": "2026-10-23T05:48:57.835837+00:00", "end": "2026-10-23T05:48:59.152912+00:00", "seconds": 1.317, "exit": 0, "timeout": false, "counts": {"tests": 19, "pass": 19, "fail": 0, "cancelled": 0, "skipped": 0}, "skips": ["# skipped 0"], "log": "agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-threads.test.tap", "tmpdir": "/mnt/storage/scratch/tmp/rbqz65jtz"} +{"label": "single-bus-user-principal.test", "args": ["timeout", "-k", "10", "300", "node", "--test-reporter=tap", "--test", "packages/bus/tests/user-principal.test.mjs"], "start": "2026-10-23T05:48:59.154288+00:00", "end": "2026-10-23T05:48:59.719646+00:00", "seconds": 0.565, "exit": 0, "timeout": false, "counts": {"tests": 23, "pass": 23, "fail": 0, "cancelled": 0, "skipped": 0}, "skips": ["# skipped 0"], "log": "agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-user-principal.test.tap", "tmpdir": "/mnt/storage/scratch/tmp/rztf343rx"} +{"label": "single-cli-launcher.test", "args": ["timeout", "-k", "10", "300", "node", "--test-reporter=tap", "--test", "packages/cli/tests/launcher.test.mjs"], "start": "2026-10-23T05:48:59.721051+00:00", "end": "2026-10-23T05:49:26.296570+00:00", "seconds": 26.576, "exit": 1, "timeout": false, "counts": {"tests": 34, "pass": 33, "fail": 1, "cancelled": 0, "skipped": 0}, "skips": ["# skipped 0"], "log": "agents/rocko/work/queue-219/run-4a9c-mos1408/single-cli-launcher.test.tap", "tmpdir": "/mnt/storage/scratch/tmp/r8xucbjgl"} diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-bus-creative.test.tap b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-bus-creative.test.tap new file mode 100644 index 000000000..c397aec5c --- /dev/null +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-bus-creative.test.tap @@ -0,0 +1,36 @@ +TAP version 13 +# organization: {"event":"reload-needs-restart","business":"demo"} +# organization: {"event":"reload-needs-restart","business":"demo"} +# Subtest: M6: a creative raise refuses decision-not-allowed, cited or not, and no decision row appears; the assistant still raises +ok 1 - M6: a creative raise refuses decision-not-allowed, cited or not, and no decision row appears; the assistant still raises + --- + duration_ms: 37.235384 + type: 'test' + ... +# Subtest: a creative instance sends only to the user and hears only from the user +ok 2 - a creative instance sends only to the user and hears only from the user + --- + duration_ms: 44.266739 + type: 'test' + ... +# Subtest: M10 through the broker: the chief and the tester are refused on bean, the human's limits.set of its models key works +ok 3 - M10 through the broker: the chief and the tester are refused on bean, the human's limits.set of its models key works + --- + duration_ms: 83.079495 + type: 'test' + ... +# Subtest: row 256 T6 the human deletes a creative instance through the broker; the chief and the tester are refused at the verb +ok 4 - row 256 T6 the human deletes a creative instance through the broker; the chief and the tester are refused at the verb + --- + duration_ms: 63.576082 + type: 'test' + ... +1..4 +# tests 4 +# suites 0 +# pass 4 +# fail 0 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 356.82109 diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-bus-threads.test.tap b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-bus-threads.test.tap new file mode 100644 index 000000000..99b713932 --- /dev/null +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-bus-threads.test.tap @@ -0,0 +1,124 @@ +TAP version 13 +# Subtest: REQ-ROLE-2 REQ-THREAD-1: one holder per thread, through the broker and the trigger +ok 1 - REQ-ROLE-2 REQ-THREAD-1: one holder per thread, through the broker and the trigger + --- + duration_ms: 37.957781 + type: 'test' + ... +# Subtest: REQ-ROLE-2 REQ-THREAD-1: the 3e triggers refuse writes that skip the broker +ok 2 - REQ-ROLE-2 REQ-THREAD-1: the 3e triggers refuse writes that skip the broker + --- + duration_ms: 68.301373 + type: 'test' + ... +# Subtest: REQ-MSG-1: a message to or from the assistant names its thread, or is refused +ok 3 - REQ-MSG-1: a message to or from the assistant names its thread, or is refused + --- + duration_ms: 31.316512 + type: 'test' + ... +# Subtest: REQ-MSG-1: the chief of staff's reply comes back to the thread that sent the work +ok 4 - REQ-MSG-1: the chief of staff's reply comes back to the thread that sent the work + --- + duration_ms: 593.922455 + type: 'test' + ... +# Subtest: REQ-MSG-1: a reply can't carry one thread instance's thread to another thread instance +ok 5 - REQ-MSG-1: a reply can't carry one thread instance's thread to another thread instance + --- + duration_ms: 24.857971 + type: 'test' + ... +# Subtest: REQ-THREAD-1 REQ-USER-2: one thread's messages never reach another thread's holder +ok 6 - REQ-THREAD-1 REQ-USER-2: one thread's messages never reach another thread's holder + --- + duration_ms: 27.519976 + type: 'test' + ... +# Subtest: REQ-LAUNCH-1: a message to a thread with no holder waits, and the next holder gets it +ok 7 - REQ-LAUNCH-1: a message to a thread with no holder waits, and the next holder gets it + --- + duration_ms: 30.716822 + type: 'test' + ... +# Subtest: REQ-THREAD-2 REQ-LAUNCH-1: threadIdle releases only a thread with nothing waiting, and only for its holder +ok 8 - REQ-THREAD-2 REQ-LAUNCH-1: threadIdle releases only a thread with nothing waiting, and only for its holder + --- + duration_ms: 33.754906 + type: 'test' + ... +# Subtest: REQ-THREAD-2 REQ-LAUNCH-1: a message delivered to a run that ended unread doesn't hold up the next holder +ok 9 - REQ-THREAD-2 REQ-LAUNCH-1: a message delivered to a run that ended unread doesn't hold up the next holder + --- + duration_ms: 29.046481 + type: 'test' + ... +# Subtest: REQ-ROLE-2 REQ-THREAD-1 Q169: a role.revoke the human approved ends every thread run of the assistant +ok 10 - REQ-ROLE-2 REQ-THREAD-1 Q169: a role.revoke the human approved ends every thread run of the assistant + --- + duration_ms: 26.449042 + type: 'test' + ... +# Subtest: Q169 REQ-USER-2: assistant.stop ends the human's own assistant thread runs, and only the human's +ok 11 - Q169 REQ-USER-2: assistant.stop ends the human's own assistant thread runs, and only the human's + --- + duration_ms: 38.524966 + type: 'test' + ... +# Subtest: REQ-USER-2: the human verbs refuse another owner's thread +ok 12 - REQ-USER-2: the human verbs refuse another owner's thread + --- + duration_ms: 28.558392 + type: 'test' + ... +# Subtest: REQ-THREAD-1: thread.create refuses when the business has no thread instance or more than one +ok 13 - REQ-THREAD-1: thread.create refuses when the business has no thread instance or more than one + --- + duration_ms: 23.166467 + type: 'test' + ... +# Subtest: REQ-ORG-2: projects and threads are named once, and the repo names a business project +ok 14 - REQ-ORG-2: projects and threads are named once, and the repo names a business project + --- + duration_ms: 28.154835 + type: 'test' + ... +# Subtest: REQ-THREAD-2: thread.messages returns the thread's history in pages and writes nothing +ok 15 - REQ-THREAD-2: thread.messages returns the thread's history in pages and writes nothing + --- + duration_ms: 86.802213 + type: 'test' + ... +# Subtest: project.create refuses a repository name without its matching repo before any write +ok 16 - project.create refuses a repository name without its matching repo before any write + --- + duration_ms: 15.244016 + type: 'test' + ... +# Subtest: project.create keeps unrelated names with and without a repository +ok 17 - project.create keeps unrelated names with and without a repository + --- + duration_ms: 11.57841 + type: 'test' + ... +# Subtest: C2-09a: explicit creative instances bind independent threads, default sessions and holders +ok 18 - C2-09a: explicit creative instances bind independent threads, default sessions and holders + --- + duration_ms: 46.82113 + type: 'test' + ... +# Subtest: C2-09a: explicit missing, non-thread and malformed selectors create no thread or session +ok 19 - C2-09a: explicit missing, non-thread and malformed selectors create no thread or session + --- + duration_ms: 20.263528 + type: 'test' + ... +1..19 +# tests 19 +# suites 0 +# pass 19 +# fail 0 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 1283.575562 diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-bus-user-principal.test.tap b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-bus-user-principal.test.tap new file mode 100644 index 000000000..bffa47082 --- /dev/null +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-bus-user-principal.test.tap @@ -0,0 +1,148 @@ +TAP version 13 +# Subtest: R3: bindUser binds a granted user and refuses one with no live grant +ok 1 - R3: bindUser binds a granted user and refuses one with no live grant + --- + duration_ms: 15.216913 + type: 'test' + ... +# Subtest: R3: a user named like the business human, the tester or a principal word is refused, whatever the grants say +ok 2 - R3: a user named like the business human, the tester or a principal word is refused, whatever the grants say + --- + duration_ms: 10.127905 + type: 'test' + ... +# Subtest: R3: each collaborator verb is allowed to a granted user +ok 3 - R3: each collaborator verb is allowed to a granted user + --- + duration_ms: 18.491408 + type: 'test' + ... +# Subtest: R3: every verb off the collaborator list refuses a user access-refused before its own checks +ok 4 - R3: every verb off the collaborator list refuses a user access-refused before its own checks + --- + duration_ms: 95.840429 + type: 'test' + ... +# Subtest: R3: task, web and host verbs refuse a user access-refused +ok 5 - R3: task, web and host verbs refuse a user access-refused + --- + duration_ms: 22.333602 + type: 'test' + ... +# Subtest: R3 leak: project.list shows granted projects only +ok 6 - R3 leak: project.list shows granted projects only + --- + duration_ms: 12.494935 + type: 'test' + ... +# Subtest: R3 leak: thread.create needs the project and the thread instance granted, and an ungranted id reads as one that does not exist +ok 7 - R3 leak: thread.create needs the project and the thread instance granted, and an ungranted id reads as one that does not exist + --- + duration_ms: 15.195921 + type: 'test' + ... +# Subtest: R3 leak: thread.list shows the user its own threads that are still granted, never others +ok 8 - R3 leak: thread.list shows the user its own threads that are still granted, never others + --- + duration_ms: 33.037437 + type: 'test' + ... +# Subtest: R3 leak: names shows granted instances only +ok 9 - R3 leak: names shows granted instances only + --- + duration_ms: 14.891286 + type: 'test' + ... +# Subtest: R3: thread.send is from user: and writes no human.input +ok 10 - R3: thread.send is from user: and writes no human.input + --- + duration_ms: 13.366772 + type: 'test' + ... +# Subtest: R3: a reply-chain message.send into another user thread is refused +ok 11 - R3: a reply-chain message.send into another user thread is refused + --- + duration_ms: 15.523733 + type: 'test' + ... +# Subtest: R3: the run reply to a user message reaches that user thread.messages, with no waiting and no notice +ok 12 - R3: the run reply to a user message reaches that user thread.messages, with no waiting and no notice + --- + duration_ms: 18.163702 + type: 'test' + ... +# Subtest: R3: a reply to: user without a matching in_reply_to is refused user-reply-required +ok 13 - R3: a reply to: user without a matching in_reply_to is refused user-reply-required + --- + duration_ms: 26.445744 + type: 'test' + ... +# Subtest: R3: a non-thread run, the human and the tester cannot send to: user +ok 14 - R3: a non-thread run, the human and the tester cannot send to: user + --- + duration_ms: 17.29104 + type: 'test' + ... +# Subtest: R3: a user thread run cannot message the owner, while its uncited gated raise reaches only the owner inbox, B9 +ok 15 - R3: a user thread run cannot message the owner, while its uncited gated raise reaches only the owner inbox, B9 + --- + duration_ms: 19.258704 + type: 'test' + ... +# Subtest: R3: a removed or expired grant refuses the next request access-revoked, and the file is read per request +ok 16 - R3: a removed or expired grant refuses the next request access-revoked, and the file is read per request + --- + duration_ms: 18.231409 + type: 'test' + ... +# Subtest: R3: a user refusal is recorded as action.refused by user: +ok 17 - R3: a user refusal is recorded as action.refused by user: + --- + duration_ms: 13.797301 + type: 'test' + ... +# Subtest: R3: a business with a role keyed user refuses invalid-business +ok 18 - R3: a business with a role keyed user refuses invalid-business + --- + duration_ms: 9.468564 + type: 'test' + ... +# Subtest: R3: name.set refuses the name user +ok 19 - R3: name.set refuses the name user + --- + duration_ms: 9.752458 + type: 'test' + ... +# Subtest: R3 r4: while halted, a granted user's thread.send refuses processing-halted and thread.messages still answers +ok 20 - R3 r4: while halted, a granted user's thread.send refuses processing-halted and thread.messages still answers + --- + duration_ms: 13.617784 + type: 'test' + ... +# Subtest: R3 r4: the chief's hire refuses an instance named user with the code it gives human and tester +ok 21 - R3 r4: the chief's hire refuses an instance named user with the code it gives human and tester + --- + duration_ms: 14.726171 + type: 'test' + ... +# Subtest: R3 r4: a non-function access throws before the store is read +ok 22 - R3 r4: a non-function access throws before the store is read + --- + duration_ms: 0.105564 + type: 'test' + ... +# Subtest: C2-09a: naming an instance cannot bypass its collaborator grant or disclose an ungranted project +ok 23 - C2-09a: naming an instance cannot bypass its collaborator grant or disclose an ungranted project + --- + duration_ms: 15.883063 + type: 'test' + ... +1..23 +# tests 23 +# suites 0 +# pass 23 +# fail 0 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 523.149354 diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-cli-launcher.test.tap b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-cli-launcher.test.tap new file mode 100644 index 000000000..a43f32623 --- /dev/null +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408-red-before-mos1418/single-cli-launcher.test.tap @@ -0,0 +1,259 @@ +TAP version 13 +# Subtest: C2-04: launcher workspace clones code but never the library +ok 1 - C2-04: launcher workspace clones code but never the library + --- + duration_ms: 1444.885132 + type: 'test' + ... +# Subtest: bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest +ok 2 - bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest + --- + duration_ms: 1312.408111 + type: 'test' + ... +# usage: {"event":"usage-unknown","business":"acme","run":"r910aab09c0d6","instance":"chief","turn":"368e3045-8783-47d3-8ae1-e429b321d9b1","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r5bb5f0657d94","instance":"coder","turn":"3432d01f-ca94-4a5e-88d3-19588a40a81d","source":"@none","code":"baseline-unknown"} +# Subtest: the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code +ok 3 - the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code + --- + duration_ms: 745.868459 + type: 'test' + ... +# usage: {"event":"usage-unknown","business":"acme","run":"r0f3692812967","instance":"chief","turn":"3bd3e4d7-3af4-4a1c-99f1-a8f25b7ab1dd","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"re94397bc3680","instance":"pm","turn":"b2db478d-2905-49fd-a81a-0800643bed6a","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r0f3692812967","instance":"chief","turn":"ac2aa380-f3a0-40d4-960f-a543acb8c431","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"re94397bc3680","instance":"pm","turn":"e8b5124b-591b-4d5b-829c-e944bb3e24bb","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"re94397bc3680","instance":"pm","turn":"6a6e1fa4-5182-42dc-bbc2-26f136eedee3","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r0f3692812967","instance":"chief","turn":"54eb77b5-3880-4afb-9772-0b9d590eb328","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"re94397bc3680","instance":"pm","turn":"11fe8b92-1dae-416a-838b-ae305db74bc6","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r0f3692812967","instance":"chief","turn":"68266357-b477-433c-99ed-f1af37143d91","source":"@none","code":"baseline-unknown"} +# Subtest: a launched pm's request_staff reaches the chief of staff; no other session has the tool and the launch limits still hold +ok 4 - a launched pm's request_staff reaches the chief of staff; no other session has the tool and the launch limits still hold + --- + duration_ms: 1245.265675 + type: 'test' + ... +# Subtest: each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged +ok 5 - each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged + --- + duration_ms: 4072.119958 + type: 'test' + ... +# Subtest: a role the broker says holds a merge decision gets land_work, its prompt line, its landing and the git socket; without it, none +ok 6 - a role the broker says holds a merge decision gets land_work, its prompt line, its landing and the git socket; without it, none + --- + duration_ms: 621.888037 + type: 'test' + ... +# Subtest: REQ-LAUNCH-1 the tick starts a listed merge decision holder once, with land_work, the Landing line and its landing turn; an unlisted holder starts nothing +ok 7 - REQ-LAUNCH-1 the tick starts a listed merge decision holder once, with land_work, the Landing line and its landing turn; an unlisted holder starts nothing + --- + duration_ms: 417.133166 + type: 'test' + ... +# usage: {"event":"usage-unknown","business":"acme","run":"r8e91dd37dc4b","instance":"coder","turn":"a7b24e30-ac5e-47cf-a951-90cf0d948b4d","source":"@none","code":"baseline-unknown"} +# Subtest: a holder running since before the resolution is stopped once the broker releases it, land-relaunch, and the next tick starts it: landing turn first, then the message that waited +ok 8 - a holder running since before the resolution is stopped once the broker releases it, land-relaunch, and the next tick starts it: landing turn first, then the message that waited + --- + duration_ms: 730.904044 + type: 'test' + ... +# Subtest: a landing run that ended isn't started again for its decision, a spent decision starts nothing, and a new decision starts the holder +ok 9 - a landing run that ended isn't started again for its decision, a spent decision starts nothing, and a new decision starts the holder + --- + duration_ms: 698.470719 + type: 'test' + ... +# Subtest: after a restart, the new host's first tick starts a holder whose decision is still held +ok 10 - after a restart, the new host's first tick starts a holder whose decision is still held + --- + duration_ms: 920.432738 + type: 'test' + ... +# Subtest: a refused landing start is logged once per code with its decision, isn't a start, and the next tick with room starts the holder +ok 11 - a refused landing start is logged once per code with its decision, isn't a start, and the next tick with room starts the holder + --- + duration_ms: 641.309798 + type: 'test' + ... +# Subtest: a project clone that can't be made refuses checkout-failed, from --chief and from a session, with no run and no clone left +ok 12 - a project clone that can't be made refuses checkout-failed, from --chief and from a session, with no run and no clone left + --- + duration_ms: 267.647539 + type: 'test' + ... +# Subtest: a runner that stops at once ends its launch with the runner's reason +ok 13 - a runner that stops at once ends its launch with the runner's reason + --- + duration_ms: 332.423482 + type: 'test' + ... +# Subtest: a Claude Code launch with no session credential refuses before it starts +ok 14 - a Claude Code launch with no session credential refuses before it starts + --- + duration_ms: 181.931254 + type: 'test' + ... +# Subtest: a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables +ok 15 - a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables + --- + duration_ms: 315.27683 + type: 'test' + ... +# usage: {"event":"plugin-refused","kind":"harness","name":"down","code":"missing","message":"/mnt/storage/scratch/rocko-r219/packages/plugins/harness/down is missing","kept":[]} +# plugins: {"event":"plugin-refused","kind":"harness","name":"down","code":"missing","message":"/mnt/storage/scratch/rocko-r219/packages/plugins/harness/down is missing","kept":[]} +# Subtest: row 65 a failed primary preflight launches the fallback on a fixture harness module, recording the route and the reason +ok 16 - row 65 a failed primary preflight launches the fallback on a fixture harness module, recording the route and the reason + --- + duration_ms: 353.028786 + type: 'test' + ... +# Subtest: a runner killed from the host ends its launch as killed, not failed +ok 17 - a runner killed from the host ends its launch as killed, not failed + --- + duration_ms: 375.548364 + type: 'test' + ... +# Subtest: a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback +ok 18 - a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback + --- + duration_ms: 187.871873 + type: 'test' + ... +# Subtest: row 64 a role skill that isn't enabled refuses skill-not-enabled, and one that isn't a plain directory skill-refused, before anything starts +ok 19 - row 64 a role skill that isn't enabled refuses skill-not-enabled, and one that isn't a plain directory skill-refused, before anything starts + --- + duration_ms: 187.807189 + type: 'test' + ... +# Subtest: row 213 a creative instance's persona is read from /personas: off its digest the launch fails with the mismatch as its reason +not ok 20 - row 213 a creative instance's persona is read from /personas: off its digest the launch fails with the mismatch as its reason + --- + duration_ms: 255.698016 + type: 'test' + location: '/mnt/storage/scratch/rocko-r219/packages/cli/tests/launcher.test.mjs:766:1' + failureType: 'testCodeFailure' + error: |- + Expected values to be strictly deep-equal: + + actual - expected + + [ + + 'compactions', + + undefined, + + 2 + - 'refused', + - 'launch-failed', + - 1 + ] + + code: 'ERR_ASSERTION' + name: 'AssertionError' + expected: + 0: 'refused' + 1: 'launch-failed' + 2: 1 + actual: + 0: 'compactions' + 2: 2 + operator: 'deepStrictEqual' + stack: |- + TestContext. (file:///mnt/storage/scratch/rocko-r219/packages/cli/tests/launcher.test.mjs:789:10) + process.processTicksAndRejections (node:internal/process/task_queues:104:5) + async Test.run (node:internal/test_runner/test:1409:7) + async Test.processPendingSubtests (node:internal/test_runner/test:974:7) + ... +# Subtest: row 64 on Claude Code a role skill that isn't enabled refuses the same way, before anything starts +ok 21 - row 64 on Claude Code a role skill that isn't enabled refuses the same way, before anything starts + --- + duration_ms: 194.030218 + type: 'test' + ... +# usage: {"event":"usage-unknown","business":"acme","run":"r3715d40bd7fb","instance":"chief","turn":"4c85d22f-fdab-4ab7-ad8a-6b4c4d1fbf72","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r16ae936abadf","instance":"coder","turn":"2c6b06f1-8f36-4261-81b7-2689b9bb7ff4","source":"@none","code":"baseline-unknown"} +# Subtest: a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold +ok 22 - a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold + --- + duration_ms: 1456.773218 + type: 'test' + ... +# Subtest: a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again +ok 23 - a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again + --- + duration_ms: 1063.59923 + type: 'test' + ... +# Subtest: a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again +ok 24 - a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again + --- + duration_ms: 4016.888246 + type: 'test' + ... +# Subtest: bus start --no-launch leaves an earlier host's live session alone: no signal, no end, sessions.json as it was +ok 25 - bus start --no-launch leaves an earlier host's live session alone: no signal, no end, sessions.json as it was + --- + duration_ms: 237.364574 + type: 'test' + ... +# Subtest: bus start --no-launch refuses every launch: the PM launch and a session's request, each logged, nothing spawned +ok 26 - bus start --no-launch refuses every launch: the PM launch and a session's request, each logged, nothing spawned + --- + duration_ms: 197.775267 + type: 'test' + ... +# Subtest: bus start --no-launch starts no thread tick; the same host with launches runs it +ok 27 - bus start --no-launch starts no thread tick; the same host with launches runs it + --- + duration_ms: 1181.987654 + type: 'test' + ... +# Subtest: a malformed sessions.json refuses the host start with exit 3 and stays as it was +ok 28 - a malformed sessions.json refuses the host start with exit 3 and stays as it was + --- + duration_ms: 177.111363 + type: 'test' + ... +# Subtest: an over-long launch request is refused at once, not at the 10 s idle timeout +ok 29 - an over-long launch request is refused at once, not at the 10 s idle timeout + --- + duration_ms: 183.364128 + type: 'test' + ... +# Subtest: a launch client that never closes its side doesn't hold the host's close +ok 30 - a launch client that never closes its side doesn't hold the host's close + --- + duration_ms: 179.49662 + type: 'test' + ... +# Subtest: a runner that ignores SIGTERM is killed when the host closes +ok 31 - a runner that ignores SIGTERM is killed when the host closes + --- + duration_ms: 1352.083242 + type: 'test' + ... +# Subtest: kill switch refuses chief and hire before launch work and a clear permits a later request +ok 32 - kill switch refuses chief and hire before launch work and a clear permits a later request + --- + duration_ms: 319.505559 + type: 'test' + ... +# Subtest: kill switch stops an actual managed namespace and records the halt reason without rewriting its bundle +ok 33 - kill switch stops an actual managed namespace and records the halt reason without rewriting its bundle + --- + duration_ms: 334.076309 + type: 'test' + ... +# Subtest: a launch waiting across halt and resume cannot use its stale admission +ok 34 - a launch waiting across halt and resume cannot use its stale admission + --- + duration_ms: 201.932098 + type: 'test' + ... +1..34 +# tests 34 +# suites 0 +# pass 33 +# fail 1 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 26522.233904 diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408/head.json b/agents/rocko/work/queue-219/run-4a9c-mos1408/head.json index 07dd958dd..7b97b61f3 100644 --- a/agents/rocko/work/queue-219/run-4a9c-mos1408/head.json +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408/head.json @@ -1 +1 @@ -{"head": "b97a3ff9757ec2ac39d9ab4cebb9769e471df932", "manifest": "a973912fe2fb9c64606cd99a385305b8f51498a096511b3a980bca6b0a5b98aa", "start": "2026-10-23T05:48:57.416807+00:00"} \ No newline at end of file +{"head": "7cbd839d037f711de136108882b53597cf16d742", "manifest": "84a8bd9afc5420fc315586584aec91258a28bd728000dcd2cb61ac41b2fe711a", "start": "2026-10-23T06:09:43.704053+00:00"} \ No newline at end of file diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408/results.jsonl b/agents/rocko/work/queue-219/run-4a9c-mos1408/results.jsonl index 550ae9ec5..b5678d996 100644 --- a/agents/rocko/work/queue-219/run-4a9c-mos1408/results.jsonl +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408/results.jsonl @@ -1,4 +1,5 @@ -{"label": "single-bus-creative.test", "args": ["timeout", "-k", "10", "300", "node", "--test-reporter=tap", "--test", "packages/bus/tests/creative.test.mjs"], "start": "2026-10-23T05:48:57.418927+00:00", "end": "2026-10-23T05:48:57.834246+00:00", "seconds": 0.415, "exit": 0, "timeout": false, "counts": {"tests": 4, "pass": 4, "fail": 0, "cancelled": 0, "skipped": 0}, "skips": ["# skipped 0"], "log": "agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-creative.test.tap", "tmpdir": "/mnt/storage/scratch/tmp/rs156uowe"} -{"label": "single-bus-threads.test", "args": ["timeout", "-k", "10", "300", "node", "--test-reporter=tap", "--test", "packages/bus/tests/threads.test.mjs"], "start": "2026-10-23T05:48:57.835837+00:00", "end": "2026-10-23T05:48:59.152912+00:00", "seconds": 1.317, "exit": 0, "timeout": false, "counts": {"tests": 19, "pass": 19, "fail": 0, "cancelled": 0, "skipped": 0}, "skips": ["# skipped 0"], "log": "agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-threads.test.tap", "tmpdir": "/mnt/storage/scratch/tmp/rbqz65jtz"} -{"label": "single-bus-user-principal.test", "args": ["timeout", "-k", "10", "300", "node", "--test-reporter=tap", "--test", "packages/bus/tests/user-principal.test.mjs"], "start": "2026-10-23T05:48:59.154288+00:00", "end": "2026-10-23T05:48:59.719646+00:00", "seconds": 0.565, "exit": 0, "timeout": false, "counts": {"tests": 23, "pass": 23, "fail": 0, "cancelled": 0, "skipped": 0}, "skips": ["# skipped 0"], "log": "agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-user-principal.test.tap", "tmpdir": "/mnt/storage/scratch/tmp/rztf343rx"} -{"label": "single-cli-launcher.test", "args": ["timeout", "-k", "10", "300", "node", "--test-reporter=tap", "--test", "packages/cli/tests/launcher.test.mjs"], "start": "2026-10-23T05:48:59.721051+00:00", "end": "2026-10-23T05:49:26.296570+00:00", "seconds": 26.576, "exit": 1, "timeout": false, "counts": {"tests": 34, "pass": 33, "fail": 1, "cancelled": 0, "skipped": 0}, "skips": ["# skipped 0"], "log": "agents/rocko/work/queue-219/run-4a9c-mos1408/single-cli-launcher.test.tap", "tmpdir": "/mnt/storage/scratch/tmp/r8xucbjgl"} +{"label": "single-bus-creative.test", "args": ["timeout", "-k", "10", "300", "node", "--test-reporter=tap", "--test", "packages/bus/tests/creative.test.mjs"], "start": "2026-10-23T06:09:43.706182+00:00", "end": "2026-10-23T06:09:44.121877+00:00", "seconds": 0.416, "exit": 0, "timeout": false, "counts": {"tests": 4, "pass": 4, "fail": 0, "cancelled": 0, "skipped": 0}, "skips": ["# skipped 0"], "log": "agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-creative.test.tap", "tmpdir": "/mnt/storage/scratch/tmp/rmc2uerc6"} +{"label": "single-bus-threads.test", "args": ["timeout", "-k", "10", "300", "node", "--test-reporter=tap", "--test", "packages/bus/tests/threads.test.mjs"], "start": "2026-10-23T06:09:44.123566+00:00", "end": "2026-10-23T06:09:45.490657+00:00", "seconds": 1.367, "exit": 0, "timeout": false, "counts": {"tests": 19, "pass": 19, "fail": 0, "cancelled": 0, "skipped": 0}, "skips": ["# skipped 0"], "log": "agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-threads.test.tap", "tmpdir": "/mnt/storage/scratch/tmp/rismt3po4"} +{"label": "single-bus-user-principal.test", "args": ["timeout", "-k", "10", "300", "node", "--test-reporter=tap", "--test", "packages/bus/tests/user-principal.test.mjs"], "start": "2026-10-23T06:09:45.492192+00:00", "end": "2026-10-23T06:09:46.107814+00:00", "seconds": 0.616, "exit": 0, "timeout": false, "counts": {"tests": 23, "pass": 23, "fail": 0, "cancelled": 0, "skipped": 0}, "skips": ["# skipped 0"], "log": "agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-user-principal.test.tap", "tmpdir": "/mnt/storage/scratch/tmp/repq8xlqh"} +{"label": "single-cli-launcher.test", "args": ["timeout", "-k", "10", "300", "node", "--test-reporter=tap", "--test", "packages/cli/tests/launcher.test.mjs"], "start": "2026-10-23T06:09:46.109200+00:00", "end": "2026-10-23T06:10:12.635877+00:00", "seconds": 26.527, "exit": 0, "timeout": false, "counts": {"tests": 34, "pass": 34, "fail": 0, "cancelled": 0, "skipped": 0}, "skips": ["# skipped 0"], "log": "agents/rocko/work/queue-219/run-4a9c-mos1408/single-cli-launcher.test.tap", "tmpdir": "/mnt/storage/scratch/tmp/rkqztfjx8"} +{"label": "single-cli-thread-launch.test", "args": ["timeout", "-k", "10", "300", "node", "--test-reporter=tap", "--test", "packages/cli/tests/thread-launch.test.mjs"], "start": "2026-10-23T06:10:12.637449+00:00", "end": "2026-10-23T06:10:30.695792+00:00", "seconds": 18.058, "exit": 1, "timeout": false, "counts": {"tests": 14, "pass": 13, "fail": 1, "cancelled": 0, "skipped": 0}, "skips": ["# skipped 0"], "log": "agents/rocko/work/queue-219/run-4a9c-mos1408/single-cli-thread-launch.test.tap", "tmpdir": "/mnt/storage/scratch/tmp/rbyz9a6iu"} diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-creative.test.tap b/agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-creative.test.tap index c397aec5c..978d05307 100644 --- a/agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-creative.test.tap +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-creative.test.tap @@ -4,25 +4,25 @@ TAP version 13 # Subtest: M6: a creative raise refuses decision-not-allowed, cited or not, and no decision row appears; the assistant still raises ok 1 - M6: a creative raise refuses decision-not-allowed, cited or not, and no decision row appears; the assistant still raises --- - duration_ms: 37.235384 + duration_ms: 43.416235 type: 'test' ... # Subtest: a creative instance sends only to the user and hears only from the user ok 2 - a creative instance sends only to the user and hears only from the user --- - duration_ms: 44.266739 + duration_ms: 43.274527 type: 'test' ... # Subtest: M10 through the broker: the chief and the tester are refused on bean, the human's limits.set of its models key works ok 3 - M10 through the broker: the chief and the tester are refused on bean, the human's limits.set of its models key works --- - duration_ms: 83.079495 + duration_ms: 76.669863 type: 'test' ... # Subtest: row 256 T6 the human deletes a creative instance through the broker; the chief and the tester are refused at the verb ok 4 - row 256 T6 the human deletes a creative instance through the broker; the chief and the tester are refused at the verb --- - duration_ms: 63.576082 + duration_ms: 60.418493 type: 'test' ... 1..4 @@ -33,4 +33,4 @@ ok 4 - row 256 T6 the human deletes a creative instance through the broker; the # cancelled 0 # skipped 0 # todo 0 -# duration_ms 356.82109 +# duration_ms 374.784911 diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-threads.test.tap b/agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-threads.test.tap index 99b713932..b05384a32 100644 --- a/agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-threads.test.tap +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-threads.test.tap @@ -2,115 +2,115 @@ TAP version 13 # Subtest: REQ-ROLE-2 REQ-THREAD-1: one holder per thread, through the broker and the trigger ok 1 - REQ-ROLE-2 REQ-THREAD-1: one holder per thread, through the broker and the trigger --- - duration_ms: 37.957781 + duration_ms: 37.977187 type: 'test' ... # Subtest: REQ-ROLE-2 REQ-THREAD-1: the 3e triggers refuse writes that skip the broker ok 2 - REQ-ROLE-2 REQ-THREAD-1: the 3e triggers refuse writes that skip the broker --- - duration_ms: 68.301373 + duration_ms: 68.5376 type: 'test' ... # Subtest: REQ-MSG-1: a message to or from the assistant names its thread, or is refused ok 3 - REQ-MSG-1: a message to or from the assistant names its thread, or is refused --- - duration_ms: 31.316512 + duration_ms: 33.043064 type: 'test' ... # Subtest: REQ-MSG-1: the chief of staff's reply comes back to the thread that sent the work ok 4 - REQ-MSG-1: the chief of staff's reply comes back to the thread that sent the work --- - duration_ms: 593.922455 + duration_ms: 616.013932 type: 'test' ... # Subtest: REQ-MSG-1: a reply can't carry one thread instance's thread to another thread instance ok 5 - REQ-MSG-1: a reply can't carry one thread instance's thread to another thread instance --- - duration_ms: 24.857971 + duration_ms: 22.785202 type: 'test' ... # Subtest: REQ-THREAD-1 REQ-USER-2: one thread's messages never reach another thread's holder ok 6 - REQ-THREAD-1 REQ-USER-2: one thread's messages never reach another thread's holder --- - duration_ms: 27.519976 + duration_ms: 27.208648 type: 'test' ... # Subtest: REQ-LAUNCH-1: a message to a thread with no holder waits, and the next holder gets it ok 7 - REQ-LAUNCH-1: a message to a thread with no holder waits, and the next holder gets it --- - duration_ms: 30.716822 + duration_ms: 29.67741 type: 'test' ... # Subtest: REQ-THREAD-2 REQ-LAUNCH-1: threadIdle releases only a thread with nothing waiting, and only for its holder ok 8 - REQ-THREAD-2 REQ-LAUNCH-1: threadIdle releases only a thread with nothing waiting, and only for its holder --- - duration_ms: 33.754906 + duration_ms: 33.136315 type: 'test' ... # Subtest: REQ-THREAD-2 REQ-LAUNCH-1: a message delivered to a run that ended unread doesn't hold up the next holder ok 9 - REQ-THREAD-2 REQ-LAUNCH-1: a message delivered to a run that ended unread doesn't hold up the next holder --- - duration_ms: 29.046481 + duration_ms: 30.610794 type: 'test' ... # Subtest: REQ-ROLE-2 REQ-THREAD-1 Q169: a role.revoke the human approved ends every thread run of the assistant ok 10 - REQ-ROLE-2 REQ-THREAD-1 Q169: a role.revoke the human approved ends every thread run of the assistant --- - duration_ms: 26.449042 + duration_ms: 27.293744 type: 'test' ... # Subtest: Q169 REQ-USER-2: assistant.stop ends the human's own assistant thread runs, and only the human's ok 11 - Q169 REQ-USER-2: assistant.stop ends the human's own assistant thread runs, and only the human's --- - duration_ms: 38.524966 + duration_ms: 39.426851 type: 'test' ... # Subtest: REQ-USER-2: the human verbs refuse another owner's thread ok 12 - REQ-USER-2: the human verbs refuse another owner's thread --- - duration_ms: 28.558392 + duration_ms: 30.185753 type: 'test' ... # Subtest: REQ-THREAD-1: thread.create refuses when the business has no thread instance or more than one ok 13 - REQ-THREAD-1: thread.create refuses when the business has no thread instance or more than one --- - duration_ms: 23.166467 + duration_ms: 25.774336 type: 'test' ... # Subtest: REQ-ORG-2: projects and threads are named once, and the repo names a business project ok 14 - REQ-ORG-2: projects and threads are named once, and the repo names a business project --- - duration_ms: 28.154835 + duration_ms: 26.886111 type: 'test' ... # Subtest: REQ-THREAD-2: thread.messages returns the thread's history in pages and writes nothing ok 15 - REQ-THREAD-2: thread.messages returns the thread's history in pages and writes nothing --- - duration_ms: 86.802213 + duration_ms: 90.298865 type: 'test' ... # Subtest: project.create refuses a repository name without its matching repo before any write ok 16 - project.create refuses a repository name without its matching repo before any write --- - duration_ms: 15.244016 + duration_ms: 16.900927 type: 'test' ... # Subtest: project.create keeps unrelated names with and without a repository ok 17 - project.create keeps unrelated names with and without a repository --- - duration_ms: 11.57841 + duration_ms: 11.07789 type: 'test' ... # Subtest: C2-09a: explicit creative instances bind independent threads, default sessions and holders ok 18 - C2-09a: explicit creative instances bind independent threads, default sessions and holders --- - duration_ms: 46.82113 + duration_ms: 47.924595 type: 'test' ... # Subtest: C2-09a: explicit missing, non-thread and malformed selectors create no thread or session ok 19 - C2-09a: explicit missing, non-thread and malformed selectors create no thread or session --- - duration_ms: 20.263528 + duration_ms: 19.917458 type: 'test' ... 1..19 @@ -121,4 +121,4 @@ ok 19 - C2-09a: explicit missing, non-thread and malformed selectors create no t # cancelled 0 # skipped 0 # todo 0 -# duration_ms 1283.575562 +# duration_ms 1316.555344 diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-user-principal.test.tap b/agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-user-principal.test.tap index bffa47082..fa7fb3a32 100644 --- a/agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-user-principal.test.tap +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408/single-bus-user-principal.test.tap @@ -2,139 +2,139 @@ TAP version 13 # Subtest: R3: bindUser binds a granted user and refuses one with no live grant ok 1 - R3: bindUser binds a granted user and refuses one with no live grant --- - duration_ms: 15.216913 + duration_ms: 14.967712 type: 'test' ... # Subtest: R3: a user named like the business human, the tester or a principal word is refused, whatever the grants say ok 2 - R3: a user named like the business human, the tester or a principal word is refused, whatever the grants say --- - duration_ms: 10.127905 + duration_ms: 10.570753 type: 'test' ... # Subtest: R3: each collaborator verb is allowed to a granted user ok 3 - R3: each collaborator verb is allowed to a granted user --- - duration_ms: 18.491408 + duration_ms: 20.155376 type: 'test' ... # Subtest: R3: every verb off the collaborator list refuses a user access-refused before its own checks ok 4 - R3: every verb off the collaborator list refuses a user access-refused before its own checks --- - duration_ms: 95.840429 + duration_ms: 102.151204 type: 'test' ... # Subtest: R3: task, web and host verbs refuse a user access-refused ok 5 - R3: task, web and host verbs refuse a user access-refused --- - duration_ms: 22.333602 + duration_ms: 23.297818 type: 'test' ... # Subtest: R3 leak: project.list shows granted projects only ok 6 - R3 leak: project.list shows granted projects only --- - duration_ms: 12.494935 + duration_ms: 13.028484 type: 'test' ... # Subtest: R3 leak: thread.create needs the project and the thread instance granted, and an ungranted id reads as one that does not exist ok 7 - R3 leak: thread.create needs the project and the thread instance granted, and an ungranted id reads as one that does not exist --- - duration_ms: 15.195921 + duration_ms: 17.131417 type: 'test' ... # Subtest: R3 leak: thread.list shows the user its own threads that are still granted, never others ok 8 - R3 leak: thread.list shows the user its own threads that are still granted, never others --- - duration_ms: 33.037437 + duration_ms: 34.619156 type: 'test' ... # Subtest: R3 leak: names shows granted instances only ok 9 - R3 leak: names shows granted instances only --- - duration_ms: 14.891286 + duration_ms: 15.416042 type: 'test' ... # Subtest: R3: thread.send is from user: and writes no human.input ok 10 - R3: thread.send is from user: and writes no human.input --- - duration_ms: 13.366772 + duration_ms: 13.82133 type: 'test' ... # Subtest: R3: a reply-chain message.send into another user thread is refused ok 11 - R3: a reply-chain message.send into another user thread is refused --- - duration_ms: 15.523733 + duration_ms: 15.982484 type: 'test' ... # Subtest: R3: the run reply to a user message reaches that user thread.messages, with no waiting and no notice ok 12 - R3: the run reply to a user message reaches that user thread.messages, with no waiting and no notice --- - duration_ms: 18.163702 + duration_ms: 19.055939 type: 'test' ... # Subtest: R3: a reply to: user without a matching in_reply_to is refused user-reply-required ok 13 - R3: a reply to: user without a matching in_reply_to is refused user-reply-required --- - duration_ms: 26.445744 + duration_ms: 28.510227 type: 'test' ... # Subtest: R3: a non-thread run, the human and the tester cannot send to: user ok 14 - R3: a non-thread run, the human and the tester cannot send to: user --- - duration_ms: 17.29104 + duration_ms: 18.072201 type: 'test' ... # Subtest: R3: a user thread run cannot message the owner, while its uncited gated raise reaches only the owner inbox, B9 ok 15 - R3: a user thread run cannot message the owner, while its uncited gated raise reaches only the owner inbox, B9 --- - duration_ms: 19.258704 + duration_ms: 20.4668 type: 'test' ... # Subtest: R3: a removed or expired grant refuses the next request access-revoked, and the file is read per request ok 16 - R3: a removed or expired grant refuses the next request access-revoked, and the file is read per request --- - duration_ms: 18.231409 + duration_ms: 17.954184 type: 'test' ... # Subtest: R3: a user refusal is recorded as action.refused by user: ok 17 - R3: a user refusal is recorded as action.refused by user: --- - duration_ms: 13.797301 + duration_ms: 15.466589 type: 'test' ... # Subtest: R3: a business with a role keyed user refuses invalid-business ok 18 - R3: a business with a role keyed user refuses invalid-business --- - duration_ms: 9.468564 + duration_ms: 10.080838 type: 'test' ... # Subtest: R3: name.set refuses the name user ok 19 - R3: name.set refuses the name user --- - duration_ms: 9.752458 + duration_ms: 10.585336 type: 'test' ... # Subtest: R3 r4: while halted, a granted user's thread.send refuses processing-halted and thread.messages still answers ok 20 - R3 r4: while halted, a granted user's thread.send refuses processing-halted and thread.messages still answers --- - duration_ms: 13.617784 + duration_ms: 14.198383 type: 'test' ... # Subtest: R3 r4: the chief's hire refuses an instance named user with the code it gives human and tester ok 21 - R3 r4: the chief's hire refuses an instance named user with the code it gives human and tester --- - duration_ms: 14.726171 + duration_ms: 16.565998 type: 'test' ... # Subtest: R3 r4: a non-function access throws before the store is read ok 22 - R3 r4: a non-function access throws before the store is read --- - duration_ms: 0.105564 + duration_ms: 0.139037 type: 'test' ... # Subtest: C2-09a: naming an instance cannot bypass its collaborator grant or disclose an ungranted project ok 23 - C2-09a: naming an instance cannot bypass its collaborator grant or disclose an ungranted project --- - duration_ms: 15.883063 + duration_ms: 17.431688 type: 'test' ... 1..23 @@ -145,4 +145,4 @@ ok 23 - C2-09a: naming an instance cannot bypass its collaborator grant or discl # cancelled 0 # skipped 0 # todo 0 -# duration_ms 523.149354 +# duration_ms 550.515661 diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408/single-cli-launcher.test.tap b/agents/rocko/work/queue-219/run-4a9c-mos1408/single-cli-launcher.test.tap index a43f32623..2ca5baf8a 100644 --- a/agents/rocko/work/queue-219/run-4a9c-mos1408/single-cli-launcher.test.tap +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408/single-cli-launcher.test.tap @@ -2,102 +2,102 @@ TAP version 13 # Subtest: C2-04: launcher workspace clones code but never the library ok 1 - C2-04: launcher workspace clones code but never the library --- - duration_ms: 1444.885132 + duration_ms: 1386.548788 type: 'test' ... # Subtest: bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest ok 2 - bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest --- - duration_ms: 1312.408111 + duration_ms: 1321.365393 type: 'test' ... -# usage: {"event":"usage-unknown","business":"acme","run":"r910aab09c0d6","instance":"chief","turn":"368e3045-8783-47d3-8ae1-e429b321d9b1","source":"@none","code":"baseline-unknown"} -# usage: {"event":"usage-unknown","business":"acme","run":"r5bb5f0657d94","instance":"coder","turn":"3432d01f-ca94-4a5e-88d3-19588a40a81d","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"rfeb27e280740","instance":"chief","turn":"6c7140a2-fbf2-4eb5-aade-18915e5d6071","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r4d1379ff54ed","instance":"coder","turn":"0828e9f0-3389-4d81-b773-d33ac8ed9436","source":"@none","code":"baseline-unknown"} # Subtest: the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code ok 3 - the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code --- - duration_ms: 745.868459 + duration_ms: 729.440258 type: 'test' ... -# usage: {"event":"usage-unknown","business":"acme","run":"r0f3692812967","instance":"chief","turn":"3bd3e4d7-3af4-4a1c-99f1-a8f25b7ab1dd","source":"@none","code":"baseline-unknown"} -# usage: {"event":"usage-unknown","business":"acme","run":"re94397bc3680","instance":"pm","turn":"b2db478d-2905-49fd-a81a-0800643bed6a","source":"@none","code":"baseline-unknown"} -# usage: {"event":"usage-unknown","business":"acme","run":"r0f3692812967","instance":"chief","turn":"ac2aa380-f3a0-40d4-960f-a543acb8c431","source":"@none","code":"baseline-unknown"} -# usage: {"event":"usage-unknown","business":"acme","run":"re94397bc3680","instance":"pm","turn":"e8b5124b-591b-4d5b-829c-e944bb3e24bb","source":"@none","code":"baseline-unknown"} -# usage: {"event":"usage-unknown","business":"acme","run":"re94397bc3680","instance":"pm","turn":"6a6e1fa4-5182-42dc-bbc2-26f136eedee3","source":"@none","code":"baseline-unknown"} -# usage: {"event":"usage-unknown","business":"acme","run":"r0f3692812967","instance":"chief","turn":"54eb77b5-3880-4afb-9772-0b9d590eb328","source":"@none","code":"baseline-unknown"} -# usage: {"event":"usage-unknown","business":"acme","run":"re94397bc3680","instance":"pm","turn":"11fe8b92-1dae-416a-838b-ae305db74bc6","source":"@none","code":"baseline-unknown"} -# usage: {"event":"usage-unknown","business":"acme","run":"r0f3692812967","instance":"chief","turn":"68266357-b477-433c-99ed-f1af37143d91","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r6b4b3482e67f","instance":"chief","turn":"aa4f1c16-5525-464e-bf32-86dce3279b86","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r1c029998bdea","instance":"pm","turn":"9bcd06eb-6a90-41fb-80ae-56a32bc1c628","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r6b4b3482e67f","instance":"chief","turn":"8bbc58a8-f1e1-478c-957b-76e1d7d3a0d4","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r1c029998bdea","instance":"pm","turn":"e1f9e391-0762-4643-bdfb-737a3f75468c","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r1c029998bdea","instance":"pm","turn":"ee01381b-77c5-4737-9963-cc39bfcf3329","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r6b4b3482e67f","instance":"chief","turn":"48edb0d5-0ec4-4e59-9eae-4f767252eaf9","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r1c029998bdea","instance":"pm","turn":"837ddcc0-6e74-408d-a21f-aeefc287e380","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r6b4b3482e67f","instance":"chief","turn":"702b0c7b-18c4-4f01-b71b-29fdf5ae1d79","source":"@none","code":"baseline-unknown"} # Subtest: a launched pm's request_staff reaches the chief of staff; no other session has the tool and the launch limits still hold ok 4 - a launched pm's request_staff reaches the chief of staff; no other session has the tool and the launch limits still hold --- - duration_ms: 1245.265675 + duration_ms: 1260.785468 type: 'test' ... # Subtest: each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged ok 5 - each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged --- - duration_ms: 4072.119958 + duration_ms: 4079.777924 type: 'test' ... # Subtest: a role the broker says holds a merge decision gets land_work, its prompt line, its landing and the git socket; without it, none ok 6 - a role the broker says holds a merge decision gets land_work, its prompt line, its landing and the git socket; without it, none --- - duration_ms: 621.888037 + duration_ms: 602.445534 type: 'test' ... # Subtest: REQ-LAUNCH-1 the tick starts a listed merge decision holder once, with land_work, the Landing line and its landing turn; an unlisted holder starts nothing ok 7 - REQ-LAUNCH-1 the tick starts a listed merge decision holder once, with land_work, the Landing line and its landing turn; an unlisted holder starts nothing --- - duration_ms: 417.133166 + duration_ms: 401.42897 type: 'test' ... -# usage: {"event":"usage-unknown","business":"acme","run":"r8e91dd37dc4b","instance":"coder","turn":"a7b24e30-ac5e-47cf-a951-90cf0d948b4d","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r91e3ad863ad3","instance":"coder","turn":"b80d99c8-e809-4461-9c46-6b36ca47b775","source":"@none","code":"baseline-unknown"} # Subtest: a holder running since before the resolution is stopped once the broker releases it, land-relaunch, and the next tick starts it: landing turn first, then the message that waited ok 8 - a holder running since before the resolution is stopped once the broker releases it, land-relaunch, and the next tick starts it: landing turn first, then the message that waited --- - duration_ms: 730.904044 + duration_ms: 676.724798 type: 'test' ... # Subtest: a landing run that ended isn't started again for its decision, a spent decision starts nothing, and a new decision starts the holder ok 9 - a landing run that ended isn't started again for its decision, a spent decision starts nothing, and a new decision starts the holder --- - duration_ms: 698.470719 + duration_ms: 578.985078 type: 'test' ... # Subtest: after a restart, the new host's first tick starts a holder whose decision is still held ok 10 - after a restart, the new host's first tick starts a holder whose decision is still held --- - duration_ms: 920.432738 + duration_ms: 870.835521 type: 'test' ... # Subtest: a refused landing start is logged once per code with its decision, isn't a start, and the next tick with room starts the holder ok 11 - a refused landing start is logged once per code with its decision, isn't a start, and the next tick with room starts the holder --- - duration_ms: 641.309798 + duration_ms: 633.801547 type: 'test' ... # Subtest: a project clone that can't be made refuses checkout-failed, from --chief and from a session, with no run and no clone left ok 12 - a project clone that can't be made refuses checkout-failed, from --chief and from a session, with no run and no clone left --- - duration_ms: 267.647539 + duration_ms: 281.44642 type: 'test' ... # Subtest: a runner that stops at once ends its launch with the runner's reason ok 13 - a runner that stops at once ends its launch with the runner's reason --- - duration_ms: 332.423482 + duration_ms: 350.182818 type: 'test' ... # Subtest: a Claude Code launch with no session credential refuses before it starts ok 14 - a Claude Code launch with no session credential refuses before it starts --- - duration_ms: 181.931254 + duration_ms: 227.256782 type: 'test' ... # Subtest: a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables ok 15 - a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables --- - duration_ms: 315.27683 + duration_ms: 355.020473 type: 'test' ... # usage: {"event":"plugin-refused","kind":"harness","name":"down","code":"missing","message":"/mnt/storage/scratch/rocko-r219/packages/plugins/harness/down is missing","kept":[]} @@ -105,155 +105,126 @@ ok 15 - a pi launch needs no session credential: pi logs in through its own ENV_ # Subtest: row 65 a failed primary preflight launches the fallback on a fixture harness module, recording the route and the reason ok 16 - row 65 a failed primary preflight launches the fallback on a fixture harness module, recording the route and the reason --- - duration_ms: 353.028786 + duration_ms: 395.228029 type: 'test' ... # Subtest: a runner killed from the host ends its launch as killed, not failed ok 17 - a runner killed from the host ends its launch as killed, not failed --- - duration_ms: 375.548364 + duration_ms: 426.266157 type: 'test' ... # Subtest: a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback ok 18 - a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback --- - duration_ms: 187.871873 + duration_ms: 224.638023 type: 'test' ... # Subtest: row 64 a role skill that isn't enabled refuses skill-not-enabled, and one that isn't a plain directory skill-refused, before anything starts ok 19 - row 64 a role skill that isn't enabled refuses skill-not-enabled, and one that isn't a plain directory skill-refused, before anything starts --- - duration_ms: 187.807189 + duration_ms: 193.63499 type: 'test' ... # Subtest: row 213 a creative instance's persona is read from /personas: off its digest the launch fails with the mismatch as its reason -not ok 20 - row 213 a creative instance's persona is read from /personas: off its digest the launch fails with the mismatch as its reason +ok 20 - row 213 a creative instance's persona is read from /personas: off its digest the launch fails with the mismatch as its reason --- - duration_ms: 255.698016 + duration_ms: 276.964378 type: 'test' - location: '/mnt/storage/scratch/rocko-r219/packages/cli/tests/launcher.test.mjs:766:1' - failureType: 'testCodeFailure' - error: |- - Expected values to be strictly deep-equal: - + actual - expected - - [ - + 'compactions', - + undefined, - + 2 - - 'refused', - - 'launch-failed', - - 1 - ] - - code: 'ERR_ASSERTION' - name: 'AssertionError' - expected: - 0: 'refused' - 1: 'launch-failed' - 2: 1 - actual: - 0: 'compactions' - 2: 2 - operator: 'deepStrictEqual' - stack: |- - TestContext. (file:///mnt/storage/scratch/rocko-r219/packages/cli/tests/launcher.test.mjs:789:10) - process.processTicksAndRejections (node:internal/process/task_queues:104:5) - async Test.run (node:internal/test_runner/test:1409:7) - async Test.processPendingSubtests (node:internal/test_runner/test:974:7) ... # Subtest: row 64 on Claude Code a role skill that isn't enabled refuses the same way, before anything starts ok 21 - row 64 on Claude Code a role skill that isn't enabled refuses the same way, before anything starts --- - duration_ms: 194.030218 + duration_ms: 199.95263 type: 'test' ... -# usage: {"event":"usage-unknown","business":"acme","run":"r3715d40bd7fb","instance":"chief","turn":"4c85d22f-fdab-4ab7-ad8a-6b4c4d1fbf72","source":"@none","code":"baseline-unknown"} -# usage: {"event":"usage-unknown","business":"acme","run":"r16ae936abadf","instance":"coder","turn":"2c6b06f1-8f36-4261-81b7-2689b9bb7ff4","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r0292ea4215c0","instance":"chief","turn":"e2a42abe-43ec-4873-a349-c93850a0b2d1","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r0292ea4215c0","instance":"chief","turn":"b84fdcd2-08f1-4c7d-a1be-edf183ecf035","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"rb645b3bdbf17","instance":"coder","turn":"8c651e69-aef3-428e-9ecb-fa2c53e86f79","source":"@none","code":"baseline-unknown"} # Subtest: a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold ok 22 - a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold --- - duration_ms: 1456.773218 + duration_ms: 1419.59525 type: 'test' ... # Subtest: a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again ok 23 - a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again --- - duration_ms: 1063.59923 + duration_ms: 1046.224325 type: 'test' ... # Subtest: a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again ok 24 - a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again --- - duration_ms: 4016.888246 + duration_ms: 3988.178511 type: 'test' ... # Subtest: bus start --no-launch leaves an earlier host's live session alone: no signal, no end, sessions.json as it was ok 25 - bus start --no-launch leaves an earlier host's live session alone: no signal, no end, sessions.json as it was --- - duration_ms: 237.364574 + duration_ms: 242.392974 type: 'test' ... # Subtest: bus start --no-launch refuses every launch: the PM launch and a session's request, each logged, nothing spawned ok 26 - bus start --no-launch refuses every launch: the PM launch and a session's request, each logged, nothing spawned --- - duration_ms: 197.775267 + duration_ms: 218.198607 type: 'test' ... # Subtest: bus start --no-launch starts no thread tick; the same host with launches runs it ok 27 - bus start --no-launch starts no thread tick; the same host with launches runs it --- - duration_ms: 1181.987654 + duration_ms: 1199.857353 type: 'test' ... # Subtest: a malformed sessions.json refuses the host start with exit 3 and stays as it was ok 28 - a malformed sessions.json refuses the host start with exit 3 and stays as it was --- - duration_ms: 177.111363 + duration_ms: 181.02391 type: 'test' ... # Subtest: an over-long launch request is refused at once, not at the 10 s idle timeout ok 29 - an over-long launch request is refused at once, not at the 10 s idle timeout --- - duration_ms: 183.364128 + duration_ms: 181.674266 type: 'test' ... # Subtest: a launch client that never closes its side doesn't hold the host's close ok 30 - a launch client that never closes its side doesn't hold the host's close --- - duration_ms: 179.49662 + duration_ms: 183.632625 type: 'test' ... # Subtest: a runner that ignores SIGTERM is killed when the host closes ok 31 - a runner that ignores SIGTERM is killed when the host closes --- - duration_ms: 1352.083242 + duration_ms: 1365.88402 type: 'test' ... # Subtest: kill switch refuses chief and hire before launch work and a clear permits a later request ok 32 - kill switch refuses chief and hire before launch work and a clear permits a later request --- - duration_ms: 319.505559 + duration_ms: 334.261623 type: 'test' ... # Subtest: kill switch stops an actual managed namespace and records the halt reason without rewriting its bundle ok 33 - kill switch stops an actual managed namespace and records the halt reason without rewriting its bundle --- - duration_ms: 334.076309 + duration_ms: 337.104395 type: 'test' ... # Subtest: a launch waiting across halt and resume cannot use its stale admission ok 34 - a launch waiting across halt and resume cannot use its stale admission --- - duration_ms: 201.932098 + duration_ms: 194.683714 type: 'test' ... 1..34 # tests 34 # suites 0 -# pass 33 -# fail 1 +# pass 34 +# fail 0 # cancelled 0 # skipped 0 # todo 0 -# duration_ms 26522.233904 +# duration_ms 26492.10694 diff --git a/agents/rocko/work/queue-219/run-4a9c-mos1408/single-cli-thread-launch.test.tap b/agents/rocko/work/queue-219/run-4a9c-mos1408/single-cli-thread-launch.test.tap new file mode 100644 index 000000000..ca9bd70c9 --- /dev/null +++ b/agents/rocko/work/queue-219/run-4a9c-mos1408/single-cli-thread-launch.test.tap @@ -0,0 +1,122 @@ +TAP version 13 +# usage: {"event":"usage-unknown","business":"acme","run":"r2204f4cc976c","instance":"assistant","turn":"fdd36b75-12af-4495-ba60-8a2fdd991676","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r42619c482fdd","instance":"assistant","turn":"4a493440-df70-4de2-a9be-f31bb9efbe67","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r65dae8bd493b","instance":"assistant","turn":"2a27c708-e430-4bcc-82b5-67dc51173d46","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r4f264ceb0a50","instance":"assistant","turn":"dedfd89c-7a7a-4bb8-ba67-ca0e6a417ee8","source":"@none","code":"baseline-unknown"} +# Subtest: REQ-THREAD-2 REQ-LAUNCH-1 REQ-MSG-1 a thread message starts the thread's session, which stops when idle; its history survives the stop and a restart and no other thread sees it +ok 1 - REQ-THREAD-2 REQ-LAUNCH-1 REQ-MSG-1 a thread message starts the thread's session, which stops when idle; its history survives the stop and a restart and no other thread sees it + --- + duration_ms: 5837.256276 + type: 'test' + ... +# usage: {"event":"usage-unknown","business":"acme","run":"red085121d9a7","instance":"assistant","turn":"536c8b62-f126-4b17-a1f2-756e2aede65e","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"ree31908db53d","instance":"assistant","turn":"f23f7966-9c87-4d7b-9d9a-93211c1431c0","source":"@none","code":"baseline-unknown"} +# Subtest: REQ-THREAD-2 REQ-LAUNCH-1 a running thread counts against its model's limit and an idle-stopped one doesn't +ok 2 - REQ-THREAD-2 REQ-LAUNCH-1 a running thread counts against its model's limit and an idle-stopped one doesn't + --- + duration_ms: 1713.759995 + type: 'test' + ... +# usage: {"event":"usage-unknown","business":"acme","run":"r5c82fe368237","instance":"assistant","turn":"081a280e-73b4-48fc-89a9-688e73704186","source":"@none","code":"baseline-unknown"} +# Subtest: REQ-THREAD-2 REQ-LAUNCH-1 a turn longer than the idle time isn't stopped, and the thread idles out after it +ok 3 - REQ-THREAD-2 REQ-LAUNCH-1 a turn longer than the idle time isn't stopped, and the thread idles out after it + --- + duration_ms: 4442.41406 + type: 'test' + ... +# usage: {"event":"usage-unknown","business":"acme","run":"r008070c44356","instance":"assistant","turn":"3175be8d-24d9-4d46-b9fe-e08c8edd0a5e","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r008070c44356","instance":"assistant","turn":"961030fd-2dd3-4deb-a9be-f10af56ba7f7","source":"@none","code":"baseline-unknown"} +# Subtest: REQ-THREAD-2 REQ-LAUNCH-1 a message sent after the idle snapshot keeps the session, which answers it +ok 4 - REQ-THREAD-2 REQ-LAUNCH-1 a message sent after the idle snapshot keeps the session, which answers it + --- + duration_ms: 2897.279317 + type: 'test' + ... +# Subtest: REQ-ROLE-2 REQ-LAUNCH-1 a thread held by a run the launcher didn't start gets no second session +ok 5 - REQ-ROLE-2 REQ-LAUNCH-1 a thread held by a run the launcher didn't start gets no second session + --- + duration_ms: 79.744891 + type: 'test' + ... +# usage: {"event":"usage-unknown","business":"acme","run":"rf65e96bfb0f4","instance":"assistant","turn":"61a6ab74-b6bd-4274-a83e-0f8fb4f61a68","source":"@none","code":"baseline-unknown"} +# Subtest: REQ-LAUNCH-1 a thread run's session file has no landing, even when landHolders names its instance +ok 6 - REQ-LAUNCH-1 a thread run's session file has no landing, even when landHolders names its instance + --- + duration_ms: 374.600592 + type: 'test' + ... +# Subtest: REQ-LAUNCH-1 a run that ends without taking its thread's message isn't started again for that message +ok 7 - REQ-LAUNCH-1 a run that ends without taking its thread's message isn't started again for that message + --- + duration_ms: 232.164242 + type: 'test' + ... +# Subtest: REQ-THREAD-2 REQ-LAUNCH-1 a thread id that isn't a safe segment, or a thread of a non-thread instance, launches nothing +ok 8 - REQ-THREAD-2 REQ-LAUNCH-1 a thread id that isn't a safe segment, or a thread of a non-thread instance, launches nothing + --- + duration_ms: 226.367358 + type: 'test' + ... +# Subtest: REQ-THREAD-1 REQ-LAUNCH-1 the chief can't launch the assistant outside a thread +ok 9 - REQ-THREAD-1 REQ-LAUNCH-1 the chief can't launch the assistant outside a thread + --- + duration_ms: 78.315815 + type: 'test' + ... +# Subtest: C2-09a: fromSession refuses a creative instance outside a thread without a sandbox +ok 10 - C2-09a: fromSession refuses a creative instance outside a thread without a sandbox + --- + duration_ms: 74.784926 + type: 'test' + ... +# limits: {"event":"limit-reached","business":"acme","check":"token-limit","export":"mayLaunch","subject":{"instance":"assistant","provider":"zai","model":"claude-sonnet-5-5","account":null},"path":["tokens","instances","assistant"],"limit":0,"count":0,"unknownTurns":0} +# limits: {"event":"limit-reached","business":"acme","check":"token-limit","export":"mayLaunch","subject":{"instance":"assistant","provider":"zai","model":"claude-sonnet-5-5","account":null},"path":["tokens","instances","assistant"],"limit":0,"count":0,"unknownTurns":0} +# Subtest: REQ-LIMIT-3 REQ-LIMIT-4 a thread start whose instance is at its token limit is refused, logged once with the limit and the count +ok 11 - REQ-LIMIT-3 REQ-LIMIT-4 a thread start whose instance is at its token limit is refused, logged once with the limit and the count + --- + duration_ms: 192.227024 + type: 'test' + ... +# usage: {"event":"usage-unknown","business":"acme","run":"r3ffa99981f81","instance":"assistant","turn":"c194605d-1b92-488c-9afe-2599f205e80e","source":"@none","code":"baseline-unknown"} +# limits: {"event":"limit-reached","business":"acme","check":"token-limit","export":"mustStop","subject":{"instance":"assistant","provider":"zai","model":"claude-sonnet-5-5","account":null},"path":["tokens","instances","assistant"],"limit":0,"count":0,"unknownTurns":1} +# Subtest: REQ-LIMIT-4 a thread run at its token limit stops at the end of its turn: the reply lands, and the end record names the limit +ok 12 - REQ-LIMIT-4 a thread run at its token limit stops at the end of its turn: the reply lands, and the end record names the limit + --- + duration_ms: 346.819478 + type: 'test' + ... +# usage: {"event":"usage-unknown","business":"acme","run":"r013acfa3de9d","instance":"bean","turn":"26896136-02b5-4948-8c84-7e0be580c485","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r13746c56cf7b","instance":"chet","turn":"2a7d4bed-79e7-4b74-b602-c07fe90be87f","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"r013acfa3de9d","instance":"bean","turn":"32222fad-092d-4ac9-b5de-d40de519bf82","source":"@none","code":"baseline-unknown"} +# Subtest: C2-09a: creative thread messages start distinct runs and keep each instance history separate +not ok 13 - C2-09a: creative thread messages start distinct runs and keep each instance history separate + --- + duration_ms: 746.425172 + type: 'test' + location: '/mnt/storage/scratch/rocko-r219/packages/cli/tests/thread-launch.test.mjs:463:1' + failureType: 'testCodeFailure' + error: "ENOENT: no such file or directory, open '/mnt/storage/scratch/tmp/rbyz9a6iu/mosaic-thread-Cgx1tR/data/threads/acme/0a27cae9-cdb8-436d-8cc8-a59b316577e9/session/history'" + code: 'ENOENT' + stack: |- + readFileSync (node:fs:621:20) + TestContext. (file:///mnt/storage/scratch/rocko-r219/packages/cli/tests/thread-launch.test.mjs:489:16) + async Test.run (node:internal/test_runner/test:1409:7) + async Test.processPendingSubtests (node:internal/test_runner/test:974:7) + ... +# usage: {"event":"usage-unknown","business":"acme","run":"rb9998f7e5721","instance":"bean","turn":"6c83f086-940d-43ab-8219-0ee1b9dd4c7d","source":"@none","code":"baseline-unknown"} +# usage: {"event":"usage-unknown","business":"acme","run":"rdf0bd4d3e49a","instance":"bean","turn":"663ed95c-c09b-4053-aeb7-1696b6acf272","source":"@none","code":"baseline-unknown"} +# Subtest: C2-09a: terminating one creative thread does not signal an unselected peer of the same instance +ok 14 - C2-09a: terminating one creative thread does not signal an unselected peer of the same instance + --- + duration_ms: 643.667218 + type: 'test' + ... +1..14 +# tests 14 +# suites 0 +# pass 13 +# fail 1 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 18010.958178 diff --git a/packages/cli/tests/thread-launch.test.mjs b/packages/cli/tests/thread-launch.test.mjs index 84f8ed088..fa3adb866 100644 --- a/packages/cli/tests/thread-launch.test.mjs +++ b/packages/cli/tests/thread-launch.test.mjs @@ -486,8 +486,10 @@ test('C2-09a: creative thread messages start distinct runs and keep each instanc assert.deepEqual((await send(bean, 'bean', 'Bean input')).before, []); assert.deepEqual((await send(chet, 'chet', 'Chet input')).before, []); assert.deepEqual((await send(bean, 'bean', 'Bean again')).before, ['Bean input']); - assert.equal(readFileSync(join(f.dataRoot, 'threads', 'acme', bean, 'session', 'history'), 'utf8'), 'Bean input\nBean again\n'); - assert.equal(readFileSync(join(f.dataRoot, 'threads', 'acme', chet, 'session', 'history'), 'utf8'), 'Chet input\n'); + assert.equal(readFileSync(join(f.dataRoot, 'threads', 'acme', bean, 'sessions', host.runtime.threadsState('acme').find(x => x.thread === bean).session, 'session', 'history'), 'utf8'), 'Bean input\nBean again\n'); + assert.equal(readFileSync(join(f.dataRoot, 'threads', 'acme', chet, 'sessions', host.runtime.threadsState('acme').find(x => x.thread === chet).session, 'session', 'history'), 'utf8'), 'Chet input\n'); + assert.equal(existsSync(join(f.dataRoot, 'threads', 'acme', bean, 'session')), false); + assert.equal(existsSync(join(f.dataRoot, 'threads', 'acme', chet, 'session')), false); const launches = launchLog(f).filter(row => row.event === 'launch'); assert.ok(launches.some(row => row.instance === 'bean' && row.thread === bean)); assert.ok(launches.some(row => row.instance === 'chet' && row.thread === chet));