--- id: RAILIANCE-WP-0014 type: workplan title: "Credential-change test suite stabilization" domain: financials repo: railiance-platform status: finished owner: codex topic_slug: railiance created: "2026-08-11" updated: "2026-08-11" related_repos: - rapp-postgres - ops-warden state_hub_workstream_id: "4dea40b7-43f5-499a-8919-79221623aa79" --- # RAILIANCE-WP-0014 - Credential-change test suite stabilization ## Goal Make `python3 -m unittest discover -s tests -p 'test_credential*.py'` pass non-interactively again, without weakening any approval or front-door gate. Origin: the `rapp-postgres` coordination message of 2026-08-11, a residual from `RAPP-POSTGRES-WP-0002-T04`. The focused credential broker suite was green (9/9) at commit `9f6bdff`, but full discovery reported five failures unrelated to the broker work. ## Diagnosis None of the five failures were broker regressions. Two were missing or invalid artifacts in `CCR-2026-0009`; three were test fixtures that had silently decayed as real CCR lanes advanced through their lifecycle. The decay is the important finding. Three refusal tests used the live `CCR-2026-0002` file as their "unapproved CCR" fixture. That lane has since been approved, applied and activated, so the refusal those tests assert stopped being the correct behaviour for that input — the gate did exactly the right thing and the tests failed anyway. `applier-apply` then walked past its (correctly ordered) blocker check into the interactive confirmation prompt and raised `EOFError` under a non-interactive runner. A refusal test that reads its approval state off a mutable production artifact will keep breaking every time a lane advances. ## Boundaries This workplan may: - repair credential-change test fixtures and add missing CCR source artifacts - correct schema-invalid field values in credential change requests It must not: - relax `applier_readiness_blockers`, `runbook_readiness_blockers`, or the front-door `resolvable`/`status` coupling in `scripts/credential-change.py` - change grant delivery or revocation semantics in `credential-grants/catalog.yaml` - write or move any secret value ## Tasks ```task id: RAILIANCE-WP-0014-T01 status: done priority: high state_hub_task_id: "18d3358b-2cd6-4fe0-aab6-602f85d8cccd" ``` Repair `CCR-2026-0009` (qonto-assistant workload KV read lane) so it validates: - add the missing `openbao/policies/workload-kv-read-qonto-assistant.hcl`, scoped read-only to `tenants/{data,metadata}/binky/qonto-api` and mirroring the `workload-kv-read-binky-qonto-api.hcl` shape - replace the schema-invalid `access_frontdoor.readiness: proposed` with `pending-review`, which is the correct state for a CCR still in review The CCR stays `status: proposed` with `resolvable: false` — this task adds the missing source artifact, it does not advance the lane. ```task id: RAILIANCE-WP-0014-T02 status: done priority: high state_hub_task_id: "4705a142-4168-41a8-aba0-691d8fc3c6bd" ``` Stop the refusal tests depending on live CCR lifecycle state. Add a `unapproved_ccr()` helper to `tests/test_credential_change.py` that materializes a temp copy normalized to `status: proposed`, no review comments, `readiness: pending-review`, `resolvable: false`, and point `test_applier_dry_run_refuses_unapproved_ccr`, `test_applier_apply_refuses_unapproved_ccr`, `test_runbook_refuses_unapproved_ccr` and `test_operator_commands_refuse_unapproved_ccr` at it. This also removes the `EOFError`: with a genuinely unapproved CCR, `command_applier_apply` returns 1 at its blocker check and never reaches the confirmation prompt. The gate ordering in `scripts/credential-change.py` was already correct and was left untouched. ```task id: RAILIANCE-WP-0014-T03 status: done priority: medium state_hub_task_id: "dd34d578-97e5-4c26-aef8-faafc994c387" ``` Fix `test_approve_records_comment_but_unconfirmed_claim_still_blocks_apply`, which demoted an active CCR to `approved` while leaving `access_frontdoor.resolvable: true`, tripping the `resolvable=true requires status active` rule. Build the fixture from `unapproved_ccr()` so the front-door state is consistent with the status the test actually wants. The validation rule is correct and unchanged. ```task id: RAILIANCE-WP-0014-T04 status: done priority: high state_hub_task_id: "457053f2-261f-4c80-92d4-c084e6236e88" ``` Confirm acceptance against the requested criteria: - `python3 -m unittest discover -s tests -p 'test_credential*.py' < /dev/null` — 52 tests, OK - full repo discovery `-p 'test_*.py'` — 61 tests, OK - `make credential-change-validate` — all nine CCRs OK - `python3 scripts/credential-grants-validate.py` — catalog valid, 3 grants - both `rapp-postgres/audit-core-*` `openbao-database-credential` grants still carry `delivery.allowed: [exec-env]` with `child_only`/`redact_logs`, and `revocation.required: true` with `on_exec_exit: true` ## Outcome Suite green non-interactively; no gate, blocker, validation rule or grant semantic was modified. The one behavioural change outside tests is the addition of a previously missing least-privilege policy artifact.