row 281 b3: the first base run's five red tests were test faults, no code change; T4b reads the manual's revoke id from its event, T5b expects gitea-left:<id>,tokens (N10), T6b allows step 0's GET checks, gitea T8 leaves the call log out, cli T10 makes the 0700 store root; rebase onto 980cec88c noted (sage-2605, sage-2615, #1786)
This commit is contained in:
1 parent
eed5a65b6d
commit
06ee5ed6b6
4 files changed
+55
-5
No files matched your search
@@ -692,6 +692,48 @@ this row:
|
|||||||
- `read(business, actor)` changes only the listing's `endProtected`. The broker and these tests don't read it.
|
- `read(business, actor)` changes only the listing's `endProtected`. The broker and these tests don't read it.
|
||||||
The T10 cli case deletes `sec-1` (security-reviewer), which isn't END_PROTECTED.
|
The T10 cli case deletes `sec-1` (security-reviewer), which isn't END_PROTECTED.
|
||||||
|
|
||||||
|
Rebase onto 980cec88c, after row 277 (sage-2605). It's on branch `megavolt/row281-c`.
|
||||||
|
`git range-diff 46dedf925..7454be493 980cec88c..c174f37ac` gives `=` for all ten commits. Row 277's
|
||||||
|
`thread.claims` change to `broker.mjs` doesn't touch step G or any line the mutants change:
|
||||||
|
`scratch/megavolt-r281/anchors.sh` counts each M1-M21 sed anchor in the rebased `broker.mjs`, and all 28
|
||||||
|
counts are 1. The mutants script runs it after a green base and stops if a count isn't 1.
|
||||||
|
|
||||||
|
b3, after the first base run on c174f37ac (11:24:47Z) came back red. These tests had never run before: b2
|
||||||
|
was written under git and docs only. bus: 120 tests, 116 pass, 4 fail. cli lifecycle: 24 tests, 23 pass,
|
||||||
|
1 fail. Every older test in those files passed. No mutant ran. Each failure is a test fault, and b3 changes
|
||||||
|
no code:
|
||||||
|
- T4b, `fire-revokes-grants`: **test changed**. The test read `manual.revoke`, but the `hosts.revoke`
|
||||||
|
answer has never carried `revoke`: `#revokeHost` drops it, so the verb's answer keeps its pre-281 shape.
|
||||||
|
The fire's close names the manual revoke, as section 3a says. The test now takes that id from the
|
||||||
|
manual's `grant.revoked` event.
|
||||||
|
- T5b, `fire-revokes-grants`: **test changed**. The job refused `gitea-left:9,tokens`, and the test
|
||||||
|
expected `gitea-left:<digits>`. Section 4 sets the reason as `gitea-left:<ids>`, the ids left at Gitea.
|
||||||
|
For a fire, a close's `serviceLeft` is the N10 list from before 281: each token id whose DELETE failed,
|
||||||
|
then `tokens` or `login` for a fire step that failed (`#giteaCloseRevoke`). With the DELETE failing,
|
||||||
|
`#giteaFire`'s token list never clears, so `tokens` is a real entry left at Gitea. It belongs in the
|
||||||
|
reason. The test now expects `gitea-left:<id>,tokens`.
|
||||||
|
- T6b, `fire-revokes-grants`: **test changed**. After the fire, the executor made 4 Gitea calls where the
|
||||||
|
test expected none. They are step 0's admin checks C1 to C4: version, self, token scopes and the repo
|
||||||
|
check, all GET. Step 1's `#giteaRevokedSince` then ends the decision `revoked` before any write. Section
|
||||||
|
3c of d6 names that step 1 check as the only thing that stops the decision. T6b's line in section 8 says
|
||||||
|
"before any Gitea call", but the evidence it lists is no row, no token file and no token create. That
|
||||||
|
phrase was wrong, because step 0 always runs first. The approved text stays as it is, and this note is
|
||||||
|
the correction. The test now checks that every call after the fire is a GET and that no token create
|
||||||
|
happened. Moving the revoked check ahead of step 0 would change the executor for every revoke, not just
|
||||||
|
step G's, and the design doesn't ask for that.
|
||||||
|
- T8, `gitea-grant`: **test changed**. The human revoke's events between `grant.revoked` and
|
||||||
|
`grant.revoke.closed` include its 5 `grant.call`/`grant.result` pairs (`#giteaLog` logs both around every
|
||||||
|
Gitea call). T8 checks that a human revoke writes no job records, not that it makes no calls. It now
|
||||||
|
leaves the call log out before comparing.
|
||||||
|
- T10, cli `lifecycle`: **test changed** (sage-2615 asked about this one). The `credential-file` came from
|
||||||
|
`startBroker`, not from the delete and not from Gitea. The seeded host grant gives `sec-1` a `host.web1`
|
||||||
|
store reference. `Credentials` checks the store root (`<config dir>/secrets/agents`) with `privateDir` at
|
||||||
|
start, and the fixture had no store root. That check is row 199's, and it is right to fail closed. The
|
||||||
|
test now makes the store root at 0700 under `dirname(p.f.config)`, as the bus host fixtures do, never
|
||||||
|
under the real config. sage-2575's condition still holds. This business has no Gitea grant, so
|
||||||
|
`#grantSet` adds no `gitea` entry (`#giteaPending` and `#giteaUnfired` read only the store), and step G
|
||||||
|
never reaches `#stepGGitea`. A delete with no Gitea grant reads no Gitea credential file.
|
||||||
|
|
||||||
## Runs
|
## Runs
|
||||||
|
|
||||||
(after the gate)
|
(after the gate)
|
||||||
|
|||||||
@@ -532,7 +532,9 @@ test('row 281 T4b (Q1): a line a manual hosts.revoke left on C is removed by the
|
|||||||
assert.equal(close.length, 1);
|
assert.equal(close.length, 1);
|
||||||
assert.equal(close[0].hostLine, 'removed');
|
assert.equal(close[0].hostLine, 'removed');
|
||||||
assert.equal(close[0].tag, tag3);
|
assert.equal(close[0].tag, tag3);
|
||||||
assert.equal(close[0].revoke, manual.revoke);
|
// hosts.revoke's answer has no revoke id; the manual's grant.revoked has it.
|
||||||
|
const [manualRevoked] = f.revoked('coder', (e) => e.host === 'web3' && e.fire !== true);
|
||||||
|
assert.equal(close[0].revoke, manualRevoked.revoke);
|
||||||
const [done] = f.allowed('fire.done');
|
const [done] = f.allowed('fire.done');
|
||||||
assert.deepEqual(done.body.grants.map((x) => x.grant), ['host:web1', 'line:web3']);
|
assert.deepEqual(done.body.grants.map((x) => x.grant), ['host:web1', 'line:web3']);
|
||||||
assert.deepEqual(done.body.grantsLeft, []);
|
assert.deepEqual(done.body.grantsLeft, []);
|
||||||
@@ -594,7 +596,8 @@ test("row 281 T5b (N1): a failed token DELETE refuses gitea-left; the repeat ret
|
|||||||
await f.broker.grantsIdle();
|
await f.broker.grantsIdle();
|
||||||
const [refused] = f.grantEvents().filter((e) => e.action === 'grant.revoke.refused');
|
const [refused] = f.grantEvents().filter((e) => e.action === 'grant.revoke.refused');
|
||||||
assert.equal(refused.grant, 'gitea');
|
assert.equal(refused.grant, 'gitea');
|
||||||
assert.match(refused.reason, /^gitea-left:\d+$/);
|
// The close's serviceLeft (N10): the token id, and "tokens" for the fire's list that didn't clear.
|
||||||
|
assert.match(refused.reason, /^gitea-left:\d+,tokens$/);
|
||||||
assert.equal(refused.job, out.job);
|
assert.equal(refused.job, out.job);
|
||||||
assert.equal(f.userTokens('demo-coder').length, 1);
|
assert.equal(f.userTokens('demo-coder').length, 1);
|
||||||
assert.equal(f.allowed('fire.done').length, 0);
|
assert.equal(f.allowed('fire.done').length, 0);
|
||||||
@@ -648,7 +651,8 @@ test('row 281 T6b (sage-2574): a Gitea decision approved before the fire and run
|
|||||||
assert.equal(f.ended(id).code, 'revoked');
|
assert.equal(f.ended(id).code, 'revoked');
|
||||||
assert.equal(f.giteaRow('coder'), undefined);
|
assert.equal(f.giteaRow('coder'), undefined);
|
||||||
assert.equal(existsSync(f.tokenFile('coder')), false);
|
assert.equal(existsSync(f.tokenFile('coder')), false);
|
||||||
assert.equal(f.gitea.requests.length, calls);
|
// Only step 0's C1 to C4 reach Gitea, all reads; step 1's recheck ends it before any write.
|
||||||
|
assert.ok(f.gitea.requests.slice(calls).every((e) => e.method === 'GET'));
|
||||||
assert.ok(!f.gitea.requests.some((e) => e.method === 'POST' && e.path.endsWith('/tokens')));
|
assert.ok(!f.gitea.requests.some((e) => e.method === 'POST' && e.path.endsWith('/tokens')));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1227,7 +1227,9 @@ test('row 281 T8: a tester grants.revoke refuses tester-dev-only once bus.tester
|
|||||||
assert.equal(f.grantEvents().length, before);
|
assert.equal(f.grantEvents().length, before);
|
||||||
assert.ok(f.record());
|
assert.ok(f.record());
|
||||||
await f.call('human', 'grants.revoke', { service: 'gitea', agent: 'coder', reason: 'r' });
|
await f.call('human', 'grants.revoke', { service: 'gitea', agent: 'coder', reason: 'r' });
|
||||||
const after = f.grantEvents().slice(before).map((e) => [e.action, e.fire ?? false]);
|
const after = f.grantEvents().slice(before)
|
||||||
|
.filter((e) => e.action !== 'grant.call' && e.action !== 'grant.result')
|
||||||
|
.map((e) => [e.action, e.fire ?? false]);
|
||||||
assert.deepEqual(after, [['grant.revoked', false], ['grant.revoke.closed', false]]);
|
assert.deepEqual(after, [['grant.revoked', false], ['grant.revoke.closed', false]]);
|
||||||
assert.deepEqual(f.userTokens(), []);
|
assert.deepEqual(f.userTokens(), []);
|
||||||
const jobs = f.store.all("SELECT body FROM events WHERE business='demo' AND kind='action.allowed'").map((e) => JSON.parse(e.body).action)
|
const jobs = f.store.all("SELECT body FROM events WHERE business='demo' AND kind='action.allowed'").map((e) => JSON.parse(e.body).action)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { spawn, spawnSync } from "node:child_process";
|
|||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
import { DatabaseSync } from "node:sqlite";
|
import { DatabaseSync } from "node:sqlite";
|
||||||
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { launchDoc } from "../../business/tests/helpers.mjs";
|
import { launchDoc } from "../../business/tests/helpers.mjs";
|
||||||
import { BusError } from "../../bus/src/broker.mjs";
|
import { BusError } from "../../bus/src/broker.mjs";
|
||||||
@@ -436,6 +436,8 @@ test("row 194 V5: a delete with no workspace commits and closes with the transac
|
|||||||
// fail or wait; either way the request has already closed.
|
// fail or wait; either way the request has already closed.
|
||||||
test("row 281 T10: the delete pass closes a granted instance's first request grants-revoking and the host channel stays up", { skip, timeout: 90000 }, async (t) => {
|
test("row 281 T10: the delete pass closes a granted instance's first request grants-revoking and the host channel stays up", { skip, timeout: 90000 }, async (t) => {
|
||||||
const p = prepare(t);
|
const p = prepare(t);
|
||||||
|
// A host grant gives the agent a host.web1 store reference, so Credentials checks the store root at start.
|
||||||
|
mkdirSync(join(dirname(p.f.config), "secrets", "agents"), { recursive: true, mode: 0o700 });
|
||||||
const seeded = new Store(p.f.dataRoot);
|
const seeded = new Store(p.f.dataRoot);
|
||||||
const entry = { address: "192.0.2.10", port: 22, user: "deploy", hostKeys: ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHostKeyForTests"] };
|
const entry = { address: "192.0.2.10", port: 22, user: "deploy", hostKeys: ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHostKeyForTests"] };
|
||||||
hostRecord(seeded, { business: "acme", host: "web1", entry });
|
hostRecord(seeded, { business: "acme", host: "web1", entry });
|
||||||
|
|||||||
Reference in new issue
Block a user