diff --git a/agents/morgana/work/queue-275/BUILD.md b/agents/morgana/work/queue-275/BUILD.md index 8ec27fbb6..3336594b5 100644 --- a/agents/morgana/work/queue-275/BUILD.md +++ b/agents/morgana/work/queue-275/BUILD.md @@ -59,10 +59,12 @@ No other test names a viewer grant on the bus (git grep for `level: 'viewer'` ou 2. A viewer binds; project.list, names, thread.list and thread.messages answer what its grant covers (alice downgraded, bob viewer from the start). 3. A viewer is refused thread.create, thread.open and thread.send `access-refused`, and messages, threads - and thread_opens counts are unchanged. Kills mutant 6. + and thread_opens counts are unchanged. Kills mutant 6 at alice's send. r3 (duckworth-0166 B1): bob is + asked create only, as `#thread` refuses a non-owner `thread-not-owned` before the level check; open and + send are asked on alice's own thread. Bob stays the viewer who never held a collaborator grant. 4. Every verb off USER_VERBS refuses a viewer `access-refused` before its own checks. 5. Mixed grants: collaborator on project a, viewer on b. Create in a works, create in b refuses; send in b's - old thread refuses, open refuses, messages reads. + old thread refuses, open refuses, messages reads the message sent while collaborator (r3, N1). 6. Board reads (BOARD_VERBS, the project file verbs, limits, plugins, instances, routes) refuse alice (downgraded viewer), bob (viewer) and carol (collaborator) `access-refused`. Kills mutant 10. 7. Governance pin, user side: one row per item of the 5.7 sentence (decisions, inbox entries, claims as diff --git a/packages/bus/tests/viewer.test.mjs b/packages/bus/tests/viewer.test.mjs index 2406d24b5..f28fff7ce 100644 --- a/packages/bus/tests/viewer.test.mjs +++ b/packages/bus/tests/viewer.test.mjs @@ -102,11 +102,12 @@ test('275: a viewer is refused thread.create, thread.open and thread.send access const { store, code, p1, A } = viewers(t); const counts = () => ['messages', 'threads', 'thread_opens'].map((x) => store.get(`SELECT count(*) n FROM ${x}`).n); const before = counts(); - for (const who of ['alice', 'bob']) { + for (const who of ['alice', 'bob']) assert.equal(code(who, 'thread.create', { project: p1, name: `${who} new` }), 'access-refused', `${who} create`); - assert.equal(code(who, 'thread.open', { thread: A }), 'access-refused', `${who} open`); - assert.equal(code(who, 'thread.send', { thread: A, body: 'still here?' }), 'access-refused', `${who} send`); - } + // Only alice owns a thread: #thread refuses a non-owner thread-not-owned before the level check + // (duckworth-0166 B1), so open and send are asked on her own A. + assert.equal(code('alice', 'thread.open', { thread: A }), 'access-refused', 'alice open'); + assert.equal(code('alice', 'thread.send', { thread: A, body: 'still here?' }), 'access-refused', 'alice send'); assert.deepEqual(counts(), before); }); @@ -126,6 +127,7 @@ test('275: mixed grants, collaborator on one project and viewer on another, take access.doc.grants = [grant({ projects: [pa] }), grant({ id: 'g-2', projects: [pb] })]; user('alice'); const B = call('alice', 'thread.create', { project: pb, name: 'b work' }).id; + const inB = call('alice', 'thread.send', { thread: B, body: 'in b, before' }).id; access.doc.grants = [grant({ projects: [pa] }), grant({ id: 'g-2', projects: [pb], level: 'viewer' })]; const A = call('alice', 'thread.create', { project: pa, name: 'a work' }).id; assert.ok(A, 'create in the collaborator project works'); @@ -135,7 +137,7 @@ test('275: mixed grants, collaborator on one project and viewer on another, take assert.ok(call('alice', 'thread.send', { thread: A, body: 'in a' }).id); assert.equal(code('alice', 'thread.send', { thread: B, body: 'in b' }), 'access-refused'); assert.equal(code('alice', 'thread.open', { thread: B }), 'access-refused'); - assert.deepEqual(call('alice', 'thread.messages', { thread: B }), []); + assert.deepEqual(call('alice', 'thread.messages', { thread: B }).map((m) => m.id), [inB]); }); // Mutant 10 (row 193 design 8): a user allowed `trail`. This test kills it; R3's exact USER_VERBS