Skip to content

Commit 4eeb7a0

Browse files
authored
refactor(policy): remove NemoClaw policy lifecycle ownership
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
1 parent 3a2403c commit 4eeb7a0

480 files changed

Lines changed: 8232 additions & 36527 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ci/onboard-entry-composition-budget.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
"runOnboard.finalizationDeps.verifyDeployment.getMessagingChannels": 1
1212
},
1313
"policy": {
14-
"createOnboardPolicyApplication.getRecordedPolicyTier": 1,
1514
"preflightAuthoritativeRebuildTarget": 1,
16-
"runOnboard": 5,
15+
"runOnboard": 4,
1716
"sandboxCreateIntentResolver.getAgentPolicyPath": 1
1817
},
1918
"provider": {

ci/source-architecture-budget.json

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,31 @@
33
"fanIn": {
44
"defaultMax": 20,
55
"maxByFile": {
6-
"src/lib/actions/sandbox/mcp-bridge-contracts.ts": 26,
6+
"src/lib/actions/sandbox/mcp-bridge-contracts.ts": 25,
77
"src/lib/actions/sandbox/process-recovery.ts": 27,
88
"src/lib/adapters/docker/index.ts": 43,
99
"src/lib/adapters/openshell/client.ts": 20,
1010
"src/lib/adapters/openshell/resolve.ts": 27,
1111
"src/lib/adapters/openshell/runtime.ts": 55,
1212
"src/lib/adapters/openshell/timeouts.ts": 39,
1313
"src/lib/agent/defs.ts": 33,
14-
"src/lib/cli/branding.ts": 86,
14+
"src/lib/cli/branding.ts": 85,
1515
"src/lib/cli/nemoclaw-oclif-command.ts": 107,
1616
"src/lib/cli/terminal-style.ts": 43,
1717
"src/lib/core/json-types.ts": 37,
1818
"src/lib/core/ports.ts": 89,
1919
"src/lib/core/shell-quote.ts": 28,
2020
"src/lib/core/url-utils.ts": 30,
21-
"src/lib/core/wait.ts": 38,
21+
"src/lib/core/wait.ts": 37,
2222
"src/lib/credentials/store.ts": 46,
2323
"src/lib/inference/config.ts": 30,
24-
"src/lib/inference/web-search.ts": 21,
2524
"src/lib/messaging/channels/index.ts": 25,
2625
"src/lib/onboard/gateway-binding.ts": 52,
2726
"src/lib/runner.ts": 86,
2827
"src/lib/security/redact.ts": 54,
29-
"src/lib/state/onboard-session.ts": 36,
3028
"src/lib/state/mcp-lifecycle-lock.ts": 21,
31-
"src/lib/state/registry.ts": 101,
29+
"src/lib/state/onboard-session.ts": 35,
30+
"src/lib/state/registry.ts": 98,
3231
"src/lib/state/state-root.ts": 21,
3332
"src/lib/subprocess-env.ts": 24,
3433
"src/lib/validation.ts": 24
@@ -40,7 +39,7 @@
4039
"src/lib/actions/inference-set.ts": 32,
4140
"src/lib/actions/sandbox/connect.ts": 43,
4241
"src/lib/actions/sandbox/destroy.ts": 29,
43-
"src/lib/actions/sandbox/doctor.ts": 29,
42+
"src/lib/actions/sandbox/doctor.ts": 27,
4443
"src/lib/actions/sandbox/gateway-state.ts": 21,
4544
"src/lib/actions/sandbox/status-snapshot.ts": 19,
4645
"src/lib/actions/sandbox/policy-channel.ts": 30,
@@ -51,7 +50,7 @@
5150
"src/lib/inference/local.ts": 22,
5251
"src/lib/inference/onboard-probes.ts": 21,
5352
"src/lib/inference/vllm.ts": 21,
54-
"src/lib/onboard.ts": 202,
53+
"src/lib/onboard.ts": 201,
5554
"src/lib/onboard/machine/handlers/sandbox.ts": 21,
5655
"src/lib/policy/index.ts": 23,
5756
"src/lib/sandbox/config.ts": 22,
@@ -60,9 +59,9 @@
6059
},
6160
"allowedCycles": [],
6261
"maxRootFiles": {
63-
"src/lib/onboard": 308,
62+
"src/lib/onboard": 307,
6463
"src/lib/actions": 19,
65-
"src/lib/actions/sandbox": 183,
64+
"src/lib/actions/sandbox": 182,
6665
"src/lib/state": 39,
6766
"src/lib/inference": 63,
6867
"scripts": 42

nemoclaw/src/blueprint/runner-identity.test.ts

Lines changed: 15 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ import {
1919
MATCHING_INFERENCE_ROUTE_LISTING,
2020
MATCHING_RUNTIME_PROVIDER_LISTING,
2121
providersV2EnabledResult,
22-
resultWithBlueprintPolicyAuthority,
23-
sandboxIdentityResult,
22+
resultWithBlueprintPolicy,
2423
sequentialCommandResult,
2524
successResult,
2625
TEST_SANDBOX_POLICY,
@@ -51,6 +50,7 @@ vi.mock("node:fs", async (importOriginal) => {
5150
openSync: memory.openSync,
5251
readFileSync: memory.readFileSync,
5352
renameSync: memory.renameSync,
53+
unlinkSync: memory.unlinkSync,
5454
writeFileSync: memory.writeFileSync,
5555
readdirSync: memory.readdirSync,
5656
realpathSync: memory.realpathSync,
@@ -110,7 +110,7 @@ function responseQueue(
110110
const fallback = responses.get(command)?.shift() ?? fallbacks.get(command) ?? success;
111111
return fallback.exitCode === undefined
112112
? fallback
113-
: resultWithBlueprintPolicyAuthority(args, {
113+
: resultWithBlueprintPolicy(args, {
114114
...fallback,
115115
exitCode: fallback.exitCode ?? 1,
116116
});
@@ -179,7 +179,7 @@ describe("blueprint identity wrapper", () => {
179179
realpaths.clear();
180180
vi.clearAllMocks();
181181
mockExeca.mockImplementation(async (_command: string, args: string[]) =>
182-
resultWithBlueprintPolicyAuthority(
182+
resultWithBlueprintPolicy(
183183
args,
184184
args.join(" ") === "settings get --global --json" ? providersV2Enabled : success,
185185
),
@@ -409,145 +409,6 @@ describe("blueprint identity wrapper", () => {
409409
).not.toContain("refresh configure");
410410
});
411411

412-
it("establishes the policy receipt before the first identity mutation", async () => {
413-
process.env.OKTA_CLIENT_ID = "client-id";
414-
process.env.OKTA_REFRESH_TOKEN = "refresh-secret";
415-
process.env.OKTA_CLIENT_SECRET = "client-secret";
416-
responseQueue([
417-
[
418-
"provider get acme-okta-runtime",
419-
[
420-
failureResult("provider not found"),
421-
...Array.from({ length: 4 }, () => ({
422-
exitCode: 0,
423-
stdout: matchingProvider,
424-
stderr: "",
425-
})),
426-
],
427-
],
428-
]);
429-
430-
await actionApply("default", blueprint({ identity: oktaIdentity() }));
431-
432-
const commands = mockExeca.mock.calls.map(([, args]) => (args ?? []).join(" "));
433-
const createIndex = commands.indexOf(
434-
"sandbox create -g test-gateway --from openclaw --name test-sandbox --policy /tmp/nemoclaw-test-policy.yaml --forward 18789",
435-
);
436-
const firstReceiptValidation = commands.indexOf(
437-
"sandbox get -g test-gateway test-sandbox",
438-
createIndex + 1,
439-
);
440-
const firstIdentityMutation = commands.indexOf(
441-
"provider create --name acme-okta-runtime --type okta-runtime-v1 --runtime-credentials",
442-
);
443-
expect(createIndex).toBeGreaterThan(-1);
444-
expect(firstReceiptValidation).toBeGreaterThan(createIndex);
445-
expect(firstIdentityMutation).toBeGreaterThan(firstReceiptValidation);
446-
});
447-
448-
it("stops before identity mutation when the receipt sandbox identity changes", async () => {
449-
process.env.OKTA_CLIENT_ID = "client-id";
450-
process.env.OKTA_REFRESH_TOKEN = "refresh-secret";
451-
process.env.OKTA_CLIENT_SECRET = "client-secret";
452-
const identityResult = sequentialCommandResult("sandbox get -g test-gateway test-sandbox", [
453-
sandboxIdentityResult("test-sandbox"),
454-
sandboxIdentityResult("test-sandbox", "replacement-id"),
455-
]);
456-
mockExeca.mockImplementation(
457-
async (_command: string, args: string[]) =>
458-
identityResult(args) ??
459-
resultWithBlueprintPolicyAuthority(
460-
args,
461-
args.join(" ") === "settings get --global --json" ? providersV2Enabled : success,
462-
),
463-
);
464-
465-
await expect(actionApply("default", blueprint({ identity: oktaIdentity() }))).rejects.toThrow(
466-
/receipt does not match the live sandbox policy/u,
467-
);
468-
const commands = mockExeca.mock.calls.map(([, args]) => (args ?? []).join(" "));
469-
expect(commands).not.toContain(
470-
"provider create --name acme-okta-runtime --type okta-runtime-v1 --runtime-credentials",
471-
);
472-
expect(commands).not.toContain("sandbox provider attach test-sandbox acme-okta-runtime");
473-
});
474-
475-
it("validates the policy receipt before inference-provider reuse inspection", async () => {
476-
process.env.OKTA_CLIENT_ID = "client-id";
477-
process.env.OKTA_REFRESH_TOKEN = "refresh-secret";
478-
process.env.OKTA_CLIENT_SECRET = "client-secret";
479-
responseQueue([
480-
["provider get test-provider", [failureResult("gateway configuration not found")]],
481-
]);
482-
483-
await expect(actionApply("default", blueprint({ identity: oktaIdentity() }))).rejects.toThrow(
484-
/Failed to inspect inference provider 'test-provider'.*gateway configuration not found/u,
485-
);
486-
const commands = mockExeca.mock.calls.map(([, args]) => (args ?? []).join(" "));
487-
const receiptValidation = commands.indexOf("sandbox get -g test-gateway test-sandbox");
488-
const providerInspection = commands.indexOf("provider get test-provider");
489-
expect(receiptValidation).toBeGreaterThanOrEqual(0);
490-
expect(providerInspection).toBeGreaterThanOrEqual(0);
491-
expect(receiptValidation).toBeLessThan(providerInspection);
492-
expect(commands).not.toContain(
493-
"provider create --name acme-okta-runtime --type okta-runtime-v1 --runtime-credentials",
494-
);
495-
});
496-
it.each([
497-
["not configured", "Gateway inference:\n\n Not configured\n"],
498-
[
499-
"OpenShell v0.0.99 ANSI not configured",
500-
[
501-
"\u001b[1mInference:\u001b[0m",
502-
"",
503-
" Not configured",
504-
"",
505-
"\u001b[1mSystem inference:\u001b[0m",
506-
"",
507-
" Not configured",
508-
"",
509-
].join("\n"),
510-
],
511-
[
512-
"configured for a different model",
513-
matchingInferenceRoute.replace("Model: test-model", "Model: other-model"),
514-
],
515-
])("sets the requested route when the reused route is %s", async (_label, routeOutput) => {
516-
process.env.OKTA_CLIENT_ID = "client-id";
517-
process.env.OKTA_REFRESH_TOKEN = "refresh-secret";
518-
process.env.OKTA_CLIENT_SECRET = "client-secret";
519-
responseQueue([
520-
[
521-
"sandbox get test-sandbox",
522-
[{ exitCode: 0, stdout: "Name: test-sandbox\nPhase: Ready", stderr: "" }],
523-
],
524-
[
525-
"provider get test-provider",
526-
[{ exitCode: 0, stdout: matchingInferenceProvider, stderr: "" }],
527-
],
528-
["inference get", [{ exitCode: 0, stdout: routeOutput, stderr: "" }]],
529-
[
530-
"provider get acme-okta-runtime",
531-
[
532-
failureResult("provider not found"),
533-
...Array.from({ length: 4 }, () => ({
534-
exitCode: 0,
535-
stdout: matchingProvider,
536-
stderr: "",
537-
})),
538-
],
539-
],
540-
]);
541-
542-
await actionApply("default", blueprint({ identity: oktaIdentity() }));
543-
544-
const commands = mockExeca.mock.calls.map(([, args]) => (args ?? []).join(" "));
545-
expect(commands).toContain("inference set --provider test-provider --model test-model");
546-
expect(
547-
commands.indexOf("inference set --provider test-provider --model test-model"),
548-
).toBeLessThan(commands.indexOf("sandbox provider attach test-sandbox acme-okta-runtime"));
549-
});
550-
551412
it("reuses the ANSI-formatted OpenShell v0.0.99 inference route", async () => {
552413
process.env.OKTA_CLIENT_ID = "client-id";
553414
process.env.OKTA_REFRESH_TOKEN = "refresh-secret";
@@ -1036,9 +897,9 @@ describe("blueprint identity wrapper", () => {
1036897
}),
1037898
});
1038899
const policyResult = sequentialCommandResult(POLICY_BOUNDARY_COMMAND, [
1039-
resultWithBlueprintPolicyAuthority(POLICY_BOUNDARY_COMMAND.split(" "), success),
900+
resultWithBlueprintPolicy(POLICY_BOUNDARY_COMMAND.split(" "), success),
1040901
{
1041-
...resultWithBlueprintPolicyAuthority(POLICY_BOUNDARY_COMMAND.split(" "), success),
902+
...resultWithBlueprintPolicy(POLICY_BOUNDARY_COMMAND.split(" "), success),
1042903
stdout: JSON.stringify({
1043904
scope: "sandbox",
1044905
sandbox: "test-sandbox",
@@ -1053,7 +914,7 @@ describe("blueprint identity wrapper", () => {
1053914
mockExeca.mockImplementation(async (_command: string, args: string[]) =>
1054915
args.join(" ") === "provider get test-provider"
1055916
? { exitCode: 0, stdout: matchingInferenceProvider, stderr: "" }
1056-
: (policyResult(args) ?? resultWithBlueprintPolicyAuthority(args, success)),
917+
: (policyResult(args) ?? resultWithBlueprintPolicy(args, success)),
1057918
);
1058919

1059920
await expect(actionRollback("provider-authority-drift")).rejects.toThrow(
@@ -1076,10 +937,7 @@ describe("blueprint identity wrapper", () => {
1076937
policy_authority: managedPolicyAuthorityReceipt(),
1077938
}),
1078939
});
1079-
const matchingPolicy = resultWithBlueprintPolicyAuthority(
1080-
POLICY_BOUNDARY_COMMAND.split(" "),
1081-
success,
1082-
);
940+
const matchingPolicy = resultWithBlueprintPolicy(POLICY_BOUNDARY_COMMAND.split(" "), success);
1083941
const policyResult = sequentialCommandResult(POLICY_BOUNDARY_COMMAND, [
1084942
matchingPolicy,
1085943
matchingPolicy,
@@ -1098,7 +956,7 @@ describe("blueprint identity wrapper", () => {
1098956
]);
1099957
mockExeca.mockImplementation(
1100958
async (_command: string, args: string[]) =>
1101-
policyResult(args) ?? resultWithBlueprintPolicyAuthority(args, success),
959+
policyResult(args) ?? resultWithBlueprintPolicy(args, success),
1102960
);
1103961

1104962
await expect(actionRollback("sandbox-authority-drift")).rejects.toThrow(
@@ -1167,7 +1025,9 @@ describe("blueprint identity wrapper", () => {
11671025
attachment_created: false,
11681026
});
11691027

1170-
await expect(actionRollback(plan.run_id)).rejects.toThrow(/mutable sandbox and provider names/u);
1028+
await expect(actionRollback(plan.run_id)).rejects.toThrow(
1029+
/mutable sandbox and provider names/u,
1030+
);
11711031
expect(store.get(`/fakehome/.nemoclaw/state/runs/${plan.run_id}/rolled_back`)).toBeUndefined();
11721032
});
11731033

@@ -1209,7 +1069,9 @@ describe("blueprint identity wrapper", () => {
12091069
attachment_created: true,
12101070
});
12111071

1212-
await expect(actionRollback(plan.run_id)).rejects.toThrow(/mutable sandbox and provider names/u);
1072+
await expect(actionRollback(plan.run_id)).rejects.toThrow(
1073+
/mutable sandbox and provider names/u,
1074+
);
12131075
expect(store.get(`/fakehome/.nemoclaw/state/runs/${plan.run_id}/rolled_back`)).toBeUndefined();
12141076
});
12151077

nemoclaw/src/blueprint/runner-mock-fixtures.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ export function inMemoryFsMethods(store: Map<string, RunnerFsEntry>, options?: I
8989
store.set(destination, entry);
9090
store.delete(source);
9191
}),
92+
unlinkSync: spy((target: string) => {
93+
if (!store.delete(target)) return missingEntry(target);
94+
}),
9295
readdirSync: (p: string) => {
9396
const prefix = p.endsWith("/") ? p : `${p}/`;
9497
const entries = new Set(

nemoclaw/src/blueprint/runner-name-validation.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
} from "./runner-mock-fixtures.js";
1919
import {
2020
minimalBlueprint,
21-
resultWithBlueprintPolicyAuthority,
21+
resultWithBlueprintPolicy,
2222
successResult,
2323
TEST_SANDBOX_POLICY,
2424
TEST_SANDBOX_POLICY_PATH,
@@ -47,6 +47,7 @@ vi.mock("node:fs", async (importOriginal) => {
4747
openSync: memory.openSync,
4848
readFileSync: memory.readFileSync,
4949
renameSync: memory.renameSync,
50+
unlinkSync: memory.unlinkSync,
5051
writeFileSync: memory.writeFileSync,
5152
readdirSync: memory.readdirSync,
5253
};
@@ -128,7 +129,7 @@ describe("blueprint name validation (fail-closed integration)", () => {
128129
vi.clearAllMocks();
129130
vi.spyOn(process.stdout, "write").mockImplementation(stdout.write);
130131
mockExeca.mockImplementation(async (_command: string, args: string[]) =>
131-
resultWithBlueprintPolicyAuthority(args, successResult()),
132+
resultWithBlueprintPolicy(args, successResult()),
132133
);
133134
});
134135

0 commit comments

Comments
 (0)