RPF-WP-0054-T04: create-only broker grant keycape/client-secret-generate
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

- New CCR request type custody-generate-grant (scripts/broker_grant_lane.py):
  grant policy (create on keycape/+ data, read on metadata, deny on reserved
  entries, revoke-self; no update, data read, metadata write, list or
  delete), broker issuer policy and a 5m orphan non-renewable token role,
  with readback. Binds no identity, so the apply is inert until T01.
- CCR-2026-0040 (proposed), catalog grant (pilot, approval-required,
  exec-env only), generated policies, tests.
- T03 drops custom_metadata: a metadata write would also allow changing
  other entries' version settings.
- test_operator_matrix_lane follows CCR-2026-0039 now being applied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 469992@bnt-lap001
Assistant-Session: e4b8e420-df0a-441d-83ae-7f19130594d9
This commit is contained in:
codex 2026-10-09 19:08:25 +02:00
parent 60435ab53e
commit 857be4695f
8 changed files with 602 additions and 4 deletions

View file

@ -0,0 +1,75 @@
id: CCR-2026-0040
kind: credential-change-request
schema_version: 1
request_type: custody-generate-grant
title: Broker grant keycape/client-secret-generate (create-only, generate in custody)
status: proposed
created: '2026-10-09'
updated: '2026-10-09'
requester:
agent: railiance-platform
reason: RPF-WP-0054-T04. net-kingdom SecurityCaseType oidc-browser-relying-party step
mint-secret (lane keycape-client-secret-generate); ops-warden d9f889b1, ops-mason
789feb39. Founder chose the templated create-only grant 2026-10-09 (T02 option A).
review:
required: true
required_approvers:
- platform-owner
- key-cape-owner
comments: []
target:
domain: infotech
tenant: platform
workload: keycape
environment: production
purpose: Let the credential broker create a new KeyCape confidential-client secret at
platform/workloads/keycape/<client_id> once, without any value leaving custody.
openbao:
grant_id: keycape/client-secret-generate
kv_prefix: platform/workloads/keycape
reserved_entries:
- secrets-engine-openbao
policy_name: keycape-client-secret-generate
policy_file: openbao/policies/keycape-client-secret-generate.hcl
issuer_policy: credential-broker-keycape-client-secret-generate-issuer
issuer_policy_file: openbao/policies/credential-broker-keycape-client-secret-generate-issuer.hcl
token_role: keycape-client-secret-generate
token_ttl: 5m
founder_confirmation_items:
- This apply writes two policies and one token role. It binds the issuer policy to no
identity, so nothing can mint the token until a separate review binds it to the
caller named in RPF-WP-0054-T01.
- Create-only on keycape/+ means a token can create a new client entry once and can
never change an existing one. Its widest misuse is an unused entry for a client
that does not exist.
access_frontdoor:
type: none
catalog_id: none
risk:
level: medium
notes:
- No data read, no update, no metadata write, no list, no delete. Exact denies on
reserved entries take precedence over the + glob.
- The token lives at most 5m and only inside the broker process.
verification:
positive:
- Readback after apply matches source for both policies and the token role (done by
applier-apply; prints nothing).
- Disposable probe (T05) creates probe-secret-generate-<date> at version 1.
negative:
- A second create on the probe path, a data read, update, delete and any path outside
keycape/ are denied for the grant token (sys/capabilities-self).
- The reserved entry keycape/secrets-engine-openbao reports deny on data and metadata.
activation_conditions:
- Founder approval (two reviews).
- Issuer binding reviewed after RPF-WP-0054-T01; probe passed (T05).
evidence: []
lifecycle:
deactivate: Delete auth/token/roles/keycape-client-secret-generate, then both policies;
generated entries stay in custody with their cases.
rotate: Not applicable; rotation is a separate lane (RPF-WP-0054-T07).
compromised: Delete the token role and revoke outstanding tokens by accessor; review
metadata for entries without an accepted case.
state_hub:
workplan: RPF-WP-0054
task: RPF-WP-0054-T04

View file

@ -104,6 +104,82 @@ grants:
audit: {openbao_audit_required: true, state_hub_metadata_allowed: true, record_secret_values: false}
revocation: {required: true, by_accessor: true, on_exec_exit: true, on_denied_request: false}
- id: keycape/client-secret-generate
title: KeyCape confidential-client secret generation in custody
status: pilot
grant_class: approval-required
credential_type: openbao-token
issuer: openbao
audience: railiance-platform-credential-broker
description: >
RPF-WP-0054 / CCR-2026-0040. Short-lived token the broker uses inside its
own process to create platform/workloads/keycape/<client_id> once (CAS 0)
for an accepted oidc-browser-relying-party case. Create-only: it cannot
update, read values, write metadata, list or delete. The generated value
never leaves the broker process and the bao child's stdin; the caller gets a
receipt only. Founder decision 2026-10-09 (RPF-WP-0054-T02 option A).
openbao:
namespace: openbao
token_role: keycape-client-secret-generate
issuer_policy: credential-broker-keycape-client-secret-generate-issuer
policies:
- keycape-client-secret-generate
disallowed_policies:
- root
- platform-admin
mount_paths:
- platform/data/workloads/keycape/+
- platform/metadata/workloads/keycape/+
ttl:
default: 5m
max: 5m
renewable: false
requires_human_above: 5m
actors:
allowed_types:
- approved-agent
- human-operator
required_subject_binding: keycape-or-kubernetes-service-account
authorization:
flex_auth_required: false
flex_auth_mode: optional-preflight
approval_required: true
purpose_required: true
allowed_purpose_examples:
- oidc-browser-relying-party-mint-secret
delivery:
allowed:
- exec-env
preferred: exec-env
denied:
- chat
- state-hub-body
- git
- command-line-token-argument
- llm-prompt
exec_env:
variable: VAULT_TOKEN
child_only: true
redact_logs: true
audit:
openbao_audit_required: true
state_hub_metadata_allowed: true
record_secret_values: false
metadata_fields:
- grant_id
- actor
- subject
- purpose
- case_ref
- client_id
- kv_version
- lease_accessor
- status
revocation:
required: true
by_accessor: true
on_exec_exit: true
on_denied_request: false
- id: ops-warden/warden-sign
title: Ops Warden OpenBao SSH signing smoke token
status: pilot

View file

@ -0,0 +1,17 @@
# CCR-2026-0040 (RPF-WP-0054): the credential broker may mint keycape/client-secret-generate
# tokens only through this token role. Bind to the reviewed caller identity only.
path "auth/token/create/keycape-client-secret-generate" {
capabilities = ["create", "update"]
}
path "auth/token/lookup-accessor" {
capabilities = ["update"]
}
path "auth/token/revoke-accessor" {
capabilities = ["update"]
}
path "auth/token/lookup-self" {
capabilities = ["read"]
}
path "sys/capabilities-self" {
capabilities = ["update"]
}

View file

@ -0,0 +1,17 @@
# CCR-2026-0040 (RPF-WP-0054): grant keycape/client-secret-generate. Create-only: no update,
# data read, metadata write, list or delete. Exact deny beats the + glob.
path "platform/data/workloads/keycape/+" {
capabilities = ["create"]
}
path "platform/metadata/workloads/keycape/+" {
capabilities = ["read"]
}
path "platform/data/workloads/keycape/secrets-engine-openbao" {
capabilities = ["deny"]
}
path "platform/metadata/workloads/keycape/secrets-engine-openbao" {
capabilities = ["deny"]
}
path "auth/token/revoke-self" {
capabilities = ["update"]
}

View file

@ -0,0 +1,268 @@
"""custody-generate-grant CCRs: a create-only KV grant the credential broker mints.
RPF-WP-0054-T04. Loaded by scripts/credential-change.py for request_type
custody-generate-grant. The lane writes three reviewed objects and nothing else:
* the grant policy: ``create`` on ``<prefix>/+`` data, ``read`` on ``<prefix>/+``
metadata, explicit ``deny`` on reserved entries, ``revoke-self``. It has no
``update`` anywhere (an existing entry can never be overwritten), no data
read, no metadata write (custom_metadata writes would also let the token
change ``delete_version_after``/``max_versions`` of other entries), no list
and no delete;
* the broker issuer policy: create tokens only through this grant's token role;
* the token role: exactly the grant policy, orphan, non-renewable, no default
policy, at most 15 minutes.
Binding the issuer policy to the calling identity is a separate review once the
caller is known (RPF-WP-0054-T01); until then nothing can mint the token, so
the apply is inert. Nothing here reads, prints or accepts a secret value.
"""
from __future__ import annotations
import importlib.util
import re
from pathlib import Path
from typing import Any
import yaml
REQUEST_TYPE = "custody-generate-grant"
PREFIX_RE = re.compile(r"^platform/workloads/[a-z0-9-]+$")
ENTRY_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$")
NAME_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$")
CATALOG = "credential-grants/catalog.yaml"
MAX_TTL_SECONDS = 15 * 60
# Same list scripts/openbao-apply-token-grants.py writes; default is excluded by
# token_no_default_policy.
DISALLOWED_POLICIES = ["root", "platform-admin"]
def _service_jwt_lane():
spec = importlib.util.spec_from_file_location(
"service_jwt_lane", Path(__file__).resolve().parent / "service_jwt_lane.py"
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
_shared = _service_jwt_lane()
ttl_seconds = _shared.ttl_seconds
normalized = _shared.normalized
BaoError = _shared.BaoError
_run = _shared._run
_read_json = _shared._read_json
def _paths(prefix: str) -> tuple[str, str]:
mount, rest = prefix.split("/", 1)
return f"{mount}/data/{rest}", f"{mount}/metadata/{rest}"
def generated_policy_hcl(ccr: dict[str, Any]) -> str:
openbao = ccr["openbao"]
data, meta = _paths(openbao["kv_prefix"])
lines = [
f"# {ccr['id']} (RPF-WP-0054): grant {openbao['grant_id']}. Create-only: no update,",
"# data read, metadata write, list or delete. Exact deny beats the + glob.",
f'path "{data}/+" {{', ' capabilities = ["create"]', "}",
f'path "{meta}/+" {{', ' capabilities = ["read"]', "}",
]
for entry in openbao["reserved_entries"]:
for base in (data, meta):
lines += [f'path "{base}/{entry}" {{', ' capabilities = ["deny"]', "}"]
lines += ['path "auth/token/revoke-self" {', ' capabilities = ["update"]', "}"]
return "\n".join(lines) + "\n"
def generated_issuer_hcl(ccr: dict[str, Any]) -> str:
openbao = ccr["openbao"]
lines = [
f"# {ccr['id']} (RPF-WP-0054): the credential broker may mint {openbao['grant_id']}",
"# tokens only through this token role. Bind to the reviewed caller identity only.",
]
for path, caps in (
(f"auth/token/create/{openbao['token_role']}", '"create", "update"'),
("auth/token/lookup-accessor", '"update"'),
("auth/token/revoke-accessor", '"update"'),
("auth/token/lookup-self", '"read"'),
("sys/capabilities-self", '"update"'),
):
lines += [f'path "{path}" {{', f" capabilities = [{caps}]", "}"]
return "\n".join(lines) + "\n"
def role_payload(ccr: dict[str, Any]) -> dict[str, Any]:
openbao = ccr["openbao"]
return {
"allowed_policies": [openbao["policy_name"]],
"disallowed_policies": DISALLOWED_POLICIES,
"orphan": True,
"renewable": False,
"token_explicit_max_ttl": openbao["token_ttl"],
"token_no_default_policy": True,
"token_type": "service",
}
def _catalog_grant(repo_dir: Path, grant_id: str) -> dict[str, Any] | None:
catalog = yaml.safe_load((repo_dir / CATALOG).read_text(encoding="utf-8")) or {}
for grant in catalog.get("grants") or []:
if grant.get("id") == grant_id:
return grant
return None
def _check_file(repo_dir: Path, rel: str, expected: str, field: str, errors: list[str]) -> None:
path = repo_dir / rel
if not path.exists():
errors.append(f"{field} does not exist: {rel}")
elif normalized(path.read_text(encoding="utf-8")) != normalized(expected):
errors.append(f"{field} does not match the generated policy")
def validate(ccr: dict[str, Any], repo_dir: Path) -> list[str]:
errors: list[str] = []
openbao = ccr.get("openbao")
if not isinstance(openbao, dict):
return ["openbao must be an object"]
if (ccr.get("target") or {}).get("environment") != "production":
errors.append("target.environment must be production")
review = ccr.get("review") or {}
if review.get("required") is not True or not review.get("required_approvers"):
errors.append("review.required must be true with required_approvers")
prefix = str(openbao.get("kv_prefix") or "")
if not PREFIX_RE.match(prefix):
errors.append("openbao.kv_prefix must be platform/workloads/<owner>")
reserved = openbao.get("reserved_entries")
if not isinstance(reserved, list) or not all(ENTRY_RE.match(str(e)) for e in reserved):
errors.append("openbao.reserved_entries must list exact entry names")
for key in ("policy_name", "issuer_policy", "token_role"):
if not NAME_RE.match(str(openbao.get(key) or "")):
errors.append(f"openbao.{key} contains unsafe characters")
seconds = ttl_seconds(openbao.get("token_ttl"))
if seconds is None or not 0 < seconds <= MAX_TTL_SECONDS:
errors.append("openbao.token_ttl must be set and at most 15m")
if errors:
return errors
for key, file_key, expected in (
("policy_name", "policy_file", generated_policy_hcl(ccr)),
("issuer_policy", "issuer_policy_file", generated_issuer_hcl(ccr)),
):
rel = str(openbao.get(file_key) or "")
if rel != f"openbao/policies/{openbao[key]}.hcl":
errors.append(f"openbao.{file_key} must be openbao/policies/<{key}>.hcl")
else:
_check_file(repo_dir, rel, expected, f"openbao.{file_key}", errors)
grant = _catalog_grant(repo_dir, str(openbao.get("grant_id") or ""))
if grant is None:
errors.append(f"openbao.grant_id not in {CATALOG}")
else:
gb = grant.get("openbao") or {}
if gb.get("policies") != [openbao["policy_name"]]:
errors.append("catalog grant policies must be exactly [openbao.policy_name]")
if gb.get("token_role") != openbao["token_role"] or gb.get("issuer_policy") != openbao["issuer_policy"]:
errors.append("catalog grant token_role/issuer_policy must match the CCR")
if ttl_seconds((grant.get("ttl") or {}).get("max")) != seconds:
errors.append("catalog grant ttl.max must equal openbao.token_ttl")
return errors
def readiness_blockers(ccr: dict[str, Any], repo_dir: Path, allowed_statuses: set[str]) -> list[str]:
blockers: list[str] = []
if ccr.get("status") not in allowed_statuses:
blockers.append(
"applier requires approved, applied, verified, or active CCR status, "
f"got {ccr.get('status')}"
)
blockers.extend(validate(ccr, repo_dir))
return blockers
def dry_run_payload(ccr: dict[str, Any], repo_dir: Path, warnings: list[str]) -> dict[str, Any]:
openbao = ccr["openbao"]
return {
"id": ccr["id"],
"title": ccr["title"],
"status": ccr["status"],
"environment": ccr["target"]["environment"],
"warnings": warnings,
"source_artifacts": {},
"mutations": [
{"kind": "policy_write", "openbao_path": f"sys/policies/acl/{openbao['policy_name']}",
"policy_name": openbao["policy_name"], "source": openbao["policy_file"],
"body": generated_policy_hcl(ccr).rstrip()},
{"kind": "policy_write", "openbao_path": f"sys/policies/acl/{openbao['issuer_policy']}",
"policy_name": openbao["issuer_policy"], "source": openbao["issuer_policy_file"],
"body": generated_issuer_hcl(ccr).rstrip()},
{"kind": "token_role_write", "openbao_path": f"auth/token/roles/{openbao['token_role']}",
"payload": role_payload(ccr)},
{"kind": "readback_compare", "openbao_path": "both policies, token role",
"payload": {"prints": "booleans only"}},
],
"out_of_scope": [
"secret value reads or writes",
"binding the issuer policy to any identity (separate review after RPF-WP-0054-T01)",
"token creation",
"changes to any other policy or role",
],
"required_evidence": [
"CCR id and approval reference",
"readback equality for both policies and the token role",
"disposable probe (RPF-WP-0054-T05): create once, second create and data read denied",
],
}
def apply_details(ccr: dict[str, Any], actor: str) -> list[str]:
openbao = ccr["openbao"]
return [
f"Delegated metadata applier ran as {actor} using local bao CLI ambient authority.",
f"Policy metadata write: sys/policies/acl/{openbao['policy_name']}",
f"Issuer policy metadata write: sys/policies/acl/{openbao['issuer_policy']}",
f"Token role metadata write: auth/token/roles/{openbao['token_role']}",
"Readback matched source for both policies and the token role.",
"Issuer policy is not bound to any identity by this apply.",
"No secret values were read, written, printed, or accepted in argv.",
]
def readback_mismatches(ccr: dict[str, Any], repo_dir: Path, bao_bin: str) -> list[str]:
openbao = ccr["openbao"]
mismatches: list[str] = []
for name, expected in (
(openbao["policy_name"], generated_policy_hcl(ccr)),
(openbao["issuer_policy"], generated_issuer_hcl(ccr)),
):
text = _run(bao_bin, ["policy", "read", name], repo_dir, quiet=True, capture=True)
if normalized(text) != normalized(expected):
mismatches.append(f"policy {name} differs from source")
role = _read_json(bao_bin, ["read", f"auth/token/roles/{openbao['token_role']}"], repo_dir).get("data", {})
for key, want in role_payload(ccr).items():
got = role.get(key)
if key == "token_explicit_max_ttl":
same = ttl_seconds(got) == ttl_seconds(want)
elif key == "disallowed_policies":
same = sorted(got or []) == sorted(want)
else:
same = got == want
if not same:
mismatches.append(f"token role {key} differs")
return mismatches
def run_apply(ccr: dict[str, Any], repo_dir: Path, bao_bin: str, quiet: bool = False) -> None:
openbao = ccr["openbao"]
_run(bao_bin, ["policy", "write", openbao["policy_name"], openbao["policy_file"]], repo_dir, quiet)
_run(bao_bin, ["policy", "write", openbao["issuer_policy"], openbao["issuer_policy_file"]], repo_dir, quiet)
role = role_payload(ccr)
args = ["write", f"auth/token/roles/{openbao['token_role']}"]
for key, value in role.items():
rendered = ",".join(value) if isinstance(value, list) else str(value).lower() if isinstance(value, bool) else value
args.append(f"{key}={rendered}")
_run(bao_bin, args, repo_dir, quiet)
mismatches = readback_mismatches(ccr, repo_dir, bao_bin)
if mismatches:
raise BaoError("readback mismatch: " + "; ".join(mismatches))

View file

@ -823,6 +823,7 @@ def is_service_jwt(ccr: dict[str, Any]) -> bool:
LANE_MODULES = {
"service-jwt-login": "service_jwt_lane",
"workload-kv-operator-matrix": "operator_matrix_lane",
"custody-generate-grant": "broker_grant_lane",
}

View file

@ -0,0 +1,137 @@
"""RPF-WP-0054-T04: custody-generate-grant lane (CCR-2026-0040)."""
from __future__ import annotations
import importlib.util
import json
import os
import re
import stat
import sys
import tempfile
import unittest
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parents[1]
CCR_PATH = ROOT / "credential-change-requests/CCR-2026-0040-keycape-client-secret-generate-grant.yaml"
def load(name: str, rel: str):
spec = importlib.util.spec_from_file_location(name, ROOT / rel)
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
lane = load("broker_grant_lane", "scripts/broker_grant_lane.py")
cc = load("credential_change_cli_grant", "scripts/credential-change.py")
def ccr() -> dict:
return yaml.safe_load(CCR_PATH.read_text())
# Stateful fake: policies stored as text; token role key=value args stored the way
# OpenBao returns them (lists, booleans, seconds).
FAKE_BAO = r'''#!/usr/bin/env python3
import json, os, sys
path = os.environ["FAKE_BAO_STATE"]
state = json.load(open(path))
args = [a for a in sys.argv[1:] if a != "-format=json"]
if args[:2] == ["policy", "write"]:
state["policies"][args[2]] = open(args[3]).read()
elif args[:2] == ["policy", "read"]:
print(state["policies"][args[2]])
elif args[0] == "write":
role = {}
for kv in args[2:]:
k, v = kv.split("=", 1)
if k.endswith("_policies"): role[k] = v.split(",")
elif v in ("true", "false"): role[k] = v == "true"
elif k.endswith("_ttl"): role[k] = int(v[:-1]) * 60
else: role[k] = v
if state.get("tamper"): role["renewable"] = True
state["roles"][args[1]] = role
elif args[0] == "read":
print(json.dumps({"data": state["roles"][args[1]]}))
json.dump(state, open(path, "w"))
'''
def stanzas(text: str) -> dict[str, str]:
return dict(re.findall(r'path "([^"]+)"\s*{\s*capabilities = \[([^\]]*)\]', text))
class BrokerGrantLaneTests(unittest.TestCase):
def test_ccr_validates_and_waits_for_approval(self):
_ccr, errors, _w = cc.validate_ccr(CCR_PATH)
self.assertEqual(errors, [])
blockers = cc.applier_readiness_blockers(_ccr)
self.assertTrue(any("got proposed" in b for b in blockers), blockers)
def test_grant_policy_is_create_only(self):
policy = stanzas((ROOT / ccr()["openbao"]["policy_file"]).read_text())
self.assertEqual(policy["platform/data/workloads/keycape/+"], '"create"')
self.assertEqual(policy["platform/metadata/workloads/keycape/+"], '"read"')
for path in ("platform/data/workloads/keycape/secrets-engine-openbao",
"platform/metadata/workloads/keycape/secrets-engine-openbao"):
self.assertEqual(policy[path], '"deny"')
granted = " ".join(c for p, c in policy.items() if p != "auth/token/revoke-self")
for word in ("update", "delete", "list", "patch", "sudo"):
self.assertNotIn(f'"{word}"', granted)
self.assertFalse(any("*" in p for p in policy))
def test_issuer_policy_mints_only_through_this_role(self):
issuer = stanzas((ROOT / ccr()["openbao"]["issuer_policy_file"]).read_text())
creates = [p for p in issuer if p.startswith("auth/token/create")]
self.assertEqual(creates, ["auth/token/create/keycape-client-secret-generate"])
def test_validator_rejects_drift(self):
cases = [
("long ttl", lambda c: c["openbao"].update(token_ttl="1h")),
("prefix wildcard", lambda c: c["openbao"].update(kv_prefix="platform/workloads/*")),
("unknown grant", lambda c: c["openbao"].update(grant_id="keycape/other")),
("reserved glob", lambda c: c["openbao"].update(reserved_entries=["*"])),
("policy drift", lambda c: c["openbao"].update(reserved_entries=[])),
]
for label, mutate in cases:
with self.subTest(label):
c = ccr()
mutate(c)
self.assertNotEqual(lane.validate(c, ROOT), [], label)
def _apply(self, tamper=False):
tmp = Path(tempfile.mkdtemp())
(tmp / "state.json").write_text(json.dumps({"policies": {}, "roles": {}, "tamper": tamper}))
bao = tmp / "bao"
bao.write_text(FAKE_BAO)
bao.chmod(bao.stat().st_mode | stat.S_IEXEC)
os.environ["FAKE_BAO_STATE"] = str(tmp / "state.json")
error = None
try:
lane.run_apply(ccr(), ROOT, str(bao), quiet=True)
except lane.BaoError as exc:
error = exc
return error, json.loads((tmp / "state.json").read_text())
def test_apply_writes_three_objects_and_reads_back(self):
error, state = self._apply()
self.assertIsNone(error)
self.assertEqual(sorted(state["policies"]), [
"credential-broker-keycape-client-secret-generate-issuer",
"keycape-client-secret-generate",
])
role = state["roles"]["auth/token/roles/keycape-client-secret-generate"]
self.assertEqual(role["allowed_policies"], ["keycape-client-secret-generate"])
self.assertEqual(role["token_explicit_max_ttl"], 300)
def test_readback_detects_role_drift(self):
error, _state = self._apply(tamper=True)
self.assertIsNotNone(error)
self.assertIn("renewable", str(error))
if __name__ == "__main__":
unittest.main()

View file

@ -60,12 +60,19 @@ LIVE_BOUNDARY = 'path "platform/data/workloads/forgejo/forgejo-admin" {\n capab
class OperatorMatrixLaneTests(unittest.TestCase):
def test_ccr_validates_and_waits_for_approval_and_binding(self):
def test_ccr_validates_and_records_the_delegated_apply(self):
_ccr, errors, _w = cc.validate_ccr(CCR_PATH)
self.assertEqual(errors, [])
blockers = cc.applier_readiness_blockers(_ccr)
self.assertIn("applier requires confirmed OpenBao auth binding", blockers)
self.assertTrue(any("got proposed" in b for b in blockers))
self.assertEqual(_ccr["status"], "applied")
self.assertEqual(cc.applier_readiness_blockers(_ccr), [])
self.assertTrue(any(e["kind"] == "delegated_metadata_apply" and e["result"] == "passed"
for e in _ccr["verification"]["evidence"]))
def test_unconfirmed_binding_blocks_the_applier(self):
c = ccr()
c["openbao"]["auth"]["bound_claims_confirmed"] = False
self.assertIn("applier requires confirmed OpenBao auth binding",
cc.applier_readiness_blockers(c))
def test_policy_is_the_exact_matrix_and_nothing_else(self):
policy = (ROOT / ccr()["openbao"]["policy_file"]).read_text()