fix: preserve per-run locator fallback
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user