fix: close Release A monitor migration blockers

This commit is contained in:
AgentRun Codex
2026-07-12 23:24:41 +00:00
parent e3d21ed4e1
commit c101b2543e
9 changed files with 162 additions and 64 deletions
+29 -4
View File
@@ -4,7 +4,7 @@ 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 const MONITOR_CENTRAL_SCHEMA_VERSION = "2026-07-12-p17-3";
export interface MonitorScope {
readonly sentinelId?: string;
@@ -52,7 +52,8 @@ export interface MonitorCentralStore {
schemaVersion(): Promise<string | null>;
capability(): Promise<{ readonly ingest: boolean; readonly query: boolean }>;
ingest(input: NormalizedMonitorCentralIngest): Promise<{ readonly disposition: "inserted" | "idempotent" }>;
importSource(manifest: MonitorImportManifest, inputs: readonly NormalizedMonitorCentralIngest[]): Promise<{ readonly disposition: "inserted" | "idempotent"; readonly runs: readonly { readonly disposition: "inserted" | "idempotent" }[] }>;
importSource(manifest: MonitorImportManifest, inputs: readonly NormalizedMonitorCentralIngest[], runtimeMetadata?: readonly MonitorRuntimeMetadata[]): Promise<{ readonly disposition: "inserted" | "idempotent"; readonly runs: readonly { readonly disposition: "inserted" | "idempotent" }[] }>;
runtimeMetadata(scope: Required<MonitorScope>): Promise<readonly MonitorRuntimeMetadata[]>;
updateArtifactAvailability(scope: Required<MonitorScope>, runId: string, locatorIndex: number, reachable: boolean): Promise<void>;
overview(scope: MonitorScope): Promise<Record<string, unknown>>;
runs(scope: MonitorScope, options: RunQueryOptions): Promise<readonly MonitorRun[]>;
@@ -71,6 +72,12 @@ export interface MonitorImportManifest {
readonly rowCount: number;
}
export interface MonitorRuntimeMetadata extends Required<MonitorScope> {
readonly key: string;
readonly value: unknown;
readonly updatedAt: string;
}
interface RunCursor {
readonly updatedAt: string;
readonly runId: string;
@@ -197,7 +204,7 @@ export function createPostgresMonitorCentralStore(databaseUrl: string, options:
async ingest(input) {
return sql.begin((transaction) => writePostgresIngest(transaction, input));
},
async importSource(manifest, inputs) {
async importSource(manifest, inputs, runtimeMetadata = []) {
return sql.begin(async (transaction) => {
const claimed = await transaction<{ manifest_id: string }[]>`
insert into monitor.import_manifests (manifest_id, source_fingerprint, row_count, imported_at)
@@ -213,9 +220,23 @@ export function createPostgresMonitorCentralStore(databaseUrl: string, options:
}
const runs = [] as { disposition: "inserted" | "idempotent" }[];
for (const input of inputs) runs.push(await writePostgresIngest(transaction, input));
for (const item of runtimeMetadata) {
await transaction`
insert into monitor.runners (sentinel_id, node, lane, updated_at)
values (${item.sentinelId}, ${item.node}, ${item.lane}, ${item.updatedAt})
on conflict (sentinel_id, node, lane) do update set updated_at = greatest(monitor.runners.updated_at, excluded.updated_at)`;
await transaction`
insert into monitor.runtime_metadata (sentinel_id, node, lane, key, value, updated_at)
values (${item.sentinelId}, ${item.node}, ${item.lane}, ${item.key}, ${JSON.stringify(item.value)}::jsonb, ${item.updatedAt})
on conflict (sentinel_id, node, lane, key) do update set value = excluded.value, updated_at = excluded.updated_at`;
}
return { disposition: "inserted" as const, runs };
});
},
async runtimeMetadata(scope) {
const rows = await sql<({ key: string; value: unknown; updated_at: string })[]>`select key, value, updated_at from monitor.runtime_metadata where sentinel_id = ${scope.sentinelId} and node = ${scope.node} and lane = ${scope.lane} order by key`;
return rows.map((item) => ({ ...scope, key: item.key, value: item.value, updatedAt: new Date(item.updated_at).toISOString() }));
},
async updateArtifactAvailability(scope, runId, locatorIndex, reachable) {
await sql`
update monitor.artifact_locators
@@ -303,12 +324,14 @@ export const MONITOR_CENTRAL_MIGRATIONS = [
"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.runtime_metadata (sentinel_id text not null, node text not null, lane text not null, key text not null, value jsonb not null, updated_at timestamptz not null, primary key (sentinel_id, node, lane, key), foreign key (sentinel_id, node, lane) references monitor.runners (sentinel_id, node, lane))",
"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<string, NormalizedMonitorCentralIngest>();
const manifests = new Map<string, MonitorImportManifest>();
const metadata = new Map<string, MonitorRuntimeMetadata>();
return {
async close() {},
async migrate() {},
@@ -326,7 +349,7 @@ export function createInMemoryMonitorCentralStore(): MonitorCentralStore {
records.set(key, structuredClone(input));
return { disposition: "inserted" };
},
async importSource(manifest, inputs) {
async importSource(manifest, inputs, runtimeMetadata = []) {
const previous = manifests.get(manifest.manifestId);
if (previous) {
if (previous.sourceFingerprint !== manifest.sourceFingerprint || previous.rowCount !== manifest.rowCount) throw Object.assign(new Error("monitor import manifest conflict"), { code: "monitor_import_manifest_conflict" });
@@ -342,9 +365,11 @@ export function createInMemoryMonitorCentralStore(): MonitorCentralStore {
}
records.clear();
for (const [key, value] of staged) records.set(key, value);
for (const item of runtimeMetadata) metadata.set(`${item.sentinelId}/${item.node}/${item.lane}/${item.key}`, structuredClone(item));
manifests.set(manifest.manifestId, { ...manifest });
return { disposition: "inserted", runs };
},
async runtimeMetadata(scope) { return [...metadata.values()].filter((item) => item.sentinelId === scope.sentinelId && item.node === scope.node && item.lane === scope.lane).sort((left, right) => left.key.localeCompare(right.key)).map(structuredClone); },
async updateArtifactAvailability(scope, runId, locatorIndex, reachable) {
const key = identityKey({ ...scope, runId });
const existing = records.get(key);