fix: close Release A monitor migration blockers
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user