plan --case: explicit reference-case render; WP-0005-T04 done; factor-issuer described

--allow-unimplemented (acknowledged type only, plan marked reference_case)
lets the accepted Forgejo case render; reviewed now requires a live survey.
WP-0005 lane applied and custody moved by the founder. keycape-factor-issuer
filled from net-kingdom, rotation gap attributed to railiance-platform.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 327203@bnt-lap001
Assistant-Session: 55028ae3-4914-4bd1-8f5c-815f3527aef0
This commit is contained in:
tegwick 2026-10-09 19:42:02 +02:00
parent c6eb701c3d
commit a3e97c8bd4
6 changed files with 71 additions and 20 deletions

View file

@ -58,8 +58,13 @@ def _issues(validator, issues) -> list[str]:
return [f"{i.path}: {i.message}" for i in issues if i.level == "ERROR"]
def render(case_path: Path, nk_root: Path = DEFAULT_NK_ROOT, types_root: Path | None = None) -> tuple[dict, dict, dict]:
"""Validate the case against its implemented type and render it. Returns (case, type, render)."""
def render(case_path: Path, nk_root: Path = DEFAULT_NK_ROOT, types_root: Path | None = None,
allow_unimplemented: bool = False) -> tuple[dict, dict, dict]:
"""Validate the case against its implemented type and render it. Returns (case, type, render).
allow_unimplemented mirrors net-kingdom's validator flag: only the reference case
that takes a type to `implemented` may render against an acknowledged type.
"""
v = load_validator(nk_root)
case = yaml.safe_load(Path(case_path).read_text()) or {}
types_root = types_root or nk_root / "security-case-types"
@ -72,7 +77,10 @@ def render(case_path: Path, nk_root: Path = DEFAULT_NK_ROOT, types_root: Path |
declarations, load_issues = v.composer.load_declarations(decls)
if v.has_errors(load_issues):
raise CasePlanError("type's provider declarations do not load", _issues(v, load_issues))
issues, rendered = v.render_case(case, type_data, declarations, require_implemented=True)
status = type_data.get("spec", {}).get("status")
if allow_unimplemented and status != "acknowledged":
raise CasePlanError(f"--allow-unimplemented applies only to an acknowledged type, not {status!r}")
issues, rendered = v.render_case(case, type_data, declarations, require_implemented=not allow_unimplemented)
if rendered is None:
raise CasePlanError("case refused", _issues(v, issues))
return case, type_data, rendered
@ -155,15 +163,20 @@ def plan_document(case: dict, type_data: dict, rendered: dict, structure: Struct
"credential_type": "security-case",
"derived_from_type": rendered["type"],
"render_digest": rendered["digest"],
"status": "reviewed" if acc and not collisions else "draft",
"status": "reviewed" if acc and not collisions and structure.source == "live" else "draft",
"case_acceptance": acc,
"reference_case": tspec.get("status") != "implemented",
"approved_by": None,
"approved_at": None,
}
lines = ["---", yaml.safe_dump(front, sort_keys=False).rstrip(), "---", "",
f"# Construction plan (derived-from-type): {cspec['id']}", "",
f"Rendered from `{rendered['type']}` ({tspec['title']}). Do not edit by hand:",
f"re-run `ops-mason plan --case` instead. Render digest `{rendered['digest']}`.", "",
f"re-run `ops-mason plan --case` instead. Render digest `{rendered['digest']}`.", ""]
if tspec.get("status") != "implemented":
lines += [f"**Reference case:** the type is `{tspec.get('status')}`, not yet `implemented`. This case",
"is the one whose live materialization takes the type to `implemented`.", ""]
lines += [
"## 1. Demand", "", tspec["purpose"].strip(), "",
"## 2. Existing-structure survey", "",
f"Survey source: {structure.source} (metadata only).", ""]
@ -191,8 +204,9 @@ def plan_document(case: dict, type_data: dict, rendered: dict, structure: Struct
def plan_from_case(case_path: Path, *, nk_root: Path = DEFAULT_NK_ROOT, types_root: Path | None = None,
structure: Structure | None = None, catalog: Path = DEFAULT_CATALOG) -> tuple[str, list[str], dict]:
case, type_data, rendered = render(case_path, nk_root, types_root)
structure: Structure | None = None, catalog: Path = DEFAULT_CATALOG,
allow_unimplemented: bool = False) -> tuple[str, list[str], dict]:
case, type_data, rendered = render(case_path, nk_root, types_root, allow_unimplemented)
if structure is None:
structure = live_structure(rendered, catalog)
collisions = review(rendered, structure)