From f5256e67790a5e8a88d92f314a306ef0cd63c183 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Fri, 23 Oct 2026 04:27:51 -0500 Subject: [PATCH] morgana: row 295 r2 narrow alice grant (Dewey F1, sage-3333): aliceAccess(ref) names only the test's project, set after project.create; asserts and bindLaunch lines byte-identical; BUILD.md preservation record updated --- agents/morgana/work/queue-295/BUILD.md | 28 +++++++++++++++------- packages/bus/tests/message-filter.test.mjs | 16 +++++++++---- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/agents/morgana/work/queue-295/BUILD.md b/agents/morgana/work/queue-295/BUILD.md index f66cab244..b9e949d39 100644 --- a/agents/morgana/work/queue-295/BUILD.md +++ b/agents/morgana/work/queue-295/BUILD.md @@ -168,15 +168,25 @@ the kill, a run that polls would exit 22 on not-holder and hide the mutant. ### Assertion-preservation record (mos-1436 conditions 7-9) -- message-filter.test.mjs gains `aliceAccess` with one grant: user alice, business demo, instances - `['assistant']` (the thread's instance), collaborator level. projects is `['*']` because both - tests make their project after the broker. `setup(t, { access } = {})` passes it to `new Broker`. - Only :117 passes it; every other `setup(t)` gives `access: undefined`, which the constructor's - default turns into null, as before. -- The governance pin passes `access: aliceAccess` to `fixture()`. No other line of either test - changes. The bindLaunch lines (:119, :169 before; :126, :176 after) and every assertion are - byte-identical. The diff's removed lines are only `function setup(t) {`, the two setup calls and - the two helper lines below. +- message-filter.test.mjs gains `aliceAccess(ref)`. It returns the access function, whose single + grant is user alice, business demo, projects `[ref.project]`, instances `['assistant']` (the + thread's instance), collaborator level. That's the narrowest grant (Dewey's F1, sage-3333; + f652dfa5b had projects `['*']`). The broker has no accessStamp here, so #accessDoc calls the + function again on each read. +- `setup(t, { access } = {})` passes `access` to `new Broker`. Only the trail test (:117 at 9f29e1b2f, :126 now) passes it; every other + `setup(t)` gives `access: undefined`, which the constructor's default turns into null, as before. + setup() already returned `project`. +- The trail test and the governance pin (:176) each add `const ref = {};` before setup or fixture, and set + `ref.project = project;` once the project exists. The trail test also destructures `project` from setup(). + The governance pin passes `access: aliceAccess(ref)` to `fixture()`. +- Nothing reads access before `ref.project` is set. The broker reads it on a user's request + (request()), in threadsState, in #covered, and in bindLaunch/role.claim on a thread. + setup() binds only the human, a reader and the tester and makes the project. fixture() binds + coder and reviewer runs with no thread. Neither binds or claims as alice, and neither calls + threadsState. +- No other line of either test changes. The bindLaunch lines (:119, :169 at 9f29e1b2f; :130, :182 now) + and every assertion are byte-identical. The removed lines are only `function setup(t) {`, the + two setup/fixture calls and the two helper lines below. - spec-fixtures.mjs (11 importers) gains one optional parameter with a null default, which goes to `new Broker` unchanged. No other caller changes. diff --git a/packages/bus/tests/message-filter.test.mjs b/packages/bus/tests/message-filter.test.mjs index edb425cf6..94b65d2cd 100644 --- a/packages/bus/tests/message-filter.test.mjs +++ b/packages/bus/tests/message-filter.test.mjs @@ -16,10 +16,12 @@ const AT = '2026-10-21T00:00:00.000Z'; const within = (grants) => ({ authority: { withinRole: grants, crossRole: [] } }); const options = [{ key: 'yes', text: 'Yes' }, { key: 'no', text: 'No' }]; // FED R4 (row 295, mos-1436): a new run on a user's thread binds only while a grant covers it, so the -// two tests that launch on alice's thread pass this access document; no other test's broker has one. -const aliceAccess = () => ({ +// two tests that launch on alice's thread pass this access function; no other test's broker has one. +// The grant names only the test's project, which the test sets in `ref` once it exists; the broker +// has no stamp here, so it calls the function again on each read. +const aliceAccess = (ref) => () => ({ accessVersion: 1, revision: 1, users: [], - grants: [{ id: 'g-alice', user: 'alice', business: 'demo', projects: ['*'], instances: ['assistant'], level: 'collaborator' }], + grants: [{ id: 'g-alice', user: 'alice', business: 'demo', projects: [ref.project], instances: ['assistant'], level: 'collaborator' }], }); // Row 264a (#1769): a thread made by hand gets the default session the 3k-3l migration gives a // legacy thread, with the thread's id. @@ -122,7 +124,9 @@ test('replies on a hidden message id answers exactly as on an unknown id, for th }); test('trail on a decision keeps the decision and drops the user thread messages, their deliveries and their events', (t) => { - const { b, store, call, U } = setup(t, { access: aliceAccess }); + const ref = {}; + const { b, store, call, project, U } = setup(t, { access: aliceAccess(ref) }); + ref.project = project; const run = b.bindLaunch({ business: 'demo', role: 'assistant', run: 'assistant-run', harness: 'pi', thread: U }); b.request(run, { verb: 'role.claim' }); const d = b.request(run, { @@ -170,8 +174,10 @@ test('messages in the business human and tester threads are not hidden (row 177 // Row 193's user sentence, one assertion per item: "The business owner sees your thread's agent's // decisions, inbox entries, claims, tasks and project files, but not your messages." test('governance pin: the owner sees decisions, inbox entries, claims, tasks and project files, but not the user messages', async (t) => { - const f = await fixture(t, { roles: { assistant: { definition: 'assistant', ...within(['message.send', 'record.write']) } }, access: aliceAccess }); + const ref = {}; + const f = await fixture(t, { roles: { assistant: { definition: 'assistant', ...within(['message.send', 'record.write']) } }, access: aliceAccess(ref) }); const project = (await f.call(f.human, 'project.create', { name: 'alice work' })).id; + ref.project = project; const U = threadOf(f.store, project, 'user:alice', 'alice'); const run = f.broker.bindLaunch({ business: 'demo', role: 'assistant', run: 'assistant-run', harness: 'pi', thread: U }); await f.call(run, 'role.claim');