197 lines
10 KiB
Diff
197 lines
10 KiB
Diff
diff --git a/docs/forgejo-structural-materialization-source-v1.md b/docs/forgejo-structural-materialization-source-v1.md
|
|
index 9175c5c..c4be07f 100644
|
|
--- a/docs/forgejo-structural-materialization-source-v1.md
|
|
+++ b/docs/forgejo-structural-materialization-source-v1.md
|
|
@@ -115,3 +115,31 @@ binding/application; mint is removed from its structural readiness blocker.
|
|
WARDEN-WP-0044-T03 and NK-WP-0048-T06 retain native follow-through, full current
|
|
run digest/machine source binding, verification and first live-case acceptance.
|
|
No case/type status, credential, policy, catalog activation or workload was changed.
|
|
+
|
|
+## Explicit source-root selection — 2026-10-16
|
|
+
|
|
+`plan`, `build` and `structure-request` accept `--types-root` and pass it to
|
|
+NetKingdom's existing exact-version loader. The default remains
|
|
+`<netkingdom-root>/security-case-types`; a requested version never falls forward
|
|
+or backward. Provider declarations still resolve from the NetKingdom repository's
|
|
+siblings, independently of the selected type root.
|
|
+
|
|
+Prepare the acknowledged 1.2 source projection without a live survey:
|
|
+
|
|
+```bash
|
|
+ops-mason plan --case ../net-kingdom/security-case-type-revisions/1.2/oidc-browser-relying-party/examples/forgejo.yaml --types-root ../net-kingdom/security-case-type-revisions/1.2 --no-live --allow-unimplemented
|
|
+```
|
|
+
|
|
+This actual case is proposed and has no acceptance. Its six-object draft includes
|
|
+the dedicated consumer ESO role bound to namespace `forgejo` and service account
|
|
+`forgejo-keycape-forgejo-eso`. Source-root selection grants no acceptance: normal
|
|
+admission still refuses the acknowledged type; `build` refuses the proposed case,
|
|
+and `structure-request` refuses the historical 1.1 plan/bundle for it. A current
|
|
+accepted case and reviewed plan are needed before regenerating a build/request.
|
|
+Do not repin historical acceptance or relabel an offline plan as reviewed.
|
|
+
|
|
+Six integration checks exercise these paths against the actual owner validator
|
|
+and source package, including malformed namespace refusal. This CLI compatibility
|
|
+change does not supply the owner-native contracts listed above. Machine descriptors,
|
|
+source receipt authentication, current CCR42 binding and native six-object proof
|
|
+remain with MASON-WP-0010-T03 / NK-WP-0048-T06 and their existing owners.
|
|
diff --git a/src/ops_mason/cli.py b/src/ops_mason/cli.py
|
|
index 2f0210a..3731077 100644
|
|
--- a/src/ops_mason/cli.py
|
|
+++ b/src/ops_mason/cli.py
|
|
@@ -53,6 +53,7 @@ def _parser() -> argparse.ArgumentParser:
|
|
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("--types-root", type=Path, help="explicit exact-version SecurityCaseType root")
|
|
plan.add_argument(
|
|
"--allow-unimplemented",
|
|
action="store_true",
|
|
@@ -74,6 +75,7 @@ def _parser() -> argparse.ArgumentParser:
|
|
help="reference case only: build against an acknowledged type",
|
|
)
|
|
build.add_argument("--netkingdom-root", type=Path, default=None)
|
|
+ build.add_argument("--types-root", type=Path, help="explicit exact-version SecurityCaseType root")
|
|
structure = families.add_parser(
|
|
"structure-request", help="read-only typed structural evidence request"
|
|
)
|
|
@@ -81,6 +83,7 @@ def _parser() -> argparse.ArgumentParser:
|
|
structure.add_argument("--plan", required=True, type=Path)
|
|
structure.add_argument("--bundle", required=True, type=Path)
|
|
structure.add_argument("--netkingdom-root", type=Path)
|
|
+ structure.add_argument("--types-root", type=Path, help="explicit exact-version SecurityCaseType root")
|
|
structure.add_argument("--allow-unimplemented", action="store_true")
|
|
return parser
|
|
|
|
@@ -101,6 +104,7 @@ def _build(args) -> int:
|
|
args.plan,
|
|
out,
|
|
nk_root=nk_root,
|
|
+ types_root=args.types_root,
|
|
allow_unimplemented=args.allow_unimplemented,
|
|
)
|
|
except (
|
|
@@ -134,6 +138,7 @@ def _plan(args) -> int:
|
|
doc, collisions, rendered = case_plan.plan_from_case(
|
|
args.case,
|
|
nk_root=nk_root,
|
|
+ types_root=args.types_root,
|
|
structure=case_plan.Structure() if args.no_live else None,
|
|
allow_unimplemented=args.allow_unimplemented,
|
|
)
|
|
@@ -171,6 +176,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
args.plan,
|
|
args.bundle,
|
|
nk_root=args.netkingdom_root or case_plan.DEFAULT_NK_ROOT,
|
|
+ types_root=args.types_root,
|
|
allow_unimplemented=args.allow_unimplemented,
|
|
)
|
|
except Exception:
|
|
diff --git a/tests/test_case_version_source.py b/tests/test_case_version_source.py
|
|
new file mode 100644
|
|
index 0000000..49aee70
|
|
--- /dev/null
|
|
+++ b/tests/test_case_version_source.py
|
|
@@ -0,0 +1,84 @@
|
|
+"""Actual NetKingdom 1.2 source selection; no accepted/native case fixture."""
|
|
+import json
|
|
+from pathlib import Path
|
|
+
|
|
+import pytest
|
|
+import yaml
|
|
+
|
|
+from ops_mason import case_plan, cli
|
|
+
|
|
+NK = case_plan.DEFAULT_NK_ROOT
|
|
+TYPES = NK / "security-case-type-revisions/1.2"
|
|
+CASE = TYPES / "oidc-browser-relying-party/examples/forgejo.yaml"
|
|
+pytestmark = pytest.mark.skipif(not CASE.is_file(), reason="NetKingdom 1.2 source absent")
|
|
+
|
|
+
|
|
+def test_explicit_source_root_renders_actual_six_object_draft(tmp_path, capsys):
|
|
+ output = tmp_path / "plan.md"
|
|
+ assert cli.main(["plan", "--case", str(CASE), "--types-root", str(TYPES),
|
|
+ "--no-live", "--allow-unimplemented", "--out", str(output)]) == 0
|
|
+ plan = output.read_text()
|
|
+ front = yaml.safe_load(plan.split("---\n", 2)[1])
|
|
+ assert front["status"] == "draft"
|
|
+ assert front["case_acceptance"] is None
|
|
+ assert front["derived_from_type"] == "oidc-browser-relying-party@1.2"
|
|
+ assert front["reference_case"] is True
|
|
+ case, doc, rendered = case_plan.render(CASE, types_root=TYPES, allow_unimplemented=True)
|
|
+ assert case["spec"]["status"] == "proposed"
|
|
+ assert "acceptance" not in case["spec"]
|
|
+ assert doc["spec"]["status"] == "acknowledged"
|
|
+ assert len(rendered["materialization"]) == 6
|
|
+ role = next(item for item in rendered["materialization"]
|
|
+ if item["id"] == "consumer-eso-role")
|
|
+ body = yaml.safe_load(role["body"])
|
|
+ assert body["bound_service_account_namespaces"] == ["forgejo"]
|
|
+ assert body["bound_service_account_names"] == ["forgejo-keycape-forgejo-eso"]
|
|
+ assert front["render_digest"] == rendered["digest"]
|
|
+ assert json.loads(capsys.readouterr().err)["ok"] is True
|
|
+
|
|
+
|
|
+def test_explicit_root_does_not_waive_implemented_gate(tmp_path, capsys):
|
|
+ output = tmp_path / "plan.md"
|
|
+ assert cli.main(["plan", "--case", str(CASE), "--types-root", str(TYPES),
|
|
+ "--no-live", "--out", str(output)]) == 2
|
|
+ assert not output.exists()
|
|
+ assert "only implemented types" in capsys.readouterr().err
|
|
+
|
|
+
|
|
+def test_missing_explicit_root_never_falls_forward_to_12(tmp_path, capsys):
|
|
+ output = tmp_path / "plan.md"
|
|
+ assert cli.main(["plan", "--case", str(CASE), "--no-live",
|
|
+ "--allow-unimplemented", "--out", str(output)]) == 2
|
|
+ assert not output.exists()
|
|
+ assert "not 1.2" in capsys.readouterr().err
|
|
+
|
|
+
|
|
+def test_proposed_case_cannot_build_even_with_selected_acknowledged_source(tmp_path, capsys):
|
|
+ output = tmp_path / "bundle"
|
|
+ assert cli.main(["build", "--case", str(CASE), "--types-root", str(TYPES),
|
|
+ "--allow-unimplemented", "--plan", str(tmp_path / "absent-plan.md"),
|
|
+ "--out", str(output)]) == 2
|
|
+ assert not output.exists()
|
|
+ assert "case is not accepted" in capsys.readouterr().err
|
|
+
|
|
+
|
|
+def test_historical_bundle_cannot_attest_12_inventory(tmp_path, capsys):
|
|
+ owner = Path("/home/worsch/ops-mason")
|
|
+ assert cli.main(["structure-request", "--case", str(CASE), "--types-root", str(TYPES),
|
|
+ "--allow-unimplemented", "--plan", str(owner / "plans/case-forgejo-netkingdom-oidc.md"),
|
|
+ "--bundle", str(owner / "builds/case-forgejo-netkingdom-oidc")]) == 2
|
|
+ captured = capsys.readouterr()
|
|
+ assert captured.out == ""
|
|
+ assert json.loads(captured.err)["error"] == "structural-source-preparation-refused"
|
|
+
|
|
+
|
|
+def test_actual_validator_refuses_invalid_consumer_namespace(tmp_path, capsys):
|
|
+ case = yaml.safe_load(CASE.read_text())
|
|
+ case["spec"]["parameters"]["consumer_namespace"] = "../../other"
|
|
+ source = tmp_path / "case.yaml"
|
|
+ source.write_text(yaml.safe_dump(case))
|
|
+ assert cli.main(["plan", "--case", str(source), "--types-root", str(TYPES),
|
|
+ "--no-live", "--allow-unimplemented"]) == 2
|
|
+ captured = capsys.readouterr()
|
|
+ assert captured.out == ""
|
|
+ assert "consumer_namespace" in captured.err
|
|
diff --git a/workplans/MASON-WP-0010-security-case-materialization.md b/workplans/MASON-WP-0010-security-case-materialization.md
|
|
index 3fb2e46..4b1a1fd 100644
|
|
--- a/workplans/MASON-WP-0010-security-case-materialization.md
|
|
+++ b/workplans/MASON-WP-0010-security-case-materialization.md
|
|
@@ -247,3 +247,14 @@ parameter/acceptance field; it writes and signs nothing. Details and exact nativ
|
|
`docs/forgejo-structural-materialization-source-v1.md`. T03 remains progress;
|
|
case remains accepted@1.1, not materialized/live. CCR42 consumer auth remains
|
|
in_flight, and native owner evidence is not manufactured by source preparation.
|
|
+
|
|
+2026-10-16 (T03 source compatibility): `plan`, `build` and
|
|
+`structure-request` now expose explicit `--types-root`, passing it to the existing
|
|
+exact-version NetKingdom API. Actual acknowledged 1.2 source renders the proposed
|
|
+Forgejo case into a six-object **draft**, preserving its absent acceptance.
|
|
+Six actual-source integration checks prove the root selection, dedicated consumer
|
|
+namespace/SA, ordinary unimplemented refusal, proposed build refusal, historical
|
|
+1.1 bundle refusal and malformed namespace refusal. Existing 44 plan/build/
|
|
+structure tests pass. No live survey, execution, case/type status, acceptance,
|
|
+native owner evidence or CCR was changed. T03 remains progress; actual accepted
|
|
+1.2 case/reviewed source and authenticated native owner evidence remain required.
|