From 3e4bd1ca8f4983f48ca68a8d4bd40d441b45e0d2 Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 07:30:41 +0800 Subject: [PATCH 01/32] test(track-b): validate graph registry contract at host boundary --- packages/extension-host/index.mjs | 27 +++++++++++++++++++ .../test/run95-registry-contract.test.ts | 11 ++++++++ .../packages/extension-host/index.mjs | 6 ++++- 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts diff --git a/packages/extension-host/index.mjs b/packages/extension-host/index.mjs index e531c5b3..1ec09555 100644 --- a/packages/extension-host/index.mjs +++ b/packages/extension-host/index.mjs @@ -9,6 +9,33 @@ import { verifySignedBundle, } from "../extension-sdk/index.mjs"; +/** + * Reject malformed graph-contract metadata at the host boundary. Extension + * packages must not be able to treat an incomplete registry as a usable + * contract, even when they are loaded independently from the Track-B bundle. + */ +export function validateGraphRegistry(registry) { + if (!registry || registry.version !== 1 || !Array.isArray(registry.kinds)) { + throw new Error("invalid graph registry"); + } + const seen = new Set(); + const kinds = registry.kinds.map((kind) => { + if ( + !kind?.id || + !Number.isInteger(kind.version) || + !kind.category || + !Array.isArray(kind.fields) + ) { + throw new Error("incomplete graph registry entry"); + } + const key = `${kind.id}@${kind.version}`; + if (seen.has(key)) throw new Error(`duplicate graph registry entry: ${key}`); + seen.add(key); + return Object.freeze({ ...kind, fields: Object.freeze([...kind.fields]) }); + }); + return Object.freeze({ version: registry.version, kinds: Object.freeze(kinds) }); +} + const runtimePath = fileURLToPath(new URL("./worker-runtime.mjs", import.meta.url)); const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const normalizeModuleUrl = (value) => diff --git a/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts b/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts new file mode 100644 index 00000000..b16e0e6d --- /dev/null +++ b/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts @@ -0,0 +1,11 @@ +import { describe, expect, test } from "vitest"; + +describe("Run 95 graph registry contract", () => { + test("SP0 rejects an incomplete graph registry before an extension can consume it", async () => { + const host = await import("../../../packages/extension-host/index.mjs"); + expect(host.validateGraphRegistry).toBeTypeOf("function"); + expect(() => + host.validateGraphRegistry({ version: 1, kinds: [{ id: "core.message", version: 1 }] }), + ).toThrow(/incomplete/i); + }); +}); diff --git a/role-model-router/packages/extension-host/index.mjs b/role-model-router/packages/extension-host/index.mjs index b4266be3..44555213 100644 --- a/role-model-router/packages/extension-host/index.mjs +++ b/role-model-router/packages/extension-host/index.mjs @@ -1,7 +1,11 @@ // Canonical Track B public extension substrate output. The process-isolated // implementation is shared with the public package entry point so existing // consumers and the phase-manifest path execute the same host. -export { ExtensionHost, ExtensionSupervisor } from "../../../packages/extension-host/index.mjs"; +export { + ExtensionHost, + ExtensionSupervisor, + validateGraphRegistry, +} from "../../../packages/extension-host/index.mjs"; export { createSignedBundle, decodeFrame, From 8ceb31459904ecc7fcfae891460bf35ac46bdb9f Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 07:35:33 +0800 Subject: [PATCH 02/32] feat(track-b): verify staged graph registry bindings --- .../src/track-b-runtime.ts | 27 +++++++ .../test/run95-registry-contract.test.ts | 70 +++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts b/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts index 486ee8df..3ee1f52b 100644 --- a/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts +++ b/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts @@ -794,6 +794,11 @@ export async function stageTrackBRuntimeDistribution(options: { const manifest = JSON.parse(manifestBytes.toString("utf8")) as { readonly schemaVersion: string; readonly publicSourceTree?: string; + readonly graphRegistry?: { + readonly version?: number; + readonly artifactSha256?: string; + readonly kinds?: readonly unknown[]; + }; readonly sidecar: { readonly modulePath: string; readonly artifactSha256: string }; readonly publicRuntimeAdapter?: { readonly modulePath: string; @@ -819,6 +824,28 @@ export async function stageTrackBRuntimeDistribution(options: { if (!compatibilityGeneration || manifest.extensions.length !== 13) { throw new Error("Track B runtime distribution manifest is unsupported or incomplete"); } + if ( + compatibilityGeneration === "N" && + (!manifest.graphRegistry || + manifest.graphRegistry.version !== 1 || + !/^[a-f0-9]{64}$/.test(manifest.graphRegistry.artifactSha256 ?? "") || + !Array.isArray(manifest.graphRegistry.kinds)) + ) { + throw new Error("Track B runtime distribution graph registry is missing or invalid"); + } + if (compatibilityGeneration === "N") { + const graphRegistryBytes = Buffer.from( + JSON.stringify({ + version: manifest.graphRegistry!.version, + kinds: manifest.graphRegistry!.kinds, + }), + "utf8", + ); + const graphRegistryDigest = createHash("sha256").update(graphRegistryBytes).digest("hex"); + if (graphRegistryDigest !== manifest.graphRegistry!.artifactSha256) { + throw new Error("Track B runtime distribution graph registry digest does not bind its contents"); + } + } if (options.expectedPublicSourceTree) { if ( !/^[0-9a-f]{40}$/.test(options.expectedPublicSourceTree) || diff --git a/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts b/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts index b16e0e6d..331d250b 100644 --- a/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts +++ b/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts @@ -1,5 +1,12 @@ +import { createHash } from "node:crypto"; +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; + import { describe, expect, test } from "vitest"; +import { stageTrackBRuntimeDistribution } from "../src/track-b-runtime.js"; + describe("Run 95 graph registry contract", () => { test("SP0 rejects an incomplete graph registry before an extension can consume it", async () => { const host = await import("../../../packages/extension-host/index.mjs"); @@ -8,4 +15,67 @@ describe("Run 95 graph registry contract", () => { host.validateGraphRegistry({ version: 1, kinds: [{ id: "core.message", version: 1 }] }), ).toThrow(/incomplete/i); }); + + test("SP0 refuses an N distribution that omits its graph registry", async () => { + const root = await mkdtemp(path.join(os.tmpdir(), "run95-registry-stage-")); + try { + const bytes = Buffer.from("export async function run(){return {available:true}}\n"); + const artifactSha256 = createHash("sha256").update(bytes).digest("hex"); + const extensions = Array.from({ length: 13 }, (_, index) => ({ + descriptor: { id: `extension-${index}`, protocolVersion: "1.1.0", capabilities: ["health"] }, + modulePath: `extensions/extension-${index}.mjs`, + artifactSha256, + })); + await mkdir(path.join(root, "extensions")); + await writeFile(path.join(root, "sidecar.mjs"), bytes); + await Promise.all(extensions.map((row) => writeFile(path.join(root, row.modulePath), bytes))); + await writeFile( + path.join(root, "track-b-runtime-manifest.json"), + JSON.stringify({ + schemaVersion: "role-model.track-b-runtime-distribution.v2", + sidecar: { modulePath: "sidecar.mjs", artifactSha256 }, + extensions, + }), + ); + await expect( + stageTrackBRuntimeDistribution({ sourceRoot: root, releaseDir: path.join(root, "release") }), + ).rejects.toThrow(/graph registry/i); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); + + test("SP0 refuses a graph registry descriptor whose digest does not bind its contents", async () => { + const root = await mkdtemp(path.join(os.tmpdir(), "run95-registry-digest-")); + try { + const bytes = Buffer.from("export async function run(){return {available:true}}\n"); + const artifactSha256 = createHash("sha256").update(bytes).digest("hex"); + const extensions = Array.from({ length: 13 }, (_, index) => ({ + descriptor: { id: `extension-${index}`, protocolVersion: "1.1.0", capabilities: ["health"] }, + modulePath: `extensions/extension-${index}.mjs`, + artifactSha256, + })); + await mkdir(path.join(root, "extensions")); + await writeFile(path.join(root, "sidecar.mjs"), bytes); + await Promise.all(extensions.map((row) => writeFile(path.join(root, row.modulePath), bytes))); + await writeFile( + path.join(root, "track-b-runtime-manifest.json"), + JSON.stringify({ + schemaVersion: "role-model.track-b-runtime-distribution.v2", + graphRegistry: { + version: 1, + artifactSha256: "0".repeat(64), + kinds: [{ id: "core.message", version: 1, category: "message", fields: [] }], + }, + sidecar: { modulePath: "sidecar.mjs", artifactSha256 }, + extensions, + }), + ); + await expect( + stageTrackBRuntimeDistribution({ sourceRoot: root, releaseDir: path.join(root, "release") }), + ).rejects.toThrow(/graph registry.*digest/i); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); }); From 44275641f4f446cc2bb747da8e0dc02f97cea569 Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 07:40:24 +0800 Subject: [PATCH 03/32] test(track-b): bind v2 registry fixtures to staged contract --- .../src/track-b-runtime.ts | 21 +++++++++ .../test/recursive-87-compatibility.test.ts | 27 +++++++++++ .../test/run95-registry-contract.test.ts | 46 +++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts b/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts index 3ee1f52b..f36c25cf 100644 --- a/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts +++ b/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts @@ -799,6 +799,17 @@ export async function stageTrackBRuntimeDistribution(options: { readonly artifactSha256?: string; readonly kinds?: readonly unknown[]; }; + readonly registryBindings?: { + readonly graphRegistry?: { + readonly schemaVersion?: string; + readonly version?: number; + readonly path?: string; + }; + readonly storageRegistry?: { + readonly schemaVersion?: string; + readonly modulePath?: string; + }; + }; readonly sidecar: { readonly modulePath: string; readonly artifactSha256: string }; readonly publicRuntimeAdapter?: { readonly modulePath: string; @@ -833,6 +844,16 @@ export async function stageTrackBRuntimeDistribution(options: { ) { throw new Error("Track B runtime distribution graph registry is missing or invalid"); } + if ( + compatibilityGeneration === "N" && + (manifest.registryBindings?.graphRegistry?.schemaVersion !== "role-model.graph-registry.v1" || + manifest.registryBindings?.graphRegistry?.version !== 1 || + manifest.registryBindings?.graphRegistry?.path !== "shared/graph/registry.json" || + manifest.registryBindings.storageRegistry?.schemaVersion !== "role-model.storage-registry.v1" || + manifest.registryBindings?.storageRegistry?.modulePath !== "shared/retention/index.mjs") + ) { + throw new Error("Track B runtime distribution registry bindings are missing or invalid"); + } if (compatibilityGeneration === "N") { const graphRegistryBytes = Buffer.from( JSON.stringify({ diff --git a/role-model-router/apps/runtime-host-bridge/test/recursive-87-compatibility.test.ts b/role-model-router/apps/runtime-host-bridge/test/recursive-87-compatibility.test.ts index 759944ec..92d46297 100644 --- a/role-model-router/apps/runtime-host-bridge/test/recursive-87-compatibility.test.ts +++ b/role-model-router/apps/runtime-host-bridge/test/recursive-87-compatibility.test.ts @@ -11,6 +11,28 @@ import { trackBDistributionRequiresSQLiteMaintenance, } from "../src/track-b-runtime.js"; +const graphRegistryKinds = [ + { id: "core.message", version: 1, category: "message", fields: [] }, +]; +const graphRegistry = { + version: 1, + artifactSha256: createHash("sha256") + .update(JSON.stringify({ version: 1, kinds: graphRegistryKinds })) + .digest("hex"), + kinds: graphRegistryKinds, +}; +const registryBindings = { + graphRegistry: { + schemaVersion: "role-model.graph-registry.v1", + version: 1, + path: "shared/graph/registry.json", + }, + storageRegistry: { + schemaVersion: "role-model.storage-registry.v1", + modulePath: "shared/retention/index.mjs", + }, +}; + test("AR6 refuses a package provenance stamp when the public source tree is dirty", () => { expect(() => resolvePackagedRuntimeSourceTree({ @@ -70,6 +92,9 @@ test("SP7 stages N and N-1 distributions and refuses unsupported future versions path.join(root, "track-b-runtime-manifest.json"), JSON.stringify({ schemaVersion: version, + ...(version === "role-model.track-b-runtime-distribution.v2" + ? { graphRegistry, registryBindings } + : {}), sidecar: { modulePath: "sidecar.mjs", artifactSha256 }, extensions, }), @@ -123,6 +148,8 @@ test("SP7 refuses a current Track B distribution built from another public sourc JSON.stringify({ schemaVersion: "role-model.track-b-runtime-distribution.v2", publicSourceTree: "a".repeat(40), + graphRegistry, + registryBindings, sidecar: { modulePath: "sidecar.mjs", artifactSha256 }, extensions, }), diff --git a/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts b/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts index 331d250b..1bdc118b 100644 --- a/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts +++ b/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts @@ -67,6 +67,17 @@ describe("Run 95 graph registry contract", () => { artifactSha256: "0".repeat(64), kinds: [{ id: "core.message", version: 1, category: "message", fields: [] }], }, + registryBindings: { + graphRegistry: { + schemaVersion: "role-model.graph-registry.v1", + version: 1, + path: "shared/graph/registry.json", + }, + storageRegistry: { + schemaVersion: "role-model.storage-registry.v1", + modulePath: "shared/retention/index.mjs", + }, + }, sidecar: { modulePath: "sidecar.mjs", artifactSha256 }, extensions, }), @@ -78,4 +89,39 @@ describe("Run 95 graph registry contract", () => { await rm(root, { recursive: true, force: true }); } }); + + test("SP0 refuses an N distribution that does not bind graph and storage registries", async () => { + const root = await mkdtemp(path.join(os.tmpdir(), "run95-registry-bindings-")); + try { + const bytes = Buffer.from("export async function run(){return {available:true}}\n"); + const artifactSha256 = createHash("sha256").update(bytes).digest("hex"); + const kinds = [{ id: "core.message", version: 1, category: "message", fields: [] }]; + const extensions = Array.from({ length: 13 }, (_, index) => ({ + descriptor: { id: `extension-${index}`, protocolVersion: "1.1.0", capabilities: ["health"] }, + modulePath: `extensions/extension-${index}.mjs`, + artifactSha256, + })); + await mkdir(path.join(root, "extensions")); + await writeFile(path.join(root, "sidecar.mjs"), bytes); + await Promise.all(extensions.map((row) => writeFile(path.join(root, row.modulePath), bytes))); + await writeFile( + path.join(root, "track-b-runtime-manifest.json"), + JSON.stringify({ + schemaVersion: "role-model.track-b-runtime-distribution.v2", + graphRegistry: { + version: 1, + artifactSha256: createHash("sha256").update(JSON.stringify({ version: 1, kinds })).digest("hex"), + kinds, + }, + sidecar: { modulePath: "sidecar.mjs", artifactSha256 }, + extensions, + }), + ); + await expect( + stageTrackBRuntimeDistribution({ sourceRoot: root, releaseDir: path.join(root, "release") }), + ).rejects.toThrow(/registry bindings/i); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); }); From 00f2a31b1abd288e4111ed4afddd63aedeb66f3e Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 07:47:25 +0800 Subject: [PATCH 04/32] test(track-b): cover graph registry boundary cases --- .../test/run95-registry-contract.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts b/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts index 1bdc118b..50bfa5fd 100644 --- a/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts +++ b/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts @@ -14,6 +14,22 @@ describe("Run 95 graph registry contract", () => { expect(() => host.validateGraphRegistry({ version: 1, kinds: [{ id: "core.message", version: 1 }] }), ).toThrow(/incomplete/i); + expect(() => + host.validateGraphRegistry({ + version: 1, + kinds: [ + { id: "core.message", version: 1, category: "node", fields: [] }, + { id: "core.message", version: 1, category: "node", fields: [] }, + ], + }), + ).toThrow(/duplicate/i); + expect(() => host.validateGraphRegistry({ version: 2, kinds: [] })).toThrow(/invalid/i); + expect( + host.validateGraphRegistry({ + version: 1, + kinds: [{ id: "extension.synthetic.archive", version: 9, category: "archive", fields: [] }], + }).kinds[0]?.id, + ).toBe("extension.synthetic.archive"); }); test("SP0 refuses an N distribution that omits its graph registry", async () => { From fa8b711a2f70cd54daefed7425ea6e4014937b8b Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 08:03:10 +0800 Subject: [PATCH 05/32] feat(trace): bind message graph occurrence provenance --- .../packages/trace/src/lineage.ts | 18 +++++ .../packages/trace/test/run91-lineage.test.ts | 9 ++- .../test/run95-occurrence-lineage.test.ts | 71 +++++++++++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 role-model-router/packages/trace/test/run95-occurrence-lineage.test.ts diff --git a/role-model-router/packages/trace/src/lineage.ts b/role-model-router/packages/trace/src/lineage.ts index 9923e57b..bec06701 100644 --- a/role-model-router/packages/trace/src/lineage.ts +++ b/role-model-router/packages/trace/src/lineage.ts @@ -36,6 +36,11 @@ export interface TraceLineageStageReceipt { readonly predecessor_stage_id?: string; readonly policy_receipt?: string; readonly artifact_hash?: string; + /** Immutable causal event identity; never substitutes for the content id. */ + readonly occurrence_id?: string; + /** Content-addressed payload identity, which may be shared by occurrences. */ + readonly content_id?: string; + readonly predecessor_occurrence_id?: string; readonly pending_intent?: boolean; readonly source_ids?: readonly string[]; } @@ -131,6 +136,19 @@ function validateStage( ) { throw new Error(`Trace stage ${stage.stage_id} message_graph requires an artifact hash.`); } + if ( + stage.stage === "message_graph" && + ["recorded", "emitted", "consumed"].includes(stage.disposition) + ) { + if (!stage.occurrence_id) + throw new Error(`Trace stage ${stage.stage_id} message_graph requires occurrence_id.`); + if (!stage.content_id) + throw new Error(`Trace stage ${stage.stage_id} message_graph requires content_id.`); + assertOpaqueId("occurrence_id", stage.occurrence_id); + assertOpaqueId("content_id", stage.content_id); + if (stage.predecessor_occurrence_id !== undefined) + assertOpaqueId("predecessor_occurrence_id", stage.predecessor_occurrence_id); + } if (expectedPredecessor !== undefined && stage.predecessor_stage_id !== expectedPredecessor) { throw new Error(`Trace stage ${stage.stage_id} predecessor does not match stage order.`); } diff --git a/role-model-router/packages/trace/test/run91-lineage.test.ts b/role-model-router/packages/trace/test/run91-lineage.test.ts index 824f2ca5..422b33a8 100644 --- a/role-model-router/packages/trace/test/run91-lineage.test.ts +++ b/role-model-router/packages/trace/test/run91-lineage.test.ts @@ -39,7 +39,14 @@ function makeInput(overrides: Partial = {}): TraceLin receipt_id: `${stage}-receipt-001`, disposition: stage === "recommendation" ? "not_eligible" : "recorded", ...(stage === "recommendation" ? { policy_receipt: "recommendation-policy:disabled" } : {}), - ...(stage === "message_graph" ? { artifact_hash: "sha256:graph-001" } : {}), + ...(stage === "message_graph" + ? { + artifact_hash: "sha256:graph-001", + occurrence_id: "occurrence-run91-message-001", + content_id: "content-run91-message-001", + predecessor_occurrence_id: "occurrence-run91-root-001", + } + : {}), predecessor_stage_id: index > 0 ? `${stageNames[index - 1]}-receipt-001` : undefined, })), ...overrides, diff --git a/role-model-router/packages/trace/test/run95-occurrence-lineage.test.ts b/role-model-router/packages/trace/test/run95-occurrence-lineage.test.ts new file mode 100644 index 00000000..c247dee1 --- /dev/null +++ b/role-model-router/packages/trace/test/run95-occurrence-lineage.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from "vitest"; + +import { type TraceLineageManifestInput, createTraceLineageManifest } from "../src/index.js"; + +const stages = [ + "router_ingress", + "routing_attempt", + "upstream_execution", + "runtime_observation", + "message_graph", + "contribution", + "crowdsourcing", + "recommendation", +] as const; + +function input(): TraceLineageManifestInput { + const common = { + request_id: "request-occurrence-1", + routing_decision_id: "decision-occurrence-1", + endpoint_id: "deepseek.personal.deepseek-v4-flash-max", + model_id: "deepseek/deepseek-v4-flash", + reasoning_effort: "max", + effort_source: "variant" as const, + }; + return { + ...common, + source_set: ["request:request-occurrence-1"], + stages: stages.map((stage, index) => ({ + ...common, + stage_id: `stage-${stage}`, + stage, + receipt_id: `receipt-${stage}`, + disposition: "recorded" as const, + predecessor_stage_id: index ? `stage-${stages[index - 1]}` : undefined, + ...(stage === "message_graph" + ? { + artifact_hash: "sha256:message-content-1", + occurrence_id: "occurrence-message-1", + content_id: "content-message-1", + predecessor_occurrence_id: "occurrence-root-1", + } + : {}), + })), + }; +} + +describe("Run 95 occurrence-aware trace lineage", () => { + it("requires message-graph receipts to bind separate occurrence and content identities", () => { + const manifest = createTraceLineageManifest(input()); + expect(manifest.stages.find((stage) => stage.stage === "message_graph")).toMatchObject({ + occurrence_id: "occurrence-message-1", + content_id: "content-message-1", + predecessor_occurrence_id: "occurrence-root-1", + reasoning_effort: "max", + effort_source: "variant", + }); + + const base = input(); + const missingContent = { + ...base, + stages: base.stages.map((stage) => { + if (stage.stage !== "message_graph") return stage; + const { content_id: _contentId, ...withoutContent } = stage as typeof stage & { + content_id?: string; + }; + return withoutContent; + }), + } as TraceLineageManifestInput; + expect(() => createTraceLineageManifest(missingContent)).toThrow(/content_id/i); + }); +}); From d6bf71965ba2bc7c3235fe4c7e9588c9e3b124ae Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 08:10:03 +0800 Subject: [PATCH 06/32] feat(sqlite): validate deduplicated storage inventories --- .../packages/sqlite-memory/src/index.ts | 57 +++++++++++++++++++ .../test/run95-bounded-storage.test.ts | 38 +++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 role-model-router/packages/sqlite-memory/test/run95-bounded-storage.test.ts diff --git a/role-model-router/packages/sqlite-memory/src/index.ts b/role-model-router/packages/sqlite-memory/src/index.ts index e2fb5b00..3fe7ad2e 100644 --- a/role-model-router/packages/sqlite-memory/src/index.ts +++ b/role-model-router/packages/sqlite-memory/src/index.ts @@ -4800,6 +4800,63 @@ export function readRuntimeObservationStorageRecord( return parsed as Readonly>; } +/** + * Consumer-side guard for the storage inventory produced by Track B. It keeps + * physical measurement authority in one resource row and makes any logical + * byte allocation explicit rather than silently double-charging it. + */ +export function validateBoundedStorageInventory(inventory: { + readonly accountingState: string; + readonly uniquePhysicalBytes: number; + readonly physicalResources: readonly { + readonly id: string; + readonly physicalBytes: number | null; + readonly health: string; + readonly logicalClassIds: readonly string[]; + }[]; + readonly logicalClasses: readonly { + readonly id: string; + readonly physicalResourceId: string | null; + readonly physicalBytes: number | null; + }[]; +}): void { + if (inventory.accountingState !== "physical_resources_deduplicated") { + throw new Error("storage inventory must declare deduplicated physical accounting"); + } + const resourceIds = new Set(); + let measuredTotal = 0; + for (const resource of inventory.physicalResources) { + if (!resource.id || resourceIds.has(resource.id)) { + throw new Error("storage inventory contains duplicate physical resources"); + } + resourceIds.add(resource.id); + if (resource.health === "unavailable") { + if (resource.physicalBytes !== null) { + throw new Error("unavailable physical resource bytes must remain unknown"); + } + continue; + } + if (!Number.isSafeInteger(resource.physicalBytes) || resource.physicalBytes < 0) { + throw new Error("measured physical resource bytes must be non-negative integers"); + } + measuredTotal += resource.physicalBytes; + } + if (inventory.uniquePhysicalBytes !== measuredTotal) { + throw new Error("unique physical byte total does not match deduplicated resources"); + } + for (const logicalClass of inventory.logicalClasses) { + if (logicalClass.physicalBytes !== null) { + throw new Error("logical storage classes cannot independently charge physical bytes"); + } + if ( + logicalClass.physicalResourceId !== null && + !resourceIds.has(logicalClass.physicalResourceId) + ) { + throw new Error("logical storage class references an unknown physical resource"); + } + } +} + export interface ReadObservationTelemetryColumnsInput { readonly databasePath: string; readonly requestId: string; diff --git a/role-model-router/packages/sqlite-memory/test/run95-bounded-storage.test.ts b/role-model-router/packages/sqlite-memory/test/run95-bounded-storage.test.ts new file mode 100644 index 00000000..6eb77819 --- /dev/null +++ b/role-model-router/packages/sqlite-memory/test/run95-bounded-storage.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, test } from "vitest"; + +import * as sqliteMemory from "../src/index.js"; + +describe("Run 95 bounded storage inventory", () => { + test("accepts a deduplicated physical inventory and rejects a logical double charge", () => { + const inventory = { + accountingState: "physical_resources_deduplicated", + uniquePhysicalBytes: 4096, + physicalResources: [ + { + id: "physical-history", + physicalBytes: 4096, + health: "ready", + logicalClassIds: ["history", "history-index"], + }, + { + id: "physical-cloud", + physicalBytes: null, + health: "unavailable", + logicalClassIds: ["cloud-history"], + }, + ], + logicalClasses: [ + { id: "history", physicalResourceId: "physical-history", physicalBytes: null }, + { id: "history-index", physicalResourceId: "physical-history", physicalBytes: null }, + { id: "cloud-history", physicalResourceId: "physical-cloud", physicalBytes: null }, + ], + }; + expect(() => sqliteMemory.validateBoundedStorageInventory(inventory)).not.toThrow(); + expect(() => + sqliteMemory.validateBoundedStorageInventory({ + ...inventory, + logicalClasses: [{ ...inventory.logicalClasses[0], physicalBytes: 4096 }], + }), + ).toThrow(/logical|double|physical/i); + }); +}); From 1c98c2a03aaf72eddb31b7a594a5bb997f222780 Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 08:23:28 +0800 Subject: [PATCH 07/32] feat(sqlite): validate occurrence migration cutover --- .../sqlite-memory/src/legacy-migration.ts | 46 +++++++++++++++++++ .../test/run95-migration-parity.test.ts | 25 ++++++++++ 2 files changed, 71 insertions(+) create mode 100644 role-model-router/packages/sqlite-memory/test/run95-migration-parity.test.ts diff --git a/role-model-router/packages/sqlite-memory/src/legacy-migration.ts b/role-model-router/packages/sqlite-memory/src/legacy-migration.ts index 27e71524..48a3c345 100644 --- a/role-model-router/packages/sqlite-memory/src/legacy-migration.ts +++ b/role-model-router/packages/sqlite-memory/src/legacy-migration.ts @@ -16,6 +16,52 @@ export type LegacyMigrationState = | "rolled_back" | "failed"; +export type OccurrenceMigrationState = + | "v2_primary" + | "occurrence_shadow" + | "occurrence_parity" + | "occurrence_primary" + | "v2_archive_retired"; + +/** + * Cross-repository cutover guard shared by the public SQLite reader and the + * private occurrence authority. It does not mutate journal state: callers must + * persist the accepted transition atomically with their own cursor/checkpoint. + */ +export function validateOccurrenceMigrationTransition(input: { + readonly from: OccurrenceMigrationState; + readonly to: OccurrenceMigrationState; + readonly parityVerified: boolean; + readonly backupVerified: boolean; + readonly consumersVerified: boolean; + readonly rollbackWindowVerified?: boolean; +}): void { + const allowed = new Set([ + "v2_primary:occurrence_shadow", + "occurrence_shadow:occurrence_parity", + "occurrence_parity:occurrence_primary", + "occurrence_primary:v2_archive_retired", + ]); + const transition = `${input.from}:${input.to}`; + if (input.to === "occurrence_primary" && input.from !== "occurrence_parity") { + throw new Error("occurrence cutover requires the explicit verified parity state"); + } + if (!allowed.has(transition)) + throw new Error(`unsupported occurrence migration transition ${transition}`); + if (input.to === "occurrence_parity" && !input.parityVerified) { + throw new Error("occurrence migration parity must be verified before parity state"); + } + if ( + input.to === "occurrence_primary" && + (!input.parityVerified || !input.backupVerified || !input.consumersVerified) + ) { + throw new Error("occurrence cutover requires verified parity, backup, and consumers"); + } + if (input.to === "v2_archive_retired" && input.rollbackWindowVerified !== true) { + throw new Error("occurrence archive retirement requires a verified rollback window"); + } +} + export interface LegacyArtifactWriteInput { readonly scopeId: string; readonly sourceId: string; diff --git a/role-model-router/packages/sqlite-memory/test/run95-migration-parity.test.ts b/role-model-router/packages/sqlite-memory/test/run95-migration-parity.test.ts new file mode 100644 index 00000000..5cbb70be --- /dev/null +++ b/role-model-router/packages/sqlite-memory/test/run95-migration-parity.test.ts @@ -0,0 +1,25 @@ +import { expect, test } from "vitest"; + +import * as sqliteMemory from "../src/index.js"; + +test("Run 95 occurrence migration state only permits cutover after parity and recovery proof", () => { + expect(() => + sqliteMemory.validateOccurrenceMigrationTransition({ + from: "occurrence_shadow", + to: "occurrence_primary", + parityVerified: false, + backupVerified: true, + consumersVerified: true, + }), + ).toThrow(/parity/i); + + expect(() => + sqliteMemory.validateOccurrenceMigrationTransition({ + from: "occurrence_parity", + to: "occurrence_primary", + parityVerified: true, + backupVerified: true, + consumersVerified: true, + }), + ).not.toThrow(); +}); From 9ba2df684afb8b69328058d3be35f43a11df4565 Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 08:37:17 +0800 Subject: [PATCH 08/32] feat(runtime): propagate occurrence provenance to extensions --- .../src/track-b-projections.ts | 2 + .../src/track-b-runtime.ts | 41 +++++++- .../test/run95-all13-correlation.test.ts | 96 +++++++++++++++++++ 3 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 role-model-router/apps/runtime-host-bridge/test/run95-all13-correlation.test.ts diff --git a/role-model-router/apps/runtime-host-bridge/src/track-b-projections.ts b/role-model-router/apps/runtime-host-bridge/src/track-b-projections.ts index 0d12aa56..ff570725 100644 --- a/role-model-router/apps/runtime-host-bridge/src/track-b-projections.ts +++ b/role-model-router/apps/runtime-host-bridge/src/track-b-projections.ts @@ -11,6 +11,7 @@ export async function consumeTrackBProjection( readonly channel: string; readonly authorizationEpoch: number; readonly identity?: Readonly>; + readonly occurrence?: Readonly<{ occurrenceId: string; contentId: string }>; }, ) { const projection = validateProjectionV2(value); @@ -43,6 +44,7 @@ export async function consumeTrackBProjection( authorizationEpoch: input.authorizationEpoch, capability, ...(input.identity ? { identity: input.identity } : {}), + ...(input.occurrence ? { occurrence: input.occurrence } : {}), projection, }), ); diff --git a/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts b/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts index f36c25cf..2eeb5cc3 100644 --- a/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts +++ b/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts @@ -849,22 +849,27 @@ export async function stageTrackBRuntimeDistribution(options: { (manifest.registryBindings?.graphRegistry?.schemaVersion !== "role-model.graph-registry.v1" || manifest.registryBindings?.graphRegistry?.version !== 1 || manifest.registryBindings?.graphRegistry?.path !== "shared/graph/registry.json" || - manifest.registryBindings.storageRegistry?.schemaVersion !== "role-model.storage-registry.v1" || + manifest.registryBindings.storageRegistry?.schemaVersion !== + "role-model.storage-registry.v1" || manifest.registryBindings?.storageRegistry?.modulePath !== "shared/retention/index.mjs") ) { throw new Error("Track B runtime distribution registry bindings are missing or invalid"); } if (compatibilityGeneration === "N") { + const graphRegistry = manifest.graphRegistry; + if (!graphRegistry) throw new Error("Track B runtime distribution graph registry is missing"); const graphRegistryBytes = Buffer.from( JSON.stringify({ - version: manifest.graphRegistry!.version, - kinds: manifest.graphRegistry!.kinds, + version: graphRegistry.version, + kinds: graphRegistry.kinds, }), "utf8", ); const graphRegistryDigest = createHash("sha256").update(graphRegistryBytes).digest("hex"); - if (graphRegistryDigest !== manifest.graphRegistry!.artifactSha256) { - throw new Error("Track B runtime distribution graph registry digest does not bind its contents"); + if (graphRegistryDigest !== graphRegistry.artifactSha256) { + throw new Error( + "Track B runtime distribution graph registry digest does not bind its contents", + ); } } if (options.expectedPublicSourceTree) { @@ -1838,6 +1843,7 @@ export interface TrackBShadowPipelineInput { readonly evaluationCases: readonly Record[]; readonly trajectoryEvents: readonly Record[]; readonly identity?: TrackBVariantIdentity; + readonly occurrence?: Readonly<{ occurrenceId: string; contentId: string }>; } export interface TrackBVariantIdentity { @@ -1965,6 +1971,7 @@ export async function runTrackBShadowPipeline( authorizationEpoch: input.authorizationEpoch, capability, ...(input.identity ? { identity: input.identity } : {}), + ...(input.occurrence ? { occurrence: input.occurrence } : {}), value, }); const replay = await runtime.invoke( @@ -2099,6 +2106,7 @@ async function runTrackBObservationPipeline( readonly sourceGraphRef: string; readonly trajectoryEvents: readonly Record[]; readonly identity: TrackBVariantIdentity; + readonly occurrence?: Readonly<{ occurrenceId: string; contentId: string }>; }, ) { const envelope = (capability: string, value: unknown): Record => ({ @@ -2110,6 +2118,7 @@ async function runTrackBObservationPipeline( authorizationEpoch: input.authorizationEpoch, capability, identity: input.identity, + ...(input.occurrence ? { occurrence: input.occurrence } : {}), value, }); const replay = await runtime.invoke( @@ -2200,6 +2209,12 @@ export async function runTrackBPostObservation( throw new Error("persisted observation identity is required for Track B shadow processing"); } const identity = normalizeTrackBVariantIdentity(observation); + const occurrenceId = String(observation.occurrenceId ?? `occurrence:${requestId}`); + const contentId = String(observation.contentId ?? `content:${requestId}`); + if (!occurrenceId || !contentId) { + throw new Error("post-observation occurrence and content identity is required"); + } + let occurrence = Object.freeze({ occurrenceId, contentId }); const run88Correlation = input.expectedReleaseId ? normalizeRun88RuntimeCorrelation(input.run88Correlation ?? {}, input.expectedReleaseId) : null; @@ -2215,6 +2230,7 @@ export async function runTrackBPostObservation( authorizationEpoch: input.authorizationEpoch, capability, identity, + occurrence, ...(run88Correlation ? { run88Correlation } : {}), ...extra, }); @@ -2249,6 +2265,18 @@ export async function runTrackBPostObservation( }), ); const artifactRef = String(artifact.id ?? `observation:${requestId}`); + const artifactOccurrence = + artifact.occurrence && typeof artifact.occurrence === "object" + ? (artifact.occurrence as Record) + : null; + if ( + artifactOccurrence?.occurrenceId === occurrenceId && + typeof artifactOccurrence.contentId === "string" + ) { + occurrence = Object.freeze({ occurrenceId, contentId: artifactOccurrence.contentId }); + } else { + occurrence = Object.freeze({ occurrenceId, contentId: artifactRef }); + } await observedRuntime.invoke( "event-log", businessEnvelope("event:append", { @@ -2406,6 +2434,7 @@ export async function runTrackBPostObservation( evaluationCases: routingShadowCases, trajectoryEvents, identity, + occurrence, }) : await runTrackBObservationPipeline(observedRuntime, { requestId, @@ -2418,6 +2447,7 @@ export async function runTrackBPostObservation( sourceGraphRef, trajectoryEvents, identity, + occurrence, }); const projection = createProjectionV2({ scope: input.scope, @@ -2450,6 +2480,7 @@ export async function runTrackBPostObservation( channel: input.channel, authorizationEpoch: input.authorizationEpoch, identity: { ...identity }, + occurrence, }); const registry = Object.fromEntries( [...closureEntries.entries()].sort(([left], [right]) => left.localeCompare(right)), diff --git a/role-model-router/apps/runtime-host-bridge/test/run95-all13-correlation.test.ts b/role-model-router/apps/runtime-host-bridge/test/run95-all13-correlation.test.ts new file mode 100644 index 00000000..660942e0 --- /dev/null +++ b/role-model-router/apps/runtime-host-bridge/test/run95-all13-correlation.test.ts @@ -0,0 +1,96 @@ +import { describe, expect, test } from "vitest"; + +import { + TRACK_B_CANONICAL_EXTENSION_IDS, + runTrackBPostObservation, +} from "../src/track-b-runtime.js"; + +describe("Run 95 all-thirteen occurrence correlation", () => { + test("every canonical extension receives the same bounded occurrence/content reference", async () => { + const envelopes = new Map>>(); + const result = await runTrackBPostObservation( + { + invoke: async (id, envelope) => { + const values = envelopes.get(id) ?? []; + values.push(envelope as Record); + envelopes.set(id, values); + const base = { + extensionId: id, + workerPid: 95, + durableLocator: { id, requestId: envelope.requestId }, + evidenceRef: `evidence:${id}:${envelope.requestId}`, + businessOutput: { id, bounded: true }, + }; + if (id === "artifact-store") { + return { + ...base, + id: "artifact:run95", + occurrence: { occurrenceId: "occurrence:run95", contentId: "content:authoritative" }, + }; + } + if (id === "repository-context") { + return { + ...base, + available: true, + context: { + scopeId: envelope.scope, + repoFingerprint: "a".repeat(64), + packageId: null, + fallbackLevel: "repo_task", + branchCompatibility: "unknown", + fingerprintEpoch: 1, + }, + diagnostics: [], + }; + } + if (envelope.capability === "knowledge:write") return { ...base, id: "knowledge:run95" }; + if (envelope.capability === "evaluation:run-local") { + return { + ...base, + count: 1, + scores: [1], + environment: "local", + provenance: { evidenceRef: base.evidenceRef }, + }; + } + if (envelope.capability === "knowledge:eval-consumer") { + return { ...base, id: "candidate:run95", state: "shadow", productionEffects: {} }; + } + return base; + }, + }, + { + requestId: "request:run95", + routingDecisionId: "decision:run95", + endpointId: "endpoint:run95:max", + modelId: "model:run95", + reasoningEffort: "max", + effortSource: "client", + occurrenceId: "occurrence:run95", + contentId: "content:caller-placeholder", + }, + { scope: "tenant:run95", channel: "development", authorizationEpoch: 95 }, + ); + + expect(Object.keys(result.extensionClosure.registry)).toEqual( + [...TRACK_B_CANONICAL_EXTENSION_IDS].sort(), + ); + expect(envelopes.get("artifact-store")).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + occurrence: expect.objectContaining({ occurrenceId: "occurrence:run95" }), + }), + ]), + ); + for (const id of TRACK_B_CANONICAL_EXTENSION_IDS.filter((id) => id !== "artifact-store")) { + expect(envelopes.get(id)).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + occurrence: { occurrenceId: "occurrence:run95", contentId: "content:authoritative" }, + }), + ]), + ); + } + expect(JSON.stringify(result.extensionClosure)).not.toContain("raw-prompt"); + }); +}); From 5d4307e5911a5457d6659b2bad9a403fee82d672 Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 09:32:16 +0800 Subject: [PATCH 09/32] feat(runtime): distinguish storage observation state --- .../src/track-b-operations.ts | 21 +++++++++-- .../test/run95-storage-projection.test.ts | 36 +++++++++++++++++++ .../apps/runtime-ui/app/lib/runtime-api.ts | 8 +++++ .../app/routes/storage-retention.test.tsx | 8 ++++- .../app/routes/storage-retention.tsx | 19 +++++++--- .../runtime-ui/e2e/track-b-operations.spec.ts | 12 +++++++ .../packages/sqlite-memory/src/index.ts | 6 +++- .../test/run95-bounded-storage.test.ts | 9 +++++ 8 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 role-model-router/apps/runtime-host-bridge/test/run95-storage-projection.test.ts diff --git a/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts b/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts index 9ab08f13..5ec95d48 100644 --- a/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts +++ b/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts @@ -137,9 +137,24 @@ export function normalizeStorageRetentionContract(value: unknown): Record { + if (!resource || typeof resource !== "object") return resource; + const row = resource as Record; + const unavailable = row.health === "unavailable"; + const observedAt = typeof row.observedAt === "string" ? row.observedAt : undefined; + return { + ...row, + ...(unavailable && typeof row.observationReason !== "string" + ? { observationReason: "Observation reported unavailable" } + : {}), + ...(typeof row.lastCheckedAt !== "string" && observedAt ? { lastCheckedAt: observedAt } : {}), + }; + }; + const physicalResources = ( + Array.isArray(raw.physicalResources) + ? raw.physicalResources + : (nestedPhysicalResources ?? (Array.isArray(inventory?.entries) ? inventory.entries : [])) + ).map(normalizeObservation); return { ...raw, logicalClasses, diff --git a/role-model-router/apps/runtime-host-bridge/test/run95-storage-projection.test.ts b/role-model-router/apps/runtime-host-bridge/test/run95-storage-projection.test.ts new file mode 100644 index 00000000..b87d3919 --- /dev/null +++ b/role-model-router/apps/runtime-host-bridge/test/run95-storage-projection.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, test } from "vitest"; + +import { normalizeStorageRetentionContract } from "../src/track-b-operations.js"; + +describe("Run 95 storage projection", () => { + test("preserves unavailable-observation diagnostics instead of replacing them with zero-byte readiness", () => { + const summary = normalizeStorageRetentionContract({ + policyState: { channel: "development", state: "enforced" }, + storageInventory: { + complete: false, + physicalResources: [ + { + id: "physical-cloud-history", + owner: "history-service", + health: "unavailable", + measurement: "remote_observed", + physicalBytes: null, + heldItems: 0, + retentionState: "measured_uncovered", + observationState: "observed", + observedAt: "2026-08-30T00:00:00.000Z", + }, + ], + }, + }); + const row = (summary.physicalResources as Record[])[0]; + expect(row).toMatchObject({ + health: "unavailable", + physicalBytes: null, + observationReason: "Observation reported unavailable", + lastCheckedAt: "2026-08-30T00:00:00.000Z", + retentionState: "measured_uncovered", + }); + expect(summary.policyState).toEqual({ channel: "development", state: "enforced" }); + }); +}); diff --git a/role-model-router/apps/runtime-ui/app/lib/runtime-api.ts b/role-model-router/apps/runtime-ui/app/lib/runtime-api.ts index 97984a58..39879b8b 100644 --- a/role-model-router/apps/runtime-ui/app/lib/runtime-api.ts +++ b/role-model-router/apps/runtime-ui/app/lib/runtime-api.ts @@ -1779,6 +1779,10 @@ export interface RuntimeStorageRetentionSummary { readonly observationState?: string; readonly measurementSource?: string; readonly observedAt?: string; + /** Human-readable reason when observation is unavailable or degraded. */ + readonly observationReason?: string | null; + /** Timestamp of the most recent health/observation attempt. */ + readonly lastCheckedAt?: string | null; readonly freshUntil?: string; readonly retentionState?: string; readonly accountingState?: string; @@ -1796,6 +1800,8 @@ export interface RuntimeStorageRetentionSummary { readonly observationState?: string; readonly measurementSource?: string; readonly observedAt?: string; + readonly observationReason?: string | null; + readonly lastCheckedAt?: string | null; readonly freshUntil?: string; readonly owners?: readonly string[]; readonly logicalClassIds?: readonly string[]; @@ -1859,6 +1865,8 @@ export interface RuntimeStorageRetentionSummary { readonly observationState?: string; readonly measurementSource?: string; readonly observedAt?: string | null; + readonly observationReason?: string | null; + readonly lastCheckedAt?: string | null; readonly freshUntil?: string | null; }[]; /** v2 keeps the measured physical inventory separate from logical accounting. */ diff --git a/role-model-router/apps/runtime-ui/app/routes/storage-retention.test.tsx b/role-model-router/apps/runtime-ui/app/routes/storage-retention.test.tsx index 91cabec6..14a850e2 100644 --- a/role-model-router/apps/runtime-ui/app/routes/storage-retention.test.tsx +++ b/role-model-router/apps/runtime-ui/app/routes/storage-retention.test.tsx @@ -45,12 +45,17 @@ describe("StorageRetentionRoute", () => { "unavailableResourceCount", "not service health", "unattributedPhysicalBytes", - "Global policy state", + "Policy state", "Physical resource mapping", "Observation state", "Measurement source", "Fresh through", + "Last checked", + "Reason", + "Retention coverage", "row.freshUntil", + "row.lastCheckedAt", + "row.observationReason", "row.owners", "row.physicalResourceId", "row.observationState", @@ -61,6 +66,7 @@ describe("StorageRetentionRoute", () => { expect(source).not.toContain( "summary.policyState ? summary.policyState.state : row.retentionState", ); + expect(source).not.toContain(">Enforcement<"); expect(source).not.toContain("Maximum bytes"); expect(source).not.toContain("StatusPill"); expect(source).not.toContain("FactCard"); diff --git a/role-model-router/apps/runtime-ui/app/routes/storage-retention.tsx b/role-model-router/apps/runtime-ui/app/routes/storage-retention.tsx index 93cb0959..3ed22e18 100644 --- a/role-model-router/apps/runtime-ui/app/routes/storage-retention.tsx +++ b/role-model-router/apps/runtime-ui/app/routes/storage-retention.tsx @@ -180,8 +180,11 @@ export function StorageRetentionRouteView() { ]} /> {error ? : null} - {summary?.policyState ? ( -

Global policy state: {summary.policyState.state}

+ {summary ? ( +

+ Policy state{summary.policyState?.channel ? ` (${summary.policyState.channel})` : ""}:{" "} + {summary.policyState?.state ?? "not reported"} +

) : null}

Unattributed physical bytes are measured physical allocation not mapped to logical storage @@ -190,7 +193,7 @@ export function StorageRetentionRouteView() { {summary?.physicalResources ? (

@@ -202,10 +205,12 @@ export function StorageRetentionRouteView() { "Health", "Observation state", "Measurement source", + "Last checked", "Fresh through", + "Reason", "Physical bytes", "Legal holds", - "Enforcement", + "Retention coverage", ].map((heading) => ( + + diff --git a/role-model-router/apps/runtime-ui/e2e/track-b-operations.spec.ts b/role-model-router/apps/runtime-ui/e2e/track-b-operations.spec.ts index 27549180..d2d0782f 100644 --- a/role-model-router/apps/runtime-ui/e2e/track-b-operations.spec.ts +++ b/role-model-router/apps/runtime-ui/e2e/track-b-operations.spec.ts @@ -37,3 +37,15 @@ test("operates disclosure, opt-out, retention, recommendations, and failure isol page.getByText(/private operations endpoint is required for retention execution/i), ).toBeVisible(); }); + +test("Run 95 storage inventory separates policy state from unavailable-observation diagnostics", async ({ + page, +}) => { + await page.goto("/app/system/storage-retention"); + await expect(page.getByText(/Policy state/)).toBeVisible(); + await expect(page.getByRole("heading", { name: "Physical storage inventory" })).toBeVisible(); + await expect(page.getByRole("columnheader", { name: "Last checked" })).toBeVisible(); + await expect(page.getByRole("columnheader", { name: "Reason" })).toBeVisible(); + await expect(page.getByRole("columnheader", { name: "Retention coverage" })).toBeVisible(); + await expect(page.getByText("Enforcement", { exact: true })).toHaveCount(0); +}); diff --git a/role-model-router/packages/sqlite-memory/src/index.ts b/role-model-router/packages/sqlite-memory/src/index.ts index 3fe7ad2e..620373e7 100644 --- a/role-model-router/packages/sqlite-memory/src/index.ts +++ b/role-model-router/packages/sqlite-memory/src/index.ts @@ -4836,7 +4836,11 @@ export function validateBoundedStorageInventory(inventory: { } continue; } - if (!Number.isSafeInteger(resource.physicalBytes) || resource.physicalBytes < 0) { + if ( + resource.physicalBytes === null || + !Number.isSafeInteger(resource.physicalBytes) || + resource.physicalBytes < 0 + ) { throw new Error("measured physical resource bytes must be non-negative integers"); } measuredTotal += resource.physicalBytes; diff --git a/role-model-router/packages/sqlite-memory/test/run95-bounded-storage.test.ts b/role-model-router/packages/sqlite-memory/test/run95-bounded-storage.test.ts index 6eb77819..2f597e8c 100644 --- a/role-model-router/packages/sqlite-memory/test/run95-bounded-storage.test.ts +++ b/role-model-router/packages/sqlite-memory/test/run95-bounded-storage.test.ts @@ -34,5 +34,14 @@ describe("Run 95 bounded storage inventory", () => { logicalClasses: [{ ...inventory.logicalClasses[0], physicalBytes: 4096 }], }), ).toThrow(/logical|double|physical/i); + expect(() => + sqliteMemory.validateBoundedStorageInventory({ + ...inventory, + physicalResources: [ + { ...inventory.physicalResources[0], physicalBytes: null }, + ...inventory.physicalResources.slice(1), + ], + }), + ).toThrow(/measured physical resource bytes/i); }); }); From 8377e263c9ab3e9fadf0f311aef9a9101d36078e Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 10:10:18 +0800 Subject: [PATCH 10/32] test(runtime): serialize worker-backed host integration --- .../test/recursive-87-compatibility.test.ts | 4 +- .../test/run95-registry-contract.test.ts | 37 +++++++++++++++---- .../apps/runtime-host-bridge/vitest.config.ts | 3 ++ scripts/ci-workflow.test.mjs | 8 ++++ 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/role-model-router/apps/runtime-host-bridge/test/recursive-87-compatibility.test.ts b/role-model-router/apps/runtime-host-bridge/test/recursive-87-compatibility.test.ts index 92d46297..d778ce44 100644 --- a/role-model-router/apps/runtime-host-bridge/test/recursive-87-compatibility.test.ts +++ b/role-model-router/apps/runtime-host-bridge/test/recursive-87-compatibility.test.ts @@ -11,9 +11,7 @@ import { trackBDistributionRequiresSQLiteMaintenance, } from "../src/track-b-runtime.js"; -const graphRegistryKinds = [ - { id: "core.message", version: 1, category: "message", fields: [] }, -]; +const graphRegistryKinds = [{ id: "core.message", version: 1, category: "message", fields: [] }]; const graphRegistry = { version: 1, artifactSha256: createHash("sha256") diff --git a/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts b/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts index 50bfa5fd..a90a141e 100644 --- a/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts +++ b/role-model-router/apps/runtime-host-bridge/test/run95-registry-contract.test.ts @@ -38,7 +38,11 @@ describe("Run 95 graph registry contract", () => { const bytes = Buffer.from("export async function run(){return {available:true}}\n"); const artifactSha256 = createHash("sha256").update(bytes).digest("hex"); const extensions = Array.from({ length: 13 }, (_, index) => ({ - descriptor: { id: `extension-${index}`, protocolVersion: "1.1.0", capabilities: ["health"] }, + descriptor: { + id: `extension-${index}`, + protocolVersion: "1.1.0", + capabilities: ["health"], + }, modulePath: `extensions/extension-${index}.mjs`, artifactSha256, })); @@ -54,7 +58,10 @@ describe("Run 95 graph registry contract", () => { }), ); await expect( - stageTrackBRuntimeDistribution({ sourceRoot: root, releaseDir: path.join(root, "release") }), + stageTrackBRuntimeDistribution({ + sourceRoot: root, + releaseDir: path.join(root, "release"), + }), ).rejects.toThrow(/graph registry/i); } finally { await rm(root, { recursive: true, force: true }); @@ -67,7 +74,11 @@ describe("Run 95 graph registry contract", () => { const bytes = Buffer.from("export async function run(){return {available:true}}\n"); const artifactSha256 = createHash("sha256").update(bytes).digest("hex"); const extensions = Array.from({ length: 13 }, (_, index) => ({ - descriptor: { id: `extension-${index}`, protocolVersion: "1.1.0", capabilities: ["health"] }, + descriptor: { + id: `extension-${index}`, + protocolVersion: "1.1.0", + capabilities: ["health"], + }, modulePath: `extensions/extension-${index}.mjs`, artifactSha256, })); @@ -99,7 +110,10 @@ describe("Run 95 graph registry contract", () => { }), ); await expect( - stageTrackBRuntimeDistribution({ sourceRoot: root, releaseDir: path.join(root, "release") }), + stageTrackBRuntimeDistribution({ + sourceRoot: root, + releaseDir: path.join(root, "release"), + }), ).rejects.toThrow(/graph registry.*digest/i); } finally { await rm(root, { recursive: true, force: true }); @@ -113,7 +127,11 @@ describe("Run 95 graph registry contract", () => { const artifactSha256 = createHash("sha256").update(bytes).digest("hex"); const kinds = [{ id: "core.message", version: 1, category: "message", fields: [] }]; const extensions = Array.from({ length: 13 }, (_, index) => ({ - descriptor: { id: `extension-${index}`, protocolVersion: "1.1.0", capabilities: ["health"] }, + descriptor: { + id: `extension-${index}`, + protocolVersion: "1.1.0", + capabilities: ["health"], + }, modulePath: `extensions/extension-${index}.mjs`, artifactSha256, })); @@ -126,7 +144,9 @@ describe("Run 95 graph registry contract", () => { schemaVersion: "role-model.track-b-runtime-distribution.v2", graphRegistry: { version: 1, - artifactSha256: createHash("sha256").update(JSON.stringify({ version: 1, kinds })).digest("hex"), + artifactSha256: createHash("sha256") + .update(JSON.stringify({ version: 1, kinds })) + .digest("hex"), kinds, }, sidecar: { modulePath: "sidecar.mjs", artifactSha256 }, @@ -134,7 +154,10 @@ describe("Run 95 graph registry contract", () => { }), ); await expect( - stageTrackBRuntimeDistribution({ sourceRoot: root, releaseDir: path.join(root, "release") }), + stageTrackBRuntimeDistribution({ + sourceRoot: root, + releaseDir: path.join(root, "release"), + }), ).rejects.toThrow(/registry bindings/i); } finally { await rm(root, { recursive: true, force: true }); diff --git a/role-model-router/apps/runtime-host-bridge/vitest.config.ts b/role-model-router/apps/runtime-host-bridge/vitest.config.ts index 3f3b0514..791dee7d 100644 --- a/role-model-router/apps/runtime-host-bridge/vitest.config.ts +++ b/role-model-router/apps/runtime-host-bridge/vitest.config.ts @@ -3,6 +3,9 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { include: ["test/**/*.test.ts", "src/**/*.test.ts"], + // These tests launch real workers and SQLite-backed runtimes. Parallel files + // can contend for startup and teardown resources, obscuring real failures. + fileParallelism: false, testTimeout: 30_000, }, }); diff --git a/scripts/ci-workflow.test.mjs b/scripts/ci-workflow.test.mjs index 5289a440..09fa9b0c 100644 --- a/scripts/ci-workflow.test.mjs +++ b/scripts/ci-workflow.test.mjs @@ -6,6 +6,10 @@ const workflow = readFileSync(new URL("../.github/workflows/ci.yml", import.meta const packageManifest = JSON.parse( readFileSync(new URL("../package.json", import.meta.url), "utf8"), ); +const runtimeHostVitestConfig = readFileSync( + new URL("../role-model-router/apps/runtime-host-bridge/vitest.config.ts", import.meta.url), + "utf8", +); test("CI is scoped to long-lived branches with stable cancellable lanes", () => { assert.match(workflow, /push:\s*\n\s*branches:\s*\n\s*- dev\s*\n\s*- stage\s*\n\s*- main/); @@ -93,3 +97,7 @@ test("workspace tests serialize the resource-heavy runtime proofs", () => { /--filter=\.\/\*\* --filter=!@role-model-router\/runtime-host-bridge --filter=!@try-works\/pi-role-model test.*--filter @role-model-router\/runtime-host-bridge test.*--filter @try-works\/pi-role-model test/, ); }); + +test("runtime-host integration tests do not contend for worker-owned state", () => { + assert.match(runtimeHostVitestConfig, /fileParallelism:\s*false/); +}); From 312e7b76dc1d213f30769da07ab259669df8851f Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 11:08:46 +0800 Subject: [PATCH 11/32] fix(runtime): resolve built profile aggregator exports --- .../packages/profile-aggregator/package.json | 1 + .../test/benchmark-routing-quality.test.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/role-model-router/packages/profile-aggregator/package.json b/role-model-router/packages/profile-aggregator/package.json index 68a27b06..2f1d96dd 100644 --- a/role-model-router/packages/profile-aggregator/package.json +++ b/role-model-router/packages/profile-aggregator/package.json @@ -6,6 +6,7 @@ "exports": { ".": { "types": "./src/index.ts", + "import": "./dist/index.js", "runtime": "./dist/index.js", "default": "./src/index.ts" } diff --git a/role-model-router/packages/profile-aggregator/test/benchmark-routing-quality.test.ts b/role-model-router/packages/profile-aggregator/test/benchmark-routing-quality.test.ts index fe787eb8..3545693f 100644 --- a/role-model-router/packages/profile-aggregator/test/benchmark-routing-quality.test.ts +++ b/role-model-router/packages/profile-aggregator/test/benchmark-routing-quality.test.ts @@ -1,3 +1,5 @@ +import { execFileSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; import { describe, expect, test } from "vitest"; import type { ObservedPerformanceSample } from "../src/index.js"; @@ -35,6 +37,20 @@ describe("resolveRoutingBenchmarkQuality", () => { expect(typeof runtime.resolveRoutingBenchmarkQuality).toBe("function"); }); + test("plain Node resolves the built package export rather than sibling TypeScript source", () => { + const packageRoot = fileURLToPath(new URL("..", import.meta.url)); + const output = execFileSync( + process.execPath, + [ + "--input-type=module", + "--eval", + 'import("@role-model-router/profile-aggregator").then((module) => console.log(typeof module.resolveRoutingBenchmarkQuality))', + ], + { cwd: packageRoot, encoding: "utf8" }, + ); + expect(output.trim()).toBe("function"); + }); + test("uses quick hard mean for quick-only runs instead of averaging empty buckets", () => { const quality = resolveRoutingBenchmarkQuality( Array.from({ length: 12 }, (_, index) => From cb843ed043ad36b3b0322d6f8f584ed0e2e7a79d Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 14:33:12 +0800 Subject: [PATCH 12/32] fix(storage): recognize verified graph pointers during migration --- .../sqlite-memory/src/legacy-migration.ts | 92 ++++++++++++++++--- .../test/legacy-migration.test.ts | 50 ++++++++++ 2 files changed, 128 insertions(+), 14 deletions(-) diff --git a/role-model-router/packages/sqlite-memory/src/legacy-migration.ts b/role-model-router/packages/sqlite-memory/src/legacy-migration.ts index 48a3c345..58319a67 100644 --- a/role-model-router/packages/sqlite-memory/src/legacy-migration.ts +++ b/role-model-router/packages/sqlite-memory/src/legacy-migration.ts @@ -307,14 +307,28 @@ function tableExists(database: DatabaseSync, table: string): boolean { type LegacyRowClassification = | { readonly kind: "import" } + | { readonly kind: "canonical"; readonly reference: GraphArtifactReference } | { readonly kind: "quarantine"; readonly reason: "malformed_json" | "unresolved_graph_pointer" }; -function classifyLegacyRow(observationJson: string): LegacyRowClassification { +function classifyLegacyRow( + observationJson: string, + canonicalPointerValidator?: (pointer: { + readonly requestId: string; + readonly artifactRef: GraphArtifactReference | string; + readonly graphPrimary?: boolean; + readonly migrated?: boolean; + }) => boolean, +): LegacyRowClassification { try { const parsed = JSON.parse(observationJson); - return isGraphObservationPointer(parsed) - ? { kind: "quarantine", reason: "unresolved_graph_pointer" } - : { kind: "import" }; + if (!isGraphObservationPointer(parsed)) return { kind: "import" }; + if ( + typeof parsed.artifactRef === "object" && + canonicalPointerValidator?.(parsed) + ) { + return { kind: "canonical", reference: parsed.artifactRef }; + } + return { kind: "quarantine", reason: "unresolved_graph_pointer" }; } catch (error) { if (error instanceof SyntaxError) return { kind: "quarantine", reason: "malformed_json" }; throw error; @@ -343,7 +357,16 @@ function readQuarantine(database: DatabaseSync): Array<{ source_id: string; reas .all("runtime_observations") as Array<{ source_id: string; reason: string }>; } -function sourceProof(database: DatabaseSync, pageSize = 1_000): { count: number; hash: string } { +function sourceProof( + database: DatabaseSync, + pageSize = 1_000, + canonicalPointerValidator?: (pointer: { + readonly requestId: string; + readonly artifactRef: GraphArtifactReference | string; + readonly graphPrimary?: boolean; + readonly migrated?: boolean; + }) => boolean, +): { count: number; hash: string } { if (!tableExists(database, "runtime_observations")) return { count: 0, hash: sha256("") }; const digest = createHash("sha256"); let cursor = ""; @@ -368,11 +391,15 @@ function sourceProof(database: DatabaseSync, pageSize = 1_000): { count: number; for (const row of rows) { let rowHash = sha256(row.observation_json); try { - if (isGraphObservationPointer(JSON.parse(row.observation_json))) { + const parsed = JSON.parse(row.observation_json); + if (isGraphObservationPointer(parsed)) { // Run 94 SP6: a pointer-shaped row without a matching migration ref is // unclassified residue — quarantined and excluded from parity inputs. - if (!row.source_hash) continue; - rowHash = row.source_hash; + const confirmedCanonical = + typeof parsed.artifactRef === "object" && + canonicalPointerValidator?.(parsed) === true; + if (!row.source_hash && !confirmedCanonical) continue; + rowHash = row.source_hash ?? sha256(row.observation_json); } } catch (error) { if (error instanceof SyntaxError) { @@ -824,12 +851,24 @@ export class LegacySqliteMigration { readonly #artifactRollback?: (input: LegacyArtifactWriteResult) => void; readonly #now: () => number; readonly #routerRoot: string; + readonly #canonicalPointerValidator?: (pointer: { + readonly requestId: string; + readonly artifactRef: GraphArtifactReference | string; + readonly graphPrimary?: boolean; + readonly migrated?: boolean; + }) => boolean; constructor(input: { readonly databasePath: string; readonly backupPath: string; readonly artifactWriter: (input: LegacyArtifactWriteInput) => LegacyArtifactWriteResult; readonly artifactRollback?: (input: LegacyArtifactWriteResult) => void; + readonly canonicalPointerValidator?: (pointer: { + readonly requestId: string; + readonly artifactRef: GraphArtifactReference | string; + readonly graphPrimary?: boolean; + readonly migrated?: boolean; + }) => boolean; readonly now?: () => number; readonly routerRoot?: string; }) { @@ -837,6 +876,7 @@ export class LegacySqliteMigration { this.#backupPath = input.backupPath; this.#artifactWriter = input.artifactWriter; this.#artifactRollback = input.artifactRollback; + this.#canonicalPointerValidator = input.canonicalPointerValidator; this.#now = input.now ?? Date.now; this.#routerRoot = input.routerRoot ?? resolveLegacyMigrationRouterRoot(); } @@ -844,7 +884,7 @@ export class LegacySqliteMigration { audit(): LegacyStorageAudit { const database = open(this.#databasePath, true); try { - const proof = sourceProof(database); + const proof = sourceProof(database, 1_000, this.#canonicalPointerValidator); const rows = database .prepare( tableExists(database, "legacy_graph_migration_refs") @@ -865,7 +905,7 @@ export class LegacySqliteMigration { const quarantined = rows.flatMap((row) => row.imported_source_id ? [] - : classifyLegacyRow(row.observation_json).kind === "quarantine" + : classifyLegacyRow(row.observation_json, this.#canonicalPointerValidator).kind === "quarantine" ? [row] : [], ); @@ -958,7 +998,7 @@ export class LegacySqliteMigration { | { valid?: number } | undefined; if (postcondition?.valid !== 1) throw new Error("migration registry postcondition failed"); - const auditProof = sourceProof(database); + const auditProof = sourceProof(database, 1_000, this.#canonicalPointerValidator); database .prepare( `INSERT OR IGNORE INTO legacy_migration_journal @@ -981,7 +1021,10 @@ export class LegacySqliteMigration { .all(input.batchSize) as Array<{ request_id: string; observation_json: string }>; let migratedCount = 0; for (const row of rows) { - const classification = classifyLegacyRow(row.observation_json); + const classification = classifyLegacyRow( + row.observation_json, + this.#canonicalPointerValidator, + ); if (classification.kind === "quarantine") { database .prepare( @@ -997,6 +1040,27 @@ export class LegacySqliteMigration { ); continue; } + if (classification.kind === "canonical") { + const sourceHash = sha256(row.observation_json); + database + .prepare( + `INSERT INTO legacy_graph_migration_refs + (source_table, source_id, source_hash, scope_id, artifact_id, artifact_path, + artifact_content_hash, migrated_at_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + ) + .run( + "runtime_observations", + row.request_id, + sourceHash, + classification.reference.scopeId, + classification.reference.artifactId, + classification.reference.artifactPath ?? `artifact://${classification.reference.scopeId}/${classification.reference.artifactId}`, + classification.reference.contentHash, + this.#now(), + ); + migratedCount += 1; + continue; + } const contentHash = sha256(row.observation_json); const artifact = this.#artifactWriter({ scopeId: input.scopeId, @@ -1121,7 +1185,7 @@ export class LegacySqliteMigration { if (journal.holdUntilMs === null || this.#now() > journal.holdUntilMs) { throw new Error("shadow mirror deadline expired; restart backfill before parity"); } - const source = sourceProof(database); + const source = sourceProof(database, 1_000, this.#canonicalPointerValidator); const target = targetProof(database); if (source.count !== target.count || source.hash !== target.hash) throw new Error("first parity mismatch"); @@ -1170,7 +1234,7 @@ export class LegacySqliteMigration { try { if (currentState(database) !== "legacy_read_hold") throw new Error("legacy read hold required"); - const source = sourceProof(database); + const source = sourceProof(database, 1_000, this.#canonicalPointerValidator); const target = targetProof(database); if (source.count !== target.count || source.hash !== target.hash) { throw new Error("second parity mismatch"); diff --git a/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts b/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts index 93ff974e..690cc099 100644 --- a/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts +++ b/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts @@ -259,6 +259,56 @@ describe("TB04 real SQLite legacy migration", () => { ).toThrow(/shadow mirror deadline expired/i); }); + test("Run 95 accepts a verifier-confirmed graph-primary pointer without treating it as unresolved legacy residue", () => { + const { databasePath, backupPath } = fixture(); + const database = new DatabaseSync(databasePath); + database + .prepare( + `INSERT INTO runtime_observations + (request_id, routing_decision_id, endpoint_id, conversation_id, created_at_ms, observation_json) + VALUES (?, ?, ?, ?, ?, ?)`, + ) + .run( + "request-current-graph", + "route-current-graph", + "endpoint-current-graph", + "conversation-current-graph", + 102, + JSON.stringify({ + requestId: "request-current-graph", + graphPrimary: true, + artifactRef: { + scopeId: "scope-1", + artifactId: "a".repeat(64), + contentHash: "b".repeat(64), + }, + }), + ); + database.close(); + const migration = new LegacySqliteMigration({ + databasePath, + backupPath, + artifactWriter: ({ sourceId, contentHash }) => ({ + artifactId: `artifact-${sourceId}`, + artifactPath: `artifact://${sourceId}`, + contentHash, + }), + canonicalPointerValidator: (pointer) => + pointer.requestId === "request-current-graph" && + typeof pointer.artifactRef === "object" && + pointer.artifactRef.scopeId === "scope-1" && + pointer.artifactRef.artifactId === "a".repeat(64) && + pointer.artifactRef.contentHash === "b".repeat(64), + }); + + while (migration.backfill({ scopeId: "scope-1", batchSize: 10 }).pendingCount > 0) { + // exhaust bounded legacy rows before entering the shadow window + } + + expect(migration.audit().quarantinedRequestIds).not.toContain("request-current-graph"); + expect(() => migration.enterShadowMirror({ deadlineMs: Date.now() + 10_000 })).not.toThrow(); + }); + test("rollback restores the populated legacy database and removes backfilled artifacts", () => { const { databasePath, backupPath, rich } = fixture(); const artifacts = new Set(); From 13919c6083583b140daff248a0cd35b2f567cd7a Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 14:42:00 +0800 Subject: [PATCH 13/32] fix(storage): recover verified pointer quarantine --- .../sqlite-memory/src/legacy-migration.ts | 25 +++++++++ .../test/legacy-migration.test.ts | 54 +++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/role-model-router/packages/sqlite-memory/src/legacy-migration.ts b/role-model-router/packages/sqlite-memory/src/legacy-migration.ts index 58319a67..70f7da1d 100644 --- a/role-model-router/packages/sqlite-memory/src/legacy-migration.ts +++ b/role-model-router/packages/sqlite-memory/src/legacy-migration.ts @@ -994,6 +994,31 @@ export class LegacySqliteMigration { ); database.exec(migrationSql); ensureQuarantineTable(database); + // A pointer may have been quarantined by an older runtime before its artifact + // was durable or before this verifier was available. Reconsider only entries + // which the current validator proves canonical; malformed or still-unresolved + // rows remain explicit quarantine and continue to block cutover. + const quarantinedPointers = database + .prepare( + `SELECT observations.request_id, observations.observation_json + FROM runtime_observations AS observations + INNER JOIN legacy_migration_quarantine AS quarantine + ON quarantine.source_table='runtime_observations' + AND quarantine.source_id=observations.request_id + ORDER BY observations.request_id ASC`, + ) + .all() as Array<{ request_id: string; observation_json: string }>; + const clearQuarantine = database.prepare( + "DELETE FROM legacy_migration_quarantine WHERE source_table=? AND source_id=?", + ); + for (const row of quarantinedPointers) { + if ( + classifyLegacyRow(row.observation_json, this.#canonicalPointerValidator).kind === + "canonical" + ) { + clearQuarantine.run("runtime_observations", row.request_id); + } + } const postcondition = database.prepare(entry.postconditionQuery).get() as | { valid?: number } | undefined; diff --git a/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts b/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts index 690cc099..6022f351 100644 --- a/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts +++ b/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts @@ -309,6 +309,60 @@ describe("TB04 real SQLite legacy migration", () => { expect(() => migration.enterShadowMirror({ deadlineMs: Date.now() + 10_000 })).not.toThrow(); }); + test("Run 95 reprocesses a stale unresolved-pointer quarantine after its artifact becomes verified", () => { + const { databasePath, backupPath } = fixture(); + const pointer = { + requestId: "request-recovered-graph", + graphPrimary: true, + artifactRef: { + scopeId: "scope-1", + artifactId: "c".repeat(64), + contentHash: "d".repeat(64), + }, + }; + const database = new DatabaseSync(databasePath); + database + .prepare( + `INSERT INTO runtime_observations + (request_id, routing_decision_id, endpoint_id, conversation_id, created_at_ms, observation_json) + VALUES (?, ?, ?, ?, ?, ?)`, + ) + .run( + pointer.requestId, + "route-recovered-graph", + "endpoint-recovered-graph", + "conversation-recovered-graph", + 103, + JSON.stringify(pointer), + ); + database.close(); + + const createMigration = (verified: boolean) => + new LegacySqliteMigration({ + databasePath, + backupPath, + artifactWriter: ({ sourceId, contentHash }) => ({ + artifactId: `artifact-${sourceId}`, + artifactPath: `artifact://${sourceId}`, + contentHash, + }), + canonicalPointerValidator: candidate => verified && candidate.requestId === pointer.requestId, + }); + + const unresolved = createMigration(false); + while (unresolved.backfill({ scopeId: "scope-1", batchSize: 10 }).pendingCount > 0) { + // Exhaust the legacy rows so the unresolved pointer is persisted as quarantine. + } + expect(unresolved.audit().quarantinedRequestIds).toContain(pointer.requestId); + + const recovered = createMigration(true); + while (recovered.backfill({ scopeId: "scope-1", batchSize: 10 }).pendingCount > 0) { + // A later verified artifact must make a previous quarantine eligible again. + } + expect(recovered.audit().quarantinedRequestIds).not.toContain(pointer.requestId); + expect(() => recovered.enterShadowMirror({ deadlineMs: Date.now() + 10_000 })).not.toThrow(); + }); + test("rollback restores the populated legacy database and removes backfilled artifacts", () => { const { databasePath, backupPath, rich } = fixture(); const artifacts = new Set(); From 2832896bae464ea25db9b13113ff88e91d3419ec Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 15:03:53 +0800 Subject: [PATCH 14/32] fix(storage): refresh migration proof receipt --- .../packages/sqlite-memory/src/legacy-migration.ts | 10 ++++++++-- .../sqlite-memory/test/legacy-migration.test.ts | 4 ++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/role-model-router/packages/sqlite-memory/src/legacy-migration.ts b/role-model-router/packages/sqlite-memory/src/legacy-migration.ts index 70f7da1d..132adfda 100644 --- a/role-model-router/packages/sqlite-memory/src/legacy-migration.ts +++ b/role-model-router/packages/sqlite-memory/src/legacy-migration.ts @@ -1142,6 +1142,11 @@ export class LegacySqliteMigration { } } } + // Recompute the source proof after stale quarantine entries may have been + // reclassified as canonical graph pointers. The journal is the operator + // receipt for this batch, so it must describe the same source set that + // parity will subsequently compare to the target. + const source = sourceProof(database, 1_000, this.#canonicalPointerValidator); const proof = targetProof(database); const pending = Number( ( @@ -1156,10 +1161,11 @@ export class LegacySqliteMigration { ); database .prepare( - `UPDATE legacy_migration_journal SET target_count = ?, target_hash = ?, cursor = ?, updated_at_ms = ? + `UPDATE legacy_migration_journal + SET source_count = ?, source_hash = ?, target_count = ?, target_hash = ?, cursor = ?, updated_at_ms = ? WHERE migration_id = ?`, ) - .run(proof.count, proof.hash, rows.at(-1)?.request_id ?? null, this.#now(), MIGRATION_ID); + .run(source.count, source.hash, proof.count, proof.hash, rows.at(-1)?.request_id ?? null, this.#now(), MIGRATION_ID); return { migratedCount, pendingCount: pending }; } finally { database.close(); diff --git a/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts b/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts index 6022f351..fd68da9e 100644 --- a/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts +++ b/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts @@ -360,6 +360,10 @@ describe("TB04 real SQLite legacy migration", () => { // A later verified artifact must make a previous quarantine eligible again. } expect(recovered.audit().quarantinedRequestIds).not.toContain(pointer.requestId); + expect(readLegacyMigrationJournal(databasePath)).toMatchObject({ + sourceCount: 3, + targetCount: 3, + }); expect(() => recovered.enterShadowMirror({ deadlineMs: Date.now() + 10_000 })).not.toThrow(); }); From 0a92f461ef96894a0c1bba64d7bcbeab11bf0281 Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 16:15:38 +0800 Subject: [PATCH 15/32] fix(runtime): retry queued cloud aggregates on startup --- .../apps/runtime-host-bridge/src/cli.ts | 3 ++ .../src/track-b-operations.ts | 4 +++ .../test/track-b-operations-api.test.ts | 29 +++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/role-model-router/apps/runtime-host-bridge/src/cli.ts b/role-model-router/apps/runtime-host-bridge/src/cli.ts index 114cbd0e..6e36a97f 100644 --- a/role-model-router/apps/runtime-host-bridge/src/cli.ts +++ b/role-model-router/apps/runtime-host-bridge/src/cli.ts @@ -1320,6 +1320,9 @@ export async function main(): Promise { qaStartupReceipts.set(extension.descriptor.id, { ...receipt, requestId }); } await drainPostObservationOutbox(extensionRuntime); + // A prior cloud outage must not require an unrelated new provider request + // before its already-authorized, durable aggregate is retried. + await postObservationOperations?.retryContributionAggregates(); } catch (error) { console.error("[role-model] extension host failed after core runtime was ready:", error); } diff --git a/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts b/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts index 5ec95d48..a919966a 100644 --- a/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts +++ b/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts @@ -1775,6 +1775,10 @@ export function createTrackBOperations({ }); return remote ?? { status: "operations_boundary_unconfigured" }; }, + async retryContributionAggregates(): Promise { + const remote = await requestPrivate("contribution/retry", { method: "POST", body: {} }); + return remote ?? { status: "operations_boundary_unconfigured" }; + }, async recordLocalRouteCapture(input: Record): Promise { if (!operationsEndpoint) return { status: "operations_boundary_unconfigured" }; const url = new URL(operationsEndpoint); diff --git a/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts b/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts index 65d06fca..caa03954 100644 --- a/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts +++ b/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts @@ -53,6 +53,35 @@ afterEach(async () => { }); describe("Track B operations APIs", () => { + test("retries durable contribution aggregates without manufacturing another request", async () => { + const token = "run95-contribution-retry-token"; + const server = createServer((request, response) => { + expect(request.method).toBe("POST"); + expect(request.url).toBe("/contribution/retry"); + expect(request.headers.authorization).toBe(`Bearer ${token}`); + response.writeHead(200, { "content-type": "application/json" }); + response.end(JSON.stringify({ status: "uploaded", delivered: 1, queued: 0 })); + }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("operations server did not bind"); + try { + const operations = createTrackBOperations({ + statePath: path.join(os.tmpdir(), "run95-contribution-retry-state.json"), + catalog: [], + operationsEndpoint: `http://127.0.0.1:${address.port}`, + operationsToken: token, + }); + await expect(operations.retryContributionAggregates()).resolves.toEqual({ + status: "uploaded", + delivered: 1, + queued: 0, + }); + } finally { + await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))); + } + }); + test("builds provider evidence and a semantic Verifiers export from the exact durable graph", () => { const observation = { requestId: "request-export-94", From 2f56b069943d3a626334e84f693a1673f089e90c Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 18:16:57 +0800 Subject: [PATCH 16/32] fix(runtime): make recommendation downloads idempotent --- .../apps/runtime-host-bridge/src/index.ts | 3 ++- .../src/track-b-operations.ts | 3 +++ .../test/track-b-operations-api.test.ts | 21 +++++++++++++++++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/role-model-router/apps/runtime-host-bridge/src/index.ts b/role-model-router/apps/runtime-host-bridge/src/index.ts index d322fb70..2355f278 100644 --- a/role-model-router/apps/runtime-host-bridge/src/index.ts +++ b/role-model-router/apps/runtime-host-bridge/src/index.ts @@ -25748,6 +25748,7 @@ export async function createRuntimeBridgeBackend( const contribution = (await operations.readContributionState()) as { readonly recommendationTier?: string; }; + const activeChannelSequence = await operations.readRecommendationRevision(); let run88CorrelationHeader: Record = {}; if (runtimeChannel === "stage") { const identity = options.run88StageIdentity; @@ -25785,7 +25786,7 @@ export async function createRuntimeBridgeBackend( releaseTrack: "stable", recommendationTier: contribution.recommendationTier ?? "advanced", clientSchemaVersions: ["1.0.0"], - activeChannelSequence: 0, + activeChannelSequence, identityKind: "anonymous_public", scopeId: recommendationScopeId, boundaryProtocolVersion: "1.1", diff --git a/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts b/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts index a919966a..5e58a904 100644 --- a/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts +++ b/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts @@ -1818,6 +1818,9 @@ export function createTrackBOperations({ async listRecommendations(): Promise { return (await readState(statePath)).recommendations ?? []; }, + async readRecommendationRevision(): Promise { + return (await readState(statePath)).recommendationRevision ?? 0; + }, async importRecommendationBundle( bundle: Record, verificationKey: string, diff --git a/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts b/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts index caa03954..163b66cc 100644 --- a/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts +++ b/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts @@ -78,7 +78,9 @@ describe("Track B operations APIs", () => { queued: 0, }); } finally { - await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))); + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())), + ); } }); @@ -1692,9 +1694,23 @@ describe("Track B operations APIs", () => { if (url === "https://recommendations.example/api/role-model/recommendations/resolve") { expect(init?.method).toBe("POST"); expect(new Headers(init?.headers).get("authorization")).toBe("Bearer service-token"); - expect(JSON.parse(String(init?.body))).toMatchObject({ + const resolveRequest = JSON.parse(String(init?.body)); + expect(resolveRequest).toMatchObject({ scopeId: "public:deepseek-high", }); + if (resolveRequest.activeChannelSequence === 2) { + return new Response( + JSON.stringify({ + contract: "RecommendationResolveResponseV1", + channel: "development", + status: "not_modified", + snapshotId: "snapshot-pack-downloaded", + channelSequence: 2, + }), + { status: 200, headers: { "content-type": "application/json" } }, + ); + } + expect(resolveRequest.activeChannelSequence).toBe(0); return new Response( JSON.stringify({ contract: "RecommendationResolveResponseV1", @@ -1756,6 +1772,7 @@ describe("Track B operations APIs", () => { confidence: 0.92, }, ]); + await expect(backend.downloadRecommendations()).resolves.toEqual(downloaded); const applied = await backend.applyRecommendation({ id: "recommendation-pack-downloaded" }); expect(applied).toMatchObject({ activePack: { From a2f971ab2d063e447c3acc491d2251799a20d499 Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 18:27:27 +0800 Subject: [PATCH 17/32] fix(runtime): persist recommendation head cursor --- .../apps/runtime-host-bridge/src/index.ts | 41 ++++++++++++-- .../src/track-b-operations.ts | 53 +++++++++++++++++++ .../test/track-b-operations-api.test.ts | 14 ++++- 3 files changed, 104 insertions(+), 4 deletions(-) diff --git a/role-model-router/apps/runtime-host-bridge/src/index.ts b/role-model-router/apps/runtime-host-bridge/src/index.ts index 2355f278..83e5d7fd 100644 --- a/role-model-router/apps/runtime-host-bridge/src/index.ts +++ b/role-model-router/apps/runtime-host-bridge/src/index.ts @@ -25748,7 +25748,15 @@ export async function createRuntimeBridgeBackend( const contribution = (await operations.readContributionState()) as { readonly recommendationTier?: string; }; - const activeChannelSequence = await operations.readRecommendationRevision(); + const recommendationTier = contribution.recommendationTier ?? "advanced"; + const persistedCursor = await operations.readRecommendationCursor(); + const activeCursor = + persistedCursor?.channel === channel && + persistedCursor.scopeId === recommendationScopeId && + persistedCursor.recommendationTier === recommendationTier + ? persistedCursor + : null; + const legacyRecommendationRevision = await operations.readRecommendationRevision(); let run88CorrelationHeader: Record = {}; if (runtimeChannel === "stage") { const identity = options.run88StageIdentity; @@ -25784,9 +25792,15 @@ export async function createRuntimeBridgeBackend( channel, runtimeChannel: channel, releaseTrack: "stable", - recommendationTier: contribution.recommendationTier ?? "advanced", + recommendationTier, clientSchemaVersions: ["1.0.0"], - activeChannelSequence, + activeChannelSequence: activeCursor?.channelSequence ?? 0, + ...(activeCursor + ? { + activeSnapshotId: activeCursor.snapshotId, + activeManifestHash: activeCursor.manifestHash, + } + : {}), identityKind: "anonymous_public", scopeId: recommendationScopeId, boundaryProtocolVersion: "1.1", @@ -25798,10 +25812,30 @@ export async function createRuntimeBridgeBackend( return operations.listRecommendations(); if ( resolved.status !== "available" || + !Number.isSafeInteger(Number(resolved.channelSequence)) || + typeof resolved.snapshotId !== "string" || typeof resolved.bundleUri !== "string" || typeof resolved.manifestHash !== "string" ) throw new Error("recommendation resolve response did not include an available bundle"); + const recommendationCursor = { + channel, + scopeId: recommendationScopeId, + recommendationTier, + channelSequence: Number(resolved.channelSequence), + snapshotId: resolved.snapshotId, + manifestHash: resolved.manifestHash, + }; + if (!activeCursor && recommendationCursor.channelSequence === legacyRecommendationRevision) { + const existing = await operations.listRecommendations(); + if ( + existing.length > 0 && + existing.every((row) => row.provenance === `cloud:${recommendationCursor.manifestHash}`) + ) { + await operations.rememberRecommendationCursor(recommendationCursor); + return existing; + } + } const manifestUrl = new URL(resolved.bundleUri); const manifestResponse = await fetch(manifestUrl); if (!manifestResponse.ok) @@ -25841,6 +25875,7 @@ export async function createRuntimeBridgeBackend( signature, }, verificationKey, + recommendationCursor, ); }, async applyRecommendation(body: Record): Promise { diff --git a/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts b/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts index 5e58a904..f38ea616 100644 --- a/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts +++ b/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts @@ -203,6 +203,7 @@ type BridgeState = { readonly contribution?: ContributionState; readonly recommendations?: readonly RecommendationRecord[]; readonly recommendationRevision?: number; + readonly recommendationCursor?: RecommendationCursor | null; readonly extensionMutationReceipts?: readonly ExtensionMutationReceipt[]; readonly activePack?: { readonly id: string; @@ -214,6 +215,14 @@ type BridgeState = { readonly effortSource?: RecommendationEffortSource; } | null; }; +type RecommendationCursor = { + readonly channel: string; + readonly scopeId: string; + readonly recommendationTier: string; + readonly channelSequence: number; + readonly snapshotId: string; + readonly manifestHash: string; +}; type RetentionPolicy = { readonly policyId: string; readonly scope: string; @@ -357,6 +366,7 @@ const EMPTY_STATE: BridgeState = { contribution: EMPTY_CONTRIBUTION, recommendations: [], recommendationRevision: 0, + recommendationCursor: null, extensionMutationReceipts: [], activePack: null, }; @@ -407,12 +417,25 @@ const validate = (value: BridgeState): BridgeState => { !Number.isInteger(plan.sourceRevision)) ) throw new Error("invalid retention plan"); + const recommendationCursor = value.recommendationCursor ?? null; + if ( + recommendationCursor && + (!recommendationCursor.channel || + !recommendationCursor.scopeId || + !recommendationCursor.recommendationTier || + !Number.isSafeInteger(recommendationCursor.channelSequence) || + recommendationCursor.channelSequence < 1 || + !recommendationCursor.snapshotId || + !/^[a-f0-9]{64}$/.test(recommendationCursor.manifestHash)) + ) + throw new Error("invalid recommendation cursor"); return { ...value, retention: { ...value.retention, policies: value.retention.policies ?? [] }, contribution: value.contribution ?? EMPTY_CONTRIBUTION, recommendations: value.recommendations ?? [], recommendationRevision: value.recommendationRevision ?? 0, + recommendationCursor, activePack: value.activePack ?? null, }; }; @@ -1821,6 +1844,28 @@ export function createTrackBOperations({ async readRecommendationRevision(): Promise { return (await readState(statePath)).recommendationRevision ?? 0; }, + async readRecommendationCursor(): Promise { + return (await readState(statePath)).recommendationCursor ?? null; + }, + async rememberRecommendationCursor(cursor: RecommendationCursor): Promise { + const state = await readState(statePath); + const normalized = validate({ ...state, recommendationCursor: cursor }).recommendationCursor; + if ( + !normalized || + normalized.channelSequence !== (state.recommendationRevision ?? 0) || + !(state.recommendations ?? []).length || + (state.recommendations ?? []).some( + (row) => row.provenance !== `cloud:${normalized.manifestHash}`, + ) + ) + throw new Error("recommendation cursor does not identify the imported bundle"); + await writeState(statePath, { + ...state, + revision: state.revision + 1, + generatedAt: new Date().toISOString(), + recommendationCursor: normalized, + }); + }, async importRecommendationBundle( bundle: Record, verificationKey: string, @@ -1883,16 +1928,24 @@ export function createTrackBOperations({ async importRecommendationArtifactBundle( bundle: ArtifactBundleImport, verificationKey: string, + cursor?: RecommendationCursor, ): Promise { const state = await readState(statePath); const rows = importArtifactBundleRecords(bundle, verificationKey, state); const channelSequence = Number(bundle.manifest.channelSequence); + if ( + cursor && + (cursor.channelSequence !== channelSequence || + cursor.manifestHash !== bundle.expectedManifestSha256) + ) + throw new Error("recommendation cursor does not match the imported Artifact Bundle"); await writeState(statePath, { ...state, revision: state.revision + 1, generatedAt: new Date().toISOString(), recommendations: rows, recommendationRevision: channelSequence, + recommendationCursor: cursor ?? null, }); return rows; }, diff --git a/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts b/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts index 163b66cc..04059f12 100644 --- a/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts +++ b/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts @@ -1698,7 +1698,11 @@ describe("Track B operations APIs", () => { expect(resolveRequest).toMatchObject({ scopeId: "public:deepseek-high", }); - if (resolveRequest.activeChannelSequence === 2) { + if ( + resolveRequest.activeChannelSequence === 2 && + resolveRequest.activeSnapshotId === "snapshot-pack-downloaded" && + resolveRequest.activeManifestHash === manifestSha256 + ) { return new Response( JSON.stringify({ contract: "RecommendationResolveResponseV1", @@ -1711,6 +1715,8 @@ describe("Track B operations APIs", () => { ); } expect(resolveRequest.activeChannelSequence).toBe(0); + expect(resolveRequest.activeSnapshotId).toBeUndefined(); + expect(resolveRequest.activeManifestHash).toBeUndefined(); return new Response( JSON.stringify({ contract: "RecommendationResolveResponseV1", @@ -1773,6 +1779,12 @@ describe("Track B operations APIs", () => { }, ]); await expect(backend.downloadRecommendations()).resolves.toEqual(downloaded); + const legacyStatePath = path.join(directory, "track-b-production-bridge.json"); + const legacyState = JSON.parse(await readFile(legacyStatePath, "utf8")); + delete legacyState.recommendationCursor; + await writeFile(legacyStatePath, `${JSON.stringify(legacyState, null, 2)}\n`, "utf8"); + await expect(backend.downloadRecommendations()).resolves.toEqual(downloaded); + await expect(backend.downloadRecommendations()).resolves.toEqual(downloaded); const applied = await backend.applyRecommendation({ id: "recommendation-pack-downloaded" }); expect(applied).toMatchObject({ activePack: { From 83c6b7273c9f2dddb4b2d18f6abc8832fb20c414 Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 19:29:19 +0800 Subject: [PATCH 18/32] fix(runtime): coalesce sibling model health probes --- .../src/remote-health-probe.test.ts | 58 +++++++++++++++++++ .../src/remote-health-probe.ts | 38 +++++++++++- 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/role-model-router/apps/runtime-host-bridge/src/remote-health-probe.test.ts b/role-model-router/apps/runtime-host-bridge/src/remote-health-probe.test.ts index 01c35e7e..ac516a1b 100644 --- a/role-model-router/apps/runtime-host-bridge/src/remote-health-probe.test.ts +++ b/role-model-router/apps/runtime-host-bridge/src/remote-health-probe.test.ts @@ -45,6 +45,64 @@ describe("remote-health-probe", () => { }); }); + it("coalesces one model-list request across effort siblings on the same provider account", async () => { + let requestCount = 0; + const result = await probeRemoteEndpoints({ + litellmHealthy: true, + targets: [ + { + endpointId: "deepseek.personal.primary.global.deepseek-v4-flash", + providerAccountId: "deepseek.personal.primary", + modelId: "deepseek/deepseek-v4-flash", + apiBase: "https://api.deepseek.com/v1", + servingSource: "remote-service", + }, + { + endpointId: "deepseek.personal.primary.global.deepseek-v4-flash-max", + providerAccountId: "deepseek.personal.primary", + modelId: "deepseek/deepseek-v4-flash", + apiBase: "https://api.deepseek.com/v1", + servingSource: "remote-service", + }, + { + endpointId: "deepseek.personal.primary.global.deepseek-v4-pro-max", + providerAccountId: "deepseek.personal.primary", + modelId: "deepseek/deepseek-v4-pro", + apiBase: "https://api.deepseek.com/v1", + servingSource: "remote-service", + }, + ], + resolveAuthorization: async () => "deepseek-live-key", + networkFetcher: async () => { + requestCount += 1; + return new Response( + JSON.stringify({ data: [{ id: "deepseek-v4-flash" }, { id: "deepseek-v4-pro" }] }), + { status: 200, headers: { "content-type": "application/json" } }, + ); + }, + }); + + expect(requestCount).toBe(1); + expect(result).toMatchObject({ probed: 3, healthy: 3, degraded: 0 }); + expect(result.results).toMatchObject([ + { + endpointId: "deepseek.personal.primary.global.deepseek-v4-flash", + modelId: "deepseek/deepseek-v4-flash", + reason: "healthy", + }, + { + endpointId: "deepseek.personal.primary.global.deepseek-v4-flash-max", + modelId: "deepseek/deepseek-v4-flash", + reason: "healthy", + }, + { + endpointId: "deepseek.personal.primary.global.deepseek-v4-pro-max", + modelId: "deepseek/deepseek-v4-pro", + reason: "healthy", + }, + ]); + }); + it("maps auth failures to degraded health with auth reason", async () => { const result = await probeRemoteEndpoints({ litellmHealthy: true, diff --git a/role-model-router/apps/runtime-host-bridge/src/remote-health-probe.ts b/role-model-router/apps/runtime-host-bridge/src/remote-health-probe.ts index dd749c25..03298bb3 100644 --- a/role-model-router/apps/runtime-host-bridge/src/remote-health-probe.ts +++ b/role-model-router/apps/runtime-host-bridge/src/remote-health-probe.ts @@ -1,3 +1,5 @@ +import { createHash } from "node:crypto"; + import { resolveOpenAIProviderUpstreamModelId } from "@role-model-router/provider-openai"; export type RemoteHealthProbeReason = @@ -297,9 +299,43 @@ async function probeTarget( export async function probeRemoteEndpoints( context: RemoteHealthProbeContext, ): Promise { + const modelListRequests = new Map>(); + const sharedNetworkFetcher: typeof fetch = async (input, init) => { + const url = + typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; + const method = ( + init?.method ?? (input instanceof Request ? input.method : "GET") + ).toUpperCase(); + const headers = new Headers( + init?.headers ?? (input instanceof Request ? input.headers : undefined), + ); + const authorizationSha256 = createHash("sha256") + .update(headers.get("authorization") ?? "") + .digest("hex"); + headers.delete("authorization"); + const requestKey = JSON.stringify([ + method, + url, + authorizationSha256, + [...headers.entries()].sort(([left], [right]) => left.localeCompare(right)), + ]); + + let responsePromise = modelListRequests.get(requestKey); + if (!responsePromise) { + responsePromise = context.networkFetcher(input, init); + modelListRequests.set(requestKey, responsePromise); + } + return (await responsePromise).clone(); + }; + const results: RemoteHealthProbeResult[] = []; for (const target of context.targets) { - results.push(await probeTarget(target, context)); + results.push( + await probeTarget(target, { + ...context, + networkFetcher: sharedNetworkFetcher, + }), + ); } const healthy = results.filter((result) => result.reason === "healthy").length; From 82674dce1369d68ac2393852e40b4aa3c23f284c Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 20:58:50 +0800 Subject: [PATCH 19/32] fix(runtime): preserve public startup retry typing --- role-model-router/apps/runtime-host-bridge/src/cli.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/role-model-router/apps/runtime-host-bridge/src/cli.ts b/role-model-router/apps/runtime-host-bridge/src/cli.ts index 6e36a97f..e3e23a36 100644 --- a/role-model-router/apps/runtime-host-bridge/src/cli.ts +++ b/role-model-router/apps/runtime-host-bridge/src/cli.ts @@ -1007,6 +1007,13 @@ export async function main(): Promise { ), }); let postObservationOperations: ReturnType | null = null; + // `createBackend` initializes this after the packaged runtime is selected. + // Keep that initialization boundary opaque to TypeScript's local control-flow + // analysis: the public-only build does not inline the private operations + // adapter, but startup still needs to retry a durable outbox when it is + // available at runtime. + const currentPostObservationOperations = (): ReturnType | null => + postObservationOperations; const drainPostObservationOutbox = async ( runtime: Awaited>, ) => @@ -1322,7 +1329,7 @@ export async function main(): Promise { await drainPostObservationOutbox(extensionRuntime); // A prior cloud outage must not require an unrelated new provider request // before its already-authorized, durable aggregate is retried. - await postObservationOperations?.retryContributionAggregates(); + await currentPostObservationOperations()?.retryContributionAggregates(); } catch (error) { console.error("[role-model] extension host failed after core runtime was ready:", error); } From daa284627211d86290ab7e7a76039fc6004b8b98 Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 21:15:11 +0800 Subject: [PATCH 20/32] style: format Run 95 migration sources --- .../sqlite-memory/src/legacy-migration.ts | 24 ++++++++++++------- .../test/legacy-migration.test.ts | 3 ++- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/role-model-router/packages/sqlite-memory/src/legacy-migration.ts b/role-model-router/packages/sqlite-memory/src/legacy-migration.ts index 132adfda..f73071eb 100644 --- a/role-model-router/packages/sqlite-memory/src/legacy-migration.ts +++ b/role-model-router/packages/sqlite-memory/src/legacy-migration.ts @@ -322,10 +322,7 @@ function classifyLegacyRow( try { const parsed = JSON.parse(observationJson); if (!isGraphObservationPointer(parsed)) return { kind: "import" }; - if ( - typeof parsed.artifactRef === "object" && - canonicalPointerValidator?.(parsed) - ) { + if (typeof parsed.artifactRef === "object" && canonicalPointerValidator?.(parsed)) { return { kind: "canonical", reference: parsed.artifactRef }; } return { kind: "quarantine", reason: "unresolved_graph_pointer" }; @@ -396,8 +393,7 @@ function sourceProof( // Run 94 SP6: a pointer-shaped row without a matching migration ref is // unclassified residue — quarantined and excluded from parity inputs. const confirmedCanonical = - typeof parsed.artifactRef === "object" && - canonicalPointerValidator?.(parsed) === true; + typeof parsed.artifactRef === "object" && canonicalPointerValidator?.(parsed) === true; if (!row.source_hash && !confirmedCanonical) continue; rowHash = row.source_hash ?? sha256(row.observation_json); } @@ -905,7 +901,8 @@ export class LegacySqliteMigration { const quarantined = rows.flatMap((row) => row.imported_source_id ? [] - : classifyLegacyRow(row.observation_json, this.#canonicalPointerValidator).kind === "quarantine" + : classifyLegacyRow(row.observation_json, this.#canonicalPointerValidator).kind === + "quarantine" ? [row] : [], ); @@ -1079,7 +1076,8 @@ export class LegacySqliteMigration { sourceHash, classification.reference.scopeId, classification.reference.artifactId, - classification.reference.artifactPath ?? `artifact://${classification.reference.scopeId}/${classification.reference.artifactId}`, + classification.reference.artifactPath ?? + `artifact://${classification.reference.scopeId}/${classification.reference.artifactId}`, classification.reference.contentHash, this.#now(), ); @@ -1165,7 +1163,15 @@ export class LegacySqliteMigration { SET source_count = ?, source_hash = ?, target_count = ?, target_hash = ?, cursor = ?, updated_at_ms = ? WHERE migration_id = ?`, ) - .run(source.count, source.hash, proof.count, proof.hash, rows.at(-1)?.request_id ?? null, this.#now(), MIGRATION_ID); + .run( + source.count, + source.hash, + proof.count, + proof.hash, + rows.at(-1)?.request_id ?? null, + this.#now(), + MIGRATION_ID, + ); return { migratedCount, pendingCount: pending }; } finally { database.close(); diff --git a/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts b/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts index fd68da9e..b6a63e21 100644 --- a/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts +++ b/role-model-router/packages/sqlite-memory/test/legacy-migration.test.ts @@ -346,7 +346,8 @@ describe("TB04 real SQLite legacy migration", () => { artifactPath: `artifact://${sourceId}`, contentHash, }), - canonicalPointerValidator: candidate => verified && candidate.requestId === pointer.requestId, + canonicalPointerValidator: (candidate) => + verified && candidate.requestId === pointer.requestId, }); const unresolved = createMigration(false); From 9e95cd29d5ed9f41c4c095f09c348081acc5fdf8 Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 21:32:15 +0800 Subject: [PATCH 21/32] fix(ci): build runtime test dependency --- .../apps/runtime-host-bridge/package.json | 4 ++-- .../test/clean-checkout-test-contract.test.ts | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 role-model-router/apps/runtime-host-bridge/test/clean-checkout-test-contract.test.ts diff --git a/role-model-router/apps/runtime-host-bridge/package.json b/role-model-router/apps/runtime-host-bridge/package.json index ffd26d1f..d98c34e3 100644 --- a/role-model-router/apps/runtime-host-bridge/package.json +++ b/role-model-router/apps/runtime-host-bridge/package.json @@ -41,8 +41,8 @@ "scripts": { "build": "tsc -p tsconfig.json", "test": "vitest run", - "test:critical": "vitest run test/account-repair.test.ts test/unified-runtime-config.test.ts test/provider-overlap-metadata.test.ts test/benchmark-summary.test.ts test/validate-observability.test.ts test/validate-ui.test.ts", - "test:router": "vitest run test/controller-routing-contract.test.ts test/runtime-routing-model.test.ts test/request-capability-inference.test.ts test/alias-capability-routing.test.ts test/restart-rehydration.test.ts test/endpoint-rehydration.test.ts test/model-capability-resolver.test.ts test/session-readiness-api.test.ts test/downstream-openai-discovery.test.ts test/validate-restart-rehydration.test.ts test/validate-observability.test.ts", + "test:critical": "corepack pnpm --filter @role-model-router/profile-aggregator build && vitest run test/account-repair.test.ts test/unified-runtime-config.test.ts test/provider-overlap-metadata.test.ts test/benchmark-summary.test.ts test/validate-observability.test.ts test/validate-ui.test.ts", + "test:router": "corepack pnpm --filter @role-model-router/profile-aggregator build && vitest run test/controller-routing-contract.test.ts test/runtime-routing-model.test.ts test/request-capability-inference.test.ts test/alias-capability-routing.test.ts test/restart-rehydration.test.ts test/endpoint-rehydration.test.ts test/model-capability-resolver.test.ts test/session-readiness-api.test.ts test/downstream-openai-discovery.test.ts test/validate-restart-rehydration.test.ts test/validate-observability.test.ts", "package-sea": "tsx src/package-sea.ts", "validate-packaging": "corepack pnpm build && tsx src/validate-packaging.ts" } diff --git a/role-model-router/apps/runtime-host-bridge/test/clean-checkout-test-contract.test.ts b/role-model-router/apps/runtime-host-bridge/test/clean-checkout-test-contract.test.ts new file mode 100644 index 00000000..f89ed7bb --- /dev/null +++ b/role-model-router/apps/runtime-host-bridge/test/clean-checkout-test-contract.test.ts @@ -0,0 +1,19 @@ +import { readFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const packageJsonPath = fileURLToPath(new URL("../package.json", import.meta.url)); + +describe("clean checkout runtime test contract", () => { + it("builds profile-aggregator before runtime suites import its dist entrypoint", async () => { + const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")) as { + scripts: Record; + }; + + for (const scriptName of ["test:critical", "test:router"]) { + expect(packageJson.scripts[scriptName]).toContain( + "pnpm --filter @role-model-router/profile-aggregator build", + ); + } + }); +}); From 0b8cb3e3233ed69aaeb41df287ed65d71f189ebe Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 21:46:51 +0800 Subject: [PATCH 22/32] fix(ci): keep restart validation hermetic --- .../src/validate-restart-rehydration.ts | 4 ++++ .../test/clean-checkout-test-contract.test.ts | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/role-model-router/apps/runtime-host-bridge/src/validate-restart-rehydration.ts b/role-model-router/apps/runtime-host-bridge/src/validate-restart-rehydration.ts index 19588b3a..600cb4c3 100644 --- a/role-model-router/apps/runtime-host-bridge/src/validate-restart-rehydration.ts +++ b/role-model-router/apps/runtime-host-bridge/src/validate-restart-rehydration.ts @@ -83,6 +83,10 @@ export async function runRestartRehydrationValidation( runtimeStateRoot: options.runtimeStateRoot, scopeId: options.scopeId, unifiedRuntimeConfigPath, + // Registry rehydration and /v1/models readback do not exercise the + // local vendor process. Keep this CI validation hermetic instead of + // resolving a mutable llama-swap release from GitHub. + runtimeVendorStartup: "disabled", // This validation proves durable restart rehydration. Its fixture API key // is deliberately synthetic, so admission must stay hermetic rather than // contacting the real Moonshot endpoint during CI. diff --git a/role-model-router/apps/runtime-host-bridge/test/clean-checkout-test-contract.test.ts b/role-model-router/apps/runtime-host-bridge/test/clean-checkout-test-contract.test.ts index f89ed7bb..3e28e4bc 100644 --- a/role-model-router/apps/runtime-host-bridge/test/clean-checkout-test-contract.test.ts +++ b/role-model-router/apps/runtime-host-bridge/test/clean-checkout-test-contract.test.ts @@ -3,6 +3,9 @@ import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; const packageJsonPath = fileURLToPath(new URL("../package.json", import.meta.url)); +const restartValidationPath = fileURLToPath( + new URL("../src/validate-restart-rehydration.ts", import.meta.url), +); describe("clean checkout runtime test contract", () => { it("builds profile-aggregator before runtime suites import its dist entrypoint", async () => { @@ -16,4 +19,10 @@ describe("clean checkout runtime test contract", () => { ); } }); + + it("keeps restart rehydration validation hermetic when it only needs registry readback", async () => { + const source = await readFile(restartValidationPath, "utf8"); + + expect(source).toContain('runtimeVendorStartup: "disabled"'); + }); }); From 0b7003ba9f51ecc8b2005c1872017166c979379b Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 21:50:51 +0800 Subject: [PATCH 23/32] fix(ci): build smoke dependency closure --- package.json | 2 +- scripts/ci-workflow.test.mjs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index aa56673e..50446d21 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "storage:compact": "node scripts/track-b/run-command-test.mjs TB06-CMD-04", "test:track-b-authorization": "node scripts/track-b/run-command-test.mjs TB07-CMD-03", "test:rust": "cargo test --manifest-path role-model-router/rust/Cargo.toml --workspace", - "smoke": "corepack pnpm --filter @role-model-router/gateway-smoke exec tsx src/index.ts", + "smoke": "corepack pnpm --filter @role-model-router/gateway-smoke... run build && corepack pnpm --filter @role-model-router/gateway-smoke exec tsx src/index.ts", "ci:check": "corepack pnpm run lint && corepack pnpm run schemas:validate && corepack pnpm run build && corepack pnpm run test && corepack pnpm run runtime:test-critical && corepack pnpm run test:rust && corepack pnpm run smoke" }, "devDependencies": { diff --git a/scripts/ci-workflow.test.mjs b/scripts/ci-workflow.test.mjs index 09fa9b0c..731ed3ed 100644 --- a/scripts/ci-workflow.test.mjs +++ b/scripts/ci-workflow.test.mjs @@ -98,6 +98,13 @@ test("workspace tests serialize the resource-heavy runtime proofs", () => { ); }); +test("smoke prepares its dist-exporting workspace dependencies in a clean checkout", () => { + assert.match( + packageManifest.scripts.smoke, + /pnpm --filter @role-model-router\/gateway-smoke\.\.\. run build/, + ); +}); + test("runtime-host integration tests do not contend for worker-owned state", () => { assert.match(runtimeHostVitestConfig, /fileParallelism:\s*false/); }); From 6ae7a47b572e6460da30c47325d26c527a690fc2 Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 22:04:10 +0800 Subject: [PATCH 24/32] test(track-b): align storage header contract --- ...ack-b-storage-graph-cloud-roundtrip.sp8.storage-ui.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/role-model-router/apps/runtime-ui/e2e/recursive-94-direct-track-b-storage-graph-cloud-roundtrip.sp8.storage-ui.spec.ts b/role-model-router/apps/runtime-ui/e2e/recursive-94-direct-track-b-storage-graph-cloud-roundtrip.sp8.storage-ui.spec.ts index b35993ab..fd8da4fd 100644 --- a/role-model-router/apps/runtime-ui/e2e/recursive-94-direct-track-b-storage-graph-cloud-roundtrip.sp8.storage-ui.spec.ts +++ b/role-model-router/apps/runtime-ui/e2e/recursive-94-direct-track-b-storage-graph-cloud-roundtrip.sp8.storage-ui.spec.ts @@ -58,7 +58,9 @@ test.describe("@recursive:94-direct-track-b-storage-graph-cloud-roundtrip @sp8 @ await expect(summary.getByText("Reclaimable", { exact: true })).toBeVisible(); await expect(summary.getByText("Unattributed physical bytes", { exact: true })).toBeVisible(); await expect(summary.getByText("Legal holds")).toBeVisible(); - await expect(page.getByRole("columnheader", { name: "Enforcement" })).toBeVisible(); + // The storage projection reports measured observation and retention state; + // the former legacy Enforcement column must not be revived by the packaged UI. + await expect(page.getByRole("columnheader", { name: "Retention state" })).toBeVisible(); await expect(page.getByRole("columnheader", { name: "Observation state" })).toBeVisible(); await expect(page.getByRole("columnheader", { name: "Fresh through" })).toBeVisible(); }); From 6f591e272f6697427c2a23341ffc2eaa78e377de Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Sun, 30 Aug 2026 22:12:22 +0800 Subject: [PATCH 25/32] test(track-b): assert physical storage projection --- ...k-b-storage-graph-cloud-roundtrip.sp8.storage-ui.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/role-model-router/apps/runtime-ui/e2e/recursive-94-direct-track-b-storage-graph-cloud-roundtrip.sp8.storage-ui.spec.ts b/role-model-router/apps/runtime-ui/e2e/recursive-94-direct-track-b-storage-graph-cloud-roundtrip.sp8.storage-ui.spec.ts index fd8da4fd..ce33f8c3 100644 --- a/role-model-router/apps/runtime-ui/e2e/recursive-94-direct-track-b-storage-graph-cloud-roundtrip.sp8.storage-ui.spec.ts +++ b/role-model-router/apps/runtime-ui/e2e/recursive-94-direct-track-b-storage-graph-cloud-roundtrip.sp8.storage-ui.spec.ts @@ -58,9 +58,9 @@ test.describe("@recursive:94-direct-track-b-storage-graph-cloud-roundtrip @sp8 @ await expect(summary.getByText("Reclaimable", { exact: true })).toBeVisible(); await expect(summary.getByText("Unattributed physical bytes", { exact: true })).toBeVisible(); await expect(summary.getByText("Legal holds")).toBeVisible(); - // The storage projection reports measured observation and retention state; - // the former legacy Enforcement column must not be revived by the packaged UI. - await expect(page.getByRole("columnheader", { name: "Retention state" })).toBeVisible(); + // The physical-resource projection reports measured observation state. Its + // legacy Enforcement column must not be revived by the packaged UI. + await expect(page.getByRole("columnheader", { name: "Enforcement" })).toHaveCount(0); await expect(page.getByRole("columnheader", { name: "Observation state" })).toBeVisible(); await expect(page.getByRole("columnheader", { name: "Fresh through" })).toBeVisible(); }); From 85c5b250398ddcb59b3cdfa0cfe3a006d0e24957 Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Mon, 31 Aug 2026 05:43:50 +0800 Subject: [PATCH 26/32] fix(release): build Track B adapter dependencies --- .github/workflows/build-binaries.yml | 10 ++++++++++ scripts/build-binaries-workflow.test.mjs | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 7bf91da1..9fa0a4f2 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -280,6 +280,16 @@ jobs: - name: Build UI run: pnpm --filter @role-model-router/runtime-ui run build + # The Track B adapter bundles sqlite-memory directly from TypeScript. + # That package resolves profile-aggregator through its runtime export, + # which intentionally targets dist/. A fresh CI checkout therefore + # needs this focused public build before the paired private distribution + # can be assembled. + - name: Build public runtime adapter dependencies + if: env.ROLE_MODEL_BUILD_CHANNEL == 'stage' || env.ROLE_MODEL_BUILD_CHANNEL == 'production' + working-directory: ${{ env.ROLE_MODEL_BUILD_CHANNEL == 'production' && '.cache/paired-public' || '.' }} + run: corepack pnpm --filter @role-model-router/profile-aggregator... build + - name: Validate exact private release revision if: env.ROLE_MODEL_BUILD_CHANNEL == 'stage' || env.ROLE_MODEL_BUILD_CHANNEL == 'production' shell: bash diff --git a/scripts/build-binaries-workflow.test.mjs b/scripts/build-binaries-workflow.test.mjs index 2f3b2c97..fd0fd857 100644 --- a/scripts/build-binaries-workflow.test.mjs +++ b/scripts/build-binaries-workflow.test.mjs @@ -109,6 +109,18 @@ test("production installs dependencies in the accepted stage checkout before Tra ); }); +test("paired Track B packaging builds public runtime dependencies before bundling source imports", () => { + assert.match(workflow, /Build public runtime adapter dependencies/); + assert.match( + workflow, + /Build public runtime adapter dependencies[\s\S]*?corepack pnpm --filter @role-model-router\/profile-aggregator\.\.\. build/, + ); + assert.match( + workflow, + /Build public runtime adapter dependencies[\s\S]*?if: env\.ROLE_MODEL_BUILD_CHANNEL == 'stage' \|\| env\.ROLE_MODEL_BUILD_CHANNEL == 'production'/, + ); +}); + test("stable tags require a manually accepted exact stage candidate", () => { assert.match(workflow, /rc-approved/); assert.match(workflow, /candidate\.workflow_run\.head_sha/); From 7d01aa0f75d2c61fcc296942ce88eae524f754e1 Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Mon, 31 Aug 2026 06:53:53 +0800 Subject: [PATCH 27/32] fix(stage): keep secrets out of release packages --- .github/workflows/build-binaries.yml | 25 +++++------ .../apps/runtime-host-bridge/src/cli.ts | 9 +--- .../src/track-b-runtime.ts | 25 +++++------ .../test/track-b-runtime-composition.test.ts | 45 ++++++------------- scripts/build-binaries-workflow.test.mjs | 7 +++ 5 files changed, 44 insertions(+), 67 deletions(-) diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 9fa0a4f2..0856d752 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -450,21 +450,20 @@ jobs: } } - - name: Include managed stage secrets in the stage package - if: env.ROLE_MODEL_BUILD_CHANNEL == 'stage' + - name: Assert release package has no bundled secret material shell: pwsh - env: - STAGE_ARTIFACT_DIGEST_KEY: ${{ secrets.STAGE_ARTIFACT_DIGEST_KEY }} - STAGE_ARTIFACT_ENCRYPTION_KEY: ${{ secrets.STAGE_ARTIFACT_ENCRYPTION_KEY }} - STAGE_DESTINATION_MATERIAL: ${{ secrets.STAGE_DESTINATION_MATERIAL }} - STAGE_RECOMMENDATION_MATERIAL: ${{ secrets.STAGE_RECOMMENDATION_MATERIAL }} run: | - $secretsDir = "role-model-router/dist/release/${{ matrix.target }}/secrets" - New-Item -ItemType Directory -Force -Path $secretsDir | Out-Null - [IO.File]::WriteAllText("$secretsDir/artifact-digest.key", $env:STAGE_ARTIFACT_DIGEST_KEY) - [IO.File]::WriteAllText("$secretsDir/artifact-encryption.key", $env:STAGE_ARTIFACT_ENCRYPTION_KEY) - [IO.File]::WriteAllText("$secretsDir/destination-material.json", $env:STAGE_DESTINATION_MATERIAL) - [IO.File]::WriteAllText("$secretsDir/recommendation-material.json", $env:STAGE_RECOMMENDATION_MATERIAL) + $packageDir = "role-model-router/dist/release/${{ matrix.target }}" + $forbiddenPaths = @( + "$packageDir/secrets", + "$packageDir/.env", + "$packageDir/.env.local" + ) + foreach ($path in $forbiddenPaths) { + if (Test-Path $path) { + throw "Bundled secret material is forbidden in release packages: $path" + } + } - name: Archive standalone package (Unix) if: matrix.target != 'win32-x64' diff --git a/role-model-router/apps/runtime-host-bridge/src/cli.ts b/role-model-router/apps/runtime-host-bridge/src/cli.ts index e3e23a36..7368eda8 100644 --- a/role-model-router/apps/runtime-host-bridge/src/cli.ts +++ b/role-model-router/apps/runtime-host-bridge/src/cli.ts @@ -703,9 +703,7 @@ export function applyRecommendationServiceLauncherConfig(values: LauncherConfigV (channel === "stage" ? "https://recommendations-stage.role-model.dev" : undefined); const verificationKey = readLauncherString(values, "recommendation-verification-key"); const serviceToken = readLauncherString(values, "recommendation-service-token"); - const materialFile = - readLauncherString(values, "recommendation-material-file") ?? - (channel === "stage" ? path.resolve("secrets", "recommendation-material.json") : undefined); + const materialFile = readLauncherString(values, "recommendation-material-file"); const aggregateScope = readLauncherString(values, "aggregate-scope"); const recommendationScope = readLauncherString(values, "recommendation-scope"); @@ -1254,10 +1252,7 @@ export async function main(): Promise { trustMaterialFile: args.values["destination-material-file"] ?? args.values["destination-trust-material-file"] ?? - process.env.ROLE_MODEL_DESTINATION_AUTH_SECRET_FILE ?? - (runtimeChannel === "stage" - ? path.resolve("secrets", "destination-material.json") - : undefined), + process.env.ROLE_MODEL_DESTINATION_AUTH_SECRET_FILE, aggregateEndpoint: args.values["aggregate-ingestion-url"] ?? process.env.ROLE_MODEL_AGGREGATE_INGESTION_URL ?? diff --git a/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts b/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts index 2eeb5cc3..b56116e2 100644 --- a/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts +++ b/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts @@ -185,7 +185,7 @@ async function assertManagedArtifactKeyFile(filePath: string): Promise { } /** - * Resolves operator-supplied keys or provisions a runtime-owned production key pair. + * Resolves operator-supplied keys or provisions a runtime-owned Stage/production key pair. * The owned pair lives under the stable runtime state root, never the versioned package, * so manual binary updates keep existing Message Graph ciphertext readable. */ @@ -211,19 +211,7 @@ export async function resolveManagedArtifactKeyFiles(options: { ]); return resolved; } - if (options.channel === "stage") { - const packagedDigest = path.resolve("secrets", "artifact-digest.key"); - const packagedEncryption = path.resolve("secrets", "artifact-encryption.key"); - await Promise.all([ - assertManagedArtifactKeyFile(packagedDigest), - assertManagedArtifactKeyFile(packagedEncryption), - ]); - return { - artifactDigestKeyFile: packagedDigest, - artifactEncryptionKeyFile: packagedEncryption, - }; - } - if (options.channel !== "production") return {}; + if (options.channel === "development") return {}; const stableStateRoot = path.resolve(options.stateRoot); const keyRoot = path.join(stableStateRoot, "managed-keys"); @@ -277,6 +265,13 @@ export interface TrackBProductionRuntimeOptions { sidecar: OwnedTrackBSidecarSpec; } +/** + * A persisted Track B state can take longer than a process-spawn grace period + * to reconcile before it can report ready. Keep that recovery bounded while + * matching the extension supervisor's documented allowance. + */ +export const TRACK_B_SIDECAR_STARTUP_TIMEOUT_MS = 90_000; + /** * Normal host-path adapter for graph-primary observation storage. The SQLite * package owns the journal and pointer rows; the injected store owns rich bytes. @@ -2996,7 +2991,7 @@ export function createOwnedTrackBSidecarSpec(options: { // window to reconcile durable state before it can publish readiness. Keep // this bounded, but align the owned sidecar with the production extension // supervisor's recovery allowance. - }, options.startupTimeoutMs ?? 30_000); + }, options.startupTimeoutMs ?? TRACK_B_SIDECAR_STARTUP_TIMEOUT_MS); const rejectError = (error: Error) => { clearTimeout(timer); reject( diff --git a/role-model-router/apps/runtime-host-bridge/test/track-b-runtime-composition.test.ts b/role-model-router/apps/runtime-host-bridge/test/track-b-runtime-composition.test.ts index 66ed59ee..de9a23bd 100644 --- a/role-model-router/apps/runtime-host-bridge/test/track-b-runtime-composition.test.ts +++ b/role-model-router/apps/runtime-host-bridge/test/track-b-runtime-composition.test.ts @@ -8,6 +8,7 @@ import { pathToFileURL } from "node:url"; import { afterEach, describe, expect, test } from "vitest"; import { + TRACK_B_SIDECAR_STARTUP_TIMEOUT_MS, createOwnedTrackBSidecarSpec, createPackagedProductionRuntime, createProductionExtensionRuntime, @@ -34,6 +35,10 @@ afterEach(async () => { describe("production Track B composition", () => { const repoRoot = path.resolve(import.meta.dirname, "..", "..", "..", ".."); + test("allows the documented bounded recovery window before a persisted sidecar is rejected", () => { + expect(TRACK_B_SIDECAR_STARTUP_TIMEOUT_MS).toBe(90_000); + }); + test("provisions production Message Graph keys once and reuses them across package updates", async () => { const stateRoot = await mkdtemp(path.join(os.tmpdir(), "role-model-managed-artifact-keys-")); roots.push(stateRoot); @@ -90,13 +95,9 @@ describe("production Track B composition", () => { ).rejects.toThrow(/incomplete.*managed artifact key/i); }); - test("resolves packaged stage artifact keys from the release secrets directory", async () => { + test("provisions stage artifact keys under durable state instead of a release secrets directory", async () => { const packageRoot = await mkdtemp(path.join(os.tmpdir(), "role-model-stage-package-")); roots.push(packageRoot); - const secretsDir = path.join(packageRoot, "secrets"); - await mkdir(secretsDir, { recursive: true }); - await writeFile(path.join(secretsDir, "artifact-digest.key"), Buffer.alloc(32, 3)); - await writeFile(path.join(secretsDir, "artifact-encryption.key"), Buffer.alloc(32, 5)); const previousCwd = process.cwd(); process.chdir(packageRoot); try { @@ -112,43 +113,23 @@ describe("production Track B composition", () => { channel: "stage", stateRoot: path.join(packageRoot, "state"), }); + expect(await readFile(resolved.artifactDigestKeyFile)).toHaveLength(32); + expect(await readFile(resolved.artifactEncryptionKeyFile)).toHaveLength(32); expect(resolved.artifactDigestKeyFile).toBe( - path.join(packageRoot, "secrets", "artifact-digest.key"), + path.join(packageRoot, "state", "managed-keys", "artifact-digest.key"), ); expect(resolved.artifactEncryptionKeyFile).toBe( - path.join(packageRoot, "secrets", "artifact-encryption.key"), + path.join(packageRoot, "state", "managed-keys", "artifact-encryption.key"), ); } finally { process.chdir(previousCwd); } }); - test("refuses a stage package whose packaged artifact keys are missing", async () => { - const packageRoot = await mkdtemp(path.join(os.tmpdir(), "role-model-stage-package-empty-")); - roots.push(packageRoot); - const previousCwd = process.cwd(); - process.chdir(packageRoot); - try { - const runtimeModule = await import("../src/track-b-runtime.js"); - const resolveManagedArtifactKeyFiles = Reflect.get( - runtimeModule, - "resolveManagedArtifactKeyFiles", - ) as (input: { channel: "stage"; stateRoot: string }) => Promise; - await expect( - resolveManagedArtifactKeyFiles({ - channel: "stage", - stateRoot: path.join(packageRoot, "state"), - }), - ).rejects.toThrow(/managed artifact key|ENOENT/i); - } finally { - process.chdir(previousCwd); - } - }); - - test("packages the stage channel with self-contained secrets defaults", async () => { + test("requires external stage trust and recommendation material instead of package-local defaults", async () => { const cliSource = readFileSync(new URL("../src/cli.ts", import.meta.url), "utf8"); - expect(cliSource).toMatch(/secrets",\s*"recommendation-material\.json/); - expect(cliSource).toMatch(/secrets",\s*"destination-material\.json/); + expect(cliSource).not.toMatch(/secrets",\s*"recommendation-material\.json/); + expect(cliSource).not.toMatch(/secrets",\s*"destination-material\.json/); expect(cliSource).toMatch(/recommendations-stage\.role-model\.dev/); expect(cliSource).toMatch(/ingest-stage\.role-model\.dev\/contribution\/aggregate/); expect(cliSource).toMatch(/standalone-runtime-stage/); diff --git a/scripts/build-binaries-workflow.test.mjs b/scripts/build-binaries-workflow.test.mjs index fd0fd857..f35cabed 100644 --- a/scripts/build-binaries-workflow.test.mjs +++ b/scripts/build-binaries-workflow.test.mjs @@ -121,6 +121,13 @@ test("paired Track B packaging builds public runtime dependencies before bundlin ); }); +test("stage and production release archives never receive managed runtime secrets", () => { + assert.doesNotMatch(workflow, /Include managed stage secrets in the stage package/); + assert.doesNotMatch(workflow, /STAGE_ARTIFACT_DIGEST_KEY|STAGE_ARTIFACT_ENCRYPTION_KEY/); + assert.doesNotMatch(workflow, /STAGE_DESTINATION_MATERIAL|STAGE_RECOMMENDATION_MATERIAL/); + assert.match(workflow, /Assert release package has no bundled secret material/); +}); + test("stable tags require a manually accepted exact stage candidate", () => { assert.match(workflow, /rc-approved/); assert.match(workflow, /candidate\.workflow_run\.head_sha/); From 47f786afefc67b454d9ca576b5477907a00550ce Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Mon, 31 Aug 2026 08:06:00 +0800 Subject: [PATCH 28/32] fix(stage): disable aggregate without external trust --- .../apps/runtime-host-bridge/src/cli.ts | 15 +++++++++------ .../test/track-b-runtime-composition.test.ts | 6 ++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/role-model-router/apps/runtime-host-bridge/src/cli.ts b/role-model-router/apps/runtime-host-bridge/src/cli.ts index 7368eda8..05bf1709 100644 --- a/role-model-router/apps/runtime-host-bridge/src/cli.ts +++ b/role-model-router/apps/runtime-host-bridge/src/cli.ts @@ -1217,6 +1217,10 @@ export async function main(): Promise { } const trackBStateRoot = path.join(options.runtimeStateRoot, options.scopeId, "track-b"); const runtimeChannel = packagedProfile?.channel ?? "development"; + const destinationTrustMaterialFile = + args.values["destination-material-file"] ?? + args.values["destination-trust-material-file"] ?? + process.env.ROLE_MODEL_DESTINATION_AUTH_SECRET_FILE; const artifactKeyFiles = await resolveManagedArtifactKeyFiles({ channel: runtimeChannel, stateRoot: trackBStateRoot, @@ -1249,20 +1253,19 @@ export async function main(): Promise { channel: runtimeChannel, artifactDigestKeyFile: artifactKeyFiles.artifactDigestKeyFile, artifactEncryptionKeyFile: artifactKeyFiles.artifactEncryptionKeyFile, - trustMaterialFile: - args.values["destination-material-file"] ?? - args.values["destination-trust-material-file"] ?? - process.env.ROLE_MODEL_DESTINATION_AUTH_SECRET_FILE, + trustMaterialFile: destinationTrustMaterialFile, aggregateEndpoint: args.values["aggregate-ingestion-url"] ?? process.env.ROLE_MODEL_AGGREGATE_INGESTION_URL ?? - (runtimeChannel === "stage" + (runtimeChannel === "stage" && destinationTrustMaterialFile ? "https://ingest-stage.role-model.dev/contribution/aggregate" : undefined), aggregateScope: args.values["aggregate-scope"] ?? process.env.ROLE_MODEL_AGGREGATE_SCOPE ?? - (runtimeChannel === "stage" ? "standalone-runtime-stage" : undefined), + (runtimeChannel === "stage" && destinationTrustMaterialFile + ? "standalone-runtime-stage" + : undefined), ...(aggregateCorrelationReleaseId && aggregateCorrelationCohortId ? { aggregateCorrelationReleaseId, diff --git a/role-model-router/apps/runtime-host-bridge/test/track-b-runtime-composition.test.ts b/role-model-router/apps/runtime-host-bridge/test/track-b-runtime-composition.test.ts index de9a23bd..ac9808e9 100644 --- a/role-model-router/apps/runtime-host-bridge/test/track-b-runtime-composition.test.ts +++ b/role-model-router/apps/runtime-host-bridge/test/track-b-runtime-composition.test.ts @@ -135,6 +135,12 @@ describe("production Track B composition", () => { expect(cliSource).toMatch(/standalone-runtime-stage/); }); + test("does not enable the Stage aggregate destination unless external trust material is supplied", () => { + const cliSource = readFileSync(new URL("../src/cli.ts", import.meta.url), "utf8"); + expect(cliSource).toMatch(/destinationTrustMaterialFile/); + expect(cliSource).toMatch(/runtimeChannel === "stage" && destinationTrustMaterialFile/); + }); + test("owns and supervises the private operations sidecar without URL injection", async () => { const stateRoot = await mkdtemp(path.join(os.tmpdir(), "role-model-track-b-runtime-")); roots.push(stateRoot); From 287cb4def07b52d734eb29badd824c2544763422 Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Mon, 31 Aug 2026 09:48:29 +0800 Subject: [PATCH 29/32] docs(release): clarify stage candidate publication --- docs/operations/02-ci-and-release-flow.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/operations/02-ci-and-release-flow.md b/docs/operations/02-ci-and-release-flow.md index f3d91f66..7b7ffe5c 100644 --- a/docs/operations/02-ci-and-release-flow.md +++ b/docs/operations/02-ci-and-release-flow.md @@ -56,6 +56,10 @@ Reviewed `hotfix/* -> main` is the explicit emergency exception. packages are available by explicit manual dispatch. Every successful `stage` push publishes a GitHub prerelease with all four stage-channel archives and `SHA256SUMS.txt`; prereleases are never selected by the stable installers. +An explicit `workflow_dispatch` build is useful for package diagnostics, but it does not publish or replace a Stage +prerelease. To create a candidate for acceptance, promote a reviewed public change through `dev -> stage` so that a +`stage` push produces a new immutable `stage-rc-` identity. + After testing, a maintainer runs `.github/workflows/accept-release-candidate.yml` with the exact prerelease tag and checks the explicit acceptance input. That workflow re-downloads the candidate, validates all checksums and stage manifests, and creates the immutable `rc-approved/` receipt. Do not approve a candidate based only on From a4f49ee5c9e93e332894e0d3cb6a5c8cf237bf44 Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Mon, 31 Aug 2026 11:19:40 +0800 Subject: [PATCH 30/32] fix(storage): expose asynchronous audit state --- .../src/track-b-operations.ts | 19 +++++- .../test/track-b-operations-api.test.ts | 58 +++++++++++++++++++ .../apps/runtime-ui/app/lib/runtime-api.ts | 8 +++ .../app/routes/storage-retention.tsx | 10 ++++ 4 files changed, 94 insertions(+), 1 deletion(-) diff --git a/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts b/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts index f38ea616..3f975dff 100644 --- a/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts +++ b/role-model-router/apps/runtime-host-bridge/src/track-b-operations.ts @@ -687,6 +687,23 @@ const privateRetentionRequest = async ( return result; }; +const normalizeStorageAuditReadiness = (value: unknown) => { + const raw = recordValue(value); + if (raw.schemaVersion !== "role-model.storage-audit-readiness.v1") + return { storageAudit: value ?? null, storageAuditStatus: null }; + const audit = recordValue(raw.audit); + return { + storageAudit: audit.schemaVersion === "role-model.storage-audit.v1" ? audit : null, + storageAuditStatus: { + schemaVersion: "role-model.storage-audit-readiness.v1", + status: typeof raw.status === "string" ? raw.status : "pending", + observedAt: typeof raw.observedAt === "string" ? raw.observedAt : null, + freshUntil: typeof raw.freshUntil === "string" ? raw.freshUntil : null, + reason: typeof raw.reason === "string" ? raw.reason : undefined, + }, + }; +}; + function boundedIdentity(value: unknown, label: string): string { if (typeof value !== "string" || value.length < 1 || value.length > 1024) throw new Error(`${label} is required`); @@ -1522,7 +1539,7 @@ export function createTrackBOperations({ if (remote) return { ...normalizeStorageRetentionContract(remote), - storageAudit: storageAudit ?? null, + ...normalizeStorageAuditReadiness(storageAudit), }; const state = await readState(statePath); const categories = state.storageServices.map((row) => ({ diff --git a/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts b/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts index 04059f12..db5f19f0 100644 --- a/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts +++ b/role-model-router/apps/runtime-host-bridge/test/track-b-operations-api.test.ts @@ -2003,6 +2003,64 @@ describe("Track B operations APIs", () => { } }); + test("run95 preserves an asynchronous storage-audit readiness state without presenting it as a completed audit", async () => { + const operations = createServer((request, response) => { + response.writeHead(200, { "content-type": "application/json" }); + if (request.url === "/storage-retention") { + response.end( + JSON.stringify({ + revision: 7, + totalBytes: 140, + physicalResources: [], + logicalClasses: [], + policyState: { state: "absent", channel: "stage" }, + }), + ); + return; + } + if (request.url === "/storage-audit") { + response.end( + JSON.stringify({ + schemaVersion: "role-model.storage-audit-readiness.v1", + status: "pending", + observedAt: null, + freshUntil: null, + reason: "Read-only storage audit is in progress", + }), + ); + return; + } + response.writeHead(404).end(JSON.stringify({ error: "not found" })); + }); + await new Promise((resolve, reject) => { + operations.once("error", reject); + operations.listen(0, "127.0.0.1", resolve); + }); + try { + const address = operations.address(); + if (!address || typeof address === "string") + throw new Error("operations server did not bind"); + const api = createTrackBOperations({ + statePath: path.join(os.tmpdir(), `run95-storage-audit-${Date.now()}.json`), + catalog: [], + operationsEndpoint: `http://127.0.0.1:${address.port}`, + operationsToken: "run95-storage-audit-token-0001", + }); + await expect(api.readStorageRetention()).resolves.toMatchObject({ + revision: 7, + storageAudit: null, + storageAuditStatus: { + schemaVersion: "role-model.storage-audit-readiness.v1", + status: "pending", + }, + }); + } finally { + await new Promise((resolve, reject) => + operations.close((error) => (error ? reject(error) : resolve())), + ); + } + }); + test("run79 mutateExtension enables disables and sets mode with audit receipts", async () => { const runtimeStateRoot = path.join(os.tmpdir(), `track-b-run79-mutate-${Date.now()}`); roots.push(runtimeStateRoot); diff --git a/role-model-router/apps/runtime-ui/app/lib/runtime-api.ts b/role-model-router/apps/runtime-ui/app/lib/runtime-api.ts index 39879b8b..b7662a58 100644 --- a/role-model-router/apps/runtime-ui/app/lib/runtime-api.ts +++ b/role-model-router/apps/runtime-ui/app/lib/runtime-api.ts @@ -1891,6 +1891,14 @@ export interface RuntimeStorageRetentionSummary { readonly graphEdges?: number; readonly measuredAt?: string; } | null; + /** Audit freshness is separate from completed physical accounting. */ + readonly storageAuditStatus?: { + readonly schemaVersion: "role-model.storage-audit-readiness.v1"; + readonly status: "pending" | "stale" | "ready" | string; + readonly observedAt?: string | null; + readonly freshUntil?: string | null; + readonly reason?: string; + } | null; readonly policyState?: { readonly channel: string; readonly state: string; diff --git a/role-model-router/apps/runtime-ui/app/routes/storage-retention.tsx b/role-model-router/apps/runtime-ui/app/routes/storage-retention.tsx index 3ed22e18..3bb64090 100644 --- a/role-model-router/apps/runtime-ui/app/routes/storage-retention.tsx +++ b/role-model-router/apps/runtime-ui/app/routes/storage-retention.tsx @@ -186,6 +186,16 @@ export function StorageRetentionRouteView() { {summary.policyState?.state ?? "not reported"}

) : null} + {summary?.storageAuditStatus ? ( +

+ Storage audit: {summary.storageAuditStatus.status} + {summary.storageAuditStatus.observedAt + ? ` (observed ${summary.storageAuditStatus.observedAt})` + : summary.storageAuditStatus.reason + ? ` — ${summary.storageAuditStatus.reason}` + : ""} +

+ ) : null}

Unattributed physical bytes are measured physical allocation not mapped to logical storage classes; they are not service health. From 3fb0142ffc16a40c0b56f5037015ec99bbe516cd Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Mon, 31 Aug 2026 14:09:18 +0800 Subject: [PATCH 31/32] fix(runtime): preserve graph key recovery boundary --- .../src/track-b-runtime.ts | 21 ++++++++++++++++++ .../test/track-b-runtime-composition.test.ts | 22 ++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts b/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts index b56116e2..6b60497d 100644 --- a/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts +++ b/role-model-router/apps/runtime-host-bridge/src/track-b-runtime.ts @@ -78,6 +78,18 @@ export interface ManagedArtifactKeyFiles { readonly artifactEncryptionKeyFile?: string; } +function hasPersistedArtifactState(stateRoot: string): boolean { + const legacyFile = path.join(stateRoot, "artifact-store.json"); + const roots = [path.join(stateRoot, "artifact-store"), `${legacyFile}.store`]; + return ( + existsSync(legacyFile) || + roots.some( + (root) => + existsSync(path.join(root, "metadata.sqlite")) || existsSync(path.join(root, "blobs")), + ) + ); +} + /** * Small local graph adapter for fixture and development runs that do not have * the private operations sidecar configured. Rich observations live in these @@ -236,6 +248,15 @@ export async function resolveManagedArtifactKeyFiles(options: { if (await pathExists(keyRoot)) return readPublishedPair(); + // Generating a replacement pair for persisted ciphertext irreversibly makes + // the existing graph unreadable. Require recovery of the original pair + // instead; first install is the only safe time to provision keys. + if (hasPersistedArtifactState(stableStateRoot)) { + throw new Error( + "managed artifact keys are absent for existing artifact state; restore both Message Graph keys from backup instead of generating replacements", + ); + } + await mkdir(stableStateRoot, { recursive: true }); const temporaryRoot = await mkdtemp(`${keyRoot}.tmp-`); try { diff --git a/role-model-router/apps/runtime-host-bridge/test/track-b-runtime-composition.test.ts b/role-model-router/apps/runtime-host-bridge/test/track-b-runtime-composition.test.ts index ac9808e9..49724d74 100644 --- a/role-model-router/apps/runtime-host-bridge/test/track-b-runtime-composition.test.ts +++ b/role-model-router/apps/runtime-host-bridge/test/track-b-runtime-composition.test.ts @@ -1,5 +1,5 @@ import { createHash } from "node:crypto"; -import { readFileSync } from "node:fs"; +import { existsSync, readFileSync } from "node:fs"; import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; @@ -95,6 +95,26 @@ describe("production Track B composition", () => { ).rejects.toThrow(/incomplete.*managed artifact key/i); }); + test("refuses to provision replacement Message Graph keys over persisted artifact state", async () => { + const stateRoot = await mkdtemp(path.join(os.tmpdir(), "role-model-missing-artifact-keys-")); + roots.push(stateRoot); + const runtimeModule = await import("../src/track-b-runtime.js"); + const resolveManagedArtifactKeyFiles = Reflect.get( + runtimeModule, + "resolveManagedArtifactKeyFiles", + ) as (input: { channel: "stage"; stateRoot: string }) => Promise; + await mkdir(path.join(stateRoot, "artifact-store"), { recursive: true }); + await writeFile( + path.join(stateRoot, "artifact-store", "metadata.sqlite"), + "existing encrypted graph state", + ); + + await expect(resolveManagedArtifactKeyFiles({ channel: "stage", stateRoot })).rejects.toThrow( + /restore.*Message Graph keys|existing.*artifact/i, + ); + expect(existsSync(path.join(stateRoot, "managed-keys"))).toBe(false); + }); + test("provisions stage artifact keys under durable state instead of a release secrets directory", async () => { const packageRoot = await mkdtemp(path.join(os.tmpdir(), "role-model-stage-package-")); roots.push(packageRoot); From b339439537ac640a16b7cc66848a227dfbe4a95e Mon Sep 17 00:00:00 2001 From: Erik <262919414+try-works@users.noreply.github.com> Date: Mon, 31 Aug 2026 15:58:34 +0800 Subject: [PATCH 32/32] fix(runtime): extend bounded remote readiness probes --- .../apps/runtime-host-bridge/src/remote-health-probe.ts | 6 ++++-- .../test/remote-endpoint-admission-probe.test.ts | 9 ++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/role-model-router/apps/runtime-host-bridge/src/remote-health-probe.ts b/role-model-router/apps/runtime-host-bridge/src/remote-health-probe.ts index 03298bb3..5cae5f4c 100644 --- a/role-model-router/apps/runtime-host-bridge/src/remote-health-probe.ts +++ b/role-model-router/apps/runtime-host-bridge/src/remote-health-probe.ts @@ -61,6 +61,8 @@ export interface RemoteEndpointAdmissionProbeContext { readonly probeTimeoutMs?: number; } +export const DEFAULT_REMOTE_PROBE_TIMEOUT_MS = 15_000; + const COMPARABLE_MODEL_ID_ALIASES: Readonly> = { "moonshot/kimi-k2.7-code": ["kimi-for-coding"], "kimi-k2.7-code": ["kimi-for-coding"], @@ -195,7 +197,7 @@ async function probeTarget( ? resolvedAuthorization : `Bearer ${resolvedAuthorization}`, }, - signal: AbortSignal.timeout(context.probeTimeoutMs ?? 5000), + signal: AbortSignal.timeout(context.probeTimeoutMs ?? DEFAULT_REMOTE_PROBE_TIMEOUT_MS), }); return { response, @@ -394,7 +396,7 @@ export async function probeRemoteEndpointAdmission( "content-type": "application/json", authorization: credential.startsWith("Bearer ") ? credential : `Bearer ${credential}`, }, - signal: AbortSignal.timeout(context.probeTimeoutMs ?? 5_000), + signal: AbortSignal.timeout(context.probeTimeoutMs ?? DEFAULT_REMOTE_PROBE_TIMEOUT_MS), body: JSON.stringify(body), }); diff --git a/role-model-router/apps/runtime-host-bridge/test/remote-endpoint-admission-probe.test.ts b/role-model-router/apps/runtime-host-bridge/test/remote-endpoint-admission-probe.test.ts index 562c5f51..a705a51b 100644 --- a/role-model-router/apps/runtime-host-bridge/test/remote-endpoint-admission-probe.test.ts +++ b/role-model-router/apps/runtime-host-bridge/test/remote-endpoint-admission-probe.test.ts @@ -1,8 +1,15 @@ import { describe, expect, test } from "vitest"; -import { probeRemoteEndpointAdmission } from "../src/remote-health-probe.js"; +import { + DEFAULT_REMOTE_PROBE_TIMEOUT_MS, + probeRemoteEndpointAdmission, +} from "../src/remote-health-probe.js"; describe("remote endpoint admission probes", () => { + test("uses the 15-second bounded default for remote provider readiness", () => { + expect(DEFAULT_REMOTE_PROBE_TIMEOUT_MS).toBe(15_000); + }); + test("uses the configured effort in a bounded OpenAI-compatible readiness request", async () => { const calls: Array<{ url: string; init?: RequestInit }> = [];

{heading} @@ -235,9 +240,15 @@ export function StorageRetentionRouteView() { {row.measurementSource ?? row.measurement} + {row.lastCheckedAt ?? row.observedAt ?? "Not checked"} + {row.freshUntil ?? "Not time-bounded"} + {row.observationReason ?? "No diagnostic supplied"} + {row.physicalBytes === null ? "Unavailable" : formatBytes(row.physicalBytes)}