mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-22 22:52:03 +00:00
CLI: compute node effective exec policy
This commit is contained in:
@@ -24,7 +24,7 @@ openclaw approvals get --node <id|name|ip>
|
||||
openclaw approvals get --gateway
|
||||
```
|
||||
|
||||
`openclaw approvals get` now shows the effective exec policy for local and gateway targets:
|
||||
`openclaw approvals get` now shows the effective exec policy for local, gateway, and node targets:
|
||||
|
||||
- requested `tools.exec` policy
|
||||
- host approvals-file policy
|
||||
@@ -34,7 +34,8 @@ Precedence is intentional:
|
||||
|
||||
- the host approvals file is the enforceable source of truth
|
||||
- requested `tools.exec` policy can narrow or broaden intent, but the effective result is still derived from the host rules
|
||||
- node output stays host-file-only because gateway `tools.exec` policy is applied later at runtime
|
||||
- `--node` combines the node host approvals file with gateway `tools.exec` policy, because both still apply at runtime
|
||||
- if gateway config is unavailable, the CLI falls back to the node approvals snapshot and notes that the final runtime policy could not be computed
|
||||
|
||||
## Replace approvals from a file
|
||||
|
||||
|
||||
@@ -153,6 +153,7 @@ describe("exec approvals CLI", () => {
|
||||
expect(callGatewayFromCli).toHaveBeenCalledWith("exec.approvals.node.get", expect.anything(), {
|
||||
nodeId: "node-1",
|
||||
});
|
||||
expect(callGatewayFromCli).toHaveBeenCalledWith("config.get", expect.anything(), {});
|
||||
expect(runtimeErrors).toHaveLength(0);
|
||||
});
|
||||
|
||||
@@ -251,6 +252,68 @@ describe("exec approvals CLI", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("adds combined node effective policy to json output", async () => {
|
||||
callGatewayFromCli.mockImplementation(
|
||||
async (method: string, _opts: unknown, params?: unknown) => {
|
||||
if (method === "config.get") {
|
||||
return {
|
||||
config: {
|
||||
tools: {
|
||||
exec: {
|
||||
security: "full",
|
||||
ask: "off",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
if (method === "exec.approvals.node.get") {
|
||||
return {
|
||||
path: "/tmp/node-exec-approvals.json",
|
||||
exists: true,
|
||||
hash: "hash-node-1",
|
||||
file: {
|
||||
version: 1,
|
||||
defaults: { security: "allowlist", ask: "always", askFallback: "deny" },
|
||||
agents: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
return { method, params };
|
||||
},
|
||||
);
|
||||
|
||||
await runApprovalsCommand(["approvals", "get", "--node", "macbook", "--json"]);
|
||||
|
||||
expect(defaultRuntime.writeJson).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
effectivePolicy: {
|
||||
note: "Effective exec policy is the node host approvals file intersected with gateway tools.exec policy.",
|
||||
scopes: [
|
||||
expect.objectContaining({
|
||||
scopeLabel: "tools.exec",
|
||||
security: expect.objectContaining({
|
||||
requested: "full",
|
||||
host: "allowlist",
|
||||
effective: "allowlist",
|
||||
}),
|
||||
ask: expect.objectContaining({
|
||||
requested: "off",
|
||||
host: "always",
|
||||
effective: "always",
|
||||
}),
|
||||
askFallback: expect.objectContaining({
|
||||
effective: "deny",
|
||||
source: "~/.openclaw/exec-approvals.json defaults.askFallback",
|
||||
}),
|
||||
}),
|
||||
],
|
||||
},
|
||||
}),
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps gateway approvals output when config.get fails", async () => {
|
||||
callGatewayFromCli.mockImplementation(
|
||||
async (method: string, _opts: unknown, params?: unknown) => {
|
||||
@@ -283,6 +346,38 @@ describe("exec approvals CLI", () => {
|
||||
expect(runtimeErrors).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("keeps node approvals output when gateway config is unavailable", async () => {
|
||||
callGatewayFromCli.mockImplementation(
|
||||
async (method: string, _opts: unknown, params?: unknown) => {
|
||||
if (method === "config.get") {
|
||||
throw new Error("gateway config unavailable");
|
||||
}
|
||||
if (method === "exec.approvals.node.get") {
|
||||
return {
|
||||
path: "/tmp/node-exec-approvals.json",
|
||||
exists: true,
|
||||
hash: "hash-node-1",
|
||||
file: { version: 1, agents: {} },
|
||||
};
|
||||
}
|
||||
return { method, params };
|
||||
},
|
||||
);
|
||||
|
||||
await runApprovalsCommand(["approvals", "get", "--node", "macbook", "--json"]);
|
||||
|
||||
expect(defaultRuntime.writeJson).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
effectivePolicy: {
|
||||
note: "Gateway config unavailable. Node output above shows host approvals state only, and final runtime policy still intersects with gateway tools.exec.",
|
||||
scopes: [],
|
||||
},
|
||||
}),
|
||||
0,
|
||||
);
|
||||
expect(runtimeErrors).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("keeps local approvals output when config load fails", async () => {
|
||||
readBestEffortConfig.mockRejectedValue(new Error("duplicate agent directories"));
|
||||
|
||||
|
||||
@@ -162,9 +162,6 @@ async function loadConfigForApprovalsTarget(params: {
|
||||
opts: ExecApprovalsCliOpts;
|
||||
source: ApprovalsTargetSource;
|
||||
}): Promise<OpenClawConfig | null> {
|
||||
if (params.source === "node") {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
if (params.source === "local") {
|
||||
return await readBestEffortConfig();
|
||||
@@ -217,9 +214,18 @@ function buildEffectivePolicyReport(params: {
|
||||
approvals: ExecApprovalsFile;
|
||||
}): EffectivePolicyReport {
|
||||
if (params.source === "node") {
|
||||
if (!params.cfg) {
|
||||
return {
|
||||
scopes: [],
|
||||
note: "Gateway config unavailable. Node output above shows host approvals state only, and final runtime policy still intersects with gateway tools.exec.",
|
||||
};
|
||||
}
|
||||
return {
|
||||
scopes: [],
|
||||
note: "Node output shows host approvals state only. Gateway tools.exec policy still intersects at runtime.",
|
||||
scopes: collectExecPolicySnapshots({
|
||||
cfg: params.cfg,
|
||||
approvals: params.approvals,
|
||||
}),
|
||||
note: "Effective exec policy is the node host approvals file intersected with gateway tools.exec policy.",
|
||||
};
|
||||
}
|
||||
if (!params.cfg) {
|
||||
|
||||
Reference in New Issue
Block a user