RPF-WP-0061: option A recorded, client guide (T05), fixture independent of live CCR state
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
45c8d9370e
commit
04f18c8c73
4 changed files with 86 additions and 6 deletions
|
|
@ -303,3 +303,61 @@ Use the whynot-design npm token lane as the pilot:
|
|||
|
||||
Once that path feels good, reuse it for the sibling workload-KV lanes and the
|
||||
credential broker's OpenBao token-role gates.
|
||||
|
||||
## Self-Service Intake (RPF-WP-0061)
|
||||
|
||||
Clients file standard CCRs without a railiance-platform worker session. The
|
||||
platform's judgement is in [`registry/ccr-standard-profile.yaml`](../registry/ccr-standard-profile.yaml);
|
||||
`review-standard` applies it, and `.forgejo/workflows/ccr-intake.yaml` re-checks
|
||||
it in CI. Allowed submitters: **ops-mason** only (profile `submitters`).
|
||||
|
||||
**In profile** means: a `workload-kv-read` lane on mount `platform`; one exact path
|
||||
`platform/workloads/<a>/<b>[/<c>]` or `platform/suppliers/<supplier>/<account>`;
|
||||
`metadata_read: false`; a policy file equal to the generated HCL; Kubernetes auth
|
||||
with role `external-secrets-*`, audience `openbao`, all ttls 15m or less,
|
||||
`token_no_default_policy: true`; one service account in one non-platform
|
||||
namespace; risk `low` or `medium`; and no other live CCR reading the path.
|
||||
Everything else (OIDC roles, AppRole, token roles, second readers, high risk)
|
||||
still goes to the `railiance-platform` inbox.
|
||||
|
||||
### Client steps
|
||||
|
||||
Run these in a railiance-platform checkout that is up to date with `main`:
|
||||
|
||||
```bash
|
||||
# 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> \
|
||||
--requester ops-mason --push --notify
|
||||
# [NEEDS-PLATFORM] lines mean out of profile: stop. --notify messaged
|
||||
# railiance-platform. Do not edit the review comment to get past it.
|
||||
|
||||
# 2. Wait for the "CCR intake gate" run on ccr/<id>-<slug> to pass, then
|
||||
# fast-forward main to it. Never merge a red or unchecked ccr/* branch.
|
||||
git switch main && git merge --ff-only ccr/<id>-<slug> && git push origin main
|
||||
|
||||
# 3. Open the founder decision and commit the decision id.
|
||||
python3 scripts/credential-change.py request-decision <id>
|
||||
git commit -m "<id>: request founder decision" -- credential-change-requests/<id>-<slug>.yaml
|
||||
git push origin main
|
||||
```
|
||||
|
||||
After step 3, the client waits. 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
|
||||
verified (RPF-WP-0061 follow-up B), the sync must come from someone other than
|
||||
the requester. After the CCR is `approved`, the usual steps apply:
|
||||
`confirm-binding` against native state, apply (ops-mason under
|
||||
ops-mason-construct for in-profile lanes, otherwise the applier), then
|
||||
`record-evidence`.
|
||||
|
||||
### Known limits
|
||||
|
||||
- All workstation agents push to Forgejo as the same account, so `ccr/*`
|
||||
cannot yet be restricted to ops-mason. The submitter list and the
|
||||
merge-only-green rule are enforced by tooling and convention, not by Forgejo.
|
||||
The main-branch `check-ids` job and `check-branch` detect violations after the
|
||||
fact (follow-up C).
|
||||
- The automated review is a source review only. It never sets `approved` and
|
||||
never touches OpenBao.
|
||||
|
|
|
|||
|
|
@ -295,7 +295,9 @@ def decision_payload(ccr: dict[str, Any], cc: Any) -> dict[str, Any]:
|
|||
f"- Risk: {(ccr.get('risk') or {}).get('classification')}",
|
||||
f"- Platform review: {review.get('reviewer')}: {review.get('comment')}",
|
||||
"",
|
||||
cc.render_decision_templates(ccr),
|
||||
# The link line would be a placeholder: the decision does not exist yet.
|
||||
*[line for line in cc.render_decision_templates(ccr).splitlines()
|
||||
if not line.startswith("Decision link:")],
|
||||
"",
|
||||
"Approval permits apply after confirm-binding; it exposes and stores no value.",
|
||||
])
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ class CcrIntakeTests(unittest.TestCase):
|
|||
self.addCleanup(env.stop)
|
||||
self.profile, _digest = ccr_intake.load_profile()
|
||||
self.path = self.ccrs / STANDARD
|
||||
# The live CCR moves through approval; the fixture must stay pre-approval.
|
||||
self.edit(lambda d: (d.update(status="proposed"), d["review"].update(comments=[]),
|
||||
d.pop("state_hub", None)))
|
||||
|
||||
def edit(self, change) -> None:
|
||||
data = cc.load_yaml(self.path)
|
||||
|
|
@ -254,6 +257,7 @@ class CcrIntakeTests(unittest.TestCase):
|
|||
payload = post.call_args.args[2]
|
||||
self.assertIn("APPROVE: CCR-2026-0051", payload["description"])
|
||||
self.assertNotIn("status", payload)
|
||||
self.assertNotIn("Decision link", payload["description"])
|
||||
state_hub = cc.load_yaml(self.path)["state_hub"]
|
||||
self.assertEqual(state_hub["decision_id"], "dec-1")
|
||||
self.assertEqual(cc.load_yaml(self.path)["status"], "proposed")
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ gaps, core-hub database policy assertion) predate this work.
|
|||
|
||||
```task
|
||||
id: RPF-WP-0061-T03
|
||||
status: progress
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "0fd97924-1bc7-5aa1-a520-c6c5737bf09b"
|
||||
```
|
||||
|
|
@ -143,7 +143,7 @@ fact.
|
|||
|
||||
```task
|
||||
id: RPF-WP-0061-T04
|
||||
status: progress
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "15ac9eae-9890-5140-b1ca-cd0ad3ca77ac"
|
||||
```
|
||||
|
|
@ -163,15 +163,31 @@ platform worker never verified the resolver) but self-service makes it
|
|||
cheaper to exploit. Until it is closed, `sync-decision` on a self-service CCR
|
||||
stays a platform/founder step.
|
||||
|
||||
Founder decision 2026-10-18: option A. Clients submit, pass CI, get the
|
||||
automated review and open the decision themselves; the founder resolves it;
|
||||
`sync-decision` stays with a platform session or the founder. CCR-2026-0051 went
|
||||
through this path: founder resolved e633607e at 10:46:17Z; synced at 45c8d93.
|
||||
Follow-ups, not part of this plan: (B) founder approval provable through an
|
||||
MFA-backed OIDC login so clients can sync themselves; (C) separate Forgejo and
|
||||
OS identities per agent so `ccr/*` can be restricted. Both stem from the
|
||||
finding that every workstation agent pushes as Forgejo user `tegwick` with the
|
||||
key `workstation-automation`.
|
||||
|
||||
## T05 — Client documentation and first self-service CCR
|
||||
|
||||
```task
|
||||
id: RPF-WP-0061-T05
|
||||
status: todo
|
||||
status: wait
|
||||
priority: medium
|
||||
state_hub_task_id: "27797fa5-3d47-5037-a98c-2d269198d1c4"
|
||||
```
|
||||
|
||||
Document the flow in `docs/credential-change-approval.md` and notify
|
||||
ops-mason and ops-warden. Acceptance: one CCR filed, reviewed, approved and
|
||||
applied by a client with no railiance-platform worker session involved.
|
||||
ops-mason and ops-warden. Acceptance (narrowed by option A): ops-mason submits
|
||||
one CCR, passes the CI gate, merges and opens the decision with no
|
||||
railiance-platform worker session; only the post-approval sync is platform/founder.
|
||||
|
||||
2026-10-18: guide added as "Self-Service Intake" in
|
||||
`docs/credential-change-approval.md`; ops-mason and ops-warden notified. Waiting
|
||||
for ops-mason's first self-service submission, which also gives the first live
|
||||
`ccr/*` CI run.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue