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:
parent
c6eb701c3d
commit
a3e97c8bd4
6 changed files with 71 additions and 20 deletions
|
|
@ -311,18 +311,21 @@ platform/workloads/keycape/secrets-engine-openbao:
|
|||
- CCR-2026-0034.yaml
|
||||
used_by: sso/keycape-secrets-engine-openbao-client key client-secret; issuer-side delivery only
|
||||
platform/workloads/net-kingdom/keycape-factor-issuer:
|
||||
description: ''
|
||||
description: Renewable login (USERNAME, PASSWORD, REQUEST) of the local privacyIDEA service admin keycape-factor-reader,
|
||||
permission tokenlist for realm coulomb only. Used to obtain the short-lived factor-read JWT. Not the
|
||||
JWT KeyCape uses, not a privacyIDEA admin credential (pi-admin is operators/privacyidea/pi-admin).
|
||||
missing:
|
||||
- description
|
||||
- used_by
|
||||
- rotation
|
||||
- on_loss
|
||||
on_loss: ''
|
||||
on_loss: 'Resettable by a privacyIDEA administrator via attended pi-admin custody: new password for
|
||||
keycape-factor-reader, CAS-write here. Cost: renewal stops; JWT valid up to 2h, then coulomb MFA lookup
|
||||
fails closed until restored.'
|
||||
owner: railiance-platform
|
||||
review: draft
|
||||
rotation: ''
|
||||
sources: []
|
||||
used_by: ''
|
||||
sources:
|
||||
- net-kingdom reply 75b48030 (2026-10-09); rotation is a railiance-platform gap
|
||||
used_by: CronJob sso/keycape-factor-renewer (own SA, every 10 min, 5-min OpenBao session); writes keycape-factor-read.
|
||||
The keycape-factor-read ESO reader is denied this path.
|
||||
platform/workloads/net-kingdom/keycape-factor-read:
|
||||
description: Deliver only a provider-issued coulomb factor-read JWT to KeyCape, separating it from issuer
|
||||
credentials and signing keys.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ def _parser() -> argparse.ArgumentParser:
|
|||
plan.add_argument("--no-live", action="store_true",
|
||||
help="skip the live OpenBao survey (marks the plan's survey source 'none')")
|
||||
plan.add_argument("--netkingdom-root", type=Path, default=None)
|
||||
plan.add_argument("--allow-unimplemented", action="store_true",
|
||||
help="reference case only: render against an acknowledged type")
|
||||
return parser
|
||||
|
||||
|
||||
|
|
@ -55,7 +57,8 @@ def _plan(args) -> int:
|
|||
nk_root = args.netkingdom_root or case_plan.DEFAULT_NK_ROOT
|
||||
try:
|
||||
doc, collisions, rendered = case_plan.plan_from_case(
|
||||
args.case, nk_root=nk_root, structure=case_plan.Structure() if args.no_live else None)
|
||||
args.case, nk_root=nk_root, structure=case_plan.Structure() if args.no_live else None,
|
||||
allow_unimplemented=args.allow_unimplemented)
|
||||
except case_plan.CasePlanError as exc:
|
||||
print(json.dumps({"ok": False, "error": str(exc), "issues": exc.issues}), file=sys.stderr)
|
||||
return 2
|
||||
|
|
|
|||
|
|
@ -53,9 +53,11 @@ def test_renders_reference_case_as_derived_plan(tmp_path, types_root):
|
|||
|
||||
def test_acceptance_replaces_executive_summary(tmp_path, types_root):
|
||||
acc = {"at": "2026-10-08", "record": "informed-decision:forgejo-oidc", "by": [{"role": "owner"}, {"role": "founder"}]}
|
||||
doc, _, _ = cp.plan_from_case(_case(tmp_path, acceptance=acc), types_root=types_root, structure=cp.Structure())
|
||||
doc, _, _ = cp.plan_from_case(_case(tmp_path, acceptance=acc), types_root=types_root, structure=cp.Structure(source="live"))
|
||||
front = yaml.safe_load(doc.split("---")[1])
|
||||
assert front["status"] == "reviewed"
|
||||
offline, _, _ = cp.plan_from_case(_case(tmp_path, acceptance=acc), types_root=types_root, structure=cp.Structure())
|
||||
assert yaml.safe_load(offline.split("---")[1])["status"] == "draft"
|
||||
assert front["case_acceptance"]["record"] == "informed-decision:forgejo-oidc"
|
||||
|
||||
|
||||
|
|
@ -107,3 +109,18 @@ def test_cli_plan_refuses_unimplemented_type_with_exit_2(tmp_path, capsys):
|
|||
from ops_mason.cli import main
|
||||
assert main(["plan", "--case", str(_case(tmp_path)), "--no-live"]) == 2
|
||||
assert "only implemented types" in capsys.readouterr().err
|
||||
|
||||
|
||||
def test_reference_case_renders_against_acknowledged_type_only_when_asked(tmp_path):
|
||||
doc, _, _ = cp.plan_from_case(_case(tmp_path), structure=cp.Structure(), allow_unimplemented=True)
|
||||
front = yaml.safe_load(doc.split("---")[1])
|
||||
assert front["reference_case"] is True and "**Reference case:**" in doc
|
||||
|
||||
|
||||
def test_allow_unimplemented_refused_for_non_acknowledged_type(tmp_path, types_root):
|
||||
f = types_root / TYPE.name / "type.yaml"
|
||||
data = yaml.safe_load(f.read_text())
|
||||
data["spec"]["status"] = "proposed"
|
||||
f.write_text(yaml.safe_dump(data, sort_keys=False))
|
||||
with pytest.raises(cp.CasePlanError):
|
||||
cp.plan_from_case(_case(tmp_path), types_root=types_root, structure=cp.Structure(), allow_unimplemented=True)
|
||||
|
|
|
|||
|
|
@ -171,6 +171,13 @@ net-kingdom (keycape-factor-issuer) and key-cape (rapp-qonto keycape-client).
|
|||
user-engine/runtime's `on_loss` is ops-mason's own to write from the
|
||||
user-engine plan once its ESO rotation rehearsal is recorded.
|
||||
|
||||
2026-10-09: net-kingdom answered for `keycape-factor-issuer` (message
|
||||
`75b48030`): four fields filled in the manifest; `rotation` stays empty because
|
||||
no rotation document exists, a gap attributed to railiance-platform. The entry
|
||||
stays a draft until that exists. railiance-platform retired the duplicate
|
||||
`activity-core/default/llm-connect-provider-secrets` (message `af873813`), and
|
||||
the four `infotech` fluid-telegram entries were moved to `coulomb`.
|
||||
|
||||
## Make an undescribed path visible before it accumulates
|
||||
|
||||
```task
|
||||
|
|
|
|||
|
|
@ -179,10 +179,8 @@ gate accepts it (checked by loading the plan, no bao call). Hub decision
|
|||
|
||||
```task
|
||||
id: MASON-WP-0005-T04
|
||||
status: wait
|
||||
status: done
|
||||
depends_on: [MASON-WP-0005-T03]
|
||||
blocked_on: human
|
||||
blocking_reason: "Founder ruled coulomb (2026-10-09): railiance-platform prepares the silent move of the four infotech values plus the lane apply. Plan approved 2026-10-09. Writes are founder-applied through railiance-platform's per-path applier (RPF-WP-0035-T03, attended provisioning decision 62aa95e4), including the agent read-boundary denies; ops-mason verifies afterwards."
|
||||
priority: high
|
||||
state_hub_task_id: "0e3d1333-ffc3-5f67-8528-50a9551c4949"
|
||||
```
|
||||
|
|
@ -205,13 +203,22 @@ platform-admin step that writes each entry at the `coulomb` path with CAS 0,
|
|||
verifies versions, then deletes the `infotech` metadata, so `redaction-salt`
|
||||
keeps its value and stays write-once. fluid-telegram repoints its refs.
|
||||
|
||||
2026-10-09 done (railiance-platform `ce1c1a6`, message `af873813`, evidence
|
||||
`docs/evidence/2026-10-09-fluid-telegram-t03-founder-session.json`): the
|
||||
founder applied CCR-2026-0039 at 10:30Z (policy, OIDC role, 8 agent denies
|
||||
appended to `agent-high-risk-boundary`; readback matched) and ran the custody
|
||||
move: all four entries now at the `coulomb` path, version 1 each, `infotech`
|
||||
metadata deleted. fluid-telegram must set
|
||||
`FLUID_BAO_PREFIX=workloads/coulomb/fluid-telegram/hall-of-helix`.
|
||||
|
||||
## Verify capabilities and record metadata-only evidence
|
||||
|
||||
```task
|
||||
id: MASON-WP-0005-T05
|
||||
status: wait
|
||||
depends_on: [MASON-WP-0005-T04]
|
||||
blocking_reason: "After T04 construction."
|
||||
blocked_on: human
|
||||
blocking_reason: "Lane built 2026-10-09; verification needs a survey grant (positive matrix, sibling/parent/agent denial via policy text and capabilities). Non-member refusal and the salt CAS probe are owner-run (FT-WP-0002)."
|
||||
priority: high
|
||||
state_hub_task_id: "4c205be7-4f1f-5dd1-aafa-fc112fdd640e"
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue