fix: restore beta runtime release gates (#114064)

* test(qa): fix release runtime recovery gates

* test(qa): typecheck restart execution assertions
This commit is contained in:
Peter Steinberger
2026-07-26 03:58:41 -04:00
committed by GitHub
parent 5347285d6b
commit 8684e7ce03
8 changed files with 80 additions and 32 deletions
@@ -1281,6 +1281,18 @@ jobs:
pnpm openclaw qa suite "${runtime_pair_args[@]}"
if [[ "$RUNTIME_PAIR_LANE" == "core" ]]; then
# Restart recovery owns an OpenClaw Gateway lifecycle; it is release-critical
# but cannot be compared against the Codex app-server runtime.
pnpm openclaw qa suite \
--provider-mode mock-openai \
--concurrency "${QA_PARITY_CONCURRENCY}" \
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
--alt-model "openai/gpt-5.6-luna-alt" \
--scenario gateway-restart-inflight-run \
--output-dir ".artifacts/qa-e2e/openclaw-core-restart"
fi
- name: Generate runtime-pair lane report
id: generate_runtime_parity_report
if: always()
@@ -1739,7 +1739,6 @@ describe("qa cli runtime", () => {
"channel-chat-baseline",
"runtime-tool-bash",
"approval-turn-tool-followthrough",
"gateway-restart-inflight-run",
"runtime-first-hour-20-turn",
"runtime-tool-apply-patch",
"source-docs-discovery-report",
@@ -1793,10 +1792,6 @@ describe("qa cli runtime", () => {
stderrWrite,
"excluded incompatible non-flow scenario(s): codex-plugin-cold-install (script)",
);
expectWriteContains(
stderrWrite,
"excluded lane-incompatible scenario(s): gateway-restart-inflight-run",
);
});
it("rejects explicit runtime-pair scenarios with no compatible flow execution", async () => {
@@ -67,6 +67,8 @@ describe("qa scenario catalog channel contracts", () => {
);
const storeReads = [...flow.matchAll(/readRawQaSessionStore/gu)].map((match) => match.index);
expect(flow).toContain("readSessionTranscriptSummary(env, sessionKey)");
expect(flow).not.toContain("waitForAgentHistoryReply");
expect(completionWaits).toHaveLength(2);
expect(storeReads).toHaveLength(2);
expect(completionWaits.every((wait, index) => wait < (storeReads[index] ?? -1))).toBe(true);
@@ -19,7 +19,7 @@ describe("QA runtime-pair scenario catalog", () => {
.length,
]),
);
expect(laneCounts).toEqual({ core: 39, extended: 8, soak: 2 });
expect(laneCounts).toEqual({ core: 38, extended: 8, soak: 2 });
});
it("declares every release agentic scenario in the core lane", () => {
@@ -51,7 +51,12 @@ describe("QA runtime-pair scenario catalog", () => {
});
}
expect(readQaScenarioById("gateway-restart-inflight-run").runtimePairLane).toBe("core");
expect(readQaScenarioById("gateway-restart-inflight-run").runtimePairLane).toBeUndefined();
expect(readQaScenarioById("gateway-restart-inflight-run").execution).toMatchObject({
kind: "flow",
runtime: "openclaw",
timeoutMs: 420_000,
});
expect(readQaScenarioExecutionConfig("gateway-restart-inflight-run")).toMatchObject({
requiredProviderMode: "mock-openai",
});
+14 -2
View File
@@ -467,14 +467,26 @@ describe("qa scenario catalog", () => {
).toContain('"alsoAllow":["qa_restart_wait","qa_restart_unsafe_probe"]');
expect(gatewayRestartContract).toContain("plannedToolName === 'wait'");
expect(gatewayRestartContract).toContain("lastAssistantToolNames?.includes('wait')");
expect(gatewayRestartContract).toContain('"taskTracking":false');
expect(gatewayRestartContract).toContain("restartRecoveryDeliveryContext");
expect(gatewayRestartContract).toContain("sendInbound");
expect(gatewayRestartContract).not.toContain("startAgentRun");
expect(gatewayRestartContract).toContain('"restartGatewayWithConfigPatch"');
expect(gatewayRestartContract).toContain("interruptedMatches.length === 1");
expect(gatewayRestartContract).toContain("restartNotices.length === 0");
expect(gatewayRestartContract).toContain("dispatching restart-safe recovery");
expect(gatewayRestartContract).toContain("[OpenClaw heartbeat poll]");
expect(gatewayRestartContract).toContain("liveTurnTimeoutMs(env, 180000)");
expect(gatewayRestartContract).toContain("dmScope: 'per-channel-peer'");
expect(gatewayRestartContract).toContain("id: `dm:${conversationId}`");
expect(gatewayRestartContract).toContain("dmScope: env.cfg.session?.dmScope");
expect(readQaScenarioById("gateway-restart-inflight-run").gatewayConfigPatch).toMatchObject({
plugins: {
slots: { memory: "none" },
entries: {
acpx: { enabled: false },
"memory-core": { enabled: false },
},
},
});
const liveMultiRestart = readQaScenarioById("gateway-restart-multi-live");
const liveMultiRestartContract = JSON.stringify(liveMultiRestart.execution.flow);
expect(JSON.stringify(liveMultiRestart.gatewayConfigPatch)).toContain(
@@ -102,14 +102,12 @@ flow:
ref: prompt
timeoutMs:
expr: liveTurnTimeoutMs(env, 90000)
- call: waitForAgentHistoryReply
saveAs: outbound
- call: waitForCondition
saveAs: parentTranscript
args:
- ref: env
- ref: sessionKey
- lambda:
params: [text]
expr: "config.expectedReplyGroups.every((group) => group.some((needle) => normalizeLowercaseStringOrEmpty(text).includes(needle)))"
async: true
expr: "readSessionTranscriptSummary(env, sessionKey).then((summary) => config.expectedReplyGroups.every((group) => group.some((needle) => normalizeLowercaseStringOrEmpty(summary.finalText).includes(needle))) ? summary : undefined).catch(() => undefined)"
- expr: "30000"
- expr: "env.providerMode === 'mock-openai' ? 100 : 250"
- if:
@@ -173,7 +171,7 @@ flow:
expr: "`expected at least two sessions_spawn tool calls during subagent fanout scenario, saw ${fanoutSpawnRequests.length}`"
- set: details
value:
expr: "outbound.text"
expr: "parentTranscript.finalText"
- set: lastError
value: __done__
catchAs: attemptError
@@ -3,7 +3,6 @@ title: Gateway restart Code Mode wait recovery
scenario:
id: gateway-restart-inflight-run
surface: runtime
runtimePairLane: core
coverage:
primary:
- session-memory.recovery-restart-recovery
@@ -11,6 +10,16 @@ scenario:
- gateway.restart-and-stop-gateway-restart
- session-memory.recovery-delivery
gatewayConfigPatch:
# Channel restart recovery deliberately fails closed when reply hooks are active.
# This scenario isolates the replay-safe Code Mode contract from those hooks.
plugins:
slots:
memory: none
entries:
acpx:
enabled: false
memory-core:
enabled: false
tools:
alsoAllow:
- qa_restart_wait
@@ -33,6 +42,8 @@ scenario:
- src/gateway/server-restart-sentinel.ts
execution:
kind: flow
runtime: openclaw
timeoutMs: 420000
summary: Restart while replay-safe Code Mode wait is executing, then verify automatic reconstruction and delivery.
config:
requiredProviderMode: mock-openai
@@ -64,26 +75,26 @@ flow:
expr: "`restart-inflight-${randomUUID().slice(0, 8)}`"
- set: sessionKey
value:
expr: "buildAgentSessionKey({ agentId: 'qa', channel: 'qa-channel', accountId: transport.accountId, peer: { kind: 'direct', id: conversationId }, dmScope: 'per-channel-peer', identityLinks: env.cfg.session?.identityLinks })"
expr: "buildAgentSessionKey({ agentId: 'qa', channel: 'qa-channel', accountId: transport.accountId, peer: { kind: 'direct', id: `dm:${conversationId}` }, dmScope: env.cfg.session?.dmScope, identityLinks: env.cfg.session?.identityLinks })"
- set: restartPatch
value:
expr: "({ gateway: { controlUi: { allowedOrigins: [`http://127.0.0.1:${64000 + Math.floor(Math.random() * 999)}`] } }, tools: { codeMode: { enabled: false } } })"
- set: requestCursorBefore
value:
expr: "env.mock ? (await fetchJson(`${env.mock.baseUrl}/debug/request-cursor`)).cursor : 0"
- call: startAgentRun
saveAs: started
args:
- ref: env
- sessionKey:
ref: sessionKey
message:
expr: config.prompt
to:
expr: "`dm:${conversationId}`"
taskTracking: false
timeoutMs:
expr: liveTurnTimeoutMs(env, 180000)
# Real channel ingress owns the durable delivery context needed by recovery.
# Synthetic chat/agent RPC starts cannot prove recovered channel delivery.
- sendInbound:
accountId: default
conversation:
id:
ref: conversationId
kind: direct
senderId:
ref: conversationId
senderName: QA Restart Operator
text:
expr: config.prompt
- call: waitForCondition
saveAs: plannedWait
args:
@@ -100,6 +111,17 @@ flow:
expr: "readSessionTranscriptSummary(env, sessionKey).then((summary) => summary.lastMessageRole === 'assistant' && summary.lastAssistantToolNames?.includes('wait') ? summary : undefined)"
- expr: liveTurnTimeoutMs(env, 120000)
- 25
- call: readRawQaSessionStore
saveAs: preRestartStore
args:
- ref: env
- set: preRestartEntry
value:
expr: preRestartStore[sessionKey]
- assert:
expr: "preRestartEntry?.restartRecoveryDeliveryContext?.channel === 'qa-channel' && preRestartEntry.restartRecoveryDeliveryContext.to === `dm:${conversationId}`"
message:
expr: "`restart recovery delivery claim missing before restart: ${JSON.stringify(preRestartEntry ?? null)}`"
- call: restartGatewayWithConfigPatch
args:
- env:
@@ -166,4 +188,4 @@ flow:
expr: "recoveryLogs.includes('dispatching restart-safe recovery') && recoveryLogs.includes('restart-safe recovery tool policy retained')"
message:
expr: "`restart-safe host policy was not observed; logs=${recoveryLogs}`"
detailsExpr: "`runId=${started.runId} session=${sessionKey} plannedTool=${plannedWait.plannedToolName} persistedTail=${interruptedTranscript.lastMessageRole}/${interruptedTranscript.lastAssistantStopReason}/${interruptedTranscript.lastAssistantToolNames?.join(',')} recoveredTail=${postRestartTranscript.lastMessageRole}/${postRestartTranscript.lastAssistantStopReason}/${postRestartTranscript.lastAssistantContentTypes?.join(',')} recoveredMarkers=${interruptedMatches.length} resendNotices=${restartNotices.length} restartSafePolicy=true heartbeatPoll=false\\n${outbound.text}`"
detailsExpr: "`session=${sessionKey} plannedTool=${plannedWait.plannedToolName} persistedTail=${interruptedTranscript.lastMessageRole}/${interruptedTranscript.lastAssistantStopReason}/${interruptedTranscript.lastAssistantToolNames?.join(',')} recoveredTail=${postRestartTranscript.lastMessageRole}/${postRestartTranscript.lastAssistantStopReason}/${postRestartTranscript.lastAssistantContentTypes?.join(',')} recoveredMarkers=${interruptedMatches.length} resendNotices=${restartNotices.length} restartSafePolicy=true heartbeatPoll=false\\n${outbound.text}`"
@@ -2771,6 +2771,8 @@ describe("package artifact reuse", () => {
expect(runtimePairRun).toContain("--runtime-parity-tier standard,live-only");
expect(runtimePairRun).toContain("--runtime-parity-tier soak");
expect(runtimePairRun).toContain("Frozen candidate cannot select runtime-pair lane");
expect(runtimePairRun).toContain("--scenario gateway-restart-inflight-run");
expect(runtimePairRun).toContain('--output-dir ".artifacts/qa-e2e/openclaw-core-restart"');
expect(workflowStep(laneJob, "Upload runtime-pair lane artifacts").with?.name).toContain(
"${{ matrix.lane }}",
);