morgana: row 306 build (#1813): #accessRevoked from #revokeLost, #agent answers thread-revoked until endLaunch, runner stops on thread-revoked, READMEs, access-revoke.test 7 7 (mos-1463)
This commit is contained in:
1 parent
bd3fa2a484
commit
2d0318a44a
6 files changed
+47
-18
No files matched your search
@@ -186,3 +186,20 @@ Both stay out of this row (sage-3483); Sage adds them to DEFERRED at landing. De
|
||||
- C3: the gate lists the six suites and every scripts/test-*.sh with 0 failures; mos-1463's
|
||||
bounds for access-revoke.test.mjs are in "Expected first-run reds".
|
||||
- Cosmetic: threadIdle's `#claimEnd` is :1133.
|
||||
|
||||
## Build (dewey-0920 APPROVE r2, sage-3487)
|
||||
|
||||
- Red-first commit d38c5213a: packages/bus/tests/revoke-window.test.mjs (T1 two tests, T2) and
|
||||
packages/cli/tests/revoke-window.test.mjs (T3), no product code.
|
||||
- Build commit (this one): D1 in broker.mjs, D2 in runner.mjs, D3 in the two READMEs and the
|
||||
#revokeLost comment, and the mos-1463 edit to access-revoke.test.mjs (numstat 7 7).
|
||||
- Nothing has run yet; every run is slot work.
|
||||
|
||||
Expected first-run reds, on d38c5213a (new tests, fe4ee8c7b product):
|
||||
- bus revoke-window: both T1 tests red, A answers `not-holder` where `thread-revoked` is expected.
|
||||
T2 green.
|
||||
- cli revoke-window T3 red: A's verdict is `session stopped working: not-holder`, its end
|
||||
`session-invalid`/22.
|
||||
- access-revoke.test.mjs is unchanged at d38c5213a, so it stays green there.
|
||||
|
||||
On the build commit every file above, and every gate suite, is expected green.
|
||||
@@ -325,8 +325,9 @@ starts such a thread and sends a running one SIGTERM.
|
||||
Revocation (row 295, #1801, FED R4, federation design 3.7). A user thread is lost when no grant
|
||||
in the access file covers it at collaborator level, so a downgrade to viewer is a loss. One
|
||||
transaction ends the held claim of every session of every lost thread, op `revoke`, reason
|
||||
`access-revoked`; the run's next bus call refuses `not-holder`, and a new run of the thread
|
||||
refuses `thread-revoked` at `bindLaunch` and at `role.claim`. A rebind of an earlier host's run is
|
||||
`access-revoked`; the run's bus calls refuse `thread-revoked` until it ends (row 306, so the runner
|
||||
stops rather than exit as a lost session), and a new run of the thread refuses `thread-revoked` at
|
||||
`bindLaunch` and at `role.claim`. A rebind of an earlier host's run is
|
||||
allowed, so the launcher's recovery can end it. Four triggers end the claims, each with its `by`:
|
||||
the runtime's timer, every 30 s (`access.intervalMs` sets it in process, as `librarySync.intervalMs`
|
||||
does), which reads the file whatever its stamp (`access-timer`); a user's request or a
|
||||
|
||||
@@ -429,6 +429,10 @@ export class Broker {
|
||||
// landRelease release and then signal, so the run's own calls in between answer launcher-released,
|
||||
// not not-holder, and the runner exits stopped instead of as a lost session.
|
||||
#launcherReleased = new Set();
|
||||
// Row 306 (#1813): runs whose claim #revokeLost ended, by business:run, until endLaunch. The launcher
|
||||
// signals only after its threadsState op returns, so the run's own calls in between answer
|
||||
// thread-revoked, not not-holder, and the runner exits stopped instead of as a lost session.
|
||||
#accessRevoked = new Set();
|
||||
#clock = 0;
|
||||
#processing;
|
||||
// credentialStatus is the runtime's metadata-only Credentials.status(); the broker never gets use().
|
||||
@@ -1005,10 +1009,10 @@ export class Broker {
|
||||
covers(g, { business: t.business, project: t.project, instance: t.instance }, now, { level: 'collaborator' }));
|
||||
}
|
||||
// Design 3.7: on a loss, one transaction ends the held claim of every session of every user thread
|
||||
// the access file no longer covers, in every business. The run's next bus call refuses not-holder
|
||||
// (F10), and a later claim refuses thread-revoked. Runs only when the read is new: a forced read,
|
||||
// a stamp it hasn't swept, or no stamp at all. `doc` is a read the caller just made, so a request
|
||||
// reads once. Returns the ended claims.
|
||||
// the access file no longer covers, in every business. The run's bus calls refuse thread-revoked
|
||||
// until it ends (row 306), and a later claim refuses thread-revoked. Runs only when the read is
|
||||
// new: a forced read, a stamp it hasn't swept, or no stamp at all. `doc` is a read the caller just
|
||||
// made, so a request reads once. Returns the ended claims.
|
||||
#revokeLost(by, force = false, doc = this.#accessDoc(force)) {
|
||||
const stamp = this.#accessSeen.stamp;
|
||||
if (!force && stamp !== null && stamp === this.#accessSwept) return [];
|
||||
@@ -1023,6 +1027,7 @@ export class Broker {
|
||||
ended.push(h);
|
||||
}
|
||||
});
|
||||
for (const h of ended) this.#accessRevoked.add(h.business + ':' + h.holder_run);
|
||||
this.#accessSwept = stamp;
|
||||
return ended;
|
||||
}
|
||||
@@ -1067,7 +1072,8 @@ export class Broker {
|
||||
if (s.human) fail('agent-required');
|
||||
const h = this.#own(s);
|
||||
if (h?.op !== 'claim' || (s.thread ? h.generation !== this.#runGeneration(s) : h.holder_run !== s.run))
|
||||
fail(this.#launcherReleased.has(s.business + ':' + s.run) ? 'launcher-released' : 'not-holder');
|
||||
fail(this.#launcherReleased.has(s.business + ':' + s.run) ? 'launcher-released'
|
||||
: this.#accessRevoked.has(s.business + ':' + s.run) ? 'thread-revoked' : 'not-holder');
|
||||
return h;
|
||||
}
|
||||
// Row 264a (#1769, T2): the generation a thread run holds, its latest claim's in its session, or
|
||||
@@ -1392,6 +1398,7 @@ export class Broker {
|
||||
if (exitCode !== null && !Number.isSafeInteger(exitCode)) fail('invalid-request');
|
||||
this.#ended.add(key);
|
||||
this.#launcherReleased.delete(key);
|
||||
this.#accessRevoked.delete(key);
|
||||
for (const [cap, s] of this.#sessions)
|
||||
if (!s.human && !s.reader && s.business === record.business && s.run === record.run) this.#sessions.delete(cap);
|
||||
return this.#store.transaction(() => {
|
||||
|
||||
@@ -92,7 +92,7 @@ function setup(t, { runs = true, grants = [grant(), grant({ user: 'bob' })] } =
|
||||
|
||||
const withoutAlice = (gs) => gs.filter((g) => g.user !== 'alice');
|
||||
|
||||
test('R4: the timer sweep after a hand edit ends only the lost user\'s run; its next call refuses not-holder', (t) => {
|
||||
test('R4: the timer sweep after a hand edit ends only the lost user\'s run; its next call refuses thread-revoked', (t) => {
|
||||
const { b, code, edit, claim, A } = setup(t);
|
||||
edit(withoutAlice);
|
||||
const ended = b.accessSweep();
|
||||
@@ -100,7 +100,7 @@ test('R4: the timer sweep after a hand edit ends only the lost user\'s run; its
|
||||
assert.deepEqual({ ...ended[0], session: undefined }, { business: 'demo', thread: A, session: undefined, run: 'a-run' });
|
||||
assert.equal(typeof ended[0].session, 'string');
|
||||
assert.deepEqual(claim('a-run'), { op: 'revoke', reason: 'access-revoked', by: 'access-timer' });
|
||||
assert.equal(code('a', 'message.receive'), 'not-holder', 'the ended run');
|
||||
assert.equal(code('a', 'message.receive'), 'thread-revoked', 'the ended run');
|
||||
assert.equal(code('b', 'message.receive'), null, 'another user\'s run stays');
|
||||
assert.equal(code('h', 'message.receive'), null, 'the human\'s run stays');
|
||||
assert.deepEqual([claim('b-run').op, claim('h-run').op], ['claim', 'claim']);
|
||||
@@ -117,7 +117,7 @@ test('R4: an expiry with no write and no request ends the run at the next timer
|
||||
assert.equal(code('bob', 'thread.list'), null);
|
||||
assert.equal(claim('a-run').op, 'claim');
|
||||
assert.deepEqual(b.accessSweep().map((h) => h.run), ['a-run']);
|
||||
assert.equal(code('a', 'message.receive'), 'not-holder');
|
||||
assert.equal(code('a', 'message.receive'), 'thread-revoked');
|
||||
assert.equal(code('b', 'message.receive'), null);
|
||||
});
|
||||
|
||||
@@ -137,7 +137,7 @@ test('R4: another user\'s request after a hand edit ends the lost user\'s run, a
|
||||
assert.equal(code('a', 'message.receive'), null, 'an agent request is not a trigger');
|
||||
assert.equal(code('bob', 'thread.list'), null);
|
||||
assert.deepEqual(claim('a-run'), { op: 'revoke', reason: 'access-revoked', by: 'access-change' });
|
||||
assert.equal(code('a', 'message.receive'), 'not-holder');
|
||||
assert.equal(code('a', 'message.receive'), 'thread-revoked');
|
||||
assert.equal(code('b', 'message.receive'), null);
|
||||
assert.equal(code('h', 'message.receive'), null);
|
||||
});
|
||||
@@ -168,7 +168,7 @@ test('R4: access.reload ends the lost runs at once for the human; the tester, a
|
||||
assert.deepEqual(ended.map((h) => [h.thread, h.run]), [[A, 'a-run']]);
|
||||
assert.equal(typeof ended[0].session, 'string');
|
||||
assert.deepEqual(claim('a-run'), { op: 'revoke', reason: 'access-revoked', by: 'jason' });
|
||||
assert.equal(code('a', 'message.receive'), 'not-holder');
|
||||
assert.equal(code('a', 'message.receive'), 'thread-revoked');
|
||||
assert.equal(code('b', 'message.receive'), null);
|
||||
assert.deepEqual(b.accessSweep(), []);
|
||||
});
|
||||
@@ -198,7 +198,7 @@ test('R4: threadsState after an expiry with no write and no sweep ends the claim
|
||||
const a = row(A);
|
||||
assert.deepEqual([a.revoked, a.holder], [true, null]);
|
||||
assert.deepEqual(claim('a-run'), { op: 'revoke', reason: 'access-revoked', by: 'access-expiry' });
|
||||
assert.equal(code('a', 'message.receive'), 'not-holder');
|
||||
assert.equal(code('a', 'message.receive'), 'thread-revoked');
|
||||
assert.deepEqual([row(B).holder, claim('b-run').op], ['b-run', 'claim']);
|
||||
// Ended once: the next call writes no second revoke.
|
||||
row(A);
|
||||
@@ -259,7 +259,7 @@ test('R4 runtime: a hand edit ends the run at the first timer period, intervalMs
|
||||
t.mock.timers.tick(ACCESS_SWEEP_MS - 1);
|
||||
assert.equal(receive(), null, 'before the first period');
|
||||
t.mock.timers.tick(1);
|
||||
assert.equal(receive(), 'not-holder', 'at the first period');
|
||||
assert.equal(receive(), 'thread-revoked', 'at the first period');
|
||||
// After close the timer is gone: the sweep is never called again.
|
||||
let sweeps = 0;
|
||||
b.accessSweep = () => void sweeps++;
|
||||
|
||||
@@ -293,7 +293,7 @@ it never touches the disk and isn't in the environment or argv.
|
||||
|
||||
| Exit | Meaning | Launch end reason |
|
||||
|---|---|---|
|
||||
| 0 | stopped: by a signal, or the launcher's idle stop or land-relaunch released the claim (`launcher-released`) | `stopped` |
|
||||
| 0 | stopped: by a signal, the launcher's idle stop or land-relaunch released the claim (`launcher-released`), or the access sweep ended a lost user thread's claim (`thread-revoked`, row 306) | `stopped` |
|
||||
| 2 | bad run directory or stdin | `failed` |
|
||||
| 20 | founder credentials | `founder-credentials` |
|
||||
| 21 | `role.claim` refused | `claim-refused` |
|
||||
|
||||
@@ -56,6 +56,9 @@ const SESSION_GONE = new Set(["unauthenticated", "not-holder", "run-ended"]);
|
||||
// Row 302 (#1809): the launcher's idle stop or land-relaunch released the claim and will stop the run.
|
||||
// That is a stop, not a lost session, so the run exits stopped even when its call lands before the signal.
|
||||
const LAUNCHER_RELEASED = "launcher-released";
|
||||
// Row 306 (#1813): the access sweep ended the claim of a thread whose user lost the grant, and the
|
||||
// launcher will stop the run. That is a stop too, when the run's call lands before the signal.
|
||||
const THREAD_REVOKED = "thread-revoked";
|
||||
const BODY_MAX = 32000;
|
||||
const DEFAULTS = Object.freeze({ turnTimeout: 900, pollInterval: 2000, brokerRetries: 30 });
|
||||
|
||||
@@ -485,12 +488,13 @@ export async function main(runDir, { stdin = process.stdin, env = process.env, l
|
||||
|
||||
let code = EXIT.stopped;
|
||||
let failures = 0;
|
||||
// Whether a broker refusal ends the run, and how: a launcher release stops it, a gone session is 22.
|
||||
// Whether a broker refusal ends the run, and how: a launcher release or an access revoke stops it,
|
||||
// a gone session is 22. Either stop already ended the claim, so `released` skips the exit release.
|
||||
// The reply site has logged its failure already, so it passes say=false.
|
||||
let released = false;
|
||||
const ended = (err, say = true) => {
|
||||
if (err.code === LAUNCHER_RELEASED) {
|
||||
log("runner: session released by the launcher");
|
||||
if (err.code === LAUNCHER_RELEASED || err.code === THREAD_REVOKED) {
|
||||
log(err.code === LAUNCHER_RELEASED ? "runner: session released by the launcher" : "runner: thread revoked by the access file");
|
||||
released = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user