Wake the existing PM when validated evidence arrives after its first notice, including overlapping recovery across retained request history. Preserve durable deduplication and current authority checks. Put the exact verification record and verdict contract in the first scoped review request. Carry linked owner-request provenance into runner headers and expose the existing requirement-record example to planning tools. Implementation: Codex Terra initial repair; Codex Sol lead R1 correction, regressions and developer documentation. Independent R2 APPROVE, 115 focused checks. Matching final local coverage proves 4,333 distinct required cases; the intentional FEDR7 skip is covered by its standard scratch wrapper. Reviewed patch: 18ff6455e7ab69bc55cef88845f86189ed5f67b114ad7996f714a35ca1a90239 Source manifest: 6daf0668101bbad030c45411304d0d049de5e4da32bdcb03c40095ed1c63128f
66 lines
3.5 KiB
JavaScript
66 lines
3.5 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createRequestLifecycle } from '../src/request-lifecycle.mjs';
|
|
|
|
function recoveryFixture({ retained, target = 0, signalAt = null }) {
|
|
const requests = Array.from({ length: retained }, (_, i) => ({
|
|
business: 'demo', request: `r${i}`, revision: 1, source_message: 'source',
|
|
phase: i === target ? 'implementation' : 'cancelled', pm: 'pm', project: 'factory',
|
|
wait: null, question: null, plan_digest: 'plan',
|
|
selection: { bindings: [{ digest: 'plan', plan: { staff: { coder: 'coder', reviewer: 'reviewer', chief: 'chief' } } }] },
|
|
spec_commit: null, candidate: null, snapshot: 'snapshot', tasks: ['vikunja:2/1'], evidence: [],
|
|
}));
|
|
const receipts = [], messages = [], inspected = new Set();
|
|
let lifecycle, evidence = false, signalled = false;
|
|
const journal = {
|
|
listRequests: () => requests.map(({ business, request }) => ({ business, request })),
|
|
readRequest(business, request) {
|
|
inspected.add(request);
|
|
if (!signalled && request === `r${signalAt}`) {
|
|
signalled = true; evidence = true;
|
|
lifecycle.recover({ limit: 64 });
|
|
}
|
|
return structuredClone(requests.find(row => row.business === business && row.request === request));
|
|
},
|
|
effects: (business, request) => receipts.filter(row => row.business === business && row.request === request),
|
|
transaction: fn => fn(),
|
|
prepareEffect(row) {
|
|
const old = receipts.find(r => r.business === row.business && r.request === row.request && r.op === row.op && r.stage === row.stage);
|
|
if (old) return old;
|
|
receipts.push(row); return row;
|
|
},
|
|
finishEffect(row) { receipts.push(row); return row; },
|
|
};
|
|
const services = {
|
|
origin() {}, authorize() {}, raiseDecision() {}, readDecision() {}, project() {},
|
|
continuationContext: (_, role) => ({ run: `${role}-live`, prerequisite: 'stable' }),
|
|
continuationProgress: state => state.request === `r${target}` && evidence
|
|
? { phase: 'review', tasks: state.tasks, evidence: ['validated-push', 'validated-review'] } : null,
|
|
continuationMessage(state, role, body) {
|
|
const id = `m${messages.length}`; messages.push({ id, request: state.request, role, body }); return id;
|
|
},
|
|
};
|
|
lifecycle = createRequestLifecycle({ journal, services });
|
|
return { lifecycle, inspected, wakes: () => messages.filter(m => m.request === `r${target}` && m.role === 'pm').length };
|
|
}
|
|
|
|
for (const [retained, target, signalAt, prime] of [[128, 0, 1, false], [257, 64, 65, true]]) {
|
|
test(`overlapping evidence revisits its request across bounded recovery batches (${retained} retained rows)`, async t => {
|
|
const f = recoveryFixture({ retained, target, signalAt }); t.after(() => f.lifecycle.close());
|
|
if (prime) await f.lifecycle.recover({ limit: 64 });
|
|
await f.lifecycle.recover({ limit: 64 });
|
|
assert.equal(f.wakes(), 2, 'the joined evidence-triggered recovery wakes the live PM without a later timer sweep');
|
|
for (let i = 0; i < 6; i++) await f.lifecycle.recover({ limit: 64 });
|
|
assert.equal(f.wakes(), 2, 'unchanged evidence remains durably deduplicated');
|
|
});
|
|
}
|
|
|
|
test('quiescent recovery keeps its per-call batch bound and advances the cursor', async t => {
|
|
const f = recoveryFixture({ retained: 128 }); t.after(() => f.lifecycle.close());
|
|
await f.lifecycle.recover({ limit: 64 });
|
|
assert.equal(f.inspected.size, 64);
|
|
await f.lifecycle.recover({ limit: 64 });
|
|
assert.equal(f.inspected.size, 128);
|
|
assert.equal(f.wakes(), 1);
|
|
});
|