fix: a recovered replay job can no longer hijack the active run
leaseNextJob hands back any pending replay_article job, it has no idea about runs, and the worker was attributing whatever came back to whichever run was active. One recovered dead letter from run 1 would have been stamped with run 2's id, given run 2's feedback brief, and dragged run 2's cursor to wherever that old article sits in the archive. A pinned run would then decide its set was finished after a couple of articles. There are 139 dead letters and they are built to recover, so this was not hypothetical. The idempotency key already says which run enqueued the job. Ask it. Also: refuse to inherit the parent's model label when starting a run. Inheriting is exactly how run 1 came to be labelled qwen for predictions deepseek made. The split moves to the replay container's actual restart time rather than the commit timestamp five minutes later. Verified the running container really does have the instrument rules, the de-anchoring and the enum before trusting it as the boundary. It makes no difference to the partition, there are no replay predictions at all between 15:57 and midnight that day, but the boundary should be the thing that actually changed the prompt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnNxwxfXSbeNtjvtz5gayb
This commit is contained in:
+28
-1
@@ -14,7 +14,7 @@ const { createOrderIntent } = require('../src/autonomy/orderIntents');
|
||||
const { enqueueCoordinatorEvent, reconcileArchiveBatch, reconcileLiveBatch, isTransientCoordinatorFailure } = require('../workers/autonomyWorker');
|
||||
const { buildGraphContext } = require('../src/autonomy/graphContext');
|
||||
const { buildPrompt } = require('../workers/coordinatorWorker');
|
||||
const { scheduleNext, replayPrompt } = require('../workers/replayWorker');
|
||||
const { scheduleNext, replayPrompt, runForJob } = require('../workers/replayWorker');
|
||||
const { refreshHistoricalCalibration, createDecisions, ensureCalibrationColumns } = require('../workers/calibrationWorker');
|
||||
|
||||
test('autonomy schema and leased jobs are restart-safe', () => {
|
||||
@@ -183,6 +183,33 @@ test('the feedback brief reaches the prompt and stays out of it when empty', ()
|
||||
assert.ok(!replayPrompt(article).includes('CALIBRATION FEEDBACK'));
|
||||
});
|
||||
|
||||
test('a leftover job from an older run cannot hijack the active run', () => {
|
||||
const db = new Database(':memory:');
|
||||
initAutonomySchema(db);
|
||||
const first = db.prepare(`
|
||||
INSERT INTO autonomy_replay_runs (watermark_at, strategy_version, prompt_version, coordinator_model, status)
|
||||
VALUES ('2020-01-09T00:00:00Z', 'autonomy-1', 'replay-coordinator-1', 'old-model', 'paused')
|
||||
`).run().lastInsertRowid;
|
||||
const second = db.prepare(`
|
||||
INSERT INTO autonomy_replay_runs (watermark_at, strategy_version, prompt_version, coordinator_model, feedback_brief)
|
||||
VALUES ('2020-01-09T00:00:00Z', 'autonomy-2', 'replay-coordinator-2', 'new-model', 'you over-call positive')
|
||||
`).run().lastInsertRowid;
|
||||
const active = db.prepare('SELECT * FROM autonomy_replay_runs WHERE id=?').get(second);
|
||||
|
||||
// a recovered dead letter from run 1, leased while run 2 is the active one
|
||||
const owner = runForJob(db, { id: 9, idempotency_key: `replay:${first}:article:4242` }, active);
|
||||
assert.equal(owner.id, first, 'the job belongs to the run that enqueued it');
|
||||
assert.equal(owner.feedback_brief, null, 'and it must not be handed run 2 brief');
|
||||
assert.equal(owner.prompt_version, 'replay-coordinator-1');
|
||||
|
||||
const own = runForJob(db, { id: 10, idempotency_key: `replay:${second}:article:1` }, active);
|
||||
assert.equal(own.id, second);
|
||||
|
||||
// unattributable jobs fall back rather than being dropped, but loudly
|
||||
assert.equal(runForJob(db, { id: 11, idempotency_key: null }, active).id, second);
|
||||
assert.equal(runForJob(db, { id: 12, idempotency_key: 'replay:999:article:1' }, active).id, second);
|
||||
});
|
||||
|
||||
test('calibration and policy abstain on insufficient evidence', () => {
|
||||
const calibration = calibrateOutcomes([
|
||||
{ excess_return: 0.02, direction_correct: 1 },
|
||||
|
||||
Reference in New Issue
Block a user