bus: row 236 r3, a revoke during a settle whoami ends revoked in the 401 and token-unknown branches (Dewey N5, #1742)

This commit is contained in:
jason.woltje committed 2026-10-21 22:09:55 -05:00
1 parent 77dd43c9e3
commit 7965efd0fb
3 files changed
+41 -2

No files matched your search

+7
View File
@@ -74,6 +74,10 @@ Each is a reading of the design where it left room. Dewey may rule otherwise.
another id with `gitea-user-foreign`, any other status leaves the entry. Skips go in `grant.swept` and
the sweep answer as `skipped: [{agent, user, id, reason}]`; a skipped entry is not left, so the next
sweep does not retry it.
20. r3, Dewey N5 (dewey-0783): the settle's 401 branch and its grant-token-unknown branch take the same
revoke check. A revoke during the whoami ends the grant `revoked`, not `grant-lost`, and the
token-unknown branch neither holds the agent again nor writes `grant-token-unknown`, so no start
names a file the revoke unlinked.
## Named for DEFERRED (r2, Dewey N1-N4)
@@ -126,3 +130,6 @@ r2 tests, each red on 8bf442be9 by reading:
interrupted settled:back` after the revoke deleted it.
- B2: "X-G14: a sweep retries a fire only for the user it fired: a remade or gone user is skipped and
named". On 8bf442be9 the retry fires the remade user (token list, delete, PATCH) and has no `skipped`.
- N5 (r3): "X-G11: a revoke during a settle whoami that finds a foreign token or a 401 ends it revoked, not
held or lost". On 77dd43c9e the branches end `grant-token-unknown` (the agent back in tokenUnknown) and
`grant-lost`.
+5 -2
View File
@@ -1808,13 +1808,16 @@ export class Broker {
try {
this.#giteaKeys.unregister(business, agent);
} catch {}
this.#store.transaction(() => {
return this.#store.transaction(() => {
if (revoked()) return 'revoked';
this.#store.run('DELETE FROM gitea_grants WHERE business=? AND agent=?', business, agent);
this.#giteaFailed(business, decision, agent, 'grant-lost', { user, ...(serviceLeft.length ? { serviceLeft } : {}) });
return 'grant-lost';
});
return 'grant-lost';
}
if (who.status === 200 && who.id !== null) {
// A revoke during the whoami unlinked the file: holding it again would name a file that is gone.
if (this.#store.transaction(revoked)) return 'revoked';
// A live token this grant didn't mint: memory drops it, and the file, the record and both tokens stay.
try {
this.#giteaKeys.hold(business, agent);
+29
View File
@@ -991,6 +991,35 @@ test('X-G11: a revoke while a sweep settles a rotation, forward or back, ends it
}
});
test('X-G11: a revoke during a settle whoami that finds a foreign token or a 401 ends it revoked, not held or lost', async (t) => {
for (const kind of ['grant-token-unknown', 'grant-lost']) {
const f = await setup(t);
const { id } = await openRotation(f);
// A token this grant did not mint, or one Gitea does not know.
const file = kind === 'grant-token-unknown' ? f.gitea.addToken(USER, 'foreign', SEAT_SCOPES).sha1 : 'f'.repeat(40);
writeFileSync(f.tokenFile, file + '\n');
let release;
const held = new Promise((r) => (release = r));
const seat = (e) => e.method === 'GET' && e.path === '/api/v1/token' && e.as !== 'mosaic-grant';
f.gitea.rule(seat, async () => {
await held;
return { pass: true };
});
const sweep = f.call('human', 'grants.sweep', { service: 'gitea' });
await until(() => f.gitea.requests.some(seat));
const revoke = f.call('human', 'grants.revoke', { service: 'gitea', agent: 'coder', reason: 'stop' });
release();
await revoke;
const swept = await sweep;
await f.broker.giteaIdle();
assert.deepEqual(swept.settled, [{ decision: id, outcome: 'revoked' }], kind);
assert.equal(f.ended(id).code, 'revoked');
assert.equal(f.grantEvents().some((e) => e.grantDecision === id && e.code === kind), false);
assert.deepEqual([...tokenUnknown(f.store, 'demo').keys()], []);
assert.equal(f.record(), undefined);
}
});
test('X-G12: a token that reaches wider than its forks fails with the repository named', async (t) => {
const collab = await setup(t);
collab.gitea.addRepo('other', 'secret', { collaborators: [USER] });