RPF-WP-0061: render-policy and plan_ref for ops-mason submissions; approvals only via sync-decision
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 306568@bnt-lap001 Assistant-Session: 8bdf8150-ea56-41e7-9132-13f04f6b0b91
This commit is contained in:
parent
04f18c8c73
commit
7e7ca8be51
3 changed files with 56 additions and 1 deletions
|
|
@ -325,6 +325,11 @@ still goes to the `railiance-platform` inbox.
|
|||
Run these in a railiance-platform checkout that is up to date with `main`:
|
||||
|
||||
```bash
|
||||
# 0. Generate the policy instead of writing it, so it is byte-equal by construction.
|
||||
python3 scripts/credential-change.py render-policy --kv-path <platform/...> > <policy.hcl>
|
||||
# Optional: plan_ref: {repo, path, commit} in the draft links the client's
|
||||
# construction plan; submit keeps it and review-standard only checks its shape.
|
||||
|
||||
# 1. File the draft. Allocates the CCR id, copies the policy, runs the review.
|
||||
python3 scripts/credential-change.py submit \
|
||||
--ccr <draft.yaml> --policy <policy.hcl> --slug <slug> \
|
||||
|
|
@ -342,7 +347,11 @@ git commit -m "<id>: request founder decision" -- credential-change-requests/<id
|
|||
git push origin main
|
||||
```
|
||||
|
||||
After step 3, the client waits. The **founder** resolves the State Hub decision.
|
||||
After step 3, the client waits and does not edit `status` or `review.comments`
|
||||
itself. Approvals reach the file only through `sync-decision`, even when the
|
||||
founder approved the client's plan elsewhere. The hub decision is the single
|
||||
approval record for both the client plan and the CCR; clients copy their plan
|
||||
approval *from* it after the sync. The **founder** resolves the State Hub decision.
|
||||
Then a **platform session or the founder** runs `sync-decision <id>`. Clients do
|
||||
not run `sync-decision` on their own CCRs. State Hub does not authenticate who
|
||||
resolved a decision (`decided_by` is free text). Until founder approval can be
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import json
|
|||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.request
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timezone
|
||||
|
|
@ -126,6 +127,13 @@ def standard_violations(
|
|||
risk = (ccr.get("risk") or {}).get("classification")
|
||||
if risk not in profile["allowed_risk_classifications"]:
|
||||
v.append(f"risk classification {risk!r} needs a platform review")
|
||||
plan_ref = ccr.get("plan_ref")
|
||||
if plan_ref is not None and not (
|
||||
isinstance(plan_ref, dict)
|
||||
and all(isinstance(plan_ref.get(k), str) and plan_ref.get(k) for k in ("repo", "path", "commit"))
|
||||
and re.match(r"^[0-9a-f]{7,40}$", plan_ref["commit"])
|
||||
):
|
||||
v.append("plan_ref must have non-empty repo, path and a hex commit")
|
||||
conflicts = reader_conflicts(ccr, directory, profile, cc)
|
||||
if conflicts:
|
||||
v.append(f"kv_path already has a reader: {', '.join(conflicts)}")
|
||||
|
|
@ -338,6 +346,23 @@ def command_request_decision(args: argparse.Namespace, cc: Any) -> int:
|
|||
return 0
|
||||
|
||||
|
||||
def standard_policy_hcl(kv_path: str, cc: Any) -> str:
|
||||
"""The exact HCL review-standard compares against, for a standard-lane path."""
|
||||
return cc.generated_policy_hcl({
|
||||
"request_type": "workload-kv-read",
|
||||
"openbao": {"mount": kv_path.split("/", 1)[0], "kv_path": kv_path,
|
||||
"metadata_read": False, "token_self_lifecycle": True},
|
||||
})
|
||||
|
||||
|
||||
def command_render_policy(args: argparse.Namespace, cc: Any) -> int:
|
||||
profile, _digest = load_profile(Path(args.profile))
|
||||
if not any(re.match(p, args.kv_path) for p in profile["kv_path_patterns"]):
|
||||
cc.fail(f"kv_path {args.kv_path!r} is outside the standard workload/supplier prefixes")
|
||||
sys.stdout.write(standard_policy_hcl(args.kv_path, cc))
|
||||
return 0
|
||||
|
||||
|
||||
def git(*argv: str) -> None:
|
||||
subprocess.run(["git", "-C", str(REPO_DIR), *argv], check=True)
|
||||
|
||||
|
|
@ -426,6 +451,13 @@ def add_parsers(sub: Any, cc: Any, state_hub_url: str) -> None:
|
|||
decision_p.add_argument("--state-hub-url", default=state_hub_url)
|
||||
decision_p.set_defaults(func=lambda args: command_request_decision(args, cc))
|
||||
|
||||
render_policy = sub.add_parser(
|
||||
"render-policy", help="Print the standard-lane policy HCL for a KV path (what review-standard expects)"
|
||||
)
|
||||
render_policy.add_argument("--kv-path", required=True)
|
||||
render_policy.add_argument("--profile", default=str(DEFAULT_PROFILE))
|
||||
render_policy.set_defaults(func=lambda args: command_render_policy(args, cc))
|
||||
|
||||
submit_p = sub.add_parser(
|
||||
"submit",
|
||||
help="File a client CCR draft under the next CCR id and run the standard review",
|
||||
|
|
|
|||
|
|
@ -277,6 +277,20 @@ class CcrIntakeTests(unittest.TestCase):
|
|||
with mock.patch.object(cc, "state_hub_post_json", return_value=resolved), self.assertRaises(SystemExit):
|
||||
ccr_intake.request_decision(self.path, cc, "http://hub")
|
||||
|
||||
def test_render_policy_matches_reviewed_policy_file(self) -> None:
|
||||
rendered = ccr_intake.standard_policy_hcl("platform/suppliers/scaleway/mas-tem-relay", cc)
|
||||
self.assertEqual(rendered, (REPO_DIR / POLICY).read_text())
|
||||
|
||||
def test_plan_ref_passes_through_submit_and_is_shape_checked(self) -> None:
|
||||
args = self.submit_args()
|
||||
draft = cc.load_yaml(Path(args.ccr))
|
||||
draft["plan_ref"] = {"repo": "ops-mason", "path": "plans/x.md", "commit": "d19e6b1"}
|
||||
cc.dump_yaml(Path(args.ccr), draft)
|
||||
destination, violations = ccr_intake.submit(args, cc)
|
||||
self.assertEqual(violations, [])
|
||||
self.assertEqual(cc.load_yaml(destination)["plan_ref"]["commit"], "d19e6b1")
|
||||
self.assert_violation(lambda d: d.update(plan_ref={"repo": "ops-mason"}), "plan_ref")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue