RPF-WP-0061-T01/T02: standard lane profile and self-service CCR submit/review-standard
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

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:
codex 2026-10-18 12:29:47 +02:00
parent 4398f89e02
commit b6148da443
5 changed files with 619 additions and 10 deletions

View file

@ -0,0 +1,76 @@
# Standard lane profile for self-service CCR intake (RPF-WP-0061-T01).
#
# A CCR that satisfies every rule below receives the railiance-platform-owner
# review from `credential-change.py review-standard` without a platform worker
# session. Anything else is routed to the railiance-platform inbox. Changes to
# this file are platform-owner review changes: the profile digest is recorded
# in every review comment it produces.
#
# The profile never grants approval. Founder approval, confirm-binding against
# native state and the applier boundary are unchanged.
profile: ccr-standard-lane
version: 1
# Identities allowed to file CCRs with `credential-change.py submit`.
submitters:
- ops-mason
request_type: workload-kv-read
mount: platform
kv_path_patterns:
- '^platform/workloads/[a-z0-9][a-z0-9-]*/[a-z0-9][a-z0-9-]*(/[a-z0-9][a-z0-9-]*)?$'
- '^platform/suppliers/[a-z0-9][a-z0-9-]*/[a-z0-9][a-z0-9-]*$'
policy_name_prefix: workload-kv-read-
# The policy file must equal the generated HCL: data read, plus
# lookup-self/revoke-self when token_self_lifecycle is set.
metadata_read: false
token_capabilities_self: false
auth:
method: kubernetes
mount: kubernetes
role_prefix: external-secrets-
audience: openbao
max_ttl_seconds: 900
token_no_default_policy: true
service_accounts: 1
namespaces: 1
forbidden_namespaces:
- argocd
- cert-manager
- default
- external-secrets
- kube-public
- kube-system
- openbao
allowed_risk_classifications:
- low
- medium
# A second reader of an existing path is a platform judgement.
reader_conflict_statuses:
- draft
- in_flight
- proposed
- needs_changes
- approved
- apply_pending
- applied
- verified
- active
- rotated
# Calibration against all 51 CCRs on main at 2026-10-18 (version 1, status
# ignored): only CCR-2026-0051 passes. First failing rule for the others:
# metadata_read set ............ 0001-0004, 0006, 0010-0014, 0017-0020,
# 0023-0027, 0029-0031, 0035, 0044-0046
# non-standard role/policy name 0015, 0016, 0032, 0036, 0037, 0048, 0049
# audience not openbao ......... 0021, 0022, 0047
# central ESO SA namespace ..... 0033, 0041 (external-secrets/external-secrets)
# not kubernetes / not platform 0007-0009, 0034, 0042
# policy file drift ............ 0005
# risk high .................... 0050
# other request types .......... 0028, 0038-0040, 0043
# The profile is deliberately stricter than past practice: older shapes need a
# platform review instead of silently passing.

310
scripts/ccr_intake.py Normal file
View file

@ -0,0 +1,310 @@
"""Self-service CCR intake: submit, standard-lane review and id checks.
RPF-WP-0061-T02. Lets a client file a CCR and obtain the platform-owner review
for in-profile lanes without a railiance-platform worker session. The rules
live in registry/ccr-standard-profile.yaml; this module only evaluates them.
Nothing here touches OpenBao or grants approval: the CCR stays `proposed`
until the founder decision is synced.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import shutil
import subprocess
import urllib.request
from collections import defaultdict
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
import yaml
REPO_DIR = Path(__file__).resolve().parents[1]
DEFAULT_PROFILE = REPO_DIR / "registry/ccr-standard-profile.yaml"
CCR_ID_RE = re.compile(r"^CCR-(\d{4})-(\d{4})$")
CCR_FILE_RE = re.compile(r"^(CCR-\d{4}-\d{4})(-[a-z0-9-]+)?\.ya?ml$")
SLUG_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$")
TTL_UNITS = {"s": 1, "m": 60, "h": 3600, "d": 86400}
PLATFORM_OWNER = "railiance-platform-owner"
def load_profile(path: Path = DEFAULT_PROFILE) -> tuple[dict[str, Any], str]:
raw = path.read_bytes()
return yaml.safe_load(raw), "sha256:" + hashlib.sha256(raw).hexdigest()
def ttl_seconds(value: Any) -> int | None:
if not isinstance(value, str) or not re.match(r"^[1-9][0-9]*[smhd]$", value):
return None
return int(value[:-1]) * TTL_UNITS[value[-1]]
def bound_list(auth: dict[str, Any], key: str) -> list[Any]:
claims = auth.get("bound_claims") or {}
return list(claims.get(key) or auth.get(f"bound_{key}") or [])
def ccr_files(directory: Path) -> list[Path]:
return sorted(p for p in directory.glob("CCR-*.y*ml") if CCR_FILE_RE.match(p.name))
def reader_conflicts(
ccr: dict[str, Any], directory: Path, profile: dict[str, Any], cc: Any
) -> list[str]:
kv_path = (ccr.get("openbao") or {}).get("kv_path")
statuses = set(profile["reader_conflict_statuses"])
conflicts = []
for path in ccr_files(directory):
other = cc.load_yaml(path)
if other.get("id") == ccr.get("id"):
continue
if (other.get("openbao") or {}).get("kv_path") == kv_path and other.get("status") in statuses:
conflicts.append(f"{other.get('id')} ({other.get('status')})")
return conflicts
def standard_violations(
ccr: dict[str, Any], profile: dict[str, Any], directory: Path, cc: Any
) -> list[str]:
"""Return every reason the CCR falls outside the standard lane profile."""
v: list[str] = []
if ccr.get("request_type") != profile["request_type"]:
return [f"request_type must be {profile['request_type']}"]
openbao = ccr.get("openbao") or {}
auth = openbao.get("auth") or {}
want = profile["auth"]
kv_path = openbao.get("kv_path") or ""
if openbao.get("mount") != profile["mount"]:
v.append(f"mount must be {profile['mount']}")
if not any(re.match(p, kv_path) for p in profile["kv_path_patterns"]):
v.append(f"kv_path {kv_path!r} is outside the standard workload/supplier prefixes")
if openbao.get("metadata_read", True) is not profile["metadata_read"]:
v.append("metadata_read must be false")
if bool(openbao.get("token_capabilities_self", False)) is not profile["token_capabilities_self"]:
v.append("token_capabilities_self is not standard")
policy_name = openbao.get("policy_name") or ""
if not policy_name.startswith(profile["policy_name_prefix"]):
v.append(f"policy_name must start with {profile['policy_name_prefix']}")
policy_file = openbao.get("policy_file")
if not policy_file:
v.append("policy_file is required")
else:
policy_path = REPO_DIR / policy_file
if not policy_path.exists():
v.append(f"policy_file {policy_file} does not exist")
elif cc.normalized_policy_body(policy_path.read_text()) != cc.normalized_policy_body(
cc.generated_policy_hcl(ccr)
):
v.append("policy_file differs from the generated policy")
if auth.get("method") != want["method"] or auth.get("mount") != want["mount"]:
v.append(f"auth must be {want['method']} on mount {want['mount']}")
if not str(auth.get("role") or "").startswith(want["role_prefix"]):
v.append(f"auth role must start with {want['role_prefix']}")
if auth.get("audience") != want["audience"]:
v.append(f"audience must be {want['audience']}")
for key in ("ttl", "token_max_ttl", "token_explicit_max_ttl"):
seconds = ttl_seconds(auth.get(key))
if seconds is None or seconds > want["max_ttl_seconds"]:
v.append(f"{key} must be set and at most {want['max_ttl_seconds']}s")
if auth.get("token_no_default_policy") is not want["token_no_default_policy"]:
v.append("token_no_default_policy must be true")
if auth.get("policies") != [policy_name]:
v.append("auth policies must be exactly [policy_name]")
accounts = bound_list(auth, "service_account_names")
namespaces = bound_list(auth, "service_account_namespaces")
if len(accounts) != want["service_accounts"] or "*" in accounts:
v.append(f"exactly {want['service_accounts']} exact service account required")
if len(namespaces) != want["namespaces"] or "*" in namespaces:
v.append(f"exactly {want['namespaces']} exact namespace required")
forbidden = sorted(set(namespaces) & set(want["forbidden_namespaces"]))
if forbidden:
v.append(f"namespace {forbidden} is reserved for platform components")
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")
conflicts = reader_conflicts(ccr, directory, profile, cc)
if conflicts:
v.append(f"kv_path already has a reader: {', '.join(conflicts)}")
return v
def duplicate_ids(directory: Path, cc: Any) -> dict[str, list[str]]:
seen: dict[str, list[str]] = defaultdict(list)
for path in ccr_files(directory):
seen[str(cc.load_yaml(path).get("id"))].append(path.name)
prefix = CCR_FILE_RE.match(path.name).group(1)
if prefix != cc.load_yaml(path).get("id"):
seen[f"{prefix} (file name vs id)"].append(path.name)
return {k: v for k, v in seen.items() if len(v) > 1 or "file name" in k}
def next_ccr_id(directory: Path, year: int) -> str:
numbers = [0]
for path in ccr_files(directory):
match = CCR_ID_RE.match(CCR_FILE_RE.match(path.name).group(1))
if match and int(match.group(1)) == year:
numbers.append(int(match.group(2)))
return f"CCR-{year}-{max(numbers) + 1:04d}"
def record_review(path: Path, ccr: dict[str, Any], violations: list[str], profile: dict[str, Any], digest: str, cc: Any) -> None:
label = f"{profile['profile']} v{profile['version']} {digest}"
if violations:
decision = "needs_platform_review"
comment = f"Outside {label}: " + "; ".join(violations)
else:
decision = "approved"
comment = (f"Standard lane: passes {label}. Platform-owner source review only; "
"founder approval and confirm-binding still required.")
comments = ccr.setdefault("review", {}).setdefault("comments", [])
comments.append({
"at": cc.utc_now(),
"reviewer": f"{PLATFORM_OWNER} (policy-as-code {profile['profile']} v{profile['version']})",
"decision": decision,
"comment": comment,
})
ccr["updated"] = datetime.now(timezone.utc).date().isoformat()
cc.dump_yaml(path, ccr)
def notify_platform(ccr: dict[str, Any], violations: list[str], base_url: str) -> None:
payload = {
"from_agent": (ccr.get("requester") or {}).get("agent", "ccr-intake"),
"to_agent": "railiance-platform",
"subject": f"{ccr['id']} needs platform review (outside standard lane profile)",
"body": f"{ccr['id']} {ccr.get('title')}: " + "; ".join(violations),
}
request = urllib.request.Request(
f"{base_url.rstrip('/')}/messages/", data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"}, method="POST")
with urllib.request.urlopen(request, timeout=15):
pass
def review_standard(path: Path, cc: Any, profile_path: Path = DEFAULT_PROFILE,
write: bool = True) -> list[str]:
ccr, _warnings = cc.validate_or_exit(path)
if ccr.get("status") != "proposed":
cc.fail(f"review-standard requires status proposed, got {ccr.get('status')}")
profile, digest = load_profile(profile_path)
violations = standard_violations(ccr, profile, path.parent, cc)
if write:
record_review(path, ccr, violations, profile, digest, cc)
return violations
def command_review_standard(args: argparse.Namespace, cc: Any) -> int:
path = cc.resolve_ccr(args.ref)
violations = review_standard(path, cc, Path(args.profile), write=not args.check)
if not violations:
print(f"[OK] {path.name}: standard lane; platform-owner review recorded"
if not args.check else f"[OK] {path.name}: standard lane")
return 0
for item in violations:
print(f"[NEEDS-PLATFORM] {path.name}: {item}")
if args.notify and not args.check:
notify_platform(cc.load_yaml(path), violations, args.state_hub_url)
print("[OK] railiance-platform notified")
return 2
def command_check_ids(args: argparse.Namespace, cc: Any) -> int:
duplicates = duplicate_ids(cc.ccr_dir(), cc)
for key, names in duplicates.items():
print(f"[ERROR] {key}: {names}")
if not duplicates:
print("[OK] CCR ids unique and matching file names")
return 1 if duplicates else 0
def git(*argv: str) -> None:
subprocess.run(["git", "-C", str(REPO_DIR), *argv], check=True)
def submit(args: argparse.Namespace, cc: Any) -> tuple[Path, list[str]]:
profile, _digest = load_profile(Path(args.profile))
if args.requester not in profile["submitters"]:
cc.fail(f"requester {args.requester!r} may not self-submit; allowed: {profile['submitters']}")
if not SLUG_RE.match(args.slug):
cc.fail("slug must be lowercase letters, digits and dashes")
source = cc.resolve_repo_path(args.ccr)
errors: list[str] = []
cc.scan_for_secrets(source, errors)
if errors:
cc.fail("; ".join(errors))
ccr = cc.load_yaml(source)
directory = cc.ccr_dir()
today = datetime.now(timezone.utc).date()
ccr_id = next_ccr_id(directory, today.year)
ccr.update(id=ccr_id, status="proposed", created=today.isoformat(), updated=today.isoformat())
ccr.setdefault("requester", {})["agent"] = args.requester
ccr.setdefault("review", {})["comments"] = []
openbao = ccr.get("openbao") or {}
if args.policy:
policy_name = openbao.get("policy_name") or cc.fail("openbao.policy_name is required")
target = REPO_DIR / "openbao/policies" / f"{policy_name}.hcl"
policy_text = cc.resolve_repo_path(args.policy).read_text()
cc.reject_secret_text(policy_text, "policy file")
if target.exists() and target.read_text() != policy_text:
cc.fail(f"{target.relative_to(REPO_DIR)} exists with different content")
target.write_text(policy_text)
openbao["policy_file"] = str(target.relative_to(REPO_DIR))
destination = directory / f"{ccr_id}-{args.slug}.yaml"
cc.dump_yaml(destination, ccr)
violations = review_standard(destination, cc, Path(args.profile))
return destination, violations
def command_submit(args: argparse.Namespace, cc: Any) -> int:
destination, violations = submit(args, cc)
ccr = cc.load_yaml(destination)
print(f"[OK] filed {ccr['id']} -> {destination.relative_to(REPO_DIR)}")
for item in violations:
print(f"[NEEDS-PLATFORM] {item}")
if args.commit or args.push:
branch = f"ccr/{destination.stem}"
git("switch", "-c", branch)
paths = [str(destination.relative_to(REPO_DIR))]
if ccr["openbao"].get("policy_file"):
paths.append(ccr["openbao"]["policy_file"])
git("add", *paths)
git("commit", "-m", f"File {ccr['id']}: {ccr.get('title')}", "--", *paths)
if args.push:
git("push", "-u", "origin", branch)
if violations and args.notify:
notify_platform(ccr, violations, args.state_hub_url)
return 0
def add_parsers(sub: Any, cc: Any, state_hub_url: str) -> None:
review = sub.add_parser(
"review-standard",
help="Check a proposed CCR against the standard lane profile; record the platform-owner review",
)
review.add_argument("ref")
review.add_argument("--profile", default=str(DEFAULT_PROFILE))
review.add_argument("--check", action="store_true", help="report only; do not write the review")
review.add_argument("--notify", action="store_true", help="message railiance-platform when out of profile")
review.add_argument("--state-hub-url", default=state_hub_url)
review.set_defaults(func=lambda args: command_review_standard(args, cc))
check_ids = sub.add_parser("check-ids", help="Fail on duplicate CCR ids or id/file name mismatch")
check_ids.set_defaults(func=lambda args: command_check_ids(args, cc))
submit_p = sub.add_parser(
"submit",
help="File a client CCR draft under the next CCR id and run the standard review",
)
submit_p.add_argument("--ccr", required=True, help="client CCR draft (non-secret YAML)")
submit_p.add_argument("--policy", help="client policy HCL; copied to openbao/policies/<policy_name>.hcl")
submit_p.add_argument("--slug", required=True)
submit_p.add_argument("--requester", required=True)
submit_p.add_argument("--profile", default=str(DEFAULT_PROFILE))
submit_p.add_argument("--commit", action="store_true", help="commit on a new ccr/<id>-<slug> branch")
submit_p.add_argument("--push", action="store_true", help="commit and push the ccr/* branch")
submit_p.add_argument("--notify", action="store_true")
submit_p.add_argument("--state-hub-url", default=state_hub_url)
submit_p.set_defaults(func=lambda args: command_submit(args, cc))

View file

@ -2384,6 +2384,9 @@ def build_parser() -> argparse.ArgumentParser:
scaffold.add_argument("--catalog-manifest")
scaffold.set_defaults(func=lambda args: command_scaffold(args, generated_policy_hcl, auth_payload))
from ccr_intake import add_parsers as add_intake_parsers
add_intake_parsers(sub, sys.modules[__name__], os.environ.get("STATE_HUB_URL", "http://127.0.0.1:8000"))
from provisioning_window import command_window
window = sub.add_parser("window-checklist", help="Render an offline attended-window preparation checklist; never execute")
window.add_argument("--window-file", required=True)

199
tests/test_ccr_intake.py Normal file
View file

@ -0,0 +1,199 @@
from __future__ import annotations
import argparse
import importlib.util
import os
import shutil
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
REPO_DIR = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_DIR / "scripts"))
SPEC = importlib.util.spec_from_file_location(
"credential_change", REPO_DIR / "scripts/credential-change.py"
)
credential_change = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
sys.modules[SPEC.name] = credential_change
SPEC.loader.exec_module(credential_change)
import ccr_intake # noqa: E402
cc = credential_change
STANDARD = "CCR-2026-0051-mas-tem-relay-smtp.yaml"
POLICY = "openbao/policies/workload-kv-read-mas-tem-relay.hcl"
class CcrIntakeTests(unittest.TestCase):
"""Run against a scratch repo root so nothing is written into the real tree."""
def setUp(self) -> None:
self.root = Path(tempfile.mkdtemp())
self.addCleanup(shutil.rmtree, self.root, True)
self.ccrs = self.root / "credential-change-requests"
self.ccrs.mkdir()
(self.root / "openbao/policies").mkdir(parents=True)
shutil.copy2(REPO_DIR / "credential-change-requests" / STANDARD, self.ccrs / STANDARD)
shutil.copy2(REPO_DIR / POLICY, self.root / POLICY)
for target in (cc, ccr_intake):
patcher = mock.patch.object(target, "REPO_DIR", self.root)
patcher.start()
self.addCleanup(patcher.stop)
env = mock.patch.dict(os.environ, {"CCR_DIR": str(self.ccrs)})
env.start()
self.addCleanup(env.stop)
self.profile, _digest = ccr_intake.load_profile()
self.path = self.ccrs / STANDARD
def edit(self, change) -> None:
data = cc.load_yaml(self.path)
change(data)
cc.dump_yaml(self.path, data)
def violations(self) -> list[str]:
return ccr_intake.standard_violations(cc.load_yaml(self.path), self.profile, self.ccrs, cc)
def assert_violation(self, change, fragment: str) -> None:
self.edit(change)
found = self.violations()
self.assertTrue(any(fragment in item for item in found), found)
def test_standard_lane_passes_and_records_review_without_approval(self) -> None:
self.assertEqual(ccr_intake.review_standard(self.path, cc), [])
data = cc.load_yaml(self.path)
self.assertEqual(data["status"], "proposed")
comment = data["review"]["comments"][-1]
self.assertEqual(comment["decision"], "approved")
self.assertIn("ccr-standard-lane v1 sha256:", comment["comment"])
self.assertTrue(comment["reviewer"].startswith("railiance-platform-owner"))
self.assertIn("apply requires status approved, got proposed", cc.apply_blockers(data))
def test_check_mode_does_not_write(self) -> None:
before = self.path.read_text()
ccr_intake.review_standard(self.path, cc, write=False)
self.assertEqual(self.path.read_text(), before)
def test_out_of_profile_records_needs_platform_review(self) -> None:
self.edit(lambda d: d["risk"].update(classification="high"))
self.assertTrue(ccr_intake.review_standard(self.path, cc))
comment = cc.load_yaml(self.path)["review"]["comments"][-1]
self.assertEqual(comment["decision"], "needs_platform_review")
self.assertEqual(cc.load_yaml(self.path)["status"], "proposed")
def test_requires_proposed_status(self) -> None:
self.edit(lambda d: d.update(status="approved"))
with self.assertRaises(SystemExit):
ccr_intake.review_standard(self.path, cc)
def test_metadata_read_rejected(self) -> None:
self.assert_violation(lambda d: d["openbao"].update(metadata_read=True), "metadata_read")
def test_kv_path_outside_prefixes_rejected(self) -> None:
self.assert_violation(
lambda d: d["openbao"].update(kv_path="platform/operators/founder/x"), "outside the standard"
)
def test_supplier_path_needs_exactly_two_segments(self) -> None:
self.assert_violation(
lambda d: d["openbao"].update(kv_path="platform/suppliers/scaleway/a/b"), "outside the standard"
)
def test_policy_file_drift_rejected(self) -> None:
(self.root / POLICY).write_text('path "platform/data/suppliers/*" {\n capabilities = ["read"]\n}\n')
self.assertTrue(any("differs" in v for v in self.violations()))
def test_long_ttl_rejected(self) -> None:
self.assert_violation(lambda d: d["openbao"]["auth"].update(token_max_ttl="1h"), "token_max_ttl")
def test_default_policy_rejected(self) -> None:
self.assert_violation(
lambda d: d["openbao"]["auth"].update(token_no_default_policy=False), "token_no_default_policy"
)
def test_role_prefix_and_audience_enforced(self) -> None:
self.assert_violation(lambda d: d["openbao"]["auth"].update(role="mas-eso"), "role must start")
self.assert_violation(lambda d: d["openbao"]["auth"].update(audience="vault"), "audience")
def test_two_service_accounts_rejected(self) -> None:
self.assert_violation(
lambda d: d["openbao"]["auth"]["bound_claims"]["service_account_names"].append("other"),
"service account",
)
def test_reserved_namespace_rejected(self) -> None:
self.assert_violation(
lambda d: d["openbao"]["auth"]["bound_claims"].update(service_account_namespaces=["external-secrets"]),
"reserved",
)
def test_second_reader_of_path_rejected(self) -> None:
other = cc.load_yaml(self.path)
other.update(id="CCR-2026-0052", status="active")
cc.dump_yaml(self.ccrs / "CCR-2026-0052-other.yaml", other)
self.assertTrue(any("already has a reader: CCR-2026-0052" in v for v in self.violations()))
def test_cancelled_reader_is_not_a_conflict(self) -> None:
other = cc.load_yaml(self.path)
other.update(id="CCR-2026-0052", status="cancelled")
cc.dump_yaml(self.ccrs / "CCR-2026-0052-other.yaml", other)
self.assertEqual(self.violations(), [])
def test_next_id_and_duplicate_detection(self) -> None:
self.assertEqual(ccr_intake.next_ccr_id(self.ccrs, 2026), "CCR-2026-0052")
self.assertEqual(ccr_intake.next_ccr_id(self.ccrs, 2027), "CCR-2027-0001")
self.assertEqual(ccr_intake.duplicate_ids(self.ccrs, cc), {})
shutil.copy2(self.path, self.ccrs / "CCR-2026-0051-race.yaml")
self.assertIn("CCR-2026-0051", ccr_intake.duplicate_ids(self.ccrs, cc))
def submit_args(self, **overrides) -> argparse.Namespace:
draft_dir = Path(tempfile.mkdtemp())
self.addCleanup(shutil.rmtree, draft_dir, True)
draft = cc.load_yaml(self.path)
draft.update(id="CCR-DRAFT-MAS", status="draft")
draft["openbao"].update(
kv_path="platform/suppliers/scaleway/other-relay",
policy_name="workload-kv-read-other-relay",
policy_file="openbao/policies/workload-kv-read-other-relay.hcl",
)
draft["openbao"]["auth"]["policies"] = ["workload-kv-read-other-relay"]
draft["review"]["comments"] = [{"reviewer": "someone", "decision": "approved", "comment": "x"}]
cc.dump_yaml(draft_dir / "draft.yaml", draft)
(draft_dir / "policy.hcl").write_text(
(self.root / POLICY).read_text().replace("mas-tem-relay", "other-relay")
)
values = dict(ccr=str(draft_dir / "draft.yaml"), policy=str(draft_dir / "policy.hcl"),
slug="other-relay", requester="ops-mason", profile=str(ccr_intake.DEFAULT_PROFILE))
values.update(overrides)
return argparse.Namespace(**values)
def test_submit_allocates_id_copies_policy_and_reviews(self) -> None:
destination, violations = ccr_intake.submit(self.submit_args(), cc)
self.assertEqual(violations, [])
self.assertEqual(destination.name, "CCR-2026-0052-other-relay.yaml")
data = cc.load_yaml(destination)
self.assertEqual((data["id"], data["status"]), ("CCR-2026-0052", "proposed"))
self.assertEqual(len(data["review"]["comments"]), 1)
self.assertTrue(data["review"]["comments"][0]["reviewer"].startswith("railiance-platform-owner"))
self.assertTrue((self.root / "openbao/policies/workload-kv-read-other-relay.hcl").exists())
def test_submit_refuses_unlisted_requester(self) -> None:
with self.assertRaises(SystemExit):
ccr_intake.submit(self.submit_args(requester="random-agent"), cc)
def test_submit_refuses_policy_overwrite(self) -> None:
(self.root / "openbao/policies/workload-kv-read-other-relay.hcl").write_text("different\n")
with self.assertRaises(SystemExit):
ccr_intake.submit(self.submit_args(), cc)
def test_submit_refuses_secret_markers(self) -> None:
args = self.submit_args()
Path(args.ccr).write_text(Path(args.ccr).read_text() + "# hvs.notarealtoken\n")
with self.assertRaises(SystemExit):
ccr_intake.submit(args, cc)
if __name__ == "__main__":
unittest.main()

View file

@ -4,13 +4,14 @@ type: workplan
title: "Self-service CCR intake: clients file and progress CCRs without a platform worker"
domain: financials
repo: railiance-platform
status: proposed
status: active
owner: codex
created: "2026-10-18"
updated: "2026-10-18"
flavor: implementation
origin: cross-repo
origin_ref: ops-mason daa119e6 / 049afa1f; MASON-IN-0005
state_hub_workstream_id: "bad0cc9b-e8ad-572b-8d0d-fb3adb208b4a"
---
Demand: every credential change request today waits on a railiance-platform
@ -39,11 +40,14 @@ Invariants (unchanged by this plan):
## Design
1. **Submit.** The client runs, in a railiance-platform checkout,
`scripts/credential-change.py submit --bundle <dir>` (or `make ccr-submit`).
It copies the CCR and policy file, allocates the next free `CCR-YYYY-NNNN`
from the files on the current `main`, sets `status: proposed`, validates,
commits on branch `ccr/<id>-<slug>` and pushes. A push or merge race simply
re-allocates; git is the allocator, so no central counter or agent is needed.
`scripts/credential-change.py submit --ccr <draft> --policy <hcl> --slug
<slug> --requester <agent> --push`. It copies the CCR and policy file,
allocates the next free `CCR-YYYY-NNNN` from the files in the checkout,
sets `status: proposed`, clears client-written review comments, validates,
commits on branch `ccr/<id>-<slug>` and pushes. Only profile `submitters`
may submit (ops-mason only, founder decision 2026-10-18). A race produces
two files with the same id; `check-ids` in CI refuses the second, which
re-submits. Git is the allocator, so no central counter or agent is needed.
2. **Platform review as policy-as-code.** `credential-change.py
review-standard <ccr>` checks the standard lane profile
(`registry/ccr-standard-profile.yaml`): `workload-kv-read`; kubernetes auth;
@ -53,8 +57,10 @@ Invariants (unchanged by this plan):
max ttl ≤ 15m; `token_no_default_policy: true`; one SA in one namespace;
no other CCR in `proposed|approved|active` reading the same path. A pass
appends the `railiance-platform-owner` review comment with the profile
version and digest. A fail writes the reasons and marks the CCR
`needs_platform_review`, which sends one hub message to `railiance-platform`.
version and digest. A fail appends a `needs_platform_review` review
comment with the reasons and, with `--notify`, sends one hub message to
`railiance-platform`. Either way the CCR status stays `proposed`; the
automated comment never sets `approved`.
3. **CI gate.** A Forgejo Action on `ccr/*` branches runs `validate`,
`review-standard` and the secret scan; only a green branch fast-forwards to
`main`. The client merges its own green branch.
@ -72,32 +78,45 @@ Invariants (unchanged by this plan):
```task
id: RPF-WP-0061-T01
status: todo
status: done
priority: high
state_hub_task_id: "c18752a6-6126-5410-bbb6-1799ccd63558"
```
Write `registry/ccr-standard-profile.yaml` from the existing approved ESO
lanes (CCR-2026-0033, -0035, -0041, -0050, -0051 shapes). Record which past
CCRs would and would not have passed it.
Done 2026-10-18: `registry/ccr-standard-profile.yaml` v1. Calibrated on all 51
CCRs: only CCR-2026-0051 passes; the profile comment lists the first failing
rule for every other CCR. Stricter than past practice by design.
## T02 — `submit` and `review-standard` subcommands
```task
id: RPF-WP-0061-T02
status: todo
status: done
priority: high
state_hub_task_id: "5dd5305f-e1e4-52f5-88aa-f0090e072d0f"
```
Add both subcommands to `scripts/credential-change.py` with tests covering
id allocation, duplicate-path refusal, policy-file mismatch and every profile
rule. `review-standard` never touches OpenBao.
Done 2026-10-18: `scripts/ccr_intake.py` adds `submit`, `review-standard`
(`--check`, `--notify`) and `check-ids` to `credential-change.py`;
`tests/test_ccr_intake.py` has 20 tests against a scratch repo root. Two
existing failures in `tests/test_credential_change.py` (CCR-2026-0035 schema
gaps, core-hub database policy assertion) predate this work.
## T03 — CI gate on `ccr/*` branches
```task
id: RPF-WP-0061-T03
status: todo
priority: medium
state_hub_task_id: "0fd97924-1bc7-5aa1-a520-c6c5737bf09b"
```
Forgejo Action running validate, review-standard and the secret scan; confirm
@ -109,6 +128,7 @@ which identities may push `ccr/*` (ops-mason first).
id: RPF-WP-0061-T04
status: todo
priority: medium
state_hub_task_id: "15ac9eae-9890-5140-b1ca-cd0ad3ca77ac"
```
## T05 — Client documentation and first self-service CCR
@ -117,6 +137,7 @@ priority: medium
id: RPF-WP-0061-T05
status: todo
priority: medium
state_hub_task_id: "27797fa5-3d47-5037-a98c-2d269198d1c4"
```
Document the flow in `docs/credential-change-approval.md` and notify