docs: preserve reviewed NetKingdom session artifacts
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Reject generated-only commits / generated-commit-guard (push) Waiting to run

This commit is contained in:
codex 2026-11-01 23:12:40 +01:00
parent ad3b052f4d
commit d9a1fdabc3
47 changed files with 9432 additions and 0 deletions

View file

@ -0,0 +1,384 @@
#!/usr/bin/env python3
"""Silent attended retained-lifecycle signer custody; never a credential front door.
Invoke only through the reviewed warden platform-admin envelope. The externally
reviewed admission SHA is an exact-command input, not approval by JSON shape.
No private value crosses stdout/stderr, argv, receipt or persistent local storage.
"""
from __future__ import annotations
import argparse
import base64
from datetime import datetime,timezone
import fcntl
import hashlib
import hmac
import http.client
import importlib.util
import json
import os
from pathlib import Path
import re
import selectors
import shlex
import subprocess
import sys
import time
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from flex_auth_signer_custody import SignerWriter,PATH,FIELDS,BINDINGS,encoded,b64
from task_execution_journal import Journal,Refused
from task_execution_native_fixture import _absolute_budget
from task_execution_recovery import _json_unique,_invalid_constant,_policy_absent,_same_policy
ROOT=Path(__file__).resolve().parents[1]
META='platform/metadata/workloads/flex-auth/envelope-signing'
CCR='credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml'
PLAN='docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json'
CLUSTER='a553c742-0115-43d4-99a4-a5ca56fe0786'
BAO_CLUSTER='fd28df5d-98ec-57dd-42ec-9b3e4f4e53bf'
NAME='flex-auth-secrets-engine'
SECRET='flex-auth-secrets-engine-envelope-signing'
ROLE='external-secrets-flex-auth-envelope-signing'
LIFECYCLE='sha256:bd11c5fe77ce6439c65fea225ad6b71d2110efc5e7b5bc9b499c59cd0a53b8b4'
REVIEWS={'founder','railiance-platform-owner','access-engine-owner','secrets-engine-owner','cluster-gitops-owner'}
SOURCE_FILES={'scripts/flex_auth_signer_native.py','scripts/flex_auth_signer_custody.py','scripts/task_execution_native_fixture.py','scripts/task_execution_journal.py','scripts/task_execution_recovery.py','scripts/credential-change.py'}
def require(ok,reason):
if not ok:raise Refused(reason)
def digest(raw):return 'sha256:'+hashlib.sha256(raw).hexdigest()
def is_digest(value):return type(value) is str and re.fullmatch(r'sha256:[0-9a-f]{64}',value) is not None
def private_read(path,limit=65536):
path=Path(path)
require(path.is_absolute() and path.absolute()==path.resolve() and not path.is_symlink(),'indirect private input')
Journal._private(path.parent.stat(),directory=True)
fd=os.open(path,os.O_RDONLY|os.O_NOFOLLOW|os.O_NONBLOCK)
try:
before=os.fstat(fd);Journal._private(before)
raw=os.read(fd,limit+1);after=os.fstat(fd)
require((before.st_dev,before.st_ino,before.st_size,before.st_mtime_ns,before.st_ctime_ns)==
(after.st_dev,after.st_ino,after.st_size,after.st_mtime_ns,after.st_ctime_ns)
and len(raw)==before.st_size and len(raw)<=limit,'private input changed or oversized')
return raw
finally:os.close(fd)
def parsed(raw):
try:return json.loads(raw,object_pairs_hook=_json_unique,parse_constant=_invalid_constant)
except Exception:raise Refused('invalid closed JSON input') from None
def validate_admission(value):
fields={'schema','action','operation_id','kid','expires_at','founder_entity_id','ccr_sha256','source_bundle','bindings',
'producer_image','caller_binding','deployment_projection_sha256','authority','routing','owner_reviews',
'exclusive_window_sha256','consumer_trust_sha256','original_receipt_sha256'}
require(type(value) is dict and set(value)==fields,'exact admission schema required')
require(value['schema']=='railiance-platform.signer-native-admission.v1' and value['action'] in {'bootstrap','recover'},'native admission action refused')
require(type(value['operation_id']) is str and re.fullmatch(r'[A-Za-z0-9_-]{1,80}',value['operation_id']) and
type(value['kid']) is str and re.fullmatch(r'[A-Za-z0-9_-]{1,80}',value['kid']),'native operation identity required')
require(type(value['founder_entity_id']) is str and re.fullmatch(r'[a-f0-9-]{36}',value['founder_entity_id']),'explicit attended identity required')
require(all(is_digest(value[x]) for x in ['ccr_sha256','deployment_projection_sha256','exclusive_window_sha256','consumer_trust_sha256']),'explicit custody/image/trust selections required')
require(type(value['producer_image']) is str and re.fullmatch(r'forgejo\.coulomb\.social/coulomb/flex-auth@sha256:[a-f0-9]{64}',value['producer_image']),'immutable producer image required')
require(value['caller_binding']=='secrets-engine=system:serviceaccount:secrets-engine:secrets-engine','retained caller binding required')
require(value['authority']=='founder-attended-exact-source-scope; no standing grant','independent attended authority required')
route=value['routing'];require(type(route) is dict and set(route)=={'lane_id','verdict','founder_act','receipt_sha256'} and
route['lane_id']=='openbao-platform-admin-login' and route['verdict']=='founder_required' and route['founder_act']=='oidc_login' and is_digest(route['receipt_sha256']),'routing mismatch refused')
reviews=value['owner_reviews'];require(type(reviews) is dict and set(reviews)==REVIEWS,'distinct owner reviews required')
for role,review in reviews.items():
require(type(review) is dict and set(review)=={'approved_admission_sha256','approval_ref'} and
is_digest(review['approved_admission_sha256']) and type(review['approval_ref']) is str and
0<len(review['approval_ref'])<=200,'exact external review required')
# Dispositions bind a claim-free scope digest, avoiding self-referential JSON.
scope={k:v for k,v in value.items() if k!='owner_reviews'};scope_digest=digest(encoded(scope))
require(all(x['approved_admission_sha256']==scope_digest for x in reviews.values()),'owner review scope mismatch')
require(type(value['source_bundle']) is dict and set(value['source_bundle'])==SOURCE_FILES and
all(is_digest(x) for x in value['source_bundle'].values()),'exact execution source bundle required')
require(type(value['bindings']) is dict and set(value['bindings'])==BINDINGS and all(is_digest(x) for x in value['bindings'].values()),'exact execution bindings required')
require(value['bindings']['policy_sha256']==LIFECYCLE and value['bindings']['image_sha256']=='sha256:'+value['producer_image'].rsplit('sha256:',1)[1]
and value['bindings']['trust_sha256']==value['consumer_trust_sha256']
and value['bindings']['deployment_sha256']==value['deployment_projection_sha256']
and value['bindings']['executed_revision_sha256']==digest(encoded(value['source_bundle'])),'immutable lifecycle/image/trust/source mismatch')
require((value['action']=='bootstrap' and value['original_receipt_sha256'] is None) or
(value['action']=='recover' and is_digest(value['original_receipt_sha256'])),'separate recovery disposition required')
try:expires=datetime.fromisoformat(value['expires_at'].replace('Z','+00:00'));remaining=(expires-datetime.now(timezone.utc)).total_seconds()
except Exception:raise Refused('invalid authority expiry') from None
require(expires.utcoffset().total_seconds()==0 and 5<remaining<=120,'bounded fresh attended window required')
return remaining
class NativeIO:
"""All tools capture/discard private bytes; fixed transport and no shell."""
def captured(self,argv,payload=None,*,allowed=(0,)):
env=os.environ.copy();env.update(BAO_ADDR='http://127.0.0.1:18200',VAULT_ADDR='http://127.0.0.1:18200')
for key in ('BAO_LOG_LEVEL','VAULT_LOG_LEVEL'):
require(env.get(key,'').lower() not in {'debug','trace'},'verbose custody environment refused')
# A hostile inherited CLI config is not an alternate identity/endpoint.
require(not env.get('BAO_NAMESPACE') and not env.get('VAULT_NAMESPACE') and not env.get('VAULT_SKIP_VERIFY'),'ambient backend override refused')
proc=subprocess.Popen(argv,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,env=env)
try:
if payload is not None:proc.stdin.write(encoded(payload));proc.stdin.flush()
proc.stdin.close()
sel=selectors.DefaultSelector();data={proc.stdout:bytearray(),proc.stderr:bytearray()}
try:
for stream in data:sel.register(stream,selectors.EVENT_READ)
deadline=time.monotonic()+2
while sel.get_map():
remaining=deadline-time.monotonic();require(remaining>0,'absolute custody command timeout')
for item,_ in sel.select(remaining):
chunk=os.read(item.fileobj.fileno(),65536)
if not chunk:sel.unregister(item.fileobj)
else:
data[item.fileobj].extend(chunk)
require(sum(map(len,data.values()))<=1048576,'custody command output bound')
code=proc.wait(timeout=max(.001,deadline-time.monotonic()))
require(code in allowed,'custody command unconfirmed')
return code,bytes(data[proc.stdout])
finally:sel.close()
except BaseException:
proc.kill();proc.wait(timeout=2);raise Refused('custody command unconfirmed') from None
finally:
proc.stdout.close();proc.stderr.close()
def bao(self,args,payload=None,allowed=(0,)):
with _absolute_budget(2):
code,raw=self.captured(['bao',*args],payload,allowed=allowed)
return code,parsed(raw)
def kube(self,resource,name=None):
args=['kubectl','--request-timeout=2s']
if resource!='namespace':args+=['-n','flex-auth']
args+=['get',resource]
if name:args.append(name)
args+=['-o','json']
with _absolute_budget(2):
_,raw=self.captured(['ssh','-o','BatchMode=yes','-o','StrictHostKeyChecking=yes','-o','ConnectTimeout=2','railiance01',shlex.join(args)])
return parsed(raw)
def http(self,method,path,body=None):
require(method in {'GET','POST'} and path in {META,PATH,PATH+'?version=1'},'fixed custody path required')
require(type(getattr(self,'_token',None)) is str and self._token,'contained attended session required')
with _absolute_budget(2):
conn=http.client.HTTPConnection('127.0.0.1',18200,timeout=2)
try:
conn.request(method,'/v1/'+path,body=None if body is None else encoded(body),
headers={'X-Vault-Token':self._token,'Content-Type':'application/json'})
response=conn.getresponse();raw=response.read(1048577)
require(len(raw)<=1048576,'custody response bound')
return response.status,parsed(raw) if raw else {}
finally:conn.close()
def deployment_projection(deployment):
spec=deployment['spec']['template']['spec'];containers=spec.get('containers',[])
require(type(containers) is list and len(containers)==1 and not spec.get('initContainers'),'single fixed signer container required')
item=containers[0]
# Never hash arbitrary env values or an entire Secret/spec response.
return {'service_account':spec.get('serviceAccountName'),'image':item.get('image'),
'args':item.get('args'),'volume_mounts':item.get('volumeMounts',[]),
'volumes':spec.get('volumes',[]),'security_context':spec.get('securityContext')}
def fence(io,contract):
namespace=io.kube('namespace','kube-system')
require(namespace.get('metadata',{}).get('uid')==CLUSTER,'wrong target cluster')
deployment=io.kube('deployment',NAME);spec=deployment.get('spec',{});meta=deployment.get('metadata',{})
require(type(spec.get('replicas')) is int and spec['replicas']==0 and
deployment.get('status',{}).get('observedGeneration',-1)==meta.get('generation') and
not deployment.get('status',{}).get('replicas',0),'all signer replicas must be stopped')
projection=deployment_projection(deployment)
require(digest(encoded(projection))==contract['deployment_projection_sha256'] and
projection['service_account']==NAME and projection['image']==contract['producer_image'],'fenced deployment source/image mismatch')
args=projection['args'];require(type(args) is list and all(type(x) is str for x in args),'exact signer args required')
def option(flag):
require(args.count(flag)==1 and args.index(flag)+1<len(args),'fixed signer flag required')
return args[args.index(flag)+1]
require(option('--caller-auth-mode')=='enforce' and option('--caller-audience')=='flex-auth' and
option('--caller-binding')==contract['caller_binding'],'retained caller guard required')
require(not any(x.startswith(flag+'=') for x in args for flag in ['--caller-auth-mode','--caller-binding','--caller-audience','--envelope-signing-key-file','--envelope-signing-kid']),'ambiguous protected args refused')
require(option('--envelope-signing-key-file')=='/var/run/secrets/decision-envelope-signing/seed' and
option('--envelope-signing-kid')==contract['kid'],'exact seed mount and startup kid required')
volumes=projection['volumes'];mounts=projection['volume_mounts']
matching=[x for x in volumes if x.get('name')=='flex-auth-envelope-signing']
require(len(matching)==1 and matching[0].get('secret')=={'secretName':SECRET,'defaultMode':288,'items':[{'key':'seed','path':'seed'}]},'exact seed volume required')
require(any(x=={'name':'flex-auth-envelope-signing','mountPath':'/var/run/secrets/decision-envelope-signing','readOnly':True} for x in mounts),'readonly seed mount required')
uid=meta.get('uid');require(type(uid) is str and uid,'deployment UID required')
sets=io.kube('replicasets').get('items');pods=io.kube('pods').get('items');hpas=io.kube('hpa').get('items')
require(type(sets) is list and type(pods) is list and type(hpas) is list,'complete fence observations required')
rsids=[]
for item in sets:
if any(x.get('uid')==uid for x in item.get('metadata',{}).get('ownerReferences',[])):
require(type(item.get('spec',{}).get('replicas')) is int and item['spec']['replicas']==0 and
type(item.get('status',{}).get('replicas',0)) is int and not item.get('status',{}).get('replicas',0),'owned ReplicaSet still running')
rsids.append(item['metadata']['uid'])
for item in pods:
podspec=item.get('spec',{})
uses_secret=any(x.get('secret',{}).get('secretName')==SECRET for x in podspec.get('volumes',[]))
owned=any(x.get('uid') in [uid,*rsids] for x in item.get('metadata',{}).get('ownerReferences',[]))
require(not owned and podspec.get('serviceAccountName')!=NAME and not uses_secret,'live or terminating signer pod refused')
require(not any(x.get('spec',{}).get('scaleTargetRef',{}).get('name')==NAME for x in hpas),'signer autoscaler refused')
return True
def checked_sources(contract):
for name,pin in contract['source_bundle'].items():
path=ROOT/name
require(path.resolve()==path.absolute() and not path.is_symlink() and path.stat().st_mode&0o022==0
and path.is_file() and path.stat().st_size<=1048576 and digest(path.read_bytes())==pin,'exact admitted execution source required')
planraw=(ROOT/PLAN).read_bytes();require(digest(planraw)==contract['bindings']['proposal_sha256'],'exact native action proposal required')
plan=parsed(planraw);require(plan.get('proposal_id')=='RPF35-PDP-SIGNER-NATIVE-20261016-R1' and plan.get('bootstrap',{}).get('kid')==contract['kid'],'exact proposed bootstrap kid required')
ccrraw=(ROOT/CCR).read_bytes();require(digest(ccrraw)==contract['ccr_sha256'],'reviewed read CCR source mismatch')
spec=importlib.util.spec_from_file_location('native_signer_ccr',ROOT/'scripts/credential-change.py');module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)
ccr,errors,_=module.validate_ccr(ROOT/CCR)
require(not errors and ccr['status'] in {'approved','applied','verified','active'} and
ccr['openbao']['auth']['bound_claims_confirmed'] is True,'approved exact read CCR required')
scope=ccr['openbao'];require(scope['kv_path']=='platform/workloads/flex-auth/envelope-signing' and
scope['fields']==['ED25519_PRIVATE_KEY'] and scope['metadata_read'] is False and scope['policy_name']=='workload-kv-read-flex-auth-envelope-signing' and
scope['auth']['role']==ROLE and scope['auth']['bound_claims']=={'service_account_names':['flex-auth-envelope-signing-eso'],'service_account_namespaces':['flex-auth']},'read scope correspondence refused')
require((ROOT/scope['policy_file']).read_text()==module.generated_policy_hcl(ccr),'read policy source drift')
def attended(io,contract):
_,status=io.bao(['status','-format=json']);require(status.get('cluster_id')==BAO_CLUSTER and status.get('sealed') is False,'backend cluster pin required')
_,lookup=io.bao(['token','lookup','-format=json']);who=lookup.get('data',{})
require('platform-admin' in who.get('policies',[]) and 'root' not in who.get('policies',[]) and
type(who.get('path')) is str and who['path'].startswith('auth/netkingdom/') and who.get('entity_id')==contract['founder_entity_id'],'exact attended non-root founder required')
_,audit=io.bao(['audit','list','-format=json']);require(type(audit) is dict and 0<len(audit)<=32,'enabled backend audit required')
# Token lookup-self is inside the contained warden child, never an agent
# retrieval. Its identifier stays in custody-process memory for fixed API I/O.
token=who.get('id');require(type(token) is str and 0<len(token)<=4096,'contained session handle unavailable')
io._token=token
_,policy=io.bao(['read','-format=json','sys/policies/acl/workload-kv-read-flex-auth-envelope-signing'])
expected_policy=(ROOT/'openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl').read_text()
require(_same_policy(policy.get('data',{}).get('policy'),expected_policy),'native reader policy drift')
_,role=io.bao(['read','-format=json','auth/kubernetes/role/'+ROLE]);role=role.get('data',{})
required={'bound_service_account_names':['flex-auth-envelope-signing-eso'],
'bound_service_account_namespaces':['flex-auth'],'bound_service_account_namespace_selector':'',
'audience':'openbao','token_policies':['workload-kv-read-flex-auth-envelope-signing'],
'token_ttl':900,'token_max_ttl':900,'token_explicit_max_ttl':900,'token_no_default_policy':True,
'token_period':0,'token_num_uses':0,'token_bound_cidrs':[]}
require(all(type(role.get(key)) is type(expected) and role.get(key)==expected for key,expected in required.items()),'native delivery role drift')
class NativeWriter(SignerWriter):
def __init__(self,io,contract,directory):
self.io=io;self.contract=contract;self.final_ready=False
self._initialize_receipt(directory,contract['operation_id'],kid=contract['kid'],expected_version=0,bindings=contract['bindings'])
def _save(self,*,initial=False):
if not self.final_ready and self.receipt['status']=='custody-verified-pending-independent-public-admission':
self.receipt['status']='seed-verified-pending-metadata'
self.receipt.update(actor='founder-attended-platform-admin',native_custody=True,
admission_sha256=digest(encoded(self.contract)),source_ccr_sha256=self.contract['ccr_sha256'])
return super()._save(initial=initial)
def _http(self,method,path,body=None):
if method=='POST':
validate_admission(self.contract);checked_sources(self.contract);fence(self.io,self.contract)
return self.io.http(method,path,body)
def exact_metadata(status,value,*,created):
require(status==200 and type(value) is dict and type(value.get('data')) is dict,'metadata readback unconfirmed')
data=value['data'];require(type(data.get('cas_required')) is bool and data['cas_required'] is True and
type(data.get('max_versions')) is int and data['max_versions']==2 and data.get('delete_version_after')=='0s' and
type(data.get('current_version')) is int and data['current_version']==(1 if created else 0),'metadata contract drift')
def reconcile(io,contract,directory,original_path):
# A fresh independently approved recover action may read/reconcile only;
# original write receipt remains untouched and no writer/keygen is created.
raw=private_read(original_path);require(digest(raw)==contract['original_receipt_sha256'],'exact original receipt required')
receipt=parsed(raw)
receipt_fields={'schema','operation_id','bindings','writer_sha256','actor','created_at','path','expected_version','kid','status','production_admission','rollback','public_key','public_fingerprint','kv_version','exact_readback','native_custody','admission_sha256','source_ccr_sha256','phase'}
require(type(receipt) is dict and set(receipt)==receipt_fields and receipt.get('schema')=='railiance-platform.signer-cas-receipt.v1' and
receipt.get('status') in {'write-intent-unknown','seed-verified-pending-metadata'} and receipt.get('path')==PATH and
receipt.get('kid')==contract['kid'] and type(receipt.get('expected_version')) is int and receipt['expected_version']==0 and
(receipt.get('kv_version') is None or (type(receipt['kv_version']) is int and receipt['kv_version']==1)) and
type(receipt.get('exact_readback')) is bool and receipt.get('production_admission') is False and
receipt.get('source_ccr_sha256')==contract['ccr_sha256'] and receipt.get('writer_sha256')==contract['source_bundle']['scripts/flex_auth_signer_custody.py'] and
receipt.get('native_custody') is True and receipt.get('bindings')==contract['bindings'] and
type(receipt.get('public_key')) is str and re.fullmatch(r'[A-Za-z0-9_-]{43}',receipt['public_key']),'original custody receipt mismatch')
# Reserve recovery evidence before private native reads. A crash or failure
# leaves this independent attempt unconfirmed and cannot replay its ID.
recovery={'schema':'railiance-platform.signer-cas-recovery.v1','operation_id':contract['operation_id'],
'original_receipt_sha256':digest(raw),'admission_sha256':digest(encoded(contract)),
'status':'recovery-read-intent-unconfirmed','kid':contract['kid'],'kv_version':1,
'public_key':receipt['public_key'],'public_fingerprint':receipt['public_fingerprint'],
'no_write_or_remint':True,'original_unknown_preserved':True}
store=object.__new__(SignerWriter);store.directory=directory;store.path=directory/(contract['operation_id']+'.json');store.receipt=recovery
store._save(initial=True)
exact_metadata(*io.http('GET',META),created=True)
status,value=io.http('GET',PATH+'?version=1');require(status==200 and type(value) is dict,'recovery private readback unconfirmed')
data=value.get('data',{});document=data.get('data');metadata=data.get('metadata',{})
require(type(document) is dict and set(document)==FIELDS and all(type(x) is str for x in document.values()) and
type(metadata.get('version')) is int and metadata['version']==1 and document['kid']==contract['kid'],'recovery version/shape mismatch')
try:
seed=base64.urlsafe_b64decode(document['ED25519_PRIVATE_KEY']+'=')
require(len(seed)==32 and b64(seed)==document['ED25519_PRIVATE_KEY'],'noncanonical recovery seed')
public=b64(Ed25519PrivateKey.from_private_bytes(seed).public_key().public_bytes(serialization.Encoding.Raw,serialization.PublicFormat.Raw))
except Exception:raise Refused('recovery key correspondence unconfirmed') from None
require(hmac.compare_digest(public,document['ED25519_PUBLIC_KEY']) and hmac.compare_digest(public,receipt['public_key']) and
receipt.get('public_fingerprint')==digest(public.encode()),'recovery public correspondence unconfirmed')
store.receipt.update(status='custody-reconciled-pending-independent-public-admission')
store._save()
return dict(store.receipt)
def execute(contract,directory,*,io=None,original_receipt=None):
remaining=validate_admission(contract);checked_sources(contract)
require(isinstance(directory,Path) and directory.absolute()==directory.resolve(),'exact private receipt directory required')
Journal._private(directory.stat(),directory=True)
plan=parsed((ROOT/PLAN).read_bytes())
require(str(directory)==plan['receipts']['directory'],'independently selected native receipt custody required')
if original_receipt is not None:require(Path(original_receipt).parent==directory,'original receipt custody mismatch')
# One native owner window; lock is custody concurrency only, not cluster CAS.
lock=os.open(directory/'.signer-window.lock',os.O_RDWR|os.O_CREAT|os.O_NOFOLLOW,0o600)
try:
Journal._private(os.fstat(lock));fcntl.flock(lock,fcntl.LOCK_EX|fcntl.LOCK_NB)
with _absolute_budget(remaining):
io=NativeIO() if io is None else io
attended(io,contract);fence(io,contract)
if contract['action']=='recover':
require(original_receipt is not None,'independent original receipt required')
return reconcile(io,contract,directory,original_receipt)
require(original_receipt is None,'bootstrap cannot import recovery/private values')
writer=NativeWriter(io,contract,directory)
status,value=io.http('GET',META)
require(_policy_absent(status,value),'bootstrap exact metadata absence required')
writer.receipt.update(status='write-intent-unknown',phase='metadata-setup');writer._save()
try:
validate_admission(contract);checked_sources(contract);fence(io,contract)
status,value=io.http('POST',META,{'cas_required':True,'max_versions':2,'delete_version_after':'0s'})
require(status in {200,204},'metadata write unconfirmed')
exact_metadata(*io.http('GET',META),created=False)
writer.receipt['phase']='seed-cas'
writer.write()
exact_metadata(*io.http('GET',META),created=True)
writer.final_ready=True
writer.receipt.update(status='custody-verified-pending-independent-public-admission',exact_readback=True)
writer._save()
return dict(writer.receipt)
except BaseException:
writer.receipt.update(status='write-intent-unknown',exact_readback=False);writer._save()
raise Refused('native custody outcome unconfirmed') from None
finally:os.close(lock)
class SilentParser(argparse.ArgumentParser):
def error(self,message):raise Refused('invalid custody command arguments')
def main(argv=None):
parser=SilentParser(description=__doc__,add_help=False)
parser.add_argument('--admission',required=True);parser.add_argument('--admission-sha256',required=True)
parser.add_argument('--receipt-directory',required=True);parser.add_argument('--original-receipt')
# argparse output is captured by warden; all execution exceptions are silent.
try:
require(not sys.flags.optimize,'optimized custody interpreter refused')
args=parser.parse_args(argv);raw=private_read(args.admission)
require(is_digest(args.admission_sha256) and digest(raw)==args.admission_sha256,'independently reviewed admission digest required')
execute(parsed(raw),Path(args.receipt_directory),original_receipt=args.original_receipt)
return 0
except BaseException:return 1
if __name__=='__main__':sys.exit(main())

View file

@ -0,0 +1,214 @@
"""Actual disposable KV effects through native production state machine.
Kubernetes, attended identity and owner dispositions are explicitly synthetic
fixture inputs; no production authority/identity/custody claim follows.
"""
from copy import deepcopy
from datetime import datetime,timedelta,timezone
import hashlib
import json
import os
from pathlib import Path
import shutil
import sys
import yaml
import pytest
sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'scripts'))
from test_task_execution_native_fixture import bao_dev
import flex_auth_signer_native as m
from task_execution_journal import Refused
@pytest.fixture
def native(bao_dev,tmp_path,monkeypatch):
origin,token,call,process=bao_dev
assert call('POST','sys/mounts/platform',{'type':'kv','options':{'version':'2'}})[0] in {200,204}
source=tmp_path/'source';source.mkdir()
for name in [*m.SOURCE_FILES,m.CCR,m.PLAN,'openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl']:
p=source/name;p.parent.mkdir(parents=True,exist_ok=True);shutil.copyfile(m.ROOT/name,p)
ccr=yaml.safe_load((source/m.CCR).read_text());ccr['status']='approved';ccr['openbao']['auth']['bound_claims_confirmed']=True
(source/m.CCR).write_text(yaml.safe_dump(ccr,sort_keys=False))
plan=json.loads((source/m.PLAN).read_text());plan['bootstrap']['kid']='synthetic-native-kid';plan['receipts']['directory']=str(tmp_path/'private');(source/m.PLAN).write_text(json.dumps(plan))
monkeypatch.setattr(m,'ROOT',source)
directory=tmp_path/'private';directory.mkdir(mode=0o700)
image='forgejo.coulomb.social/coulomb/flex-auth@sha256:'+'1'*64
dep={'metadata':{'uid':'dep','generation':1},'spec':{'replicas':0,'template':{'spec':{'serviceAccountName':m.NAME,
'containers':[{'image':image,'args':['serve','--caller-auth-mode','enforce','--caller-audience','flex-auth','--caller-binding','secrets-engine=system:serviceaccount:secrets-engine:secrets-engine','--envelope-signing-key-file','/var/run/secrets/decision-envelope-signing/seed','--envelope-signing-kid','synthetic-native-kid'],
'volumeMounts':[{'name':'flex-auth-envelope-signing','mountPath':'/var/run/secrets/decision-envelope-signing','readOnly':True}]}],
'volumes':[{'name':'flex-auth-envelope-signing','secret':{'secretName':m.SECRET,'defaultMode':288,'items':[{'key':'seed','path':'seed'}]}}]}}},'status':{'observedGeneration':1,'replicas':0}}
class IO:
def __init__(self):
self.calls=[];self.dep=deepcopy(dep);self.pods=[];self.hpas=[];self.rs=[];self.cluster=m.CLUSTER
self.entity='11111111-1111-1111-1111-111111111111';self.backend=m.BAO_CLUSTER;self.root=False;self.role_drift=False;self.lost=False;self.denied=False;self.pod_on_recheck=False;self.fences=0
def bao(self,args,payload=None,allowed=(0,)):
self.calls.append(('bao',tuple(args)))
if args[0]=='status':return 0,{'cluster_id':self.backend,'sealed':False}
if args[:2]==['token','lookup']:return 0,{'data':{'policies':['root'] if self.root else ['platform-admin'],'path':'auth/netkingdom/login/x','entity_id':self.entity,'id':token}}
if args[0]=='audit':return 0,{'file/':{}}
if args[-1].startswith('sys/policies/acl/'):
return 0,{'data':{'policy':(source/'openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl').read_text()}}
if args[-1].startswith('auth/kubernetes/role/'):
return 0,{'data':{'bound_service_account_names':['foreign'] if self.role_drift else ['flex-auth-envelope-signing-eso'],'bound_service_account_namespaces':['flex-auth'],'bound_service_account_namespace_selector':'','audience':'openbao','token_policies':['workload-kv-read-flex-auth-envelope-signing'],'token_ttl':900,'token_max_ttl':900,'token_explicit_max_ttl':900,'token_no_default_policy':True,'token_period':0,'token_num_uses':0,'token_bound_cidrs':[]}}
raise AssertionError('unexpected fixed API')
def kube(self,resource,name=None):
self.calls.append(('kube',resource,name))
if resource=='namespace':return {'metadata':{'uid':self.cluster}}
if resource=='deployment':
self.fences+=1
if self.pod_on_recheck and self.fences>=3:self.pods=[{'spec':{'serviceAccountName':m.NAME}}]
return deepcopy(self.dep)
return {'items':deepcopy({'pods':self.pods,'hpa':self.hpas,'replicasets':self.rs}[resource])}
def http(self,method,path,body=None):
self.calls.append(('http',method,path))
if self.denied and path in {m.PATH,m.PATH+'?version=1'} and method=='GET':return 403,{'errors':['secret backend detail']}
result=call(method,path,body)
if self.lost and method=='POST' and path==m.PATH:raise OSError('secret response lost')
return result
io=IO()
c={'schema':'railiance-platform.signer-native-admission.v1','action':'bootstrap','operation_id':'synthetic-native-operation','kid':'synthetic-native-kid',
'expires_at':(datetime.now(timezone.utc)+timedelta(seconds=100)).isoformat(),'founder_entity_id':io.entity,
'ccr_sha256':m.digest((source/m.CCR).read_bytes()),'source_bundle':{n:m.digest((source/n).read_bytes()) for n in m.SOURCE_FILES},
'bindings':{k:'sha256:'+'2'*64 for k in m.BINDINGS},'producer_image':image,'caller_binding':'secrets-engine=system:serviceaccount:secrets-engine:secrets-engine',
'deployment_projection_sha256':m.digest(m.encoded(m.deployment_projection(dep))),
'authority':'founder-attended-exact-source-scope; no standing grant',
'routing':{'lane_id':'openbao-platform-admin-login','verdict':'founder_required','founder_act':'oidc_login','receipt_sha256':'sha256:'+'3'*64},
'owner_reviews':{},'exclusive_window_sha256':'sha256:'+'4'*64,'consumer_trust_sha256':'sha256:'+'5'*64,'original_receipt_sha256':None}
c['bindings'].update(proposal_sha256=m.digest((source/m.PLAN).read_bytes()),policy_sha256=m.LIFECYCLE,image_sha256='sha256:'+'1'*64,trust_sha256=c['consumer_trust_sha256'],deployment_sha256=c['deployment_projection_sha256'],executed_revision_sha256=m.digest(m.encoded(c['source_bundle'])))
def approve(value):
scope=m.digest(m.encoded({k:v for k,v in value.items() if k!='owner_reviews'}))
value['owner_reviews']={role:{'approved_admission_sha256':scope,'approval_ref':'synthetic-fixture-'+role} for role in m.REVIEWS}
return value
approve(c)
return c,io,directory,call,approve,source
def test_actual_native_bootstrap_exact_readback_and_no_retry(native):
c,io,directory,call,approve,source=native
result=m.execute(c,directory,io=io)
assert result['status']=='custody-verified-pending-independent-public-admission' and result['exact_readback']
assert call('GET',m.META)[1]['data']['current_version']==1
assert [(x[1],x[2]) for x in io.calls if x[0]=='http' and x[1]=='POST']==[('POST',m.META),('POST',m.PATH)]
private=call('GET',m.PATH)[1]['data']['data']['ED25519_PRIVATE_KEY']
raw=(directory/(c['operation_id']+'.json')).read_bytes();assert private.encode() not in raw
before=len(io.calls)
with pytest.raises(FileExistsError):m.execute(c,directory,io=io)
assert not any(x[0]=='http' and x[1]=='POST' for x in io.calls[before:])
def test_actual_process_loss_unknown_fresh_recovery_read_only(native):
c,io,directory,call,approve,source=native
child=os.fork()
if child==0:
original_http=io.http
def die_after_native_write(method,path,body=None):
result=original_http(method,path,body)
if method=='POST' and path==m.PATH:os._exit(73)
return result
io.http=die_after_native_write
try:m.execute(c,directory,io=io)
except BaseException:os._exit(74)
os._exit(75)
_,status=os.waitpid(child,0)
assert os.waitstatus_to_exitcode(status)==73
old=directory/(c['operation_id']+'.json');original=old.read_bytes();assert json.loads(original)['status']=='write-intent-unknown'
recovery=deepcopy(c);recovery.update(action='recover',operation_id='synthetic-independent-recovery',original_receipt_sha256=m.digest(original));approve(recovery)
io.lost=False;io.calls.clear()
result=m.execute(recovery,directory,io=io,original_receipt=old)
assert result['status']=='custody-reconciled-pending-independent-public-admission'
assert result['no_write_or_remint'] and old.read_bytes()==original
assert not any(x[0]=='http' and x[1]=='POST' for x in io.calls)
assert call('GET',m.META)[1]['data']['current_version']==1
assert (directory/(recovery['operation_id']+'.json')).stat().st_mode&0o777==0o600
io.calls.clear()
with pytest.raises(FileExistsError):m.execute(recovery,directory,io=io,original_receipt=old)
assert not any(x[0]=='http' for x in io.calls)
@pytest.mark.parametrize('case',['missing-image','missing-trust','mismatched-source','bad-review','bad-route','expired','wrong-entity','wrong-backend','wrong-cluster','root','pod','terminating-pod','hpa','replicas','image-drift','role-drift','pod-after-metadata'])
def test_refusals_no_seed_effect(native,case):
c,io,directory,call,approve,source=native
if case=='missing-image':c['producer_image']=''
elif case=='missing-trust':c['consumer_trust_sha256']=''
elif case=='mismatched-source':(source/'scripts/flex_auth_signer_custody.py').write_text('changed source')
elif case=='bad-review':c['owner_reviews']['founder']['approved_admission_sha256']='sha256:'+'f'*64
elif case=='bad-route':c['routing']['lane_id']='openbao-structure-survey';approve(c)
elif case=='expired':c['expires_at']=(datetime.now(timezone.utc)-timedelta(seconds=1)).isoformat();approve(c)
elif case=='wrong-entity':io.entity='other'
elif case=='wrong-backend':io.backend='other'
elif case=='wrong-cluster':io.cluster='other'
elif case=='root':io.root=True
elif case=='pod':io.pods=[{'spec':{'serviceAccountName':m.NAME}}]
elif case=='terminating-pod':io.pods=[{'metadata':{'deletionTimestamp':'now'},'spec':{'volumes':[{'secret':{'secretName':m.SECRET}}]}}]
elif case=='hpa':io.hpas=[{'spec':{'scaleTargetRef':{'name':m.NAME}}}]
elif case=='replicas':io.dep['spec']['replicas']=1
elif case=='image-drift':io.dep['spec']['template']['spec']['containers'][0]['image']='unreviewed'
elif case=='role-drift':io.role_drift=True
elif case=='pod-after-metadata':io.pod_on_recheck=True
with pytest.raises(Refused):m.execute(c,directory,io=io)
assert not any(x[0]=='http' and x[1]=='POST' and x[2]==m.PATH for x in io.calls)
assert call('GET',m.PATH)[0]==404
@pytest.mark.parametrize('case',['damaged-receipt','typed-damaged-receipt','changed-version','read-denied','no-fresh-review'])
def test_recovery_refuses_without_new_effect(native,case):
c,io,directory,call,approve,source=native;io.lost=True
with pytest.raises(Refused):m.execute(c,directory,io=io)
old=directory/(c['operation_id']+'.json');recovery=deepcopy(c);recovery.update(action='recover',operation_id='recovery',original_receipt_sha256=m.digest(old.read_bytes()));approve(recovery)
if case=='damaged-receipt':old.write_text('{}')
elif case=='typed-damaged-receipt':
damaged=json.loads(old.read_text());damaged['expected_version']=False;old.write_text(json.dumps(damaged));recovery['original_receipt_sha256']=m.digest(old.read_bytes());approve(recovery)
elif case=='changed-version':
doc=call('GET',m.PATH)[1]['data']['data'];assert call('POST',m.PATH,{'options':{'cas':1},'data':doc})[0]==200
elif case=='read-denied':io.denied=True
elif case=='no-fresh-review':recovery['owner_reviews']['founder']['approved_admission_sha256']='sha256:'+'f'*64
io.calls.clear();io.lost=False
with pytest.raises(Refused):m.execute(recovery,directory,io=io,original_receipt=old)
assert not any(x[0]=='http' and x[1]=='POST' for x in io.calls)
@pytest.mark.parametrize('bad_args',[False,True])
def test_main_missing_approval_silent_and_no_io(tmp_path,capsys,bad_args):
if bad_args:
assert m.main(['--unrecognized'])==1
output=capsys.readouterr();assert output.out==output.err==''
return
assert m.main(['--admission',str(tmp_path/'missing'),'--admission-sha256','sha256:'+'0'*64,'--receipt-directory',str(tmp_path)])==1
output=capsys.readouterr();assert output.out==output.err==''
def test_native_command_byte_and_elapsed_bounds_no_output(capsys):
io=m.NativeIO()
with pytest.raises(Refused):io.captured([sys.executable,'-c',"import sys;sys.stdout.write('x'*1048577)"])
import time
started=time.monotonic()
with pytest.raises(Refused):io.captured([sys.executable,'-c',"import time;time.sleep(10)"])
assert time.monotonic()-started<3
output=capsys.readouterr();assert output.out==output.err==''
def test_exclusive_native_window_refuses_second_writer(native):
import fcntl
c,io,directory,call,approve,source=native
fd=os.open(directory/'.signer-window.lock',os.O_RDWR|os.O_CREAT,0o600)
try:
fcntl.flock(fd,fcntl.LOCK_EX|fcntl.LOCK_NB)
with pytest.raises(BlockingIOError):m.execute(c,directory,io=io)
assert io.calls==[]
finally:os.close(fd)
def test_native_fixed_http_transport_against_disposable_backend(bao_dev,monkeypatch,capsys):
origin,token,call,process=bao_dev
assert call('POST','sys/mounts/platform',{'type':'kv','options':{'version':'2'}})[0] in {200,204}
factory=m.http.client.HTTPConnection;port=int(origin.rsplit(':',1)[1]);destinations=[]
def fixture_socket(host,requested_port,*args,**kwargs):
destinations.append((host,requested_port))
return factory(host,port if requested_port==18200 else requested_port,*args,**kwargs)
monkeypatch.setattr(m.http.client,'HTTPConnection',fixture_socket)
io=m.NativeIO();io._token=token
assert io.http('GET',m.META)==(404,{'errors':[]})
assert io.http('POST',m.META,{'cas_required':True,'max_versions':2,'delete_version_after':'0s'})[0] in {200,204}
m.exact_metadata(*io.http('GET',m.META),created=False)
assert all(host=='127.0.0.1' and selected_port==18200 for host,selected_port in destinations)
with pytest.raises(Refused):io.http('GET','other/path')
output=capsys.readouterr();assert output.out==output.err==''

View file

@ -0,0 +1,331 @@
--- a/scripts/flex_auth_signer_native.py
+++ b/scripts/flex_auth_signer_native.py
@@ -75,7 +75,7 @@
def validate_admission(value):
fields={'schema','action','operation_id','kid','expires_at','founder_entity_id','ccr_sha256','source_bundle','bindings',
'producer_image','caller_binding','deployment_projection_sha256','authority','routing','owner_reviews',
- 'exclusive_window_sha256','consumer_trust_sha256','original_receipt_sha256'}
+ 'exclusive_window_sha256','consumer_trust_sha256','original_receipt_sha256','original_execution'}
require(type(value) is dict and set(value)==fields,'exact admission schema required')
require(value['schema']=='railiance-platform.signer-native-admission.v1' and value['action'] in {'bootstrap','recover'},'native admission action refused')
require(type(value['operation_id']) is str and re.fullmatch(r'[A-Za-z0-9_-]{1,80}',value['operation_id']) and
@@ -104,6 +104,15 @@
and value['bindings']['executed_revision_sha256']==digest(encoded(value['source_bundle'])),'immutable lifecycle/image/trust/source mismatch')
require((value['action']=='bootstrap' and value['original_receipt_sha256'] is None) or
(value['action']=='recover' and is_digest(value['original_receipt_sha256'])),'separate recovery disposition required')
+ original=value['original_execution']
+ if value['action']=='bootstrap':require(original is None,'bootstrap cannot import execution lineage')
+ else:
+ require(type(original) is dict and set(original)=={'bindings','writer_sha256','source_ccr_sha256'} and
+ is_digest(original['writer_sha256']) and is_digest(original['source_ccr_sha256']) and
+ type(original['bindings']) is dict and set(original['bindings'])==BINDINGS and
+ all(is_digest(x) for x in original['bindings'].values()),'independently reviewed original execution lineage required')
+ require(all(original['bindings'][key]==value['bindings'][key] for key in BINDINGS-{'proposal_sha256','executed_revision_sha256'}),
+ 'recovery cannot change original image/deployment/policy/trust authority')
try:expires=datetime.fromisoformat(value['expires_at'].replace('Z','+00:00'));remaining=(expires-datetime.now(timezone.utc)).total_seconds()
except Exception:raise Refused('invalid authority expiry') from None
require(expires.utcoffset().total_seconds()==0 and 5<remaining<=120,'bounded fresh attended window required')
@@ -226,7 +235,8 @@
require(path.resolve()==path.absolute() and not path.is_symlink() and path.stat().st_mode&0o022==0
and path.is_file() and path.stat().st_size<=1048576 and digest(path.read_bytes())==pin,'exact admitted execution source required')
planraw=(ROOT/PLAN).read_bytes();require(digest(planraw)==contract['bindings']['proposal_sha256'],'exact native action proposal required')
- plan=parsed(planraw);require(plan.get('proposal_id')=='RPF35-PDP-SIGNER-NATIVE-20261016-R1' and plan.get('bootstrap',{}).get('kid')==contract['kid'],'exact proposed bootstrap kid required')
+ plan=parsed(planraw);require(plan.get('bootstrap',{}).get('source_primitive_sha256')==contract['source_bundle']['scripts/flex_auth_signer_custody.py'],'proposal executed primitive lineage mismatch')
+ require(plan.get('proposal_id')=='RPF35-PDP-SIGNER-NATIVE-20261016-R1' and plan.get('bootstrap',{}).get('kid')==contract['kid'],'exact proposed bootstrap kid required')
ccrraw=(ROOT/CCR).read_bytes();require(digest(ccrraw)==contract['ccr_sha256'],'reviewed read CCR source mismatch')
spec=importlib.util.spec_from_file_location('native_signer_ccr',ROOT/'scripts/credential-change.py');module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)
ccr,errors,_=module.validate_ccr(ROOT/CCR)
@@ -277,7 +287,7 @@
def exact_metadata(status,value,*,created):
- require(status==200 and type(value) is dict and type(value.get('data')) is dict,'metadata readback unconfirmed')
+ require(status==200 and type(value) is dict and 'errors' not in value and type(value.get('data')) is dict,'metadata readback unconfirmed')
data=value['data'];require(type(data.get('cas_required')) is bool and data['cas_required'] is True and
type(data.get('max_versions')) is int and data['max_versions']==2 and data.get('delete_version_after')=='0s' and
type(data.get('current_version')) is int and data['current_version']==(1 if created else 0),'metadata contract drift')
@@ -294,20 +304,43 @@
receipt.get('kid')==contract['kid'] and type(receipt.get('expected_version')) is int and receipt['expected_version']==0 and
(receipt.get('kv_version') is None or (type(receipt['kv_version']) is int and receipt['kv_version']==1)) and
type(receipt.get('exact_readback')) is bool and receipt.get('production_admission') is False and
- receipt.get('source_ccr_sha256')==contract['ccr_sha256'] and receipt.get('writer_sha256')==contract['source_bundle']['scripts/flex_auth_signer_custody.py'] and
- receipt.get('native_custody') is True and receipt.get('bindings')==contract['bindings'] and
- type(receipt.get('public_key')) is str and re.fullmatch(r'[A-Za-z0-9_-]{43}',receipt['public_key']),'original custody receipt mismatch')
+ receipt.get('source_ccr_sha256')==contract['original_execution']['source_ccr_sha256'] and receipt.get('writer_sha256')==contract['original_execution']['writer_sha256'] and
+ receipt.get('native_custody') is True and receipt.get('bindings')==contract['original_execution']['bindings'] and
+ receipt.get('phase') in {'metadata-setup','seed-cas'},'original custody receipt mismatch')
+ has_public=(type(receipt.get('public_key')) is str and re.fullmatch(r'[A-Za-z0-9_-]{43}',receipt['public_key']) is not None
+ and receipt.get('public_fingerprint')==digest(receipt['public_key'].encode()))
+ no_public=(receipt.get('public_key') is None and receipt.get('public_fingerprint') is None)
+ require((receipt['phase']=='metadata-setup' and no_public and receipt['kv_version'] is None and receipt['exact_readback'] is False)
+ or (receipt['phase']=='seed-cas' and has_public),'original phase/public lineage mismatch')
# Reserve recovery evidence before private native reads. A crash or failure
# leaves this independent attempt unconfirmed and cannot replay its ID.
recovery={'schema':'railiance-platform.signer-cas-recovery.v1','operation_id':contract['operation_id'],
- 'original_receipt_sha256':digest(raw),'admission_sha256':digest(encoded(contract)),
- 'status':'recovery-read-intent-unconfirmed','kid':contract['kid'],'kv_version':1,
- 'public_key':receipt['public_key'],'public_fingerprint':receipt['public_fingerprint'],
+ 'original_receipt_sha256':digest(raw),'original_execution':contract['original_execution'],
+ 'admission_sha256':digest(encoded(contract)),
+ 'status':'recovery-read-intent-unconfirmed','original_phase':receipt['phase'],
+ 'kid':contract['kid'],'kv_version':None,'public_key':None,'public_fingerprint':None,
+ 'key_custody_verified':False,'historical_effect':'unresolved',
+ 'next_action':'fresh independently reviewed exact action required; no generation/write/retry authority',
'no_write_or_remint':True,'original_unknown_preserved':True}
store=object.__new__(SignerWriter);store.directory=directory;store.path=directory/(contract['operation_id']+'.json');store.receipt=recovery
store._save(initial=True)
- exact_metadata(*io.http('GET',META),created=True)
- status,value=io.http('GET',PATH+'?version=1');require(status==200 and type(value) is dict,'recovery private readback unconfirmed')
+ status,value=io.http('GET',META)
+ if _policy_absent(status,value):
+ absent=True
+ else:
+ require(status==200 and type(value) is dict and 'errors' not in value and type(value.get('data')) is dict,'recovery metadata unconfirmed')
+ current=value['data'].get('current_version')
+ require(type(current) is int and current in {0,1},'recovery metadata version ambiguous')
+ exact_metadata(status,value,created=current==1)
+ absent=False
+ if current==1:require(has_public and receipt['phase']=='seed-cas','unexpected key custody after metadata-only phase')
+ if absent or current==0:
+ status,value=io.http('GET',PATH+'?version=1')
+ require(_policy_absent(status,value),'recovery data absence unconfirmed')
+ store.receipt.update(status='current-custody-absent' if absent else 'metadata-only-confirmed')
+ store._save()
+ return dict(store.receipt)
+ status,value=io.http('GET',PATH+'?version=1');require(status==200 and type(value) is dict and 'errors' not in value,'recovery private readback unconfirmed')
data=value.get('data',{});document=data.get('data');metadata=data.get('metadata',{})
require(type(document) is dict and set(document)==FIELDS and all(type(x) is str for x in document.values()) and
type(metadata.get('version')) is int and metadata['version']==1 and document['kid']==contract['kid'],'recovery version/shape mismatch')
@@ -318,7 +351,7 @@
except Exception:raise Refused('recovery key correspondence unconfirmed') from None
require(hmac.compare_digest(public,document['ED25519_PUBLIC_KEY']) and hmac.compare_digest(public,receipt['public_key']) and
receipt.get('public_fingerprint')==digest(public.encode()),'recovery public correspondence unconfirmed')
- store.receipt.update(status='custody-reconciled-pending-independent-public-admission')
+ store.receipt.update(status='custody-reconciled-pending-independent-public-admission',kv_version=1,public_key=public,public_fingerprint=digest(public.encode()),key_custody_verified=True)
store._save()
return dict(store.receipt)
--- a/tests/test_flex_auth_signer_native.py
+++ b/tests/test_flex_auth_signer_native.py
@@ -72,9 +72,12 @@
'deployment_projection_sha256':m.digest(m.encoded(m.deployment_projection(dep))),
'authority':'founder-attended-exact-source-scope; no standing grant',
'routing':{'lane_id':'openbao-platform-admin-login','verdict':'founder_required','founder_act':'oidc_login','receipt_sha256':'sha256:'+'3'*64},
- 'owner_reviews':{},'exclusive_window_sha256':'sha256:'+'4'*64,'consumer_trust_sha256':'sha256:'+'5'*64,'original_receipt_sha256':None}
+ 'owner_reviews':{},'exclusive_window_sha256':'sha256:'+'4'*64,'consumer_trust_sha256':'sha256:'+'5'*64,'original_receipt_sha256':None,'original_execution':None}
c['bindings'].update(proposal_sha256=m.digest((source/m.PLAN).read_bytes()),policy_sha256=m.LIFECYCLE,image_sha256='sha256:'+'1'*64,trust_sha256=c['consumer_trust_sha256'],deployment_sha256=c['deployment_projection_sha256'],executed_revision_sha256=m.digest(m.encoded(c['source_bundle'])))
def approve(value):
+ if value['action']=='recover' and value['original_execution'] is None:
+ old=json.loads((directory/(c['operation_id']+'.json')).read_bytes())
+ value['original_execution']={key:deepcopy(old[key]) for key in ['bindings','writer_sha256','source_ccr_sha256']}
scope=m.digest(m.encoded({k:v for k,v in value.items() if k!='owner_reviews'}))
value['owner_reviews']={role:{'approved_admission_sha256':scope,'approval_ref':'synthetic-fixture-'+role} for role in m.REVIEWS}
return value
@@ -212,3 +215,104 @@
assert all(host=='127.0.0.1' and selected_port==18200 for host,selected_port in destinations)
with pytest.raises(Refused):io.http('GET','other/path')
output=capsys.readouterr();assert output.out==output.err==''
+
+
+def metadata_phase_crash(native,*,after):
+ c,io,directory,call,approve,source=native
+ child=os.fork()
+ if child==0:
+ original=io.http
+ def die(method,path,body=None):
+ if method=='POST' and path==m.META:
+ if after:original(method,path,body)
+ os._exit(73)
+ return original(method,path,body)
+ io.http=die
+ try:m.execute(c,directory,io=io)
+ except BaseException:os._exit(74)
+ os._exit(75)
+ _,status=os.waitpid(child,0);assert os.waitstatus_to_exitcode(status)==73
+ old=directory/(c['operation_id']+'.json');raw=old.read_bytes();receipt=json.loads(raw)
+ assert receipt['phase']=='metadata-setup' and receipt['status']=='write-intent-unknown'
+ assert receipt['public_key'] is None and receipt['kv_version'] is None
+ recovery=deepcopy(c);recovery.update(action='recover',operation_id='metadata-recovery',original_receipt_sha256=m.digest(raw));approve(recovery)
+ return old,raw,recovery
+
+
+@pytest.mark.parametrize('after',[False,True])
+def test_real_metadata_process_loss_read_only_phase_recovery(native,after):
+ c,io,directory,call,approve,source=native
+ old,raw,recovery=metadata_phase_crash(native,after=after)
+ io.calls.clear();result=m.execute(recovery,directory,io=io,original_receipt=old)
+ assert result['status']==('metadata-only-confirmed' if after else 'current-custody-absent')
+ assert result['public_key'] is result['public_fingerprint'] is result['kv_version'] is None
+ assert result['key_custody_verified'] is False and result['historical_effect']=='unresolved'
+ assert 'fresh independently reviewed exact action required' in result['next_action']
+ assert old.read_bytes()==raw and result['original_unknown_preserved']
+ assert not any(x[0]=='http' and x[1]=='POST' for x in io.calls)
+ assert call('GET',m.PATH+'?version=1')==(404,{'errors':[]})
+
+
+@pytest.mark.parametrize('case',['metadata-denied','metadata-misleading404','metadata-misleading200','metadata-malformed','metadata-timeout','data-denied','unexpected-version'])
+def test_metadata_recovery_ambiguous_or_denied_never_classifies_or_writes(native,case):
+ c,io,directory,call,approve,source=native
+ old,raw,recovery=metadata_phase_crash(native,after=True)
+ if case=='unexpected-version':
+ assert call('POST',m.PATH,{'options':{'cas':0},'data':{'ED25519_PRIVATE_KEY':'synthetic-unadmitted','ED25519_PUBLIC_KEY':'synthetic-unadmitted','kid':c['kid']}})[0]==200
+ original=io.http
+ def faulty(method,path,body=None):
+ if path==m.META:
+ if case=='metadata-denied':return 403,{'errors':['denied']}
+ if case=='metadata-misleading404':return 404,{'errors':['permission denied']}
+ if case=='metadata-misleading200':
+ result=original(method,path,body);result[1]['errors']=['denied'];return result
+ if case=='metadata-malformed':return 200,{'data':{'current_version':False}}
+ if case=='metadata-timeout':raise OSError('synthetic private backend detail')
+ if path==m.PATH+'?version=1' and case=='data-denied':return 404,{'errors':['denied']}
+ return original(method,path,body)
+ io.http=faulty;io.calls.clear()
+ with pytest.raises((Refused,OSError)):m.execute(recovery,directory,io=io,original_receipt=old)
+ evidence=json.loads((directory/(recovery['operation_id']+'.json')).read_bytes())
+ assert evidence['status']=='recovery-read-intent-unconfirmed' and evidence['key_custody_verified'] is False
+ assert old.read_bytes()==raw
+ assert not any(x[0]=='http' and x[1]=='POST' for x in io.calls)
+ if case=='unexpected-version':assert not any(x[0]=='http' and x[2]==m.PATH+'?version=1' for x in io.calls)
+
+
+def test_stale_current_primitive_lineage_refuses_before_native_io(native):
+ c,io,directory,call,approve,source=native
+ plan=json.loads((source/m.PLAN).read_bytes());plan['bootstrap']['source_primitive_sha256']='sha256:'+'8'*64
+ (source/m.PLAN).write_text(json.dumps(plan));c['bindings']['proposal_sha256']=m.digest((source/m.PLAN).read_bytes());approve(c)
+ with pytest.raises(Refused,match='primitive lineage'):m.execute(c,directory,io=io)
+ assert io.calls==[]
+
+
+def test_recovery_explicitly_binds_historical_execution_and_current_source(native):
+ c,io,directory,call,approve,source=native
+ old,raw,recovery=metadata_phase_crash(native,after=True)
+ # Model a separately retained older executed bundle/proposal. It must be
+ # explicitly selected in fresh recovery review, never treated as current.
+ original=json.loads(raw);original['bindings']['executed_revision_sha256']='sha256:'+'6'*64
+ original['bindings']['proposal_sha256']='sha256:'+'7'*64
+ old.write_text(json.dumps(original));recovery['original_receipt_sha256']=m.digest(old.read_bytes())
+ recovery['original_execution']={key:deepcopy(original[key]) for key in ['bindings','writer_sha256','source_ccr_sha256']};approve(recovery)
+ result=m.execute(recovery,directory,io=io,original_receipt=old)
+ assert result['status']=='metadata-only-confirmed'
+ assert result['original_execution']['bindings']['executed_revision_sha256']!=recovery['bindings']['executed_revision_sha256']
+ assert json.loads(old.read_text())==original
+
+
+def test_generated_but_unstored_seed_phase_recovers_without_public_admission(native):
+ c,io,directory,call,approve,source=native
+ io.pod_on_recheck=True
+ with pytest.raises(Refused):m.execute(c,directory,io=io)
+ old=directory/(c['operation_id']+'.json');raw=old.read_bytes();receipt=json.loads(raw)
+ assert receipt['phase']=='seed-cas' and receipt['public_key'] is not None
+ assert call('GET',m.PATH+'?version=1')==(404,{'errors':[]})
+ io.pod_on_recheck=False;io.pods=[];io.calls.clear()
+ recovery=deepcopy(c);recovery.update(action='recover',operation_id='unstored-seed-recovery',original_receipt_sha256=m.digest(raw));approve(recovery)
+ result=m.execute(recovery,directory,io=io,original_receipt=old)
+ assert result['status']=='metadata-only-confirmed' and result['key_custody_verified'] is False
+ assert result['public_key'] is None and result['public_fingerprint'] is None
+ assert old.read_bytes()==raw
+ assert not any(x[0]=='http' and x[1]=='POST' for x in io.calls)
--- a/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json
@@ -55,7 +55,9 @@
"purpose": "exact private readback in custody, closed public receipt only"
}
],
- "source_primitive_sha256": "sha256:8ed281a49c1f1cdb23772efe3593e75c62572a12b8d7a6a4c574f27b38722e75"
+ "source_primitive_sha256": "sha256:f169ad12f90d729b647085c3bed2c0d8d005db23a2341b6d3d5f1793bcfd110c",
+ "historical_source_primitive_sha256": "sha256:8ed281a49c1f1cdb23772efe3593e75c62572a12b8d7a6a4c574f27b38722e75",
+ "source_lineage_rule": "source_primitive_sha256 must equal current admitted source_bundle[scripts/flex_auth_signer_custody.py] and exact writer receipt; historical_source_primitive_sha256 is non-authoritative R1 evidence only"
},
"fencing": {
"before_key_generation": [
--- a/docs/credential-lane-designs/flex-auth-envelope-signing-native-adapter.md
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing-native-adapter.md
@@ -117,3 +117,71 @@
PV Retain/reclaim disposition and runtime process-lock/capacity/startup bounds
remain separate reviews. This writer grants no PVC, audit egress, policy role,
SecretStore/Secret or producer/consumer deployment admission.
+
+## R2 phase-aware recovery and source lineage
+
+Frozen R1's metadata-only crash counterexample remains valid historical evidence.
+R2 accepts a strict original metadata-setup receipt with no public key, or a
+seed-cas receipt with a canonical derived public key. Fresh recovery reserves
+durable evidence before target reads. Exact current metadata absence AND exact
+version1 data absence yields `current-custody-absent`. Exact configured metadata
+with current_version0 AND exact version1 data absence yields
+`metadata-only-confirmed`. Both results have no public key/fingerprint/KV version,
+key_custody_verified=false, historical_effect=unresolved, original_unknown_preserved,
+and an explicit fresh independently reviewed action requirement. A locally
+generated but unstored seed's public key is not promoted into public admission.
+Neither observation means the original attempt historically made no effects.
+
+Denied/malformed/misleading404 metadata or data, timeout, changed configuration,
+unexpected versions, or version1 after a metadata-only/no-public phase keep the
+new recovery attempt unconfirmed. They never grant generation, write, retry,
+remint, consume or business authority. A subsequent metadata-only bootstrap is
+not silently enabled: existing metadata still refuses CAS0 bootstrap admission.
+A distinct exact reviewed action/source revision must choose any cleanup/reuse
+or generation step. The original receipt remains byte-for-byte unchanged.
+
+Native plan bootstrap.source_primitive_sha256 now equals the actual shared
+primitive in the admitted source bundle and writer receipt; checked_sources
+enforces the join. The prior8ed pin is explicitly historical. The plan pins the
+shared primitive, not itself or the complete native executor, so no self-hash
+cycle exists. The exact native executor is independently pinned by source_bundle
+and its digest in bindings. A recover admission must additionally supply
+original_execution (original receipt bindings, writer SHA and CCR SHA). Fresh
+owner reviews bind both original and current lineages. This can describe an older
+executed source/proposal without labeling it current. Image/deployment/policy/
+consumer trust bindings must still match; recovery cannot change that authority.
+
+## Exact120-second expiry and attended scheduling assessment
+
+The unchanged validate_admission rule requires an absolute UTC expires_at with
+5 < expires_at-now <=120seconds, and the entire child runs beneath that fixed
+remaining absolute/monotonic budget. The five external reviews cover the scope
+digest including expires_at. No deadline is generated, refreshed or widened by
+the child; rechecking cannot extend it.
+
+Existing ops-warden proxy.proxy_attended_login_exec runs the OIDC client before the child.
+Its _contained_run captures output through subprocess.run without a timeout.
+Therefore neither OIDC/MFA nor five external reviews have a demonstrated bound
+that fits120seconds from preparing an admission. Creating a now+120seconds
+admission before unbounded review/login is not a reliable operational ceremony.
+Source success tests start from synthetic ready identities; they prove no human
+OIDC/review timing SLO.
+
+A coherent ceremony under the current rule is to pre-review one exact future
+absolute expiry/window, including all artifact/source/image/trust/identity and
+review references, then arrange attended login so the child starts within its
+implicit [expires_at-120seconds, expires_at-5seconds) interval. Review may occur
+before that interval; it does not change runtime authority. The founder must aim
+for the beginning of the interval, not its last five seconds. The fixed path has
+26 bounded native/tool requests before success (52seconds at their2second caps),
+plus key/source/receipt work. Its expiry still wins if those operations cannot
+finish; no completion or filesystem timing guarantee is inferred.
+
+If login completes too early/late, a review changes any bytes, or adequate time
+remains uncertain, refuse this revision. A new expires_at changes the approved
+scope digest and needs explicit dispositions for the new revision; existing
+source-only reviews cannot be copied into native approval. Do not silently
+refresh after OIDC, reinterpret expiry as start-only, widen120seconds, skip
+reviews or start a general window-grant service. An alternative authority/window
+contract would itself require separate owner/founder design and is not supplied
+by R2. Current native admission remains unapproved.
--- a/workplans/RPF-WP-0035-credential-lane-implementation.md
+++ b/workplans/RPF-WP-0035-credential-lane-implementation.md
@@ -1365,3 +1365,13 @@
OIDC admin login; mismatch remains a live refusal. Canonical proposed CCR, native
owner/founder approval and Access/cluster/outbox/PVC gates stay open; no live
key, credential read, grant, role, storage or deployment action performed.
+
+2026-10-16 T12 native signer R2 source correction preserves R1 failures: exact
+phase-aware read-only recovery classifies current metadata-only/absent states,
+never admits a key or retry and preserves original uncertainty. Fresh recovery
+explicitly binds historical execution plus current source; current primitive
+plan/source/receipt join is enforced and old pin labeled historical. Real fork
+metadata-loss and denied/ambiguous tests added.120second expiry is unchanged;
+external reviews and unbounded OIDC require a pre-reviewed exact scheduled
+window, otherwise refuse/new explicit revision. No native or owner approval
+inferred; no live action.

View file

@ -0,0 +1,17 @@
{
"unchanged_runtime_rule": "5 < expires_at-now <=120seconds; outer absolute/monotonic budget is remaining until same immutable expiry",
"review_binding": "All five native dispositions bind scope digest including expires_at; changing expiry changes revision and invalidates old dispositions",
"oidc_order": "ops-warden proxy.proxy_attended_login_exec invokes login via _contained_run before reviewed child",
"oidc_bound": "No timeout argument in source _contained_run subprocess.run; no proven review/OIDC bound fits120seconds",
"coherent_current_contract": "Pre-review exact future absolute expiry/window and all immutable selections; founder arranges OIDC so child begins near start of implicit [expiry-120s,expiry-5s). Early/late completion refuses. New expiry requires explicit new exact review dispositions, not automatic refresh.",
"transport_budget": "26 fixed native/tool requests at2second caps =52seconds transport cap before success, plus source/key/receipt work; start near window opening, not reserve edge. No completion/filesystem timing SLO claimed.",
"not_authorized": [
"TTL widening",
"start-only reinterpretation",
"post-OIDC remint/expiry refresh",
"copying source review into native approval",
"omitting external reviews",
"new generic window-grant service"
],
"execution_admitted": false
}

View file

@ -0,0 +1,35 @@
{
"full_patch_sha256": "c5b8f0d10650f13eb5939874bda1fb457b3dc5835e63e043af9824e43edb9f80",
"delta_sha256": "81f4c2867d09accc33a22f7537526c0bcf86c4e45803639af0c602b6446c9ab1",
"command": "SECRETS_ENGINE_SOURCE_ROOT=/tmp/netkingdom-secrets-trusted-successor PYTHONPATH=/tmp/netkingdom-secrets-trusted-successor/src pytest -q tests/test_flex_auth_signer_native.py tests/test_flex_auth_signer_custody.py tests/test_flex_auth_signer_native_preparation.py",
"result": "56 passed in 39.87s",
"prior_checks": [
{
"result": "54 passed in 46.44s",
"stage": "before unstored seed/public non-admission and misleadingHTTP200 error cases"
},
{
"result": "1 passed in 0.79s",
"stage": "unstored-seed recovery case before final full suite"
}
],
"apply_checks": {
"r1_to_r2": "pass exact preimages",
"full": "pass exact composite base preimages/new-file absence"
},
"new_cases": [
"actual fork death immediately before metadata POST: current-custody-absent, no key admitted",
"actual fork death immediately after real metadata POST: metadata-only-confirmed, original unchanged",
"metadata403/misleading404/misleading200/typed-malformed/timeout refuse, durable recovery intent stays unconfirmed",
"exact metadata0 but denied/misleading data absence refuses",
"unexpected currentversion1 after metadata-only/no-public phase refuses before private GET",
"stale primitive plan pin refuses before native I/O even with freshly selected plan digest",
"fresh explicit original_execution/current-source relationship with historical binding unit model",
"generated-but-unstored seed phase recovers metadata-only with no public key admission",
"all existing44 R1 cases retained"
],
"preserved_findings": {
"platform-signer-custody/native-adapter-r1/coordinator-review.json": "sha256:6e5b58d0c1fcb4f8bcb1dccb5b0e97cc99e2ba908f81a8750572e6e6b762162a",
"decision-native-preparation/platform-public-trust-review-r1.json": "sha256:30525ed0ac6b3e44affdac1ab23b80ef8dc7353c559d199db7cb689d1d0bf09d"
}
}

View file

@ -0,0 +1,11 @@
# Platform owner-boundary source correction R3
Exact base a3e8eb64; five-file patch replaces conflated `cluster-gitops-owner` with `cluster-runtime-storage-owner` in native admission role set, proposed CCR0050 and exact native action plan. Documentation scopes each existing slot; tests accept exact corrected set and reject old, missing or blanket extra roles before backend calls.
Platform owns OpenBao/custody and existing GitOps Applications/freeze integration. Access owns retained workload/release/Helm values and signer/outbox behavior. Cluster reviews actual runtime/storage/headroom/local-path/PV retention only; it has no workload or GitOps Application authority. Enablement reviews only actual ArgoCD installation/controller changes; this fixed custody action includes none. No invented combined assent or mandatory blanket sixth reviewer.
A documentation-only alias would preserve the misleading enforced role and allow apparent combined authority, so Platform proposes an explicit source revision. Frozen R1/R2 reviews, P1/P2 scopes, their test failures and approvals remain unchanged. Existing dispositions cannot be copied into the corrected action.
After independent source review/integration/publication, a successor packet must pin the new six-module source bundle (native executor changed), plan and proposed CCR hashes. It must select the same independently reviewed candidate/proposed baseline/projection unless explicitly changed. CCR remains proposed/bound claims false until actual independent decision/binding evidence; no approved status invented here. Collect fresh exact corrected five-role dispositions for completed scope, founder entity/window/expiry/command/file digest. Existing P2 is review preparation, not active authority. Stop/exclusive window/native projection/storage and consumer trust/install/session remain independent gates.
Platform disposition: approve source correction for coordinator and affected owners to review; no founder decision, cluster assent, Access/Secrets/Enablement approval, native execution or credential access claimed. Incremental infrastructure cost EUR0; reduces ambiguous authority coverage without adding a service/grant. Native custody high-risk/outage/loss scope remains unapproved.

View file

@ -0,0 +1,92 @@
--- a/scripts/flex_auth_signer_native.py
+++ b/scripts/flex_auth_signer_native.py
@@ -41,7 +41,7 @@
SECRET='flex-auth-secrets-engine-envelope-signing'
ROLE='external-secrets-flex-auth-envelope-signing'
LIFECYCLE='sha256:bd11c5fe77ce6439c65fea225ad6b71d2110efc5e7b5bc9b499c59cd0a53b8b4'
-REVIEWS={'founder','railiance-platform-owner','access-engine-owner','secrets-engine-owner','cluster-gitops-owner'}
+REVIEWS={'founder','railiance-platform-owner','access-engine-owner','secrets-engine-owner','cluster-runtime-storage-owner'}
SOURCE_FILES={'scripts/flex_auth_signer_native.py','scripts/flex_auth_signer_custody.py','scripts/task_execution_native_fixture.py','scripts/task_execution_journal.py','scripts/task_execution_recovery.py','scripts/credential-change.py'}
--- a/credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml
+++ b/credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml
@@ -18,7 +18,7 @@
- railiance-platform-owner
- access-engine-owner
- secrets-engine-owner
- - cluster-gitops-owner
+ - cluster-runtime-storage-owner
comments: []
target:
domain: infotech
--- a/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json
@@ -134,7 +134,7 @@
"disposition": "pending exact source/artifact review; no approval recorded"
},
{
- "role": "cluster-gitops-owner",
+ "role": "cluster-runtime-storage-owner",
"disposition": "pending exact source/artifact review; no approval recorded"
},
{
@@ -148,5 +148,11 @@
"read_ccr_field_projection": "Only ED25519_PRIVATE_KEY is delivered; public sibling fields remain visible to dedicated document-reader identity but never become consumer trust discovery",
"projection_source": "docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed",
"managed_declaration": "Missing rapp declaration remains migration debt; retain actual Access919ce25 workload identity, do not create package tuple"
+ },
+ "owner_review_scope": {
+ "railiance-platform-owner": "OpenBao custody/read lane; existing GitOps Applications and their sync/freeze/controller ownership; covers explicitly identified workload/Helm competing-writer integration with Access, not Access workload semantic ownership.",
+ "access-engine-owner": "Retained workload/release source, Helm values, candidate image, signer delivery/mount and process/outbox fencing/rollback.",
+ "cluster-runtime-storage-owner": "railiance-cluster: cluster identity/runtime/storage/headroom, local-path provisioning and actual PV retention; excludes workload and GitOps Application ownership.",
+ "enablement_condition": "railiance-enablement additional exact review required only if proposed action changes ArgoCD installation/controller configuration; this fixed custody action authorizes no such change. Changed scope needs a separately reviewed exact revision rather than blanket extra reviewer."
}
}
--- a/docs/credential-lane-designs/flex-auth-envelope-signing-native-adapter.md
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing-native-adapter.md
@@ -185,3 +185,21 @@
reviews or start a general window-grant service. An alternative authority/window
contract would itself require separate owner/founder design and is not supplied
by R2. Current native admission remains unapproved.
+
+## R3 precise owner review boundary
+
+The native review slot is now `cluster-runtime-storage-owner`, held by
+railiance-cluster for runtime/storage/headroom only. The previous
+`cluster-gitops-owner` slot conflated separate authorities and is rejected;
+no historic review is reinterpreted as a combined assent. Platform's existing
+review covers its custody/read lane and existing GitOps Applications/freeze
+integration. Access retains workload/release/Helm-values and signer/outbox
+ownership. Enablement review is needed only when the action actually changes
+ArgoCD installation/controller configuration; this action does not authorize
+that. Changed scope requires a separate exact revision and any affected owner.
+
+This is a source-schema correction, not a new approval. Existing R1/R2 source
+reviews and P1/P2 packets remain frozen. A successor packet must pin the new
+executor/source-bundle/plan/CCR hashes and collect five exact fresh dispositions
+under the corrected role set. Admission-file digest approval and CCR
+approved/binding-confirmed status/native checks remain unchanged.
--- a/tests/test_flex_auth_signer_native.py
+++ b/tests/test_flex_auth_signer_native.py
@@ -316,3 +316,20 @@
assert result['public_key'] is None and result['public_fingerprint'] is None
assert old.read_bytes()==raw
assert not any(x[0]=='http' and x[1]=='POST' for x in io.calls)
+
+
+def test_exact_runtime_storage_owner_slot_replaces_conflated_role(native):
+ c,io,directory,call,approve,source=native
+ assert m.REVIEWS=={'founder','railiance-platform-owner','access-engine-owner','secrets-engine-owner','cluster-runtime-storage-owner'}
+ assert m.validate_admission(c)>5
+ old=deepcopy(c)
+ old['owner_reviews']['cluster-gitops-owner']=old['owner_reviews'].pop('cluster-runtime-storage-owner')
+ with pytest.raises(Refused,match='distinct owner reviews'):
+ m.validate_admission(old)
+ missing=deepcopy(c);missing['owner_reviews'].pop('cluster-runtime-storage-owner')
+ with pytest.raises(Refused,match='distinct owner reviews'):
+ m.validate_admission(missing)
+ extra=deepcopy(c);extra['owner_reviews']['railiance-enablement-owner']=next(iter(extra['owner_reviews'].values()))
+ with pytest.raises(Refused,match='distinct owner reviews'):
+ m.validate_admission(extra)
+ assert io.calls==[]

View file

@ -0,0 +1,33 @@
{
"proposal_id": "RPF35-PDP-SIGNER-OWNER-BOUNDARY-20261016-R3",
"base_commit": "a3e8eb64bae110393ba0aeab043de03a92b8421a",
"role_replacement": {
"old": "cluster-gitops-owner",
"new": "cluster-runtime-storage-owner"
},
"before_sha256": {
"scripts/flex_auth_signer_native.py": "sha256:5a3b3b33bcad45ef2939413bfd3db76e5ee70e3b815d0d64af53147851aa3a0d",
"credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml": "sha256:72fd6e84e866a72f4768dabd66326aebe1fdd00013b2314de961ca72a632679f",
"docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json": "sha256:5f8c37a7d9781884bf06050be80ca192a3b92eab2d20e28f9b304e57e7456844",
"docs/credential-lane-designs/flex-auth-envelope-signing-native-adapter.md": "sha256:0e185a651fbbef96996c38ff18ce338f4d6c0117c3fb069a43e766816e56d5db",
"tests/test_flex_auth_signer_native.py": "sha256:8a99d1819c54fc20660cd6d16e8b429e2fc9f30cf4cefc90704201a588c825a6"
},
"files_sha256": {
"scripts/flex_auth_signer_native.py": "sha256:4fb9ce6608ad04c4f5dc98a95796dc88a08a2453dc15c0d40c5c688fc02f573f",
"credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml": "sha256:23e4e008144350547c4726ca01e579b583b179b7a767d9406c4c176c1a645343",
"docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json": "sha256:1865083d8382da4d987ef33586919a262970776c71b523c8cb027c027b550ece",
"docs/credential-lane-designs/flex-auth-envelope-signing-native-adapter.md": "sha256:0e2e431dcd2df66e65434ce4060a4d5b52941f50928be80a075e4f972229d3a9",
"tests/test_flex_auth_signer_native.py": "sha256:f74a2316506737966e2ea539799cb477a9cec2718a6679f417478f9972731b3e"
},
"patch_sha256": "sha256:266a5ac36b291c40e7c8a6d3da9b03169d454fac6c277ebc5f5f1c8faba9b810",
"source_review_role": "railiance-platform-owner",
"disposition": "approve-source-correction-only-pending-independent-review",
"native_authority": false,
"frozen_p1_p2_changed": false,
"lineage": "R3 is a new source revision, not retrospective approval or reinterpretation of original reviews",
"artifact_files_sha256": {
"README.md": "sha256:176e0d75de9adf23804b220b21528a077bfdb8e97edc0fe7e39296b7bb1f50a2",
"validation.json": "sha256:7a6cc839b749f48aaee96eb53d83e9c5c2da5dc392b5e1b735c0bbbd55c9c11e",
"implementation.patch": "sha256:266a5ac36b291c40e7c8a6d3da9b03169d454fac6c277ebc5f5f1c8faba9b810"
}
}

View file

@ -0,0 +1,8 @@
{
"command": "python3 -m pytest tests/test_flex_auth_signer_native.py tests/test_flex_auth_signer_native_preparation.py -q",
"result": "45 passed",
"seconds": 33.66,
"git_apply_check": "passed against canonical Platform checkout",
"test_scope": "Disposable native state machine uses synthetic owner approvals/cluster/identity; no native authority inferred",
"new_check": "Correct exact five-role set accepted, conflated/missing/blanket-sixth roles rejected before IO"
}

View file

@ -0,0 +1,11 @@
# Platform owner-boundary source reconciliation R4
R4 preserves the frozen R3 artifact and provides a full six-file patch against a3e8eb64 plus a three-file R3 delta (native plan, proposed CCR0050, existing workplan). Native executor and tests are unchanged from R3; no runtime grant or implementation behavior changed.
The plan now records native adapter implementation/tests at a3e8 rather than claiming implementation remains required. It records published84c78e0/image09a83b successful normal CI402 and source OS-lock/64MiB/startup hardening, while keeping actual mounted-runtime/durability/fence evidence open. It records established canonical local draft package d52/tree d4fb, frozen source2b/current05a; no missing-local-declaration waiver is requested. Remote publication/placement and actual runtime/package join remain open. Proposed CCR risk/activation conditions use the same current facts and retain proposed/unconfirmed state.
Concurrency distinguishes Access workload/Helm release writers, Platform GitOps Applications/sync-freeze integration, cluster runtime/storage/headroom, and Enablement only for separately proposed actual ArgoCD installation/controller changes. No synthetic combined assent. Native corrected role slot remains cluster-runtime-storage-owner, as R3.
An appended bounded RPF35-T13 preparation note records this source correction without changing task status, historical reviews/outcomes or T12 native gates. Independent current CCR/plan/source and native owner/founder admissions remain mandatory. No secrets/login/native fence/custody or remote publication performed.
Validation: exact CCR schema passes, status proposed and bound claims false. Full patch applies cleanly to current canonical Platform. R3's45tests remain the execution-source evidence; unchanged source/test hashes are pinned, no unnecessary full retest for metadata-only delta. Root may independently test the full corrected tree.

View file

@ -0,0 +1,34 @@
{
"proposal_id": "RPF35-PDP-SIGNER-OWNER-BOUNDARY-20261016-R4",
"base_commit": "a3e8eb64bae110393ba0aeab043de03a92b8421a",
"predecessor_r3_manifest_sha256": "sha256:6d1f5d81921cc872073b20554e07a94fdaa3b5b5b9f1509c260c3c58cb92cf93",
"before_sha256": {
"scripts/flex_auth_signer_native.py": "sha256:5a3b3b33bcad45ef2939413bfd3db76e5ee70e3b815d0d64af53147851aa3a0d",
"credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml": "sha256:72fd6e84e866a72f4768dabd66326aebe1fdd00013b2314de961ca72a632679f",
"docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json": "sha256:5f8c37a7d9781884bf06050be80ca192a3b92eab2d20e28f9b304e57e7456844",
"docs/credential-lane-designs/flex-auth-envelope-signing-native-adapter.md": "sha256:0e185a651fbbef96996c38ff18ce338f4d6c0117c3fb069a43e766816e56d5db",
"tests/test_flex_auth_signer_native.py": "sha256:8a99d1819c54fc20660cd6d16e8b429e2fc9f30cf4cefc90704201a588c825a6",
"workplans/RPF-WP-0035-credential-lane-implementation.md": "sha256:180731a9ac1a2b4ccb4458c6cee54151f43e63008e2c35a33db7f01ea9e9d768"
},
"files_sha256": {
"scripts/flex_auth_signer_native.py": "sha256:4fb9ce6608ad04c4f5dc98a95796dc88a08a2453dc15c0d40c5c688fc02f573f",
"credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml": "sha256:1dfcdf206d2bdfcb9b7a21c132e7e3902ad98659dd023bb3993b0825d903c1c7",
"docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json": "sha256:d7b3322d105cdde10ab3003b81b34b8356fdfa1ac08858f6464984ce1c253402",
"docs/credential-lane-designs/flex-auth-envelope-signing-native-adapter.md": "sha256:0e2e431dcd2df66e65434ce4060a4d5b52941f50928be80a075e4f972229d3a9",
"tests/test_flex_auth_signer_native.py": "sha256:f74a2316506737966e2ea539799cb477a9cec2718a6679f417478f9972731b3e",
"workplans/RPF-WP-0035-credential-lane-implementation.md": "sha256:20451474d84be986f7be1e2ff8c48e3a754e981718949f0e9d771081ae15d25b"
},
"full_patch_sha256": "sha256:25bbff23421121836625e0081213fa2d1535e69a6123fbac21b62163b94293db",
"delta_patch_sha256": "sha256:022281a1db4d1cbcad0a1485b57f4ddc1fbca3b9503efe6ef266912f592d2569",
"disposition": "Platform source/metadata preparation only; no native grant",
"frozen_prior_artifacts_changed": false,
"tests_source_unchanged_from_r3": true,
"artifact_files_sha256": {
"README.md": "sha256:72af2f502890d9f23d82cd3135aaa21346e6ab7d961d5e669bf0183e4ec55c41",
"implementation-r4-full.patch": "sha256:25bbff23421121836625e0081213fa2d1535e69a6123fbac21b62163b94293db",
"r3-ccr.yaml": "sha256:23e4e008144350547c4726ca01e579b583b179b7a767d9406c4c176c1a645343",
"r3-plan.json": "sha256:1865083d8382da4d987ef33586919a262970776c71b523c8cb027c027b550ece",
"r3-to-r4.patch": "sha256:022281a1db4d1cbcad0a1485b57f4ddc1fbca3b9503efe6ef266912f592d2569",
"validation.json": "sha256:9e42fb14c30f959e817ec5b185cfdc79b16dd712639c52dc0899874e9570a08a"
}
}

View file

@ -0,0 +1,128 @@
id: CCR-2026-0050
kind: credential-change-request
schema_version: 1
request_type: workload-kv-read
title: Retained flex-auth lifecycle PDP Ed25519 seed ESO read lane
status: proposed
created: '2026-10-16'
updated: '2026-10-16'
requester:
agent: railiance-platform
reason: CUST-WP-0080 / RPF-WP-0035-T12 / FLEX-WP-0024-T06 source proposal. Exact
new custody/read scope requires founder review; no approval or native binding
is inferred.
review:
required: true
required_approvers:
- founder
- railiance-platform-owner
- access-engine-owner
- secrets-engine-owner
- cluster-runtime-storage-owner
comments: []
target:
domain: infotech
tenant: platform
workload: flex-auth-secrets-engine
environment: production
purpose: Deliver the Ed25519 seed only to the retained lifecycle PDP startup file.
No taskpilot enablement, caller policy changes or private consumer trust discovery.
openbao:
mount: platform
kv_path: platform/workloads/flex-auth/envelope-signing
fields:
- ED25519_PRIVATE_KEY
metadata_read: false
token_self_lifecycle: true
policy_name: workload-kv-read-flex-auth-envelope-signing
policy_file: openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl
auth:
method: kubernetes
mount: kubernetes
role: external-secrets-flex-auth-envelope-signing
bound_claims:
service_account_names:
- flex-auth-envelope-signing-eso
service_account_namespaces:
- flex-auth
bound_claims_confirmed: false
policies:
- workload-kv-read-flex-auth-envelope-signing
ttl: 15m
audience: openbao
token_max_ttl: 15m
token_explicit_max_ttl: 15m
token_no_default_policy: true
access_frontdoor:
type: external-secrets
catalog_id: flex-auth-decision-envelope-signing-key
selector: Retained flex-auth/flex-auth-secrets-engine signer only
readiness: pending-review
resolvable: false
delivery:
surface: external-secrets
target: Namespace flex-auth; dedicated SA flex-auth-envelope-signing-eso; SecretStore
openbao-flex-auth-envelope-signing; ExternalSecret/Secret flex-auth-secrets-engine-envelope-signing;
seed property only -> startup file /var/run/secrets/decision-envelope-signing/seed0440,
readOnly uid/gid/fsGroup65532; no envFrom
bootstrap_command: Attended writer is a distinct exact-source action proposal in
docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json; this
read CCR authorizes neither seed writes nor any execution by itself.
risk:
classification: high
notes:
- Possession of seed can forge lifecycle PDP envelopes; independent consumer public
trust admission and fixed policy pins remain required.
- 'KV read is document-granular: dedicated ESO identity can read all three fields;
only private field is projected. Signer and Secrets consumer have no KV read role.'
- No parent/list/metadata/write/delete capability on ESO role. No token/Secret read
or ExternalSecret mutation granted to signer/consumer.
- Dedicated role is new scope; founder approval required. No workload rapp tuple
invented; absent managed package declaration remains explicit migration debt.
- Controlled outage fence stops every signer pod and disables autoscaling before
creation/rotation. Recreate single writer and retained PVC remain Access-owned
review gates.
- fields declares only projected ED25519_PRIVATE_KEY. The same KV document also
has public key/kid, and OpenBao data-read is not field-filtered; do not claim
field-level authorization.
verification:
positive:
- Exact SA/namespace/audience login; native exact data read; ESO Ready/SecretSynced;
safe metadata resourceVersion and admitted KV version correspondence.
- All retained signer replicas restart with admitted kid/source/image and seed/public
correspondence; signed non-effect lifecycle allow/deny with durable originals
and independent consumer signature verification.
negative:
- Wrong SA/namespace/audience login refused; sibling/list/metadata/write/delete
denied; signer/consumer TokenRequest and Secret/ExternalSecret mutation denied.
- Unknown/old kid, wrong policy, tamper, expired/unsigned envelope, wrong caller
and missing seed refuse; no taskpilot activation.
- Receipt write failure/lost backend response retained unknown; no blind retry/remint;
stale CAS cannot overwrite version.
activation_conditions:
- All five required approvers independently dispose the exact CCR and native writer/fence
proposal; founder exact new custody scope approved.
- Current retained identity919ce25, immutable published producer image and explicit
new flex-auth-secrets-engine-outbox PVC1Gi/local-path, retention/reclaim and bounded-runtime
plan reviewed by Access; missing managed package migration debt explicitly accepted
for this retained workload only.
- Exact native bound claims verified without printing JWT/Secret; read grant policy
and role native readback match source.
- Protected child source pinned, silent attended OIDC context verified, target cluster/backend
pinned, fence verified and durable receipt custody installed.
- Consumer independently admits owner-authenticated public receipt and exact policy/image/trust
pins before protected acceptance.
evidence: []
lifecycle:
deactivate: Fence all retained signer processes, stop consumers, disable delivery
role/ESO and online trust; retain private KV and audit receipts under explicit
owner retention until reviewed destruction.
rotate: Pre-admit successor public receipt offline; fence consumers/all signer replicas/no
HPA; CAS exact version with new in-memory seed; wait exact ESO version, restart
single writer, verify native allow/deny and old-key refusal before reopening.
compromised: Immediately remove old online trust and stop all signer processes;
forward-only CAS recovery with independently approved successor. Never restore
compromised seed; archive historical public keys for offline audit only.
state_hub:
workplan_id: RPF-WP-0035
task_id: RPF-WP-0035-T12

View file

@ -0,0 +1,158 @@
{
"schema": "railiance-platform.signer-native-action-proposal.v1",
"proposal_id": "RPF35-PDP-SIGNER-NATIVE-20261016-R1",
"status": "prepared-unapproved",
"read_ccr": "CCR-2026-0050",
"proposal_scope": "attended bootstrap of retained lifecycle PDP seed; no taskpilot enablement, rotation requires a new exact-version action revision",
"actor": "founder attended OIDC via warden openbao-platform-admin-login; reviewed silent Platform custody child",
"executor_ownership": "railiance-platform; founder executes exact child; agent receives closed metadata only",
"abort_owner": "founder; Platform preserves unknown custody/evidence, Access keeps signer/consumers fenced",
"target": {
"cluster": "primary/railiance01",
"kube_system_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
"openbao_cluster_id": "fd28df5d-98ec-57dd-42ec-9b3e4f4e53bf",
"openbao_origin": "http://127.0.0.1:18200",
"namespace": "flex-auth",
"deployment": "flex-auth-secrets-engine",
"signer_sa": "flex-auth-secrets-engine"
},
"credential_route": "openbao-platform-admin-login; flex-auth-decision-envelope-signing-key remains draft/nonresolvable; route is not a seed vending service",
"bootstrap": {
"action": "CAS0 fresh in-memory Ed25519 seed",
"kid": "flex-auth-lifecycle-20261016-r1",
"expected_version": 0,
"data_path": "platform/data/workloads/flex-auth/envelope-signing",
"document_fields": [
"ED25519_PRIVATE_KEY",
"ED25519_PUBLIC_KEY",
"kid"
],
"metadata_path": "platform/metadata/workloads/flex-auth/envelope-signing",
"metadata_contract": {
"cas_required": true,
"max_versions": 2,
"delete_version_after": "0s"
},
"allowed_native_methods": [
{
"method": "POST",
"path": "platform/metadata/workloads/flex-auth/envelope-signing",
"purpose": "CAS-required/version-retention configuration after absence/no-drift validation"
},
{
"method": "GET",
"path": "platform/metadata/workloads/flex-auth/envelope-signing",
"purpose": "exact metadata readback, no private value output"
},
{
"method": "POST",
"path": "platform/data/workloads/flex-auth/envelope-signing",
"purpose": "single CAS0 seed document write"
},
{
"method": "GET",
"path": "platform/data/workloads/flex-auth/envelope-signing?version=1",
"purpose": "exact private readback in custody, closed public receipt only"
}
],
"source_primitive_sha256": "sha256:f169ad12f90d729b647085c3bed2c0d8d005db23a2341b6d3d5f1793bcfd110c",
"historical_source_primitive_sha256": "sha256:8ed281a49c1f1cdb23772efe3593e75c62572a12b8d7a6a4c574f27b38722e75",
"source_lineage_rule": "source_primitive_sha256 must equal current admitted source_bundle[scripts/flex_auth_signer_custody.py] and exact writer receipt; historical_source_primitive_sha256 is non-authoritative R1 evidence only"
},
"fencing": {
"before_key_generation": [
"Verify cluster/backend IDs, exact current source/CCR and founder disposition hashes.",
"Read exact deployment/ReplicaSets/pods by ownerReference and SA; refuse a second workload/pod using signer SA or selecting signer Secret.",
"Access stops protected consumers and scales retained deployment0 through its reviewed release path; this child does not mutate replicas.",
"Verify deployment spec replicas0, observed generation caught up, no live or terminating owned ReplicaSet/pods, no matching signer-SA pods and no HPA targeting deployment.",
"Reject a differing/previously existing KV entry at bootstrap. Hold exclusive owner action receipt lock and recheck fence immediately before CAS."
],
"after_cas": "Keep consumers/signer stopped until independent public admission and exact ESO/readback/source/image verification. Recreate1 is Access-owned; any drift or uncertain outcome keeps fence closed. Access reports current outbox lacks OS process lock, byte ceiling and bounded startup/compaction; Recreate1 alone is not bounded durable acceptance. Keep protected acceptance closed until reviewed source hardening or explicit bounded attended fixture disposition.",
"concurrency": "Single attended owner window/exclusive receipt; GitOps/Helm autosync/other writers must be paused by Access/cluster owner under distinct reviewed action. Plan is not a Kubernetes atomic fence or same-UID isolation."
},
"protected_adapter": {
"state": "implementation/independent tests still required before executable approval; fixture code must never be promoted by removing its dev-process check",
"reuse": [
"scripts/state_hub_preflight_lane.py attended silent child/cluster/platform-admin/fencing patterns",
"scripts/flex_auth_signer_custody.py protected Ed25519 CAS/readback and receipt state machine",
"scripts/task_execution_native_fixture.py absolute main-thread2s I/O budget",
"scripts/task_execution_recovery.py strict bounded JSON/no-follow/private custody patterns"
],
"requirements": [
"New fixed native adapter consumes exact approved CCR/source/native-action digest; cannot accept arbitrary URL/path/command/payload/seed.",
"Read OIDC token only within warden child from attended context; require platform-admin and reject root; never print/store bearer. No credential comes back to agent.",
"Pin exact kube-system/OpenBao cluster IDs before private calls, refuse ambient address override; enforce2s absolute reads and1MiB cap; no redirect/shell/tracing.",
"Verify metadata absence/exact admitted values before CAS-required change; any differing existing object refuses. Exact response contracts and duplicate/nonfinite JSON rejection.",
"Private seed is generated once, never imported or persisted outside OpenBao; no subprocess argv/env seed; readback compares exact version/fields and derived public correspondence.",
"Receipt intent is durable before native effect; final success requires exact KV/metadata correspondence and receipt fsync. Process loss/error remains unknown without remint/retry."
]
},
"receipts": {
"directory": "/home/worsch/.local/state/railiance-platform/flex-auth-envelope-signing",
"mode_directory": "0700",
"mode_file": "0600",
"native_execution_location": "founder workstation inside reviewed warden child; no agent-owned shared /tmp custody",
"basename": "RPF35-PDP-SIGNER-NATIVE-20261016-R1.json",
"maximum_bytes": 65536,
"atomic": "exclusive initial receipt, fsync-before-effect, atomic replacement and directory fsync, no-follow/owner/mode/nlink validation",
"public_fields": [
"proposal/executed source/CCR/identity/cluster/image/policy/trust digests",
"actor/time",
"closed phase/status",
"exact KV path/version/kid",
"public key and explicitly encoded fingerprint",
"fence/metadata/readback checks",
"rollback or unknown recovery disposition"
],
"fingerprint": "sha256: lowercase SHA256 of canonical unpadded base64url public-key ASCII, exactly43bytes; independent Secrets owner agreement before admission",
"delivery": "Founder/Platform separately authenticate metadata receipt to Secrets owner; public receipt shape/file does not create consumer trust; raw private receipt directory is not a shared agent intake"
},
"rollback": {
"before_cas": "Abort without private write; retain receipt/fence; restore prior Access image/values only via reviewed Access action.",
"after_cas_unknown": "Retain unknown and fence; independently attended owner reconciles exact metadata/version/public correspondence. Never rerun CAS0 or restore prior seed automatically.",
"compromise": "Remove online old public key immediately; stop signer, independent new reviewed forward-only action; old private seed never restored."
},
"requested_scope": {
"read_lane": "Exact CCR0050 policy/role/delivery source only after separate founder review. No extra ESO RBAC permitted.",
"custody_writer": "One attended exact-target CAS0 plus fixed KV metadata setup/readback; no standing writer grant or broker role. This authority is separate from read CCR.",
"kubernetes": "Metadata-only fence observations by child; Access/cluster separately approve stop/restart/delivery manifests and sync control.",
"new_infrastructure_spend_eur": 0,
"new_resources": "Platform creates no storage resources. Access reports zero flex-auth PVCs; Access proposes separate exact new flex-auth-secrets-engine-outbox1Gi local-path Helmkeep. Provisioning, PV reclaim/Delete/WaitForFirstConsumer retention and runtime log bounds require separate founder/cluster/Access review; no durable outbox acceptance yet.",
"risk_bound": "Controlled loss of retained PDP availability during explicit attended fence; seed compromise can forge lifecycle decisions until independent public trust withdrawal; founder must approve this concrete high-risk scope."
},
"reviews_required": [
{
"role": "railiance-platform-owner",
"disposition": "pending exact source/artifact review; no approval recorded"
},
{
"role": "access-engine-owner",
"disposition": "pending exact source/artifact review; no approval recorded"
},
{
"role": "secrets-engine-owner",
"disposition": "pending exact source/artifact review; no approval recorded"
},
{
"role": "cluster-runtime-storage-owner",
"disposition": "pending exact source/artifact review; no approval recorded"
},
{
"role": "founder",
"disposition": "pending exact source/artifact review; no approval recorded"
}
],
"authority_granted": false,
"candidate_delivery_amendments": {
"supersedes_unadmitted_R3_role": "external-secrets-flex-auth-envelope-signing replaces flex-auth-envelope-signing-eso to fit existing delegated-applier role convention; dedicated SA unchanged",
"read_ccr_field_projection": "Only ED25519_PRIVATE_KEY is delivered; public sibling fields remain visible to dedicated document-reader identity but never become consumer trust discovery",
"projection_source": "docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed",
"managed_declaration": "Missing rapp declaration remains migration debt; retain actual Access919ce25 workload identity, do not create package tuple"
},
"owner_review_scope": {
"railiance-platform-owner": "OpenBao custody/read lane; existing GitOps Applications and their sync/freeze/controller ownership; covers explicitly identified workload/Helm competing-writer integration with Access, not Access workload semantic ownership.",
"access-engine-owner": "Retained workload/release source, Helm values, candidate image, signer delivery/mount and process/outbox fencing/rollback.",
"cluster-runtime-storage-owner": "railiance-cluster: cluster identity/runtime/storage/headroom, local-path provisioning and actual PV retention; excludes workload and GitOps Application ownership.",
"enablement_condition": "railiance-enablement additional exact review required only if proposed action changes ArgoCD installation/controller configuration; this fixed custody action authorizes no such change. Changed scope needs a separately reviewed exact revision rather than blanket extra reviewer."
}
}

View file

@ -0,0 +1,11 @@
{
"scope": "R3-to-R4 metadata/work-record only; execution source/tests unchanged from R3",
"ccr_schema_errors": [],
"ccr_schema_warnings": [
"OIDC/Kubernetes bound claim is not confirmed; apply is blocked"
],
"ccr_status": "proposed",
"bound_claims_confirmed": false,
"native_grant": false,
"r3_tests": "45passed; unchanged executor and tests; not rerun for metadata-only delta"
}

View file

@ -0,0 +1,7 @@
# Platform typed metadata repair R5
R5 preserves R4 and changes only two metadata postimages. It labels the public source hash as `source Git revision 84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0` and the public image digest as the full typed immutable OCI reference `forgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f`.
Normal pinned Gitleaks8.30.1 scanned all six staged postimage blobs clean using unchanged default rules, empty ignore file and ignore-gitleaks-allow. No suppression/config exception/bypass. Diagnosis retained: Git revision spacing alone still blocked; CCR line108's bare `flex-auth image sha256:` matched generic-api-key because auth and colon preceded the public digest. Full typed OCI reference removes the ambiguous prose and preserves the exact image identity.
Full six-file patch applies against original a3e8; R4 delta changes only proposed CCR0050 and native plan. Delta git apply --check passes against current canonical R4 staged worktree, which was not mutated. All R4 six-postimage hashes verified before deriving successor. Proposed CCR0050 schema passes and remains proposed/unconfirmed; unrelated baseline CCR0035 validation errors are not changed. Executor/tests/workplan unchanged; R3 45 tests and coordinator independent54 remain applicable source evidence. No native action, custody, deployment, approval or authority upgrade.

View file

@ -0,0 +1,176 @@
--- a/scripts/flex_auth_signer_native.py
+++ b/scripts/flex_auth_signer_native.py
@@ -41,7 +41,7 @@
SECRET='flex-auth-secrets-engine-envelope-signing'
ROLE='external-secrets-flex-auth-envelope-signing'
LIFECYCLE='sha256:bd11c5fe77ce6439c65fea225ad6b71d2110efc5e7b5bc9b499c59cd0a53b8b4'
-REVIEWS={'founder','railiance-platform-owner','access-engine-owner','secrets-engine-owner','cluster-gitops-owner'}
+REVIEWS={'founder','railiance-platform-owner','access-engine-owner','secrets-engine-owner','cluster-runtime-storage-owner'}
SOURCE_FILES={'scripts/flex_auth_signer_native.py','scripts/flex_auth_signer_custody.py','scripts/task_execution_native_fixture.py','scripts/task_execution_journal.py','scripts/task_execution_recovery.py','scripts/credential-change.py'}
--- a/credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml
+++ b/credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml
@@ -18,7 +18,7 @@
- railiance-platform-owner
- access-engine-owner
- secrets-engine-owner
- - cluster-gitops-owner
+ - cluster-runtime-storage-owner
comments: []
target:
domain: infotech
@@ -77,8 +77,10 @@
only private field is projected. Signer and Secrets consumer have no KV read role.'
- No parent/list/metadata/write/delete capability on ESO role. No token/Secret read
or ExternalSecret mutation granted to signer/consumer.
- - Dedicated role is new scope; founder approval required. No workload rapp tuple
- invented; absent managed package declaration remains explicit migration debt.
+ - Dedicated role is new scope; founder approval required. Canonical local draft
+ rapp-flex-auth-secrets-engine declaration committed d52f811ed5ec0be27c9c367e71d476125b3228be
+ (tree d4fbad71bdc8ef870603526ccd1f53195d0322af) resolves locally. Remote publication,
+ placement and actual runtime/package join remain open; no missing-declaration waiver.
- Controlled outage fence stops every signer pod and disables autoscaling before
creation/rotation. Recreate single writer and retained PVC remain Access-owned
review gates.
@@ -102,10 +104,15 @@
activation_conditions:
- All five required approvers independently dispose the exact CCR and native writer/fence
proposal; founder exact new custody scope approved.
- - Current retained identity919ce25, immutable published producer image and explicit
- new flex-auth-secrets-engine-outbox PVC1Gi/local-path, retention/reclaim and bounded-runtime
- plan reviewed by Access; missing managed package migration debt explicitly accepted
- for this retained workload only.
+ - Exact proposed candidate source Git revision 84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0 and immutable
+ OCI reference forgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f
+ published by successful ordinary CI402; source OS-lock/64MiB/startup hardening present.
+ Access reviews exact workload/Helm image/mount/outbox source and actual stopped fence;
+ Platform reviews existing GitOps Application sync/freeze; cluster reviews runtime,
+ headroom and new PVC1Gi/local-path actual PV retention. Local draft package d52
+ remains source2b/current05a, not candidate84 adoption; remote placement/runtime join
+ and native mounted-runtime acceptance remain open. Enablement only if separately
+ proposed ArgoCD installation/controller configuration changes; none granted here.
- Exact native bound claims verified without printing JWT/Secret; read grant policy
and role native readback match source.
- Protected child source pinned, silent attended OIDC context verified, target cluster/backend
--- a/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json
@@ -67,11 +67,11 @@
"Verify deployment spec replicas0, observed generation caught up, no live or terminating owned ReplicaSet/pods, no matching signer-SA pods and no HPA targeting deployment.",
"Reject a differing/previously existing KV entry at bootstrap. Hold exclusive owner action receipt lock and recheck fence immediately before CAS."
],
- "after_cas": "Keep consumers/signer stopped until independent public admission and exact ESO/readback/source/image verification. Recreate1 is Access-owned; any drift or uncertain outcome keeps fence closed. Access reports current outbox lacks OS process lock, byte ceiling and bounded startup/compaction; Recreate1 alone is not bounded durable acceptance. Keep protected acceptance closed until reviewed source hardening or explicit bounded attended fixture disposition.",
- "concurrency": "Single attended owner window/exclusive receipt; GitOps/Helm autosync/other writers must be paused by Access/cluster owner under distinct reviewed action. Plan is not a Kubernetes atomic fence or same-UID isolation."
+ "after_cas": "Keep consumers/signer stopped until independent public admission and exact ESO/readback/source/image verification. Access published source Git revision 84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0 with OS process lock,64MiB capacity/startup bounds and ordinary CI402 success, immutable OCI reference forgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f. This supplies reviewed-source/publication evidence, not native mounted-runtime/durable-record acceptance. Access owns Recreate1 activation; actual stopped fence, single writer, PV retention and protected consumer acceptance remain open.",
+ "concurrency": "Single attended owner window/exclusive receipt; Access owns workload/Helm release and freeze of competing workload writers; Platform owns existing GitOps Applications and sync/freeze integration; cluster owner reviews runtime/storage/headroom only. Enablement reviews actual ArgoCD installation/controller configuration changes only, which this action excludes. Actual competing creator IDs/revisions and stopped signer fence need separately reviewed authenticated observations. Plan is not a Kubernetes atomic fence or same-UID isolation."
},
"protected_adapter": {
- "state": "implementation/independent tests still required before executable approval; fixture code must never be promoted by removing its dev-process check",
+ "state": "Native adapter bootstrap/phase-aware read-only recovery source implemented and tested at published Platform a3e8eb64bae110393ba0aeab043de03a92b8421a; owner-boundary source R3/R4 is a successor exact review revision, not a native execution approval. Exact source/CCR, distinct owner/founder dispositions, entity/window/expiry and actual backend/fence remain mandatory. Fixture dev-process gate remains preserved.",
"reuse": [
"scripts/state_hub_preflight_lane.py attended silent child/cluster/platform-admin/fencing patterns",
"scripts/flex_auth_signer_custody.py protected Ed25519 CAS/readback and receipt state machine",
@@ -115,9 +115,9 @@
"requested_scope": {
"read_lane": "Exact CCR0050 policy/role/delivery source only after separate founder review. No extra ESO RBAC permitted.",
"custody_writer": "One attended exact-target CAS0 plus fixed KV metadata setup/readback; no standing writer grant or broker role. This authority is separate from read CCR.",
- "kubernetes": "Metadata-only fence observations by child; Access/cluster separately approve stop/restart/delivery manifests and sync control.",
+ "kubernetes": "Metadata-only fence observations by child; Access separately reviews workload/Helm stop/restart/delivery; Platform owns GitOps Application sync/freeze integration; cluster owner reviews runtime/storage/headroom/PV retention; no ArgoCD installation/controller configuration mutation included.",
"new_infrastructure_spend_eur": 0,
- "new_resources": "Platform creates no storage resources. Access reports zero flex-auth PVCs; Access proposes separate exact new flex-auth-secrets-engine-outbox1Gi local-path Helmkeep. Provisioning, PV reclaim/Delete/WaitForFirstConsumer retention and runtime log bounds require separate founder/cluster/Access review; no durable outbox acceptance yet.",
+ "new_resources": "Platform creates no storage resources. Access proposes separate exact new flex-auth-secrets-engine-outbox1Gi local-path Helmkeep. Actual provisioning/PV identity, StorageClass Delete/WaitForFirstConsumer to reviewed PV Retain disposition and native bounded-runtime evidence remain open. Source lock/64MiB/startup bounds are published in candidate84, not evidence of native mounted-runtime acceptance.",
"risk_bound": "Controlled loss of retained PDP availability during explicit attended fence; seed compromise can forge lifecycle decisions until independent public trust withdrawal; founder must approve this concrete high-risk scope."
},
"reviews_required": [
@@ -134,7 +134,7 @@
"disposition": "pending exact source/artifact review; no approval recorded"
},
{
- "role": "cluster-gitops-owner",
+ "role": "cluster-runtime-storage-owner",
"disposition": "pending exact source/artifact review; no approval recorded"
},
{
@@ -147,6 +147,12 @@
"supersedes_unadmitted_R3_role": "external-secrets-flex-auth-envelope-signing replaces flex-auth-envelope-signing-eso to fit existing delegated-applier role convention; dedicated SA unchanged",
"read_ccr_field_projection": "Only ED25519_PRIVATE_KEY is delivered; public sibling fields remain visible to dedicated document-reader identity but never become consumer trust discovery",
"projection_source": "docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed",
- "managed_declaration": "Missing rapp declaration remains migration debt; retain actual Access919ce25 workload identity, do not create package tuple"
+ "managed_declaration": "Canonical local rapp-flex-auth-secrets-engine declaration committed d52f811ed5ec0be27c9c367e71d476125b3228be, exact13file tree d4fbad71bdc8ef870603526ccd1f53195d0322af; tuple rapp-flex-auth-secrets-engine/flex-auth-secrets-engine/flex-auth-secrets-engine resolves locally as draft. Frozen source2b/current05a package baseline does not adopt candidate84/signing. Remote publication/placement and actual runtime/package join remain open. No missing-declaration claim or migration-debt waiver is applicable to the established local declaration."
+ },
+ "owner_review_scope": {
+ "railiance-platform-owner": "OpenBao custody/read lane; existing GitOps Applications and their sync/freeze/controller ownership; covers explicitly identified workload/Helm competing-writer integration with Access, not Access workload semantic ownership.",
+ "access-engine-owner": "Retained workload/release source, Helm values, candidate image, signer delivery/mount and process/outbox fencing/rollback.",
+ "cluster-runtime-storage-owner": "railiance-cluster: cluster identity/runtime/storage/headroom, local-path provisioning and actual PV retention; excludes workload and GitOps Application ownership.",
+ "enablement_condition": "railiance-enablement additional exact review required only if proposed action changes ArgoCD installation/controller configuration; this fixed custody action authorizes no such change. Changed scope needs a separately reviewed exact revision rather than blanket extra reviewer."
}
}
--- a/docs/credential-lane-designs/flex-auth-envelope-signing-native-adapter.md
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing-native-adapter.md
@@ -185,3 +185,21 @@
reviews or start a general window-grant service. An alternative authority/window
contract would itself require separate owner/founder design and is not supplied
by R2. Current native admission remains unapproved.
+
+## R3 precise owner review boundary
+
+The native review slot is now `cluster-runtime-storage-owner`, held by
+railiance-cluster for runtime/storage/headroom only. The previous
+`cluster-gitops-owner` slot conflated separate authorities and is rejected;
+no historic review is reinterpreted as a combined assent. Platform's existing
+review covers its custody/read lane and existing GitOps Applications/freeze
+integration. Access retains workload/release/Helm-values and signer/outbox
+ownership. Enablement review is needed only when the action actually changes
+ArgoCD installation/controller configuration; this action does not authorize
+that. Changed scope requires a separate exact revision and any affected owner.
+
+This is a source-schema correction, not a new approval. Existing R1/R2 source
+reviews and P1/P2 packets remain frozen. A successor packet must pin the new
+executor/source-bundle/plan/CCR hashes and collect five exact fresh dispositions
+under the corrected role set. Admission-file digest approval and CCR
+approved/binding-confirmed status/native checks remain unchanged.
--- a/tests/test_flex_auth_signer_native.py
+++ b/tests/test_flex_auth_signer_native.py
@@ -316,3 +316,20 @@
assert result['public_key'] is None and result['public_fingerprint'] is None
assert old.read_bytes()==raw
assert not any(x[0]=='http' and x[1]=='POST' for x in io.calls)
+
+
+def test_exact_runtime_storage_owner_slot_replaces_conflated_role(native):
+ c,io,directory,call,approve,source=native
+ assert m.REVIEWS=={'founder','railiance-platform-owner','access-engine-owner','secrets-engine-owner','cluster-runtime-storage-owner'}
+ assert m.validate_admission(c)>5
+ old=deepcopy(c)
+ old['owner_reviews']['cluster-gitops-owner']=old['owner_reviews'].pop('cluster-runtime-storage-owner')
+ with pytest.raises(Refused,match='distinct owner reviews'):
+ m.validate_admission(old)
+ missing=deepcopy(c);missing['owner_reviews'].pop('cluster-runtime-storage-owner')
+ with pytest.raises(Refused,match='distinct owner reviews'):
+ m.validate_admission(missing)
+ extra=deepcopy(c);extra['owner_reviews']['railiance-enablement-owner']=next(iter(extra['owner_reviews'].values()))
+ with pytest.raises(Refused,match='distinct owner reviews'):
+ m.validate_admission(extra)
+ assert io.calls==[]
--- a/workplans/RPF-WP-0035-credential-lane-implementation.md
+++ b/workplans/RPF-WP-0035-credential-lane-implementation.md
@@ -1375,3 +1375,19 @@
external reviews and unbounded OIDC require a pre-reviewed exact scheduled
window, otherwise refuse/new explicit revision. No native or owner approval
inferred; no live action.
+
+
+2026-10-16 T13 bounded source-review preparation note (no status/scope closure):
+Native signer owner-boundary R3/R4 preparation replaces conflated
+cluster-gitops-owner with cluster-runtime-storage-owner. Platform retains
+OpenBao and existing GitOps Applications/sync-freeze integration; Access owns
+workload/Helm release semantics; cluster reviews runtime/storage/headroom;
+Enablement participates only for actual ArgoCD installation/controller changes.
+Source facts reconcile implemented/tested native adapter a3e8eb64, canonical
+local draft package d52f811/tree d4fbad7 (frozen source2b/current05a) and
+ordinary CI402 published candidate84c78e0/image09a83b with OS lock/64MiB/startup
+hardening. Prior R1/R2/R3 and packetP1/P2 reviews/failures remain frozen.
+No missing-local-declaration waiver, remote placement, actual stopped fence,
+PV retention, native custody, independent post-key trust or founder/owner
+approval is inferred. Exact successor source/CCR/plan pins and fresh scoped
+reviews remain required; T12 native gates and historical T13 outcome unchanged.

View file

@ -0,0 +1,25 @@
{
"proposal_id": "RPF35-PDP-SIGNER-OWNER-BOUNDARY-20261016-R5",
"base_commit": "a3e8eb64bae110393ba0aeab043de03a92b8421a",
"predecessor_r4_manifest_sha256": "sha256:835b00b582e3cdd56a4a2aa45e803dd8f619e4c53cd2e3b3997053fc1c5926f4",
"files_sha256": {
"scripts/flex_auth_signer_native.py": "sha256:4fb9ce6608ad04c4f5dc98a95796dc88a08a2453dc15c0d40c5c688fc02f573f",
"credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml": "sha256:44bbbf4aff84c3b085afdb14708106fd9a12ac03a0c47774913931a87fb94ada",
"docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json": "sha256:22306d50e082fc35c5ff22c31e739a53c7584176969063f7907bc01d77e506e5",
"docs/credential-lane-designs/flex-auth-envelope-signing-native-adapter.md": "sha256:0e2e431dcd2df66e65434ce4060a4d5b52941f50928be80a075e4f972229d3a9",
"tests/test_flex_auth_signer_native.py": "sha256:f74a2316506737966e2ea539799cb477a9cec2718a6679f417478f9972731b3e",
"workplans/RPF-WP-0035-credential-lane-implementation.md": "sha256:20451474d84be986f7be1e2ff8c48e3a754e981718949f0e9d771081ae15d25b"
},
"full_patch_sha256": "sha256:43264f13da5ff20267dea424a7c7d8d911f7601edca6871f31394e0c292fcd59",
"r4_delta_patch_sha256": "sha256:8115c72cd7d14d870f39acb71ef9e819c40e835f56c30226128ace552cd5d5f9",
"disposition": "Platform typed public metadata correction only; no authority grant",
"r4_preserved": true,
"native_action": false,
"artifact_files_sha256": {
"README.md": "sha256:532d21e88312b0e0ded0325cdab3fa67974c91fdfac0cc0197c45df16837088b",
"implementation-r5-full.patch": "sha256:43264f13da5ff20267dea424a7c7d8d911f7601edca6871f31394e0c292fcd59",
"r4-to-r5.patch": "sha256:8115c72cd7d14d870f39acb71ef9e819c40e835f56c30226128ace552cd5d5f9",
"scanner.json": "sha256:b61a8aec0ad5012993fa615c9222ccdac35aa98bf78d3c2112cf21a5fedbbf1d",
"validation.json": "sha256:82acae7bf5dd4ecb29ee36b51c69e2c8c8af8c02ef14698079c66102141067fe"
}
}

View file

@ -0,0 +1,8 @@
{
"blocked_blob_ids": [],
"scanned_blobs": 6,
"scanner_sha256": "88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509",
"scanner_version": "8.30.1",
"scope": "changed-staged-blobs",
"status": "clean"
}

View file

@ -0,0 +1,13 @@
{
"ccr0050_errors": [],
"ccr0050_warnings": [
"OIDC/Kubernetes bound claim is not confirmed; apply is blocked"
],
"status": "proposed",
"bound_claims_confirmed": false,
"delta_scope": "two metadata files only; executor/tests/workplan unchanged from R4",
"tests": "R3 45+coordinator independent54 retained; no runtime retest for metadata wording",
"canonical_modified": false,
"scanner_config": "normal pinned adapter default useDefault=true; empty ignores; --ignore-gitleaks-allow; no suppression/bypass",
"diagnosis": "Git revision spacing alone still blocked. Actual bare flex-auth image sha256 digest matches auth-sensitive generic-api-key expression. Full typed immutable OCI reference avoids ambiguity and retains exact digest."
}

View file

@ -0,0 +1,19 @@
# Native signer preparation R1
Apply after writer R2 and metadata R3. This adds proposed CCR-2026-0050,
generated exact ESO read policy, native candidate projection, and the concrete
attended writer/fencing/receipt plan. The real validator passes; real apply
refuses proposed status/unconfirmed bindings. Three preparation tests passed.
Exact preimages, hashes, validation and source-only owner review are retained.
This read CCR grants no write custody. The separate native action specifies
target cluster/backend, CAS0/new kid, exact metadata/data methods, protected
child, fence, receipts and forward-only unknown recovery. Its native production
adapter still requires implementation/independent tests before executable approval.
No credential, secret, policy, role, deployment, route or PVC was changed live.
Native candidate amends the unadmitted R3 role to fit existing applier conventions
and declares only the projected private field; KV read remains document-granular.
Access confirmed the encoding/fingerprint interface only, not custody or deploy.
Access observed zero existing PVCs; its new storage/runtime scope requires
separate owner/founder review. Required reviews remain distinct and unfilled.

View file

@ -0,0 +1,16 @@
{
"source": "Access owner agent message during delegated review, not native approval",
"agreement": [
"strict unpadded base64url32byte seed, Secret seed0440 startup-only",
"public fingerprint SHA256 canonical base64url ASCII43bytes, not raw decoded key",
"candidate kid flex-auth-lifecycle-20261016-r1",
"candidate role external-secrets-flex-auth-envelope-signing"
],
"separate_Access_proposal": [
"new PVC flex-auth-secrets-engine-outbox1Gi local-path Helmkeep",
"source flex-auth.secrets-engine, initial auditDelivery disabled",
"explicit PVreclaim Retain review needed because current SC Delete",
"process lock/byte ceiling/bounded startup/compaction runtime work still required"
],
"native_custody_CCR_deploy_assent": false
}

View file

@ -0,0 +1,477 @@
diff --git a/credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml b/credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml
new file mode 100644
index 0000000..ccccdb2
--- /dev/null
+++ b/credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml
@@ -0,0 +1,128 @@
+id: CCR-2026-0050
+kind: credential-change-request
+schema_version: 1
+request_type: workload-kv-read
+title: Retained flex-auth lifecycle PDP Ed25519 seed ESO read lane
+status: proposed
+created: '2026-10-16'
+updated: '2026-10-16'
+requester:
+ agent: railiance-platform
+ reason: CUST-WP-0080 / RPF-WP-0035-T12 / FLEX-WP-0024-T06 source proposal. Exact
+ new custody/read scope requires founder review; no approval or native binding
+ is inferred.
+review:
+ required: true
+ required_approvers:
+ - founder
+ - railiance-platform-owner
+ - access-engine-owner
+ - secrets-engine-owner
+ - cluster-gitops-owner
+ comments: []
+target:
+ domain: infotech
+ tenant: platform
+ workload: flex-auth-secrets-engine
+ environment: production
+ purpose: Deliver the Ed25519 seed only to the retained lifecycle PDP startup file.
+ No taskpilot enablement, caller policy changes or private consumer trust discovery.
+openbao:
+ mount: platform
+ kv_path: platform/workloads/flex-auth/envelope-signing
+ fields:
+ - ED25519_PRIVATE_KEY
+ metadata_read: false
+ token_self_lifecycle: true
+ policy_name: workload-kv-read-flex-auth-envelope-signing
+ policy_file: openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl
+ auth:
+ method: kubernetes
+ mount: kubernetes
+ role: external-secrets-flex-auth-envelope-signing
+ bound_claims:
+ service_account_names:
+ - flex-auth-envelope-signing-eso
+ service_account_namespaces:
+ - flex-auth
+ bound_claims_confirmed: false
+ policies:
+ - workload-kv-read-flex-auth-envelope-signing
+ ttl: 15m
+ audience: openbao
+ token_max_ttl: 15m
+ token_explicit_max_ttl: 15m
+ token_no_default_policy: true
+access_frontdoor:
+ type: external-secrets
+ catalog_id: flex-auth-decision-envelope-signing-key
+ selector: Retained flex-auth/flex-auth-secrets-engine signer only
+ readiness: pending-review
+ resolvable: false
+delivery:
+ surface: external-secrets
+ target: Namespace flex-auth; dedicated SA flex-auth-envelope-signing-eso; SecretStore
+ openbao-flex-auth-envelope-signing; ExternalSecret/Secret flex-auth-secrets-engine-envelope-signing;
+ seed property only -> startup file /var/run/secrets/decision-envelope-signing/seed0440,
+ readOnly uid/gid/fsGroup65532; no envFrom
+ bootstrap_command: Attended writer is a distinct exact-source action proposal in
+ docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json; this
+ read CCR authorizes neither seed writes nor any execution by itself.
+risk:
+ classification: high
+ notes:
+ - Possession of seed can forge lifecycle PDP envelopes; independent consumer public
+ trust admission and fixed policy pins remain required.
+ - 'KV read is document-granular: dedicated ESO identity can read all three fields;
+ only private field is projected. Signer and Secrets consumer have no KV read role.'
+ - No parent/list/metadata/write/delete capability on ESO role. No token/Secret read
+ or ExternalSecret mutation granted to signer/consumer.
+ - Dedicated role is new scope; founder approval required. No workload rapp tuple
+ invented; absent managed package declaration remains explicit migration debt.
+ - Controlled outage fence stops every signer pod and disables autoscaling before
+ creation/rotation. Recreate single writer and retained PVC remain Access-owned
+ review gates.
+ - fields declares only projected ED25519_PRIVATE_KEY. The same KV document also
+ has public key/kid, and OpenBao data-read is not field-filtered; do not claim
+ field-level authorization.
+verification:
+ positive:
+ - Exact SA/namespace/audience login; native exact data read; ESO Ready/SecretSynced;
+ safe metadata resourceVersion and admitted KV version correspondence.
+ - All retained signer replicas restart with admitted kid/source/image and seed/public
+ correspondence; signed non-effect lifecycle allow/deny with durable originals
+ and independent consumer signature verification.
+ negative:
+ - Wrong SA/namespace/audience login refused; sibling/list/metadata/write/delete
+ denied; signer/consumer TokenRequest and Secret/ExternalSecret mutation denied.
+ - Unknown/old kid, wrong policy, tamper, expired/unsigned envelope, wrong caller
+ and missing seed refuse; no taskpilot activation.
+ - Receipt write failure/lost backend response retained unknown; no blind retry/remint;
+ stale CAS cannot overwrite version.
+ activation_conditions:
+ - All five required approvers independently dispose the exact CCR and native writer/fence
+ proposal; founder exact new custody scope approved.
+ - Current retained identity919ce25, immutable published producer image and explicit
+ new flex-auth-secrets-engine-outbox PVC1Gi/local-path, retention/reclaim and bounded-runtime
+ plan reviewed by Access; missing managed package migration debt explicitly accepted
+ for this retained workload only.
+ - Exact native bound claims verified without printing JWT/Secret; read grant policy
+ and role native readback match source.
+ - Protected child source pinned, silent attended OIDC context verified, target cluster/backend
+ pinned, fence verified and durable receipt custody installed.
+ - Consumer independently admits owner-authenticated public receipt and exact policy/image/trust
+ pins before protected acceptance.
+ evidence: []
+lifecycle:
+ deactivate: Fence all retained signer processes, stop consumers, disable delivery
+ role/ESO and online trust; retain private KV and audit receipts under explicit
+ owner retention until reviewed destruction.
+ rotate: Pre-admit successor public receipt offline; fence consumers/all signer replicas/no
+ HPA; CAS exact version with new in-memory seed; wait exact ESO version, restart
+ single writer, verify native allow/deny and old-key refusal before reopening.
+ compromised: Immediately remove old online trust and stop all signer processes;
+ forward-only CAS recovery with independently approved successor. Never restore
+ compromised seed; archive historical public keys for offline audit only.
+state_hub:
+ workplan_id: RPF-WP-0035
+ task_id: RPF-WP-0035-T12
diff --git a/openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl b/openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl
new file mode 100644
index 0000000..57425e7
--- /dev/null
+++ b/openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl
@@ -0,0 +1,11 @@
+path "platform/data/workloads/flex-auth/envelope-signing" {
+ capabilities = ["read"]
+}
+
+path "auth/token/lookup-self" {
+ capabilities = ["read"]
+}
+
+path "auth/token/revoke-self" {
+ capabilities = ["update"]
+}
diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json b/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json
new file mode 100644
index 0000000..ed8c74f
--- /dev/null
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json
@@ -0,0 +1,150 @@
+{
+ "schema": "railiance-platform.signer-native-action-proposal.v1",
+ "proposal_id": "RPF35-PDP-SIGNER-NATIVE-20261016-R1",
+ "status": "prepared-unapproved",
+ "read_ccr": "CCR-2026-0050",
+ "proposal_scope": "attended bootstrap of retained lifecycle PDP seed; no taskpilot enablement, rotation requires a new exact-version action revision",
+ "actor": "founder attended OIDC via warden openbao-platform-admin-login; reviewed silent Platform custody child",
+ "executor_ownership": "railiance-platform; founder executes exact child; agent receives closed metadata only",
+ "abort_owner": "founder; Platform preserves unknown custody/evidence, Access keeps signer/consumers fenced",
+ "target": {
+ "cluster": "primary/railiance01",
+ "kube_system_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
+ "openbao_cluster_id": "fd28df5d-98ec-57dd-42ec-9b3e4f4e53bf",
+ "openbao_origin": "http://127.0.0.1:18200",
+ "namespace": "flex-auth",
+ "deployment": "flex-auth-secrets-engine",
+ "signer_sa": "flex-auth-secrets-engine"
+ },
+ "credential_route": "openbao-platform-admin-login; flex-auth-decision-envelope-signing-key remains draft/nonresolvable; route is not a seed vending service",
+ "bootstrap": {
+ "action": "CAS0 fresh in-memory Ed25519 seed",
+ "kid": "flex-auth-lifecycle-20261016-r1",
+ "expected_version": 0,
+ "data_path": "platform/data/workloads/flex-auth/envelope-signing",
+ "document_fields": [
+ "ED25519_PRIVATE_KEY",
+ "ED25519_PUBLIC_KEY",
+ "kid"
+ ],
+ "metadata_path": "platform/metadata/workloads/flex-auth/envelope-signing",
+ "metadata_contract": {
+ "cas_required": true,
+ "max_versions": 2,
+ "delete_version_after": "0s"
+ },
+ "allowed_native_methods": [
+ {
+ "method": "POST",
+ "path": "platform/metadata/workloads/flex-auth/envelope-signing",
+ "purpose": "CAS-required/version-retention configuration after absence/no-drift validation"
+ },
+ {
+ "method": "GET",
+ "path": "platform/metadata/workloads/flex-auth/envelope-signing",
+ "purpose": "exact metadata readback, no private value output"
+ },
+ {
+ "method": "POST",
+ "path": "platform/data/workloads/flex-auth/envelope-signing",
+ "purpose": "single CAS0 seed document write"
+ },
+ {
+ "method": "GET",
+ "path": "platform/data/workloads/flex-auth/envelope-signing?version=1",
+ "purpose": "exact private readback in custody, closed public receipt only"
+ }
+ ],
+ "source_primitive_sha256": "sha256:8ed281a49c1f1cdb23772efe3593e75c62572a12b8d7a6a4c574f27b38722e75"
+ },
+ "fencing": {
+ "before_key_generation": [
+ "Verify cluster/backend IDs, exact current source/CCR and founder disposition hashes.",
+ "Read exact deployment/ReplicaSets/pods by ownerReference and SA; refuse a second workload/pod using signer SA or selecting signer Secret.",
+ "Access stops protected consumers and scales retained deployment0 through its reviewed release path; this child does not mutate replicas.",
+ "Verify deployment spec replicas0, observed generation caught up, no live or terminating owned ReplicaSet/pods, no matching signer-SA pods and no HPA targeting deployment.",
+ "Reject a differing/previously existing KV entry at bootstrap. Hold exclusive owner action receipt lock and recheck fence immediately before CAS."
+ ],
+ "after_cas": "Keep consumers/signer stopped until independent public admission and exact ESO/readback/source/image verification. Recreate1 is Access-owned; any drift or uncertain outcome keeps fence closed. Access reports current outbox lacks OS process lock, byte ceiling and bounded startup/compaction; Recreate1 alone is not bounded durable acceptance. Keep protected acceptance closed until reviewed source hardening or explicit bounded attended fixture disposition.",
+ "concurrency": "Single attended owner window/exclusive receipt; GitOps/Helm autosync/other writers must be paused by Access/cluster owner under distinct reviewed action. Plan is not a Kubernetes atomic fence or same-UID isolation."
+ },
+ "protected_adapter": {
+ "state": "implementation/independent tests still required before executable approval; fixture code must never be promoted by removing its dev-process check",
+ "reuse": [
+ "scripts/state_hub_preflight_lane.py attended silent child/cluster/platform-admin/fencing patterns",
+ "scripts/flex_auth_signer_custody.py protected Ed25519 CAS/readback and receipt state machine",
+ "scripts/task_execution_native_fixture.py absolute main-thread2s I/O budget",
+ "scripts/task_execution_recovery.py strict bounded JSON/no-follow/private custody patterns"
+ ],
+ "requirements": [
+ "New fixed native adapter consumes exact approved CCR/source/native-action digest; cannot accept arbitrary URL/path/command/payload/seed.",
+ "Read OIDC token only within warden child from attended context; require platform-admin and reject root; never print/store bearer. No credential comes back to agent.",
+ "Pin exact kube-system/OpenBao cluster IDs before private calls, refuse ambient address override; enforce2s absolute reads and1MiB cap; no redirect/shell/tracing.",
+ "Verify metadata absence/exact admitted values before CAS-required change; any differing existing object refuses. Exact response contracts and duplicate/nonfinite JSON rejection.",
+ "Private seed is generated once, never imported or persisted outside OpenBao; no subprocess argv/env seed; readback compares exact version/fields and derived public correspondence.",
+ "Receipt intent is durable before native effect; final success requires exact KV/metadata correspondence and receipt fsync. Process loss/error remains unknown without remint/retry."
+ ]
+ },
+ "receipts": {
+ "directory": "/home/worsch/.local/state/railiance-platform/flex-auth-envelope-signing",
+ "mode_directory": "0700",
+ "mode_file": "0600",
+ "native_execution_location": "founder workstation inside reviewed warden child; no agent-owned shared /tmp custody",
+ "basename": "RPF35-PDP-SIGNER-NATIVE-20261016-R1.json",
+ "maximum_bytes": 65536,
+ "atomic": "exclusive initial receipt, fsync-before-effect, atomic replacement and directory fsync, no-follow/owner/mode/nlink validation",
+ "public_fields": [
+ "proposal/executed source/CCR/identity/cluster/image/policy/trust digests",
+ "actor/time",
+ "closed phase/status",
+ "exact KV path/version/kid",
+ "public key and explicitly encoded fingerprint",
+ "fence/metadata/readback checks",
+ "rollback or unknown recovery disposition"
+ ],
+ "fingerprint": "sha256: lowercase SHA256 of canonical unpadded base64url public-key ASCII, exactly43bytes; independent Secrets owner agreement before admission",
+ "delivery": "Founder/Platform separately authenticate metadata receipt to Secrets owner; public receipt shape/file does not create consumer trust; raw private receipt directory is not a shared agent intake"
+ },
+ "rollback": {
+ "before_cas": "Abort without private write; retain receipt/fence; restore prior Access image/values only via reviewed Access action.",
+ "after_cas_unknown": "Retain unknown and fence; independently attended owner reconciles exact metadata/version/public correspondence. Never rerun CAS0 or restore prior seed automatically.",
+ "compromise": "Remove online old public key immediately; stop signer, independent new reviewed forward-only action; old private seed never restored."
+ },
+ "requested_scope": {
+ "read_lane": "Exact CCR0050 policy/role/delivery source only after separate founder review. No extra ESO RBAC permitted.",
+ "custody_writer": "One attended exact-target CAS0 plus fixed KV metadata setup/readback; no standing writer grant or broker role. This authority is separate from read CCR.",
+ "kubernetes": "Metadata-only fence observations by child; Access/cluster separately approve stop/restart/delivery manifests and sync control.",
+ "new_infrastructure_spend_eur": 0,
+ "new_resources": "Platform creates no storage resources. Access reports zero flex-auth PVCs; Access proposes separate exact new flex-auth-secrets-engine-outbox1Gi local-path Helmkeep. Provisioning, PV reclaim/Delete/WaitForFirstConsumer retention and runtime log bounds require separate founder/cluster/Access review; no durable outbox acceptance yet.",
+ "risk_bound": "Controlled loss of retained PDP availability during explicit attended fence; seed compromise can forge lifecycle decisions until independent public trust withdrawal; founder must approve this concrete high-risk scope."
+ },
+ "reviews_required": [
+ {
+ "role": "railiance-platform-owner",
+ "disposition": "pending exact source/artifact review; no approval recorded"
+ },
+ {
+ "role": "access-engine-owner",
+ "disposition": "pending exact source/artifact review; no approval recorded"
+ },
+ {
+ "role": "secrets-engine-owner",
+ "disposition": "pending exact source/artifact review; no approval recorded"
+ },
+ {
+ "role": "cluster-gitops-owner",
+ "disposition": "pending exact source/artifact review; no approval recorded"
+ },
+ {
+ "role": "founder",
+ "disposition": "pending exact source/artifact review; no approval recorded"
+ }
+ ],
+ "authority_granted": false,
+ "candidate_delivery_amendments": {
+ "supersedes_unadmitted_R3_role": "external-secrets-flex-auth-envelope-signing replaces flex-auth-envelope-signing-eso to fit existing delegated-applier role convention; dedicated SA unchanged",
+ "read_ccr_field_projection": "Only ED25519_PRIVATE_KEY is delivered; public sibling fields remain visible to dedicated document-reader identity but never become consumer trust discovery",
+ "projection_source": "docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed",
+ "managed_declaration": "Missing rapp declaration remains migration debt; retain actual Access919ce25 workload identity, do not create package tuple"
+ }
+}
diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.md b/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.md
new file mode 100644
index 0000000..01853bb
--- /dev/null
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.md
@@ -0,0 +1,37 @@
+# Native signer action preparation
+
+CCR-2026-0050 is a schema-valid proposed ESO read grant. It does not authorize
+secret writes. The separate exact native-action JSON requests one attended CAS0
+and fixed metadata setup/readback; founder disposition is still pending. No
+standing writer/broker grant, private consumer lookup, rapp declaration or policy
+change is introduced. Native private custody remains in the founder's reviewed
+warden child, not an agent prompt or temporary file.
+
+The JSON fixes the target/cluster/backend, new kid, CAS version, exact native
+methods, retention/CAS metadata, existing custody machinery, signer fence,
+receipt location/encoding, abort owner and forward-only recovery. It separately
+identifies Access/cluster-owned stop/restart/ESO/PVC and Secrets-owned independent
+public admission. These owners must review their exact inputs; Platform cannot
+sign their dispositions. The program that enforces this native contract still
+requires source implementation and independent tests before executable approval.
+The existing fixture adapter must not be promoted by deleting its process check.
+
+Source preparation validated the real credential-change schema and generated
+HCL. The expected unconfirmed-bound-claims warning and proposed status preserve
+apply refusal. No live observation/identity confirmation, credential read, CAS,
+policy write, role, Kubernetes or route mutation occurred. Missing managed
+package declaration remains explicit migration debt for the retained workload.
+
+The native candidate explicitly amends two unadmitted R3 read-lane choices:
+role `external-secrets-flex-auth-envelope-signing` follows the existing delegated
+applier convention; read CCR fields names the projected ED25519_PRIVATE_KEY only.
+The same document has public sibling fields; document-granular read means ESO
+can read those too, so this is not field-level authorization. The sibling native
+projection proposal and CCR agree on the new role. Historical R3 is not mutated.
+
+Access metadata observation found zero PVCs in flex-auth. Its exact proposed
+new outbox PVC1Gi/local-path is a separate resource/retention scope, with Delete/
+WaitForFirstConsumer PV behavior needing explicit treatment. Existing outbox code
+lacks process locking/byte ceiling/bounded startup and compaction. Neither
+Recreate1 nor Helmkeep supplies these guarantees; production durable acceptance
+remains closed until Access-owned hardening and cluster/storage review.
diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed b/docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed
new file mode 100644
index 0000000..589a7ca
--- /dev/null
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed
@@ -0,0 +1,50 @@
+# SOURCE PROPOSAL ONLY: no CCR admission, no apply, no plaintext key.
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: flex-auth-envelope-signing-eso
+ namespace: flex-auth
+automountServiceAccountToken: false
+---
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+ name: openbao-flex-auth-envelope-signing
+ namespace: flex-auth
+spec:
+ provider:
+ vault:
+ server: http://openbao.openbao.svc:8200
+ path: platform
+ version: v2
+ auth:
+ kubernetes:
+ mountPath: kubernetes
+ role: external-secrets-flex-auth-envelope-signing
+ serviceAccountRef:
+ name: flex-auth-envelope-signing-eso
+ audiences: [openbao]
+---
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+ name: flex-auth-secrets-engine-envelope-signing
+ namespace: flex-auth
+spec:
+ refreshInterval: 5m
+ secretStoreRef:
+ kind: SecretStore
+ name: openbao-flex-auth-envelope-signing
+ target:
+ name: flex-auth-secrets-engine-envelope-signing
+ creationPolicy: Owner
+ deletionPolicy: Retain
+ template:
+ metadata:
+ annotations:
+ railiance.io/custody-proposal: RPF35-PDP-SIGNER-NATIVE-20261016-R1
+ data:
+ - secretKey: seed
+ remoteRef:
+ key: workloads/flex-auth/envelope-signing
+ property: ED25519_PRIVATE_KEY
diff --git a/tests/test_flex_auth_signer_native_preparation.py b/tests/test_flex_auth_signer_native_preparation.py
new file mode 100644
index 0000000..bd1b357
--- /dev/null
+++ b/tests/test_flex_auth_signer_native_preparation.py
@@ -0,0 +1,49 @@
+"""Real CCR schema/applier contract, no credentials or backend invocation."""
+from copy import deepcopy
+import importlib.util
+import json
+from pathlib import Path
+import yaml
+ROOT=Path(__file__).resolve().parents[1]
+spec=importlib.util.spec_from_file_location('signer_ccr',ROOT/'scripts/credential-change.py')
+ccr_module=importlib.util.module_from_spec(spec);spec.loader.exec_module(ccr_module)
+CCR=ROOT/'credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml'
+
+
+def test_real_schema_and_apply_refusal():
+ ccr,errors,warnings=ccr_module.validate_ccr(CCR)
+ assert errors==[]
+ assert warnings==['OIDC/Kubernetes bound claim is not confirmed; apply is blocked']
+ blockers=ccr_module.applier_readiness_blockers(ccr)
+ assert len(blockers)==2
+ assert any('status, got proposed' in x for x in blockers)
+ assert any('confirmed OpenBao auth binding' in x for x in blockers)
+ assert ccr['review']['comments']==[] and ccr['access_frontdoor']['resolvable'] is False
+ # Pure synthetic candidate only: ensure admitted-pattern scope has no latent
+ # schema/applier role/field failures once the real owners eventually approve.
+ admitted=deepcopy(ccr);admitted['status']='approved';admitted['openbao']['auth']['bound_claims_confirmed']=True
+ assert ccr_module.applier_readiness_blockers(admitted)==[]
+
+
+def test_generated_read_scope_and_projection_join():
+ ccr,_,_=ccr_module.validate_ccr(CCR)
+ assert (ROOT/ccr['openbao']['policy_file']).read_text()==ccr_module.generated_policy_hcl(ccr)
+ hcl=ccr_module.generated_policy_hcl(ccr)
+ assert 'metadata/' not in hcl and 'list' not in hcl and 'create' not in hcl
+ assert ccr['openbao']['fields']==['ED25519_PRIVATE_KEY']
+ docs=list(yaml.safe_load_all((ROOT/'docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed').read_text()))
+ store=next(x for x in docs if x['kind']=='SecretStore')
+ assert store['spec']['provider']['vault']['auth']['kubernetes']['role']==ccr['openbao']['auth']['role']
+ external=next(x for x in docs if x['kind']=='ExternalSecret')
+ assert external['spec']['data']==[{'secretKey':'seed','remoteRef':{'key':'workloads/flex-auth/envelope-signing','property':'ED25519_PRIVATE_KEY'}}]
+
+
+def test_native_scope_separates_read_grant_and_unadmitted_execution():
+ plan=json.loads((ROOT/'docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json').read_text())
+ assert plan['authority_granted'] is False and plan['status']=='prepared-unapproved'
+ assert plan['bootstrap']['expected_version']==0
+ assert all(x['disposition'].startswith('pending ') for x in plan['reviews_required'])
+ assert 'implementation/independent tests still required' in plan['protected_adapter']['state']
+ assert plan['receipts']['maximum_bytes']==65536
+ assert all('platform/' in x['path'] for x in plan['bootstrap']['allowed_native_methods'])
+ assert 'zero flex-auth PVCs' in plan['requested_scope']['new_resources']
--- a/workplans/RPF-WP-0035-credential-lane-implementation.md
+++ b/workplans/RPF-WP-0035-credential-lane-implementation.md
@@ -1344,3 +1344,13 @@
R3 source metadata correction aligns proposal IDs across JSON/docs/Secret
annotation and documents existing base64url-ASCII public fingerprint hashing.
Frozen R2 source/evidence is preserved; no writer behavior or admission changes.
+
+2026-10-16 T12 native signer preparation: proposed CCR-2026-0050 and generated
+read-only HCL pass real schema checks; current proposed status/unconfirmed claims
+keep apply refused. Native candidate uses existing external-secrets-* role
+convention with dedicated SA unchanged and projected private field only. Exact
+attended CAS0/metadata/fence/receipt/forward-recovery action plan is separate from
+read-grant authority; production adapter implementation/independent tests and
+founder plus distinct owner reviews remain necessary. Access observed zero PVCs;
+new outbox storage/retention/runtime bounds remain separate Access/cluster scope.
+No live credentials, grants, roles, key or deployment changed. T12 stays progress.

View file

@ -0,0 +1,41 @@
{
"schema": "railiance-platform.signer-native-preparation-source.v1",
"proposal_id": "RPF35-PDP-SIGNER-NATIVE-20261016-R1",
"requires_writer_R2_patch_sha256": "b0c190060f5a41d6002e1fb7b49f047fd4874a51576f29f3c127a8634c2616f6",
"requires_writer_R3_delta_sha256": "a29cdbd2ac92fd09bc6c79144e9aeb84bb6abcd303df997c0f01c0223cf1a508",
"canonical_budget_revision": "76fdc0b",
"isolated_source": "/tmp/netkingdom-platform-signer-native-preparation-20261016",
"patch_sha256": "a9d4631d3320ac3fa56f5a61965005c546eae241d2004cc1e6385e04668b7f7f",
"files": {
"credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml": {
"before_sha256": null,
"after_sha256": "72fd6e84e866a72f4768dabd66326aebe1fdd00013b2314de961ca72a632679f"
},
"openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl": {
"before_sha256": null,
"after_sha256": "b9f816da25c7770863a5fc7b3b873e9da90b087e71a9052e766bf0a187ff85ef"
},
"docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json": {
"before_sha256": null,
"after_sha256": "abc7c97eee8c059ee1d93b6e48e6f6277c4b8810623510d5cede20fd889feb44"
},
"docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.md": {
"before_sha256": null,
"after_sha256": "241e3e46d7823c1fb0dd62ce2d61dadf659d109286acea872e0aaad9cac26760"
},
"docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed": {
"before_sha256": null,
"after_sha256": "4972402b6d6490eb4dd540525b5ce475443d6b99373c28eda197c932438d745f"
},
"tests/test_flex_auth_signer_native_preparation.py": {
"before_sha256": null,
"after_sha256": "8b05c5f519e69f66be9dc1ac942f5a7a8fe60edda63d4c38daaba7e15a166314"
},
"workplans/RPF-WP-0035-credential-lane-implementation.md": {
"before_sha256": "d3cbe2ba5e344c1e8fc7e9f422d89030616b98abd53df4b9327f44fcaca865d3",
"after_sha256": "bab368699a4f80ac0f9092a467c7f4b50c9831c4543dc86a3f51eabb82fdf320"
}
},
"native_writer_source": "fixture core pinned in native plan; production adapter implementation/independent tests still required",
"authority_granted": false
}

View file

@ -0,0 +1,22 @@
{
"reviewer_role": "railiance-platform-owner",
"agent": "railiance-platform (codex)",
"reviewed_revision_sha256": "a9d4631d3320ac3fa56f5a61965005c546eae241d2004cc1e6385e04668b7f7f",
"disposition": "approve-source-preparation-only",
"assessment": "Schema-valid proposed read CCR, generated exact data-only policy and concrete native action/fence/receipt plan reuse existing custody machinery. Corrected existing delegated-applier convention failures without widening verifier. Required external/native approvals remain distinct. New protected production adapter must still implement/test the exact native plan; source preparation is not executable/live approval.",
"required_distinct_reviews": [
"Access source/image/caller/stop-restart/outbox/PVC",
"Secrets independent owner-authenticated public receipt/trust/fingerprint/native consumer acceptance",
"cluster/GitOps delivery identity/RBAC/ESO/sync/storage",
"founder new custody scope and exact executed revision",
"Platform native protected adapter/readback/receipt/fence implementation"
],
"preserved": [
"writer R2",
"metadata R3",
"CCR0048",
"CCR0049",
"WORK-RECORDS.md"
],
"authority_granted": false
}

View file

@ -0,0 +1,22 @@
{
"patch_sha256": "a9d4631d3320ac3fa56f5a61965005c546eae241d2004cc1e6385e04668b7f7f",
"test_command": "pytest -q tests/test_flex_auth_signer_native_preparation.py",
"result": "3 passed in 0.07s",
"real_CCR_validation_errors": [],
"expected_warning": "OIDC/Kubernetes bound claim is not confirmed; apply is blocked",
"real_applier_blockers": [
"proposed status",
"unconfirmed OpenBao auth binding"
],
"original_candidate_failures_preserved": [
"unsafe lowercase kid field declaration for delegated applier",
"role did not match existing delegated-applier convention"
],
"candidate_corrections": [
"private projected field only; document reader can still read public sibling fields",
"external-secrets-flex-auth-envelope-signing role; dedicated SA unchanged"
],
"synthetic_approved_bound_copy_gate": "zero scope-shape blockers; pure local candidate only, never persisted as approved",
"apply_check": "pass exact preimages/new-file absence",
"live_actions": false
}

View file

@ -0,0 +1,8 @@
{
"reviewer": "railiance-platform-owner (agent railiance-platform, codex)",
"decision": "approved",
"scope": "Exact source-only proposal for integration review; no credential scope/admission or execution approval.",
"comment": "Platform INTENT Mission and principles 3 Durable by Default, 4 Secure Custody, 5 Operable and Observable: exact-path private delivery and independent public admission reuse existing owner patterns, retain recovery/durability gaps rather than declaring acceptance. Work item RPF-WP-0035-T12; reviewed patch sha256:0f31567718023cdf2e2c1b3a5d8c8916312fd44b039b9bc32a2e404011fe4a57 at base 5f30ddd90c33e7eb1b9efa1093c7324d83210cf2. Prepared and reviewed in isolated owner checkout with owner instructions and INTENT read; only Platform owner role reviewed.",
"authority_unchanged": true,
"missing_gates": "Proposal lists null key, public admission, CCR/exact human scope decision, writer command, CI image, package debt, installed consumer, durability, capacity, diff/dryrun, costs/risk and abort-owner receipts."
}

View file

@ -0,0 +1,46 @@
{
"schema": "railiance-platform.protected-execution-recovery-assessment.v1",
"work_item": "RPF-WP-0035-T12",
"scope": "Assessment only. Not signer admission, recovery implementation or production acceptance.",
"finding": "Wave5 SQLite persistence provides restart refusal and durable cleanup debt, not protected cleanup recovery.",
"evidence": [
{
"path": "scripts/task_execution_native_fixture.py",
"sha256": "60abb78634038ca9422a35265c7a21b6653fed8ef5b85c4d50e846f4e3da4edf",
"details": "PrivateSession token/accessor, FixtureAdmission owner/identity tokens and Popen reference, task handle and cleanup-step counters live in memory. _revoke needs retained session; no protected durable recovery reference is implemented."
},
{
"path": "scripts/task_execution_journal.py",
"sha256": "507b41a790e678dfd16af747bd851f5abc2fed5a8fb70bca05449d9e5ac674e8",
"details": "reserve INSERT rejects reused approval/operation; _admission/_consumed_admission reset outside invocation; _cleanup_admission requires same live consumed admission. observe never creates authority; before_cleanup explicitly excludes restart recovery."
},
{
"path": "tests/test_task_execution_native_fixture.py",
"sha256": "0127b2cb84570e27c9fd70560e8088a7ad531497ee4a83a8917632e8c02982c2",
"details": "Explicit synthetic owner recovery is not adapter retry/resumption or independently admitted production recovery."
}
],
"next_owner_task": {
"id": "RPF-WP-0035-T12 recovery successor source work; retain through CUST-WP-0080",
"owner": "railiance-platform",
"title": "Specify and implement independently admitted protected cleanup recovery without business resumption",
"inputs": [
"Owner-controlled protected durable handle/reference custody and recovery executor; never place token/accessor values in public journal/Hub/artifacts.",
"Exact original plan/source/target/cleanup obligations and ambiguous issuance boundary; independent recovery admission bound to interrupted operation.",
"Secrets/Access/Approval owner correspondence for original consumed operation and recovery proof; no new request flag enables cleanup.",
"Concrete proposal/exact scope reviews before any production recovery authority is granted."
],
"acceptance": [
"Crash before and after policy PUT, token-create response, handle custody commit, each of six effects, revoke and policy delete; preserve unknown/pending states.",
"Restart refuses every business dispatch and original approval reuse.",
"Independently admitted recovery can only verify/revoke the exact interrupted sessions and remove the exact operation policy; no fresh token mint or business mutation.",
"If token creation response/handle is lost, prove bounded expiry plus independent owner reconciliation or a reviewed exact recovery mechanism; inability to identify/revoke is unresolved cleanup debt, never success.",
"Verify actual revocation/invalidation and exact policy absence using safe closed metadata; keep failed/uncertain outcomes and original receipt.",
"Disposable fixture recovery terminates and verifies the owner-spawned backend through independent owner custody; passing fixture teardown is not estate backend acceptance."
],
"exclusions": [
"No implementation in signer proposal.",
"No new grant, accessor exposure, broad revoke/list privilege or resume-from-SQLite authority."
]
}
}

View file

@ -0,0 +1,48 @@
{
"id": "flex-auth-decision-envelope-signing-key",
"title": "Ed25519 key that signs flex-auth decision envelopes",
"owner_repo": "railiance-platform",
"subsystem": "OpenBao + flex-auth",
"warden_executes": false,
"warden_role": "route",
"exec_capable": false,
"resolvable": false,
"wiki_ref": "wiki/playbooks/flex-auth-decision-envelope-signing-key.md#worker-checklist",
"canon_ref": "flex-auth/docs/decision-envelope-signature.md",
"reviewed": "2026-09-14",
"status": "draft",
"risk": "high",
"high_risk": true,
"workload_ref": {
"applicability": "applicable",
"rapp_id": null,
"name": null,
"deployable": null,
"declaration_ref": null,
"reason": null,
"unknown_reason": "flex-auth has not published an authoritative workload identity declaration for envelope signing.",
"resolution": "unknown"
},
"has_rotation": false,
"delegation": {
"mode": "interim",
"intended_owner": "railiance-platform",
"blocked_on": "Shape is chosen in flex-auth (FLEX-WP-0024-T02). OpenBao KV path, Kubernetes auth role, and paste_once_provision of the private seed are not built. Testdata key in flex-auth is not this lane.",
"reviewed": "2026-09-14",
"verified": "unverified",
"is_verified": false,
"implicit": false
},
"need_keywords": [
"flex-auth",
"decision",
"envelope",
"signing",
"signature",
"ed25519",
"responder",
"authenticity",
"FLEX-WP-0024"
],
"next_action": "next action on `railiance-platform` \u2014 see `wiki/playbooks/flex-auth-decision-envelope-signing-key.md#worker-checklist`"
}

View file

@ -0,0 +1,345 @@
diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing-policy.hcl.proposed b/docs/credential-lane-designs/flex-auth-envelope-signing-policy.hcl.proposed
new file mode 100644
index 0000000..e96f5f9
--- /dev/null
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing-policy.hcl.proposed
@@ -0,0 +1,10 @@
+# SOURCE PROPOSAL ONLY. New scope remains founder-gated; no metadata/list/write.
+path "platform/data/workloads/flex-auth/envelope-signing" {
+ capabilities = ["read"]
+}
+path "auth/token/lookup-self" {
+ capabilities = ["read"]
+}
+path "auth/token/revoke-self" {
+ capabilities = ["update"]
+}
diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing-projection.yaml.proposed b/docs/credential-lane-designs/flex-auth-envelope-signing-projection.yaml.proposed
new file mode 100644
index 0000000..ad8181e
--- /dev/null
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing-projection.yaml.proposed
@@ -0,0 +1,50 @@
+# SOURCE PROPOSAL ONLY: no CCR admission, no apply, no plaintext key.
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: flex-auth-envelope-signing-eso
+ namespace: flex-auth
+automountServiceAccountToken: false
+---
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+ name: openbao-flex-auth-envelope-signing
+ namespace: flex-auth
+spec:
+ provider:
+ vault:
+ server: http://openbao.openbao.svc:8200
+ path: platform
+ version: v2
+ auth:
+ kubernetes:
+ mountPath: kubernetes
+ role: flex-auth-envelope-signing-eso
+ serviceAccountRef:
+ name: flex-auth-envelope-signing-eso
+ audiences: [openbao]
+---
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+ name: flex-auth-secrets-engine-envelope-signing
+ namespace: flex-auth
+spec:
+ refreshInterval: 5m
+ secretStoreRef:
+ kind: SecretStore
+ name: openbao-flex-auth-envelope-signing
+ target:
+ name: flex-auth-secrets-engine-envelope-signing
+ creationPolicy: Owner
+ deletionPolicy: Retain
+ template:
+ metadata:
+ annotations:
+ railiance.io/custody-proposal: RPF35-PDP-SIGNER-CUSTODY-20261016-R1
+ data:
+ - secretKey: seed
+ remoteRef:
+ key: workloads/flex-auth/envelope-signing
+ property: ED25519_PRIVATE_KEY
diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing.md b/docs/credential-lane-designs/flex-auth-envelope-signing.md
new file mode 100644
index 0000000..667f056
--- /dev/null
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing.md
@@ -0,0 +1,134 @@
+# Retained lifecycle PDP signer custody proposal
+
+Proposal ID: RPF35-PDP-SIGNER-CUSTODY-20261016-R1. Preparation under
+RPF-WP-0035-T12, beneficiary FLEX-WP-0024-T06, coordinated by CUST-WP-0080.
+Status: source proposal only; no key generated, credential read, CCR admission,
+policy scope granted, package published or deployment changed.
+
+Platform INTENT principles 3–5 require durable custody, recovery and bounded
+interfaces. Platform is Staff/PEP-shaped and is not an authorization authority.
+The exact candidate contract is `flex-auth-envelope-signing.proposal.json`.
+Sibling `.proposed` files are review inputs outside deployment discovery; they
+carry no approved CCR id and MUST NOT be applied as an admitted lane.
+
+## Existing workload and custody boundary
+
+Retain namespace/release/deployment `flex-auth/flex-auth-secrets-engine`, signer
+SA `system:serviceaccount:flex-auth:flex-auth-secrets-engine`, and the twelve-action
+`secrets-engine.catalog-lane.lifecycle` v2 policy. The consumer SA remains
+`system:serviceaccount:secrets-engine:secrets-engine`; retained caller audience is
+`flex-auth`. Neither consumer nor TokenReview credentials confer seed read rights.
+Access R2 proposes `tenancy.yaml#/workload_identity`; it is not yet an integrated
+runtime admission. An authoritative rapp declaration remains explicit managed
+package migration debt, with reference null; no synthetic rapp tuple is created.
+Legacy image package remains `forgejo.coulomb.social/coulomb/flex-auth`.
+
+Reuse CCR-2026-0015's dedicated namespace SecretStore, exact data-only policy,
+short-lived Kubernetes delivery login and separate protected CAS writer patterns.
+The dedicated delivery SA is NOT the signer SA. No RoleBinding, Secret read,
+TokenRequest or ExternalSecret-create permission is given to the signer/consumer.
+The controller's existing ability to mint only the delivery-SA token and namespace
+mutation restrictions must be independently proven before activation; source YAML
+does not establish those facts. Do not silently broaden ESO RBAC.
+
+KV data `platform/data/workloads/flex-auth/envelope-signing` is read only by the
+reviewed ESO delivery role. There is no parent/list/metadata/write permission.
+Lookup-self/read and revoke-self/update support bounded session lifecycle. If ESO
+needs metadata, retain a failing receipt and review that exact scope amendment.
+The KV document has private `ED25519_PRIVATE_KEY` and public `ED25519_PUBLIC_KEY`,
+`kid`; only the private property is projected to the Secret. A public receipt
+contains kid, public key/fingerprint, KV version and exact source/deployment/policy
+bindings, authenticated independently through reviewed owner custody. Consumers
+must never read the private KV document to obtain the public key, trust `/v1/keys`,
+or derive authority from receipt shape, policy digest or this proposal.
+
+## Seed, projection and protected writer
+
+The attended protected writer generates a fresh 32-byte CSPRNG Ed25519 seed IN
+MEMORY in the custody process and writes unpadded strict base64url via CAS zero.
+It derives the public key there, verifies correspondence, and emits metadata only.
+No seed in argv, environment, temporary files, logs, diffs, chat, Hub or CI.
+Rotation requires exact current-version CAS. No fixture seed is promotable.
+`state_hub_preflight_lane.py` is a reusable design pattern, not an Ed25519 writer;
+a reviewed signer-specific receipt-producing command is still necessary owner work.
+Do not run the HMAC writer unchanged or claim D1 admission without that command.
+The read lane does not authorize writer scope. Use the existing attended
+OpenBao/warden access route; ops-warden routes and never vends this seed.
+
+Proposed namespace SecretStore uses dedicated SA, audience `openbao`, TTL/max/
+explicit max 15m, no default policy, no periodic token. Proposed Secret name
+`flex-auth-secrets-engine-envelope-signing`, key `seed`; owner creation and Retain
+deletion policy avoid implicit removal on custody outage. Retain also means a
+compromised seed survives until explicit cleanup: disabling ESO is not revocation.
+The current Access chart supplies readOnly volume, defaultMode 0440 and uid/gid/
+fsGroup 65532. Seed path is `/var/run/secrets/decision-envelope-signing/seed`;
+private data is never envFrom. Runtime validates file mode and reads once at
+startup, so ESO refresh alone cannot rotate process memory. Freeze exact Secret
+resourceVersion, KV version, public admission and startup kid together; require
+all replicas loaded the same admitted version before probes. No extra key reload
+mechanism is proposed.
+
+## Durable evidence and output limits
+
+Reuse current Access `outbox.enabled`, existingClaim and source support:
+`--outbox-dir` plus `--emission-source`, replicaCount 1, Recreate, one writer/PVC.
+Do not combine with `--log`. Outbox AppendBatch fsyncs the source envelope before
+returning; ordinary JSONL alone does not prove native independent custody.
+Existing outbox has no total-byte retention ceiling: chart size 1Gi is requested
+storage, NOT an enforced log quota. Before rollout name admitted existing PVC,
+capacity/headroom, alerts, bounded export/retention, disk-full fail-closed behavior,
+restart/partial-write readback tests and independent receiver/audit receipt.
+Audit sender credential/receiver admission is separate, not created by this lane.
+Do not silently create a PVC/spend commitment or enable arbitrary audit egress.
+No current native durable/audit acceptance follows from the source feature.
+
+Protected writer success/failure receipts must be closed metadata objects, at
+most 64KiB, stored owner-only mode 0600 with atomic durable writes. Fields: proposal
+and executed revision digests, actor/time, exact path, CAS version, kid/public
+fingerprint, deployment/image/policy/trust digests, boolean checks, rollback outcome.
+Never include response bodies, seed, bearer tokens, accessors, wrapped values,
+process environment or exception text. Bound backend reads to 1MiB and safe error
+codes; no shell tracing. Public admission may carry the public key and must have
+independent authenticated delivery. Access R2 native proof reader bounds receipt
+reads to 524289 bytes, rejecting >524288; that is not a durable writer quota.
+
+## Rotation, revocation and rollback
+
+Admit successor public key to each consumer first through independent custody
+and verify seed/public correspondence in the protected custody writer. Fence
+protected consumers and stop every signer replica, prove no old-key process
+remains, CAS-rotate, wait ESO version, restart the one-writer pin, then verify
+successor-signed non-effect lifecycle allow/deny and durable originals before
+reopening protected consumers.
+After last old issuance retain old key online no longer than actual maximum allow
+TTL plus a reviewed bounded uncertainty margin (default TTL is not a guarantee).
+Keep archived historical keys for offline audit separate from online trust; verify
+expired envelopes remain refused. Emergency compromise removes the old online key
+immediately, stops old processes, and recovers forward; never restore revoked seed.
+No scope/admission change to task policy: service pilot stays hard DENY and
+execute-ccr stays unknown_action on lifecycle policy.
+
+On signer rollout failure, stop protected consumers and restore exact previous
+producer image/values only as availability rollback; enforcing consumers continue
+to reject unsigned decisions. Outbox/PVC and source receipts are retained. Revert
+seed only for independently reviewed non-compromise recovery with corresponding
+public admission; otherwise forward recovery. If grant or effect outcome is unknown,
+retain unknown and reconcile; never auto-retry or translate it to success.
+
+## Gates and next owner work
+
+The JSON lists exact missing inputs and approvals. Oct11 D1 preapproves a reviewed
+receipt-producing lane for an attended window; it does not create an unbuilt lane.
+Oct11 standing build-mode rule permits qualifying non-destructive rollout after
+reviewed diff/dry-run, rollback, abort owner and receipts; new secret/access scope
+still needs founder approval. Source reviews are not human approval. Supervision
+record remains supervised; costs/risk and exact executor must be admitted for the
+executable revision. Source preparation costs EUR 0 incremental infrastructure;
+future resource/custody loss bounds are null rather than guessed or self-approved.
+
+Next Platform source task under RPF-WP-0035-T12: implement/test the protected
+Ed25519 CAS writer plus fail-closed metadata receipt and exact version readback,
+stage schema-valid proposed CCR through existing credential-change tooling after
+Access identity source and required approvers are confirmed. Reuse existing lane
+machinery. A receipt-producing command does not grant its write/read scope.
+Keep CCR-2026-0048/0049, WORK-RECORDS and unrelated work unchanged.
diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing.proposal.json b/docs/credential-lane-designs/flex-auth-envelope-signing.proposal.json
new file mode 100644
index 0000000..06ceeb8
--- /dev/null
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing.proposal.json
@@ -0,0 +1,110 @@
+{
+ "schema": "railiance-platform.signer-custody-proposal.v1",
+ "proposal_id": "RPF35-PDP-SIGNER-CUSTODY-20261016-R1",
+ "status": "source-proposal-unadmitted",
+ "owner": "railiance-platform",
+ "work_item": "RPF-WP-0035-T12",
+ "coordination": "CUST-WP-0080",
+ "beneficiary": "FLEX-WP-0024-T06",
+ "scope": {
+ "namespace": "flex-auth",
+ "release": "flex-auth-secrets-engine",
+ "signer_principal": "system:serviceaccount:flex-auth:flex-auth-secrets-engine",
+ "consumer_principal": "system:serviceaccount:secrets-engine:secrets-engine",
+ "rapp_declaration_ref": null,
+ "migration_debt": "Managed package declaration not established; retained Helm workload is not an exemption.",
+ "access_identity_source": "Access source-preparation-r2.patch tenancy.yaml#/workload_identity; proposal only"
+ },
+ "proposed_lane": {
+ "catalog_id": "flex-auth-decision-envelope-signing-key",
+ "route_status": "draft/nonresolvable",
+ "kv_mount": "platform",
+ "kv_path": "workloads/flex-auth/envelope-signing",
+ "private_field": "ED25519_PRIVATE_KEY",
+ "public_fields": [
+ "ED25519_PUBLIC_KEY",
+ "kid"
+ ],
+ "policy": "workload-kv-read-flex-auth-envelope-signing",
+ "auth_mount": "kubernetes",
+ "auth_role": "flex-auth-envelope-signing-eso",
+ "delivery_sa": "flex-auth-envelope-signing-eso",
+ "audience": "openbao",
+ "token_ttl": "15m",
+ "token_max_ttl": "15m",
+ "token_explicit_max_ttl": "15m",
+ "token_no_default_policy": true,
+ "secret_store": "openbao-flex-auth-envelope-signing",
+ "secret_name": "flex-auth-secrets-engine-envelope-signing",
+ "secret_key": "seed",
+ "mount_path": "/var/run/secrets/decision-envelope-signing/seed",
+ "mode": "0440",
+ "private_encoding": "unpadded strict base64url of 32-byte Ed25519 seed",
+ "seed_load": "startup only"
+ },
+ "independent_public_admission": {
+ "kid": null,
+ "public_key": null,
+ "fingerprint": null,
+ "custody_receipt": null,
+ "consumer_trust_digest": null,
+ "rule": "Owner-authenticated public receipt independently installed with exact evaluator/mode/policy pins; no self-discovery or private KV read by consumer."
+ },
+ "required_approvals": [
+ {
+ "actor": "founder",
+ "scope": "New exact KV read/write custody policy scope and dedicated delivery role; exact proposed CCR/revision and owner reviews required; no approval inferred."
+ },
+ {
+ "actor": "Access owner",
+ "scope": "Exact retained workload identity/source, signer interface, unchanged policy/caller binding, image provenance and producer rollout."
+ },
+ {
+ "actor": "Platform owner",
+ "scope": "Custody path, protected CAS writer, delivery identity/role and recovery receipts; this source review only."
+ },
+ {
+ "actor": "Secrets owner",
+ "scope": "Independent public key/trust pins, installed helper provenance and fail-closed native consumer acceptance."
+ },
+ {
+ "actor": "cluster/GitOps owner",
+ "scope": "Namespace SecretStore/ESO TokenRequest restrictions, manifests, rollout/headroom, existing PVC and recovery."
+ }
+ ],
+ "missing_deployment_inputs": {
+ "schema_valid_reviewed_CCR": null,
+ "human_exact_scope_decision": null,
+ "protected_signer_writer_command_digest": null,
+ "approved_executor_and_abort_owner": null,
+ "access_identity_integrated_revision": null,
+ "managed_package_admission_or_explicit_debt_disposition": null,
+ "CI_source_to_image_receipt": null,
+ "candidate_image_digest": null,
+ "KV_and_Secret_safe_version_receipt": null,
+ "native_identity_and_negative_scope_receipt": null,
+ "consumer_native_allow_deny_receipts": null,
+ "PVC_and_capacity_retention_admission": null,
+ "durable_original_and_independent_audit_receipt": null,
+ "reviewed_diff_server_dryrun_and_headroom": null,
+ "rotation_revocation_and_recovery_receipts": null,
+ "activation_cost_bound_eur": null,
+ "activation_risk_bound_eur": null
+ },
+ "unchanged_policy": {
+ "package": "secrets-engine.catalog-lane.lifecycle",
+ "version": "v2",
+ "digest": "sha256:bd11c5fe77ce6439c65fea225ad6b71d2110efc5e7b5bc9b499c59cd0a53b8b4",
+ "caller_auth": "enforce",
+ "caller_audience": "flex-auth",
+ "execute_ccr": "DENY unknown_action",
+ "service_pilot": "runtime_admission false"
+ },
+ "execution": {
+ "authorized_by_this_proposal": false,
+ "key_created": false,
+ "route_promoted": false,
+ "live_changes": false,
+ "preparation_incremental_spend_eur": 0
+ }
+}
diff --git a/workplans/RPF-WP-0035-credential-lane-implementation.md b/workplans/RPF-WP-0035-credential-lane-implementation.md
index 4ea8015..7223fef 100644
--- a/workplans/RPF-WP-0035-credential-lane-implementation.md
+++ b/workplans/RPF-WP-0035-credential-lane-implementation.md
@@ -1277,3 +1277,12 @@ CAS records consumed/unknown with zero native effects. Exact dependency/source
hashes, tests and remaining gates are retained with the source package and in
docs/task-execution-mediator-seam.md. No real grant/custody, policy default, owner
sync or native production acceptance is performed; T12 stays progress.
+
+2026-10-16 T12 signer prerequisite source proposal: prepared exact-path private seed
+ESO projection and separate independently admitted public verification material for
+the existing flex-auth-secrets-engine lifecycle PDP. Proposal
+RPF35-PDP-SIGNER-CUSTODY-20261016-R1 is source only; no seed, grant, live mutation
+or deployment admission. See docs/credential-lane-designs/flex-auth-envelope-signing.md
+and its JSON/.proposed companions. New custody scope, protected receipt-producing
+Ed25519 writer, managed-package migration debt, native outbox capacity/audit and
+exact producer/consumer acceptance remain explicit gates. T12 stays progress.

View file

@ -0,0 +1,51 @@
{
"scope": "Offline source checks only. No key minted, no credentials, no network/live calls, no manifests applied. Helm used public placeholder kid only, no seed.",
"checks": [
{
"name": "new custody scope remains unadmitted with missing key/provenance/executor inputs",
"passed": true
},
{
"name": "namespace-scoped delivery only, no RBAC or raw Secret object",
"passed": true
},
{
"name": "private field alone projected; public admission separate",
"passed": true
},
{
"name": "exact KV data path and bounded self lifecycle; no metadata/list/write grant",
"passed": true
},
{
"name": "owner create and Retain explicit",
"passed": true
},
{
"name": "existing chart refuses missing independently admitted kid/Secret",
"passed": true
},
{
"name": "existing chart renders source-only signer/outbox interfaces",
"passed": true
},
{
"name": "rendered seed read-only 0440 exact item and uid/fsGroup 65532",
"passed": true
},
{
"name": "rendered single-writer Recreate existing PVC without new claim",
"passed": true
},
{
"name": "rendered lifecycle package caller enforce retained and no task enablement",
"passed": true
},
{
"name": "patch applies cleanly to exact owner base",
"passed": true
}
],
"passed": 11,
"failed": 0
}

View file

@ -0,0 +1,21 @@
# Signer custody source R2
This full source patch supersedes the proposed R1 files while retaining R1 evidence.
Apply after the budget/source slice; its workplan preimage also includes the
actual full-chain successor note. Exact dependencies, preimages and hashes are in
manifest.json. All canonical source changes remain the coordinator's integration.
The source implements an in-memory Ed25519 CAS writer and exact-version native
readback with public-only, exclusive, atomic/fsynced receipts. It reuses existing
protected native fixture private-file and absolute HTTP budget machinery. Twelve
actual disposable OpenBao cases passed. Read or write uncertainty, including a
failed final success receipt, remains unknown; no automatic remint/retry/rollback
is possible. The original proposal was rebased to integrated Access919ce25.
This is a fixed disposable-only adapter. Direct module execution refuses live
use. Production must independently admit the exact schema-valid CCR/revision,
new custody scope, protected execution/isolation, replica fencing, receipt custody,
public receipt delivery/trust and rotation/recovery gates before adding a native
adapter. No founder approval, role, key, live credential, managed package tuple,
ESO binding or production grant is inferred. Missing managed declaration remains
explicit migration debt. Source approval is not production admission.

View file

@ -0,0 +1,655 @@
diff --git a/scripts/flex_auth_signer_custody.py b/scripts/flex_auth_signer_custody.py
new file mode 100644
index 0000000..1783a6f
--- /dev/null
+++ b/scripts/flex_auth_signer_custody.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python3
+"""Protected signer CAS primitive. Disposable owner fixture only; no live entry.
+
+This module deliberately cannot admit production custody. A reviewed native CCR,
+protected executor, fencing and founder scope are prerequisites for that adapter.
+"""
+from __future__ import annotations
+from dataclasses import dataclass,field
+from datetime import datetime,timezone
+import base64
+import hashlib
+import hmac
+import http.client
+import json
+import os
+from pathlib import Path
+import re
+import secrets
+import subprocess
+
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
+from task_execution_journal import Journal,Refused
+from task_execution_native_fixture import _absolute_budget
+from task_execution_recovery import _json_unique,_invalid_constant
+
+PATH='platform/data/workloads/flex-auth/envelope-signing'
+FIELDS={'ED25519_PRIVATE_KEY','ED25519_PUBLIC_KEY','kid'}
+BINDINGS={'proposal_sha256','executed_revision_sha256','deployment_sha256','image_sha256','policy_sha256','trust_sha256'}
+
+
+def encoded(value):return json.dumps(value,sort_keys=True,separators=(',',':'),allow_nan=False).encode()
+def b64(value):return base64.urlsafe_b64encode(value).decode().rstrip('=')
+
+
+@dataclass(frozen=True)
+class FixtureWriterAdmission:
+ port:int
+ owner_token:str=field(repr=False)
+ server_process:subprocess.Popen=field(repr=False)
+ fenced:bool=False
+
+
+class SignerWriter:
+ """Single attempted operation; failed/unknown writes never retry or remint.
+
+ File permissions are accidental-disclosure guards, not same-UID isolation.
+ The receipt directory must be independent owner custody in a native adapter.
+ Python cannot promise seed zeroization; process lifetime is the boundary.
+ """
+ def __init__(self,admission:FixtureWriterAdmission,directory:Path,operation_id:str,*,kid:str,expected_version:int,bindings:dict):
+ if (type(admission) is not FixtureWriterAdmission or type(admission.port) is not int
+ or not 1<=admission.port<=65535 or type(admission.owner_token) is not str
+ or not admission.owner_token or type(admission.fenced) is not bool or not admission.fenced):
+ raise Refused('exact fenced fixture custody required')
+ process=admission.server_process
+ if (type(process) is not subprocess.Popen or process.poll() is not None or type(process.args) is not list
+ or '-dev' not in process.args or '-dev-no-store-token' not in process.args
+ or '-dev-listen-address=127.0.0.1:'+str(admission.port) not in process.args):
+ raise Refused('live owner-spawned disposable dev process required')
+ if (type(operation_id) is not str or not re.fullmatch(r'[a-zA-Z0-9_-]{1,80}',operation_id)
+ or type(kid) is not str or not re.fullmatch(r'[a-zA-Z0-9_-]{1,80}',kid)
+ or type(expected_version) is not int or not 0<=expected_version<2**31
+ or type(bindings) is not dict or set(bindings)!=BINDINGS
+ or any(type(v) is not str or not re.fullmatch(r'sha256:[0-9a-f]{64}',v) for v in bindings.values())):
+ raise Refused('exact signer operation contract required')
+ if directory.absolute()!=directory.resolve() or directory.is_symlink():raise Refused('indirect receipt custody refused')
+ Journal._private(directory.stat(),directory=True)
+ self.admission=admission;self.directory=directory;self.path=directory/(operation_id+'.json')
+ self.expected_version=expected_version;self.kid=kid;self.attempted=False
+ self.receipt={'schema':'railiance-platform.signer-cas-receipt.v1','operation_id':operation_id,
+ 'bindings':dict(bindings),'writer_sha256':'sha256:'+hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
+ 'actor':'synthetic-owner-fixture','created_at':datetime.now(timezone.utc).isoformat(),'path':PATH,'expected_version':expected_version,'kid':kid,
+ 'status':'prepared','production_admission':False,'rollback':'forward-recovery-only',
+ 'public_key':None,'public_fingerprint':None,'kv_version':None,'exact_readback':False}
+ self._save(initial=True)
+
+ def _save(self,*,initial=False):
+ raw=encoded(self.receipt)
+ if len(raw)>65536:raise Refused('receipt bound exceeded')
+ if initial:
+ target=self.path
+ else:
+ fd=os.open(self.path,os.O_RDONLY|os.O_NOFOLLOW|os.O_NONBLOCK)
+ try:Journal._private(os.fstat(fd))
+ finally:os.close(fd)
+ target=self.directory/('.pending-'+secrets.token_hex(16))
+ fd=os.open(target,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
+ try:
+ with os.fdopen(fd,'wb',closefd=False) as stream:stream.write(raw);stream.flush();os.fsync(fd)
+ finally:os.close(fd)
+ if not initial:os.replace(target,self.path)
+ fd=os.open(self.directory,os.O_RDONLY|os.O_DIRECTORY|os.O_NOFOLLOW)
+ try:os.fsync(fd)
+ finally:os.close(fd)
+
+ def _http(self,method,path,body=None):
+ # Fixed loopback owner process and fixed data path; never arbitrary URL.
+ if self.admission.server_process.poll() is not None:raise Refused('fixture custody backend unavailable')
+ with _absolute_budget(2.0):
+ conn=http.client.HTTPConnection('127.0.0.1',self.admission.port,timeout=2)
+ try:
+ conn.request(method,'/v1/'+path,body=None if body is None else encoded(body),headers={
+ 'X-Vault-Token':self.admission.owner_token,'Content-Type':'application/json'})
+ response=conn.getresponse();raw=response.read(1048577)
+ if len(raw)>1048576:raise Refused('backend response bound exceeded')
+ value=json.loads(raw,object_pairs_hook=_json_unique,parse_constant=_invalid_constant) if raw else {}
+ return response.status,value
+ finally:conn.close()
+
+ def write(self):
+ if self.attempted:raise Refused('signer operation already attempted')
+ self.attempted=True
+ # All source/receipt checks precede generation; no seed import, argv,
+ # environment, temporary file or public output interface exists.
+ private=Ed25519PrivateKey.generate()
+ seed=b64(private.private_bytes(serialization.Encoding.Raw,serialization.PrivateFormat.Raw,serialization.NoEncryption()))
+ public=b64(private.public_key().public_bytes(serialization.Encoding.Raw,serialization.PublicFormat.Raw))
+ document={'ED25519_PRIVATE_KEY':seed,'ED25519_PUBLIC_KEY':public,'kid':self.kid}
+ self.receipt.update(status='write-intent-unknown',public_key=public,
+ public_fingerprint='sha256:'+hashlib.sha256(public.encode()).hexdigest())
+ self._save() # A durable intent must exist before any possible effect.
+ try:
+ status,result=self._http('POST',PATH,{'options':{'cas':self.expected_version},'data':document})
+ if status!=200 or type(result) is not dict:raise Refused('signer write unconfirmed')
+ version=result.get('data',{}).get('version')
+ if type(version) is not int or version!=self.expected_version+1:raise Refused('signer write version unconfirmed')
+ self.receipt['kv_version']=version
+ status,result=self._http('GET',PATH+'?version='+str(version))
+ if status!=200 or type(result) is not dict:raise Refused('signer readback unconfirmed')
+ data=result.get('data',{});stored=data.get('data') if type(data) is dict else None
+ metadata=data.get('metadata') if type(data) is dict else None
+ if (type(stored) is not dict or set(stored)!=FIELDS or any(type(v) is not str for v in stored.values())
+ or type(metadata) is not dict or type(metadata.get('version')) is not int or metadata['version']!=version
+ or any(not hmac.compare_digest(stored[name],document[name]) for name in FIELDS)):
+ raise Refused('signer readback mismatch')
+ self.receipt.update(status='custody-verified-pending-independent-public-admission',exact_readback=True)
+ self._save()
+ return dict(self.receipt)
+ except BaseException:
+ # Persist only closed metadata; unknown never implies absence or retry.
+ # If receipt storage fails, the durable earlier intent remains unknown.
+ self.receipt.update(status='write-intent-unknown',exact_readback=False)
+ self._save()
+ raise Refused('signer custody outcome unconfirmed') from None
+
+
+if __name__=='__main__':
+ raise SystemExit('production signer custody unadmitted; no live entry point')
diff --git a/tests/test_flex_auth_signer_custody.py b/tests/test_flex_auth_signer_custody.py
new file mode 100644
index 0000000..797b657
--- /dev/null
+++ b/tests/test_flex_auth_signer_custody.py
@@ -0,0 +1,115 @@
+"""Actual disposable KV CAS/readback; no estate key, identity or grants."""
+from dataclasses import replace
+import base64
+import json
+from pathlib import Path
+import sys
+
+import pytest
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
+sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'scripts'))
+from test_task_execution_native_fixture import bao_dev
+from flex_auth_signer_custody import SignerWriter,FixtureWriterAdmission,BINDINGS,PATH
+from task_execution_journal import Refused
+
+
+@pytest.fixture
+def fixture(bao_dev,tmp_path):
+ origin,root,call,process=bao_dev
+ assert call('POST','sys/mounts/platform',{'type':'kv','options':{'version':'2'}})[0] in {200,204}
+ directory=tmp_path/'receipts';directory.mkdir(mode=0o700)
+ admission=FixtureWriterAdmission(int(origin.rsplit(':',1)[1]),root,process,True)
+ def writer(operation='first',version=0,**kwargs):
+ return SignerWriter(admission,directory,operation,kid=kwargs.pop('kid','synthetic-key-'+str(version)),
+ expected_version=version,bindings={key:'sha256:'+'a'*64 for key in BINDINGS},**kwargs)
+ return writer,call,directory,admission
+
+
+def test_actual_bootstrap_rotation_public_correspondence_and_cas_conflict(fixture):
+ writer,call,directory,admission=fixture
+ first=writer();receipt=first.write()
+ assert receipt['status']=='custody-verified-pending-independent-public-admission'
+ assert receipt['kv_version']==1 and receipt['exact_readback']
+ document=call('GET',PATH)[1]['data']['data']
+ seed=base64.urlsafe_b64decode(document['ED25519_PRIVATE_KEY']+'=')
+ public=Ed25519PrivateKey.from_private_bytes(seed).public_key().public_bytes(serialization.Encoding.Raw,serialization.PublicFormat.Raw)
+ assert len(seed)==32
+ assert base64.urlsafe_b64encode(public).decode().rstrip('=')==receipt['public_key']==document['ED25519_PUBLIC_KEY']
+ raw=(directory/'first.json').read_bytes()
+ assert document['ED25519_PRIVATE_KEY'].encode() not in raw and admission.owner_token.encode() not in raw
+ assert (directory/'first.json').stat().st_mode&0o777==0o600
+ with pytest.raises(Refused):first.write()
+ with pytest.raises(FileExistsError):writer()
+ conflict=writer('conflict',0)
+ with pytest.raises(Refused,match='outcome unconfirmed'):conflict.write()
+ assert json.loads((directory/'conflict.json').read_bytes())['status']=='write-intent-unknown'
+ assert call('GET',PATH)[1]['data']['metadata']['version']==1
+ second=writer('rotation',1,kid='synthetic-successor').write()
+ assert second['kv_version']==2 and second['public_key']!=receipt['public_key']
+ assert call('GET',PATH+'?version=1')[1]['data']['data']==document
+ assert call('GET',PATH)[1]['data']['data']['kid']=='synthetic-successor'
+
+
+@pytest.mark.parametrize('case',['lost-response','readback-mismatch','read-denied','write-denied'])
+def test_unknown_is_durable_and_no_retry_or_private_output(fixture,monkeypatch,case):
+ factory,call,directory,admission=fixture
+ writer=factory();original=writer._http;seen=[]
+ def fault(method,path,body=None):
+ seen.append((method,path))
+ if case=='write-denied' and method=='POST':return 403,{'errors':['synthetic secret backend detail']}
+ if case=='read-denied' and method=='GET':return 403,{'errors':['synthetic secret backend detail']}
+ result=original(method,path,body)
+ if case=='lost-response' and method=='POST':raise OSError('synthetic secret backend detail')
+ if case=='readback-mismatch' and method=='GET':result[1]['data']['data']['kid']='different'
+ return result
+ monkeypatch.setattr(writer,'_http',fault)
+ with pytest.raises(Refused,match='^signer custody outcome unconfirmed$'):writer.write()
+ raw=(directory/'first.json').read_bytes();receipt=json.loads(raw)
+ assert receipt['status']=='write-intent-unknown' and receipt['exact_readback'] is False
+ assert b'synthetic secret backend detail' not in raw and admission.owner_token.encode() not in raw
+ if case!='write-denied':
+ document=call('GET',PATH)[1]['data']['data']
+ assert document['ED25519_PRIVATE_KEY'].encode() not in raw
+ before=list(seen)
+ with pytest.raises(Refused):writer.write()
+ assert seen==before
+ with pytest.raises(FileExistsError):factory()
+
+
+def test_durable_intent_failure_prevents_effect(fixture,monkeypatch):
+ factory,call,directory,admission=fixture
+ writer=factory()
+ def fail(**kwargs):raise OSError('private storage failed')
+ monkeypatch.setattr(writer,'_save',fail)
+ with pytest.raises(OSError):writer.write()
+ assert call('GET',PATH)[0]==404
+
+
+@pytest.mark.parametrize('case',['unfenced','wrong-port','bool-version','indirect-directory','unsafe-directory'])
+def test_invalid_custody_contract_before_generation_or_http(fixture,case):
+ factory,call,directory,admission=fixture
+ expected=0
+ if case=='unfenced':admission=replace(admission,fenced=False)
+ elif case=='wrong-port':admission=replace(admission,port=admission.port+1)
+ elif case=='bool-version':expected=True
+ elif case=='indirect-directory':
+ link=directory.parent/'indirect';link.symlink_to(directory,target_is_directory=True);directory=link
+ elif case=='unsafe-directory':directory.chmod(0o755)
+ with pytest.raises(Refused):SignerWriter(admission,directory,'bad',kid='synthetic',expected_version=expected,
+ bindings={key:'sha256:'+'a'*64 for key in BINDINGS})
+ assert call('GET',PATH)[0]==404
+
+
+def test_failed_success_receipt_retains_unknown_after_actual_write(fixture,monkeypatch):
+ factory,call,directory,admission=fixture
+ writer=factory();original=writer._save
+ def fail_success_only(**kwargs):
+ if writer.receipt['status']=='custody-verified-pending-independent-public-admission':
+ raise OSError('synthetic receipt failure')
+ return original(**kwargs)
+ monkeypatch.setattr(writer,'_save',fail_success_only)
+ with pytest.raises(Refused,match='outcome unconfirmed'):writer.write()
+ assert call('GET',PATH)[1]['data']['metadata']['version']==1
+ assert json.loads((directory/'first.json').read_bytes())['status']=='write-intent-unknown'
+ with pytest.raises(Refused):writer.write()
diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing.md b/docs/credential-lane-designs/flex-auth-envelope-signing.md
new file mode 100644
index 0000000..440999f
--- /dev/null
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing.md
@@ -0,0 +1,161 @@
+# Retained lifecycle PDP signer custody proposal
+
+Proposal ID: RPF35-PDP-SIGNER-CUSTODY-20261016-R2. Preparation under
+RPF-WP-0035-T12, beneficiary FLEX-WP-0024-T06, coordinated by CUST-WP-0080.
+Status: source proposal only; no key generated, credential read, CCR admission,
+policy scope granted, package published or deployment changed.
+
+Platform INTENT principles 3–5 require durable custody, recovery and bounded
+interfaces. Platform is Staff/PEP-shaped and is not an authorization authority.
+The exact candidate contract is `flex-auth-envelope-signing.proposal.json`.
+Sibling `.proposed` files are review inputs outside deployment discovery; they
+carry no approved CCR id and MUST NOT be applied as an admitted lane.
+
+## Existing workload and custody boundary
+
+Retain namespace/release/deployment `flex-auth/flex-auth-secrets-engine`, signer
+SA `system:serviceaccount:flex-auth:flex-auth-secrets-engine`, and the twelve-action
+`secrets-engine.catalog-lane.lifecycle` v2 policy. The consumer SA remains
+`system:serviceaccount:secrets-engine:secrets-engine`; retained caller audience is
+`flex-auth`. Neither consumer nor TokenReview credentials confer seed read rights.
+Access source revision `919ce25` integrates `tenancy.yaml#/workload_identity`;
+this source declaration remains separate from runtime admission. An authoritative rapp declaration remains explicit managed
+package migration debt, with reference null; no synthetic rapp tuple is created.
+Legacy image package remains `forgejo.coulomb.social/coulomb/flex-auth`.
+
+Reuse CCR-2026-0015's dedicated namespace SecretStore, exact data-only policy,
+short-lived Kubernetes delivery login and separate protected CAS writer patterns.
+The dedicated delivery SA is NOT the signer SA. No RoleBinding, Secret read,
+TokenRequest or ExternalSecret-create permission is given to the signer/consumer.
+The controller's existing ability to mint only the delivery-SA token and namespace
+mutation restrictions must be independently proven before activation; source YAML
+does not establish those facts. Do not silently broaden ESO RBAC.
+
+KV data `platform/data/workloads/flex-auth/envelope-signing` is read only by the
+reviewed ESO delivery role. There is no parent/list/metadata/write permission.
+Lookup-self/read and revoke-self/update support bounded session lifecycle. If ESO
+needs metadata, retain a failing receipt and review that exact scope amendment.
+The KV document has private `ED25519_PRIVATE_KEY` and public `ED25519_PUBLIC_KEY`,
+`kid`; only the private property is projected to the Secret. A public receipt
+contains kid, public key/fingerprint, KV version and exact source/deployment/policy
+bindings, authenticated independently through reviewed owner custody. Consumers
+must never read the private KV document to obtain the public key, trust `/v1/keys`,
+or derive authority from receipt shape, policy digest or this proposal.
+
+## Seed, projection and protected writer
+
+The attended protected writer generates a fresh 32-byte CSPRNG Ed25519 seed IN
+MEMORY in the custody process and writes unpadded strict base64url via CAS zero.
+It derives the public key there, verifies correspondence, and emits metadata only.
+No seed in argv, environment, temporary files, logs, diffs, chat, Hub or CI.
+Rotation requires exact current-version CAS. No fixture seed is promotable.
+`state_hub_preflight_lane.py` is a reusable design pattern, not an Ed25519 writer;
+the signer-specific primitive now exists in `scripts/flex_auth_signer_custody.py`,
+with a fixed disposable-only adapter. Production command/CCR admission is still
+missing and the module rejects direct live execution.
+Do not run the HMAC writer unchanged or claim D1 admission without that command.
+The read lane does not authorize writer scope. Use the existing attended
+OpenBao/warden access route; ops-warden routes and never vends this seed.
+
+Proposed namespace SecretStore uses dedicated SA, audience `openbao`, TTL/max/
+explicit max 15m, no default policy, no periodic token. Proposed Secret name
+`flex-auth-secrets-engine-envelope-signing`, key `seed`; owner creation and Retain
+deletion policy avoid implicit removal on custody outage. Retain also means a
+compromised seed survives until explicit cleanup: disabling ESO is not revocation.
+The current Access chart supplies readOnly volume, defaultMode 0440 and uid/gid/
+fsGroup 65532. Seed path is `/var/run/secrets/decision-envelope-signing/seed`;
+private data is never envFrom. Runtime validates file mode and reads once at
+startup, so ESO refresh alone cannot rotate process memory. Freeze exact Secret
+resourceVersion, KV version, public admission and startup kid together; require
+all replicas loaded the same admitted version before probes. No extra key reload
+mechanism is proposed.
+
+## Durable evidence and output limits
+
+Reuse current Access `outbox.enabled`, existingClaim and source support:
+`--outbox-dir` plus `--emission-source`, replicaCount 1, Recreate, one writer/PVC.
+Do not combine with `--log`. Outbox AppendBatch fsyncs the source envelope before
+returning; ordinary JSONL alone does not prove native independent custody.
+Existing outbox has no total-byte retention ceiling: chart size 1Gi is requested
+storage, NOT an enforced log quota. Before rollout name admitted existing PVC,
+capacity/headroom, alerts, bounded export/retention, disk-full fail-closed behavior,
+restart/partial-write readback tests and independent receiver/audit receipt.
+Audit sender credential/receiver admission is separate, not created by this lane.
+Do not silently create a PVC/spend commitment or enable arbitrary audit egress.
+No current native durable/audit acceptance follows from the source feature.
+
+Protected writer success/failure receipts must be closed metadata objects, at
+most 64KiB, stored owner-only mode 0600 with atomic durable writes. Fields: proposal
+and executed revision digests, actor/time, exact path, CAS version, kid/public
+fingerprint, deployment/image/policy/trust digests, boolean checks, rollback outcome.
+Never include response bodies, seed, bearer tokens, accessors, wrapped values,
+process environment or exception text. Bound backend reads to 1MiB and safe error
+codes; no shell tracing. Public admission may carry the public key and must have
+independent authenticated delivery. Access R2 native proof reader bounds receipt
+reads to 524289 bytes, rejecting >524288; that is not a durable writer quota.
+
+## Rotation, revocation and rollback
+
+Admit successor public key to each consumer first through independent custody
+and verify seed/public correspondence in the protected custody writer. Fence
+protected consumers and stop every signer replica, prove no old-key process
+remains, CAS-rotate, wait ESO version, restart the one-writer pin, then verify
+successor-signed non-effect lifecycle allow/deny and durable originals before
+reopening protected consumers.
+After last old issuance retain old key online no longer than actual maximum allow
+TTL plus a reviewed bounded uncertainty margin (default TTL is not a guarantee).
+Keep archived historical keys for offline audit separate from online trust; verify
+expired envelopes remain refused. Emergency compromise removes the old online key
+immediately, stops old processes, and recovers forward; never restore revoked seed.
+No scope/admission change to task policy: service pilot stays hard DENY and
+execute-ccr stays unknown_action on lifecycle policy.
+
+On signer rollout failure, stop protected consumers and restore exact previous
+producer image/values only as availability rollback; enforcing consumers continue
+to reject unsigned decisions. Outbox/PVC and source receipts are retained. Revert
+seed only for independently reviewed non-compromise recovery with corresponding
+public admission; otherwise forward recovery. If grant or effect outcome is unknown,
+retain unknown and reconcile; never auto-retry or translate it to success.
+
+## Gates and next owner work
+
+The JSON lists exact missing inputs and approvals. Oct11 D1 preapproves a reviewed
+receipt-producing lane for an attended window; it does not create an unbuilt lane.
+Oct11 standing build-mode rule permits qualifying non-destructive rollout after
+reviewed diff/dry-run, rollback, abort owner and receipts; new secret/access scope
+still needs founder approval. Source reviews are not human approval. Supervision
+record remains supervised; costs/risk and exact executor must be admitted for the
+executable revision. Source preparation costs EUR 0 incremental infrastructure;
+future resource/custody loss bounds are null rather than guessed or self-approved.
+
+Next Platform admission task under RPF-WP-0035-T12: stage a schema-valid proposed
+CCR through existing credential-change tooling and independently admit the
+protected production executor/fencing/receipt adapter after required approvers
+confirm the exact new custody scope. The primitive is source-tested only. Reuse existing lane
+machinery. A receipt-producing command does not grant its write/read scope.
+Keep CCR-2026-0048/0049, WORK-RECORDS and unrelated work unchanged.
+
+## Executable source primitive R2
+
+The fixed disposable owner adapter reuses the native fixture's absolute two-second
+transport/body/close bound and private receipt checks. It generates a new Ed25519
+seed only in the custody process, derives its public key, CAS-writes the exact
+three-field document, and independently reads that exact KV version. The private
+value is compared in memory; receipts carry public key/fingerprint and version
+only. Rotation must CAS the expected current version and uses a fresh seed/kid.
+The fixture requires explicit owner fencing and a still-live exact loopback dev
+process. It cannot establish production fencing, process isolation or grant scope.
+
+The owner-only receipt is exclusive per operation, bounded64KiB, fsynced before
+any effect, atomically replaced and directory-fsynced. Once write intent exists,
+any denied/malformed/lost response or mismatched readback retains unknown. Neither
+a repeated object call nor process restart using that operation receipt can retry.
+There is no automatic reconciliation, rollback or seed restore. A public receipt
+requires independent authenticated owner delivery before any consumer admits it.
+Python cannot guarantee seed zeroization; the protected process lifetime remains
+the custody boundary. Same-UID code is not isolated by0600 files.
+
+Actual disposable tests prove bootstrap CAS0, versioned readback, seed/public
+correspondence, rotation CAS1, stale CAS conflict, response-loss uncertainty,
+read denial/mismatch, pre-effect durable-intent failure, receipt secrecy, retry
+refusal and invalid admission/fencing rejection. No fixture key is promotable.
diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing.proposal.json b/docs/credential-lane-designs/flex-auth-envelope-signing.proposal.json
new file mode 100644
index 0000000..cc10da1
--- /dev/null
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing.proposal.json
@@ -0,0 +1,118 @@
+{
+ "schema": "railiance-platform.signer-custody-proposal.v1",
+ "proposal_id": "RPF35-PDP-SIGNER-CUSTODY-20261016-R2",
+ "status": "source-proposal-unadmitted",
+ "owner": "railiance-platform",
+ "work_item": "RPF-WP-0035-T12",
+ "coordination": "CUST-WP-0080",
+ "beneficiary": "FLEX-WP-0024-T06",
+ "scope": {
+ "namespace": "flex-auth",
+ "release": "flex-auth-secrets-engine",
+ "signer_principal": "system:serviceaccount:flex-auth:flex-auth-secrets-engine",
+ "consumer_principal": "system:serviceaccount:secrets-engine:secrets-engine",
+ "rapp_declaration_ref": null,
+ "migration_debt": "Managed package declaration not established; retained Helm workload is not an exemption.",
+ "access_identity_source": "access-engine integrated source revision 919ce25 tenancy.yaml#/workload_identity; runtime admission remains separate"
+ },
+ "proposed_lane": {
+ "catalog_id": "flex-auth-decision-envelope-signing-key",
+ "route_status": "draft/nonresolvable",
+ "kv_mount": "platform",
+ "kv_path": "workloads/flex-auth/envelope-signing",
+ "private_field": "ED25519_PRIVATE_KEY",
+ "public_fields": [
+ "ED25519_PUBLIC_KEY",
+ "kid"
+ ],
+ "policy": "workload-kv-read-flex-auth-envelope-signing",
+ "auth_mount": "kubernetes",
+ "auth_role": "flex-auth-envelope-signing-eso",
+ "delivery_sa": "flex-auth-envelope-signing-eso",
+ "audience": "openbao",
+ "token_ttl": "15m",
+ "token_max_ttl": "15m",
+ "token_explicit_max_ttl": "15m",
+ "token_no_default_policy": true,
+ "secret_store": "openbao-flex-auth-envelope-signing",
+ "secret_name": "flex-auth-secrets-engine-envelope-signing",
+ "secret_key": "seed",
+ "mount_path": "/var/run/secrets/decision-envelope-signing/seed",
+ "mode": "0440",
+ "private_encoding": "unpadded strict base64url of 32-byte Ed25519 seed",
+ "seed_load": "startup only"
+ },
+ "independent_public_admission": {
+ "kid": null,
+ "public_key": null,
+ "fingerprint": null,
+ "custody_receipt": null,
+ "consumer_trust_digest": null,
+ "rule": "Owner-authenticated public receipt independently installed with exact evaluator/mode/policy pins; no self-discovery or private KV read by consumer."
+ },
+ "required_approvals": [
+ {
+ "actor": "founder",
+ "scope": "New exact KV read/write custody policy scope and dedicated delivery role; exact proposed CCR/revision and owner reviews required; no approval inferred."
+ },
+ {
+ "actor": "Access owner",
+ "scope": "Exact retained workload identity/source, signer interface, unchanged policy/caller binding, image provenance and producer rollout."
+ },
+ {
+ "actor": "Platform owner",
+ "scope": "Custody path, protected CAS writer, delivery identity/role and recovery receipts; this source review only."
+ },
+ {
+ "actor": "Secrets owner",
+ "scope": "Independent public key/trust pins, installed helper provenance and fail-closed native consumer acceptance."
+ },
+ {
+ "actor": "cluster/GitOps owner",
+ "scope": "Namespace SecretStore/ESO TokenRequest restrictions, manifests, rollout/headroom, existing PVC and recovery."
+ }
+ ],
+ "missing_deployment_inputs": {
+ "schema_valid_reviewed_CCR": null,
+ "human_exact_scope_decision": null,
+ "protected_signer_writer_command_digest": null,
+ "approved_executor_and_abort_owner": null,
+ "access_identity_integrated_revision": "919ce25",
+ "managed_package_admission_or_explicit_debt_disposition": null,
+ "CI_source_to_image_receipt": null,
+ "candidate_image_digest": null,
+ "KV_and_Secret_safe_version_receipt": null,
+ "native_identity_and_negative_scope_receipt": null,
+ "consumer_native_allow_deny_receipts": null,
+ "PVC_and_capacity_retention_admission": null,
+ "durable_original_and_independent_audit_receipt": null,
+ "reviewed_diff_server_dryrun_and_headroom": null,
+ "rotation_revocation_and_recovery_receipts": null,
+ "activation_cost_bound_eur": null,
+ "activation_risk_bound_eur": null
+ },
+ "unchanged_policy": {
+ "package": "secrets-engine.catalog-lane.lifecycle",
+ "version": "v2",
+ "digest": "sha256:bd11c5fe77ce6439c65fea225ad6b71d2110efc5e7b5bc9b499c59cd0a53b8b4",
+ "caller_auth": "enforce",
+ "caller_audience": "flex-auth",
+ "execute_ccr": "DENY unknown_action",
+ "service_pilot": "runtime_admission false"
+ },
+ "execution": {
+ "authorized_by_this_proposal": false,
+ "key_created": false,
+ "route_promoted": false,
+ "live_changes": false,
+ "preparation_incremental_spend_eur": 0
+ },
+ "source_writer": {
+ "path": "scripts/flex_auth_signer_custody.py",
+ "scope": "protected in-memory Ed25519 CAS/readback primitive, disposable-only fixed owner fixture adapter",
+ "production_entry": "refused pending exact CCR/founder scope/protected runtime/fencing/receipt admission",
+ "independent_public_admission": "required after custody verification; receipt shape supplies no trust authority",
+ "retry": "none after durable intent; unknown requires independent reconciliation, no remint",
+ "rollback": "forward recovery; no seed restore primitive"
+ }
+}
diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing-policy.hcl.proposed b/docs/credential-lane-designs/flex-auth-envelope-signing-policy.hcl.proposed
new file mode 100644
index 0000000..e96f5f9
--- /dev/null
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing-policy.hcl.proposed
@@ -0,0 +1,10 @@
+# SOURCE PROPOSAL ONLY. New scope remains founder-gated; no metadata/list/write.
+path "platform/data/workloads/flex-auth/envelope-signing" {
+ capabilities = ["read"]
+}
+path "auth/token/lookup-self" {
+ capabilities = ["read"]
+}
+path "auth/token/revoke-self" {
+ capabilities = ["update"]
+}
diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing-projection.yaml.proposed b/docs/credential-lane-designs/flex-auth-envelope-signing-projection.yaml.proposed
new file mode 100644
index 0000000..ad8181e
--- /dev/null
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing-projection.yaml.proposed
@@ -0,0 +1,50 @@
+# SOURCE PROPOSAL ONLY: no CCR admission, no apply, no plaintext key.
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: flex-auth-envelope-signing-eso
+ namespace: flex-auth
+automountServiceAccountToken: false
+---
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+ name: openbao-flex-auth-envelope-signing
+ namespace: flex-auth
+spec:
+ provider:
+ vault:
+ server: http://openbao.openbao.svc:8200
+ path: platform
+ version: v2
+ auth:
+ kubernetes:
+ mountPath: kubernetes
+ role: flex-auth-envelope-signing-eso
+ serviceAccountRef:
+ name: flex-auth-envelope-signing-eso
+ audiences: [openbao]
+---
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+ name: flex-auth-secrets-engine-envelope-signing
+ namespace: flex-auth
+spec:
+ refreshInterval: 5m
+ secretStoreRef:
+ kind: SecretStore
+ name: openbao-flex-auth-envelope-signing
+ target:
+ name: flex-auth-secrets-engine-envelope-signing
+ creationPolicy: Owner
+ deletionPolicy: Retain
+ template:
+ metadata:
+ annotations:
+ railiance.io/custody-proposal: RPF35-PDP-SIGNER-CUSTODY-20261016-R1
+ data:
+ - secretKey: seed
+ remoteRef:
+ key: workloads/flex-auth/envelope-signing
+ property: ED25519_PRIVATE_KEY
--- a/workplans/RPF-WP-0035-credential-lane-implementation.md
+++ b/workplans/RPF-WP-0035-credential-lane-implementation.md
@@ -1330,3 +1330,13 @@
This owner fixture uses separately installed synthetic identity/key/trust and
loopback OpenBao, grants no production authority and keeps T12 progress. Prior
failed temporal/normalization proofs remain retained.
+
+2026-10-16 T12 signer custody R2 source preparation rebases the proposal to
+integrated Access919ce25 and preserves current workplan state. Added protected
+in-memory Ed25519 CAS/readback primitive with durable closed metadata receipts
+and fixed disposable-only owner adapter. Actual synthetic OpenBao bootstrap,
+rotation, conflict/unknown/readback/receipt/private-output and admission failures
+are tested. Production direct entry refuses: exact CCR/founder scope, protected
+executor/fencing/receipt custody, independent consumer public admission, migration
+debt and native deployment prerequisites remain open. No live key/grant or CCR
+admission; RPF-WP-0035-T12 remains progress.

View file

@ -0,0 +1,43 @@
{
"schema": "railiance-platform.signer-writer-source.v1",
"proposal_id": "RPF35-PDP-SIGNER-CUSTODY-20261016-R2",
"canonical_base": "b9eaa200601f1226d13efe82472dfec81c2999aa",
"isolated_base": "87efed36e8af6ae78247fb2d725f303a68110a18",
"isolated_source": "/tmp/netkingdom-platform-signer-writer-20261016",
"requires_budget_source_slice_sha256": "7c17f607dacc9ed4f24fd8032ddac5f4fc9ae2f24555cf87a0b73c871ec8eb19",
"workplan_preimage_requires_fullchain_patch_sha256": "a26c68ff224663129757fd43d0a5364762180131030e4dda22975935be89f99d",
"preserves_signer_R1_patch_sha256": "0f31567718023cdf2e2c1b3a5d8c8916312fd44b039b9bc32a2e404011fe4a57",
"Access_identity_integrated_revision": "919ce25",
"files": {
"scripts/flex_auth_signer_custody.py": {
"before_sha256": null,
"after_sha256": "8ed281a49c1f1cdb23772efe3593e75c62572a12b8d7a6a4c574f27b38722e75"
},
"tests/test_flex_auth_signer_custody.py": {
"before_sha256": null,
"after_sha256": "cd47c33bda7a2a18808e0daa027e8a66b2a55eaf0ee79e603337f3c2eb2c3761"
},
"docs/credential-lane-designs/flex-auth-envelope-signing.md": {
"before_sha256": null,
"after_sha256": "43c80cafaae1cfeee76324d15def9006553de7e1e90a98252fac9aaf778fd445"
},
"docs/credential-lane-designs/flex-auth-envelope-signing.proposal.json": {
"before_sha256": null,
"after_sha256": "6e798b51c25526463aa0401aa5df6e9d1a0bb754a67c7c5d96876c02dfe03266"
},
"docs/credential-lane-designs/flex-auth-envelope-signing-policy.hcl.proposed": {
"before_sha256": null,
"after_sha256": "a8ae0ea97e5bde8a9e5d785fc87b8095e4cc24b5fcf358a4b7b1568ab1dd07ee"
},
"docs/credential-lane-designs/flex-auth-envelope-signing-projection.yaml.proposed": {
"before_sha256": null,
"after_sha256": "4fde665568dedc5dd800e16f11e6dbfca83ea41d955efdb1fccef3976f35d6dc"
},
"workplans/RPF-WP-0035-credential-lane-implementation.md": {
"before_sha256": "9a669a7ff80345885edc6ac6a6333c3bdb3587a862a81fe672b1057a40940598",
"after_sha256": "65a4350d5720acc476bf5ae8e366ce37d9e3b47f7f339e8834eba3e84c1c8b65"
}
},
"production_admission": false,
"patch_sha256": "b0c190060f5a41d6002e1fb7b49f047fd4874a51576f29f3c127a8634c2616f6"
}

View file

@ -0,0 +1,32 @@
{
"schema": "railiance-platform.owner-source-review.v1",
"proposal_id": "RPF35-PDP-SIGNER-CUSTODY-20261016-R2",
"reviewer_role": "railiance-platform-owner",
"agent": "railiance-platform (codex)",
"reviewed_revision_sha256": "b0c190060f5a41d6002e1fb7b49f047fd4874a51576f29f3c127a8634c2616f6",
"disposition": "approve-source-only",
"basis": "RPF-WP-0035-T12, existing Platform INTENT custody/recovery/bounded-interface principles and delegated source authorization",
"assessment": "Signer-specific in-memory Ed25519 primitive and exact CAS/readback now execute against disposable OpenBao; exclusive durable metadata receipts precede effects and preserve unknown. No private material is exposed by receipt or direct command. Proposal references integrated Access919ce25 without creating runtime admission or managed package declaration.",
"limitations": [
"production entry deliberately refuses; reviewed native protected adapter still required",
"exact schema-valid CCR and new custody scope founder decision absent",
"fixture fenced boolean is synthetic owner input, not proof of stopped production replicas",
"0600 custody does not isolate hostile same-UID code",
"Python seed zeroization not guaranteed; protected process lifetime/isolation must be admitted",
"public receipt is not independent authenticated consumer trust admission",
"no role/ESO/Kubernetes/GitOps mutation or native scope acceptance",
"no rollback/seed restore/retry; unknown requires independently admitted reconciliation"
],
"preserved": [
"original signer R1",
"frozen recovery R2",
"budget/source slice",
"fullchain successor proof/workplan",
"CCR-2026-0048",
"CCR-2026-0049",
"WORK-RECORDS.md"
],
"preparation_incremental_eur": 0,
"production_cost_risk_bound": null,
"authority_granted": false
}

View file

@ -0,0 +1,33 @@
{
"patch_sha256": "b0c190060f5a41d6002e1fb7b49f047fd4874a51576f29f3c127a8634c2616f6",
"checks": [
{
"command": "SECRETS_ENGINE_SOURCE_ROOT=/tmp/netkingdom-secrets-trusted-successor PYTHONPATH=/tmp/netkingdom-secrets-trusted-successor/src pytest -q tests/test_flex_auth_signer_custody.py",
"result": "12 passed in 8.28s",
"backend": "fresh disposable loopback OpenBao KV v2 per case"
},
{
"command": "git apply --check implementation.patch against exact preimages/new-file absence",
"result": "pass"
}
],
"previous_run": "11 passed in 7.57s before adding receipt-finalization failure test and receipt actor/time/source digest",
"actual_live_actions": false,
"fixture_generated_keys": "synthetic disposable only, no promotion interface",
"fixture_cases": [
"CAS0 bootstrap",
"exact KV version readback",
"derived public key correspondence",
"CAS1 rotation leaves historical version",
"stale CAS conflict no overwrite",
"same-operation retry/restart refusal",
"durable intent before effect",
"lost actual write response remains unknown",
"denied write/read remains unknown",
"mismatched readback remains unknown",
"success receipt failure retains unknown",
"receipt contains no seed/bearer/backend detail",
"0600 fsynced receipt custody",
"unfenced/foreign-port/bool-version/symlink/unsafe-directory refusal"
]
}

View file

@ -0,0 +1,27 @@
{
"schema": "railiance-platform.signer-writer-metadata-delta.v1",
"proposal_id": "RPF35-PDP-SIGNER-CUSTODY-20261016-R3",
"requires_R2_patch_sha256": "b0c190060f5a41d6002e1fb7b49f047fd4874a51576f29f3c127a8634c2616f6",
"patch_sha256": "a29cdbd2ac92fd09bc6c79144e9aeb84bb6abcd303df997c0f01c0223cf1a508",
"files": {
"docs/credential-lane-designs/flex-auth-envelope-signing.md": {
"before_sha256": "43c80cafaae1cfeee76324d15def9006553de7e1e90a98252fac9aaf778fd445",
"after_sha256": "35a2ad5b86e54c5432eeb28f755086f057ba889a44412ab83ac1a52ea8548ac6"
},
"docs/credential-lane-designs/flex-auth-envelope-signing.proposal.json": {
"before_sha256": "6e798b51c25526463aa0401aa5df6e9d1a0bb754a67c7c5d96876c02dfe03266",
"after_sha256": "de7855693c424246c2e6c63e5b15f1d5d4509e6b64f3f1b234d10e675525d314"
},
"docs/credential-lane-designs/flex-auth-envelope-signing-projection.yaml.proposed": {
"before_sha256": "4fde665568dedc5dd800e16f11e6dbfca83ea41d955efdb1fccef3976f35d6dc",
"after_sha256": "74be3565948d4a70c5b01c24d2e5886c06e8c1a884e4e355517cbcf77a03f514"
},
"workplans/RPF-WP-0035-credential-lane-implementation.md": {
"before_sha256": "65a4350d5720acc476bf5ae8e366ce37d9e3b47f7f339e8834eba3e84c1c8b65",
"after_sha256": "d3cbe2ba5e344c1e8fc7e9f422d89030616b98abd53df4b9327f44fcaca865d3"
}
},
"isolated_source": "/tmp/netkingdom-platform-signer-writer-r3-20261016",
"writer_behavior_changed": false,
"production_admission": false
}

View file

@ -0,0 +1,13 @@
{
"reviewer_role": "railiance-platform-owner",
"reviewed_revision_sha256": "a29cdbd2ac92fd09bc6c79144e9aeb84bb6abcd303df997c0f01c0223cf1a508",
"disposition": "approve-source-only",
"checks": [
"exact preimage apply-check pass",
"JSON/docs/YAML current proposal ID equality pass",
"R1/R2 proposal IDs absent from current files",
"writer source and tests byte-identical to frozen R2"
],
"fingerprint": "SHA256 canonical unpadded base64url public-key ASCII, not decoded bytes; consumer agreement still required",
"authority_granted": false
}

View file

@ -0,0 +1,50 @@
# SOURCE PROPOSAL ONLY: no CCR admission, no apply, no plaintext key.
apiVersion: v1
kind: ServiceAccount
metadata:
name: flex-auth-envelope-signing-eso
namespace: flex-auth
automountServiceAccountToken: false
---
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: openbao-flex-auth-envelope-signing
namespace: flex-auth
spec:
provider:
vault:
server: http://openbao.openbao.svc:8200
path: platform
version: v2
auth:
kubernetes:
mountPath: kubernetes
role: flex-auth-envelope-signing-eso
serviceAccountRef:
name: flex-auth-envelope-signing-eso
audiences: [openbao]
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: flex-auth-secrets-engine-envelope-signing
namespace: flex-auth
spec:
refreshInterval: 5m
secretStoreRef:
kind: SecretStore
name: openbao-flex-auth-envelope-signing
target:
name: flex-auth-secrets-engine-envelope-signing
creationPolicy: Owner
deletionPolicy: Retain
template:
metadata:
annotations:
railiance.io/custody-proposal: RPF35-PDP-SIGNER-CUSTODY-20261016-R1
data:
- secretKey: seed
remoteRef:
key: workloads/flex-auth/envelope-signing
property: ED25519_PRIVATE_KEY

View file

@ -0,0 +1,161 @@
# Retained lifecycle PDP signer custody proposal
Proposal ID: RPF35-PDP-SIGNER-CUSTODY-20261016-R2. Preparation under
RPF-WP-0035-T12, beneficiary FLEX-WP-0024-T06, coordinated by CUST-WP-0080.
Status: source proposal only; no key generated, credential read, CCR admission,
policy scope granted, package published or deployment changed.
Platform INTENT principles 3–5 require durable custody, recovery and bounded
interfaces. Platform is Staff/PEP-shaped and is not an authorization authority.
The exact candidate contract is `flex-auth-envelope-signing.proposal.json`.
Sibling `.proposed` files are review inputs outside deployment discovery; they
carry no approved CCR id and MUST NOT be applied as an admitted lane.
## Existing workload and custody boundary
Retain namespace/release/deployment `flex-auth/flex-auth-secrets-engine`, signer
SA `system:serviceaccount:flex-auth:flex-auth-secrets-engine`, and the twelve-action
`secrets-engine.catalog-lane.lifecycle` v2 policy. The consumer SA remains
`system:serviceaccount:secrets-engine:secrets-engine`; retained caller audience is
`flex-auth`. Neither consumer nor TokenReview credentials confer seed read rights.
Access source revision `919ce25` integrates `tenancy.yaml#/workload_identity`;
this source declaration remains separate from runtime admission. An authoritative rapp declaration remains explicit managed
package migration debt, with reference null; no synthetic rapp tuple is created.
Legacy image package remains `forgejo.coulomb.social/coulomb/flex-auth`.
Reuse CCR-2026-0015's dedicated namespace SecretStore, exact data-only policy,
short-lived Kubernetes delivery login and separate protected CAS writer patterns.
The dedicated delivery SA is NOT the signer SA. No RoleBinding, Secret read,
TokenRequest or ExternalSecret-create permission is given to the signer/consumer.
The controller's existing ability to mint only the delivery-SA token and namespace
mutation restrictions must be independently proven before activation; source YAML
does not establish those facts. Do not silently broaden ESO RBAC.
KV data `platform/data/workloads/flex-auth/envelope-signing` is read only by the
reviewed ESO delivery role. There is no parent/list/metadata/write permission.
Lookup-self/read and revoke-self/update support bounded session lifecycle. If ESO
needs metadata, retain a failing receipt and review that exact scope amendment.
The KV document has private `ED25519_PRIVATE_KEY` and public `ED25519_PUBLIC_KEY`,
`kid`; only the private property is projected to the Secret. A public receipt
contains kid, public key/fingerprint, KV version and exact source/deployment/policy
bindings, authenticated independently through reviewed owner custody. Consumers
must never read the private KV document to obtain the public key, trust `/v1/keys`,
or derive authority from receipt shape, policy digest or this proposal.
## Seed, projection and protected writer
The attended protected writer generates a fresh 32-byte CSPRNG Ed25519 seed IN
MEMORY in the custody process and writes unpadded strict base64url via CAS zero.
It derives the public key there, verifies correspondence, and emits metadata only.
No seed in argv, environment, temporary files, logs, diffs, chat, Hub or CI.
Rotation requires exact current-version CAS. No fixture seed is promotable.
`state_hub_preflight_lane.py` is a reusable design pattern, not an Ed25519 writer;
the signer-specific primitive now exists in `scripts/flex_auth_signer_custody.py`,
with a fixed disposable-only adapter. Production command/CCR admission is still
missing and the module rejects direct live execution.
Do not run the HMAC writer unchanged or claim D1 admission without that command.
The read lane does not authorize writer scope. Use the existing attended
OpenBao/warden access route; ops-warden routes and never vends this seed.
Proposed namespace SecretStore uses dedicated SA, audience `openbao`, TTL/max/
explicit max 15m, no default policy, no periodic token. Proposed Secret name
`flex-auth-secrets-engine-envelope-signing`, key `seed`; owner creation and Retain
deletion policy avoid implicit removal on custody outage. Retain also means a
compromised seed survives until explicit cleanup: disabling ESO is not revocation.
The current Access chart supplies readOnly volume, defaultMode 0440 and uid/gid/
fsGroup 65532. Seed path is `/var/run/secrets/decision-envelope-signing/seed`;
private data is never envFrom. Runtime validates file mode and reads once at
startup, so ESO refresh alone cannot rotate process memory. Freeze exact Secret
resourceVersion, KV version, public admission and startup kid together; require
all replicas loaded the same admitted version before probes. No extra key reload
mechanism is proposed.
## Durable evidence and output limits
Reuse current Access `outbox.enabled`, existingClaim and source support:
`--outbox-dir` plus `--emission-source`, replicaCount 1, Recreate, one writer/PVC.
Do not combine with `--log`. Outbox AppendBatch fsyncs the source envelope before
returning; ordinary JSONL alone does not prove native independent custody.
Existing outbox has no total-byte retention ceiling: chart size 1Gi is requested
storage, NOT an enforced log quota. Before rollout name admitted existing PVC,
capacity/headroom, alerts, bounded export/retention, disk-full fail-closed behavior,
restart/partial-write readback tests and independent receiver/audit receipt.
Audit sender credential/receiver admission is separate, not created by this lane.
Do not silently create a PVC/spend commitment or enable arbitrary audit egress.
No current native durable/audit acceptance follows from the source feature.
Protected writer success/failure receipts must be closed metadata objects, at
most 64KiB, stored owner-only mode 0600 with atomic durable writes. Fields: proposal
and executed revision digests, actor/time, exact path, CAS version, kid/public
fingerprint, deployment/image/policy/trust digests, boolean checks, rollback outcome.
Never include response bodies, seed, bearer tokens, accessors, wrapped values,
process environment or exception text. Bound backend reads to 1MiB and safe error
codes; no shell tracing. Public admission may carry the public key and must have
independent authenticated delivery. Access R2 native proof reader bounds receipt
reads to 524289 bytes, rejecting >524288; that is not a durable writer quota.
## Rotation, revocation and rollback
Admit successor public key to each consumer first through independent custody
and verify seed/public correspondence in the protected custody writer. Fence
protected consumers and stop every signer replica, prove no old-key process
remains, CAS-rotate, wait ESO version, restart the one-writer pin, then verify
successor-signed non-effect lifecycle allow/deny and durable originals before
reopening protected consumers.
After last old issuance retain old key online no longer than actual maximum allow
TTL plus a reviewed bounded uncertainty margin (default TTL is not a guarantee).
Keep archived historical keys for offline audit separate from online trust; verify
expired envelopes remain refused. Emergency compromise removes the old online key
immediately, stops old processes, and recovers forward; never restore revoked seed.
No scope/admission change to task policy: service pilot stays hard DENY and
execute-ccr stays unknown_action on lifecycle policy.
On signer rollout failure, stop protected consumers and restore exact previous
producer image/values only as availability rollback; enforcing consumers continue
to reject unsigned decisions. Outbox/PVC and source receipts are retained. Revert
seed only for independently reviewed non-compromise recovery with corresponding
public admission; otherwise forward recovery. If grant or effect outcome is unknown,
retain unknown and reconcile; never auto-retry or translate it to success.
## Gates and next owner work
The JSON lists exact missing inputs and approvals. Oct11 D1 preapproves a reviewed
receipt-producing lane for an attended window; it does not create an unbuilt lane.
Oct11 standing build-mode rule permits qualifying non-destructive rollout after
reviewed diff/dry-run, rollback, abort owner and receipts; new secret/access scope
still needs founder approval. Source reviews are not human approval. Supervision
record remains supervised; costs/risk and exact executor must be admitted for the
executable revision. Source preparation costs EUR 0 incremental infrastructure;
future resource/custody loss bounds are null rather than guessed or self-approved.
Next Platform admission task under RPF-WP-0035-T12: stage a schema-valid proposed
CCR through existing credential-change tooling and independently admit the
protected production executor/fencing/receipt adapter after required approvers
confirm the exact new custody scope. The primitive is source-tested only. Reuse existing lane
machinery. A receipt-producing command does not grant its write/read scope.
Keep CCR-2026-0048/0049, WORK-RECORDS and unrelated work unchanged.
## Executable source primitive R2
The fixed disposable owner adapter reuses the native fixture's absolute two-second
transport/body/close bound and private receipt checks. It generates a new Ed25519
seed only in the custody process, derives its public key, CAS-writes the exact
three-field document, and independently reads that exact KV version. The private
value is compared in memory; receipts carry public key/fingerprint and version
only. Rotation must CAS the expected current version and uses a fresh seed/kid.
The fixture requires explicit owner fencing and a still-live exact loopback dev
process. It cannot establish production fencing, process isolation or grant scope.
The owner-only receipt is exclusive per operation, bounded64KiB, fsynced before
any effect, atomically replaced and directory-fsynced. Once write intent exists,
any denied/malformed/lost response or mismatched readback retains unknown. Neither
a repeated object call nor process restart using that operation receipt can retry.
There is no automatic reconciliation, rollback or seed restore. A public receipt
requires independent authenticated owner delivery before any consumer admits it.
Python cannot guarantee seed zeroization; the protected process lifetime remains
the custody boundary. Same-UID code is not isolated by0600 files.
Actual disposable tests prove bootstrap CAS0, versioned readback, seed/public
correspondence, rotation CAS1, stale CAS conflict, response-loss uncertainty,
read denial/mismatch, pre-effect durable-intent failure, receipt secrecy, retry
refusal and invalid admission/fencing rejection. No fixture key is promotable.

View file

@ -0,0 +1,118 @@
{
"schema": "railiance-platform.signer-custody-proposal.v1",
"proposal_id": "RPF35-PDP-SIGNER-CUSTODY-20261016-R2",
"status": "source-proposal-unadmitted",
"owner": "railiance-platform",
"work_item": "RPF-WP-0035-T12",
"coordination": "CUST-WP-0080",
"beneficiary": "FLEX-WP-0024-T06",
"scope": {
"namespace": "flex-auth",
"release": "flex-auth-secrets-engine",
"signer_principal": "system:serviceaccount:flex-auth:flex-auth-secrets-engine",
"consumer_principal": "system:serviceaccount:secrets-engine:secrets-engine",
"rapp_declaration_ref": null,
"migration_debt": "Managed package declaration not established; retained Helm workload is not an exemption.",
"access_identity_source": "access-engine integrated source revision 919ce25 tenancy.yaml#/workload_identity; runtime admission remains separate"
},
"proposed_lane": {
"catalog_id": "flex-auth-decision-envelope-signing-key",
"route_status": "draft/nonresolvable",
"kv_mount": "platform",
"kv_path": "workloads/flex-auth/envelope-signing",
"private_field": "ED25519_PRIVATE_KEY",
"public_fields": [
"ED25519_PUBLIC_KEY",
"kid"
],
"policy": "workload-kv-read-flex-auth-envelope-signing",
"auth_mount": "kubernetes",
"auth_role": "flex-auth-envelope-signing-eso",
"delivery_sa": "flex-auth-envelope-signing-eso",
"audience": "openbao",
"token_ttl": "15m",
"token_max_ttl": "15m",
"token_explicit_max_ttl": "15m",
"token_no_default_policy": true,
"secret_store": "openbao-flex-auth-envelope-signing",
"secret_name": "flex-auth-secrets-engine-envelope-signing",
"secret_key": "seed",
"mount_path": "/var/run/secrets/decision-envelope-signing/seed",
"mode": "0440",
"private_encoding": "unpadded strict base64url of 32-byte Ed25519 seed",
"seed_load": "startup only"
},
"independent_public_admission": {
"kid": null,
"public_key": null,
"fingerprint": null,
"custody_receipt": null,
"consumer_trust_digest": null,
"rule": "Owner-authenticated public receipt independently installed with exact evaluator/mode/policy pins; no self-discovery or private KV read by consumer."
},
"required_approvals": [
{
"actor": "founder",
"scope": "New exact KV read/write custody policy scope and dedicated delivery role; exact proposed CCR/revision and owner reviews required; no approval inferred."
},
{
"actor": "Access owner",
"scope": "Exact retained workload identity/source, signer interface, unchanged policy/caller binding, image provenance and producer rollout."
},
{
"actor": "Platform owner",
"scope": "Custody path, protected CAS writer, delivery identity/role and recovery receipts; this source review only."
},
{
"actor": "Secrets owner",
"scope": "Independent public key/trust pins, installed helper provenance and fail-closed native consumer acceptance."
},
{
"actor": "cluster/GitOps owner",
"scope": "Namespace SecretStore/ESO TokenRequest restrictions, manifests, rollout/headroom, existing PVC and recovery."
}
],
"missing_deployment_inputs": {
"schema_valid_reviewed_CCR": null,
"human_exact_scope_decision": null,
"protected_signer_writer_command_digest": null,
"approved_executor_and_abort_owner": null,
"access_identity_integrated_revision": "919ce25",
"managed_package_admission_or_explicit_debt_disposition": null,
"CI_source_to_image_receipt": null,
"candidate_image_digest": null,
"KV_and_Secret_safe_version_receipt": null,
"native_identity_and_negative_scope_receipt": null,
"consumer_native_allow_deny_receipts": null,
"PVC_and_capacity_retention_admission": null,
"durable_original_and_independent_audit_receipt": null,
"reviewed_diff_server_dryrun_and_headroom": null,
"rotation_revocation_and_recovery_receipts": null,
"activation_cost_bound_eur": null,
"activation_risk_bound_eur": null
},
"unchanged_policy": {
"package": "secrets-engine.catalog-lane.lifecycle",
"version": "v2",
"digest": "sha256:bd11c5fe77ce6439c65fea225ad6b71d2110efc5e7b5bc9b499c59cd0a53b8b4",
"caller_auth": "enforce",
"caller_audience": "flex-auth",
"execute_ccr": "DENY unknown_action",
"service_pilot": "runtime_admission false"
},
"execution": {
"authorized_by_this_proposal": false,
"key_created": false,
"route_promoted": false,
"live_changes": false,
"preparation_incremental_spend_eur": 0
},
"source_writer": {
"path": "scripts/flex_auth_signer_custody.py",
"scope": "protected in-memory Ed25519 CAS/readback primitive, disposable-only fixed owner fixture adapter",
"production_entry": "refused pending exact CCR/founder scope/protected runtime/fencing/receipt admission",
"independent_public_admission": "required after custody verification; receipt shape supplies no trust authority",
"retry": "none after durable intent; unknown requires independent reconciliation, no remint",
"rollback": "forward recovery; no seed restore primitive"
}
}

View file

@ -0,0 +1,73 @@
--- a/docs/credential-lane-designs/flex-auth-envelope-signing.md
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing.md
@@ -1,6 +1,6 @@
# Retained lifecycle PDP signer custody proposal
-Proposal ID: RPF35-PDP-SIGNER-CUSTODY-20261016-R2. Preparation under
+Proposal ID: RPF35-PDP-SIGNER-CUSTODY-20261016-R3. Preparation under
RPF-WP-0035-T12, beneficiary FLEX-WP-0024-T06, coordinated by CUST-WP-0080.
Status: source proposal only; no key generated, credential read, CCR admission,
policy scope granted, package published or deployment changed.
@@ -159,3 +159,13 @@
correspondence, rotation CAS1, stale CAS conflict, response-loss uncertainty,
read denial/mismatch, pre-effect durable-intent failure, receipt secrecy, retry
refusal and invalid admission/fencing rejection. No fixture key is promotable.
+
+R3 metadata correction: every current proposal identifier is
+`RPF35-PDP-SIGNER-CUSTODY-20261016-R3`, including the Secret annotation. Frozen
+R1/R2 evidence remains historical. The writer public_fingerprint convention is
+`sha256:` plus lowercase SHA-256 hex of the canonical unpadded base64url public
+key ASCII string (43 bytes for a 32-byte Ed25519 key). It does not hash the raw
+decoded bytes. This names the existing writer convention without changing code
+or admitting any consumer. Public admission must explicitly agree this encoding
+with the independent consumer; a raw-byte fingerprint must use a separately
+named field/version and never be silently substituted.
--- a/docs/credential-lane-designs/flex-auth-envelope-signing.proposal.json
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing.proposal.json
@@ -1,6 +1,6 @@
{
"schema": "railiance-platform.signer-custody-proposal.v1",
- "proposal_id": "RPF35-PDP-SIGNER-CUSTODY-20261016-R2",
+ "proposal_id": "RPF35-PDP-SIGNER-CUSTODY-20261016-R3",
"status": "source-proposal-unadmitted",
"owner": "railiance-platform",
"work_item": "RPF-WP-0035-T12",
@@ -113,6 +113,7 @@
"production_entry": "refused pending exact CCR/founder scope/protected runtime/fencing/receipt admission",
"independent_public_admission": "required after custody verification; receipt shape supplies no trust authority",
"retry": "none after durable intent; unknown requires independent reconciliation, no remint",
- "rollback": "forward recovery; no seed restore primitive"
+ "rollback": "forward recovery; no seed restore primitive",
+ "public_fingerprint_convention": "sha256: plus lowercase SHA-256 hex of canonical unpadded base64url public-key ASCII (43 bytes for Ed25519); not raw decoded key bytes"
}
}
--- a/docs/credential-lane-designs/flex-auth-envelope-signing-projection.yaml.proposed
+++ b/docs/credential-lane-designs/flex-auth-envelope-signing-projection.yaml.proposed
@@ -42,7 +42,7 @@
template:
metadata:
annotations:
- railiance.io/custody-proposal: RPF35-PDP-SIGNER-CUSTODY-20261016-R1
+ railiance.io/custody-proposal: RPF35-PDP-SIGNER-CUSTODY-20261016-R3
data:
- secretKey: seed
remoteRef:
--- a/workplans/RPF-WP-0035-credential-lane-implementation.md
+++ b/workplans/RPF-WP-0035-credential-lane-implementation.md
@@ -1331,7 +1331,7 @@
loopback OpenBao, grants no production authority and keeps T12 progress. Prior
failed temporal/normalization proofs remain retained.
-2026-10-16 T12 signer custody R2 source preparation rebases the proposal to
+2026-10-16 T12 signer custody R3 source preparation rebases the proposal to
integrated Access919ce25 and preserves current workplan state. Added protected
in-memory Ed25519 CAS/readback primitive with durable closed metadata receipts
and fixed disposable-only owner adapter. Actual synthetic OpenBao bootstrap,
@@ -1340,3 +1340,7 @@
executor/fencing/receipt custody, independent consumer public admission, migration
debt and native deployment prerequisites remain open. No live key/grant or CCR
admission; RPF-WP-0035-T12 remains progress.
+
+R3 source metadata correction aligns proposal IDs across JSON/docs/Secret
+annotation and documents existing base64url-ASCII public fingerprint hashing.
+Frozen R2 source/evidence is preserved; no writer behavior or admission changes.

View file

@ -0,0 +1,14 @@
{
"workplan_id": "dfadb19b-445e-59c6-bcd6-48e87b912c52",
"id": "d03a5c54-a410-405d-acb0-05f1f94a3db6",
"topic_id": null,
"task_id": "6aca534c-5c7a-556f-b6b6-406433670c29",
"decision_id": null,
"event_type": "note",
"summary": "CUST-WP-0080 implementation checkpoint 86a5b0dd published and normal sync89/89 passed: 0fail/automation_error,7warnings/4info; exact original staged brief/worktree preserved. Independently verified NetKingdom registration source0775c2bf and record0394484 against reviewed hashes, all16owner tasks projected; Warden d97652a canonical87checks pass, registration36checks pass. Recovery R3 independently52synthetic checks and14dependency hashes verified; retained R2 plaintext/daemon findings and R3 deadline cleanup race, successor in progress before canonical/native use. Real checker artifact local build preparation underway. All16TSA gates remain open; no new live credentials, deployment or native admission. Durable receipts under docs/changes/netkingdom-target-2026-10-16/.",
"detail": null,
"author": "codex",
"session_id": null,
"created_at": "2026-10-16T19:58:48.962667Z",
"workstream_id": "dfadb19b-445e-59c6-bcd6-48e87b912c52"
}

View file

@ -0,0 +1,14 @@
{
"workplan_id": "dfadb19b-445e-59c6-bcd6-48e87b912c52",
"id": "ce617715-77fe-4300-995c-2ade1e2e6ce2",
"topic_id": null,
"task_id": "6aca534c-5c7a-556f-b6b6-406433670c29",
"decision_id": null,
"event_type": "note",
"summary": "CUST-WP-0080-T06 source review checkpoint: exact Access rolloutR2 (manifest5be7a7f4) and Platform custodyP4 (manifest9eec8618) frozen and committed in Custodian3291a660ae5170d368bdc325b87d9bd5c551e7ff. Root reproduced both real Helm states and source projection; eight assembler tests pass, P4 executor/scope unchanged from independently testedP3. Source role, Helm maintenance and P4 join formally reviewed under cluster own RCLUSTER-WP-0017-T01; final source review+three canonical prefix examples committed2c98c637950164779d89a0b4eda194cbfd82b283, published/synced22/22, T01done only, nativeT02/T03todo. WFFC stop uses wait=false/no atomic; ready atomic requires actual stopped rollback revision. P1/P2/P3 and scanner failures retained. Root50de8be checkpoint published/synced89/89zero failures;3291 final sync running. No deployment, custody, trust, PVC or protected effect; actual founder/entity/expiry/CCR/fence/window/dispositions unresolved. All16 TSA gates open; full target goal remains active.",
"detail": null,
"author": "codex",
"session_id": null,
"created_at": "2026-10-16T18:40:21.384433Z",
"workstream_id": "dfadb19b-445e-59c6-bcd6-48e87b912c52"
}