RPF-WP-0061-T03: ccr/* branch gate and CI workflow; first automated platform review on CCR-2026-0051
Some checks failed
CCR intake gate / gate (push) Failing after 25s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

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:37:05 +02:00
parent b6148da443
commit 10fddbc98f
5 changed files with 191 additions and 7 deletions

View file

@ -0,0 +1,43 @@
# RPF-WP-0061-T03: self-service CCR intake gate.
# ccr/* branches: exactly one new in-profile CCR (+ its new policy file) from a
# listed submitter, nothing else changed. main: post-merge id uniqueness.
# Source comes from the exact-revision archive, as in keycape-factor-acceptance.
name: CCR intake gate
on:
push:
branches: ["ccr/**", main]
workflow_dispatch:
jobs:
gate:
runs-on: ubuntu-latest
container:
image: python:3.12-slim@sha256:d764629ce0ddd8c71fd371e9901efb324a95789d2315a47db7e4d27e78f1b0e9
steps:
- name: Fetch exact revision and main
run: |
set -eu
python3 - <<'PYCODE'
import os,tarfile,tempfile,urllib.request
from pathlib import Path
base='https://forgejo.coulomb.social/'+os.environ['GITHUB_REPOSITORY']+'/archive/'
for name,ref in (('branch',os.environ['GITHUB_SHA']),('main','main')):
root=Path('/tmp/ccr-intake')/name;root.mkdir(parents=True)
with urllib.request.urlopen(base+ref+'.tar.gz',timeout=30) as response,tempfile.TemporaryFile() as archive:
archive.write(response.read());archive.seek(0)
with tarfile.open(fileobj=archive,mode='r:gz') as tar:tar.extractall(root,filter='data')
PYCODE
pip install --quiet --disable-pip-version-check pyyaml==6.0.2
- name: Gate ccr branch
if: startsWith(github.ref, 'refs/heads/ccr/')
run: |
set -eu
cd /tmp/ccr-intake/branch/*
main_root=$(echo /tmp/ccr-intake/main/*)
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -p 'test_ccr_intake.py'
python3 scripts/credential-change.py check-branch --branch "$GITHUB_REF" --main-root "$main_root"
- name: Check CCR ids on main
if: github.ref == 'refs/heads/main'
run: |
set -eu
cd /tmp/ccr-intake/branch/*
python3 scripts/credential-change.py check-ids

View file

@ -17,7 +17,13 @@ review:
- founder
- railiance-platform-owner
- mail-address-semantics-owner
comments: []
comments:
- at: '2026-10-18T10:36:53+00:00'
reviewer: railiance-platform-owner (policy-as-code ccr-standard-lane v1)
decision: approved
comment: 'Standard lane: passes ccr-standard-lane v1 sha256:5481be201456739b9e01b14b0b11211923d7d838a2e71e2e417af038a05aec95.
Platform-owner source review only; founder approval and confirm-binding still
required.'
target:
domain: infotech
tenant: platform
@ -71,13 +77,13 @@ risk:
limited to namespace mas.
- New read scope on a supplier path; founder approval required. No other lane reads
this path (ops-mason live survey 2026-10-18T10:13:55Z).
- Apply may be run by ops-mason under ops-mason-construct (MASON-IN-0005) once
installed (MASON-WP-0015-T02) and this CCR is approved; that grant covers
workload-kv-read-* policies and external-secrets-* roles only.
- Apply may be run by ops-mason under ops-mason-construct (MASON-IN-0005) once installed
(MASON-WP-0015-T02) and this CCR is approved; that grant covers workload-kv-read-*
policies and external-secrets-* roles only.
verification:
positive:
- Exact SA/namespace/audience login succeeds; ESO Ready/SecretSynced; metadata
version matches admitted KV version 2 without printing values.
- Exact SA/namespace/audience login succeeds; ESO Ready/SecretSynced; metadata version
matches admitted KV version 2 without printing values.
negative:
- Wrong SA, namespace or audience login refused; sibling paths under platform/suppliers/scaleway,
list, metadata, write and delete denied.

View file

@ -220,6 +220,64 @@ def command_check_ids(args: argparse.Namespace, cc: Any) -> int:
return 1 if duplicates else 0
def tree_digests(root: Path) -> dict[str, str]:
return {
str(path.relative_to(root)): hashlib.sha256(path.read_bytes()).hexdigest()
for path in root.rglob("*")
if path.is_file() and ".git" not in path.relative_to(root).parts
and "__pycache__" not in path.parts
}
def branch_gate(main_root: Path, stem: str, cc: Any, profile_path: Path = DEFAULT_PROFILE) -> list[str]:
"""CI gate for a ccr/<stem> branch checked out at REPO_DIR (RPF-WP-0061-T03).
The branch may add exactly one new CCR and its new policy file, nothing
else; the CCR must be an in-profile submission from a listed submitter with
a policy-as-code review against the current profile digest.
"""
errors: list[str] = []
ccr_rel = f"credential-change-requests/{stem}.yaml"
path = REPO_DIR / ccr_rel
if not CCR_FILE_RE.match(path.name) or not path.exists():
return [f"branch ccr/{stem} must add {ccr_rel}"]
if (main_root / ccr_rel).exists():
errors.append(f"{ccr_rel} already exists on main")
ccr, validation_errors, _warnings = cc.validate_ccr(path)
errors.extend(f"validate: {item}" for item in validation_errors)
policy_rel = (ccr.get("openbao") or {}).get("policy_file")
allowed = {ccr_rel} | ({policy_rel} if policy_rel else set())
branch, main = tree_digests(REPO_DIR), tree_digests(main_root)
changed = {k for k in branch.keys() | main.keys() if branch.get(k) != main.get(k)}
for extra in sorted(changed - allowed):
errors.append(f"ccr branch may not change {extra}")
if policy_rel and policy_rel in main and main[policy_rel] != branch.get(policy_rel):
errors.append(f"{policy_rel} exists on main; a submission may not modify it")
if ccr.get("status") != "proposed":
errors.append(f"status must be proposed, got {ccr.get('status')}")
profile, digest = load_profile(profile_path)
if (ccr.get("requester") or {}).get("agent") not in profile["submitters"]:
errors.append(f"requester is not a listed submitter {profile['submitters']}")
errors.extend(standard_violations(ccr, profile, path.parent, cc))
comments = (ccr.get("review") or {}).get("comments") or []
if len(comments) != 1 or comments[0].get("decision") != "approved" or digest not in str(
comments[0].get("comment")
):
errors.append("review must hold exactly the policy-as-code approval for the current profile")
errors.extend(f"duplicate id: {k} {v}" for k, v in duplicate_ids(path.parent, cc).items())
return errors
def command_check_branch(args: argparse.Namespace, cc: Any) -> int:
stem = args.branch.removeprefix("refs/heads/").removeprefix("ccr/")
errors = branch_gate(Path(args.main_root).resolve(), stem, cc)
for item in errors:
print(f"[FAIL] ccr/{stem}: {item}")
if not errors:
print(f"[OK] ccr/{stem}: one in-profile CCR, nothing else changed")
return 1 if errors else 0
def git(*argv: str) -> None:
subprocess.run(["git", "-C", str(REPO_DIR), *argv], check=True)
@ -294,6 +352,13 @@ def add_parsers(sub: Any, cc: Any, state_hub_url: str) -> None:
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))
check_branch = sub.add_parser(
"check-branch", help="CI gate: a ccr/* branch adds exactly one in-profile CCR and its policy"
)
check_branch.add_argument("--branch", required=True, help="ccr/<id>-<slug> or refs/heads/ccr/...")
check_branch.add_argument("--main-root", required=True, help="tree of main to compare against")
check_branch.set_defaults(func=lambda args: command_check_branch(args, cc))
submit_p = sub.add_parser(
"submit",
help="File a client CCR draft under the next CCR id and run the standard review",

View file

@ -194,6 +194,59 @@ class CcrIntakeTests(unittest.TestCase):
with self.assertRaises(SystemExit):
ccr_intake.submit(args, cc)
def submitted_branch(self) -> tuple[Path, str]:
main_root = Path(tempfile.mkdtemp())
self.addCleanup(shutil.rmtree, main_root, True)
shutil.copytree(self.root, main_root, dirs_exist_ok=True)
destination, _violations = ccr_intake.submit(self.submit_args(), cc)
return main_root, destination.stem
def test_branch_gate_accepts_clean_submission(self) -> None:
main_root, stem = self.submitted_branch()
self.assertEqual(ccr_intake.branch_gate(main_root, stem, cc), [])
def test_branch_gate_refuses_extra_changes(self) -> None:
main_root, stem = self.submitted_branch()
(self.root / "registry").mkdir()
(self.root / "registry/ccr-standard-profile.yaml").write_text("submitters: [anyone]\n")
errors = ccr_intake.branch_gate(main_root, stem, cc)
self.assertIn("ccr branch may not change registry/ccr-standard-profile.yaml", errors)
def test_branch_gate_refuses_modified_existing_policy(self) -> None:
main_root, stem = self.submitted_branch()
path = self.ccrs / f"{stem}.yaml"
data = cc.load_yaml(path)
data["openbao"]["policy_file"] = POLICY
cc.dump_yaml(path, data)
(self.root / POLICY).write_text((self.root / POLICY).read_text() + "# widened\n")
errors = ccr_intake.branch_gate(main_root, stem, cc)
self.assertTrue(any("may not modify" in e for e in errors), errors)
def test_branch_gate_refuses_hand_edited_review(self) -> None:
main_root, stem = self.submitted_branch()
path = self.ccrs / f"{stem}.yaml"
data = cc.load_yaml(path)
data["review"]["comments"].append({"reviewer": "founder", "decision": "approved", "comment": "ok"})
cc.dump_yaml(path, data)
errors = ccr_intake.branch_gate(main_root, stem, cc)
self.assertTrue(any("policy-as-code approval" in e for e in errors), errors)
def test_branch_gate_refuses_unlisted_requester(self) -> None:
main_root, stem = self.submitted_branch()
path = self.ccrs / f"{stem}.yaml"
data = cc.load_yaml(path)
data["requester"]["agent"] = "someone-else"
cc.dump_yaml(path, data)
errors = ccr_intake.branch_gate(main_root, stem, cc)
self.assertTrue(any("listed submitter" in e for e in errors), errors)
def test_branch_gate_refuses_existing_ccr(self) -> None:
main_root = Path(tempfile.mkdtemp())
self.addCleanup(shutil.rmtree, main_root, True)
shutil.copytree(self.root, main_root, dirs_exist_ok=True)
errors = ccr_intake.branch_gate(main_root, Path(STANDARD).stem, cc)
self.assertTrue(any("already exists on main" in e for e in errors), errors)
if __name__ == "__main__":
unittest.main()

View file

@ -114,7 +114,7 @@ gaps, core-hub database policy assertion) predate this work.
```task
id: RPF-WP-0061-T03
status: todo
status: progress
priority: medium
state_hub_task_id: "0fd97924-1bc7-5aa1-a520-c6c5737bf09b"
```
@ -122,6 +122,23 @@ state_hub_task_id: "0fd97924-1bc7-5aa1-a520-c6c5737bf09b"
Forgejo Action running validate, review-standard and the secret scan; confirm
which identities may push `ccr/*` (ops-mason first).
2026-10-18: `.forgejo/workflows/ccr-intake.yaml` runs on `ccr/**` and `main`
from exact-revision archives. On `ccr/*` it runs the intake tests and
`credential-change.py check-branch`. That command refuses the branch unless it
adds exactly one new CCR plus its new policy file and changes nothing else
(not the profile, not an existing policy). The CCR must validate, be
`proposed`, come from a listed submitter, re-pass the profile in CI and carry
exactly the policy-as-code approval for the current profile digest. On `main`
it runs `check-ids`. 26 tests. CCR-2026-0051 received the first automated
platform-owner review (still `proposed`).
Remaining, founder/Forgejo admin: a protected-branch rule on `ccr/*` that
allows pushes only from ops-mason's Forgejo identity. Forgejo cannot require
a check on main without blocking every direct push, so merging is a
convention backed by detection: ops-mason fast-forwards only a green branch,
and the main job plus `check-branch` evidence detect violations after the
fact.
## T04 — Hub decision on submit and client-run `sync-decision`
```task