diff --git a/credential-change-requests/CCR-2026-0001-whynot-design-npm-publish.yaml b/credential-change-requests/CCR-2026-0001-whynot-design-npm-publish.yaml index 8a1832e..c26ab94 100644 --- a/credential-change-requests/CCR-2026-0001-whynot-design-npm-publish.yaml +++ b/credential-change-requests/CCR-2026-0001-whynot-design-npm-publish.yaml @@ -3,7 +3,7 @@ kind: credential-change-request schema_version: 1 request_type: workload-kv-read title: whynot-design npm publish token lane -status: approved +status: applied created: '2026-06-27' updated: '2026-06-28' requester: @@ -73,8 +73,8 @@ access_frontdoor: selector: npm publish token command: warden access whynot-design-npm-publish --exec -- npm publish resolvable: false - readiness: template - activation: draft-until-ccr-verified + readiness: applied-pending-verify + activation: pending-positive-and-negative-caller-verification risk: classification: high notes: diff --git a/docs/whynot-design-npm-publish-handoff.md b/docs/whynot-design-npm-publish-handoff.md index a5bdcfb..541a02d 100644 --- a/docs/whynot-design-npm-publish-handoff.md +++ b/docs/whynot-design-npm-publish-handoff.md @@ -7,8 +7,8 @@ This is the next-session handoff for `CCR-2026-0001` and the - CCR: `CCR-2026-0001` - Decision: `e6381a56-6b04-4fd5-b2de-f3ef59cde888` -- Status: approved; non-secret OpenBao apply checks passed 2026-06-28 -- Front door: `template`, `resolvable=false` +- Status: applied; non-secret OpenBao apply checks passed 2026-06-28 +- Front door: `applied-pending-verify`, `resolvable=false` - Catalog id: `whynot-design-npm-publish` - Tenant/org: `coulomb` - Workload/project: `whynot-design` @@ -202,6 +202,6 @@ then `CCR-2026-0001` can move toward `active`, and ops-warden can mark Until then, keep the front door as: ```text -readiness = template +readiness = applied-pending-verify resolvable = false ``` diff --git a/docs/workload-kv-access-lanes.md b/docs/workload-kv-access-lanes.md index 0930830..8745e2f 100644 --- a/docs/workload-kv-access-lanes.md +++ b/docs/workload-kv-access-lanes.md @@ -30,7 +30,7 @@ Ops-warden batch follow-up: | KV mount | `platform` | | OpenBao CLI path | `platform/workloads/coulomb/whynot-design/npm-publish` | | Secret field | `NPM_AUTH_TOKEN` | -| Front-door readiness | `template`, `resolvable=false` until CCR verification | +| Front-door readiness | `applied-pending-verify`, `resolvable=false` until caller verification | | Read policy | `workload-kv-read-whynot-design-npm-publish` | | Policy file | `openbao/policies/workload-kv-read-whynot-design-npm-publish.hcl` | | OIDC auth mount | `netkingdom` | @@ -205,6 +205,6 @@ flex-auth ref: secret.read:whynot-design, if tenant policy requires it runbook: docs/workload-kv-access-lanes.md ``` -Until live provisioning and verification are complete, ops-warden should keep -the catalog entry in `template`/`draft` or equivalent non-active state with +Until positive and negative caller verification are complete, ops-warden should +keep the catalog entry in `applied-pending-verify`/non-active state with `resolvable=false`. diff --git a/scripts/credential-change.py b/scripts/credential-change.py index 95435d7..6552c25 100755 --- a/scripts/credential-change.py +++ b/scripts/credential-change.py @@ -34,6 +34,7 @@ ALLOWED_STATUSES = { "cancelled", } APPLY_ALLOWED_STATUSES = {"approved"} +POST_APPLY_STATUSES = {"applied", "verified", "active"} SECRET_MARKERS = [ "AGE-SECRET-KEY-1", "-----BEGIN PRIVATE KEY-----", @@ -435,8 +436,11 @@ def validate_or_exit(path: Path) -> tuple[dict[str, Any], list[str]]: def apply_blockers(ccr: dict[str, Any]) -> list[str]: blockers: list[str] = [] - if ccr.get("status") not in APPLY_ALLOWED_STATUSES: - blockers.append(f"apply requires status approved, got {ccr.get('status')}") + status = ccr.get("status") + if status in POST_APPLY_STATUSES: + return blockers + if status not in APPLY_ALLOWED_STATUSES: + blockers.append(f"apply requires status approved, got {status}") if ccr["openbao"]["auth"].get("bound_claims_confirmed") is not True: blockers.append("apply requires confirmed OpenBao auth binding") return blockers @@ -467,7 +471,8 @@ def status_payload(ccr: dict[str, Any], warnings: list[str]) -> dict[str, Any]: "title": ccr["title"], "status": ccr["status"], "request_type": ccr["request_type"], - "apply_allowed": not apply_blocked_by, + "apply_allowed": ccr.get("status") in APPLY_ALLOWED_STATUSES and not apply_blocked_by, + "apply_complete": ccr.get("status") in POST_APPLY_STATUSES, "apply_blockers": apply_blocked_by, "frontdoor_resolvable": not frontdoor_blocked_by, "frontdoor_blockers": frontdoor_blocked_by, @@ -504,6 +509,7 @@ def render_status(payload: dict[str, Any]) -> str: f"Readiness: {payload['access_frontdoor']['readiness']}", f"Resolvable: {payload['frontdoor_resolvable']}", f"Apply allowed: {payload['apply_allowed']}", + f"Apply complete: {payload.get('apply_complete') is True}", ] decision = payload.get("state_hub", {}).get("decision_api_url") dashboard = payload.get("state_hub", {}).get("decision_dashboard_url") diff --git a/tests/test_credential_change.py b/tests/test_credential_change.py index 890bc77..7e3d4f5 100644 --- a/tests/test_credential_change.py +++ b/tests/test_credential_change.py @@ -52,15 +52,17 @@ class CredentialChangeTests(unittest.TestCase): self.assertIn("whynot-design npm publish token lane", rendered) self.assertIn("platform/workloads/coulomb/whynot-design/npm-publish", rendered) self.assertIn("whynot-design-npm-publish", rendered) - self.assertIn("readiness: template resolvable=False", rendered) + self.assertIn("readiness: applied-pending-verify resolvable=False", rendered) self.assertIn("approve | deny | needs_changes", rendered) - def test_status_payload_marks_template_not_resolvable(self) -> None: + def test_status_payload_marks_applied_pending_verify_not_resolvable(self) -> None: ccr, _errors, warnings = credential_change.validate_ccr(self.sample) payload = credential_change.status_payload(ccr, warnings) - self.assertTrue(payload["apply_allowed"]) + self.assertFalse(payload["apply_allowed"]) + self.assertTrue(payload["apply_complete"]) self.assertFalse(payload["frontdoor_resolvable"]) - self.assertEqual(payload["access_frontdoor"]["readiness"], "template") + self.assertEqual(payload["status"], "applied") + self.assertEqual(payload["access_frontdoor"]["readiness"], "applied-pending-verify") self.assertEqual(payload["access_frontdoor"]["catalog_id"], "whynot-design-npm-publish") self.assertEqual(payload["apply_blockers"], []) self.assertEqual(payload["warnings"], []) @@ -69,7 +71,11 @@ class CredentialChangeTests(unittest.TestCase): "e6381a56-6b04-4fd5-b2de-f3ef59cde888", ) self.assertIn( - "front door requires CCR status active, got approved", + "front door requires CCR status active, got applied", + payload["frontdoor_blockers"], + ) + self.assertIn( + "front door readiness must be ready, got applied-pending-verify", payload["frontdoor_blockers"], ) self.assertIn("front door is marked resolvable=false", payload["frontdoor_blockers"])