← Files LegalQuants TransactionalARCHIVED FILE
skills/diligence/scripts/validate_framework.py
10.9 KB · Oct 3, 2026 · 06:34 UTC
#!/usr/bin/env python3
# ruff: noqa: B904, E501, UP031 -- preserve historical validator diagnostics.
"""Validate a framework.json against framework.schema.json, stdlib only.
Schema enforcement is driven by the schema file itself: type, enum, const,
pattern, minLength, maxLength, minimum, maximum, minItems, maxItems,
required, properties, additionalProperties, items, and anyOf are applied
recursively, so schema edits keep working without code changes. Keywords
the checker does not implement are reported as a note, never silently
ignored into a false pass.
Semantic checks beyond the schema: lens_id unique across lenses, issue_id
unique across ALL lenses, overlap_owner references an existing lens_id,
framework_version >= 1, and approved must be false unless --allow-approved.
Usage:
python3 validate_framework.py --framework framework.json \
[--schema ../references/framework.schema.json] [--allow-approved]
Exit codes: 0 valid, 1 invalid, 2 I/O or unusable schema.
"""
import argparse
import json
import os
import re
import sys
DEFAULT_SCHEMA = os.path.normpath(
os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"..",
"references",
"framework.schema.json",
)
)
TYPE_CHECKS = {
"object": lambda v: isinstance(v, dict),
"array": lambda v: isinstance(v, list),
"string": lambda v: isinstance(v, str),
"integer": lambda v: isinstance(v, int) and not isinstance(v, bool),
"number": lambda v: isinstance(v, (int, float)) and not isinstance(v, bool),
"boolean": lambda v: isinstance(v, bool),
"null": lambda v: v is None,
}
# Keywords this checker enforces, plus pure metadata it may skip.
HANDLED = {
"type",
"enum",
"const",
"pattern",
"minLength",
"maxLength",
"minimum",
"maximum",
"minItems",
"maxItems",
"required",
"properties",
"additionalProperties",
"items",
"anyOf",
}
METADATA = {"$schema", "$id", "title", "description", "examples", "default"}
def type_name(value):
if value is None:
return "null"
if isinstance(value, bool):
return "boolean"
if isinstance(value, int):
return "integer"
if isinstance(value, float):
return "number"
return {dict: "object", list: "array", str: "string"}.get(
type(value), type(value).__name__
)
def json_equal(a, b):
"""Equality that keeps booleans apart from 0/1."""
if isinstance(a, bool) != isinstance(b, bool):
return False
return a == b
def check_schema(value, schema, path, errors, unknown):
"""Apply one schema node to value, appending error strings with JSON paths."""
for k in schema:
if k not in HANDLED and k not in METADATA:
unknown.add(k)
if "anyOf" in schema:
branch_fails = []
for branch in schema["anyOf"]:
trial = []
check_schema(value, branch, path, trial, unknown)
if not trial:
branch_fails = None
break
branch_fails.append(trial[0])
if branch_fails is not None:
errors.append(
"{}: no anyOf alternative matched ({})".format(
path, " | ".join(branch_fails)
)
)
if "type" in schema:
types = schema["type"]
if not isinstance(types, list):
types = [types]
if not any(TYPE_CHECKS[t](value) for t in types):
errors.append(
"{}: expected type {}, got {}".format(
path, "/".join(types), type_name(value)
)
)
return # remaining keywords assume the declared type
if "enum" in schema and not any(json_equal(value, o) for o in schema["enum"]):
errors.append("{}: {!r} is not one of {}".format(path, value, schema["enum"]))
if "const" in schema and not json_equal(value, schema["const"]):
errors.append(
"{}: must be exactly {!r}, got {!r}".format(path, schema["const"], value)
)
if isinstance(value, str):
if "pattern" in schema and not re.search(schema["pattern"], value):
errors.append(
"{}: {!r} does not match pattern {}".format(
path, value, schema["pattern"]
)
)
if "minLength" in schema and len(value) < schema["minLength"]:
errors.append(
"%s: string shorter than minLength %d" % (path, schema["minLength"])
)
if "maxLength" in schema and len(value) > schema["maxLength"]:
errors.append(
"%s: string longer than maxLength %d" % (path, schema["maxLength"])
)
if isinstance(value, (int, float)) and not isinstance(value, bool):
if "minimum" in schema and value < schema["minimum"]:
errors.append(
"{}: {!r} is below minimum {!r}".format(path, value, schema["minimum"])
)
if "maximum" in schema and value > schema["maximum"]:
errors.append(
"{}: {!r} is above maximum {!r}".format(path, value, schema["maximum"])
)
if isinstance(value, list):
if "minItems" in schema and len(value) < schema["minItems"]:
errors.append(
"%s: array has %d item(s), minItems is %d"
% (path, len(value), schema["minItems"])
)
if "maxItems" in schema and len(value) > schema["maxItems"]:
errors.append(
"%s: array has %d item(s), maxItems is %d"
% (path, len(value), schema["maxItems"])
)
if "items" in schema:
for i, elem in enumerate(value):
check_schema(
elem, schema["items"], "%s[%d]" % (path, i), errors, unknown
)
if isinstance(value, dict):
props = schema.get("properties", {})
for req in schema.get("required", []):
if req not in value:
errors.append(f"{path}: missing required property '{req}'")
for k, v in value.items():
if k in props:
check_schema(v, props[k], f"{path}.{k}", errors, unknown)
elif "additionalProperties" in schema:
ap = schema["additionalProperties"]
if ap is False:
errors.append(f"{path}: unexpected property '{k}'")
elif isinstance(ap, dict):
check_schema(v, ap, f"{path}.{k}", errors, unknown)
return errors
def semantic_errors(fw, allow_approved=False):
"""Cross-field checks the schema language cannot express."""
errors = []
if not isinstance(fw, dict):
return errors
v = fw.get("framework_version")
if isinstance(v, int) and not isinstance(v, bool) and v < 1:
errors.append("$.framework_version: must be >= 1, got %d" % v)
if fw.get("approved") is True and not allow_approved:
errors.append(
"$.approved: must be false before Gate 2 approval "
"(pass --allow-approved to accept an approved framework)"
)
lenses = fw.get("lenses")
if not isinstance(lenses, list):
return errors
lens_ids = {}
issue_ids = {}
for i, lens in enumerate(lenses):
if not isinstance(lens, dict):
continue
lid = lens.get("lens_id")
lpath = "$.lenses[%d]" % i
if isinstance(lid, str):
if lid in lens_ids:
errors.append(
f"{lpath}.lens_id: duplicate lens_id {lid!r} (first used at {lens_ids[lid]})"
)
else:
lens_ids[lid] = lpath
items = lens.get("items")
if not isinstance(items, list):
continue
for j, item in enumerate(items):
if not isinstance(item, dict):
continue
iid = item.get("issue_id")
ipath = "%s.items[%d]" % (lpath, j)
if isinstance(iid, str):
if iid in issue_ids:
errors.append(
f"{ipath}.issue_id: duplicate issue_id {iid!r} (first used at {issue_ids[iid]}); "
"issue_id must be unique across all lenses"
)
else:
issue_ids[iid] = ipath
for i, lens in enumerate(lenses):
if not isinstance(lens, dict):
continue
for j, item in enumerate(lens.get("items") or []):
if not isinstance(item, dict):
continue
owner = item.get("overlap_owner")
if owner is not None and isinstance(owner, str) and owner not in lens_ids:
errors.append(
"$.lenses[%d].items[%d].overlap_owner: %r is not a "
"lens_id defined in this framework" % (i, j, owner)
)
return errors
def validate(framework, schema, allow_approved=False):
"""Full validation. Returns (errors, unknown_keywords)."""
errors = []
unknown = set()
check_schema(framework, schema, "$", errors, unknown)
errors.extend(semantic_errors(framework, allow_approved))
return errors, unknown
def load_json(path, kind):
"""Load a JSON file or raise ValueError with a readable message."""
try:
with open(path, encoding="utf-8") as f:
return json.load(f)
except (OSError, json.JSONDecodeError) as e:
raise ValueError(f"cannot read {kind} at {path}: {e}")
def main():
ap = argparse.ArgumentParser(
description="Validate framework.json against framework.schema.json."
)
ap.add_argument("--framework", required=True, help="framework.json to validate.")
ap.add_argument(
"--schema",
default=DEFAULT_SCHEMA,
help=f"Schema file (default: {DEFAULT_SCHEMA}).",
)
ap.add_argument(
"--allow-approved",
action="store_true",
help="Accept approved: true (post-Gate 2 use).",
)
args = ap.parse_args()
try:
framework = load_json(args.framework, "framework")
schema = load_json(args.schema, "schema")
except ValueError as e:
print(f"FATAL: {e}", file=sys.stderr)
sys.exit(2)
errors, unknown = validate(framework, schema, args.allow_approved)
if unknown:
print(
"note: schema uses keyword(s) this checker does not enforce: {}".format(
", ".join(sorted(unknown))
),
file=sys.stderr,
)
if errors:
print("INVALID: %s (%d error(s))" % (args.framework, len(errors)))
for e in errors:
print(" " + e)
sys.exit(1)
n_items = sum(len(lens.get("items", [])) for lens in framework.get("lenses", []))
print(
"OK: %s is valid (%d lens(es), %d item(s), version %s)"
% (
args.framework,
len(framework.get("lenses", [])),
n_items,
framework.get("framework_version"),
)
)
if __name__ == "__main__":
main()
SHA-256: ab30fadc2143fb5eb10b0159fcba237c50321735bb00423380f2f24e0ccbda77