Approve sitting-requester CCRs and add the attended provisioner.
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

CCR-2026-0026/0027 are approved for the create-only informed-decision
client. Live KeyCape registration and CAS=0 custody stay in the
contained helper; no sitting POST.

Assistant: grok
Assistant-Session: 01a0a23b-3bf0-7341-b4e5-9dc05f72573a
This commit is contained in:
codex 2026-09-15 20:08:02 +02:00
parent 6dfb751e60
commit 4b34c239bc
7 changed files with 162 additions and 9 deletions

View file

@ -3,7 +3,7 @@ kind: credential-change-request
schema_version: 1 schema_version: 1
request_type: workload-kv-read request_type: workload-kv-read
title: Informed Decision sitting-requester KeyCape verifier custody title: Informed Decision sitting-requester KeyCape verifier custody
status: proposed status: approved
created: '2026-09-15' created: '2026-09-15'
updated: '2026-09-15' updated: '2026-09-15'
requester: requester:
@ -23,6 +23,11 @@ review:
comment: Operator selected allocation of the sitting-requester CCR pair. Source comment: Operator selected allocation of the sitting-requester CCR pair. Source
only. No OpenBao apply, no secret seed, no KeyCape registration, and no sitting only. No OpenBao apply, no secret seed, no KeyCape registration, and no sitting
POST from this allocation. POST from this allocation.
- at: '2026-09-15'
reviewer: User (platform-operator; key-cape-owner)
decision: approved
comment: User instructed register sitting-requester. Apply remains the attended
helper with CAS=0 custody, exact create-only client, and no sitting POST.
target: target:
domain: financials domain: financials
tenant: platform tenant: platform

View file

@ -3,7 +3,7 @@ kind: credential-change-request
schema_version: 1 schema_version: 1
request_type: workload-kv-read request_type: workload-kv-read
title: Informed Decision sitting-requester attended operator reader title: Informed Decision sitting-requester attended operator reader
status: proposed status: approved
created: '2026-09-15' created: '2026-09-15'
updated: '2026-09-15' updated: '2026-09-15'
requester: requester:
@ -22,6 +22,11 @@ review:
decision: allocated decision: allocated
comment: Operator selected allocation of the sitting-requester CCR pair. Source comment: Operator selected allocation of the sitting-requester CCR pair. Source
only. No OpenBao apply, no secret seed, and no sitting POST from this allocation. only. No OpenBao apply, no secret seed, and no sitting POST from this allocation.
- at: '2026-09-15'
reviewer: User (platform-operator; key-cape-owner)
decision: approved
comment: User instructed register sitting-requester. Apply remains the attended
helper with CAS=0 custody, exact create-only client, and no sitting POST.
target: target:
domain: financials domain: financials
tenant: platform tenant: platform

View file

@ -0,0 +1,101 @@
"""Silent attended provisioning of the informed-decision sitting requester."""
import copy, json, os, secrets, sys, time
from pathlib import Path
from datetime import datetime, timezone
ROOT = Path('/home/worsch/railiance-platform')
sys.path.insert(0, str(ROOT / 'scripts'))
from state_hub_preflight_lane import command, bao, data, assert_cluster, LaneError
from keycape_approval_custody import require, read_optional, BOUNDARY
import importlib.util
KUBE = ['kubectl', '--kubeconfig', '/home/worsch/.kube/config-railiance01']
KV = 'platform/data/workloads/informed-decision/sitting-requester'
META = 'platform/metadata/workloads/informed-decision/sitting-requester'
CLIENT = json.loads(Path('/home/worsch/key-cape/docs/sitting-requester-registration.json').read_text())
RECEIPT = ROOT / 'docs/evidence/2026-09-15-sitting-requester-provision.json'
CCRS = ('CCR-2026-0026', 'CCR-2026-0027')
ENV_NAME = 'KEYCAPE_INFORMED_DECISION_SITTING_REQUESTER_CLIENT_SECRET'
ESO = ROOT / 'argocd/platform-addons/openbao-secretstore/sitting-requester.yaml'
def load(name, path):
spec = importlib.util.spec_from_file_location(name, path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
rollout = load('requester_rollout', '/home/worsch/net-kingdom/sso-mfa/k8s/keycape/approval-clients-rollout.py')
rollout.IDS = ('informed-decision-sitting-requester',)
def contract():
require(CLIENT['clientId'] == 'informed-decision-sitting-requester', 'requester_contract_drift')
require(CLIENT['allowedScopes'] == ['approval:create'], 'requester_contract_drift')
require(CLIENT['serviceSubject'] == 'informed-decision', 'requester_contract_drift')
require(CLIENT['audience'] == 'approval-engine', 'requester_contract_drift')
require(CLIENT['tenant'] == 'tenant:platform', 'requester_contract_drift')
require('approval:consume' not in CLIENT['allowedScopes'], 'consume_scope_forbidden')
require('approval:approve' not in CLIENT['allowedScopes'], 'approve_scope_forbidden')
def run(receipt):
require(Path.home().parent.name == '.warden-attended-login' and not os.getenv('BAO_TOKEN') and not os.getenv('VAULT_TOKEN'), 'attended_envelope_required')
assert_cluster(KUBE)
identity = data(bao(['token', 'lookup', '-format=json']))['data']
require('platform-admin' in identity['policies'] and 'root' not in identity['policies'], 'attended_operator_required')
contract()
require(read_optional(META) is None, 'existing_sitting_requester_custody_requires_reconciliation')
for ref in CCRS:
command(['python3', str(ROOT / 'scripts/credential-change.py'), 'applier-dry-run', ref, '--json'])
secret = rollout.get(KUBE, 'secret', 'keycape-config')
deployment = rollout.get(KUBE, 'deployment', 'keycape')
config = rollout.replacement(secret, [CLIENT])
spec = copy.deepcopy(deployment['spec'])
require(spec.get('replicas', 1) == 1 and spec['strategy']['type'] == 'Recreate', 'single_recreate_keycape_required')
container = next(c for c in spec['template']['spec']['containers'] if c['name'] == 'keycape')
image = container['image']
require('@sha256:' in image, 'pinned_keycape_image_required')
require(not any(e['name'] == ENV_NAME for e in container.setdefault('env', [])), 'existing_sitting_requester_env')
container['env'].append({'name': ENV_NAME, 'valueFrom': {'secretKeyRef': {'name': 'keycape-informed-decision-sitting-requester-client', 'key': 'client-secret'}}})
rollout.patch_object(KUBE, 'secret', secret, '/data/config.yaml', config, dry=True)
rollout.patch_object(KUBE, 'deployment', deployment, '/spec', spec, dry=True)
receipt['phase'] = 'metadata_preflight_passed'
boundary = read_optional(BOUNDARY)
require(boundary is not None, 'agent_boundary_required')
additions = ''.join('path "' + p + '" { capabilities = ["deny"] }\n' for p in (KV, META))
require(all('"' + p + '"' not in boundary['policy'] for p in (KV, META)), 'boundary_path_requires_reconciliation')
bao(['write', BOUNDARY, '-'], payload={'policy': boundary['policy'] + '\n' + additions})
require(read_optional(BOUNDARY)['policy'] == boundary['policy'] + '\n' + additions, 'boundary_readback_failed')
for ref in CCRS:
command(['python3', str(ROOT / 'scripts/credential-change.py'), 'applier-apply', ref, '--actor', 'operator via attended sitting-requester custody session', '--confirm', 'DELEGATED APPLY ' + ref, '--quiet'])
receipt['phase'] = 'reader_roles_applied'
result = data(bao(['write', '-format=json', KV, '-'], payload={'options': {'cas': 0}, 'data': {'CLIENT_SECRET': secrets.token_urlsafe(48)}}))
require(result['data']['version'] == 1, 'initial_version_mismatch')
receipt.update(phase='custody_seeded', kv_version=1, request_id=result.get('request_id'))
RECEIPT.write_text(json.dumps(receipt, indent=2) + '\n')
command(KUBE + ['apply', '-f', str(ESO)])
deadline = time.monotonic() + 90
while time.monotonic() < deadline:
obj = rollout.get(KUBE, 'externalsecret', 'keycape-informed-decision-sitting-requester-client')
if any(c.get('type') == 'Ready' and c.get('status') == 'True' for c in obj.get('status', {}).get('conditions', [])):
break
time.sleep(2)
else:
raise LaneError('verifier_sync_failed')
receipt['phase'] = 'verifier_synced'
rollout.patch_object(KUBE, 'secret', secret, '/data/config.yaml', config)
after = rollout.get(KUBE, 'secret', 'keycape-config')
require(after['data'] == dict(secret['data'], **{'config.yaml': config}), 'unrelated_config_or_key_changed')
rollout.patch_object(KUBE, 'deployment', deployment, '/spec', spec)
receipt['keycape'] = rollout.ready(KUBE, image)
receipt.update(status='applied', phase='keycape_ready', image_unchanged=image, unrelated_config_preserved=True, verifier_secret_exported=False, sitting_post=False)
if __name__ == '__main__':
receipt = {'observed_at': datetime.now(timezone.utc).isoformat(), 'status': 'failed', 'phase': 'preflight', 'ccrs': list(CCRS), 'credential_values_emitted': False, 'approval_mutations': False, 'sitting_post': False}
try:
run(receipt)
except Exception:
raise SystemExit(1) from None
finally:
RECEIPT.write_text(json.dumps(receipt, indent=2) + '\n')

View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
# Silent child for the governed attended login; no secret output.
set -euo pipefail
exec python3 "$(dirname "$0")/provision-sitting-requester.py" "$@" >/dev/null 2>&1

View file

@ -0,0 +1,38 @@
import importlib.util
from pathlib import Path
import unittest
spec = importlib.util.spec_from_file_location(
'sitting', Path(__file__).resolve().parents[1] / 'scripts/provision-sitting-requester.py')
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m)
class SittingRequesterProvisioningTests(unittest.TestCase):
def test_contract_is_create_only_informed_decision(self):
m.contract()
self.assertEqual(m.CLIENT['clientId'], 'informed-decision-sitting-requester')
self.assertEqual(m.CLIENT['allowedScopes'], ['approval:create'])
self.assertEqual(m.CLIENT['serviceSubject'], 'informed-decision')
self.assertEqual(m.CCRS, ('CCR-2026-0026', 'CCR-2026-0027'))
self.assertNotIn('CCR-2026-0024', m.CCRS)
self.assertNotIn('CCR-2026-0025', m.CCRS)
def test_contract_refuses_widened_scopes(self):
original = list(m.CLIENT['allowedScopes'])
m.CLIENT['allowedScopes'] = ['approval:create', 'approval:consume']
with self.assertRaises(Exception):
m.contract()
m.CLIENT['allowedScopes'] = original
def test_source_keycape_registration_matches_helper(self):
import yaml
clients = yaml.safe_load(Path('/home/worsch/key-cape/config/service-clients.example.yaml').read_text())['clients']
named = next(c for c in clients if c['clientId'] == 'informed-decision-sitting-requester')
self.assertEqual(named['secretRef'], 'env:' + m.ENV_NAME)
self.assertEqual(named['allowedScopes'], ['approval:create'])
self.assertEqual(named['serviceSubject'], 'informed-decision')
if __name__ == '__main__':
unittest.main()

View file

@ -14,7 +14,7 @@ implementations or independent incidents.
| [RPF-WP-0035](RPF-WP-0035-credential-lane-implementation.md) | Three remaining lanes: secrets-engine JWT, Fluid operator KV, KeyCape approval clients | Signing T04 is complete; T05 admission answered and awaiting owner approval plus a founder-attended window. | | [RPF-WP-0035](RPF-WP-0035-credential-lane-implementation.md) | Three remaining lanes: secrets-engine JWT, Fluid operator KV, KeyCape approval clients | Signing T04 is complete; T05 admission answered and awaiting owner approval plus a founder-attended window. |
| [RPF-WP-0036](RPF-WP-0036-platform-service-assurance.md) | Implemented local assurance/admission; waits for recurring restore evidence, Q2 reception and owner handoff | Run the assurance commands; live acceptance and external ownership remain gated. | | [RPF-WP-0036](RPF-WP-0036-platform-service-assurance.md) | Implemented local assurance/admission; waits for recurring restore evidence, Q2 reception and owner handoff | Run the assurance commands; live acceptance and external ownership remain gated. |
| [RPF-WP-0038](RPF-WP-0038-forgejo-scaleway-primary-coverage.md) | Native backup, full Scaleway archive recovery and 273 MiB Nextcloud essentials recovery verified; scheduled tier cutover remains | Bind recurring caller/dependencies, verified inventory, quota checks and separate owner retention. | | [RPF-WP-0038](RPF-WP-0038-forgejo-scaleway-primary-coverage.md) | Native backup, full Scaleway archive recovery and 273 MiB Nextcloud essentials recovery verified; scheduled tier cutover remains | Bind recurring caller/dependencies, verified inventory, quota checks and separate owner retention. |
| [RPF-WP-0042](RPF-WP-0042-informed-decision-sitting-requester.md) | Sitting-requester CCR-2026-0026/0027 allocated; no apply | Do not widen 0024/0025. Attended seed waits on owner reviews and KeyCape row. | | [RPF-WP-0042](RPF-WP-0042-informed-decision-sitting-requester.md) | Sitting-requester CCRs approved; live KeyCape/OpenBao apply remains | Attended `provision-sitting-requester.sh`; no sitting POST. |
RPF-WP-0036-T02/T05/T07 are complete; T03/T04/T06 retain the remaining RPF-WP-0036-T02/T05/T07 are complete; T03/T04/T06 retain the remaining
acceptance gates. Treat credential exposure closure as the highest-priority attended acceptance gates. Treat credential exposure closure as the highest-priority attended

View file

@ -4,7 +4,7 @@ type: workplan
title: "Allocate Informed Decision sitting-requester custody" title: "Allocate Informed Decision sitting-requester custody"
domain: financials domain: financials
repo: railiance-platform repo: railiance-platform
status: ready status: active
flavor: implementation flavor: implementation
owner: grok owner: grok
topic_slug: railiance topic_slug: railiance
@ -38,12 +38,12 @@ non-resolvable. ESO projection is unapplied source.
```task ```task
id: RPF-WP-0042-T02 id: RPF-WP-0042-T02
status: wait status: progress
priority: high priority: high
state_hub_task_id: "c6fbf99c-de2b-55be-9f0c-58be9fe7c518" state_hub_task_id: "c6fbf99c-de2b-55be-9f0c-58be9fe7c518"
``` ```
Requires named owner reviews, KeyCape row `informed-decision-sitting-requester`, Operator approved CCR-2026-0026/0027 on 2026-09-15. Source registration is in
attended CAS=0 custody, exact policy/auth readback, sibling `key-cape/config/service-clients.example.yaml`. Live apply is the silent helper
`secrets-engine/approval-requester` denial, and create-only token-exchange proof. `scripts/provision-sitting-requester.sh` through `openbao-attended-exec.py`.
No sitting POST until that proof exists. No sitting POST until exchange proof exists. Do not widen CCR-2026-0024/0025.