test(release): harden QA and live validation (#108589)

This commit is contained in:
Peter Steinberger
2026-07-15 20:45:36 -07:00
committed by GitHub
parent 957cc81175
commit de3f7925c4
6 changed files with 65 additions and 39 deletions
+15 -2
View File
@@ -623,7 +623,7 @@ describe("qa scenario catalog", () => {
const scenario = readQaScenarioById("luna-thinking-visibility-switch");
const config = readQaScenarioExecutionConfig("luna-thinking-visibility-switch") as
| {
requiredProvider?: string;
liveProvider?: string;
requiredModel?: string;
offDirective?: string;
maxDirective?: string;
@@ -632,7 +632,7 @@ describe("qa scenario catalog", () => {
| undefined;
expect(scenario.sourcePath).toBe("qa/scenarios/models/luna-thinking-visibility-switch.yaml");
expect(config?.requiredProvider).toBe("openai");
expect(config?.liveProvider).toBe("openai");
expect(config?.requiredModel).toBe("gpt-5.6-luna");
expect(config?.offDirective).toBe("/think off");
expect(config?.maxDirective).toBe("/think medium");
@@ -939,6 +939,19 @@ describe("qa scenario catalog", () => {
expect(subagentFanout.execution.suiteIsolation).toBe("isolated");
});
it("settles subagent completions before reading the SQLite session store", () => {
const scenario = requireFlowScenario(readQaScenarioById("subagent-fanout-synthesis"));
const flow = JSON.stringify(scenario.execution.flow);
const completionWaits = [...flow.matchAll(/expectedChildCompletionMarkers/gu)].map(
(match) => match.index,
);
const storeReads = [...flow.matchAll(/readRawQaSessionStore/gu)].map((match) => match.index);
expect(completionWaits).toHaveLength(2);
expect(storeReads).toHaveLength(2);
expect(completionWaits.every((wait, index) => wait < (storeReads[index] ?? -1))).toBe(true);
});
it("adds a dreaming shadow trial report scenario", () => {
const scenario = readQaScenarioById("dreaming-shadow-trial-report");
const config = readQaScenarioExecutionConfig("dreaming-shadow-trial-report") as
@@ -60,7 +60,6 @@ const hasDiscoveryLabels = vi.hoisted(() => vi.fn());
const reportsDiscoveryScopeLeak = vi.hoisted(() => vi.fn());
const reportsMissingDiscoveryFiles = vi.hoisted(() => vi.fn());
const hasModelSwitchContinuitySignal = vi.hoisted(() => vi.fn());
const qaChannelPlugin = vi.hoisted(() => ({ id: "qa-channel" }));
const scanGatewayLogSentinels = vi.hoisted(() => vi.fn());
const assertNoGatewayLogSentinels = vi.hoisted(() => vi.fn());
@@ -159,10 +158,6 @@ vi.mock("./model-switch-eval.js", () => ({
hasModelSwitchContinuitySignal,
}));
vi.mock("./runtime-api.js", () => ({
qaChannelPlugin,
}));
vi.mock("./gateway-log-sentinel.js", () => ({
scanGatewayLogSentinels,
assertNoGatewayLogSentinels,
@@ -296,7 +291,6 @@ describe("qa suite runtime flow", () => {
envArg: typeof env,
configArg: Record<string, unknown>,
) => Promise<unknown>;
qaChannelPlugin: typeof qaChannelPlugin;
webOpenPage: (params: { url: string }) => Promise<unknown>;
};
constants: {
@@ -330,7 +324,6 @@ describe("qa suite runtime flow", () => {
ensureImageGenerationConfigured,
},
);
expect(call.deps.qaChannelPlugin).toBe(qaChannelPlugin);
expect(call.constants).toEqual({
imageUnderstandingPngBase64: "small",
imageUnderstandingLargePngBase64: "large",
@@ -111,6 +111,23 @@ flow:
expr: "config.expectedReplyGroups.every((group) => group.some((needle) => normalizeLowercaseStringOrEmpty(text).includes(needle)))"
- expr: "30000"
- expr: "env.providerMode === 'mock-openai' ? 100 : 250"
- if:
# The parent reply can arrive before child transcript writes finish.
# Wait before opening the store so its integrity check sees settled FTS state.
expr: "Boolean(env.mock) && env.gateway.runtimeEnv.OPENCLAW_QA_FORCE_RUNTIME !== 'codex'"
then:
- forEach:
items:
expr: "config.expectedChildCompletionMarkers"
item: childCompletionMarker
actions:
- call: waitForOutboundMessage
args:
- ref: state
- lambda:
params: [candidate]
expr: "String(candidate.text ?? '').trim() === childCompletionMarker"
- 30000
- if:
expr: "Boolean(env.mock)"
then:
@@ -163,6 +180,21 @@ flow:
- if:
expr: "/timed out after/i.test(formatErrorMessage(attemptError))"
then:
- if:
expr: "Boolean(env.mock) && env.gateway.runtimeEnv.OPENCLAW_QA_FORCE_RUNTIME !== 'codex'"
then:
- forEach:
items:
expr: "config.expectedChildCompletionMarkers"
item: childCompletionMarker
actions:
- call: waitForOutboundMessage
args:
- ref: state
- lambda:
params: [candidate]
expr: "String(candidate.text ?? '').trim() === childCompletionMarker"
- 30000
- call: readRawQaSessionStore
saveAs: timeoutStore
args:
@@ -240,23 +272,4 @@ flow:
expr: "lastError === '__done__'"
message:
expr: "lastError instanceof Error ? formatErrorMessage(lastError) : String(lastError ?? 'fanout retry exhausted')"
- if:
# Codex completes child sessions through its app-server path but
# does not relay the child marker back onto the parent QA channel.
# The shared assertions above already prove both child tool calls
# and child session rows; keep this transport-only proof OpenClaw-specific.
expr: "Boolean(env.mock) && env.gateway.runtimeEnv.OPENCLAW_QA_FORCE_RUNTIME !== 'codex'"
then:
- forEach:
items:
expr: "config.expectedChildCompletionMarkers"
item: childCompletionMarker
actions:
- call: waitForOutboundMessage
args:
- ref: state
- lambda:
params: [candidate]
expr: "String(candidate.text ?? '').trim() === childCompletionMarker"
- 30000
detailsExpr: "details"
@@ -29,7 +29,7 @@ scenario:
kind: flow
summary: Toggle reasoning display and GPT-5.6 Luna thinking between off/none and medium, then verify visible reasoning only on the medium turn.
config:
requiredProvider: openai
liveProvider: openai
requiredModel: gpt-5.6-luna
offDirective: /think off
maxDirective: /think medium
@@ -57,7 +57,7 @@ flow:
value:
expr: splitModelRef(env.primaryModel)
- assert:
expr: "env.providerMode !== 'live-frontier' || (selected?.provider === config.requiredProvider && selected?.model === config.requiredModel)"
expr: "env.providerMode !== 'live-frontier' || (selected?.provider === config.liveProvider && selected?.model === config.requiredModel)"
message:
expr: "`expected live GPT-5.6 Luna, got ${env.primaryModel}`"
- sendInbound:
+15 -8
View File
@@ -35,14 +35,21 @@ async function expectModelReturnsAssistantText(
contextWindow: modelId === "glm-5.2" ? 1_000_000 : 202_800,
maxTokens: modelId === "glm-5.2" ? 131_072 : 131_100,
};
const res = await completeSimple(
model,
{
messages: createSingleUserPromptMessage(),
},
{ apiKey: ZAI_KEY, maxTokens: 64 },
);
const text = extractNonEmptyAssistantText(res.content);
const runProbe = (maxTokens: number) =>
completeSimple(
model,
{
messages: createSingleUserPromptMessage(),
},
{ apiKey: ZAI_KEY, maxTokens },
);
const res = await runProbe(64);
let text = extractNonEmptyAssistantText(res.content);
// GLM can spend a tiny cap entirely on hidden reasoning. Retry only a
// provider-declared truncation; other empty responses remain failures.
if (text.length === 0 && res.stopReason === "length") {
text = extractNonEmptyAssistantText((await runProbe(256)).content);
}
expect(text.length).toBeGreaterThan(0);
}
+1 -1
View File
@@ -33,7 +33,7 @@ const publicSdkContractNarrowingTiers = [
{
name: "bundled-only public export",
codeSuffix: "-public-demotion",
count: 158,
count: 157,
replacement:
"subpath becomes internal (private-local-only); no external successor — no known external consumers",
releaseNote: /public export.*module stays available for bundled plugins.*private-local-only/u,