fix: preserve per-run locator fallback

This commit is contained in:
AgentRun Codex
2026-07-13 02:17:17 +00:00
parent 59a4862d43
commit bb3dca89af
2 changed files with 34 additions and 1 deletions
+1 -1
View File
@@ -214,7 +214,7 @@ function verifySnapshotReferentialIntegrity(snapshot: MonitorSqliteSnapshot): vo
const reportSha256 = stringField(row, ["report_json_sha256", "reportJsonSha256"]);
const locatorRows = tableRows(snapshot.tables, ["artifact_locators", "locators"]);
const expectedLocators = locatorRows.filter((locator) => stringField(locator, ["run_id"]) === runId).map((locator) => locatorBindingFromRow(locator, snapshot));
if (declaredByRunId.has(runId) || manifest.artifactCount !== (rowCount ?? manifest.locators.length) || manifest.stateDir !== stateDir || manifest.reportSha256 !== reportSha256 || (stateDir.length > 0 && manifest.ownerPvc === null) || manifest.locators.some((locator) => locator.ownerPvc !== manifest.ownerPvc || locator.stateDir !== stateDir) || manifest.locators.length !== manifest.artifactCount || (locatorRows.length > 0 && !sameLocatorBindings(manifest.locators, expectedLocators))) throw Object.assign(new Error("snapshot normal artifact manifest does not match run identity"), { code: "monitor-migration-snapshot-referential-invalid" });
if (declaredByRunId.has(runId) || manifest.artifactCount !== (rowCount ?? manifest.locators.length) || manifest.stateDir !== stateDir || manifest.reportSha256 !== reportSha256 || (stateDir.length > 0 && manifest.ownerPvc === null) || manifest.locators.some((locator) => locator.ownerPvc !== manifest.ownerPvc || locator.stateDir !== stateDir) || manifest.locators.length !== manifest.artifactCount || (expectedLocators.length > 0 && !sameLocatorBindings(manifest.locators, expectedLocators))) throw Object.assign(new Error("snapshot normal artifact manifest does not match run identity"), { code: "monitor-migration-snapshot-referential-invalid" });
verifyReportLocator(row, manifest.locators);
continue;
}
@@ -321,6 +321,39 @@ test("normal report locator hash cannot drift from its SQLite row after a rehash
}
});
test("mixed per-run locator rows keep fallback runs valid and reject rebinds", async () => {
const directory = mkdtempSync(join(tmpdir(), "monitor-mixed-locator-rows-"));
try {
const sqlitePath = join(directory, "index.sqlite");
const reports = new Map([["a", Buffer.from("report A\n")], ["b", Buffer.from("report B\n")]]);
const hashes = new Map([...reports].map(([runId, report]) => [runId, `sha256:${createHash("sha256").update(report).digest("hex")}`]));
for (const [runId, report] of reports) {
const reportPath = join(directory, ".state", runId, "analysis", "report.json");
mkdirSync(join(reportPath, ".."), { recursive: true });
writeFileSync(reportPath, report);
}
const database = new Database(sqlitePath);
database.run("CREATE TABLE runs (id TEXT, state_dir TEXT, artifact_count INTEGER, report_json_sha256 TEXT, updated_at TEXT)");
database.run("CREATE TABLE artifact_locators (run_id TEXT, owner_pvc TEXT, state_dir TEXT, relative_path TEXT, sha256 TEXT, size_bytes INTEGER)");
for (const [runId, report] of reports) database.run("INSERT INTO runs VALUES (?, ?, ?, ?, ?)", [runId, `.state/${runId}`, 1, hashes.get(runId), "2026-07-12T00:00:00.000Z"]);
database.run("INSERT INTO artifact_locators VALUES (?, ?, ?, ?, ?, ?)", ["a", "fixture-pvc", ".state/a", "analysis/report.json", hashes.get("a"), reports.get("a")?.byteLength]);
database.close();
const source = { sentinelId: "fixture", node: "NC01", lane: "v03", path: sqlitePath, runtimeConfigRef: "fixture#runtime", stateRoot: directory, ownerPvc: "fixture-pvc", artifactSources: [{ ownerPvc: "fixture-pvc", mountPath: directory }] };
const snapshot = exportMonitorSqliteSnapshot(source, join(directory, "snapshot.json"));
assert.equal(verifyMonitorSqliteSnapshot(snapshot).ok, true);
const manifestA = snapshot.artifactManifests.find((manifest) => manifest.runId === "a")!;
const manifestB = snapshot.artifactManifests.find((manifest) => manifest.runId === "b")!;
const reboundUnsigned = { ...snapshot, artifactManifests: snapshot.artifactManifests.map((manifest) => manifest.runId === "a" ? { ...manifest, locators: manifestB.locators } : manifest) };
assert.equal(verifyMonitorSqliteSnapshot({ ...reboundUnsigned, manifestContentHash: monitorSnapshotContentHash(reboundUnsigned) }).ok, false);
const swappedUnsigned = { ...snapshot, artifactManifests: [{ ...manifestB, runId: "a" }, { ...manifestA, runId: "b" }] };
assert.equal(verifyMonitorSqliteSnapshot({ ...swappedUnsigned, manifestContentHash: monitorSnapshotContentHash(swappedUnsigned) }).ok, false);
writeFileSync(join(directory, ".state", "b", "added-after-export.bin"), "added\n");
await assert.rejects(() => importMonitorSqliteSnapshot(snapshot, createInMemoryMonitorCentralStore()), (error: unknown) => (error as { code?: string }).code === "monitor-migration-frozen-artifact-invalid");
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
test("artifact count without a registry-backed locator fails closed", async () => {
const directory = mkdtempSync(join(tmpdir(), "monitor-artifact-count-"));
try {