// SPEC: PJ2026-01060508 Web哨兵 draft-2026-07-12-p17-monitor-central-persistence.
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { Database } from "bun:sqlite";

import { createInMemoryMonitorCentralStore } from "./monitor-central-persistence";
import { exportMonitorSqliteSnapshot, importMonitorSqliteSnapshot, monitorSnapshotContentHash, planMonitorSqliteSources, verifyMonitorSqliteSnapshot, verifyMonitorSqliteSnapshotAgainstStore } from "./monitor-sqlite-migration";
import { buildMonitorTerminalIngest, MonitorIngestFailedError, resolveMonitorTerminalWriter, submitMonitorTerminalIngest } from "./monitor-terminal-ingest";
import { compactWebObserveAnalyzeAnalysisForRaw } from "./hwlab-node/web-probe-observe";

test("terminal ingest payload is immutable and retries bounded failures", async () => {
  const report = { ok: false, findings: [{ id: "blocked" }], updatedAt: "2026-07-12T00:00:00.000Z" };
  const hash = `sha256:${"a".repeat(64)}`;
  const artifact = [{ relativePath: "analysis/report.json", kind: "report", sha256: hash, sizeBytes: 42 }];
  const ingest = buildMonitorTerminalIngest({ sentinelId: "fixture", node: "NC01", lane: "v03", runId: "run-1" }, report, artifact, { ownerNode: "NC01", ownerLane: "v03", ownerPvc: "fixture-pvc", stateDir: "/state" });
  report.findings[0].id = "changed";
  assert.equal((ingest.payload.report as { findings: { id: string }[] }).findings[0].id, "blocked");
  assert.match(ingest.payloadHash ?? "", /^sha256:/u);
  assert.equal(ingest.payloadHash, buildMonitorTerminalIngest({ sentinelId: "fixture", node: "NC01", lane: "v03", runId: "run-1" }, { ok: false, findings: [{ id: "blocked" }], updatedAt: "2026-07-12T00:00:00.000Z" }, artifact, { ownerNode: "NC01", ownerLane: "v03", ownerPvc: "fixture-pvc", stateDir: "/state" }).payloadHash);
  assert.throws(() => buildMonitorTerminalIngest({ sentinelId: "fixture", node: "NC01", lane: "v03", runId: "" }, report, artifact, { ownerNode: "NC01", ownerLane: "v03", ownerPvc: "fixture-pvc", stateDir: "/state" }), MonitorIngestFailedError);
  await assert.rejects(
    () => submitMonitorTerminalIngest({ endpoint: "https://monitor.example.test/api/ingest", timeoutMs: 20, retry: { maxAttempts: 2, delayMs: 0 } }, ingest, async () => new Response("{}", { status: 503 })),
    (error: unknown) => error instanceof MonitorIngestFailedError && error.details.attempts === 2,
  );
});

test("terminal writer requires explicit mutually exclusive authority", () => {
  assert.throws(() => resolveMonitorTerminalWriter({}));
  assert.deepEqual(resolveMonitorTerminalWriter({ sqlite: { path: "/state/legacy.sqlite" } }), { mode: "legacy-sqlite" });
  const central = resolveMonitorTerminalWriter({ monitor: { terminalWriter: { mode: "central", ingest: { endpoint: "https://monitor.example.test/api/ingest", timeoutMs: 1_000, retry: { maxAttempts: 2, delayMs: 0 } } } } });
  assert.equal(central.mode, "central");
  assert.throws(() => resolveMonitorTerminalWriter({ monitor: { terminalWriter: { mode: "central" } } }));
  assert.throws(() => resolveMonitorTerminalWriter({ sqlite: { path: "/state/legacy.sqlite" }, monitor: { terminalWriter: { mode: "central", ingest: { endpoint: "https://monitor.example.test/api/ingest", timeoutMs: 1_000, retry: { maxAttempts: 2, delayMs: 0 } } } } }));
});

test("real compact analyze projects stable report time and artifact facts", () => {
  const compact = compactWebObserveAnalyzeAnalysisForRaw({ generatedAt: "2026-07-12T00:00:00.000Z", analyzer: { reportJsonPath: "/state/run-1/analysis/report.json", reportJsonSha256: `sha256:${"c".repeat(64)}`, reportJsonBytes: 12 } });
  assert.deepEqual({ reportUpdatedAt: compact.reportUpdatedAt, reportJsonSha256: compact.reportJsonSha256, reportJsonBytes: compact.reportJsonBytes }, { reportUpdatedAt: "2026-07-12T00:00:00.000Z", reportJsonSha256: `sha256:${"c".repeat(64)}`, reportJsonBytes: 12 });
  const ingest = buildMonitorTerminalIngest({ sentinelId: "fixture", node: "NC01", lane: "v03", runId: "generated-at" }, compact, [{ relativePath: "analysis/report.json", kind: "report-json", sha256: `sha256:${"c".repeat(64)}`, sizeBytes: 12 }], { ownerNode: "NC01", ownerLane: "v03", ownerPvc: "fixture-pvc", stateDir: "/state/run-1" });
  assert.equal(ingest.updatedAt, "2026-07-12T00:00:00.000Z");
});

test("SQLite snapshot reconciles the 228/242/234 fixture shape without production assumptions", async () => {
  const directory = mkdtempSync(join(tmpdir(), "monitor-migration-"));
  try {
    const sqlitePath = join(directory, "legacy.sqlite");
    const snapshotPath = join(directory, "snapshot.json");
    const database = new Database(sqlitePath);
    const stateRoot = join(directory, "state");
    const workspaceRoot = join(directory, "hwlab-v03");
    const workspaceRun = join(workspaceRoot, "web-observe", "run-0");
    const reportPath = join(workspaceRun, "analysis", "report.json");
    mkdirSync(join(workspaceRun, "analysis"), { recursive: true });
    writeFileSync(reportPath, JSON.stringify({ updatedAt: "2026-07-12T00:00:00.000Z", views: { summary: { title: "legacy summary" } }, findings: [{ id: "report-finding" }] }));
    const reportHash = `sha256:${Bun.CryptoHasher.hash("sha256", readFileSync(reportPath), "hex")}`;
    database.run("CREATE TABLE runs (id TEXT, state_dir TEXT, artifact_owner_pvc TEXT, artifact_count INTEGER, report_json_sha256 TEXT, updated_at TEXT)");
    database.run("CREATE TABLE findings (run_id TEXT, sentinel_id TEXT, finding_id TEXT, severity TEXT, count INTEGER, summary TEXT, report_json_sha256 TEXT, created_at TEXT)");
    database.run("CREATE TABLE metadata (id TEXT, key TEXT, value_json TEXT)");
    for (let index = 0; index < 228; index += 1) database.run("INSERT INTO runs VALUES (?, ?, ?, ?, ?, ?)", [`run-${index}`, index === 0 ? workspaceRun : null, index === 0 ? "legacy-web-observe-workspace-nc01" : null, index === 0 ? 1 : 0, index === 0 ? reportHash : null, "2026-07-12T00:00:00.000Z"]);
    for (let index = 0; index < 242; index += 1) database.run("INSERT INTO findings VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [index === 0 ? "run-0" : `run-${index % 228}`, "fixture", index === 0 ? "report-finding" : `finding-${index}`, "warning", 1, "indexed", reportHash, "2026-07-12T00:00:00.000Z"]);
    for (let index = 0; index < 234; index += 1) database.run("INSERT INTO metadata VALUES (?, ?, ?)", [`metadata-${index}`, index === 0 ? "run.report.run-0" : index === 1 ? "scheduler.heartbeat" : index === 2 ? "maintenance" : index === 3 ? "manual-trigger.latest" : `metadata-${index}`, index === 0 ? JSON.stringify({ views: { summary: { title: "legacy summary" } }, findings: [{ id: "report-finding", rootCause: "complete metadata detail" }] }) : index === 2 ? JSON.stringify(false) : JSON.stringify({ index })]);
    database.close();
    const source = { sentinelId: "fixture", node: "NC01", lane: "v03", path: sqlitePath, runtimeConfigRef: "fixture#runtime", stateRoot, ownerPvc: "fixture-pvc", artifactSources: [{ ownerPvc: "fixture-pvc", mountPath: stateRoot }, { ownerPvc: "legacy-web-observe-workspace-nc01", mountPath: workspaceRoot, hostPath: workspaceRoot }] };
    const plan = planMonitorSqliteSources([source]);
    assert.equal((plan.sources as { exists: boolean }[])[0].exists, true);
    const snapshot = exportMonitorSqliteSnapshot(source, snapshotPath);
    const verified = verifyMonitorSqliteSnapshot(snapshot);
    assert.equal(verified.ok, true);
    assert.deepEqual(verified.reconciliation, { runs: 228, findings: 242, metadata: 234, payloads: 0, locators: 0, latest: 0 });
    const persisted = JSON.parse(readFileSync(snapshotPath, "utf8"));
    assert.equal(persisted.schema, snapshot.schema);
    const store = createInMemoryMonitorCentralStore();
    assert.deepEqual((await importMonitorSqliteSnapshot(snapshot, store)).imported, { inserted: 228, idempotent: 0 });
    assert.deepEqual((await importMonitorSqliteSnapshot(snapshot, store)).imported, { inserted: 0, idempotent: 228 });
    const importedMetadata = await store.runtimeMetadata({ sentinelId: "fixture", node: "NC01", lane: "v03" });
    assert.equal(importedMetadata.length, 234);
    assert.equal(importedMetadata.find((item) => item.key === "scheduler.heartbeat")?.updatedAt, snapshot.createdAt);
    const importedRun = await store.run({ sentinelId: "fixture", node: "NC01", lane: "v03" }, "run-0");
    assert.equal(importedRun?.artifactLocators[0]?.sha256, reportHash);
    assert.equal(importedRun?.artifactLocators[0]?.ownerPvc, "legacy-web-observe-workspace-nc01");
    const reportFindings = importedRun?.findings.filter((finding) => finding.id === "report-finding") ?? [];
    assert.deepEqual(reportFindings, [{ id: "report-finding", rootCause: "complete metadata detail" }]);
    assert.equal((await store.view({ sentinelId: "fixture", node: "NC01", lane: "v03" }, "run-0", "summary"))?.value.title, "legacy summary");
    const pgVerification = await verifyMonitorSqliteSnapshotAgainstStore(snapshot, store);
    assert.equal(pgVerification.ok, true);
    assert.deepEqual([...new Set((pgVerification.checks as { name: string }[]).map((item) => item.name))].sort(), ["artifactLocator", "finding", "globalOverview", "run", "runtimeMetadata", "scopedOverview", "timeline", "view"]);
    assert.deepEqual((pgVerification.reconciliation as { target: Record<string, number> }).target, { runs: 228, findings: 242, payloads: 228, artifactLocators: 1, views: 1, runtimeMetadata: 234 });
    await store.importSource({ manifestId: "sha256:orphan-metadata", sourceFingerprint: "sha256:orphan-metadata", rowCount: 0 }, [], [{ sentinelId: "fixture", node: "NC01", lane: "v03", key: "orphan", value: { unexpected: true }, updatedAt: snapshot.createdAt }]);
    assert.equal((await verifyMonitorSqliteSnapshotAgainstStore(snapshot, store)).ok, false);
    assert.equal(verifyMonitorSqliteSnapshot({ ...snapshot, manifestContentHash: "sha256:tampered" }).ok, false);
  } finally {
    rmSync(directory, { recursive: true, force: true });
  }
});

test("single-source batch rolls back conflicts and preserves manifest idempotency", async () => {
  const store = createInMemoryMonitorCentralStore();
  const owner = { ownerNode: "NC01", ownerLane: "v03", ownerPvc: "fixture-pvc", stateDir: "run" };
  const artifact = [{ relativePath: "analysis/report.json", kind: "report-json", sha256: `sha256:${"b".repeat(64)}`, sizeBytes: 1 }];
  const first = buildMonitorTerminalIngest({ sentinelId: "fixture", node: "NC01", lane: "v03", runId: "first" }, { updatedAt: "2026-07-12T00:00:00.000Z" }, artifact, owner);
  const existing = buildMonitorTerminalIngest({ sentinelId: "fixture", node: "NC01", lane: "v03", runId: "existing" }, { updatedAt: "2026-07-12T00:00:00.000Z", status: "ok" }, artifact, owner);
  const conflicting = buildMonitorTerminalIngest({ sentinelId: "fixture", node: "NC01", lane: "v03", runId: "existing" }, { updatedAt: "2026-07-12T00:00:00.000Z", status: "blocked" }, artifact, owner);
  await store.ingest(existing);
  await assert.rejects(() => store.importSource({ manifestId: "sha256:batch-conflict", sourceFingerprint: "sha256:source", rowCount: 2 }, [first, conflicting]), (error: unknown) => (error as { code?: string }).code === "payload_hash_conflict");
  assert.equal(await store.run({ sentinelId: "fixture", node: "NC01", lane: "v03" }, "first"), null);
  const manifest = { manifestId: "sha256:batch-ok", sourceFingerprint: "sha256:source", rowCount: 1 };
  assert.equal((await store.importSource(manifest, [first])).disposition, "inserted");
  assert.equal((await store.importSource(manifest, [first])).disposition, "idempotent");
});

test("metadata-only snapshot verifies scoped runtime metadata and rejects extras", async () => {
  const directory = mkdtempSync(join(tmpdir(), "monitor-metadata-only-"));
  try {
    const sqlitePath = join(directory, "index.sqlite");
    const snapshotPath = join(directory, "snapshot.json");
    const database = new Database(sqlitePath);
    database.run("CREATE TABLE metadata (key TEXT, value_json TEXT)");
    database.run("INSERT INTO metadata VALUES (?, ?)", ["scheduler.heartbeat", JSON.stringify({ healthy: true })]);
    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, snapshotPath);
    const store = createInMemoryMonitorCentralStore();
    await importMonitorSqliteSnapshot(snapshot, store);
    assert.equal((await verifyMonitorSqliteSnapshotAgainstStore(snapshot, store)).ok, true);
    await store.importSource({ manifestId: "sha256:metadata-only-extra", sourceFingerprint: "sha256:metadata-only-extra", rowCount: 0 }, [], [{ sentinelId: "fixture", node: "NC01", lane: "v03", key: "orphan", value: true, updatedAt: snapshot.createdAt }]);
    assert.equal((await verifyMonitorSqliteSnapshotAgainstStore(snapshot, store)).ok, false);
  } finally {
    rmSync(directory, { recursive: true, force: true });
  }
});

test("WAL export freezes a serialized SQLite snapshot independent of later live writes", () => {
  const directory = mkdtempSync(join(tmpdir(), "monitor-wal-snapshot-"));
  try {
    const sqlitePath = join(directory, "index.sqlite");
    const snapshotPath = join(directory, "snapshot.json");
    const database = new Database(sqlitePath);
    database.exec("PRAGMA journal_mode = WAL");
    database.run("CREATE TABLE metadata (key TEXT, value_json TEXT)");
    database.run("INSERT INTO metadata VALUES (?, ?)", ["scheduler.heartbeat", JSON.stringify({ sequence: 1 })]);
    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, snapshotPath);
    database.run("INSERT INTO metadata VALUES (?, ?)", ["scheduler.heartbeat.late", JSON.stringify({ sequence: 2 })]);
    database.close();

    assert.equal(snapshot.artifactLocator.path, join(directory, "snapshot.sqlite"));
    assert.equal(snapshot.tables.find((table) => table.name === "metadata")?.rowCount, 1);
    assert.equal(verifyMonitorSqliteSnapshot(snapshot).ok, true);
  } finally {
    rmSync(directory, { recursive: true, force: true });
  }
});

test("workspace artifact source verifies locator identity hash and size", async () => {
  const directory = mkdtempSync(join(tmpdir(), "monitor-workspace-artifact-"));
  try {
    const sqlitePath = join(directory, "index.sqlite");
    const snapshotPath = join(directory, "snapshot.json");
    const workspaceRoot = join(directory, "hwlab-v03");
    const stateDir = join(workspaceRoot, "web-observe", "run-1");
    const artifacts = [
      ["analysis/report.json", Buffer.from('{"ok":true}\n')],
      ["analysis/details.json", Buffer.from('{"details":true}\n')],
      ["artifacts/request.txt", Buffer.from("request\n")],
      ["artifacts/response.txt", Buffer.from("response\n")],
      ["screenshots/page.png", Buffer.from([0x89, 0x50, 0x4e, 0x47])],
    ] as const;
    const reportBytes = artifacts[0][1];
    mkdirSync(join(stateDir, "analysis"), { recursive: true });
    for (const [relativePath, bytes] of artifacts) {
      const path = join(stateDir, relativePath);
      mkdirSync(join(path, ".."), { recursive: true });
      writeFileSync(path, bytes);
    }
    const reportHash = `sha256:${createHash("sha256").update(reportBytes).digest("hex")}`;
    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("INSERT INTO runs VALUES (?, ?, ?, ?, ?)", ["run-1", stateDir, 5, reportHash, "2026-07-12T00:00:00.000Z"]);
    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 }, { ownerPvc: "legacy-web-observe-workspace-nc01", mountPath: workspaceRoot, hostPath: workspaceRoot }] };
    const snapshot = exportMonitorSqliteSnapshot(source, snapshotPath);
    const store = createInMemoryMonitorCentralStore();
    await importMonitorSqliteSnapshot(snapshot, store);
    const run = await store.run({ sentinelId: "fixture", node: "NC01", lane: "v03" }, "run-1");
    assert.equal(run?.artifactLocators.length, 5);
    assert.deepEqual(run?.artifactLocators.map((locator) => ({ ownerPvc: locator.ownerPvc, stateDir: locator.stateDir, relativePath: locator.relativePath, sha256: locator.sha256, sizeBytes: locator.sizeBytes })), artifacts.map(([relativePath, bytes]) => ({ ownerPvc: "legacy-web-observe-workspace-nc01", stateDir, relativePath, sha256: `sha256:${createHash("sha256").update(bytes).digest("hex")}`, sizeBytes: bytes.byteLength })).sort((left, right) => left.relativePath.localeCompare(right.relativePath)));
    assert.equal((await verifyMonitorSqliteSnapshotAgainstStore(snapshot, store)).ok, true);
    writeFileSync(join(stateDir, "artifacts", "request.txt"), "changed\n");
    await assert.rejects(() => importMonitorSqliteSnapshot(snapshot, createInMemoryMonitorCentralStore()), (error: unknown) => (error as { code?: string }).code === "monitor-migration-frozen-artifact-invalid");
    writeFileSync(join(stateDir, "artifacts", "request.txt"), artifacts[2][1]);
    writeFileSync(join(stateDir, "artifacts", "new-after-export.txt"), "new\n");
    await assert.rejects(() => importMonitorSqliteSnapshot(snapshot, createInMemoryMonitorCentralStore()), (error: unknown) => (error as { code?: string }).code === "monitor-migration-frozen-artifact-invalid");
    rmSync(join(stateDir, "artifacts", "new-after-export.txt"));
    rmSync(join(stateDir, "artifacts", "response.txt"));
    await assert.rejects(() => verifyMonitorSqliteSnapshotAgainstStore(snapshot, store), (error: unknown) => { const migrationError = error as { code?: string; diagnostic?: Record<string, unknown> }; return migrationError.code === "monitor-migration-frozen-artifact-invalid" && migrationError.diagnostic?.runId === "run-1" && migrationError.diagnostic?.stateDir === stateDir && migrationError.diagnostic?.expectedCount === 5; });
  } finally {
    rmSync(directory, { recursive: true, force: true });
  }
});

test("declared pre-migration artifact gaps import empty locators and fail closed on identity drift", async () => {
  const directory = mkdtempSync(join(tmpdir(), "monitor-artifact-gaps-"));
  try {
    const sqlitePath = join(directory, "index.sqlite");
    const gaps = [
      { runId: "gap-run-a", artifactCount: 1, stateDir: ".state/missing/a", reportSha256: `sha256:${"a".repeat(64)}` },
      { runId: "gap-run-b", artifactCount: 4, stateDir: ".state/missing/b", reportSha256: `sha256:${"b".repeat(64)}` },
    ];
    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)");
    for (const gap of gaps) database.run("INSERT INTO runs VALUES (?, ?, ?, ?, ?)", [gap.runId, gap.stateDir, gap.artifactCount, gap.reportSha256, "2026-07-08T03:58:39.000Z"]);
    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 }], legacyArtifactGaps: gaps };
    const snapshot = exportMonitorSqliteSnapshot(source, join(directory, "snapshot.json"));
    const store = createInMemoryMonitorCentralStore();
    await importMonitorSqliteSnapshot(snapshot, store);
    for (const gap of gaps) {
      const run = await store.run({ sentinelId: "fixture", node: "NC01", lane: "v03" }, gap.runId);
      assert.equal(run?.artifactLocators.length, 0);
      assert.equal((run?.timeline.at(-1) as { code?: string; locator?: unknown; bytesVerified?: boolean } | undefined)?.code, "legacy-artifact-bytes-missing-before-migration");
      assert.equal((run?.timeline.at(-1) as { locator?: unknown }).locator, null);
      assert.equal((run?.timeline.at(-1) as { bytesVerified?: boolean }).bytesVerified, false);
    }
    assert.equal((await verifyMonitorSqliteSnapshotAgainstStore(snapshot, store)).ok, true);
    const timelineTamperedStore = { ...store, async run(scope: { sentinelId: string; node: string; lane: string }, runId: string) { const run = await store.run(scope, runId); return run === null ? null : { ...run, timeline: [] }; } };
    assert.equal((await verifyMonitorSqliteSnapshotAgainstStore(snapshot, timelineTamperedStore)).ok, false);
    const swappedManifests = snapshot.artifactManifests.map((manifest, index) => ({ ...manifest, missingBeforeMigration: { ...gaps[(index + 1) % gaps.length] } }));
    const swappedUnsigned = { ...snapshot, artifactManifests: swappedManifests };
    const swappedSnapshot = { ...swappedUnsigned, manifestContentHash: monitorSnapshotContentHash(swappedUnsigned) };
    assert.equal(verifyMonitorSqliteSnapshot(swappedSnapshot).ok, false);
    const recoveredPath = join(directory, gaps[0].stateDir, "recovered.bin");
    mkdirSync(join(recoveredPath, ".."), { recursive: true });
    writeFileSync(recoveredPath, "recovered\n");
    await assert.rejects(() => importMonitorSqliteSnapshot(snapshot, createInMemoryMonitorCentralStore()), (error: unknown) => (error as { code?: string }).code === "monitor-migration-artifact-gap-recovered");
    rmSync(recoveredPath);
    assert.throws(() => exportMonitorSqliteSnapshot({ ...source, legacyArtifactGaps: [{ ...gaps[0], artifactCount: 2 }, gaps[1]] }, join(directory, "changed.json")), (error: unknown) => (error as { code?: string }).code === "monitor-migration-artifact-gap-identity-invalid");
    assert.throws(() => exportMonitorSqliteSnapshot({ ...source, legacyArtifactGaps: [{ ...gaps[0], stateDir: "wrong-state-dir" }, gaps[1]] }, join(directory, "wrong-state-dir.json")), (error: unknown) => (error as { code?: string }).code === "monitor-migration-artifact-gap-identity-invalid");
    assert.throws(() => exportMonitorSqliteSnapshot({ ...source, legacyArtifactGaps: [{ ...gaps[0], reportSha256: `sha256:${"c".repeat(64)}` }, gaps[1]] }, join(directory, "wrong-report.json")), (error: unknown) => (error as { code?: string }).code === "monitor-migration-artifact-gap-identity-invalid");
    assert.throws(() => exportMonitorSqliteSnapshot({ ...source, legacyArtifactGaps: [...gaps, { runId: "third", artifactCount: 1, stateDir: "missing", reportSha256: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }] }, join(directory, "third.json")), (error: unknown) => (error as { code?: string }).code === "monitor-migration-artifact-gap-unmatched");
    const unexpectedArtifact = Buffer.from("unexpected\n");
    const unexpectedPath = join(directory, gaps[0].stateDir, "extra.bin");
    mkdirSync(join(unexpectedPath, ".."), { recursive: true });
    writeFileSync(unexpectedPath, unexpectedArtifact);
    const locatorDatabase = new Database(sqlitePath);
    locatorDatabase.run("CREATE TABLE artifact_locators (run_id TEXT, owner_pvc TEXT, state_dir TEXT, relative_path TEXT, sha256 TEXT, size_bytes INTEGER)");
    locatorDatabase.run("INSERT INTO artifact_locators VALUES (?, ?, ?, ?, ?, ?)", [gaps[0].runId, "fixture-pvc", gaps[0].stateDir, "extra.bin", `sha256:${createHash("sha256").update(unexpectedArtifact).digest("hex")}`, unexpectedArtifact.byteLength]);
    locatorDatabase.close();
    assert.throws(() => exportMonitorSqliteSnapshot(source, join(directory, "unexpected-locator.json")), (error: unknown) => (error as { code?: string }).code === "monitor-migration-artifact-gap-locator-unexpected");
  } finally {
    rmSync(directory, { recursive: true, force: true });
  }
});

test("empty normal artifact directories freeze owner state and normal manifests cannot be exchanged", async () => {
  const directory = mkdtempSync(join(tmpdir(), "monitor-empty-artifact-"));
  try {
    const sqlitePath = join(directory, "index.sqlite");
    const emptyStateDir = ".state/empty";
    const fullStateDir = ".state/full";
    mkdirSync(join(directory, emptyStateDir), { recursive: true });
    const fullDirectory = join(directory, fullStateDir);
    mkdirSync(join(fullDirectory, "analysis"), { recursive: true });
    const report = Buffer.from("report\n");
    writeFileSync(join(fullDirectory, "analysis", "report.json"), 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("INSERT INTO runs VALUES (?, ?, ?, ?, ?)", ["empty", emptyStateDir, 0, "", "2026-07-12T00:00:00.000Z"]);
    database.run("INSERT INTO runs VALUES (?, ?, ?, ?, ?)", ["full", fullStateDir, 1, `sha256:${createHash("sha256").update(report).digest("hex")}`, "2026-07-12T00:00:00.000Z"]);
    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"));
    const parsedSnapshot = JSON.parse(readFileSync(join(directory, "snapshot.json"), "utf8"));
    const roundtripStore = createInMemoryMonitorCentralStore();
    await importMonitorSqliteSnapshot(parsedSnapshot, roundtripStore);
    assert.equal((await verifyMonitorSqliteSnapshotAgainstStore(parsedSnapshot, roundtripStore)).ok, true);
    const emptyManifest = snapshot.artifactManifests.find((manifest) => manifest.runId === "empty");
    assert.deepEqual({ artifactCount: emptyManifest?.artifactCount, stateDir: emptyManifest?.stateDir, ownerPvc: emptyManifest?.ownerPvc, locatorCount: emptyManifest?.locators.length }, { artifactCount: 0, stateDir: emptyStateDir, ownerPvc: "fixture-pvc", locatorCount: 0 });
    const swappedManifests = snapshot.artifactManifests.map((manifest, index, manifests) => ({ ...manifests[(index + 1) % manifests.length], runId: manifest.runId }));
    const swappedUnsigned = { ...snapshot, artifactManifests: swappedManifests };
    assert.equal(verifyMonitorSqliteSnapshot({ ...swappedUnsigned, manifestContentHash: monitorSnapshotContentHash(swappedUnsigned) }).ok, false);
    const emptyDirectory = join(directory, emptyStateDir);
    mkdirSync(emptyDirectory, { recursive: true });
    writeFileSync(join(emptyDirectory, "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("normal report locator hash cannot drift from its SQLite row after a rehashed snapshot", async () => {
  const directory = mkdtempSync(join(tmpdir(), "monitor-report-locator-binding-"));
  try {
    const sqlitePath = join(directory, "index.sqlite");
    const stateDir = ".state/run";
    const reportPath = join(directory, stateDir, "analysis", "report.json");
    const reportA = Buffer.from("report A\n");
    const reportB = Buffer.from("report B with different size\n");
    mkdirSync(join(reportPath, ".."), { recursive: true });
    writeFileSync(reportPath, reportA);
    const reportAHash = `sha256:${createHash("sha256").update(reportA).digest("hex")}`;
    const reportBHash = `sha256:${createHash("sha256").update(reportB).digest("hex")}`;
    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)");
    database.run("INSERT INTO runs VALUES (?, ?, ?, ?, ?)", ["run", stateDir, 1, reportAHash, "2026-07-12T00:00:00.000Z"]);
    database.run("INSERT INTO artifact_locators VALUES (?, ?, ?, ?, ?, ?)", ["run", "fixture-pvc", stateDir, "analysis/report.json", reportAHash, reportA.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"));
    writeFileSync(reportPath, reportB);
    const tamperedUnsigned = { ...snapshot, artifactManifests: snapshot.artifactManifests.map((manifest) => ({ ...manifest, locators: manifest.locators.map((locator) => ({ ...locator, sha256: reportBHash, sizeBytes: reportB.byteLength })) })) };
    const tampered = { ...tamperedUnsigned, manifestContentHash: monitorSnapshotContentHash(tamperedUnsigned) };
    assert.equal(verifyMonitorSqliteSnapshot(tampered).ok, false);
    await assert.rejects(() => importMonitorSqliteSnapshot(tampered, createInMemoryMonitorCentralStore()), (error: unknown) => (error as { code?: string }).code === "monitor-migration-snapshot-invalid");
  } finally {
    rmSync(directory, { recursive: true, force: true });
  }
});

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 {
    const sqlitePath = join(directory, "index.sqlite");
    const snapshotPath = join(directory, "snapshot.json");
    const database = new Database(sqlitePath);
    database.run("CREATE TABLE runs (id TEXT, artifact_count INTEGER, updated_at TEXT)");
    database.run("INSERT INTO runs VALUES (?, ?, ?)", ["run-1", 1, "2026-07-12T00:00:00.000Z"]);
    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 }] };
    assert.throws(() => exportMonitorSqliteSnapshot(source, snapshotPath), (error: unknown) => (error as { code?: string }).code === "monitor-migration-artifact-count-mismatch");
  } finally {
    rmSync(directory, { recursive: true, force: true });
  }
});
