From b22007061ae625ca04edfd07ac143e036a77acbf Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 25 Jul 2026 13:23:55 -0700 Subject: [PATCH] fix(policy): register shared network schema at runtime Signed-off-by: Carlos Villela --- package.json | 1 + src/lib/policy/sandbox-policy-validation.ts | 27 +++++++++++++++---- .../openshell-policy-boundary.test.ts | 3 +++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 371aa600cf..fa4c37d2ca 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "nemoclaw/openclaw.plugin.json", "nemoclaw/package.json", "nemoclaw-blueprint/", + "schemas/network-policy.schema.json", "schemas/sandbox-policy.schema.json", "scripts/", "docs/resources/local-credential-form.html", diff --git a/src/lib/policy/sandbox-policy-validation.ts b/src/lib/policy/sandbox-policy-validation.ts index 22301d341f..ad4f62cb20 100644 --- a/src/lib/policy/sandbox-policy-validation.ts +++ b/src/lib/policy/sandbox-policy-validation.ts @@ -10,6 +10,7 @@ import Ajv from "ajv/dist/2020.js"; import { parseOpenShellPolicy } from "./merge"; const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", ".."); +const NETWORK_POLICY_SCHEMA_PATH = path.join(PACKAGE_ROOT, "schemas", "network-policy.schema.json"); const SANDBOX_POLICY_SCHEMA_PATH = path.join(PACKAGE_ROOT, "schemas", "sandbox-policy.schema.json"); const MAX_SCHEMA_ERRORS = 3; const MAX_SCHEMA_ERROR_MESSAGE_CHARS = 120; @@ -20,13 +21,27 @@ let cachedSandboxPolicyValidator: ValidateFunction | null = null; function loadSandboxPolicyValidator(): ValidateFunction { if (cachedSandboxPolicyValidator) return cachedSandboxPolicyValidator; - let schema: AnySchemaObject; + let networkPolicySchema: AnySchemaObject; + let sandboxPolicySchema: AnySchemaObject; try { - const parsed: unknown = JSON.parse(fs.readFileSync(SANDBOX_POLICY_SCHEMA_PATH, "utf-8")); - if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + const parsedNetworkPolicySchema: unknown = JSON.parse( + fs.readFileSync(NETWORK_POLICY_SCHEMA_PATH, "utf-8"), + ); + const parsedSandboxPolicySchema: unknown = JSON.parse( + fs.readFileSync(SANDBOX_POLICY_SCHEMA_PATH, "utf-8"), + ); + if ( + !parsedNetworkPolicySchema || + typeof parsedNetworkPolicySchema !== "object" || + Array.isArray(parsedNetworkPolicySchema) || + !parsedSandboxPolicySchema || + typeof parsedSandboxPolicySchema !== "object" || + Array.isArray(parsedSandboxPolicySchema) + ) { throw new Error("schema root is not an object"); } - schema = parsed as AnySchemaObject; + networkPolicySchema = parsedNetworkPolicySchema as AnySchemaObject; + sandboxPolicySchema = parsedSandboxPolicySchema as AnySchemaObject; } catch { throw new Error( "Sandbox policy validation schema is unavailable from this NemoClaw installation.", @@ -34,7 +49,9 @@ function loadSandboxPolicyValidator(): ValidateFunction { } try { - const compiled = new Ajv({ allErrors: true, strict: false, $data: true }).compile(schema); + const ajv = new Ajv({ allErrors: true, strict: false, $data: true }); + ajv.addSchema(networkPolicySchema); + const compiled = ajv.compile(sandboxPolicySchema); cachedSandboxPolicyValidator = compiled; return compiled; } catch { diff --git a/test/package-contract/openshell-policy-boundary.test.ts b/test/package-contract/openshell-policy-boundary.test.ts index 639f7508a7..797088dbd3 100644 --- a/test/package-contract/openshell-policy-boundary.test.ts +++ b/test/package-contract/openshell-policy-boundary.test.ts @@ -214,6 +214,9 @@ describe("OpenShell policy boundary package contract", () => { expect(fs.existsSync(path.join(installedRoot, "schemas", "sandbox-policy.schema.json"))).toBe( true, ); + expect(fs.existsSync(path.join(installedRoot, "schemas", "network-policy.schema.json"))).toBe( + true, + ); expect( fs.existsSync( path.join(installedRoot, "dist", "lib", "policy", "sandbox-policy-validation.js"),