WI-5 #832: Receipt-challenge protocol (compaction-refresh, milestone 188) (#845)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful

This commit was merged in pull request #845.
This commit is contained in:
2026-07-19 23:18:56 +00:00
parent e522b22fa4
commit 07553ead33
10 changed files with 941 additions and 62 deletions
@@ -1,4 +1,5 @@
import { createHash } from 'node:crypto';
import { chmod, writeFile } from 'node:fs/promises';
import { createConnection, type Socket } from 'node:net';
const DEFAULT_TIMEOUT_MS = 3_000;
@@ -185,3 +186,51 @@ export async function requestBrokerReply<T extends object>(
options,
);
}
export interface ReceiptChallengeCycle {
sessionId: string;
runtimeGeneration: number;
receiptChallenge: string;
receipt: string;
}
export interface ReceiptChallengeReply {
ok: boolean;
code?: string;
state?: 'UNVERIFIED' | 'PENDING_VERIFICATION' | 'PENDING_PROMOTION' | 'VERIFIED';
}
/**
* Complete the shipped begin -> trusted-observer -> consume -> promote path.
* The private fixture is read by the daemon's injected test observer; the
* observation request itself never carries assistant-message content.
*/
export async function observeAndPromoteReceiptChallenge(
socketPath: string,
observerFixturePath: string,
cycle: ReceiptChallengeCycle,
): Promise<ReceiptChallengeReply> {
await writeFile(
observerFixturePath,
`${JSON.stringify({
session_id: cycle.sessionId,
runtime_generation: cycle.runtimeGeneration,
latest_assistant_message: cycle.receipt,
})}\n`,
{ encoding: 'utf8', mode: 0o600 },
);
await chmod(observerFixturePath, 0o600);
const observed = await requestBrokerReply<ReceiptChallengeReply>(socketPath, {
action: 'observe_receipt',
session_id: cycle.sessionId,
runtime_generation: cycle.runtimeGeneration,
receipt_challenge: cycle.receiptChallenge,
});
if (observed.ok !== true || observed.state !== 'PENDING_PROMOTION') return observed;
return await requestBrokerReply<ReceiptChallengeReply>(socketPath, {
action: 'promote_lease',
session_id: cycle.sessionId,
runtime_generation: cycle.runtimeGeneration,
receipt_challenge: cycle.receiptChallenge,
});
}