mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-10-31 19:52:43 +00:00
* docs(managed-agent): repair the R2 review's doc findings on #12692
Six doc findings from the post-merge R2 review round on #12692,
re-verified against current main (2c591ecc08) before patching, plus
two adjacent corrections in the same files:
- The README's dual-path setup assigned 127.0.0.1:4170 to both the
private Hosted Harness and the ordinary daemon; the second process
to bind would EADDRINUSE. The private harness now moves to a
distinct port (4171) with the matching harness base URL, keeping
the ordinary daemon on the proxy's default 4170.
- Preview-only design links in the storage-event design pair are
downgraded to plain-text references naming the integration-preview
documents, in both languages — clicking them 404'd, and the
delegated normative requirements were unverifiable from this repo.
- The English document's Related-designs entries pointed at .zh-CN
twins despite English versions existing; they now link the
in-tree English files, and the closing paragraph names the two
integration-snapshot documents as out-of-tree references.
- The rollout-ordering stage labels (C, D, E, G, W0, W1, O4) gain a
one-line glossary at first use naming proposal #12380 as their
source, in both languages.
- The zh-CN verification record claims one daemon-side panel where
the English says all panels; both instances are singularized to
match the English original.
- The durable-store pair's §4.2 claim that attachment coalescing
compares the full descriptor identity and fails closed with
managed_session_store_conflict is narrowed to the shipped behavior
(coalescing on (tenantId, sessionId) only), in both languages;
the fence stays named as integration-slice target design.
- The review-corrections bullet claiming executions:prepare and
executions/{id}:start 501 is updated: both are live routes again,
and only executions/{id}:resolve still 501s, in both languages.
* docs(managed-agent): close the launch/contract gaps from the #13343 review
Six findings on the doc repair, each verified against the cited code
before patching:
- The fenced `qwen serve --profile hosted-harness` block could not
start: the profile requires a bearer token, --no-web and a sha256
capability digest, none present in the block or section. The launch
now reuses the Spring credential pair (QWEN_SERVER_TOKEN /
QWEN_HOSTED_HARNESS_CAPABILITY_DIGEST spellings) plus --no-web --
which has no environment fallback.
- The 4171 base-url export sat after the foreground
`mvn spring-boot:run` step, while the value is read once at JVM
startup. The section now states the restart requirement and warns
that --port is a request: on EADDRINUSE the serve walker puts a
displaced ordinary daemon exactly on 4171.
- The bare :101 command reference points at the credentialed launch.
- README 209-211 asserted the fail-closed
managed_session_store_conflict fence as shipped while the design
pair this commit edits narrows it to target design. The README
sentence now matches: coalescing on (tenantId, sessionId) -- so
another tenant cannot reuse the in-memory Session -- with neither
endpoint/workspace/generation comparison nor the fence as shipped;
same correction applied to the previously self-contradictory
design-doc sentence in both languages (tenant is part of the
coalescing key, not on the uncompared list).
- Review-corrections gate item 1 still claimed the profile rejects
startup and that acquire/control/release 501; only
executions/{id}:resolve still 501s. Reworded in both languages,
keeping the genuinely open parts (real tool turn, durable-store
adapter/worker bundle, crash recovery).
Two pins in scripts/tests/managed-agent-server-e2e.test.js hold the
repaired claims: every fenced hosted-harness launch block carries
--no-web plus token and digest sources (reds if any are stripped),
and the 4171 export pairs with a JVM-startup restart note (reds if
the clause is removed). Suite 9/9 green; Prettier clean.
* docs(managed-agent): name the attach-time generation fence and pin docs to the profile validator
The R2 review found the narrowed attachment contract still overstated the
gap: a presented Harness writer generation IS compared on every attach
(contract middleware boot-id 409 and the store-descriptor writerId 409,
both before coalescing), so the README and the durable-store design pair
now say the fence applies to the generation while endpoint/workspace
remain uncompared and managed_session_store_conflict stays target design.
The README pins move from hand-copied substrings to the real
validateHostedHarnessProfile, so a fenced launch block that the profile
would reject (missing token/digest/--no-web, non-loopback hostname, an
unseen fence spelling) reds the suite; the dual-path slice test gains a
fail-closed closing anchor and a wrap-tolerant caveat match, and a new
pin asserts the attachment paragraph names the generation rejection.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(managed-agent): pin the hosted-harness README launch literally
The grammar-based pin derived argv/env from the fenced block and mirrored
serve.ts option mapping by hand; every shell spelling or flag it did not
model failed open, and each review round found new corners. Match the
launch block exactly instead, cross-check that every $VAR the block
references is assigned in a fenced README block, and take the CLI-side
credential env names from the production constants so a rename there
reddens the pin instead of stranding the README spelling.
* docs(managed-agent): correct the attach contract's tenant-isolation claim
The Harness keys its in-memory Session by sessionId alone, so the README
and the durable-store design doc cannot promise (tenantId, sessionId)
coalescing: name the connector-side cache as the per-tenant layer and
the writer generation as the only attach-time fence, in both languages.
Also fold the three launch-pin suggestions into the e2e test: scope the
$VAR provenance scan to the Prerequisites section, and parse the
validator fixture and the Spring base-URL port from the pinned launch
block so the copies cannot drift apart.
* docs(managed-agent): supersede the foundation boundary, pin fence polarity
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(managed-agent): wire the Session Store into the by-hand dual-path recipe
* docs(managed-agent): order the Session Store exports before the Spring restart
R6-1: the by-hand dual-path recipe exported the three Session Store values
after its only Spring restart cue, so an in-order reader restarted onto a
store-disabled JVM and every attach failed 400 invalid_managed_session_store.
All four exports now precede the single restart, whose read-once caveat is
re-scoped to cover them, and the Spring-side values are no longer called
"the launch".
R9-1: gate 1 of the review-corrections doc still named real tool turns, the
copied E2E script's durable-store adapter and worker bundle, and crash
recovery as unproven although the hosted-harness-mysql CI job gates all
three (HostedWorkspaceToolTurnIT, HostedProcessCrashIT, three owner-failover
modes). Narrowed to the gate-4 recovery signalling, in-flight recovery,
complete Workspace lifecycle and distributed provisioning, in both
languages.
Pins: the dual-path slice now asserts the store exports precede the restart
cue, and a doc-oracle case reddens when either language re-lists CI-gated
Hosted capabilities as unproven while the tool-turn IT and the failover
lane exist; both went red on the pre-fix text before the docs changed.
---------
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
463 lines
20 KiB
JavaScript
463 lines
20 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2026 Qwen Team
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { existsSync, readFileSync } from 'node:fs';
|
|
import {
|
|
createSourceFile,
|
|
isFunctionDeclaration,
|
|
isVariableStatement,
|
|
ScriptTarget,
|
|
transpileModule,
|
|
} from 'typescript';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { QWEN_SERVER_TOKEN_ENV } from '../../packages/cli/src/serve/channel-worker-env.js';
|
|
import { HOSTED_HARNESS_CAPABILITY_DIGEST_ENV } from '../../packages/cli/src/serve/hosted-harness-contract.js';
|
|
import { validateHostedHarnessProfile } from '../../packages/cli/src/serve/hosted-harness-profile.js';
|
|
|
|
const read = (file) =>
|
|
readFileSync(new URL(`../../${file}`, import.meta.url), 'utf8');
|
|
|
|
// Repo-root script mentions, bare or ./-prefixed. A package-relative tail such
|
|
// as packages/foo/scripts/bar.js is not a root mention, so the lookbehind
|
|
// still rejects a `scripts/` preceded by a path character.
|
|
const namedScripts = (text) => [
|
|
...new Set(
|
|
[...text.matchAll(/(?<![\w./-])(?:\.\/)?scripts\/[\w./-]*[\w-]/g)].map(
|
|
(match) => match[0].replace(/^\.\//, ''),
|
|
),
|
|
),
|
|
];
|
|
|
|
// Fenced shell blocks of a markdown text, bodies only; language-less fences
|
|
// are invisible to this scan.
|
|
const fencedShellBlocks = (text) =>
|
|
[
|
|
...text.matchAll(/```[ \t]*(?:bash|sh|shell|console|zsh)\n([\s\S]*?)```/g),
|
|
].map((match) => match[1]);
|
|
|
|
describe('managed-agent-server e2e runner', () => {
|
|
it('keeps service and proxy ports distinct when an ephemeral port repeats', async () => {
|
|
const source = createSourceFile(
|
|
'runner.ts',
|
|
read('scripts/run-managed-agent-server-e2e.ts'),
|
|
ScriptTarget.Latest,
|
|
true,
|
|
);
|
|
const allocation = source.statements
|
|
.filter(
|
|
(node) =>
|
|
(isFunctionDeclaration(node) &&
|
|
['freePort', 'startHeldExecutionStartProxy'].includes(
|
|
node.name?.text,
|
|
)) ||
|
|
(isVariableStatement(node) &&
|
|
node.declarationList.declarations.some(
|
|
(declaration) =>
|
|
declaration.name.getText(source) === 'allocatedPorts',
|
|
)),
|
|
)
|
|
.map((node) => node.getText(source))
|
|
.join('\n');
|
|
const { outputText } = transpileModule(allocation, {
|
|
compilerOptions: { target: ScriptTarget.ES2022 },
|
|
});
|
|
const sequence = [
|
|
33061, 33231, 36301, 36302, 36301, 36303, 38943, 36417, 36417, 36418,
|
|
36417, 36418, 36419,
|
|
];
|
|
const createServer = () => ({
|
|
once() {},
|
|
off() {},
|
|
closeAllConnections() {},
|
|
listen(port, _host, ready) {
|
|
this.port = port || sequence.shift();
|
|
expect(this.port).toBeDefined();
|
|
ready();
|
|
},
|
|
address() {
|
|
return { port: this.port };
|
|
},
|
|
close(done) {
|
|
done?.();
|
|
},
|
|
});
|
|
const { freePort, startHeldExecutionStartProxy } = new Function(
|
|
'createServer',
|
|
`${outputText}\nreturn { freePort, startHeldExecutionStartProxy };`,
|
|
)(createServer);
|
|
const ports = [];
|
|
for (const count of [4, 3]) {
|
|
for (let index = 0; index < count; index++) ports.push(await freePort());
|
|
const proxy = await startHeldExecutionStartProxy('http://127.0.0.1:1');
|
|
ports.push(Number(new URL(proxy.baseUrl).port));
|
|
await proxy.close();
|
|
}
|
|
expect(new Set(ports).size).toBe(9);
|
|
});
|
|
|
|
// #12941: the Stage A acceptance criterion names a 15-second Runtime delay,
|
|
// but the ordering assertion was gated at 20 s, so a --runtime-delay-ms 15000
|
|
// run silently skipped it. Pin the threshold to the criterion's delay and
|
|
// the README's statement of the arming delay to the threshold.
|
|
it('arms the model-before-Runtime assertion at the criterion delay', () => {
|
|
const source = read('scripts/run-managed-agent-server-e2e.ts');
|
|
expect(source).toContain('modelBeforeRuntimeAssertionDelayMs = 15_000');
|
|
expect(source).toContain(
|
|
'runtimeDelayMs >= modelBeforeRuntimeAssertionDelayMs',
|
|
);
|
|
const delaySeconds =
|
|
Number(
|
|
source
|
|
.match(/modelBeforeRuntimeAssertionDelayMs = (\d[\d_]*)/)[1]
|
|
.replace(/_/g, ''),
|
|
) / 1000;
|
|
expect(read('packages/sdk-java/managed-agent-server/README.md')).toContain(
|
|
`the ${delaySeconds} seconds the acceptance criterion`,
|
|
);
|
|
});
|
|
|
|
// When the assertion fires the operator must tell an ordering defect from
|
|
// provider latency, so the thrown message must carry the deciding sequence
|
|
// operands alongside the in-scope timings (observedAt is a poll-batch stamp,
|
|
// so the timings alone can be identical or argue against the verdict).
|
|
it('reports the ordering timings when the assertion fires', () => {
|
|
const source = read('scripts/run-managed-agent-server-e2e.ts');
|
|
expect(source).toContain('firstModelSequence=${firstModel.event.sequence}');
|
|
expect(source).toContain(
|
|
'runtimeReadySequence=${runtimeReady.event.sequence}',
|
|
);
|
|
expect(source).toContain(
|
|
'firstModelEventMs=${firstModel.observedAt - requestStartedAt}',
|
|
);
|
|
expect(source).toContain(
|
|
'runtimeReadyMs=${runtimeReady.observedAt - requestStartedAt}',
|
|
);
|
|
expect(source).toContain('runtimeDelayMs=${runtimeDelayMs}');
|
|
});
|
|
|
|
// #12941: the README named scripts/run-managed-hosted-runtime-e2e.ts as the
|
|
// deterministic CI proof, a file that has never existed. Any script the
|
|
// README names must be real.
|
|
it('names only scripts that exist', () => {
|
|
const readme = read('packages/sdk-java/managed-agent-server/README.md');
|
|
expect(readme).not.toContain('run-managed-hosted-runtime-e2e');
|
|
for (const script of namedScripts(readme)) {
|
|
expect(
|
|
existsSync(new URL(`../../${script}`, import.meta.url)),
|
|
`${script} named in the managed-agent README does not exist`,
|
|
).toBe(true);
|
|
}
|
|
});
|
|
|
|
// With the server defaults flipped on, dropping these pins would make the
|
|
// runner start the durable path off Linux and fail at server startup while
|
|
// `npm run test:scripts` stayed green: trusted recovery is pinned off at
|
|
// both Spring launch sites, and durable local process only ever follows the
|
|
// workspace-Turn modes.
|
|
it('pins the runtime recovery flags for every runner mode', () => {
|
|
const source = read('scripts/run-managed-agent-server-e2e.ts');
|
|
expect(
|
|
source.match(
|
|
/QWEN_MANAGED_AGENT_RUNTIME_TRUSTED_LOCAL_REBOOT_RECOVERY:\s*'false'/g,
|
|
),
|
|
'both Spring launch sites must pin trusted reboot recovery off',
|
|
).toHaveLength(2);
|
|
expect(
|
|
source.match(
|
|
/QWEN_MANAGED_AGENT_RUNTIME_DURABLE_LOCAL_PROCESS:\s*'false'/g,
|
|
),
|
|
'both non-workspaceTurns branches must pin durable local process off',
|
|
).toHaveLength(2);
|
|
});
|
|
|
|
// Every mode now runs through the G0 public Workspace admission, and the
|
|
// failover modes hand the same Session to a replacement owner: both Spring
|
|
// launch sites and both Harness launch sites must carry the admission
|
|
// wiring, or a mode turns red only after the failover kill with an error
|
|
// that reads like a takeover defect instead of a config asymmetry.
|
|
it('pins the G0 workspace admission at both launch sites', () => {
|
|
const source = read('scripts/run-managed-agent-server-e2e.ts');
|
|
expect(
|
|
source.match(
|
|
/QWEN_MANAGED_AGENT_TRUSTED_ACTOR_HEADER: trustedActorHeader/g,
|
|
),
|
|
'both Spring launch sites must configure the trusted actor header',
|
|
).toHaveLength(2);
|
|
expect(
|
|
source.match(/QWEN_MANAGED_AGENT_WORKSPACE_FILES_ENABLED: 'true'/g),
|
|
'both Spring launch sites must enable Hosted Workspace files',
|
|
).toHaveLength(2);
|
|
expect(
|
|
source.match(/'--managed-runtime-broker-url'/g),
|
|
'both Harness launch sites must pass the Runtime Broker flags',
|
|
).toHaveLength(2);
|
|
// The mount argument is pushed once into the springArguments both Spring
|
|
// launch sites share; re-gating it would fail validateWorkspaceFiles at
|
|
// startup in every non-workspaceTurns mode while CI stayed green.
|
|
expect(
|
|
source.match(/workspace-mounts\[0\]\.root=/g),
|
|
'the shared Spring arguments must configure the Workspace mount',
|
|
).toHaveLength(1);
|
|
// The mount root itself must sit in the unconditional mkdir list: a
|
|
// re-gated entry still starts Spring (nothing checks the root exists)
|
|
// and only breaks the real-model side-effect assertion, which no lane
|
|
// runs.
|
|
expect(
|
|
source.match(/^\s+workspaceMount,$/m),
|
|
'the Workspace mount root must be created for every mode',
|
|
).not.toBeNull();
|
|
expect(
|
|
source.match(
|
|
/INSERT INTO qwen_managed_agent\.managed_workspace_registry/g,
|
|
),
|
|
'the Workspace registry row must be seeded for every mode',
|
|
).toHaveLength(1);
|
|
expect(
|
|
source.match(/INSERT INTO qwen_managed_agent\.managed_workspace_access/g),
|
|
'the Workspace access grant must be seeded for every mode',
|
|
).toHaveLength(1);
|
|
// The counts above cannot see WHERE an item sits: the runner before the
|
|
// admission alignment gated these same items inside workspaceTurns
|
|
// conditionals and satisfied every count. These negative pins are the
|
|
// symmetry witness. The windows stay short so the gates that must stay
|
|
// (durable local process, the Linux check, the 0700 state dir, the
|
|
// unbound create body) and the comment mentioning workspaceTurns do not
|
|
// trip them.
|
|
for (const reGated of [
|
|
/workspaceTurns[\s\S]{0,120}?QWEN_MANAGED_AGENT_TRUSTED_ACTOR_HEADER/,
|
|
/workspaceTurns[\s\S]{0,120}?QWEN_MANAGED_AGENT_WORKSPACE_FILES_ENABLED/,
|
|
/workspaceTurns[\s\S]{0,120}?--managed-runtime-broker-url/,
|
|
/workspaceTurns[\s\S]{0,120}?workspaceMount/,
|
|
/if \(workspaceTurns\) \{\s*runMysql\(/,
|
|
]) {
|
|
expect(
|
|
source.match(reGated),
|
|
`G0 admission re-gated behind workspaceTurns: ${reGated}`,
|
|
).toBeNull();
|
|
}
|
|
});
|
|
|
|
// The README currently names no script, so only a fixture can pin the
|
|
// extractor itself: an extractor that stops matching must fail, not pass.
|
|
it('extracts the script spellings the README could use', () => {
|
|
expect(namedScripts('npx tsx ./scripts/nope.ts')).toEqual([
|
|
'scripts/nope.ts',
|
|
]);
|
|
expect(namedScripts('see `scripts/nope.ts`')).toEqual(['scripts/nope.ts']);
|
|
expect(namedScripts('packages/foo/scripts/nope.ts')).toEqual([]);
|
|
});
|
|
|
|
it('pins the fenced hosted-harness launch block in the server README to a startable form', () => {
|
|
// The oracle is the profile validator itself, not a copy of its rules:
|
|
// the documented launch must supply everything
|
|
// validateHostedHarnessProfile rejects for missing, or the launch fails
|
|
// at startup while this pin stays green. The block is matched exactly —
|
|
// a grammar that parses the fence into argv/env fails open on every
|
|
// shell spelling it does not model. The CLI-side credential names come
|
|
// from the production constants so renaming one reddens this pin instead
|
|
// of stranding the README's spelling.
|
|
const readme = read('packages/sdk-java/managed-agent-server/README.md');
|
|
const fencedBlocks = fencedShellBlocks(readme);
|
|
const command =
|
|
'qwen serve --profile hosted-harness --port 4171 --hostname 127.0.0.1 --no-web';
|
|
const launchBlocks = fencedBlocks.filter((block) =>
|
|
block.includes(command),
|
|
);
|
|
expect(
|
|
launchBlocks,
|
|
'the README must fence exactly one hosted-harness launch block',
|
|
).toHaveLength(1);
|
|
expect(launchBlocks[0]).toBe(
|
|
[
|
|
`${QWEN_SERVER_TOKEN_ENV}="$QWEN_MANAGED_AGENT_HARNESS_TOKEN" \\`,
|
|
`${HOSTED_HARNESS_CAPABILITY_DIGEST_ENV}="$QWEN_MANAGED_AGENT_CAPABILITY_DIGEST" \\`,
|
|
command,
|
|
].join('\n') + '\n',
|
|
);
|
|
// A `$VAR` reference defers to a name the reader was told to export in
|
|
// the Prerequisites section; a renamed or dropped export there expands
|
|
// to empty in the reader's shell and the launch dies at startup, so
|
|
// every name the launch block references must be assigned inside that
|
|
// section — an assignment in a fence anywhere else in the README never
|
|
// reaches the reader's shell.
|
|
const prereqStart = readme.indexOf('## Prerequisites');
|
|
const prereqEnd = readme.indexOf('## Public Session lifecycle');
|
|
expect(prereqStart).toBeGreaterThan(-1);
|
|
expect(prereqEnd).toBeGreaterThan(prereqStart);
|
|
const assigned = new Set();
|
|
for (const fenced of fencedShellBlocks(
|
|
readme.slice(prereqStart, prereqEnd),
|
|
)) {
|
|
for (const match of fenced.matchAll(
|
|
/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=/gm,
|
|
)) {
|
|
assigned.add(match[1]);
|
|
}
|
|
}
|
|
for (const match of launchBlocks[0].matchAll(
|
|
/\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?/g,
|
|
)) {
|
|
expect(
|
|
assigned.has(match[1]),
|
|
`the launch block references $${match[1]}, which the Prerequisites section does not assign`,
|
|
).toBe(true);
|
|
}
|
|
// The validator's input is parsed from the pinned block so the fixture
|
|
// cannot drift from the documented launch: an edit that drops --no-web
|
|
// or widens --hostname must redden this oracle, not only the exact-text
|
|
// pin above. mode stays the constant serve.ts hardcodes — the launch
|
|
// carries no flag for it — and the deferred `$VAR` credentials stand in
|
|
// as conforming values so the validator judges the launch's shape rather
|
|
// than the placeholder spelling.
|
|
expect(() =>
|
|
validateHostedHarnessProfile({
|
|
profile: 'hosted-harness',
|
|
hostname: /--hostname\s+(\S+)/.exec(launchBlocks[0])[1],
|
|
port: Number(/--port\s+(\d+)/.exec(launchBlocks[0])[1]),
|
|
mode: 'http-bridge',
|
|
token: 'documented-value',
|
|
serveWebShell: !launchBlocks[0].includes('--no-web'),
|
|
hostedHarnessCapabilityDigest: `sha256:${'a'.repeat(64)}`,
|
|
}),
|
|
).not.toThrow();
|
|
});
|
|
|
|
it('pairs the 4171 base-url export with a startup-order note in the dual-path entry', () => {
|
|
// The base URL is read once at JVM startup, so the section that moves
|
|
// Spring to 4171 must say the value applies before (or via a restart
|
|
// of) `mvn spring-boot:run`, or the reader's running server stays on
|
|
// the 4170 value exported in Prerequisites.
|
|
const readme = read('packages/sdk-java/managed-agent-server/README.md');
|
|
const start = readme.indexOf(
|
|
'## Full WebShell dual-path development entry',
|
|
);
|
|
const end = readme.indexOf('## Embedded Runtime Broker');
|
|
expect(start).toBeGreaterThan(-1);
|
|
expect(end).toBeGreaterThan(start);
|
|
const slice = readme.slice(start, end);
|
|
// The export must point at the port the documented launch binds, not at
|
|
// a second copy of the number: a launch-block port bump mirrored into
|
|
// the launch pin above would otherwise leave this assertion green while
|
|
// Spring keeps calling the old port and every Managed Turn fails.
|
|
const launchBlocks = fencedShellBlocks(readme).filter((fenced) =>
|
|
fenced.includes('qwen serve --profile hosted-harness'),
|
|
);
|
|
expect(
|
|
launchBlocks,
|
|
'the README must fence exactly one hosted-harness launch block',
|
|
).toHaveLength(1);
|
|
const port = /--port\s+(\d+)/.exec(launchBlocks[0])[1];
|
|
expect(
|
|
slice,
|
|
'the Spring base URL must point at the port the launch block binds',
|
|
).toContain(
|
|
`QWEN_MANAGED_AGENT_HARNESS_BASE_URL='http://127.0.0.1:${port}'`,
|
|
);
|
|
// The caveat sentence is hard-wrapped in the README, so match across the
|
|
// line break; the fallback is the restart clause, not the bare word
|
|
// `restart` that any unrelated sentence in the slice could supply.
|
|
expect(slice.replace(/\s+/g, ' ')).toMatch(
|
|
/read once at JVM startup|restart it with the override/i,
|
|
);
|
|
// The by-hand recipe re-anchors Spring to the Prerequisites environment,
|
|
// which never names the Session Store, and application.yml defaults the
|
|
// store off — a reader who follows that path wires Spring without a
|
|
// store descriptor and the Harness answers every attach with
|
|
// 400 invalid_managed_session_store, so the slice must name the switch.
|
|
expect(slice).toContain('QWEN_MANAGED_AGENT_SESSION_STORE_ENABLED');
|
|
// The store switch is read once at JVM startup exactly like the base
|
|
// URL: exported below the restart cue it never reaches the reader's
|
|
// JVM, and every attach fails with 400 invalid_managed_session_store
|
|
// while the recipe reads complete — so the exports must land before the
|
|
// single restart. The cue is hard-wrapped, so compare on the collapsed
|
|
// slice; a missing cue fails closed through the -1.
|
|
const flat = slice.replace(/\s+/g, ' ');
|
|
expect(
|
|
flat.indexOf('QWEN_MANAGED_AGENT_SESSION_STORE_ENABLED'),
|
|
'the Session Store exports must precede the Spring restart cue',
|
|
).toBeLessThan(flat.indexOf('restart it with the override'));
|
|
});
|
|
|
|
it('keeps the review-corrections merge gates behind the CI-gated Hosted proofs', () => {
|
|
// The hosted-harness-mysql CI job runs HostedWorkspaceToolTurnIT (a real
|
|
// file-tool Turn through the packaged worker) and the three
|
|
// owner-failover E2E modes against the production HTTP durable-store
|
|
// adapter, all fail-closed via the failsafe includes and
|
|
// check-failsafe-reports.js. While both oracles stand, the
|
|
// review-corrections gates must not re-assert those capabilities as
|
|
// unproven — the foundation-boundary banner names that document the
|
|
// current authority, so the false claim reaches integrators in either
|
|
// language.
|
|
const toolTurnIt =
|
|
'packages/sdk-java/managed-agent-server/src/test/java/com/alibaba/qwen/code/managedagent/HostedWorkspaceToolTurnIT.java';
|
|
expect(
|
|
existsSync(new URL(`../../${toolTurnIt}`, import.meta.url)) &&
|
|
read('.github/workflows/sdk-java.yml').includes(
|
|
'test:e2e:managed-session-failover',
|
|
),
|
|
'the Hosted tool-turn IT and the failover E2E lane must both exist for this oracle to mean anything',
|
|
).toBe(true);
|
|
for (const [file, heading, claim] of [
|
|
[
|
|
'docs/design/2026-09-25-managed-agent-review-corrections.md',
|
|
'## Remaining integration gates',
|
|
/what remains unproven is[^.]*\./gi,
|
|
],
|
|
[
|
|
'docs/design/2026-09-25-managed-agent-review-corrections.zh-CN.md',
|
|
'## 剩余集成门禁',
|
|
/仍未证明[^。]*。/g,
|
|
],
|
|
]) {
|
|
const doc = read(file);
|
|
expect(doc, `${file} must keep its merge-gates section`).toContain(
|
|
heading,
|
|
);
|
|
const gates = doc.slice(doc.indexOf(heading));
|
|
for (const [sentence] of gates.matchAll(claim)) {
|
|
expect(
|
|
sentence,
|
|
`${file} lists CI-gated Hosted capabilities as unproven`,
|
|
).not.toMatch(
|
|
/tool turns|durable-store adapter|worker bundle|工具 Turn/i,
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
it('pins the attach-time generation fence in the Harness attachment contract', () => {
|
|
// The attachment paragraph publishes which identities an attach does NOT
|
|
// compare. The Harness keys its in-memory Session by sessionId alone and
|
|
// compares no tenant on attach, so the paragraph must say exactly that —
|
|
// an integrator who reads a tenant-keyed coalescing claim leaves tenant
|
|
// scoping out of their own gateway on the recovery redrive, which is not
|
|
// fenced. The Harness writer generation is the one identity that IS
|
|
// enforced — the contract middleware answers a stale boot id with 409
|
|
// hosted_harness_generation_mismatch and the attach handler rejects a
|
|
// store descriptor whose writerId differs before any coalescing — so the
|
|
// paragraph must name that rejection: an integrator reading "not
|
|
// rejected" for the generation omits the 409 path and every attach fails
|
|
// after a Harness restart with no documented way out. Each fence pin
|
|
// below matches a polarity phrase, not the bare name: the name alone
|
|
// stays green when the paragraph says the generation is not rejected or
|
|
// the store fence is shipped behavior.
|
|
const readme = read('packages/sdk-java/managed-agent-server/README.md');
|
|
const anchor = readme.indexOf('The Java connector caches an attachment');
|
|
expect(anchor).toBeGreaterThan(-1);
|
|
const end = readme.indexOf('\n\n', anchor);
|
|
expect(end).toBeGreaterThan(anchor);
|
|
const paragraph = readme.slice(anchor, end).replace(/\s+/g, ' ');
|
|
expect(paragraph).toContain(
|
|
'keys its in-memory Session by `sessionId` alone',
|
|
);
|
|
expect(paragraph).toContain(
|
|
'fails closed with `409 hosted_harness_generation_mismatch`',
|
|
);
|
|
expect(paragraph).toContain(
|
|
'`managed_session_store_conflict` fence is target design',
|
|
);
|
|
});
|
|
});
|