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

This commit is contained in:
jason.woltje committed 2026-10-23 04:27:51 -05:00
1 parent f652dfa5b8
commit f5256e6779
2 files changed
+30 -14

No files matched your search

+19 -9
View File
@@ -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.
+11 -5
View File
@@ -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');