test(gateway): bind restored requester fixture provenance (#155621)

Bind the restored-requester E2E seed to canonical physical requester and controller store identities, then verify the persisted SQLite round trip. Preserve the original four cases, deadlines and assertions. Capture bounded, redacted first-wait diagnostics and settle all Gateway/model cleanup owners.

Test-fixture correction only; no production provenance guard, schema or dependency changes.

Validation: exact-head CI 35719796768 passed. Reviewed production-plus-fixture composite de1bb285 passed all four real built-Gateway loopback E2E cases in native run 35722722579, with fulfilled fixture cleanup and verified host closure. This is not authenticated external-provider or release acceptance.

Co-authored-by: RomneyDa <6581799+RomneyDa@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
This commit is contained in:
RoboClaw
2026-09-22 05:14:43 -07:00
committed by GitHub
co-authored by RomneyDa steipete vincentkoc jalehman
parent 86fc1df067
commit 4383499752
@@ -8,9 +8,11 @@ import {
saveSubagentRegistryToSqlite,
} from "../src/agents/subagents/registry/subagent-registry.store.sqlite.js";
import type { SubagentRunRecord } from "../src/agents/subagents/registry/subagent-registry.types.js";
import { resolvePhysicalSessionStorePath } from "../src/config/sessions/session-store-path.js";
import type { OpenClawConfig } from "../src/config/types.openclaw.js";
import type { SessionsListResult } from "../src/gateway/session-utils.types.js";
import { connectGatewayClient, disconnectGatewayClient } from "../src/gateway/test-helpers.e2e.js";
import { redactSensitiveText } from "../src/logging/redact.js";
import type { Deferred } from "../src/shared/deferred.js";
import { closeOpenClawStateDatabaseForTest } from "../src/state/openclaw-state-db.js";
import { writeOpenAiResponsesSse } from "./helpers/openai-responses-sse.js";
@@ -45,8 +47,29 @@ afterEach(async () => {
for (const server of modelServers) {
server.releaseAll();
}
await Promise.allSettled(instances.splice(0).map((instance) => instance.cleanup()));
await Promise.allSettled(modelServers.splice(0).map((server) => server.close()));
const ownedInstances = instances.splice(0);
const gatewayCleanup = await Promise.allSettled(
ownedInstances.map((instance) => instance.cleanup()),
);
const modelCleanup = await Promise.allSettled(
modelServers.splice(0).map((server) => server.close()),
);
console.info(
"[restored-requester] cleanup",
JSON.stringify({
gateways: ownedInstances.map((instance, index) => ({
name: instance.name,
status: gatewayCleanup[index]?.status ?? "unavailable",
})),
modelServers: modelCleanup.map((result) => result.status),
}),
);
const cleanupErrors = [...gatewayCleanup, ...modelCleanup].flatMap((result) =>
result.status === "rejected" ? [result.reason] : [],
);
if (cleanupErrors.length > 0) {
throw new AggregateError(cleanupErrors, "Restored requester fixture cleanup failed");
}
});
describe("Gateway restored requester settlement", () => {
@@ -66,7 +89,7 @@ describe("Gateway restored requester settlement", () => {
env: { OPENCLAW_SKIP_PROVIDERS: undefined, OPENCLAW_TEST_MINIMAL_GATEWAY: undefined },
});
instances.push(instance);
await seedRestoredRequesters(instance, 1);
await seedRestoredRequesters(instance, 1, cfg);
await instance.startGateway();
const client = await connectGatewayClient({
url: instance.url,
@@ -78,7 +101,11 @@ describe("Gateway restored requester settlement", () => {
await client.request<SessionsListResult>("sessions.list", { agentId: "main" })
).sessions.find((row) => row.key === sessionKey);
try {
await vi.waitFor(() => expect(modelServer.requestCount()).toBe(1), { timeout: 30_000 });
await vi
.waitFor(() => expect(modelServer.requestCount()).toBe(1), { timeout: 30_000 })
.catch((error: unknown) => {
throw gatewayDiagnosticError(instance, error);
});
const initial = await session();
expect(initial).toMatchObject({ status: "running", hasActiveRun: true });
// This elapsed wait is the regression itself: the real model socket must
@@ -159,9 +186,10 @@ describe("Gateway restored requester settlement", () => {
async () => {
const modelServer = await startHeldModelServer();
modelServers.push(modelServer);
const cfg = createTestConfig(modelServer.url);
const instance = await createOpenClawTestInstance({
name: "gateway-restored-requester-settle",
config: createTestConfig(modelServer.url),
config: cfg,
env: {
OPENCLAW_SKIP_PROVIDERS: undefined,
OPENCLAW_TEST_MINIMAL_GATEWAY: undefined,
@@ -169,13 +197,17 @@ describe("Gateway restored requester settlement", () => {
});
instances.push(instance);
await seedRestoredRequesters(instance, 3);
await seedRestoredRequesters(instance, 3, cfg);
await instance.startGateway();
await vi.waitFor(
() => expect(modelServer.countRequestsContaining(RESTORED_WAKE_MARKER)).toBe(2),
{ interval: 20, timeout: 30_000 },
);
await vi
.waitFor(() => expect(modelServer.countRequestsContaining(RESTORED_WAKE_MARKER)).toBe(2), {
interval: 20,
timeout: 30_000,
})
.catch((error: unknown) => {
throw gatewayDiagnosticError(instance, error);
});
expect(modelServer.active(), instance.logs()).toBe(2);
expect(modelServer.peakRestored(), instance.logs()).toBe(2);
@@ -198,16 +230,51 @@ describe("Gateway restored requester settlement", () => {
);
});
async function seedRestoredRequesters(instance: OpenClawTestInstance, count: number) {
function gatewayDiagnosticError(instance: OpenClawTestInstance, cause: unknown): Error {
const readiness = instance.readiness.map(
({ outcome, elapsedMs, attempts, lastProbe, child }) => ({
outcome,
elapsedMs,
attempts,
lastProbe,
child,
}),
);
// Snapshot the existing bounded child buffers before normal fixture cleanup removes state.
let diagnostic = redactSensitiveText(
`${instance.name}\n${JSON.stringify({ readiness })}\n${instance.logs()}`,
{ mode: "tools" },
);
for (const token of [instance.gatewayToken, instance.hookToken]) {
if (token) {
diagnostic = diagnostic.replaceAll(token, "[fixture token]");
}
}
return new Error(diagnostic, { cause });
}
async function seedRestoredRequesters(
instance: OpenClawTestInstance,
count: number,
cfg: OpenClawConfig,
) {
instance.state.applyEnv();
try {
const endedAt = Date.now();
const restoredRuns = Array.from({ length: count }, (_, index): SubagentRunRecord => {
const runId = `run-gateway-restored-settle-${index}`;
const requesterSessionKey = `agent:main:gateway-restored-requester-${index}`;
const requesterStorePath = resolvePhysicalSessionStorePath(
{ sessionKey: requesterSessionKey, agentId: "main", env: instance.env },
cfg,
);
return {
runId,
childSessionKey: `agent:main:subagent:gateway-restored-settle-${index}`,
requesterSessionKey: `agent:main:gateway-restored-requester-${index}`,
requesterSessionKey,
requesterAgentId: "main",
requesterStorePath,
controllerStorePath: requesterStorePath,
requesterDisplayKey: `gateway-restored-requester-${index}`,
task: "resume a durable requester wake through the Gateway CLI",
cleanup: "keep",
@@ -246,6 +313,19 @@ async function seedRestoredRequesters(instance: OpenClawTestInstance, count: num
defaultSessionId: `gateway-restored-requester-${index}`,
});
}
const persistedRuns = loadSubagentRegistryFromSqlite();
for (const entry of restoredRuns) {
// Direct SQLite fixtures must preserve the physical identity stamped by normal launch.
const storePath = resolvePhysicalSessionStorePath(
{ sessionKey: entry.requesterSessionKey, agentId: "main", env: instance.env },
cfg,
);
expect(persistedRuns.get(entry.runId)).toMatchObject({
requesterAgentId: "main",
requesterStorePath: storePath,
controllerStorePath: storePath,
});
}
} finally {
// Keep this one state lease through the Gateway run and retained-result reads;
// instance.cleanup owns restoration after every process has stopped.