morgana: row 295 r2 old-test setup (mos-1436 option a): message-filter's :117 and governance-pin tests get an alice collaborator access document, spec-fixtures fixture() takes an optional access (null default); asserts and bindLaunch lines byte-identical; BUILD.md gate record, assertion preservation, N1 revoke window

This commit is contained in:
jason.woltje committed 2026-10-23 04:23:33 -05:00
1 parent 9f29e1b2fd
commit f652dfa5b8
3 files changed
+56 -5

No files matched your search

+44
View File
@@ -151,3 +151,47 @@ the kill, a run that polls would exit 22 on not-holder and hide the mutant.
packages/cli/tests/access-boot.test.mjs and packages/webui/tests/granted-user.test.mjs run whole at
the gate with the touched files. stack-release.test's copy list carries access.mjs, which adds
no import here.
## Gate on 07802dc57 (sage-3318)
- First run, merge b42c591f8 (tree 0626929e5): access-revoke.test went 4 pass, 6 fail. These were
first-run reds in the row's new tests, placed per mos-1272 in 9f29e1b2f (sage-3326, Dewey
dewey-0905). Five claim-row compares failed because store rows have a null prototype, so they're
now spread; thread-revoke:207 had the same compare and got the same fix. The demo business switches
on the pi harness, which startBroker's harnessOn needs. In thread-revoke, Alice binds at her first
call, because a second host's bind after the hand edit refuses access-revoked.
- Second run, merge 38591cf96 (tree f719c0dc5): 12 whole files green. suite-bus stopped on 2
old-test reds in packages/bus/tests/message-filter.test.mjs. ":117 trail on a decision ..." and
":165 governance pin ..." each threw thread-revoked from bindLaunch's new check. Those fixtures
write a `user:alice` thread with no access function, and with no document no grant covers it.
Mos ruled option (a) in mos-1436: old-test setup only.
### 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.
- spec-fixtures.mjs (11 importers) gains one optional parameter with a null default, which goes to
`new Broker` unchanged. No other caller changes.
-export async function fixture(t, { adapter = true, roles = {}, arbiters = { technical: 'reviewer', delivery: 'reviewer' } } = {}) {
+export async function fixture(t, { adapter = true, roles = {}, arbiters = { technical: 'reviewer', delivery: 'reviewer' }, access = null } = {}) {
- const broker = new Broker({ store, businesses: { demo: business }, project: adapter ? {
+ const broker = new Broker({ store, businesses: { demo: business }, access, project: adapter ? {
- Condition 4: M8 is the mutant. It deletes the broker.mjs:1371 thread-revoked check in bindLaunch,
and its kill test is access-revoke's bind/claim refusal test. The red-first run on 38591cf96
(both tests red), the green run and M8 run on Sage's CLEAR, followed by a full gate restart on
the new merge with a new manifest.
### N items
- N1 (morgana-0167, sage-3318): a revoked run that calls the bus after its claim ends and before the
launcher's SIGTERM gets not-holder and exits 22, not stopped (the same shape as #1809). 302's
launcher-released path doesn't cover it. Sage decides after 295 lands whether this becomes a row.
+10 -3
View File
@@ -15,6 +15,12 @@ import { fixture } from './spec-fixtures.mjs';
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 = () => ({
accessVersion: 1, revision: 1, users: [],
grants: [{ id: 'g-alice', user: 'alice', business: 'demo', projects: ['*'], 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.
const DEFAULT_SESSION = [
@@ -55,7 +61,7 @@ const fromAlice = (store, thread, body, extra = {}) =>
const toAlice = (store, thread, body, extra = {}) =>
put(store, { thread, from_role: 'assistant', from_run: 'assistant-run', to_role: 'user', body, ...extra });
function setup(t) {
function setup(t, { access } = {}) {
const root = mkdtempSync(join(tmpdir(), 'bus-message-filter-'));
const store = new Store(root);
t.after(() => {
@@ -64,6 +70,7 @@ function setup(t) {
});
const b = new Broker({
store,
access,
businesses: {
demo: {
id: 'demo', human: 'jason', arbiters: { technical: 'chief', delivery: 'chief' }, vars: { 'bus.tester': true },
@@ -115,7 +122,7 @@ 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);
const { b, store, call, U } = setup(t, { access: aliceAccess });
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, {
@@ -163,7 +170,7 @@ 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']) } } });
const f = await fixture(t, { roles: { assistant: { definition: 'assistant', ...within(['message.send', 'record.write']) } }, access: aliceAccess });
const project = (await f.call(f.human, 'project.create', { name: 'alice work' })).id;
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 });
+2 -2
View File
@@ -7,7 +7,7 @@ import { Broker } from '../src/broker.mjs';
import { initProject, readSpec, writeRecord, listProjectFiles, readProjectFile, writeHumanFile, ProjectError } from '../../project/src/index.mjs';
export const git = (repo, ...args) => execFileSync('git', ['-c', 'core.hooksPath=/dev/null', '-C', repo, ...args], { encoding: 'utf8' }).trim();
const options = [{ key: 'yes', text: 'Yes' }, { key: 'no', text: 'No' }];
export async function fixture(t, { adapter = true, roles = {}, arbiters = { technical: 'reviewer', delivery: 'reviewer' } } = {}) {
export async function fixture(t, { adapter = true, roles = {}, arbiters = { technical: 'reviewer', delivery: 'reviewer' }, access = null } = {}) {
const root = mkdtempSync(join(tmpdir(), 'bus-spec-'));
const store = new Store(root);
t.after(() => { store.close(); rmSync(root, { recursive: true, force: true }); });
@@ -19,7 +19,7 @@ export async function fixture(t, { adapter = true, roles = {}, arbiters = { tech
...roles,
} };
const { repo, commit } = await initProject({ business, project: 'stack', dataRoot: root });
const broker = new Broker({ store, businesses: { demo: business }, project: adapter ? {
const broker = new Broker({ store, businesses: { demo: business }, access, project: adapter ? {
readSpec: context => readSpec({ ...context, dataRoot: root }),
writeRecord: context => writeRecord({ ...context, dataRoot: root }),
listFiles: context => listProjectFiles({ ...context, dataRoot: root }),