Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 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
338 changes: 318 additions & 20 deletions scripts/patch-openclaw-device-self-approval.mts

Large diffs are not rendered by default.

111 changes: 111 additions & 0 deletions src/lib/actions/sandbox/auto-pair-approval-script.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it } from "vitest";

import {
AUTO_PAIR_MAX_APPROVALS,
buildAutoPairApprovalScript,
parseAutoPairApprovalReceipt,
readAutoPairApprovalPolicyModule,
} from "./auto-pair-approval";

const SUMMARY_MARKER = "__NEMOCLAW_AUTO_PAIR_APPROVED__";
const RECEIPT_MARKER = "__NEMOCLAW_AUTO_PAIR_RECEIPT__";

describe("buildAutoPairApprovalScript (#4263/#4616)", () => {
it("builds the bounded allowlisted approval pass", () => {
const script = buildAutoPairApprovalScript("UE9MSUNZ");
expect(script).toContain("/tmp/nemoclaw-proxy-env.sh");
expect(script).toContain("command -v openclaw");
expect(script).toContain("command -v python3");
expect(script).toContain("'devices', 'list', '--json'");
expect(script).toContain("'devices', 'approve'");
expect(script).toContain("approval_request_decision(device)");
expect(script).toContain("if not decision['allowed']:");
expect(script).toContain("approve_env = gateway_approval_env(os.environ)");
expect(script).toContain(`MAX_APPROVALS = ${AUTO_PAIR_MAX_APPROVALS}`);
expect(script).toContain("'UE9MSUNZ'");
});

it("adds local-device filtering only for restored-clone approval", () => {
const ordinary = buildAutoPairApprovalScript("UE9MSUNZ");
const restoredClone = buildAutoPairApprovalScript("UE9MSUNZ", {
emitReceipt: true,
localDeviceOnly: true,
budget: { maxApprovals: AUTO_PAIR_MAX_APPROVALS },
});

expect(ordinary).not.toContain("local_identity_public_key");
expect(ordinary).not.toContain("NEMOCLAW_OPENCLAW_FORCE_DEVICE_PAIRING");
expect(ordinary).not.toContain("load_clone_local_pending");
expect(ordinary).not.toContain("open_clone_state_root");
expect(ordinary).not.toContain("pass_fds=");
expect(ordinary).not.toContain("NODE_DISABLE_COMPILE_CACHE");
expect(ordinary).not.toContain("OPENCLAW_NO_RESPAWN");
expect(restoredClone).toContain("local_identity_public_key");
expect(restoredClone).toContain(
"approve_env.pop('NEMOCLAW_OPENCLAW_FORCE_DEVICE_PAIRING', None)",
);
expect(restoredClone).not.toContain("'devices', 'list', '--json'");
expect(restoredClone).toContain("'pending.json'");
expect(restoredClone).toContain("'paired.json'");
expect(restoredClone).toContain("local_approval_auth_mode == 'runtime'");
expect(restoredClone).toContain("local_approval_auth_mode == 'paired-token'");
expect(restoredClone).toContain("sync_approved_clone_device_auth");
expect(restoredClone).toContain("os.O_DIRECTORY | os.O_NOFOLLOW");
expect(restoredClone).toContain("dir_fd=clone_state_dir_fd");
expect(restoredClone).toContain("clone_devices_dir_fd,");
expect(restoredClone).toContain("clone_identity_dir_fd,");
expect(restoredClone).toContain("metadata.st_nlink != 1");
expect(restoredClone).toContain("pass_fds=approval_pass_fds");
expect(restoredClone).toContain("approve_env['NODE_DISABLE_COMPILE_CACHE'] = '1'");
expect(restoredClone).toContain("approve_env['OPENCLAW_NO_RESPAWN'] = '1'");
expect(restoredClone).toContain("approve_env.pop('OPENCLAW_GATEWAY_TOKEN', None)");
expect(restoredClone).toContain(
"approve_env['OPENCLAW_STATE_DIR'] = f'/proc/self/fd/{clone_state_dir_fd}'",
);
expect(restoredClone).toContain("approve_env['NEMOCLAW_OPENCLAW_PENDING_FD']");
expect(restoredClone).toContain("approve_env['NEMOCLAW_OPENCLAW_PAIRED_FD']");
expect(restoredClone).toContain("approve_env['NEMOCLAW_OPENCLAW_IDENTITY_FD']");
expect(restoredClone).toContain("clone_directory_is_current('devices'");
expect(restoredClone).toContain("if not related_pending:");
expect(restoredClone).toContain("len(related_pending) > 1");
expect(restoredClone).toContain("pending = related_pending");
expect(restoredClone).toContain("MAX_APPROVALS = 1");
expect(restoredClone).toContain(RECEIPT_MARKER);
});

it("omits the summary marker by default and appends it when requested", () => {
const silent = buildAutoPairApprovalScript("UE9MSUNZ");
const reporting = buildAutoPairApprovalScript("UE9MSUNZ", { emitSummary: true });
expect(silent).not.toContain(SUMMARY_MARKER);
expect(silent).not.toContain(RECEIPT_MARKER);
expect(reporting).toContain(`print(f'${SUMMARY_MARKER}={approved_count}')`);
// The reporting script is the silent script with exactly the summary line
// inserted before the heredoc terminator — nothing else changes.
const stripped = reporting.replace(`print(f'${SUMMARY_MARKER}={approved_count}')\n`, "");
expect(stripped).toBe(silent);
});

it("reads the real policy module from disk", () => {
const module = readAutoPairApprovalPolicyModule();
expect(module).toBeTruthy();
expect(module).toContain("def approval_request_decision");
expect(module).toContain("def gateway_approval_env");
expect(module).not.toContain("recover_failed_scope_approval");
});

it("accepts exactly one terminal fixed receipt", () => {
expect(
parseAutoPairApprovalReceipt(`ignored setup output\n${RECEIPT_MARKER}=approved-one\n`),
).toBe("approved-one");
for (const output of [
`${RECEIPT_MARKER}=approved-one\nlater output\n`,
`${RECEIPT_MARKER}=approve-failed\n${RECEIPT_MARKER}=approved-one\n`,
`${RECEIPT_MARKER}=raw-request-id\n`,
]) {
expect(parseAutoPairApprovalReceipt(output)).toBeNull();
}
});
});
Loading
Loading