Skip to content
Merged
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
2ff7866
fix(snapshot): approve restored clone pairing from local state
cv Jul 27, 2026
919cc45
test(snapshot): keep clone pairing fixture linear
cv Jul 27, 2026
6220e46
test(snapshot): remove inert primary-state sentinel
cv Jul 27, 2026
cd0a91c
test(cli): isolate compiled dispatch cache
cv Jul 27, 2026
d0ba4ec
merge(main): update repair branch
cv Jul 27, 2026
7a7b56c
ci(e2e): route clone pairing snapshot coverage
cv Jul 27, 2026
039f410
Merge branch 'main' into codex/fix-restored-clone-pairing-gate
cv Jul 27, 2026
a7bf7e3
merge(main): trust snapshot coverage selector
cv Jul 27, 2026
977b87f
merge(pr): preserve concurrent main sync
cv Jul 27, 2026
f04cdb6
fix(snapshot): retain restored clone identity for pairing
cv Jul 27, 2026
0f5ea39
merge(pr): sync release evidence from main
cv Jul 27, 2026
5c01638
Merge branch 'main' into codex/fix-restored-clone-pairing-gate
cv Jul 27, 2026
d3bea7c
merge(pr): preserve concurrent remote sync
cv Jul 27, 2026
8905412
fix(snapshot): accept duplicated pairing request ID
cv Jul 27, 2026
8b78339
merge(pr): sync current main
cv Jul 27, 2026
ab26497
fix(snapshot): read restored clone pending state
cv Jul 28, 2026
72b2f11
merge(pr): sync current main
cv Jul 28, 2026
d2ba160
test(snapshot): keep pending fixtures linear
cv Jul 28, 2026
08b9ef6
merge(pr): sync current main
cv Jul 28, 2026
acd04de
fix(snapshot): retain clone token for cold pairing
cv Jul 28, 2026
e9652d9
fix(snapshot): converge restored clone device auth
cv Jul 28, 2026
d592843
test(snapshot): linearize clone auth fixture setup
cv Jul 28, 2026
e9c189f
fix(snapshot): reject invalid stored-auth scopes
cv Jul 28, 2026
8bfc001
fix(snapshot): use clone token for scope repair
cv Jul 28, 2026
606fc06
fix(snapshot): reconcile watcher-completed clone approval
cv Jul 28, 2026
98bb2ec
test(snapshot): pin clone approval request identity
cv Jul 28, 2026
4d15b3e
fix(snapshot): reconcile timed-out clone approval
cv Jul 28, 2026
df88095
test(snapshot): bound approval timeout fixture
cv Jul 28, 2026
2468d21
fix(snapshot): pin clone state descriptors
cv Jul 28, 2026
16a6381
fix(snapshot): pin restored clone pairing context
cv Jul 28, 2026
eaa7138
test(snapshot): keep race cleanup linear
cv Jul 28, 2026
c27b46e
Merge branch 'main' into codex/fix-restored-clone-pairing-gate
cv Jul 28, 2026
5b350b1
Merge branch 'main' into codex/fix-restored-clone-pairing-gate
cjagwani Jul 29, 2026
13b4f0c
Merge branch 'main' into codex/fix-restored-clone-pairing-gate
cjagwani Jul 29, 2026
a547645
merge: resolve conflicts with main
github-actions[bot] Jul 31, 2026
477bf2c
Merge branch 'main' into codex/fix-restored-clone-pairing-gate
cv Aug 3, 2026
05b5bf5
test(e2e): format snapshot pairing control
cv Aug 3, 2026
9146a55
test(e2e): name source pairing control precisely
cv Aug 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 89 additions & 38 deletions test/e2e/live/snapshot-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ const BASELINE_EXCLUSION_KEY = "openclaw_docs";
const LIVE_TIMEOUT_MS = 36 * 60_000;
const INFERENCE_API_KEY = "nvapi-snapshot-commands-fixture-credential";
const INFERENCE_MODEL = "snapshot-commands-model";
const SOURCE_PAIRING_NEGATIVE_CONTROL_MODEL = "snapshot-commands-source-pairing-negative-control";
const SOURCE_PAIRING_NEGATIVE_CONTROL = "/tmp/nemoclaw-snapshot-source-pairing-negative-control";
const SOURCE_PAIRING_NEGATIVE_CONTROL_REQUEST = JSON.stringify({
model: SOURCE_PAIRING_NEGATIVE_CONTROL_MODEL,
messages: [{ role: "user", content: "source sandbox pairing negative control" }],
max_tokens: 1,
});
const OPENCLAW_MAIN_SESSION_STORE = "/sandbox/.openclaw/agents/main/sessions/sessions.json";
const PROTECTED_CREDENTIALS_DIR = "/sandbox/.openclaw/credentials";
const PROTECTED_CREDENTIAL_FILE = `${PROTECTED_CREDENTIALS_DIR}/backup-all-fixture.json`;
Expand Down Expand Up @@ -136,6 +143,12 @@ async function expectAuthenticatedGatewayPairing(
set -eu
PROXY_ENV=/tmp/nemoclaw-proxy-env.sh
[ -r "$PROXY_ENV" ] && . "$PROXY_ENV"
if [ -e ${JSON.stringify(SOURCE_PAIRING_NEGATIVE_CONTROL)} ]; then
curl -fsS https://inference.local/v1/chat/completions \
-H "Content-Type: application/json" \
--data ${JSON.stringify(SOURCE_PAIRING_NEGATIVE_CONTROL_REQUEST)} >/dev/null
exit 97
fi
openclaw agent --agent main --json -m "ping" \
--session-id ${JSON.stringify(sessionId)}
`),
Expand Down Expand Up @@ -328,7 +341,7 @@ test("snapshot commands preserve create/list/latest restore/targeted restore/no-
"baseline exclusions remain active in registry and live policy across rebuild",
"snapshot restore --to carries baseline exclusions into clone registry and live policy",
"snapshot restore --to returns only after restored gateway pairing is authenticated",
"post-restore verification stores its unique session only in the clone and sends one authenticated inference request",
"post-restore clone verification sends one clone-fixture request, stores its unique session only in the clone, and sends no source-sandbox negative-control request",
"latest snapshot restore recovers latest workspace state",
"timestamp-targeted restore recovers the first snapshot state",
"snapshot directory excludes credential-bearing env/json files",
Expand Down Expand Up @@ -547,45 +560,83 @@ test("snapshot commands preserve create/list/latest restore/targeted restore/no-
CLONE_SANDBOX_NAME,
"phase-4-clone-baseline-exclusion",
);
const clonePairingRequestOffset = inference.requests().length;
const pairingSessionId = await expectAuthenticatedGatewayPairing(
sandbox,
CLONE_SANDBOX_NAME,
inferenceConfig,
"phase-4-verify-clone-gateway-pairing",
);
const clonePairingRequests = inference
.requests()
.slice(clonePairingRequestOffset)
.filter(
try {
const installSourcePairingNegativeControl = await sandbox.exec(
SANDBOX_NAME,
["sh", "-lc", `set -eu; umask 077; : > ${JSON.stringify(SOURCE_PAIRING_NEGATIVE_CONTROL)}`],
{
artifactName: "phase-4-install-source-pairing-negative-control",
env: commandEnv(),
timeoutMs: 30_000,
},
);
expect(
installSourcePairingNegativeControl.exitCode,
"source-pairing-negative-control-setup-failed",
).toBe(0);
const clonePairingRequestOffset = inference.requests().length;
const pairingSessionId = await expectAuthenticatedGatewayPairing(
sandbox,
CLONE_SANDBOX_NAME,
inferenceConfig,
"phase-4-verify-clone-gateway-pairing",
);
const pairingRequestDelta = inference.requests().slice(clonePairingRequestOffset);
const clonePairingRequests = pairingRequestDelta.filter(
(request) => request.path === "/v1/chat/completions" && request.model === INFERENCE_MODEL,
);
await expectSandboxSessionPresence(
sandbox,
CLONE_SANDBOX_NAME,
pairingSessionId,
true,
"phase-4-verify-clone-session-owner",
);
await expectSandboxSessionPresence(
sandbox,
SANDBOX_NAME,
pairingSessionId,
false,
"phase-4-verify-primary-session-non-owner",
);
await artifacts.writeJson("phase-4-pairing-inference-request-deltas.json", {
cloneAuthenticatedCount: clonePairingRequests.filter((request) => request.auth === "ok").length,
cloneSessionOwned: true,
primarySessionOwned: false,
});
expect(clonePairingRequests.length, "clone-pairing-inference-request-count").toBe(1);
expect(
clonePairingRequests[0]?.auth === "ok" &&
clonePairingRequests[0]?.model === INFERENCE_MODEL &&
clonePairingRequests[0]?.path === "/v1/chat/completions",
"clone-pairing-inference-request-classification",
).toBe(true);
const sourcePairingNegativeControlRequests = pairingRequestDelta.filter(
(request) =>
request.path === "/v1/chat/completions" &&
request.model === SOURCE_PAIRING_NEGATIVE_CONTROL_MODEL,
);
await expectSandboxSessionPresence(
sandbox,
CLONE_SANDBOX_NAME,
pairingSessionId,
true,
"phase-4-verify-clone-session-owner",
);
await expectSandboxSessionPresence(
sandbox,
SANDBOX_NAME,
pairingSessionId,
false,
"phase-4-verify-source-session-non-owner",
);
await artifacts.writeJson("phase-4-pairing-inference-request-deltas.json", {
cloneAuthenticatedCount: clonePairingRequests.filter((request) => request.auth === "ok")
.length,
cloneSessionOwned: true,
sourceNegativeControlCount: sourcePairingNegativeControlRequests.length,
sourceSessionOwned: false,
});
expect(clonePairingRequests.length, "clone-pairing-inference-request-count").toBe(1);
expect(
clonePairingRequests[0]?.auth === "ok" &&
clonePairingRequests[0]?.model === INFERENCE_MODEL &&
clonePairingRequests[0]?.path === "/v1/chat/completions",
"clone-pairing-inference-request-classification",
).toBe(true);
expect(
sourcePairingNegativeControlRequests.length,
"source-pairing-negative-control-request-count",
).toBe(0);
} finally {
const removeSourcePairingNegativeControl = await sandbox.exec(
SANDBOX_NAME,
["rm", "-f", SOURCE_PAIRING_NEGATIVE_CONTROL],
{
artifactName: "phase-4-remove-source-pairing-negative-control",
env: commandEnv(),
timeoutMs: 30_000,
},
);
expect(
removeSourcePairingNegativeControl.exitCode,
"source-pairing-negative-control-cleanup-failed",
).toBe(0);
}
const destroyClone = await host.command("nemoclaw", [CLONE_SANDBOX_NAME, "destroy", "--yes"], {
artifactName: "phase-4-destroy-clone",
env: commandEnv(),
Expand Down
Loading