openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
452 lines (451 loc) • 22.1 kB
JavaScript
import { n as MAX_TIMER_TIMEOUT_MS } from "./number-coercion-CkhMiRw8.mjs";
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { r as defaultRuntime } from "./runtime-BRg2NubB.mjs";
import { t as formatCliCommand } from "./command-format-DwEdrDKL.mjs";
import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs";
import { i as listAgentIds } from "./agent-roster-DH6r_8f4.mjs";
import { d as resolveAgentWorkspaceDir, g as resolveDefaultAgentId } from "./agent-scope-config-BEk6-ThS.mjs";
import { c as readConfigFileSnapshot } from "./io.runtime-B134MOMz.mjs";
import "./string-coerce-runtime-BbIJhy7G.mjs";
import "./number-runtime-D3v2w3D4.mjs";
import "./routing-DvCc0aVb.mjs";
import "./agent-scope-runtime-OJ73CMLc.mjs";
import "./runtime-zz0jl_-f.mjs";
import { i as parseHealthFindingSeverity, n as healthFindingMeetsSeverity } from "./health-checks-_h8bcnIK.mjs";
import { t as exitCodeFromFindings } from "./doctor-lint-flow-ImPAIMVU.mjs";
import "./health-z0EzzE-W.mjs";
import "./setup-tools-w-mO5UXy.mjs";
import { _ as POLICY_RULE_METADATA, a as scopedPolicyFields, c as policyRuleValueIsValid, g as POLICY_FIX_METADATA_BY_CHECK_ID, i as normalizePolicySelectorValues, l as createPolicyAttestation, m as getPolicyPath, n as toAttestedFinding, o as scopedPolicyOverlays, p as ocPathSegment, r as policyContainerShapeFindings, s as isPolicyValueAtLeastAsStrict, t as evaluatePolicy, y as POLICY_CHECK_IDS } from "./evaluation-Cxe0aZKk.mjs";
import { promises } from "node:fs";
import { basename, isAbsolute, resolve } from "node:path";
import { setTimeout } from "node:timers/promises";
import JSON5 from "json5";
//#region extensions/policy/src/policy-conformance.ts
const POLICY_CONFORMANCE_CHECK_IDS = {
missing: "policy/policy-conformance-missing",
weaker: "policy/policy-conformance-weaker",
invalid: "policy/policy-conformance-invalid"
};
async function buildPolicyConformanceReport(params) {
const baselinePath = resolvePolicyPath(params.baselinePath);
const policyPath = resolvePolicyPath(params.policyPath);
const baselineResult = await readPolicyDocument(baselinePath);
const policyResult = await readPolicyDocument(policyPath);
const report = (findings, rulesChecked = 0) => ({
ok: findings.length === 0,
baselinePath: baselineResult.displayName,
policyPath: policyResult.displayName,
rulesChecked,
findings
});
if (!baselineResult.ok || !policyResult.ok) return report([baselineResult, policyResult].filter((result) => {
return !result.ok;
}).map((result) => invalidConformanceFinding(result.displayName, result.message, result.target, `Fix ${result.displayName} so it contains valid policy JSONC.`)));
const baseline = baselineResult;
const policy = policyResult;
const baselineClaims = collectPolicyRuleClaims(baseline);
const candidateClaims = collectPolicyRuleClaims(policy);
const documents = [baseline, policy];
const invalidFindings = uniqueConformanceFindings([
...documents.flatMap((document) => policyContainerShapeFindings(document.value, document.displayName, document.displayName).map((finding) => invalidConformanceFinding(document.displayName, finding.message, finding.target ?? `oc://${document.displayName}`, finding.fixHint ?? `Fix ${document.displayName} so it uses the documented policy syntax.`))),
...documents.flatMap(collectInvalidScopedPolicyFindings),
...[[baseline, baselineClaims], [policy, candidateClaims]].flatMap(([document, claims]) => claims.filter((claim) => !policyRuleValueIsValid(claim.metadata, claim.value)).map((claim) => invalidConformanceFinding(document.displayName, `${document.displayName} ${claim.propertyPath} is not valid policy conformance syntax.`, claim.target, `Fix ${claim.propertyPath} so it uses the documented policy syntax.`)))
]);
if (invalidFindings.length > 0) return report(invalidFindings);
return report(baselineClaims.map((claim) => conformanceFinding(claim, candidateClaims, policy.displayName)).filter((finding) => finding !== void 0), baselineClaims.length);
}
function uniqueConformanceFindings(findings) {
const seen = /* @__PURE__ */ new Set();
return findings.filter((finding) => {
const key = `${finding.checkId}\n${finding.target}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
function collectInvalidScopedPolicyFindings(document) {
if (!isRecord(document.value) || document.value.scopes === void 0) return [];
if (!isRecord(document.value.scopes)) return [invalidConformanceFinding(document.displayName, `${document.displayName} scopes must be an object.`, `oc://${document.displayName}/scopes`, "Fix scopes so it uses the documented policy syntax.")];
const findings = [];
for (const [scopeName, overlay] of Object.entries(document.value.scopes)) {
const scopePath = `scopes.${scopeName}`;
const scopeTarget = `oc://${document.displayName}/scopes/${ocPathSegment(scopeName)}`;
if (!isRecord(overlay)) {
findings.push(invalidConformanceFinding(document.displayName, `${document.displayName} ${scopePath} must be an object.`, scopeTarget, `Fix ${scopePath} so it uses the documented policy syntax.`));
continue;
}
for (const metadata of POLICY_RULE_METADATA) {
if (getPolicyPath(overlay, metadata.policyPath) === void 0) continue;
if ((metadata.scopeSelectors ?? []).some((selector) => normalizePolicySelectorValues(overlay[selector], selector).length > 0)) continue;
const propertyPath = `${scopePath}.${metadata.policyPath.join(".")}`;
findings.push(invalidConformanceFinding(document.displayName, `${document.displayName} ${propertyPath} needs a valid selector for policy conformance.`, `${scopeTarget}/${metadata.policyPath.map(ocPathSegment).join("/")}`, `Fix ${propertyPath} so it uses the documented policy syntax.`));
}
}
return findings;
}
function invalidConformanceFinding(displayName, message, target, fixHint) {
return {
checkId: POLICY_CONFORMANCE_CHECK_IDS.invalid,
severity: "error",
message,
source: "policy",
path: displayName,
target,
requirement: target,
fixHint
};
}
function conformanceFinding(baseline, candidateClaims, policyDisplayName) {
if (baselineRuleIsNoOp(baseline.metadata, baseline.value)) return;
const exactCandidates = candidateClaims.filter((candidate) => candidate.key === baseline.key);
const candidates = baseline.selector === void 0 || exactCandidates.length > 0 ? exactCandidates : candidateClaims.filter((candidate) => candidate.selector === void 0 && candidate.metadata === baseline.metadata);
if (candidates.length === 0) return unsatisfiedConformanceFinding(baseline, policyDisplayName);
const satisfies = (candidate) => isPolicyValueAtLeastAsStrict(baseline.metadata, candidate.value, baseline.value);
if (baseline.selector !== void 0 && exactCandidates.length === 0 && candidates.some(satisfies)) return;
const weaker = candidates.find((candidate) => !satisfies(candidate));
if (weaker !== void 0) return unsatisfiedConformanceFinding(baseline, policyDisplayName, weaker);
if (baseline.selector === void 0) {
const weakerScopedOverride = candidateClaims.find((candidate) => candidate.selector !== void 0 && candidate.metadata === baseline.metadata && !satisfies(candidate));
if (weakerScopedOverride !== void 0) return unsatisfiedConformanceFinding(baseline, policyDisplayName, weakerScopedOverride);
}
}
function baselineRuleIsNoOp(metadata, baseline) {
switch (metadata.strictness) {
case "allowlist-subset": return metadata.emptyList === "disabled" && policyRuleListIsEmpty(baseline);
case "denylist-superset":
case "routing-probes": return policyRuleListIsEmpty(baseline);
case "requires-true": return baseline !== true;
case "requires-false": return baseline !== false;
case "exact-list":
case "ordered-string": return false;
}
return false;
}
function policyRuleListIsEmpty(value) {
return Array.isArray(value) && value.length === 0;
}
function unsatisfiedConformanceFinding(baseline, policyDisplayName, candidate) {
return {
checkId: candidate === void 0 ? POLICY_CONFORMANCE_CHECK_IDS.missing : POLICY_CONFORMANCE_CHECK_IDS.weaker,
severity: "error",
message: candidate === void 0 ? `${policyDisplayName} is missing ${baseline.propertyPath}.` : `${policyDisplayName} ${baseline.propertyPath} is weaker than the baseline policy.`,
source: "policy",
path: policyDisplayName,
target: candidate?.target ?? `oc://${policyDisplayName}/${baseline.propertyPath.replaceAll(".", "/")}`,
requirement: baseline.target,
fixHint: candidate === void 0 ? `Add an equally or more restrictive ${baseline.propertyPath} rule, or update the baseline policy after review.` : `Use an equally or more restrictive ${baseline.propertyPath} value, or update the baseline policy after review.`
};
}
function collectPolicyRuleClaims(document) {
return [...collectTopLevelPolicyRuleClaims(document), ...collectScopedPolicyRuleClaims(document)];
}
function collectTopLevelPolicyRuleClaims(document) {
const claims = [];
for (const metadata of POLICY_RULE_METADATA) {
const value = getPolicyPath(document.value, metadata.policyPath);
if (value === void 0) continue;
const propertyPath = metadata.policyPath.join(".");
claims.push({
key: `global:${propertyPath}`,
metadata,
value,
target: `oc://${document.displayName}/${metadata.policyPath.map(ocPathSegment).join("/")}`,
propertyPath
});
}
return claims;
}
function collectScopedPolicyRuleClaims(document) {
const claims = [];
for (const [scopeName, overlay] of scopedPolicyOverlays(document.value)) for (const selector of ["agentIds", "channelIds"]) {
const selectorValues = normalizePolicySelectorValues(overlay[selector], selector);
if (selectorValues.length === 0) continue;
for (const { metadata, value, propertyPath, targetPath } of scopedPolicyFields(scopeName, overlay, selector)) for (const selectorValue of selectorValues) claims.push({
key: `${selector}:${selectorValue}:${propertyPath}`,
metadata,
value,
target: `oc://${document.displayName}/${targetPath}`,
propertyPath: `scopes.${scopeName}.${propertyPath}`,
selector: {
kind: selector,
value: selectorValue
}
});
}
return coalesceScopedPolicyRuleClaims(claims);
}
function coalesceScopedPolicyRuleClaims(claims) {
const byKey = /* @__PURE__ */ new Map();
for (const claim of claims) {
const previous = byKey.get(claim.key);
if (previous === void 0 || isPolicyValueAtLeastAsStrict(previous.metadata, claim.value, previous.value)) byKey.set(claim.key, claim);
}
return [...byKey.values()];
}
async function readPolicyDocument(path) {
const displayName = basename(path);
let operation = "read";
try {
const raw = await promises.readFile(path, "utf-8");
operation = "parsed";
return {
ok: true,
displayName,
value: JSON5.parse(raw)
};
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
return {
ok: false,
displayName,
message: `${displayName} could not be ${operation}: ${message}`,
target: `oc://${displayName}`
};
}
}
function resolvePolicyPath(path) {
return isAbsolute(path) ? path : resolve(path);
}
//#endregion
//#region extensions/policy/src/cli.ts
function registerPolicyCli(program) {
const policy = program.command("policy").description("Verify workspace policy conformance");
policy.command("compare").description("Compare policy.jsonc against an authored baseline policy file").requiredOption("--baseline <path>", "Baseline policy file to compare against").option("--policy <path>", "Policy file to check; defaults to configured policy path").option("--agent <id>", "Agent id for relative policy workspace paths").option("--json", "Emit JSON output").action(async (options) => {
process.exitCode = await runPolicyCommand(async () => {
if (options.baseline === void 0 || options.baseline.trim() === "") throw new Error("Missing required --baseline value.");
const policyPath = await policyCompareCandidatePath(options);
const report = await buildPolicyConformanceReport({
baselinePath: options.baseline,
policyPath
});
writePolicyConformanceReport(report, options);
return report.ok ? 0 : 1;
});
});
policy.command("check").description("Check policy requirements and emit an audit attestation").option("--agent <id>", "Agent id (required when multiple agents are configured)").option("--json", "Emit JSON output").option("--severity-min <severity>", "Minimum severity: info, warning, or error").action(async (options) => {
process.exitCode = await runPolicyCommand(async () => {
const report = await buildPolicyCheckReport(options, "policy check");
writePolicyCheckReport(report, options);
return report.exitCode;
});
});
policy.command("watch").description("Watch policy evidence and report accepted-attestation drift").option("--agent <id>", "Agent id (required when multiple agents are configured)").option("--json", "Emit JSON output").option("--severity-min <severity>", "Minimum severity: info, warning, or error").option("--interval-ms <ms>", "Polling interval in milliseconds").option("--once", "Run one watch evaluation and exit").action(async (options) => {
process.exitCode = await runPolicyCommand(async () => {
const intervalMs = normalizeWatchIntervalMs(options.intervalMs);
let previousKey;
for (;;) {
const report = await buildPolicyCheckReport(options, "policy watch");
const status = policyWatchStatus(report);
const key = `${status}:${report.attestation?.attestationHash ?? ""}:${report.exitCode}`;
if (previousKey === void 0 || previousKey !== key || options.once === true) {
writePolicyWatchReport(report, status, options);
previousKey = key;
}
if (options.once === true) return status === "stale" ? 1 : report.exitCode;
await setTimeout(intervalMs);
}
});
});
}
async function runPolicyCommand(run) {
try {
return await run();
} catch (err) {
defaultRuntime.error(err instanceof Error ? err.message : String(err));
return 2;
}
}
async function buildPolicyCheckReport(options, ownerSurface) {
const severityMin = options.severityMin === void 0 ? "info" : parseHealthFindingSeverity(options.severityMin);
if (severityMin === null) throw new Error("Invalid --severity-min value. Expected one of: info, warning, error.");
const snapshot = await readConfigFileSnapshot({ observe: false });
if (!snapshot.valid) {
const visibleFindings = snapshot.issues.map((issue) => ({
checkId: "policy/config-invalid",
severity: "error",
message: issue.message,
source: "policy",
path: issue.path
})).filter((finding) => healthFindingMeetsSeverity(finding, severityMin));
return {
ok: visibleFindings.length === 0,
evidence: { channels: [] },
checksRun: 1,
checksSkipped: POLICY_CHECK_IDS.length,
findings: visibleFindings.map(toJsonFinding),
exitCode: visibleFindings.length === 0 ? 0 : 1
};
}
const cfg = policyCommandConfig(snapshot.config);
const ctx = {
mode: "lint",
runtime: {
log(value) {
process.stdout.write(`${String(value)}\n`);
},
error(value) {
defaultRuntime.error(String(value));
},
exit(code) {
process.exitCode = code;
}
},
cfg,
cwd: resolveAgentWorkspaceDir(cfg, resolvePolicyCommandAgentId(cfg, options.agent, ownerSurface)),
...snapshot.path !== void 0 ? { configPath: snapshot.path } : {}
};
const evaluation = await evaluatePolicy(ctx);
const jsonFindings = evaluation.findings.filter((finding) => healthFindingMeetsSeverity(finding, severityMin)).map(toJsonFinding);
const attestedFindings = evaluation.attestedFindings.map(toAttestedJsonFinding);
const exitCode = exitCodeFromFindings(evaluation.findings, severityMin);
const attestation = createPolicyAttestation({
ok: evaluation.attestedFindings.length === 0,
checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
policyPath: evaluation.policyPath,
policyHash: evaluation.policy?.hash,
evidence: evaluation.evidence,
findings: attestedFindings
});
return {
ok: exitCode === 0,
attestation,
evidence: evaluation.evidence,
checksRun: POLICY_CHECK_IDS.length,
checksSkipped: 0,
findings: jsonFindings,
expectedAttestationHash: evaluation.expectedAttestationHash,
exitCode
};
}
function policyCommandConfig(cfg) {
return {
...cfg,
plugins: {
...cfg.plugins,
entries: {
...cfg.plugins?.entries,
policy: {
...cfg.plugins?.entries?.["policy"],
enabled: true,
config: {
enabled: true,
...typeof cfg.plugins?.entries?.["policy"]?.config === "object" && cfg.plugins.entries["policy"].config !== null ? cfg.plugins.entries["policy"].config : {}
}
}
}
}
};
}
function resolvePolicyCommandAgentId(cfg, rawAgentId, surface) {
const requestedAgentId = rawAgentId?.trim();
if (rawAgentId !== void 0 && !requestedAgentId) throw new Error("--agent must not be blank");
if (requestedAgentId) {
const agentId = normalizeAgentId(requestedAgentId);
if (!listAgentIds(cfg).includes(agentId)) throw new Error(`Unknown agent id "${requestedAgentId}". Run ${formatCliCommand("openclaw agents list")} to see configured agents.`);
return agentId;
}
return resolveDefaultAgentId(cfg, {
surface,
hint: "Pass --agent <id>."
});
}
async function policyCompareCandidatePath(options) {
if (options.policy !== void 0 && options.policy.trim() !== "") return options.policy.trim();
const snapshot = await readConfigFileSnapshot({ observe: false });
if (!snapshot.valid) return "policy.jsonc";
const pluginConfig = snapshot.config.plugins?.entries?.["policy"]?.config;
const configured = typeof pluginConfig === "object" && pluginConfig !== null && "path" in pluginConfig ? pluginConfig.path : void 0;
const policyPath = typeof configured === "string" && configured.trim() !== "" ? configured.trim() : "policy.jsonc";
if (isAbsolute(policyPath)) return policyPath;
const cwd = resolveAgentWorkspaceDir(snapshot.config, resolvePolicyCommandAgentId(snapshot.config, options.agent, "policy compare"));
return resolve(cwd, policyPath);
}
function writePolicyCheckReport(report, options) {
if (options.json === true || !process.stdout.isTTY) process.stdout.write(JSON.stringify({
ok: report.ok,
attestation: report.attestation,
evidence: report.evidence,
checksRun: report.checksRun,
checksSkipped: report.checksSkipped,
findings: report.findings
}) + "\n");
else if (report.findings.length === 0) {
const policyHash = report.attestation?.policy?.hash ?? "missing";
const evidenceHash = report.attestation?.workspace.hash ?? "unavailable";
process.stdout.write(`policy check: no findings (policy ${policyHash}, evidence ${evidenceHash})\n`);
} else {
process.stdout.write(`policy check: ${report.findings.length} finding(s)\n`);
for (const finding of report.findings) {
const where = finding.path !== void 0 ? ` ${finding.path}` : "";
const line = finding.line !== void 0 ? `:${finding.line}` : "";
process.stdout.write(` [${finding.severity}] ${finding.checkId}${where}${line} - ${finding.message}\n`);
}
}
}
function writePolicyConformanceReport(report, options) {
if (options.json === true || !process.stdout.isTTY) {
process.stdout.write(JSON.stringify(report) + "\n");
return;
}
if (report.findings.length === 0) {
process.stdout.write(`policy compare: no findings (${report.policyPath} is at least as strict as ${report.baselinePath}; ${report.rulesChecked} rule(s) checked)\n`);
return;
}
process.stdout.write(`policy compare: ${report.findings.length} finding(s) (${report.rulesChecked} rule(s) checked)\n`);
for (const finding of report.findings) process.stdout.write(` [${finding.severity}] ${finding.checkId} - ${finding.message}\n`);
}
function writePolicyWatchReport(report, status, options) {
if (options.json === true || !process.stdout.isTTY) {
process.stdout.write(JSON.stringify({
status,
ok: report.ok,
expectedAttestationHash: report.expectedAttestationHash,
attestation: report.attestation,
findings: report.findings
}) + "\n");
return;
}
if (status === "stale") {
process.stdout.write(`policy watch: accepted attestation is stale (current ${report.attestation?.attestationHash}, expected ${report.expectedAttestationHash}). Review policy check output, then update the supervisor/gateway accepted attestation.\n`);
return;
}
if (status === "findings") {
process.stdout.write(`policy watch: ${report.findings.length} finding(s); accepted attestation cannot be updated until policy check is clean.\n`);
return;
}
process.stdout.write(`policy watch: clean (attestation ${report.attestation?.attestationHash}, evidence ${report.attestation?.workspace.hash})\n`);
}
function policyWatchStatus(report) {
if (!report.ok && report.findings.some((finding) => finding.checkId !== "policy/attestation-hash-mismatch")) return "findings";
const expected = report.expectedAttestationHash?.trim();
if (expected && report.attestation !== void 0 && report.attestation.attestationHash !== expected) return "stale";
return report.ok ? "clean" : "findings";
}
function normalizeWatchIntervalMs(value) {
if (value === void 0) return 2e3;
const raw = typeof value === "number" ? value : /^\+?\d+$/.test(value.trim()) ? Number(value.trim()) : NaN;
if (!Number.isSafeInteger(raw) || raw < 250) throw new Error("--interval-ms must be an integer >= 250.");
return Math.min(raw, MAX_TIMER_TIMEOUT_MS);
}
function toAttestedJsonFinding(finding) {
const { column: _column, ...json } = toAttestedFinding(finding);
return json;
}
function toJsonFinding(finding) {
return {
...toAttestedJsonFinding(finding),
...policyFindingMetadata(finding)
};
}
function policyFindingMetadata(finding) {
const metadata = POLICY_FIX_METADATA_BY_CHECK_ID.get(finding.checkId);
if (metadata === void 0) return {};
return { policy: { fixRecommendation: {
fixClass: metadata.fixClass,
...metadata.policyPath !== void 0 ? { policyPath: metadata.policyPath } : {},
...metadata.configTargets !== void 0 ? { configTargets: metadata.configTargets } : {},
summary: metadata.summary
} } };
}
//#endregion
export { registerPolicyCli };