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:
ImBenji
2026-09-08 01:01:36 +01:00
co-authored by Claude Opus 5
parent ec29e64e96
commit b246bd9d4b
5 changed files with 109 additions and 17 deletions
+14 -2
View File
@@ -25,6 +25,13 @@ const { STRATEGY_VERSION, PROMPT_VERSION } = require("../workers/replayWorker");
const INTELLIGENCE = process.env.INTELLIGENCE_DB || "/data/intelligence.sqlite";
// When duriin-api-replay-1 restarted onto the prompt it runs today. Not the
// commit timestamp, which is five minutes later and would have been wrong.
// Replay was between daily budgets across the restart, so there is an eight
// hour hole in predictions around it and every candidate split inside that
// hole partitions the data identically.
const SPLIT = "2026-09-04 19:17:43";
function options() {
const argv = process.argv.slice(2);
const out = {};
@@ -61,7 +68,7 @@ function articleIdOf(raw, predictionId) {
function main() {
const opts = options();
const parentId = Number(opts.parent || 1);
const split = String(opts.split || "2026-09-04 19:30");
const split = String(opts.split || SPLIT);
const commit = opts.commit === true;
const model = String(opts.model || process.env.OPEN_ROUTER_LLM_MODEL || "");
@@ -102,6 +109,11 @@ function main() {
console.log(` new run would be ${STRATEGY_VERSION}/${PROMPT_VERSION} on ${model || "(model from config at run time)"}`);
if (!articles.size) throw new Error("no evaluation articles, refusing to create an empty run");
// inheriting the parent's model here is how run 1 ended up labelled qwen for
// predictions deepseek made. a label nobody set is worse than a failure.
if (commit && !model) {
throw new Error("pass --model, or set OPEN_ROUTER_LLM_MODEL. refusing to guess what will run this");
}
if (!commit) {
console.log("\ndry run, nothing written. pass --commit to apply.");
@@ -114,7 +126,7 @@ function main() {
const created = db.prepare(`
INSERT INTO autonomy_replay_runs (watermark_at, strategy_version, prompt_version, coordinator_model, parent_run_id, feedback_brief)
VALUES (?, ?, ?, ?, ?, ?)
`).run(parent.watermark_at, STRATEGY_VERSION, PROMPT_VERSION, model || parent.coordinator_model, parentId, brief);
`).run(parent.watermark_at, STRATEGY_VERSION, PROMPT_VERSION, model, parentId, brief);
const runId = created.lastInsertRowid;
const insert = db.prepare("INSERT OR IGNORE INTO autonomy_replay_run_articles (run_id, article_id, effective_at) VALUES (?, ?, ?)");
for (const [articleId, effectiveAt] of articles) insert.run(runId, articleId, effectiveAt);