Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6528330
fix(runtime): bound private storage reads
try-works Aug 31, 2026
9cfd764
fix(runtime): resolve packaged taxonomy data
try-works Aug 31, 2026
c25904e
fix(runtime): retain Track B capture failure class
try-works Aug 31, 2026
c4f433c
fix(runtime): discover state-root configuration
try-works Aug 31, 2026
7f7f92f
fix(runtime): allow bounded durable route capture
try-works Aug 31, 2026
57acc71
style(runtime): format packaged capture fixes
try-works Aug 31, 2026
0748070
test(runtime): make config path assertion portable
try-works Aug 31, 2026
55dcf10
test(runtime): keep mixed path assertions portable
try-works Aug 31, 2026
def6889
Merge pull request #218 from try-works/codex/run95-storage-summary-fo…
try-works Aug 31, 2026
142dbfa
Merge pull request #219 from try-works/dev
try-works Aug 31, 2026
f217461
fix(release): require exact private stage head
try-works Aug 31, 2026
6632790
style(release): format provenance contract test
try-works Aug 31, 2026
f2b6c66
Merge pull request #220 from try-works/codex/run95-stage-private-head…
try-works Aug 31, 2026
9efbe04
Merge branch 'stage' into dev
try-works Aug 31, 2026
a1aa1fe
Merge pull request #221 from try-works/dev
try-works Aug 31, 2026
ad14fa4
fix(track-b): retry pending receipts on readback
try-works Aug 31, 2026
c83b6a5
Merge pull request #222 from try-works/codex/run95-stage-phase5-recei…
try-works Aug 31, 2026
2460154
Merge branch 'stage' into dev
try-works Aug 31, 2026
66781dc
Merge pull request #223 from try-works/dev
try-works Aug 31, 2026
60dd67c
Merge pull request #226 from try-works/main
try-works Aug 31, 2026
73517b5
Merge pull request #227 from try-works/dev
try-works Aug 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/build-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,9 @@ jobs:
exit 1
fi
git fetch --no-tags origin "+refs/heads/${REQUIRED_PRIVATE_BRANCH}:refs/remotes/origin/${REQUIRED_PRIVATE_BRANCH}"
if ! git merge-base --is-ancestor "$RELEASE_PRIVATE_SHA" "origin/$REQUIRED_PRIVATE_BRANCH"; then
echo "Private revision must be promoted through role-model-internal/$REQUIRED_PRIVATE_BRANCH before $ROLE_MODEL_BUILD_CHANNEL packaging."
required_private_head="$(git rev-parse "origin/$REQUIRED_PRIVATE_BRANCH")"
if [[ "$RELEASE_PRIVATE_SHA" != "$required_private_head" ]]; then
echo "Paired private revision must equal the current role-model-internal/$REQUIRED_PRIVATE_BRANCH head before $ROLE_MODEL_BUILD_CHANNEL packaging."
exit 1
fi

Expand Down
21 changes: 13 additions & 8 deletions role-model-router/apps/runtime-host-bridge/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1012,10 +1012,9 @@ export async function main(): Promise<void> {
// available at runtime.
const currentPostObservationOperations = (): ReturnType<typeof createTrackBOperations> | null =>
postObservationOperations;
const drainPostObservationOutbox = async (
runtime: Awaited<ReturnType<typeof createProductionExtensionRuntime>>,
) =>
postObservationOutbox.drain((observation) => {
const postObservationHandler =
(runtime: Awaited<ReturnType<typeof createProductionExtensionRuntime>>) =>
(observation: Parameters<typeof runTrackBPostObservation>[1]) => {
const processingInput = {
scope: options.scopeId,
channel: packagedProfile?.channel ?? "development",
Expand All @@ -1036,7 +1035,10 @@ export async function main(): Promise<void> {
(aggregate) => operations.recordContributionAggregate(aggregate),
)
: runTrackBPostObservation(runtime, observation, processingInput);
});
};
const drainPostObservationOutbox = async (
runtime: Awaited<ReturnType<typeof createProductionExtensionRuntime>>,
) => postObservationOutbox.drain(postObservationHandler(runtime));
const createBackend = async (
trackBOperationsEndpoint?: string,
trackBOperationsToken?: string,
Expand Down Expand Up @@ -1097,14 +1099,17 @@ export async function main(): Promise<void> {
readTrackBExtensionReadback: async (body) => {
const requestId = String(body.requestId ?? "").trim();
if (!requestId) throw new Error("Track B extension readback requestId is required");
const receipt = await postObservationOutbox.readReceipt(requestId);
const runtime = extensionRuntimeRef.current;
if (!runtime) throw new Error("Track B extension runtime is unavailable");
const receipt = await postObservationOutbox.drainUntilReceipt(
requestId,
postObservationHandler(runtime),
);
if (!receipt) throw new Error(`Track B observation receipt not found: ${requestId}`);
const result = receipt.result as Record<string, unknown>;
const closure = result.extensionClosure as TrackBExtensionClosure | undefined;
if (!closure)
throw new Error(`Track B observation has no extension closure: ${requestId}`);
const runtime = extensionRuntimeRef.current;
if (!runtime) throw new Error("Track B extension runtime is unavailable");
return verifyTrackBExtensionClosureAfterRestart(runtime, closure, {
channel: packagedProfile?.channel ?? "development",
scope: options.scopeId,
Expand Down
43 changes: 33 additions & 10 deletions role-model-router/apps/runtime-host-bridge/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24070,6 +24070,7 @@ export async function createRuntimeBridgeBackend(
| { readonly scopeId: string; readonly artifactId: string; readonly contentHash: string }
| undefined;
let routeCapture: Record<string, unknown> | undefined;
let routeCaptureDegradationReason: string | undefined;
try {
if (localGraphStore) {
const content = JSON.stringify(bundle);
Expand Down Expand Up @@ -24116,10 +24117,25 @@ export async function createRuntimeBridgeBackend(
};
}
}
} catch {
} catch (error) {
// Capture remains non-routing-critical before graph-primary cutover.
// Run 94 (SP2): without a graph artifact reference the SQLite row must still be
// bounded — persist the compact degradation stub instead of the full bundle.
// Keep the operator receipt actionable without copying a private-boundary
// message, which may include untrusted capture content.
const status =
error &&
typeof error === "object" &&
"status" in error &&
typeof error.status === "number" &&
Number.isInteger(error.status)
? error.status
: undefined;
routeCaptureDegradationReason =
status && status >= 100 && status <= 599
? `track-b-capture-boundary-http-${status}`
: "track-b-capture-boundary-unavailable";
console.error("Track B route capture failed", routeCaptureDegradationReason);
}
const graphEvidence = routeCapture
? {
Expand Down Expand Up @@ -24160,9 +24176,11 @@ export async function createRuntimeBridgeBackend(
schemaVersion: "role-model.degradation-receipt.v1",
degraded: true,
capability: "runtime-observation-persist",
reason: String(
error instanceof Error ? error.message : "runtime observation persist failed",
).slice(0, 256),
reason:
routeCaptureDegradationReason ??
String(
error instanceof Error ? error.message : "runtime observation persist failed",
).slice(0, 256),
routingContinues: true,
atMs: Date.now(),
};
Expand Down Expand Up @@ -29191,10 +29209,6 @@ export function resolveBridgeServerOptions(input: {
const packagedProfile = readPackagedRuntimeProfile(input.executablePath);
const profile = packagedProfile ?? resolveRuntimeChannelProfile("production");
const statePath = resolveBridgePathApi([input.localAppData], process.env.LOCALAPPDATA);
const runtimeStatePath = resolveBridgePathApi(
[input.runtimeStateRoot, input.localAppData],
process.env.LOCALAPPDATA,
);
const inferredRepoRoot = input.executablePath
? (() => {
const executableDir = repoPath.dirname(repoPath.resolve(input.executablePath));
Expand Down Expand Up @@ -29226,6 +29240,13 @@ export function resolveBridgeServerOptions(input: {
statePath.join(os.homedir(), ".local", "state");
const runtimeStateRoot =
input.runtimeStateRoot?.trim() || statePath.join(platformStateBase, profile.state_root_name);
const explicitUnifiedRuntimeConfigPath = input.unifiedRuntimeConfigPath?.trim();
const stateRootUnifiedRuntimeConfigPath = statePath.join(runtimeStateRoot, "runtime-config.yaml");
const legacyUnifiedRuntimeConfigPath = statePath.join(
runtimeStateRoot,
"state",
"runtime-config.yaml",
);

return {
host: input.host?.trim() || profile.host,
Expand All @@ -29240,7 +29261,9 @@ export function resolveBridgeServerOptions(input: {
preferRepoRootBuild: Boolean(input.repoRoot?.trim()) || Boolean(packagedProfile),
}),
unifiedRuntimeConfigPath:
input.unifiedRuntimeConfigPath?.trim() ||
runtimeStatePath.join(runtimeStateRoot, "state", "runtime-config.yaml"),
explicitUnifiedRuntimeConfigPath ||
(existsSync(stateRootUnifiedRuntimeConfigPath)
? stateRootUnifiedRuntimeConfigPath
: legacyUnifiedRuntimeConfigPath),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -661,21 +661,37 @@ const privateRetentionRequest = async (
token: string | undefined,
route: string,
init: { readonly method?: string; readonly body?: Record<string, unknown> } = {},
// Route captures may perform bounded durable CAS and SQLite commits after the
// provider response. Five seconds aborts healthy local captures on mature
// runtimes; retain a finite budget while allowing that proven completion path.
timeoutMs = 8_000,
): Promise<unknown | null> => {
if (!endpoint) return null;
if (!token || token.trim().length < 24) {
throw new Error(
"Track B private operations boundary requires a launcher-issued authentication token",
);
}
const response = await fetch(new URL(route, endpoint.endsWith("/") ? endpoint : `${endpoint}/`), {
method: init.method ?? "GET",
headers: {
...(init.body ? { "content-type": "application/json" } : {}),
authorization: `Bearer ${token}`,
},
...(init.body ? { body: JSON.stringify(init.body) } : {}),
});
let response: Response;
try {
response = await fetch(new URL(route, endpoint.endsWith("/") ? endpoint : `${endpoint}/`), {
method: init.method ?? "GET",
headers: {
...(init.body ? { "content-type": "application/json" } : {}),
authorization: `Bearer ${token}`,
},
...(init.body ? { body: JSON.stringify(init.body) } : {}),
signal: AbortSignal.timeout(timeoutMs),
});
} catch (error) {
if (error instanceof Error && error.name === "TimeoutError") {
throw new TrackBPrivateOperationError(
504,
`private Track B operation timed out after ${timeoutMs}ms`,
);
}
throw error;
}
const result = (await response.json().catch(() => ({}))) as { readonly error?: unknown };
if (!response.ok)
throw new TrackBPrivateOperationError(
Expand Down Expand Up @@ -1060,13 +1076,16 @@ export function createTrackBOperations({
runtimeChannel = "development",
operationsEndpoint = process.env.ROLE_MODEL_TRACK_B_OPERATIONS_URL?.trim(),
operationsToken = process.env.ROLE_MODEL_TRACK_B_OPERATIONS_TOKEN,
operationsTimeoutMs = 8_000,
extensionRuntime,
}: {
readonly statePath: string;
readonly catalog: readonly Record<string, unknown>[];
readonly runtimeChannel?: "development" | "stage" | "production";
readonly operationsEndpoint?: string;
readonly operationsToken?: string;
/** Bounds a private sidecar operation so a dashboard request cannot wait forever. */
readonly operationsTimeoutMs?: number;
readonly extensionRuntime?: {
listExtensions(): readonly unknown[] | Promise<readonly unknown[]>;
mutateExtension(input: Record<string, unknown>): unknown | Promise<unknown>;
Expand All @@ -1075,7 +1094,8 @@ export function createTrackBOperations({
const requestPrivate = (
route: string,
init?: { readonly method?: string; readonly body?: Record<string, unknown> },
) => privateRetentionRequest(operationsEndpoint, operationsToken, route, init);
) =>
privateRetentionRequest(operationsEndpoint, operationsToken, route, init, operationsTimeoutMs);
return {
async readGraphMigration(): Promise<unknown> {
const remote = await requestPrivate("graph-migration");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1792,6 +1792,15 @@ export function createTrackBPostObservationOutbox({
}
});
},
async drainUntilReceipt(
requestId: string,
handler: (observation: TrackBPostObservationWorkItem) => Promise<unknown>,
): Promise<TrackBPostObservationReceipt | null> {
const existing = await this.readReceipt(requestId);
if (existing) return existing;
await this.drain(handler);
return this.readReceipt(requestId);
},
async read(): Promise<{
readonly pendingCount: number;
readonly receiptCount: number;
Expand Down
31 changes: 29 additions & 2 deletions role-model-router/apps/runtime-host-bridge/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23616,10 +23616,37 @@ describe("runtime-host-bridge", () => {
runtimeStateRoot: "C:\\runtime-state",
scopeId: "standalone-runtime",
staticRoot: path.join(repoRoot, "role-model-router", "apps", "runtime-ui", "build", "client"),
unifiedRuntimeConfigPath: "C:\\runtime-state\\state\\runtime-config.yaml",
unifiedRuntimeConfigPath: path.join("C:\\runtime-state", "state", "runtime-config.yaml"),
});
});

test("prefers an existing state-root runtime config when a packaged launch omits the flag", async () => {
const runtimeStateRoot = await mkdtemp(
path.join(os.tmpdir(), "role-model-runtime-config-default-"),
);
try {
await writeFile(
path.join(runtimeStateRoot, "runtime-config.yaml"),
'version: "1.0"\n',
"utf8",
);
const result = (
bridge as {
resolveBridgeServerOptions: (value: {
repoRoot?: string;
runtimeStateRoot?: string;
}) => { unifiedRuntimeConfigPath: string };
}
).resolveBridgeServerOptions({ repoRoot, runtimeStateRoot });

expect(result.unifiedRuntimeConfigPath).toBe(
path.join(runtimeStateRoot, "runtime-config.yaml"),
);
} finally {
await rm(runtimeStateRoot, { recursive: true, force: true });
}
});

test("keeps repoRoot-derived static paths stable when runtimeStateRoot uses a different path dialect", () => {
const result = (
bridge as {
Expand Down Expand Up @@ -23654,7 +23681,7 @@ describe("runtime-host-bridge", () => {
scopeId: "standalone-runtime",
staticRoot:
"/home/runner/work/role-model/role-model/role-model-router/apps/runtime-ui/build/client",
unifiedRuntimeConfigPath: "C:\\runtime-state\\state\\runtime-config.yaml",
unifiedRuntimeConfigPath: path.join("C:\\runtime-state", "state", "runtime-config.yaml"),
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,39 @@ test("GREEN: post-observation outbox is a normalized SQLite authority with bound
afterDrain.close();
});

test("GREEN: a readback-driven retry drains a transiently failed pending observation without another routed request", async () => {
const root = await import("node:fs/promises").then(({ mkdtemp }) =>
mkdtemp(path.join(os.tmpdir(), "run95-readback-retry-")),
);
roots.push(root);
const outbox = createTrackBPostObservationOutbox({
filePath: path.join(root, "post-observation-outbox.sqlite"),
maxItems: 8,
});
await outbox.enqueue(identity("retry-without-next-route"));
await expect(
outbox.drain(async () => {
throw new Error("temporary private operation timeout");
}),
).rejects.toThrow(/temporary private operation timeout/);
expect(await outbox.read()).toMatchObject({ pendingCount: 1, receiptCount: 0 });

const recovered = await (
outbox as unknown as {
drainUntilReceipt(
requestId: string,
handler: (item: Record<string, unknown>) => Promise<unknown>,
): Promise<{ requestId: string } | null>;
}
).drainUntilReceipt("retry-without-next-route", async (item) => ({
status: "recovered",
extensionClosure: { requestId: item.requestId },
}));

expect(recovered).toMatchObject({ requestId: "retry-without-next-route" });
expect(await outbox.read()).toMatchObject({ pendingCount: 0, receiptCount: 1 });
});

test("GREEN: imports N-1 JSON once, classifies every legacy row, and quarantines malformed rows", async () => {
const root = await import("node:fs/promises").then(({ mkdtemp }) =>
mkdtemp(path.join(os.tmpdir(), "run94-sp5-legacy-")),
Expand Down
Loading
Loading