Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0d7ac8c
fix: reconcile keepalive authority receipts with exact worker evidence
Sep 27, 2026
b6469d1
docs: rebaseline intentional keepalive reporter topology
Sep 27, 2026
95f187f
fix: wrap worker evidence API and format authority tests
Sep 27, 2026
8d26b59
test: scope mark-running credential assertion to summary step
Sep 27, 2026
7ea5249
Recover missing reporter PR target from authority attempt
Sep 27, 2026
b8ee068
Preserve authority head in recovered reporter summary
Sep 27, 2026
91d9457
Format authority delivery assertion
Sep 27, 2026
4176c45
Limit authority target scan to unassociated runs
Sep 27, 2026
92eeb50
Index authority attempts before reservation for direct recovery
Sep 27, 2026
2680eb1
Keep uncertain worker evidence retryable
Sep 27, 2026
b80c5a5
Use exact attempt-number parameter for worker job evidence
Sep 27, 2026
d40168f
Use registry worker evidence and pass cleanup fingerprint
Sep 27, 2026
d7dcb59
Classify worker execution against originating registry revision
Sep 27, 2026
32be636
fix(keepalive): require exact worker evidence for receipt recovery
Sep 27, 2026
50ff464
fix(keepalive): classify unassociated dispatches before reporter reco…
Sep 27, 2026
9de330c
fix(keepalive): guard reporter API calls and pin consumer action
Sep 27, 2026
e74b356
fix(keepalive): pin reporter classification action
Sep 27, 2026
4481165
fix(keepalive): bypass reporter setup for ordinary skips
Sep 27, 2026
6c314e2
fix(keepalive): refresh expired released challenge windows safely
Sep 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 162 additions & 1 deletion .github/scripts/__tests__/keepalive-authority-state.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ const {
confirmChallenge,
consumeChallenge,
finalizeChallenge,
findAuthorityPrForAttempt,
prepareChallenge,
readAuthorityState,
reconcileFailedAuthorityAttempt,
releasePreparedChallenge,
reopenUnconfirmedChallenge,
} = require('../keepalive_authority_state');
Expand All @@ -29,8 +31,10 @@ function status(code) {
function fakeGitHub() {
let branch = false;
let content = null;
const attemptFiles = new Map();
let beforePut = null;
let afterPut = null;
let afterIndexPut = null;
let prHead = headSha;
let prLabels = ['agent:needs-attention'];
let prUnavailable = false;
Expand All @@ -51,6 +55,21 @@ function fakeGitHub() {
branch = true;
return {};
}
if (path.includes('/contents/.github/keepalive-authority-attempts/')) {
const key = path.split('/').pop().split('?')[0];
if (method === 'GET') {
if (!attemptFiles.has(key)) throw status(404);
const encoded = attemptFiles.get(key);
return { sha: crypto.createHash('sha1').update(encoded).digest('hex'),
encoding: 'base64', content: encoded };
}
if (method === 'PUT') {
if (attemptFiles.has(key)) throw status(422);
attemptFiles.set(key, body.content);
if (afterIndexPut) await afterIndexPut();
return {};
}
}
if (!path.includes('/contents/.github/keepalive-authority/42.json')) {
throw new Error(`Unexpected ${method} ${path}`);
}
Expand All @@ -72,9 +91,14 @@ function fakeGitHub() {
request,
setBeforePut(fn) { beforePut = fn; },
setAfterPut(fn) { afterPut = fn; },
setAfterIndexPut(fn) { afterIndexPut = fn; },
setPrHead(sha) { prHead = sha; },
setPrLabels(labels) { prLabels = labels; },
setPrUnavailable(value) { prUnavailable = value; },
corruptAttemptIndex() {
const key = `${crypto.createHash('sha256').update(ownerAttempt).digest('hex')}.json`;
attemptFiles.set(key, Buffer.from('{invalid json').toString('base64'));
},
expireChallenge() {
const state = JSON.parse(Buffer.from(content, 'base64').toString('utf8'));
state.expires_at = new Date(Date.now() - 1).toISOString();
Expand Down Expand Up @@ -177,6 +201,27 @@ test('preparation is non-authorizing and can be conditionally released before re
assert.equal((await readAuthorityState(api.request, repository, prNumber)).state.status, 'available');
});

test('an exact release retry is idempotent but cannot release a replacement receipt', async () => {
const api = fakeGitHub();
const state = await beginChallenge({
request: api.request, repository, prNumber, defaultBranch: 'main',
fingerprint, headSha, ...boundary(),
});
const signed = claim(state);
const options = { request: api.request, repository, prNumber, claim: signed,
ownerAttempt, provider: 'codex', headSha };
assert.equal((await prepareChallenge(options)).prepared, true);
assert.equal((await releasePreparedChallenge(options)).released, true);
assert.equal((await releasePreparedChallenge(options)).reason,
'challenge-preparation-already-released');
assert.equal((await prepareChallenge(options)).reason, 'attempt-already-settled');
const replacement = { ...options, ownerAttempt: 'owner/repo:101:1' };
assert.equal((await prepareChallenge(replacement)).prepared, true);
assert.equal((await releasePreparedChallenge({ ...options,
ownerAttempt: 'owner/repo:999:1' })).released, false);
assert.equal((await readAuthorityState(api.request, repository, prNumber)).state.status, 'prepared');
});

test('a generation is never reused for a different originating head', async () => {
const api = fakeGitHub();
const first = await beginChallenge({
Expand Down Expand Up @@ -309,14 +354,105 @@ test('workflow reporter reopens with the persisted claim and failed run identity
request: api.request, repository, prNumber, claim: null,
ownerAttempt: 'owner/repo:101:1', provider: 'codex', headSha,
})).status, 'uncertain');
const reopened = await reopenUnconfirmedChallenge({
assert.equal((await reopenUnconfirmedChallenge({
request: api.request, repository, prNumber, claim: null,
ownerAttempt, provider: 'codex', headSha,
})).status, 'uncertain');
const reopened = await reopenUnconfirmedChallenge({
request: api.request, repository, prNumber, claim: null,
ownerAttempt, provider: 'codex', headSha, workerEvidence: 'not-started',
});
assert.equal(reopened.status, 'reopened');
assert.equal(reopened.state.consumed_claim, null);
});

test('failed-run reconciliation is independent of summary state and requires positive non-start', async () => {
const api = fakeGitHub();
assert.equal(await findAuthorityPrForAttempt({ request: api.request, repository, ownerAttempt }), null);
const state = await beginChallenge({
request: api.request, repository, prNumber, defaultBranch: 'main',
fingerprint, headSha, ...boundary(),
});
const signed = claim(state);
assert.equal((await prepareChallenge({
request: api.request, repository, prNumber, claim: signed,
ownerAttempt, provider: 'codex', headSha,
})).prepared, true);
const args = { request: api.request, repository, prNumber, ownerAttempt };
assert.equal((await reconcileFailedAuthorityAttempt({
...args, workerEvidence: 'unknown',
})).status, 'execution-not-disproved');
assert.equal((await readAuthorityState(api.request, repository, prNumber)).state.status, 'prepared');
assert.equal((await reconcileFailedAuthorityAttempt({
...args, ownerAttempt: 'owner/repo:101:1', workerEvidence: 'not-started',
})).status, 'attempt-not-current');
const released = await reconcileFailedAuthorityAttempt({
...args, workerEvidence: 'not-started',
});
assert.equal(released.status, 'released');
assert.equal(released.state.status, 'available');
assert.equal((await readAuthorityState(api.request, repository, prNumber)).state.status, 'available');
const target = await findAuthorityPrForAttempt({
request: api.request, repository, ownerAttempt,
});
assert.equal(target.prNumber, prNumber);
assert.equal(target.state.status, 'available');
});

test('a corrupted direct attempt index denies finalization and missing-PR recovery', async () => {
const api = fakeGitHub();
const state = await beginChallenge({
request: api.request, repository, prNumber, defaultBranch: 'main',
fingerprint, headSha, ...boundary(),
});
const signed = claim(state);
const args = { request: api.request, repository, prNumber,
claim: signed, ownerAttempt, provider: 'codex', headSha };
assert.equal((await prepareChallenge(args)).prepared, true);
api.corruptAttemptIndex();
assert.equal((await finalizeChallenge(args)).reason, 'attempt-index-unavailable');
await assert.rejects(findAuthorityPrForAttempt({
request: api.request, repository, ownerAttempt,
}), /Malformed authority attempt index/);
assert.equal((await readAuthorityState(api.request, repository, prNumber)).state.status, 'prepared');
});

test('lost index-create response denies this preparation but permits exact later retry', async () => {
const api = fakeGitHub();
const state = await beginChallenge({
request: api.request, repository, prNumber, defaultBranch: 'main',
fingerprint, headSha, ...boundary(),
});
const args = { request: api.request, repository, prNumber,
claim: claim(state), ownerAttempt, provider: 'codex', headSha };
api.setAfterIndexPut(() => { throw status(503); });
assert.equal((await prepareChallenge(args)).reason, 'attempt-index-uncertain');
assert.equal((await readAuthorityState(api.request, repository, prNumber)).state.status, 'available');
api.setAfterIndexPut(null);
assert.equal((await prepareChallenge(args)).prepared, true);
assert.equal((await finalizeChallenge(args)).granted, true);
});

test('consumed receipt only reopens for its exact attempt with a proven unstarted worker', async () => {
const api = fakeGitHub();
const state = await beginChallenge({
request: api.request, repository, prNumber, defaultBranch: 'main',
fingerprint, headSha, ...boundary(),
});
assert.equal((await consumeChallenge({
request: api.request, repository, prNumber, claim: claim(state),
ownerAttempt, provider: 'codex', headSha,
})).granted, true);
const args = { request: api.request, repository, prNumber, ownerAttempt };
assert.equal((await reconcileFailedAuthorityAttempt({
...args, workerEvidence: 'started',
})).status, 'execution-not-disproved');
assert.equal((await readAuthorityState(api.request, repository, prNumber)).state.status, 'consumed');
assert.equal((await reconcileFailedAuthorityAttempt({
...args, workerEvidence: 'not-started',
})).status, 'reopened');
});

test('unavailable PR read after confirmation preserves the confirmed challenge', async () => {
const api = fakeGitHub();
const state = await beginChallenge({
Expand All @@ -342,6 +478,31 @@ test('unavailable PR read after confirmation preserves the confirmed challenge',
assert.equal((await readAuthorityState(api.request, repository, prNumber)).state.status, 'confirmed');
});

test('confirmed receipt never reopens after a same-head hard label removal', async () => {
const api = fakeGitHub();
const state = await beginChallenge({
request: api.request, repository, prNumber, defaultBranch: 'main',
fingerprint, headSha, ...boundary(),
});
const signed = claim(state);
assert.equal((await consumeChallenge({
request: api.request, repository, prNumber, claim: signed,
ownerAttempt, provider: 'codex', headSha,
})).granted, true);
api.setPrLabels(['needs-human']);
assert.equal(await confirmChallenge({
request: api.request, repository, prNumber, claim: signed,
ownerAttempt, provider: 'codex', headSha,
}), true);
api.setPrLabels(['agent:needs-attention']);
const recovered = await reopenUnconfirmedChallenge({
request: api.request, repository, prNumber, claim: signed,
ownerAttempt, provider: 'codex', headSha,
});
assert.equal(recovered.status, 'uncertain');
assert.equal((await readAuthorityState(api.request, repository, prNumber)).state.status, 'confirmed');
});

test('expired consumed generation can be replaced after confirmation was omitted', async () => {
const api = fakeGitHub();
const first = await beginChallenge({
Expand Down
8 changes: 4 additions & 4 deletions .github/scripts/__tests__/keepalive-loop.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3125,7 +3125,7 @@ test('updateKeepaliveLoopSummary does not consume authority ledger when agent ex
assert.match(updateAction.body, /"disposition":"automation-retry"/);
});

test('failed workflow reporter reopens the persisted authority claim for its owning run', async () => {
test('summary input without exact job evidence cannot reopen a consumed authority receipt', async () => {
const authSummary = 'Missing token ACTIONS_BOT_PAT for GitHub API repository dispatch.';
const boundary = buildAuthorityChallengeEvidence({ agentSummary: authSummary });
const existingState = formatStateComment({
Expand Down Expand Up @@ -3172,12 +3172,12 @@ test('failed workflow reporter reopens the persisted authority claim for its own

const updateAction = github.actions.filter((action) => action.type === 'update').at(-1);
const attention = parseStateComment(updateAction.body).data.attention;
assert.equal(attention.disposition, 'challenge-due');
assert.equal(attention.disposition, 'automation-retry');
assert.equal(attention.owner, 'automation');
assert.notEqual(attention.generation, 'c'.repeat(64));
assert.equal(attention.generation, '');
assert.equal(
github.actions.filter((action) => action.type === 'authority-ledger-write').length,
1,
0,
);
});

Expand Down
100 changes: 100 additions & 0 deletions .github/scripts/__tests__/keepalive-reporter-applicability.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
'use strict';

const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const { classifyReporterRun } = require('../keepalive_reporter_applicability.js');

const root = path.resolve(__dirname, '../../..');
const head = 'a'.repeat(40);
const run = { id: 12345, run_attempt: 2, head_sha: head, pull_requests: [] };
const sources = [
'.github/workflows/agents-keepalive-loop.yml',
'templates/consumer-repo/.github/workflows/agents-81-gate-followups.yml',
];

function setup(source, title, overrides = {}) {
const pathInRepo = source.replace(/^templates\/consumer-repo\//, '');
const producer = fs.readFileSync(path.join(root, source));
let reads = 0;
const github = {
rest: {
actions: { getWorkflowRun: async () => {
reads += 1;
return { data: { ...run, event: 'workflow_dispatch', path: pathInRepo,
display_title: title, ...overrides } };
} },
repos: { getContent: async () => {
reads += 1;
return { data: { encoding: 'base64', content: producer.toString('base64') } };
} },
},
};
return { github, reads: () => reads };
}

for (const source of sources) {
test(`${source}: ordinary unassociated dispatch skips only after absent index`, async () => {
const { github, reads } = setup(source, 'keepalive-dispatch/v1 ordinary');
let lookups = 0;
const result = await classifyReporterRun({ github, owner: 'stranske', repo: 'repo', run,
lookupTarget: async () => { lookups += 1; return null; } });
assert.deepEqual(result, { status: 'skip' });
assert.equal(lookups, 1);
assert.equal(reads(), 2);
});

test(`${source}: authority candidate without index fails closed`, async () => {
const { github } = setup(source, 'keepalive-dispatch/v1 authority-candidate');
await assert.rejects(classifyReporterRun({ github, owner: 'stranske', repo: 'repo', run,
lookupTarget: async () => null }), /no immutable attempt index/);
});

test(`${source}: missing or misleading title cannot become ordinary`, async () => {
for (const title of ['', 'ordinary', 'keepalive-dispatch/v2 ordinary']) {
const { github } = setup(source, title);
await assert.rejects(classifyReporterRun({ github, owner: 'stranske', repo: 'repo', run,
lookupTarget: async () => null }), /no versioned classification/);
}
});
}

test('verified index wins over an ordinary title and needs no title lookup', async () => {
const { github, reads } = setup(sources[0], 'keepalive-dispatch/v1 ordinary');
const result = await classifyReporterRun({ github, owner: 'stranske', repo: 'repo', run,
lookupTarget: async () => ({ prNumber: 42 }) });
assert.deepEqual(result, { status: 'continue', prNumber: 42 });
assert.equal(reads(), 0);
});

test('unavailable or corrupt index lookup never becomes an ordinary skip', async () => {
const { github } = setup(sources[0], 'keepalive-dispatch/v1 ordinary');
await assert.rejects(classifyReporterRun({ github, owner: 'stranske', repo: 'repo', run,
lookupTarget: async () => { throw new Error('index 503'); } }), /index 503/);
});

test('wrong originating workflow or event cannot claim ordinary routing', async () => {
for (const override of [{ path: '.github/workflows/other.yml' }, { event: 'push' },
{ run_attempt: 3 }, { head_sha: 'b'.repeat(40) }]) {
const { github } = setup(sources[0], 'keepalive-dispatch/v1 ordinary', override);
await assert.rejects(classifyReporterRun({ github, owner: 'stranske', repo: 'repo', run,
lookupTarget: async () => null }), /no verified dispatch classification/);
}
});

test('originating revision without the classification contract fails closed', async () => {
const { github } = setup(sources[0], 'keepalive-dispatch/v1 ordinary');
github.rest.repos.getContent = async () => ({ data: {
encoding: 'base64', content: Buffer.from('name: Agents Keepalive Loop\n').toString('base64'),
} });
await assert.rejects(classifyReporterRun({ github, owner: 'stranske', repo: 'repo', run,
lookupTarget: async () => null }), /lacks the dispatch classification contract/);
});

test('associated runs avoid the unassociated locator altogether', async () => {
const result = await classifyReporterRun({ github: {}, owner: 'stranske', repo: 'repo',
run: { ...run, pull_requests: [{ number: 42 }] },
lookupTarget: async () => { throw new Error('unexpected lookup'); } });
assert.deepEqual(result, { status: 'continue' });
});
Loading
Loading