Files
openclaw/extensions/policy/src/doctor/review-required-repairs.ts
T
Peter Steinberger b080dd1e76 refactor: consolidate coercion contracts (#122458)
* refactor: consolidate coercion contracts

Centralize exact string, record, numeric, date, Boolean, argument, and structured-error coercions while preserving call-site semantics.

Migrate canonical-name collisions and deprecated internal SDK bypasses, deleting 55 net production/tooling lines. Expand declaration ownership enforcement to 101 allowed helpers and add a narrow export-completeness audit.

* fix: preserve standalone script coercions

Keep copied Control UI tooling self-contained and retain the trusted release harness module-relative source seam when the harness runs against an old target cwd.
2026-08-11 23:26:37 -07:00

136 lines
4.1 KiB
TypeScript

// Policy review-required repairs surface proposed config changes without applying them.
import type {
HealthFinding,
HealthRepairContext,
HealthRepairEffect,
HealthRepairResult,
} from "openclaw/plugin-sdk/health";
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
import { CHECK_IDS, type POLICY_CHECK_IDS } from "./check-ids.js";
import { POLICY_FIX_METADATA_BY_CHECK_ID } from "./fix-metadata.js";
type PolicyCheckId = (typeof POLICY_CHECK_IDS)[number];
const REVIEW_REQUIRED_REPAIR_CHECK_IDS = new Set<PolicyCheckId>([
CHECK_IDS.policyGatewayNonLoopbackBind,
CHECK_IDS.policyGatewayNodeCommandDenied,
]);
export function previewPolicyReviewRequiredRepair(
_ctx: HealthRepairContext,
findings: readonly HealthFinding[],
checkId: PolicyCheckId,
): Promise<HealthRepairResult> {
const metadata = POLICY_FIX_METADATA_BY_CHECK_ID.get(checkId);
if (!REVIEW_REQUIRED_REPAIR_CHECK_IDS.has(checkId) || metadata?.fixClass !== "reviewRequired") {
return Promise.resolve({
status: "skipped",
reason: "policy finding does not have a review-required repair preview",
changes: [],
});
}
if (
findings.length === 0 ||
findings.some(
(finding) =>
finding.checkId !== checkId ||
POLICY_FIX_METADATA_BY_CHECK_ID.get(finding.checkId)?.fixClass !== "reviewRequired",
)
) {
return Promise.resolve({
status: "skipped",
reason: "policy finding is not classified as review-required",
changes: [],
});
}
const previews = findings.flatMap((finding) => previewForFinding(finding, checkId));
if (previews.length === 0) {
return Promise.resolve({
status: "skipped",
reason: "policy review-required repair had no previewable config changes",
changes: [],
});
}
return Promise.resolve({
status: "skipped",
reason: "policy repair requires review before changing config",
changes: uniqueStrings(previews.map((preview) => preview.change)),
warnings: uniqueStrings(previews.map((preview) => preview.change)),
effects: uniqueEffects(previews.map((preview) => preview.effect)),
});
}
function previewForFinding(
finding: HealthFinding,
checkId: PolicyCheckId,
): readonly { readonly change: string; readonly effect: HealthRepairEffect }[] {
switch (checkId) {
case CHECK_IDS.policyGatewayNonLoopbackBind:
return previewGatewayLoopbackBind(finding);
case CHECK_IDS.policyGatewayNodeCommandDenied:
return previewGatewayNodeDenyCommand(finding);
default:
return [];
}
}
function previewGatewayLoopbackBind(
finding: HealthFinding,
): readonly { readonly change: string; readonly effect: HealthRepairEffect }[] {
if (
finding.ocPath !== "oc://openclaw.config/gateway/bind" &&
finding.ocPath !== "oc://openclaw.config/gateway/customBindHost"
) {
return [];
}
return [
{
change: "Review required: set gateway.bind=loopback for policy conformance.",
effect: {
kind: "config",
action: "would-set-after-review",
target: "gateway.bind=loopback",
dryRunSafe: true,
},
},
];
}
function previewGatewayNodeDenyCommand(
finding: HealthFinding,
): readonly { readonly change: string; readonly effect: HealthRepairEffect }[] {
const command = finding.message.match(/Gateway node command '([^']+)'/)?.[1]?.trim();
if (
command === undefined ||
command === "" ||
finding.ocPath !== "oc://openclaw.config/gateway/nodes/commands/deny"
) {
return [];
}
return [
{
change: `Review required: add ${command} to gateway.nodes.commands.deny for policy conformance.`,
effect: {
kind: "config",
action: "would-append-after-review",
target: `gateway.nodes.commands.deny += ${command}`,
dryRunSafe: true,
},
},
];
}
function uniqueEffects(values: readonly HealthRepairEffect[]): readonly HealthRepairEffect[] {
const seen = new Set<string>();
return values.filter((value) => {
const key = JSON.stringify(value);
if (seen.has(key)) {
return false;
}
seen.add(key);
return true;
});
}