row 234 (#1740): grant-migration test for pre-3j session-written grant rows, A22b's killer (sage-1703)
This commit is contained in:
1 parent
95f966b1ce
commit
097892741c
2 files changed
+40
-1
No files matched your search
@@ -127,7 +127,7 @@ Row a, killed on this row's tree:
|
||||
| A19 | let the writer accept a sudo user not in `privileged` |
|
||||
| A20 | trigger: drop the `body.operation` arm, so the consume record passes from a session |
|
||||
| A21 | trigger: accept actor_role NULL with any actor_run |
|
||||
| A22 | a grant reader drops the `kind = 'action.allowed'` or actor filter |
|
||||
| A22 | a grant reader drops the `kind = 'action.allowed'` or actor filter. The runner splits it: A22a drops the kind, and G21's `action.refused` look-alikes kill it. A22b drops the actor, which the trigger can't stand in for, because 3i-3j adds the trigger without checking old rows. Its killer is the grant-migration test "A22b, sage-1703": a session-written `grant.host.removed` with `hostLineLeft` and a `grant.host-enroll` row from 3i, migrated to 3j, where `hosts.remove` still removes the host and `reconcileGrants` ends nothing |
|
||||
| A23 | `host.run` skips the Approve record check (any `host_grants` record is enough) |
|
||||
|
||||
Row b keeps the rest of r6's list: the `ready` check, the three hash compares, the definition check, the
|
||||
|
||||
@@ -7,6 +7,7 @@ import { pathToFileURL } from 'node:url';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import { Store, schemaDigest } from '../src/store.mjs';
|
||||
import { Broker } from '../src/broker.mjs';
|
||||
// Row 234 (#1740, #436a row a): the 3i→3j step adds the hosts and host_grants tables and the grant
|
||||
// event guard (#436b section 7 at fa5658941). It runs on a 3i store built here from the pinned 3i
|
||||
// schema (8fede5fc6's schema.sql) and fixture rows. No live store is read or copied.
|
||||
@@ -131,6 +132,44 @@ test('the 3i engine refuses a 3j store, migrating or not, and leaves it at 3j',
|
||||
assert.deepEqual(rows(path), after);
|
||||
});
|
||||
|
||||
test('A22b, sage-1703: a session-written grant.* row from before the step survives it, and the grant readers ignore it', async t => {
|
||||
// The step adds the guard but leaves old rows. At 3i recordEvent had no reserved-event refusal, so a
|
||||
// session could write these, and after the step only GRANT_EVIDENCE's actor filter keeps them out.
|
||||
const { root, path } = source3i(t);
|
||||
const db = new DatabaseSync(path);
|
||||
const run = (sql, ...args) => db.prepare(sql).run(...args);
|
||||
run(`INSERT INTO decisions(id,at,business,raised_by_role,raised_by_run,class,action,route_to,question,options,recommendation,blocking)
|
||||
VALUES('d1',?,'demo','chief','run1','gated','credential.mint','human','q','["yes","no"]','yes',0)`, AT);
|
||||
const legacy = [{ action: 'grant.host.removed', host: 'web1', hostLineLeft: TAG }, { operation: 'grant.host-enroll', decision: 'd1' }];
|
||||
for (const [i, body] of legacy.entries())
|
||||
run('INSERT INTO events(id,at,business,kind,actor_role,actor_run,body) VALUES(?,?,?,?,?,?,?)', 'old' + i, AT, 'demo', 'action.allowed', 'chief', 'run1', JSON.stringify(body));
|
||||
db.close();
|
||||
new Store(root, { migrateSchema: true }).close();
|
||||
const store = new Store(root);
|
||||
t.after(() => store.close());
|
||||
assert.equal(store.get("SELECT count(*) AS n FROM events WHERE id IN ('old0','old1')").n, 2);
|
||||
host(store);
|
||||
const business = {
|
||||
id: 'demo',
|
||||
human: 'jason',
|
||||
arbiters: { technical: 'pm', delivery: 'pm' },
|
||||
roles: {
|
||||
chief: { definition: 'chief-of-staff', authority: { withinRole: ['message.send'], crossRole: [] } },
|
||||
pm: { definition: 'pm', authority: { withinRole: ['message.send'], crossRole: [] } },
|
||||
},
|
||||
vars: {},
|
||||
};
|
||||
const unenrolled = [];
|
||||
const broker = new Broker({ store, businesses: { demo: business }, hostGrant: { unenroll: (b, h) => unenrolled.push([b, h]), installerTag: () => null } });
|
||||
// The enrollment look-alike is not a spent decision, so nothing ends interrupted.
|
||||
assert.deepEqual(broker.reconcileGrants(), []);
|
||||
assert.equal(store.get("SELECT count(*) AS n FROM events WHERE actor_run='grant'").n, 0);
|
||||
// The hostLineLeft look-alike is not a line left on the host.
|
||||
const human = broker.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true });
|
||||
assert.deepEqual(await broker.request(human, { verb: 'hosts.remove', args: { host: 'web1' } }), { host: 'web1', removed: true });
|
||||
assert.deepEqual(unenrolled, [['demo', 'web1']]);
|
||||
});
|
||||
|
||||
test('the guard: a grant.* action or operation on action.allowed needs the (NULL, grant) actor', t => {
|
||||
const store = fresh(t);
|
||||
const refused = /only the grant writer records grant events/;
|
||||
|
||||
Reference in new issue
Block a user