Cancel withdrawn approval operator reader without inventing auth
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
codex 2026-09-10 08:10:33 +02:00
parent 07b6b63fe6
commit 3109f950f9
7 changed files with 152 additions and 26 deletions

View file

@ -3,9 +3,20 @@ kind: credential-change-request
schema_version: 1
request_type: workload-kv-read
title: Client-side read of the approval-engine-operator client secret
status: in_flight
status: cancelled
created: '2026-09-09'
updated: '2026-09-09'
updated: '2026-09-10'
cancellation:
owner: approval-engine
reason: >-
Requesting owner withdrew the combined lifecycle operator client: no
presenter exists and no client-side reader is wanted. Human approval belongs
to informed-decision; any future service presenter needs a narrow new request.
Completed verifier custody CCR-2026-0018 remains unchanged.
source_ref: >-
approval-engine@849c75bb094613ff6ac1a1d4cda56a745520c5a1:docs/keycape-service-registrations.md#approval-engine-operator
recorded_at: '2026-09-10'
# Historical omissions retained so cancellation does not invent an auth binding.
in_flight:
missing_fields:
- openbao.auth
@ -85,10 +96,11 @@ access_frontdoor:
catalog_id: approval-engine-operator-client
selector: approval-engine operator client secret
resolvable: false
readiness: pending-review
readiness: disabled
delivery:
surface: undetermined
surface: none
target: >-
Cancelled on owner withdrawal; the following alternatives were never admitted.
Not determined. The delivery surface follows the actor: an attended operator
implies a protected workstation file, an in-cluster component implies an
ExternalSecret into the eventual approval-engine namespace, and an agent

View file

@ -90,12 +90,20 @@ superseded
cancelled
```
`in_flight` is the only state that may explicitly omit completion-only fields.
`in_flight` is the only nonterminal state that may explicitly omit completion-only fields.
It must declare every omission in `in_flight.missing_fields`, name the owner and
blocking reason, and remain non-resolvable. The validator still checks every
other field. This is not an applyable state and must never be used to hide a
malformed active lane.
An incomplete request withdrawn by its owner may instead be `cancelled`, retaining
its `in_flight` declaration as history. This requires `cancellation.owner`,
`cancellation.reason` and a non-secret `cancellation.source_ref`, with front-door
readiness `disabled` and `resolvable: false`. Only the same declared omissions are
allowed; all other validation remains. Cancellation cannot authorize apply or
resolve a reader. Moving back to an approval/apply state requires completing the
binding; keeping the historical declaration does not bypass that requirement.
Only `approved` requests may be applied. Only `verified` requests may become
`active`.

View file

@ -4,7 +4,8 @@ RPF-WP-0035-T06, the residual handed over by the completed verifier custody
(T05). Requests:
[CCR-2026-0019](../../credential-change-requests/CCR-2026-0019-secrets-engine-approval-client-read.yaml)
and [CCR-2026-0020](../../credential-change-requests/CCR-2026-0020-approval-engine-operator-client-read.yaml).
Both are `in_flight`, neither is approved, and nothing is applied.
CCR-2026-0019 remains `in_flight` and unapproved. CCR-2026-0020 is
`cancelled` on its requesting owner's explicit withdrawal; neither reader was applied.
## Why this exists as separate requests
@ -52,23 +53,17 @@ the consumer's shape rather than engineered away.
## Reader 2 — approval-engine-operator (CCR-2026-0020)
Held open deliberately. The exact-path read policy
`workload-kv-read-approval-engine-operator-client` is written and committed so
the eventual reader inherits a bounded grant rather than one drafted under time
pressure. Writing the policy is not admitting a reader.
Cancelled 2026-09-10. Approval Engine explicitly requests cancellation because
no presenter exists and no client-side reader is wanted. Source:
`approval-engine@849c75bb094613ff6ac1a1d4cda56a745520c5a1`,
`docs/keycape-service-registrations.md`, operator withdrawal section.
The retained exact-path policy is an unapplied historical artifact, not an
admission. No role, credential delivery, rotation or verifier change follows.
**Missing:** the presenting actor and its placement. This client carries the
widest scope set in the pair — `approval:create`, `read`, `approve`, `revoke`,
`supersede`, `observe`, `emit`, and deliberately not `consume`. approval-engine
records `principal_type` on every entry rather than restricting who may supply
approver evidence, and its owner records that whether a non-human principal
*should* is approval doctrine belonging to `gate-house`. Naming a reader here
would hand the widest approval scope to a guessed identity, so the request names
what is undetermined instead.
The delivery surface follows the actor: an attended operator implies a protected
workstation file; an in-cluster component implies an ExternalSecret into the
eventual `approval-engine` namespace; an agent surface implies neither.
Human approval belongs to informed-decision's PKCE client, INFD-WP-0001-T07/T08;
a future service requester needs its own narrow registration under the existing
Approval Engine owner work. Do not recreate the combined create/approve bundle.
The completed CCR-2026-0018 verifier custody remains unchanged.
## Not in scope

View file

@ -84,6 +84,21 @@ workload_kv_read:
- rotate
- compromised
conditional:
status=cancelled_with_in_flight_declaration:
required:
- in_flight.missing_fields
- in_flight.blocking_reason
- in_flight.owner
- cancellation.owner
- cancellation.reason
- cancellation.source_ref
allowed_missing_fields:
- openbao.policy_file
- openbao.auth
access_frontdoor:
readiness: disabled
resolvable: false
note: Retain abandoned omissions without inventing a binding; all other validation and apply refusals remain.
status=in_flight:
required:
- in_flight.missing_fields

View file

@ -191,8 +191,16 @@ def reject_secret_text(text: str, field: str) -> None:
def validate_workload_kv_read(ccr: dict[str, Any], errors: list[str], warnings: list[str]) -> None:
in_flight = ccr.get("status") == "in_flight"
abandoned_incomplete = ccr.get("status") == "cancelled" and "in_flight" in ccr
missing_fields: set[str] = set()
if in_flight:
if abandoned_incomplete:
cancellation = require_object(ccr.get("cancellation"), "cancellation", errors)
for field in ("owner", "reason", "source_ref"):
require_string(cancellation.get(field), f"cancellation.{field}", errors)
frontdoor = ccr.get("access_frontdoor") or {}
if not isinstance(frontdoor, dict) or frontdoor.get("readiness") != "disabled":
errors.append("cancelled incomplete request requires disabled front door")
if in_flight or abandoned_incomplete:
declaration = require_object(ccr.get("in_flight"), "in_flight", errors)
listed_missing = require_list(
declaration.get("missing_fields"), "in_flight.missing_fields", errors
@ -504,7 +512,15 @@ def render_summary(ccr: dict[str, Any], warnings: list[str]) -> str:
)
else:
missing = ", ".join(ccr.get("in_flight", {}).get("missing_fields", []))
lines.append(f" in flight; declared missing: {missing}")
label = "cancelled; retained omissions" if ccr.get("status") == "cancelled" else "in flight; declared missing"
lines.append(f" {label}: {missing}")
if ccr.get("status") == "cancelled" and ccr.get("cancellation"):
cancellation = ccr["cancellation"]
lines.extend([
f"Cancellation owner: {cancellation['owner']}",
f"Reason: {cancellation['reason']}",
f"Source: {cancellation['source_ref']}",
])
lines.extend(
[
"Access front door:",

View file

@ -134,6 +134,62 @@ class CredentialChangeTests(unittest.TestCase):
_ccr, errors, _warnings = credential_change.validate_ccr(path)
self.assertTrue(any("openbao.policy_file" in error for error in errors))
def cancelled_incomplete_ccr(self) -> Path:
source = REPO_DIR / "credential-change-requests/CCR-2026-0011-scaleway-object-storage-bootstrap.yaml"
path = self.unapproved_ccr(source)
data = credential_change.load_yaml(path)
data["status"] = "cancelled"
data["access_frontdoor"]["readiness"] = "disabled"
data["cancellation"] = {
"owner": "requesting-owner", "reason": "No consumer requires this reader",
"source_ref": "owner/docs/reader-decision.md@reviewed-commit",
}
credential_change.dump_yaml(path, data)
return path
def test_cancelled_incomplete_request_validates_but_cannot_apply(self) -> None:
path = self.cancelled_incomplete_ccr()
ccr, errors, warnings = credential_change.validate_ccr(path)
self.assertEqual(errors, [])
payload = credential_change.status_payload(ccr, warnings)
self.assertFalse(payload["apply_allowed"])
self.assertFalse(payload["frontdoor_resolvable"])
self.assertIn("got cancelled", " ".join(payload["apply_blockers"]))
self.assertIn("cancelled; retained omissions", credential_change.render_summary(ccr, warnings))
with self.assertRaises(SystemExit):
credential_change.command_apply_plan(type("Args", (), {"ref": str(path)})())
def test_cancelled_incomplete_request_requires_owner_evidence(self) -> None:
for field in ("owner", "reason", "source_ref"):
with self.subTest(field=field):
path = self.cancelled_incomplete_ccr()
data = credential_change.load_yaml(path)
del data["cancellation"][field]
credential_change.dump_yaml(path, data)
_, errors, _ = credential_change.validate_ccr(path)
self.assertTrue(any(f"cancellation.{field}" in e for e in errors))
def test_cancelled_omissions_do_not_bypass_other_guards(self) -> None:
for label in ("frontdoor", "path", "undeclared", "present", "unsupported", "reactivate"):
with self.subTest(label=label):
path = self.cancelled_incomplete_ccr()
data = credential_change.load_yaml(path)
if label == "frontdoor":
data["access_frontdoor"]["readiness"] = "ready"
elif label == "path":
data["openbao"]["kv_path"] = "platform/*"
elif label == "undeclared":
data["in_flight"]["missing_fields"] = ["openbao.auth"]
elif label == "present":
data["openbao"]["auth"] = {"method": "oidc"}
elif label == "unsupported":
data["in_flight"]["missing_fields"].append("target")
else:
data["status"] = "approved"
credential_change.dump_yaml(path, data)
_, errors, _ = credential_change.validate_ccr(path)
self.assertTrue(errors)
def test_core_hub_runtime_lane_is_split_and_agent_denied(self) -> None:
path = (
REPO_DIR

View file

@ -7,7 +7,7 @@ repo: railiance-platform
status: blocked
owner: codex
created: "2026-09-05"
updated: "2026-09-09"
updated: "2026-09-10"
related:
- RPF-WP-0032
- RPF-WP-0033
@ -253,6 +253,29 @@ intervention_note: ""
state_hub_task_id: "68bff751-e48b-548b-8fb4-dfb3b16210c2"
```
**Current return, 2026-09-10:** CCR-2026-0020 is cancelled on the requesting
Approval Engine owner's explicit withdrawal, source
`849c75bb094613ff6ac1a1d4cda56a745520c5a1:docs/keycape-service-registrations.md`.
No presenter exists and no reader is wanted. The validator now accepts terminal
cancellation of an incomplete request only with owner/source/reason evidence,
retained declared omissions and a disabled, non-resolvable front door. Apply
remains refused. No live identity, policy, role, custody or verifier was changed.
Human approval follows existing INFD-WP-0001-T07/T08; future service requesters
need a separate narrow registration, not this withdrawn bundle.
**Remaining unblock:** CCR-2026-0019 needs the exact authorized operator group
from NetKingdom/KeyCape, then reviewed attended file delivery and its scoped
positive/negative proof. Completed CCR-2026-0017/0018 remain closed. T06 stays
`wait`; its historical two-reader notes below are superseded for reader 2.
Separate retained owner decision: KeyCape's 2026-09-10 return
`21427688-725f-4dea-aab4-7c78fd4328d2` identifies the already-live, unpresented
CCR-2026-0018 registration. Approval Engine, KeyCape and Platform must explicitly
decide retention/expiry or coordinated disablement. Reader cancellation neither
disables that live registration nor accepts indefinite retention. Keep this
disposition in T06; it is separate from the wanted CCR-2026-0019 factory reader.
No unilateral issuer/config/Secret change is authorized by this closeout.
Residual handoff from RPF-WP-0035-T05; consumes the completed verifier custody
without extending CCR-2026-0017/0018. Owner: railiance-platform with the named
secrets-engine and approval-engine operator consumers.
@ -313,7 +336,8 @@ own accepted native delivery return. Client-side admission is not ready for a
new human decision until exact requests and contained execution/rollback checks
are reviewable; the completed verifier reviews must not recur in that queue.
**Done when:** both separate consumer admissions are approved and implemented;
**Done when:** each retained consumer admission is approved and implemented, or
its requesting owner explicitly cancels it (CCR-2026-0020 now satisfies that branch);
actual consumer authentication/delivery succeeds; sibling/listing/wrong-reader
and scope refusals plus reader expiry/revocation/cleanup are evidenced; version-1
custody and verifier availability remain intact. Return the source and metadata