fix(policy): Seam A require description + all four config action-lists (guard finding)

Guard SEAM A NOT-CLEAR: validatePolicySpec returned ok:true for a spec with NO description
and for a config-surface spec omitting all four action-list fields — an advisory false-green
for incomplete custom specs, contrary to the schema (description required; config requires
default_posture + allow/ask/deny/destructive_class all present). Minimal correction (RED-first):
require description as a non-empty string; require all four config action-list fields present
as string arrays (use [] for none). 3 new RED pins proven (RED before fix); all four
authoritative built-ins still validate (each carries description + its required fields); parse/
body/serialization/A-only scope/fences unchanged. 14/14 GREEN; daemon tsc 0.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
v-openrig-buildandClaude Opus 4.8 committed 2026-08-04 05:34:14 +00:00
1 parent b240b8cf45
commit 4694e86d9d
2 files changed
+20 -1

No files matched your search

@@ -71,6 +71,7 @@ export function validatePolicySpec(fm: Record<string, unknown>): ValidationResul
if (fm["policy_schema_version"] !== 1) errors.push("policy_schema_version must be the number 1");
if (typeof fm["name"] !== "string" || (fm["name"] as string).length === 0) errors.push("name must be a non-empty string");
if (typeof fm["source"] !== "string" || !SOURCES.has(fm["source"] as string)) errors.push("source must be 'builtin' or 'custom'");
if (typeof fm["description"] !== "string" || (fm["description"] as string).length === 0) errors.push("description must be a non-empty string");
const surface = fm["surface"];
if (typeof surface !== "string" || !SURFACES.has(surface)) {
@@ -86,8 +87,10 @@ export function validatePolicySpec(fm: Record<string, unknown>): ValidationResul
// config
if (fm["launch_posture"] !== undefined) errors.push("config-surface policy must NOT carry launch_posture (flag-surface field)");
if (!DEFAULT_POSTURES.has(fm["default_posture"] as string)) errors.push("config-surface policy requires default_posture ('allow', 'ask' or 'deny')");
// The schema requires all four action-list fields present (use [] for none) — not optional.
for (const f of ACTION_LIST_FIELDS) {
if (fm[f] !== undefined && !isStringList(fm[f])) errors.push(`${f} must be a list of strings`);
if (fm[f] === undefined) errors.push(`config-surface policy requires '${f}' (a list of strings; use [] for none)`);
else if (!isStringList(fm[f])) errors.push(`${f} must be a list of strings`);
}
}
+16
View File
@@ -87,6 +87,22 @@ describe("validatePolicySpec — advisory, fail-open, surface-appropriate", () =
expect(validatePolicySpec({ source: "builtin", name: "x", surface: "sideways", policy_schema_version: 1 }).ok).toBe(false);
expect(validatePolicySpec({ source: "builtin", name: "x", surface: "config", default_posture: "maybe", policy_schema_version: 1 }).ok).toBe(false);
});
it("missing description → error (required field)", () => {
const r = validatePolicySpec({ source: "builtin", name: "x", surface: "flag", launch_posture: "floor", policy_schema_version: 1 });
expect(r.ok).toBe(false);
expect(r.errors.some((e) => e.includes("description"))).toBe(true);
});
it("non-string description → error", () => {
const r = validatePolicySpec({ source: "builtin", name: "x", surface: "flag", launch_posture: "floor", policy_schema_version: 1, description: 42 });
expect(r.ok).toBe(false);
expect(r.errors.some((e) => e.includes("description"))).toBe(true);
});
it("config policy MISSING an action list (destructive_class) → error (all four required)", () => {
const r = validatePolicySpec({ source: "builtin", name: "x", surface: "config", description: "d", default_posture: "allow", policy_schema_version: 1, allow: [], ask: [], deny: [] });
expect(r.ok).toBe(false);
expect(r.errors.some((e) => e.includes("destructive_class"))).toBe(true);
});
});
describe("round-trip — canonical serialize stability + semantic equality + body preservation", () => {