// SPEC: PJ2026-01060508 Web哨兵 draft-2026-07-12-p17-monitor-central-persistence. // Responsibility: Host PostgreSQL-backed Monitor terminal ingest, bounded read models, and health contracts. import { createHash } from "node:crypto"; import postgres from "postgres"; export const MONITOR_CENTRAL_SPEC_REF = "PJ2026-01060508 Web哨兵 draft-2026-07-12-p17-monitor-central-persistence"; export const MONITOR_CENTRAL_SCHEMA_VERSION = "2026-07-12-p17-2"; export interface MonitorScope { readonly sentinelId?: string; readonly node?: string; readonly lane?: string; } export interface MonitorArtifactLocator { readonly ownerNode: string; readonly ownerLane: string; readonly ownerPvc: string; readonly stateDir: string; readonly relativePath: string; readonly sha256: string; readonly sizeBytes: number; readonly kind: string; readonly reachable: boolean; } export interface MonitorCentralIngest { readonly sentinelId: string; readonly node: string; readonly lane: string; readonly runId: string; readonly updatedAt?: string; readonly status?: string; readonly payload: Record; readonly payloadHash?: string; readonly findings?: readonly Record[]; readonly artifactLocators?: readonly MonitorArtifactLocator[]; readonly timeline?: readonly Record[]; } export interface MonitorRun extends Required { readonly runId: string; readonly updatedAt: string; readonly status: string; readonly payloadHash: string; } export interface MonitorCentralStore { migrate(): Promise; ping(): Promise; schemaVersion(): Promise; capability(): Promise<{ readonly ingest: boolean; readonly query: boolean }>; ingest(input: NormalizedIngest): Promise<{ readonly disposition: "inserted" | "idempotent" }>; updateArtifactAvailability(scope: Required, runId: string, locatorIndex: number, reachable: boolean): Promise; overview(scope: MonitorScope): Promise>; runs(scope: MonitorScope, options: RunQueryOptions): Promise; run(scope: Required, runId: string): Promise | null>; view(scope: Required, runId: string, view: string): Promise | null>; findings(scope: MonitorScope, options: FindingQueryOptions): Promise[]>; } interface NormalizedIngest extends Required> { readonly payloadHash: string; } interface RunCursor { readonly updatedAt: string; readonly runId: string; } interface FindingCursor extends RunCursor { readonly findingIndex: number; } export interface RunQueryOptions { readonly limit: number; readonly cursor: RunCursor | null; } export interface FindingQueryOptions { readonly limit: number; readonly cursor: FindingCursor | null; } export interface MonitorCentralService { ingest(input: MonitorCentralIngest): Promise>; health(): Promise>; fetch(request: Request): Promise; } export interface MonitorCentralServiceOptions { readonly databaseUrl?: string | null; readonly store?: MonitorCentralStore; readonly pageSize?: number; readonly postgres?: PostgresMonitorCentralStoreOptions; } export interface PostgresMonitorCentralStoreOptions { readonly poolMax: number; readonly idleTimeoutSeconds: number; } export class MonitorCentralConfigurationError extends Error { readonly code = "monitor_central_database_url_missing"; constructor() { super("Monitor central service requires an explicit DATABASE_URL"); this.name = "MonitorCentralConfigurationError"; } toJSON(): Record { return { ok: false, error: { code: this.code, message: this.message }, valuesRedacted: true }; } } export function createMonitorCentralService(options: MonitorCentralServiceOptions = {}): MonitorCentralService { const pageSize = requirePositiveInteger(options.pageSize, "monitor_central_page_size_missing"); const store = options.store ?? createPostgresMonitorCentralStore(requireDatabaseUrl(options.databaseUrl), requirePostgresOptions(options.postgres)); const ingest = async (input: MonitorCentralIngest) => { const normalized = normalizeIngest(input); const result = await store.ingest(normalized); return { ok: true, ...result, identity: identityOf(normalized), payloadHash: normalized.payloadHash, valuesRedacted: true }; }; const health = async () => { const checks: Record[] = [{ name: "configuration", ok: true, valuesRedacted: true }]; try { await store.ping(); checks.push({ name: "postgres", ok: true, valuesRedacted: true }); const schemaVersion = await store.schemaVersion(); checks.push({ name: "schema", ok: schemaVersion === MONITOR_CENTRAL_SCHEMA_VERSION, version: schemaVersion, valuesRedacted: true }); const capability = await store.capability(); checks.push({ name: "ingest", ok: capability.ingest, valuesRedacted: true }); checks.push({ name: "query", ok: capability.query, valuesRedacted: true }); } catch (error) { checks.push({ name: "postgres", ok: false, code: "postgres_unavailable", message: errorMessage(error), valuesRedacted: true }); } return { ok: checks.every((item) => item.ok === true), checks, valuesRedacted: true }; }; const fetch = async (request: Request): Promise => { const url = new URL(request.url); try { if (request.method === "POST" && url.pathname === "/api/ingest") { const result = await ingest(await requestRecord(request)); return json(result, result.disposition === "inserted" ? 201 : 200); } if (request.method === "GET" && url.pathname === "/health") return json(await health()); if (request.method === "GET" && url.pathname === "/api/overview") return json({ ok: true, overview: await store.overview(scopeFrom(url)), valuesRedacted: true }); if (request.method === "GET" && url.pathname === "/api/runs") return json(runPage(await store.runs(scopeFrom(url), runQueryOptions(url, pageSize)))); if (request.method === "GET" && url.pathname === "/api/findings") return json(findingPage(await store.findings(scopeFrom(url), findingQueryOptions(url, pageSize)))); if (request.method === "GET" && url.pathname.startsWith("/api/runs/")) { const suffix = decodeURIComponent(url.pathname.slice("/api/runs/".length)); const scope = requireFullScope(scopeFrom(url)); const viewMatch = suffix.match(/^([^/]+)\/views\/([^/]+)$/u); if (viewMatch) { const view = await store.view(scope, viewMatch[1], viewMatch[2]); return view ? json({ ok: true, view, valuesRedacted: true }) : json({ ok: false, error: { code: "view_not_found" }, valuesRedacted: true }, 404); } const run = await store.run(scope, suffix); return run ? json({ ok: true, run, valuesRedacted: true }) : json({ ok: false, error: { code: "run_not_found" }, valuesRedacted: true }, 404); } return json({ ok: false, error: { code: "not_found" }, valuesRedacted: true }, 404); } catch (error) { return json({ ok: false, error: structuredError(error), valuesRedacted: true }, httpStatus(error)); } }; return { ingest, health, fetch }; } export function createPostgresMonitorCentralStore(databaseUrl: string, options: PostgresMonitorCentralStoreOptions): MonitorCentralStore { const sql = postgres(databaseUrl, { max: requirePositiveInteger(options.poolMax, "monitor_central_pg_pool_max_missing"), idle_timeout: requirePositiveInteger(options.idleTimeoutSeconds, "monitor_central_pg_idle_timeout_missing") }); return { async migrate() { for (const statement of MONITOR_CENTRAL_MIGRATIONS) await sql.unsafe(statement); await sql`insert into monitor.schema_migrations (version, applied_at) values (${MONITOR_CENTRAL_SCHEMA_VERSION}, now()) on conflict (version) do nothing`; }, async ping() { await sql`select 1`; }, async schemaVersion() { const rows = await sql<{ version: string }[]>`select version from monitor.schema_migrations order by applied_at desc limit 1`; return rows[0]?.version ?? null; }, async capability() { const rows = await sql<{ can_ingest: boolean; can_query: boolean }[]>` select has_table_privilege(current_user, 'monitor.runs', 'insert') as can_ingest, has_table_privilege(current_user, 'monitor.runs', 'select') as can_query`; return { ingest: rows[0]?.can_ingest === true, query: rows[0]?.can_query === true }; }, async ingest(input) { return sql.begin(async (transaction) => { await transaction` insert into monitor.runners (sentinel_id, node, lane, updated_at) values (${input.sentinelId}, ${input.node}, ${input.lane}, ${input.updatedAt}) on conflict (sentinel_id, node, lane) do update set updated_at = greatest(monitor.runners.updated_at, excluded.updated_at)`; const inserted = await transaction<{ payload_hash: string }[]>` insert into monitor.runs (sentinel_id, node, lane, run_id, updated_at, status, payload_hash) values (${input.sentinelId}, ${input.node}, ${input.lane}, ${input.runId}, ${input.updatedAt}, ${input.status}, ${input.payloadHash}) on conflict (sentinel_id, node, lane, run_id) do nothing returning payload_hash`; if (!inserted[0]) { const existing = await transaction<{ payload_hash: string }[]>` select payload_hash from monitor.runs where sentinel_id = ${input.sentinelId} and node = ${input.node} and lane = ${input.lane} and run_id = ${input.runId}`; if (existing[0]?.payload_hash === input.payloadHash) { await updatePostgresArtifactAvailability(transaction, input); return { disposition: "idempotent" as const }; } throw conflict(input); } await transaction` insert into monitor.run_payloads (sentinel_id, node, lane, run_id, payload_hash, payload) values (${input.sentinelId}, ${input.node}, ${input.lane}, ${input.runId}, ${input.payloadHash}, ${JSON.stringify(input.payload)}::jsonb)`; for (const [index, finding] of input.findings.entries()) await transaction` insert into monitor.findings (sentinel_id, node, lane, run_id, finding_index, finding) values (${input.sentinelId}, ${input.node}, ${input.lane}, ${input.runId}, ${index}, ${JSON.stringify(finding)}::jsonb)`; for (const [index, locator] of input.artifactLocators.entries()) await transaction` insert into monitor.artifact_locators (sentinel_id, node, lane, run_id, locator_index, owner_node, owner_lane, owner_pvc, state_dir, relative_path, sha256, size_bytes, kind, reachable) values (${input.sentinelId}, ${input.node}, ${input.lane}, ${input.runId}, ${index}, ${locator.ownerNode}, ${locator.ownerLane}, ${locator.ownerPvc}, ${locator.stateDir}, ${locator.relativePath}, ${locator.sha256}, ${locator.sizeBytes}, ${locator.kind}, ${locator.reachable})`; for (const [index, event] of input.timeline.entries()) await transaction` insert into monitor.timeline_events (sentinel_id, node, lane, run_id, event_index, event) values (${input.sentinelId}, ${input.node}, ${input.lane}, ${input.runId}, ${index}, ${JSON.stringify(event)}::jsonb)`; return { disposition: "inserted" as const }; }); }, async updateArtifactAvailability(scope, runId, locatorIndex, reachable) { await sql` update monitor.artifact_locators set reachable = ${reachable} where sentinel_id = ${scope.sentinelId} and node = ${scope.node} and lane = ${scope.lane} and run_id = ${runId} and locator_index = ${locatorIndex}`; }, async overview(scope) { const rows = await queryRuns(sql, scope, { limit: 1, cursor: null }); return { scope: publicScope(scope), runCount: await countRuns(sql, scope), latest: rows[0] ?? null }; }, async runs(scope, options) { return queryRuns(sql, scope, options); }, async run(scope, runId) { const where = identityWhere(scope, runId); const rows = await sql.unsafe(`select r.sentinel_id, r.node, r.lane, r.run_id, r.updated_at, r.status, r.payload_hash, p.payload from monitor.runs r join monitor.run_payloads p using (sentinel_id, node, lane, run_id) where ${where.text} limit 1`, where.values); const row = rows[0] as Record | undefined; if (!row) return null; const identity = rowIdentity(row); const [findings, locators, timeline] = await Promise.all([ sql.unsafe(`select finding from monitor.findings where ${identity.text} order by finding_index`, identity.values), sql.unsafe(`select owner_node, owner_lane, owner_pvc, state_dir, relative_path, sha256, size_bytes, kind, reachable from monitor.artifact_locators where ${identity.text} order by locator_index`, identity.values), sql.unsafe(`select event from monitor.timeline_events where ${identity.text} order by event_index`, identity.values), ]); return { ...runRow(row), payload: row.payload, findings: findings.map((item) => item.finding), artifactLocators: locators.map(locatorRow), timeline: timeline.map((item) => item.event) }; }, async view(scope, runId, view) { const detail = await this.run(scope, runId); const views = detail?.payload && typeof detail.payload === "object" ? (detail.payload as Record).views : null; const value = views && typeof views === "object" ? (views as Record)[view] : undefined; return value === undefined ? null : { runId, name: view, value }; }, async findings(scope, options) { const where = scopeWhere(scope); const values: unknown[] = [...where.values]; let cursor = ""; if (options.cursor) { values.push(options.cursor.updatedAt, options.cursor.runId, options.cursor.findingIndex); cursor = `and (r.updated_at, r.run_id, f.finding_index) < ($${values.length - 2}, $${values.length - 1}, $${values.length})`; } values.push(options.limit); const rows = await sql.unsafe(`select f.finding, f.finding_index, r.sentinel_id, r.node, r.lane, r.run_id, r.updated_at, r.status, r.payload_hash from monitor.findings f join monitor.runs r using (sentinel_id, node, lane, run_id) where ${where.text} ${cursor} order by r.updated_at desc, r.run_id desc, f.finding_index desc limit $${values.length}`, values); return rows.map(mapPostgresFindingRow); }, }; } export const MONITOR_CENTRAL_MIGRATIONS = [ "create schema if not exists monitor", "create table if not exists monitor.schema_migrations (version text primary key, applied_at timestamptz not null)", "create table if not exists monitor.runners (sentinel_id text not null, node text not null, lane text not null, updated_at timestamptz not null, primary key (sentinel_id, node, lane))", "create table if not exists monitor.runs (sentinel_id text not null, node text not null, lane text not null, run_id text not null, updated_at timestamptz not null, status text not null, payload_hash text not null, primary key (sentinel_id, node, lane, run_id), foreign key (sentinel_id, node, lane) references monitor.runners (sentinel_id, node, lane))", "create index if not exists monitor_runs_latest_idx on monitor.runs (updated_at desc, run_id desc)", "create table if not exists monitor.run_payloads (sentinel_id text not null, node text not null, lane text not null, run_id text not null, payload_hash text not null, payload jsonb not null, primary key (sentinel_id, node, lane, run_id), foreign key (sentinel_id, node, lane, run_id) references monitor.runs on delete cascade)", "create table if not exists monitor.findings (sentinel_id text not null, node text not null, lane text not null, run_id text not null, finding_index integer not null, finding jsonb not null, primary key (sentinel_id, node, lane, run_id, finding_index), foreign key (sentinel_id, node, lane, run_id) references monitor.runs on delete cascade)", "create table if not exists monitor.artifact_locators (sentinel_id text not null, node text not null, lane text not null, run_id text not null, locator_index integer not null, owner_node text not null, owner_lane text not null, owner_pvc text not null, state_dir text not null, relative_path text not null, sha256 text not null, size_bytes bigint not null, kind text not null, reachable boolean not null, primary key (sentinel_id, node, lane, run_id, locator_index), foreign key (sentinel_id, node, lane, run_id) references monitor.runs on delete cascade)", "create table if not exists monitor.timeline_events (sentinel_id text not null, node text not null, lane text not null, run_id text not null, event_index integer not null, event jsonb not null, primary key (sentinel_id, node, lane, run_id, event_index), foreign key (sentinel_id, node, lane, run_id) references monitor.runs on delete cascade)", "create table if not exists monitor.import_manifests (manifest_id text primary key, source_fingerprint text not null, row_count bigint not null, imported_at timestamptz not null)", ]; export function createInMemoryMonitorCentralStore(): MonitorCentralStore { const records = new Map(); return { async migrate() {}, async ping() {}, async schemaVersion() { return MONITOR_CENTRAL_SCHEMA_VERSION; }, async capability() { return { ingest: true, query: true }; }, async ingest(input) { const key = identityKey(input); const existing = records.get(key); if (existing) { if (existing.payloadHash !== input.payloadHash) throw conflict(input); records.set(key, { ...existing, artifactLocators: input.artifactLocators }); return { disposition: "idempotent" }; } records.set(key, structuredClone(input)); return { disposition: "inserted" }; }, async updateArtifactAvailability(scope, runId, locatorIndex, reachable) { const key = identityKey({ ...scope, runId }); const existing = records.get(key); if (!existing?.artifactLocators[locatorIndex]) return; const artifactLocators = existing.artifactLocators.map((locator, index) => index === locatorIndex ? { ...locator, reachable } : locator); records.set(key, { ...existing, artifactLocators }); }, async overview(scope) { const runs = await this.runs(scope, { limit: 1, cursor: null }); return { scope: publicScope(scope), runCount: [...records.values()].filter((item) => matches(scope, item)).length, latest: runs[0] ?? null }; }, async runs(scope, options) { return sortRuns([...records.values()].filter((item) => matches(scope, item)).map(runFrom)).filter((run) => afterRunCursor(run, options.cursor)).slice(0, options.limit); }, async run(scope, runId) { const value = records.get(identityKey({ ...scope, runId })); return value ? { ...runFrom(value), payload: value.payload, findings: value.findings, artifactLocators: value.artifactLocators, timeline: value.timeline } : null; }, async view(scope, runId, view) { const detail = await this.run(scope, runId); const views = detail?.payload && typeof detail.payload === "object" ? (detail.payload as Record).views : null; const value = views && typeof views === "object" ? (views as Record)[view] : undefined; return value === undefined ? null : { runId, name: view, value }; }, async findings(scope, options) { return sortRuns([...records.values()].filter((item) => matches(scope, item)).map(runFrom)).flatMap((run) => { const source = records.get(identityKey(run))!; return source.findings.map((finding, findingIndex) => ({ ...run, findingIndex, finding })); }).sort(compareFindings).filter((finding) => afterFindingCursor(finding, options.cursor)).slice(0, options.limit); }, }; } function requireDatabaseUrl(databaseUrl: string | null | undefined): string { const value = databaseUrl ?? process.env.DATABASE_URL; if (!value) throw new MonitorCentralConfigurationError(); return value; } function requirePostgresOptions(value: PostgresMonitorCentralStoreOptions | undefined): PostgresMonitorCentralStoreOptions { if (!value) throw Object.assign(new Error("Monitor central PostgreSQL pool configuration must be injected"), { code: "monitor_central_pg_options_missing" }); return value; } function requirePositiveInteger(value: number | undefined, code: string): number { if (!Number.isSafeInteger(value) || value < 1) throw Object.assign(new Error(`${code} must be a positive integer`), { code }); return value; } function normalizeIngest(input: MonitorCentralIngest): NormalizedIngest { for (const key of ["sentinelId", "node", "lane", "runId"] as const) if (!input[key]) throw invalid(`missing required ingest field: ${key}`); const normalized = { ...input, updatedAt: input.updatedAt ?? new Date().toISOString(), status: input.status ?? "unknown", findings: input.findings ?? [], artifactLocators: (input.artifactLocators ?? []).map(normalizeLocator), timeline: input.timeline ?? [] }; return { ...normalized, payloadHash: `sha256:${createHash("sha256").update(stableJson({ status: normalized.status, payload: normalized.payload, findings: normalized.findings, artifactLocators: normalized.artifactLocators.map(immutableLocator), timeline: normalized.timeline })).digest("hex")}` }; } function immutableLocator(locator: MonitorArtifactLocator): Omit { const { reachable: _reachable, ...immutable } = locator; return immutable; } function normalizeLocator(value: MonitorArtifactLocator): MonitorArtifactLocator { for (const key of ["ownerNode", "ownerLane", "ownerPvc", "stateDir", "relativePath", "sha256", "kind"] as const) if (!value[key]) throw invalid(`artifact locator must include ${key}`); if (!Number.isSafeInteger(value.sizeBytes) || value.sizeBytes < 0) throw invalid("artifact locator sizeBytes must be a non-negative integer"); if (typeof value.reachable !== "boolean") throw invalid("artifact locator reachable must be boolean"); return { ...value }; } async function requestRecord(request: Request): Promise> { const value = await request.json(); if (!value || typeof value !== "object" || Array.isArray(value)) throw invalid("ingest request body must be an object"); return value as Record; } function scopeFrom(url: URL): MonitorScope { return { sentinelId: optional(url, "sentinelId"), node: optional(url, "node"), lane: optional(url, "lane") }; } function requireFullScope(scope: MonitorScope): Required { if (!scope.sentinelId || !scope.node || !scope.lane) throw Object.assign(invalid("run detail requires sentinelId, node, and lane"), { code: "scope_required" }); return scope as Required; } function optional(url: URL, name: string): string | undefined { return url.searchParams.get(name) || undefined; } function boundedLimit(value: string | null, pageSize: number): number { const parsed = Number(value ?? pageSize); return Number.isInteger(parsed) && parsed > 0 ? Math.min(parsed, pageSize) : pageSize; } function runQueryOptions(url: URL, pageSize: number): RunQueryOptions { return { limit: boundedLimit(url.searchParams.get("limit"), pageSize), cursor: parseRunCursor(url.searchParams.get("cursor")) }; } function findingQueryOptions(url: URL, pageSize: number): FindingQueryOptions { return { limit: boundedLimit(url.searchParams.get("limit"), pageSize), cursor: parseFindingCursor(url.searchParams.get("cursor")) }; } function parseRunCursor(value: string | null): RunCursor | null { return parseCursor(value, false) as RunCursor | null; } function parseFindingCursor(value: string | null): FindingCursor | null { return parseCursor(value, true) as FindingCursor | null; } function parseCursor(value: string | null, finding: boolean): RunCursor | FindingCursor | null { if (!value) return null; try { const decoded = JSON.parse(Buffer.from(value, "base64url").toString("utf8")); if (typeof decoded.updatedAt !== "string" || typeof decoded.runId !== "string" || (finding && !Number.isInteger(decoded.findingIndex))) throw new Error("invalid cursor"); return decoded; } catch { throw invalid("invalid cursor"); } } function runPage(items: readonly MonitorRun[]): Record { const last = items.at(-1); return { ok: true, items, nextCursor: last ? cursor(last) : null, valuesRedacted: true }; } function findingPage(items: readonly Record[]): Record { const last = items.at(-1); return { ok: true, items, nextCursor: last ? cursor({ updatedAt: last.updatedAt, runId: last.runId, findingIndex: last.findingIndex }) : null, valuesRedacted: true }; } function cursor(value: unknown): string { return Buffer.from(JSON.stringify(value)).toString("base64url"); } function json(body: Record, status = 200): Response { return Response.json(body, { status }); } function httpStatus(error: unknown): number { return (error as { code?: string })?.code === "payload_hash_conflict" ? 409 : error instanceof MonitorCentralConfigurationError ? 503 : 400; } export function structuredMonitorCentralError(error: unknown): Record { return error instanceof MonitorCentralConfigurationError ? error.toJSON().error as Record : { code: (error as { code?: string })?.code ?? "invalid_request", message: errorMessage(error) }; } function structuredError(error: unknown): Record { return structuredMonitorCentralError(error); } function invalid(message: string): Error & { code: string } { return Object.assign(new Error(message), { code: "invalid_request" }); } function errorMessage(error: unknown): string { return error instanceof Error ? error.message.replace(/postgres(?:ql)?:\/\/[^\s]+/giu, "postgres://") : "unknown error"; } function identityOf(value: MonitorScope & { readonly runId: string }): Record { return { sentinelId: value.sentinelId, node: value.node, lane: value.lane, runId: value.runId }; } function identityKey(value: MonitorScope & { readonly runId: string }): string { return [value.sentinelId, value.node, value.lane, value.runId].join("\u0000"); } function conflict(value: MonitorCentralIngest): Error & { code: string } { return Object.assign(new Error("ingest identity already has a different terminal envelope"), { code: "payload_hash_conflict", identity: identityOf(value) }); } function publicScope(scope: MonitorScope): Record { return { sentinelId: scope.sentinelId ?? null, node: scope.node ?? null, lane: scope.lane ?? null }; } function matches(scope: MonitorScope, value: MonitorScope): boolean { return (!scope.sentinelId || scope.sentinelId === value.sentinelId) && (!scope.node || scope.node === value.node) && (!scope.lane || scope.lane === value.lane); } function runFrom(value: NormalizedIngest): MonitorRun { return { sentinelId: value.sentinelId, node: value.node, lane: value.lane, runId: value.runId, updatedAt: value.updatedAt, status: value.status, payloadHash: value.payloadHash }; } function sortRuns(runs: readonly MonitorRun[]): MonitorRun[] { return [...runs].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt) || right.runId.localeCompare(left.runId)); } function afterRunCursor(run: MonitorRun, cursorValue: RunCursor | null): boolean { return !cursorValue || run.updatedAt < cursorValue.updatedAt || (run.updatedAt === cursorValue.updatedAt && run.runId < cursorValue.runId); } function compareFindings(left: Record, right: Record): number { return String(right.updatedAt).localeCompare(String(left.updatedAt)) || String(right.runId).localeCompare(String(left.runId)) || Number(right.findingIndex) - Number(left.findingIndex); } function afterFindingCursor(finding: Record, cursorValue: FindingCursor | null): boolean { if (!cursorValue) return true; return String(finding.updatedAt) < cursorValue.updatedAt || (finding.updatedAt === cursorValue.updatedAt && (String(finding.runId) < cursorValue.runId || (finding.runId === cursorValue.runId && Number(finding.findingIndex) < cursorValue.findingIndex))); } function stableJson(value: unknown): string { if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`; if (value && typeof value === "object") return `{${Object.entries(value as Record).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`; return JSON.stringify(value); } function scopeWhere(scope: MonitorScope): { readonly text: string; readonly values: readonly string[] } { const entries = Object.entries({ sentinel_id: scope.sentinelId, node: scope.node, lane: scope.lane }).filter(([, value]) => value); return { text: entries.length ? entries.map(([key], index) => `r.${key} = $${index + 1}`).join(" and ") : "true", values: entries.map(([, value]) => String(value)) }; } function identityWhere(scope: Required, runId: string): { readonly text: string; readonly values: readonly string[] } { return { text: "r.sentinel_id = $1 and r.node = $2 and r.lane = $3 and r.run_id = $4", values: [scope.sentinelId, scope.node, scope.lane, runId] }; } async function queryRuns(sql: postgres.Sql, scope: MonitorScope, options: RunQueryOptions): Promise { const where = scopeWhere(scope); const values: unknown[] = [...where.values]; let cursorClause = ""; if (options.cursor) { values.push(options.cursor.updatedAt, options.cursor.runId); cursorClause = `and (r.updated_at, r.run_id) < ($${values.length - 1}, $${values.length})`; } values.push(options.limit); const rows = await sql.unsafe(`select r.sentinel_id, r.node, r.lane, r.run_id, r.updated_at, r.status, r.payload_hash from monitor.runs r where ${where.text} ${cursorClause} order by r.updated_at desc, r.run_id desc limit $${values.length}`, values); return rows.map(runRow); } async function countRuns(sql: postgres.Sql, scope: MonitorScope): Promise { const where = scopeWhere(scope); const rows = await sql.unsafe(`select count(*)::int as count from monitor.runs r where ${where.text}`, where.values); return Number(rows[0]?.count ?? 0); } async function updatePostgresArtifactAvailability(transaction: postgres.TransactionSql, input: NormalizedIngest): Promise { for (const [index, locator] of input.artifactLocators.entries()) await transaction` update monitor.artifact_locators set reachable = ${locator.reachable} where sentinel_id = ${input.sentinelId} and node = ${input.node} and lane = ${input.lane} and run_id = ${input.runId} and locator_index = ${index}`; } function rowIdentity(row: Record): { readonly text: string; readonly values: readonly string[] } { return { text: "sentinel_id = $1 and node = $2 and lane = $3 and run_id = $4", values: [String(row.sentinel_id), String(row.node), String(row.lane), String(row.run_id)] }; } function runRow(row: Record): MonitorRun { return { sentinelId: String(row.sentinel_id), node: String(row.node), lane: String(row.lane), runId: String(row.run_id), updatedAt: new Date(String(row.updated_at)).toISOString(), status: String(row.status), payloadHash: String(row.payload_hash) }; } export function mapPostgresFindingRow(row: Record): Record { return { ...runRow(row), findingIndex: Number(row.finding_index), finding: row.finding }; } function locatorRow(row: Record): MonitorArtifactLocator { return { ownerNode: String(row.owner_node), ownerLane: String(row.owner_lane), ownerPvc: String(row.owner_pvc), stateDir: String(row.state_dir), relativePath: String(row.relative_path), sha256: String(row.sha256), sizeBytes: Number(row.size_bytes), kind: String(row.kind), reachable: row.reachable === true }; }