mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
test(managed): align recovery and transcript fixture contracts (#15899)
* test(managed): record which load path produced each post-crash entry
HostedProcessCrashIT fails on main with postFailureLoads.every(Boolean), but
the recorded sequence changes shape between runs (#15898: 9 vs 10 entries,
different true positions, one run with none), so the current message cannot
say which of the connector's load paths sent the offending request.
Record a compact origin discriminator per entry — cancellationTakeover,
passiveManagedRuntimeRecovery, driveRuntimeRecovery, mcpManagementOnly and
whether cancellationPromptId was present — and include it in both assertion
messages. The assertion itself is unchanged: 'no tool replay after a crash'
is the property it protects, and the next red run should name its own origin
instead of being guessed at.
* test(managed): name the load path behind each post-crash entry
Both review findings were correct: the origin string added in fad5d81a
still could not name the culprit it exists to name.
Two load shapes reach POST /session/:id/load carrying markers the origin
string never read, so they rendered byte-identical to a bare re-attach:
- a recovery-cleanup load sets only recoveryCleanupPromptId
(LoadHarnessSession.forRecoveryCleanup throws when passive/drive/
takeover/lifecycle/promptId are set at all), and
QwenHostedHarnessConnector.doRecheckRecoveryCleanup issues one;
- a lifecycle load sets only lifecycleAuthority
(QwenHostedHarnessConnector.lifecycleAttachment builds it with
passive=drive=takeover=false).
promptId could not rescue them: withCancellationPromptId throws unless
passiveManagedRuntimeRecovery && cancellationTakeover, so promptId=true
implies takeover=true and the marker is always false on exactly the
entries every(Boolean) rejects.
Recording also happened eleven lines before await fetch(target, ...), so
a load the daemon refused was indistinguishable from an admitted one.
This driver already expects that refusal for a plain attach
(assert.equal(loaded.error, 'hosted_turn_recovery_required'), HTTP 409),
and the connector then performs the takeover load that actually replaces
the runtime. Move the recording after the response and add status, so a
refused probe that changed nothing no longer prints as the forbidden
admitted plain re-attach.
Verified by mutation over the eight load shapes that can reach this
endpoint: the previous marker set collapsed four of them (refused bare
reattach, admitted bare reattach, recovery-cleanup, lifecycle) into one
indistinguishable string; each of the three added markers is
load-bearing, since dropping any one reintroduces a collision. The
assertion itself is unchanged.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvugxtx22a
* test(managed): distinguish cleanup loads and response outcomes
Co-authored-by: Qwen-Coder <[email protected]>
* test(managed): align recovery and transcript fixture contracts
Co-authored-by: Qwen-Coder <[email protected]>
---------
Co-authored-by: Qwen-Coder <[email protected]>
This commit is contained in:
1 parent
0976826090
commit
d896c1816a
2 files changed
+58
-10
No files matched your search
@@ -50,6 +50,10 @@ const report = {
|
||||
promptSubmissions: 0,
|
||||
continuationsAfterFailure: 0,
|
||||
postFailureLoads: [] as boolean[],
|
||||
postFailureCancellationLoads: 0,
|
||||
// Record non-resuming recovery modes separately from their wire origins.
|
||||
// Both modes target the original prompt; cleanup alone cannot prove takeover.
|
||||
postFailureLoadOrigins: [] as string[],
|
||||
lateCancelReplays: 0,
|
||||
originalCancelBody: '',
|
||||
};
|
||||
@@ -156,8 +160,44 @@ const proxy = createServer(async (req, res) => {
|
||||
`/session/${config.sessionId}/managed-runtime/continue`
|
||||
)
|
||||
report.continuationsAfterFailure++;
|
||||
if (injected && target.pathname.endsWith('/load'))
|
||||
report.postFailureLoads.push(fields.cancellationTakeover === true);
|
||||
let postFailureLoadIndex: number | undefined;
|
||||
if (injected && target.pathname.endsWith('/load')) {
|
||||
const cancellationOnly =
|
||||
fields.cancellationTakeover === true &&
|
||||
fields.passiveManagedRuntimeRecovery === true &&
|
||||
typeof fields.cancellationPromptId === 'string' &&
|
||||
fields.cancellationPromptId === report.promptId &&
|
||||
fields.recoveryCleanupPromptId === undefined;
|
||||
const cleanupOnly =
|
||||
typeof fields.recoveryCleanupPromptId === 'string' &&
|
||||
fields.recoveryCleanupPromptId === report.promptId &&
|
||||
fields.cancellationTakeover !== true &&
|
||||
fields.passiveManagedRuntimeRecovery !== true &&
|
||||
fields.cancellationPromptId === undefined;
|
||||
const nonDriving =
|
||||
fields.driveRuntimeRecovery !== true &&
|
||||
fields.mcpManagementOnly !== true &&
|
||||
fields.lifecycleAuthority === undefined;
|
||||
postFailureLoadIndex = report.postFailureLoads.length;
|
||||
report.postFailureLoads.push(
|
||||
nonDriving && (cancellationOnly || cleanupOnly),
|
||||
);
|
||||
if (nonDriving && cancellationOnly)
|
||||
report.postFailureCancellationLoads++;
|
||||
report.postFailureLoadOrigins.push(
|
||||
[
|
||||
`takeover=${fields.cancellationTakeover === true}`,
|
||||
`passive=${fields.passiveManagedRuntimeRecovery === true}`,
|
||||
`drive=${fields.driveRuntimeRecovery === true}`,
|
||||
`mcpOnly=${fields.mcpManagementOnly === true}`,
|
||||
`promptId=${typeof fields.cancellationPromptId === 'string'}`,
|
||||
`promptMatches=${fields.cancellationPromptId === report.promptId}`,
|
||||
`cleanup=${typeof fields.recoveryCleanupPromptId === 'string'}`,
|
||||
`cleanupMatches=${fields.recoveryCleanupPromptId === report.promptId}`,
|
||||
`lifecycle=${fields.lifecycleAuthority !== undefined}`,
|
||||
].join(' '),
|
||||
);
|
||||
}
|
||||
const headers = Object.fromEntries(
|
||||
Object.entries(req.headers)
|
||||
.filter(
|
||||
@@ -181,6 +221,10 @@ const proxy = createServer(async (req, res) => {
|
||||
headers,
|
||||
...(body.length ? { body } : {}),
|
||||
});
|
||||
if (postFailureLoadIndex !== undefined) {
|
||||
report.postFailureLoadOrigins[postFailureLoadIndex] +=
|
||||
` status=${upstream.status}`;
|
||||
}
|
||||
if (target.pathname.endsWith('/events') && upstream.body) {
|
||||
res.writeHead(upstream.status, relayedHeaders(upstream.headers));
|
||||
const reader = upstream.body.getReader();
|
||||
@@ -679,10 +723,17 @@ async function runRecoverySettlement() {
|
||||
assert.equal(report.modelCalls, 1);
|
||||
assert.equal(report.promptSubmissions, 1);
|
||||
assert.equal(report.continuationsAfterFailure, 0);
|
||||
assert(report.postFailureLoads.length > 0);
|
||||
assert(
|
||||
report.postFailureLoads.length > 0,
|
||||
`no post-failure loads were recorded at all: ${JSON.stringify(report.postFailureLoadOrigins)}`,
|
||||
);
|
||||
assert(
|
||||
report.postFailureCancellationLoads > 0,
|
||||
'replacement must use a targeted cancellation takeover load',
|
||||
);
|
||||
assert(
|
||||
report.postFailureLoads.every(Boolean),
|
||||
'replacement must use cancellation-only loads',
|
||||
`replacement must use targeted cancellation or cleanup-only loads: ${report.postFailureLoadOrigins.join(' | ')}`,
|
||||
);
|
||||
await unlink(`${proof}.read-gate`);
|
||||
await waitUntil(async () => {
|
||||
|
||||
@@ -304,15 +304,12 @@ try {
|
||||
.join(''),
|
||||
'FG6e tool finished once.',
|
||||
);
|
||||
// Progress frames materialize as transcript items, not transcript events.
|
||||
const itemFrames = new Set([
|
||||
'item.output_text.delta',
|
||||
'item.tool_call.updated',
|
||||
]);
|
||||
// Message pages carry selected Turn control events; full Session history
|
||||
// and progress frames are checked against the SQL ledger above.
|
||||
assert.deepEqual(
|
||||
transcript.events,
|
||||
webFrames
|
||||
.filter((frame) => !itemFrames.has(frame.event))
|
||||
.filter((frame) => frame.event === 'turn.completed')
|
||||
.map((frame) => frame.data),
|
||||
);
|
||||
assert.equal(
|
||||
|
||||
Reference in new issue
Block a user