morgana: FED R3 r2 (dewey-0777): role key user refused at the broker load check and by the instance writer (N1, one test each), #threadFor user clause listed as an equivalent mutant, 193b1a filter mutant not applicable, M10/M11 and the M3x two-walls run in the kit; unrun
This commit is contained in:
1 parent
e7d2dc3e60
commit
62de337605
5 files changed
+62
-19
No files matched your search
@@ -33,6 +33,13 @@ branch name. No schema change. Nothing has run yet: every test run waits for Sag
|
||||
- `#waiting` returns null for `to: 'user'`, so no notice is posted.
|
||||
- `user` is reserved: `name.set` refuses it (`name-is-instance`) and message.send never resolves it as a name.
|
||||
- `#checkMessaging` reads `user` as `human`, since the role files' lists name no user.
|
||||
- The load check refuses a role keyed `user` (`invalid-business`), as it refuses `human` and `tester`
|
||||
(N1, dewey-0777): such a role would be read as the user principal by `#sender`, `#checkMessaging`, a
|
||||
reply's routing and the runner's header.
|
||||
|
||||
`packages/business/src/instance-settings.mjs` (r2, dewey-0777; outside the brief's manifest, for Sage to rule
|
||||
on): the instance writer refuses an instance named `user` (`invalid-request`), as it refuses `human` and
|
||||
`tester`, so it can't write a business the bus would then refuse to load.
|
||||
|
||||
`packages/bus/src/client.mjs`: a `user` option, sent as the envelope's `user`.
|
||||
|
||||
@@ -67,9 +74,13 @@ nothing." The human, tester and assistant senders get no such line.
|
||||
| revoked and expired grant refused on next request | a removed or expired grant refuses the next request access-revoked ... (also downgraded to viewer, file unreadable) |
|
||||
| refusals recorded | a user refusal is recorded as action.refused by user:<id> |
|
||||
| a user named like the human refused | a user named like the business human, the tester or a principal word is refused ... |
|
||||
| N1: role key `user` refused (r2) | a business with a role keyed user refuses invalid-business |
|
||||
|
||||
`packages/harness/tests/runner.test.mjs`: the user header, exact string, and its absence for the other senders.
|
||||
|
||||
`packages/business/tests/instance-settings.test.mjs` (r2): row 165's validation test also refuses instance
|
||||
`user`.
|
||||
|
||||
Row 196's `principal.test.mjs` already covers the user kind of `verifyPrincipal` (agent parent, non-Console
|
||||
parent, kind confusion are its mutants). No runtime test of `authenticateUser` or user-cli yet: the stub
|
||||
`authenticateUser` path needs a real Console parent, which is R6's harness.
|
||||
@@ -84,7 +95,7 @@ so the cap-taking launcher and git ops (`authorize`, `checkPush`, `recordPush`,
|
||||
| Branch | For a user |
|
||||
|---|---|
|
||||
| `#agent`, `#human` | `#human` refuses a user (`human-required`); every listed verb skips it with `!s.user`. `#agent` is reached only from `names` for a non-human, non-tester, non-user session. |
|
||||
| `#threadFor` | refuses a user (item 3). Not reachable: message.send is off the list. |
|
||||
| `#threadFor` | refuses a user (item 3). Not reachable: message.send and the review verbs are off the list. An equivalent mutant; the line stays. |
|
||||
| `#testerOwns`, `#testerThread` | `s.tester` false and `s.user` sessions have no thread: not taken. |
|
||||
| `#owner`, `#sender`, `#event` | changed: `user:<id>`, `user`, actor columns. |
|
||||
| `#userThread` | new; false for a user session itself. |
|
||||
@@ -96,7 +107,7 @@ so the cap-taking launcher and git ops (`authorize`, `checkPush`, `recordPush`,
|
||||
| `message.send`, `message.receive`, `message.read`, `messages`, `replies`, `trail`, `decisions`, `credentials`, `state`, `usage`, `decision.withdraw`, `assistant.stop`, `name.set`, `project.create`, `role.claim` | off the list: not reachable. |
|
||||
| `project.list`, `thread.create`, `thread.list`, `thread.open`, `thread.send`, `thread.messages`, `names` | changed as above. |
|
||||
|
||||
## Mutants (each must fail its named test in user-principal.test.mjs)
|
||||
## Mutants (each must fail its named test, in user-principal.test.mjs unless named)
|
||||
|
||||
| Mutant | Edit | Named test |
|
||||
|---|---|---|
|
||||
@@ -106,25 +117,30 @@ so the cap-taking launcher and git ops (`authorize`, `checkPush`, `recordPush`,
|
||||
| M2c | names filter dropped | R3 leak: names shows granted instances only |
|
||||
| M2d | `#grantedThread` check dropped | R3 leak: thread.list shows ... never others |
|
||||
| M2e | thread.create grant check dropped | R3 leak: thread.create needs the project and the thread instance granted ... |
|
||||
| M3x | `message.send` added to `USER_VERBS` (stand-in, see below) | every verb off the collaborator list refuses ... |
|
||||
| M3x | `message.send` added to `USER_VERBS` (list mutant) | every verb off the collaborator list refuses ... |
|
||||
| M4 | the reply-to check dropped | a reply to: user without a matching in_reply_to is refused user-reply-required |
|
||||
| M5 | the business human dropped from bindUser's reserved list | a user named like the business human ... |
|
||||
| M6x | `trail` and `replies` added to `USER_VERBS` (stand-in, see below) | every verb off the collaborator list refuses ... |
|
||||
| M6x | `trail` and `replies` added to `USER_VERBS` (second list mutant) | every verb off the collaborator list refuses ... |
|
||||
| M7 | user-thread-human check dropped | a user thread run cannot message the owner ..., B9 |
|
||||
| M8 | `to === 'user'` dropped from `#waiting` | the run reply to a user message reaches ... with no waiting and no notice |
|
||||
| M9 | thread.send writes `human.input` for a user | thread.send is from user:<id> and writes no human.input |
|
||||
| M10 | the load check accepts a role keyed `user` | a business with a role keyed user refuses invalid-business |
|
||||
| M11 | instance-settings accepts instance `user` | instance-settings.test.mjs: row 165 refuses each validation failure ... |
|
||||
|
||||
Two mutants named in the design and the brief can't be killed as written, and need a ruling from Sage and Dewey:
|
||||
1. "#threadFor's user refusal removed": no user request reaches `#threadFor`. Its only callers are
|
||||
message.send and `#reviewMessage` (review.request and review.verdict), all off the list, and a user would fail message.send's run branch before
|
||||
it. M3x, opening message.send to a user, is the stand-in; the line stays as defence in depth.
|
||||
2. The brief's "drops the 193b1a filter for user sessions": a user can't call trail or replies, so a user
|
||||
sees no filtered read for the mutant to change. M6x, opening trail and replies to a user, is the stand-in.
|
||||
Rulings from Dewey's early look (dewey-0777) on the two mutants the design and brief name:
|
||||
1. "#threadFor's user refusal removed" is an equivalent mutant. No user request reaches `#threadFor`: its
|
||||
only callers are message.send and `#reviewMessage` (review.request and review.verdict), all off the list.
|
||||
The line stays as defence in depth. M3x stays as a list mutant. The kit also runs the reply-chain test
|
||||
under M3x as the two-walls evidence: with the access-refused assertion turned into a log line, it records
|
||||
the code alice's send gets (expected `human-required`, from `#human`, since a user has no run) and still
|
||||
asserts bob's thread holds one message.
|
||||
2. The brief's "drops the 193b1a filter for user sessions" is not applicable: a user can't call trail or
|
||||
replies, so no filtered read reaches a user. M6x is a second list mutant, not a stand-in.
|
||||
|
||||
## Notes
|
||||
|
||||
- N1: a business role keyed `user` would be shadowed by the user principal in message.send. The business
|
||||
loader doesn't refuse that key. Follow-up for R6, or a one-line loader check if Dewey wants it here.
|
||||
- N1: fixed in r2 (dewey-0777). The broker's load check and the instance writer refuse `user`, one test each
|
||||
(M10, M11). The instance-settings change is outside the brief's manifest; Sage rules on keeping it in R3.
|
||||
- N2: server.mjs answers a `user` envelope with no `authenticateUser` as `human-required`, the existing
|
||||
default for a non-tester kind. The runtime always passes `authenticateUser`, so only a bare test server sees it.
|
||||
|
||||
@@ -133,12 +149,13 @@ Two mutants named in the design and the brief can't be killed as written, and ne
|
||||
The kit is /mnt/storage/scratch/morgana-r3 (`slot-r3.sh`, `mutate.py`, `busy.sh`), run as
|
||||
`bash busy.sh && CAND=<sha> bash slot-r3.sh` on Sage's CLEAR. It runs these files whole: user-principal,
|
||||
principal, human, tester, tester-threads, tester-writes, threads, names, messaging, messages, decisions,
|
||||
decision-route, broker, transport, peer-transport, process, message-filter and harness runner. Then the bus
|
||||
and harness suites, then the thirteen mutants, each on its named test and restored after each. It uses
|
||||
decision-route, broker, transport, peer-transport, process, message-filter, harness runner and business
|
||||
instance-settings. Then the bus, harness and business suites, then the fifteen mutants, each on its named
|
||||
test and restored after each, then the M3x two-walls run. It uses
|
||||
umask 022 and TMPDIR from `mktemp -d /mnt/storage/scratch/tmp/gr3XXXX`, refuses unless HEAD is the
|
||||
candidate and the tree is clean, and stops after two unexpected failures (mos-0823).
|
||||
|
||||
Sage's gate adds every `scripts/test-*.sh`.
|
||||
|
||||
Static check: `node --check` on every changed source and test file, run from `/`. All parse. All thirteen
|
||||
Static check: `node --check` on every changed source and test file, run from `/`. All parse. All fifteen
|
||||
mutant anchors match exactly once.
|
||||
@@ -356,7 +356,9 @@ export class Broker {
|
||||
if (!id(name) || b.id !== name || !id(b.human) || b.human === 'tester' || !object(b.roles) || !object(b.arbiters))
|
||||
fail('invalid-business');
|
||||
for (const role of Object.keys(b.roles)) {
|
||||
if (!id(role) || role === 'human' || role === 'tester') fail('invalid-business');
|
||||
// FED R3 (dewey-0777): a role keyed `user` would be read as the user principal by #sender,
|
||||
// #checkMessaging, a reply's routing and the runner's header.
|
||||
if (!id(role) || role === 'human' || role === 'tester' || role === 'user') fail('invalid-business');
|
||||
const a = b.roles[role].authority;
|
||||
if (!object(a) || !Array.isArray(a.withinRole) || !Array.isArray(a.crossRole))
|
||||
fail('invalid-authority');
|
||||
|
||||
@@ -318,6 +318,27 @@ test('R3: a user refusal is recorded as action.refused by user:<id>', (t) => {
|
||||
assert.deepEqual(rows.map((r) => [r.actor_role, JSON.parse(r.body).code]), [['user', 'access-refused'], ['user', 'access-revoked']]);
|
||||
});
|
||||
|
||||
test('R3: a business with a role keyed user refuses invalid-business', (t) => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'bus-user-principal-boot-'));
|
||||
const store = new Store(root);
|
||||
t.after(() => {
|
||||
store.close();
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
const businesses = {
|
||||
demo: {
|
||||
id: 'demo', human: 'jason', arbiters: { technical: 'chief', delivery: 'chief' },
|
||||
roles: {
|
||||
chief: { definition: 'chief-of-staff', authority: { withinRole: ['message.send'], crossRole: [] } },
|
||||
user: { definition: 'assistant', authority: { withinRole: ['message.send'], crossRole: [] } },
|
||||
},
|
||||
},
|
||||
};
|
||||
assert.throws(() => new Broker({ store, businesses }), { code: 'invalid-business' });
|
||||
delete businesses.demo.roles.user;
|
||||
assert.ok(new Broker({ store, businesses }), 'the same business without it loads');
|
||||
});
|
||||
|
||||
test('R3: name.set refuses the name user', (t) => {
|
||||
const { code } = setup(t);
|
||||
assert.equal(code('human', 'name.set', { instance: 'assistant', name: 'user' }), 'name-is-instance');
|
||||
|
||||
@@ -85,9 +85,10 @@ export function instanceSettings({ dir, rolesDir, system, levels = () => null })
|
||||
}
|
||||
const { instance, op } = args;
|
||||
if (!id(instance) || (op !== "add" && op !== "change")) throw new OrganizationError("invalid-request");
|
||||
// The broker refuses a business whose roles name human or tester (broker.mjs, the load check),
|
||||
// so an instance under either name would stop the bus at its next start (row 165 r2, Dewey F1).
|
||||
if (instance === "human" || instance === "tester") {
|
||||
// The broker refuses a business whose roles name human, tester or user (broker.mjs, the load
|
||||
// check), so an instance under any of them would stop the bus at its next start (row 165 r2,
|
||||
// Dewey F1; FED R3, dewey-0777).
|
||||
if (instance === "human" || instance === "tester" || instance === "user") {
|
||||
throw new OrganizationError("invalid-request", `${instance} names the bus's own sessions, never a role instance`);
|
||||
}
|
||||
if (Object.hasOwn(args, "definition") && !id(args.definition)) throw new OrganizationError("invalid-request");
|
||||
|
||||
@@ -140,6 +140,8 @@ test("row 165 refuses each validation failure and leaves the file's bytes alone"
|
||||
// Dewey F1: the broker refuses a business whose roles name human or tester, so the writer does too.
|
||||
refuses(f, { instance: "human", op: "add", definition: "merge-gate" }, "invalid-request");
|
||||
refuses(f, { instance: "tester", op: "add", definition: "merge-gate" }, "invalid-request");
|
||||
// FED R3 (dewey-0777): and user, the granted user's principal.
|
||||
refuses(f, { instance: "user", op: "add", definition: "merge-gate" }, "invalid-request");
|
||||
const one = fixture(doc => { doc.launch.instances = ["coder"]; });
|
||||
refuses(one, { instance: "coder", op: "change", launch: false }, "BusinessError");
|
||||
const none = fixture(doc => { delete doc.launch; });
|
||||
|
||||
Reference in new issue
Block a user