docs: preserve reviewed NetKingdom session artifacts
This commit is contained in:
parent
4bf35dffaa
commit
6cf0012a6e
50 changed files with 8349 additions and 0 deletions
|
|
@ -0,0 +1,452 @@
|
|||
#!/usr/bin/env python3
|
||||
"""T18 native adapter for an owner-admitted, already restored isolated session.
|
||||
|
||||
Called after identity-bundle.restore, never with an unvalidated directory. This
|
||||
module does not provision TLS, grant credentials, or claim downstream acceptance.
|
||||
The existing decrypt/tmpfs wrapper remains responsible for archive cleanup.
|
||||
"""
|
||||
import base64
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import io
|
||||
import os
|
||||
import secrets
|
||||
import tarfile
|
||||
import time
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
IMAGE = 'forgejo.coulomb.social/coulomb/key-cape@sha256:cdec9dd7c515f7e0a6169fc3fa560ee2cc47bf40d899755dec808c362f5ce010'
|
||||
LABEL = 'net-kingdom.recovery-run'
|
||||
|
||||
|
||||
class Refused(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def load_bundle():
|
||||
spec = importlib.util.spec_from_file_location('identity_bundle', HERE / 'identity-bundle.py')
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def private_payload(root, manifest, expected_manifest_sha256):
|
||||
"""Bind the complete validated archive before selecting KeyCape material.
|
||||
|
||||
Private hashes never enter the public receipt. The digest is supplied by the
|
||||
existing private archive admission, not inferred from an untrusted manifest.
|
||||
"""
|
||||
encoded = json.dumps(manifest, sort_keys=True, separators=(',', ':')).encode()
|
||||
if hashlib.sha256(encoded).hexdigest() != expected_manifest_sha256:
|
||||
raise Refused()
|
||||
entries = manifest['files']
|
||||
if len(entries) != len({e['path'] for e in entries}):
|
||||
raise Refused()
|
||||
values = {}
|
||||
bundle = load_bundle()
|
||||
for entry in entries:
|
||||
if not bundle.allowed(entry['path'], manifest['profile']):
|
||||
raise Refused()
|
||||
path = root / entry['path']
|
||||
bundle.private(path)
|
||||
value = path.read_bytes()
|
||||
if len(value) != entry['bytes'] or hashlib.sha256(value).hexdigest() != entry['sha256']:
|
||||
raise Refused()
|
||||
values[entry['path']] = value
|
||||
deployment = json.loads(values['resources/sso/deployment/keycape.json'])
|
||||
main = next(c for c in deployment['spec']['template']['spec']['containers'] if c['name'] == 'keycape')
|
||||
if main.get('command') or main.get('args') or main.get('envFrom'):
|
||||
raise Refused()
|
||||
if main['image'] != IMAGE:
|
||||
# Mutable-tag/historical mismatches need a separate explicit owner admission.
|
||||
raise Refused()
|
||||
secret = json.loads(values['resources/sso/secret/keycape-config.json'])['data']
|
||||
files = {'/etc/keycape/' + name: base64.b64decode(secret[name], validate=True)
|
||||
for name in ('config.yaml', 'key.pem')}
|
||||
for path, value in values.items():
|
||||
if path.startswith('data/keycape/'):
|
||||
relative = path.removeprefix('data/keycape/')
|
||||
files['/var/lib/keycape-policy/' + relative] = value
|
||||
if '/var/lib/keycape-policy/policy.json' not in files:
|
||||
raise Refused()
|
||||
if 'resources/sso/secret/keycape-factor-read.json' in values:
|
||||
factor=json.loads(values['resources/sso/secret/keycape-factor-read.json'])['data']
|
||||
files['/etc/keycape-factor/admin-token']=base64.b64decode(factor['admin-token'],validate=True)
|
||||
# Resolve original delivered environment, including all referenced secrets.
|
||||
env = {}
|
||||
for item in main.get('env', []):
|
||||
if not re.fullmatch(r'KEYCAPE_[A-Z0-9_]+',item['name']):raise Refused()
|
||||
if item['name'] in env:
|
||||
raise Refused()
|
||||
if 'value' in item:
|
||||
env[item['name']] = item['value']
|
||||
else:
|
||||
ref = item['valueFrom']['secretKeyRef']
|
||||
source = json.loads(values['resources/sso/secret/' + ref['name'] + '.json'])['data']
|
||||
env[item['name']] = base64.b64decode(source[ref['key']], validate=True).decode()
|
||||
if env.get('KEYCAPE_CONFIG') != '/etc/keycape/config.yaml' or env.get('KEYCAPE_POLICY_PATH') != '/var/lib/keycape-policy/policy.json':
|
||||
raise Refused()
|
||||
return {'files': {p: base64.b64encode(v).decode() for p, v in files.items()}, 'env': env}
|
||||
|
||||
|
||||
ENDPOINT = 'unix:///var/run/docker.sock'
|
||||
EXECUTION = None
|
||||
|
||||
|
||||
def docker_environment(environment=None):
|
||||
# Even an admitted daemon exposed by another Unix socket is outside this
|
||||
# exact session binding. Conflicting ambient routing is refused, not ignored.
|
||||
for source in (os.environ, environment or {}):
|
||||
if source.get('DOCKER_HOST') not in (None,'',ENDPOINT):raise Refused()
|
||||
if source.get('DOCKER_CONTEXT') not in (None,'','default'):raise Refused()
|
||||
if any(source.get(k) for k in ('DOCKER_TLS_VERIFY','DOCKER_CERT_PATH')):raise Refused()
|
||||
env=dict(os.environ if environment is None else environment)
|
||||
for key in ('DOCKER_CONTEXT','DOCKER_TLS_VERIFY','DOCKER_CERT_PATH'):env.pop(key,None)
|
||||
env['DOCKER_HOST']=ENDPOINT
|
||||
return env
|
||||
|
||||
|
||||
def process(argv, **kwargs):
|
||||
marker=None
|
||||
if EXECUTION is not None and (kwargs.get('start_new_session') or kwargs.get('process_group') not in (None,-1)):raise Refused()
|
||||
if argv[0]=='docker':
|
||||
argv=['docker','--host',ENDPOINT,*argv[1:]]
|
||||
kwargs['env']=docker_environment(kwargs.get('env'))
|
||||
if EXECUTION is not None:
|
||||
if (os.getpid()!=EXECUTION['pid'] or os.getsid(0)!=EXECUTION['sid'] or os.getpgrp()!=EXECUTION['pgid']
|
||||
or time.monotonic()>=EXECUTION['deadline']):raise Refused()
|
||||
marker=Path(EXECUTION['staging']+'-docker-inflight')
|
||||
fd=os.open(marker,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
|
||||
os.close(fd)
|
||||
try:
|
||||
result=subprocess.run(argv,**kwargs)
|
||||
if marker is not None and result.returncode!=0:
|
||||
fd=os.open(Path(EXECUTION['staging']+'-docker-uncertain'),os.O_WRONLY|os.O_CREAT|os.O_NOFOLLOW,0o600);os.close(fd)
|
||||
return result
|
||||
except BaseException:
|
||||
# Parent loss leaves .docker-inflight; caught timeout/transport failures
|
||||
# retain a sticky uncertainty marker even if later cleanup can run.
|
||||
if marker is not None:
|
||||
fd=os.open(Path(EXECUTION['staging']+'-docker-uncertain'),os.O_WRONLY|os.O_CREAT|os.O_NOFOLLOW,0o600);os.close(fd)
|
||||
raise
|
||||
finally:
|
||||
if marker is not None:
|
||||
try:marker.unlink()
|
||||
except FileNotFoundError:pass
|
||||
|
||||
|
||||
def command(argv, **kwargs):
|
||||
maximum = kwargs.pop('max_output', 32768)
|
||||
result = process(argv, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
|
||||
timeout=kwargs.pop('timeout', 180), **kwargs)
|
||||
if result.returncode or len(result.stdout) > maximum:
|
||||
raise Refused()
|
||||
return result.stdout
|
||||
|
||||
|
||||
def inspect_session(session, worker_only=False):
|
||||
"""No credential environment is read by Docker inspect or returned to caller."""
|
||||
if not re.fullmatch(r'[a-f0-9]{32}', session['run_id']):
|
||||
raise Refused()
|
||||
names = session['containers']
|
||||
if not names or len(names) > 12 or len(set(names)) != len(names):
|
||||
raise Refused()
|
||||
for name in names:
|
||||
if not re.fullmatch(r'nk49-kc-' + session['run_id'] + r'-[a-z0-9-]{1,24}', name):
|
||||
raise Refused()
|
||||
if session['worker'] not in names or session['issuer'] not in names:
|
||||
raise Refused()
|
||||
import os
|
||||
contexts=json.loads(command(['docker','context','inspect']))
|
||||
endpoints=[contexts[0]['Endpoints']['docker']['Host'],os.environ.get('DOCKER_HOST') or ENDPOINT]
|
||||
if not all(x==ENDPOINT for x in endpoints):raise Refused()
|
||||
info = json.loads(command(['docker', 'info', '--format', '{"ID":{{json .ID}},"Name":{{json .Name}},"OSType":{{json .OSType}}}']))
|
||||
if info != {'ID': '74f93783-6cef-43a8-a3c4-f62586cd3bc1', 'Name': 'bnt-lap001', 'OSType': 'linux'}:
|
||||
raise Refused()
|
||||
# The worker is the anchor; all dependencies/TLS/issuer share its network-none namespace.
|
||||
anchor = command(['docker', 'container', 'inspect', session['worker'], '--format', '{{.Id}}']).decode().strip()
|
||||
for name,spec in session.get('volumes',{}).items():
|
||||
volume=json.loads(command(['docker','volume','inspect',name]))
|
||||
expected={'type':'tmpfs','device':'tmpfs','o':'size=33554432,uid=1000,gid=1000,mode=0700,nosuid,nodev,noexec'}
|
||||
if len(volume)!=1 or volume[0]['Driver']!='local' or volume[0].get('Scope')!='local' or volume[0].get('Options')!=expected or spec['options']!=expected or volume[0].get('Labels',{}).get(LABEL)!=session['run_id']:raise Refused()
|
||||
for name in names:
|
||||
obj = json.loads(command(['docker', 'container', 'inspect', name, '--format',
|
||||
'{"host":{{json .HostConfig}},"mounts":{{json .Mounts}},"labels":{{json .Config.Labels}},"image":{{json .Config.Image}},"user":{{json .Config.User}},"entrypoint":{{json .Config.Entrypoint}},"cmd":{{json .Config.Cmd}}}']))
|
||||
host = obj['host']
|
||||
mounts=obj['mounts']
|
||||
destinations=[m['Destination'] for m in mounts]
|
||||
if len(destinations)!=len(set(destinations)):raise Refused()
|
||||
for volume,expected_spec in session.get('volumes',{}).items():
|
||||
matches=[m for m in mounts if m['Destination']==expected_spec['target']]
|
||||
if len(matches)!=1 or matches[0]['Type']!='volume' or matches[0]['Name']!=volume or matches[0]['RW']!=(name==session['worker'] or expected_spec['target']=='/var/lib/keycape-policy'):raise Refused()
|
||||
if (obj['labels'].get(LABEL) != session['run_id'] or not obj['user'] or obj['user'].split(':')[0] in ('0', 'root')
|
||||
or not host['ReadonlyRootfs'] or host['Privileged'] or host.get('Binds') or host.get('PortBindings')
|
||||
or host.get('PidMode') or host.get('IpcMode') == 'host'
|
||||
or host.get('UTSMode') or host.get('UsernsMode') == 'host' or host.get('CgroupnsMode') == 'host'
|
||||
or host.get('Sysctls',{}) not in ({},{'net.ipv4.ip_unprivileged_port_start':'0'} if name==session['worker'] else {})
|
||||
or host.get('CapAdd') or host.get('Devices') or host.get('DeviceRequests') or host.get('VolumesFrom')
|
||||
or host.get('CapDrop') != ['ALL']
|
||||
or host.get('SecurityOpt') not in (['no-new-privileges'], ['no-new-privileges:true'])
|
||||
or any(m['Type']!='tmpfs' and not (m['Type']=='volume' and m['Name'] in session.get('volumes',{}) and m['Destination']==session['volumes'][m['Name']]['target']) for m in obj['mounts'])
|
||||
or host['NetworkMode'] != ('none' if name == session['worker'] else 'container:' + anchor)
|
||||
or obj['image'] != session['images'][name] or '@sha256:' not in obj['image']):
|
||||
raise Refused()
|
||||
if worker_only:return
|
||||
if session['images'][session['issuer']] != IMAGE:
|
||||
raise Refused()
|
||||
# Known admitted archive has no command/args override; image default /keycape
|
||||
# must be the actual issuer executable, not an alternate same-image command.
|
||||
launch=json.loads(command(['docker','container','inspect',session['issuer'],'--format',
|
||||
'{"entrypoint":{{json .Config.Entrypoint}},"cmd":{{json .Config.Cmd}},"path":{{json .Path}},"args":{{json .Args}}}']))
|
||||
if launch != {'entrypoint':['/keycape'],'cmd':None,'path':'/keycape','args':[]}:
|
||||
raise Refused()
|
||||
|
||||
|
||||
def container_file(container, path):
|
||||
# Docker streams one tar member into memory; never materialize private files.
|
||||
raw = command(['docker', 'cp', container + ':' + path, '-'], max_output=128*1024*1024)
|
||||
with tarfile.open(fileobj=io.BytesIO(raw), mode='r:') as archive:
|
||||
members = archive.getmembers()
|
||||
if len(members) != 1 or not members[0].isfile() or members[0].size > 96*1024*1024:
|
||||
raise Refused()
|
||||
return archive.extractfile(members[0]).read()
|
||||
|
||||
|
||||
JWKS_PROBE = r"""
|
||||
import base64,ipaddress,json,socket,ssl,sys,urllib.request
|
||||
from urllib.parse import urlsplit
|
||||
p=json.load(sys.stdin);issuer=p['issuer'];origin=urlsplit(issuer)
|
||||
def require(condition):
|
||||
if not condition:raise ValueError('public_jwks_binding_refused')
|
||||
require(origin.scheme=='https')
|
||||
resolved=socket.getaddrinfo(origin.hostname,origin.port or 443,type=socket.SOCK_STREAM)
|
||||
require(bool(resolved) and all(ipaddress.ip_address(x[4][0]).is_loopback for x in resolved))
|
||||
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
||||
def redirect_request(self,*args,**kwargs):return None
|
||||
opener=urllib.request.build_opener(urllib.request.ProxyHandler({}),NoRedirect(),
|
||||
urllib.request.HTTPSHandler(context=ssl.create_default_context(cadata=p['trust'])))
|
||||
def get(url):
|
||||
u=urlsplit(url)
|
||||
require((u.scheme,u.hostname,u.port or 443)==(origin.scheme,origin.hostname,origin.port or 443))
|
||||
require(not u.username and not u.password and not u.fragment)
|
||||
with opener.open(url,timeout=10) as response:
|
||||
raw=response.read(1024*1024+1);require(len(raw)<=1024*1024)
|
||||
return json.loads(raw)
|
||||
d=get(issuer.rstrip('/')+'/.well-known/openid-configuration');require(d['issuer']==issuer)
|
||||
keys=get(d['jwks_uri'])['keys']
|
||||
def integer(value):return int.from_bytes(base64.urlsafe_b64decode(value+'='*(-len(value)%4)),'big')
|
||||
# The exact admitted owner release constructs one key, kid key-1. Reject every
|
||||
# extra key, including irrelevant keys: a native successful exchange can then
|
||||
# only have used the original signer through the existing verifier.
|
||||
require(isinstance(keys,list) and len(keys)==1)
|
||||
k=keys[0]
|
||||
require(k.get('kty')=='RSA' and k.get('kid')=='key-1' and k.get('use')=='sig' and k.get('alg')=='RS256')
|
||||
require(integer(k['n'])==int(p['n']) and integer(k['e'])==int(p['e']))
|
||||
print('{"original_public_signing_key_in_live_jwks":true}')
|
||||
"""
|
||||
|
||||
# A failed public readiness probe is closed data, not a failing Docker RPC.
|
||||
# Transport/CLI nonzero remains uncertain; no deliberately failing exec is
|
||||
# retried as if daemon acceptance had been established.
|
||||
JWKS_TRANSPORT = ("import contextlib,io\ntry:\n"
|
||||
" with contextlib.redirect_stdout(io.StringIO()):\n"
|
||||
" exec(" + repr(JWKS_PROBE) + ")\n"
|
||||
"except Exception:\n print('{\"original_public_signing_key_in_live_jwks\":false}')\n"
|
||||
"else:\n print('{\"original_public_signing_key_in_live_jwks\":true}')\n")
|
||||
|
||||
|
||||
def check_jwks(session, admission, public_numbers):
|
||||
trust=runtime_file(session,admission['trust_path']).decode()
|
||||
request={'issuer':admission['issuer'],'trust':trust,'n':str(public_numbers.n),'e':str(public_numbers.e)}
|
||||
result=json.loads(command(['docker','exec','-i',session['worker'],'python3','-c',JWKS_TRANSPORT],input=json.dumps(request).encode()))
|
||||
if result != {'original_public_signing_key_in_live_jwks': True}:raise Refused()
|
||||
|
||||
|
||||
RUNTIME_FILES = r"""
|
||||
import base64,json,sys
|
||||
from pathlib import Path
|
||||
p=json.load(sys.stdin)
|
||||
roots=('/etc/keycape/','/var/lib/keycape-policy/','/etc/keycape-factor/','/recovery-tls/')
|
||||
result={}
|
||||
paths=[]
|
||||
if p.get('tree'):
|
||||
paths=list(Path('/var/lib/keycape-policy').rglob('*'))
|
||||
else:paths=[Path(x) for x in p['paths']]
|
||||
for f in paths:
|
||||
if f.is_dir():continue
|
||||
if not str(f).startswith(roots) or not f.is_file() or any(x.is_symlink() for x in (f,*f.parents)) or f.stat().st_size>8*1024*1024:raise ValueError()
|
||||
result[str(f)]=base64.b64encode(f.read_bytes()).decode()
|
||||
if len(result)>4096:raise ValueError()
|
||||
print(json.dumps(result))
|
||||
"""
|
||||
|
||||
|
||||
def runtime_files(session, paths=(), tree=False):
|
||||
raw=command(['docker','exec','-i',session['worker'],'python3','-c',RUNTIME_FILES],
|
||||
input=json.dumps({'paths':list(paths),'tree':tree}).encode(),max_output=48*1024*1024)
|
||||
return {k:base64.b64decode(v,validate=True) for k,v in json.loads(raw).items()}
|
||||
|
||||
|
||||
def runtime_file(session,path):
|
||||
return runtime_files(session,[path])[path]
|
||||
|
||||
|
||||
def run_verifier(session, admission, payload):
|
||||
issuer = session['issuer']
|
||||
if {v['target'] for v in session.get('volumes',{}).values()}!={'/etc/keycape','/var/lib/keycape-policy','/etc/keycape-factor','/recovery-tls'}:raise Refused()
|
||||
from urllib.parse import urlsplit
|
||||
import ipaddress
|
||||
address = urlsplit(admission['issuer'])
|
||||
if (address.scheme != 'https' or not address.hostname or address.username or address.password
|
||||
or address.query or address.fragment):
|
||||
raise Refused()
|
||||
if hashlib.sha256(container_file(issuer, '/keycape')).hexdigest() != admission['verifier_sha256']:
|
||||
raise Refused()
|
||||
if hashlib.sha256(runtime_file(session, admission['trust_path'])).hexdigest() != admission['trust_sha256']:
|
||||
raise Refused()
|
||||
# Capture private environment only in owner process memory, never receipt.
|
||||
environment = json.loads(command(['docker','container','inspect',issuer,'--format','{{json .Config.Env}}']))
|
||||
env = dict(item.split('=',1) for item in environment)
|
||||
if len(env)!=len(environment):raise Refused()
|
||||
if set(env)-set(payload['env'])-{'PATH','HOME','HOSTNAME','SSL_CERT_FILE'}:raise Refused()
|
||||
if env.get('SSL_CERT_FILE') != admission['trust_path'] or any(k.lower().endswith('_proxy') and v for k,v in env.items()):
|
||||
raise Refused()
|
||||
if {k:v for k,v in env.items() if k.startswith('KEYCAPE_')}!={k:v for k,v in payload['env'].items() if k.startswith('KEYCAPE_')}:raise Refused()
|
||||
for name,value in payload['env'].items():
|
||||
if env.get(name) != value:
|
||||
raise Refused()
|
||||
files = {k:base64.b64decode(v,validate=True) for k,v in payload['files'].items()}
|
||||
def equal():
|
||||
if runtime_files(session,files)!=files:raise Refused()
|
||||
durable=runtime_files(session,tree=True)
|
||||
if durable!={k:v for k,v in files.items() if k.startswith('/var/lib/keycape-policy/')}:
|
||||
raise Refused()
|
||||
equal()
|
||||
# Reading YAML is data parsing only; no archived executable is evaluated.
|
||||
import yaml
|
||||
cfg = yaml.safe_load(files['/etc/keycape/config.yaml'])
|
||||
if cfg['issuer'] != admission['issuer']:
|
||||
raise Refused()
|
||||
consumer = admission['consumer']
|
||||
client = next(x for x in cfg['clients'] if x['clientId']==consumer['client_id'])
|
||||
if (client['secretRef']!='env:'+consumer['secret_env'] or client['clientType']!='confidential'
|
||||
or 'client_credentials' not in client['grantTypes'] or not env.get(consumer['secret_env'])):
|
||||
raise Refused()
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
key=serialization.load_pem_private_key(files['/etc/keycape/key.pem'],password=None)
|
||||
check_jwks(session,admission,key.public_key().public_numbers())
|
||||
wrong = secrets.token_urlsafe(48)
|
||||
if wrong == env[consumer['secret_env']]:raise Refused()
|
||||
args = ['docker','exec','--env','NK49_UNRELATED_NEGATIVE_SECRET='+wrong,issuer,'/keycape','verify-client',
|
||||
'-issuer',admission['issuer'],'-previous-secret-env','NK49_UNRELATED_NEGATIVE_SECRET']
|
||||
flags = {'client_id':'client-id','secret_env':'secret-env','audience':'audience','scope':'scope',
|
||||
'subject':'expect-subject','tenant':'expect-tenant','roles':'expect-roles','deny_scope':'deny-scope'}
|
||||
for name,flag in flags.items():args+=['-'+flag,consumer[name]]
|
||||
# Native command reports on stderr. Do not return or print its raw response.
|
||||
result = process(args,stdout=subprocess.PIPE,stderr=subprocess.PIPE,timeout=150)
|
||||
raw = (result.stdout+result.stderr).decode()
|
||||
required = ['discovery and endpoint origin','exchange and JWKS signature for granted scopes','access-token claim bindings',
|
||||
'principal_type is service','sub matches the registration','tenant matches the registration','roles present',
|
||||
'granted scope carries no excess grant','predecessor secret refused']
|
||||
required += ['excess scope refused: '+scope for scope in consumer['deny_scope'].split()]
|
||||
if result.returncode or any('PASS '+name+'\n' not in raw for name in required) or 'FAIL ' in raw:
|
||||
raise Refused()
|
||||
equal()
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
key=serialization.load_pem_private_key(files['/etc/keycape/key.pem'],password=None)
|
||||
check_jwks(session,admission,key.public_key().public_numbers())
|
||||
public=key.public_key().public_bytes(serialization.Encoding.DER,serialization.PublicFormat.SubjectPublicKeyInfo)
|
||||
return {'native_service_registration_verified':True,'wrong_unrelated_secret_explicitly_refused':True,
|
||||
'denied_scopes_refused':True,'original_config_registry_policy_receipts_equal':True,
|
||||
'public_signing_spki_sha256':hashlib.sha256(public).hexdigest(),
|
||||
'original_https_issuer_and_trust_preserved':True,'original_public_signing_key_in_live_jwks':True}
|
||||
|
||||
|
||||
def cleanup(session, receipt=None):
|
||||
"""Attempt every owned cleanup even when one obligation fails.
|
||||
|
||||
Preserve exact safe container names and outcomes so the owner can recover an
|
||||
uncertain/partial cleanup. An inspect/daemon failure never means absence.
|
||||
"""
|
||||
obligations = []
|
||||
failed = False
|
||||
for name in reversed(session['containers']):
|
||||
row = {'container': name, 'status': 'unverified'}
|
||||
obligations.append(row)
|
||||
try:
|
||||
present=command(['docker','container','ls','--all','--filter','name=^/'+name+'$','--format','{{.Names}}']).decode().splitlines()
|
||||
if not present:
|
||||
row['status']='absence_verified'
|
||||
continue
|
||||
label = command(['docker', 'container', 'inspect', name, '--format',
|
||||
'{{index .Config.Labels "' + LABEL + '"}}']).decode().strip()
|
||||
if label != session['run_id']:
|
||||
row['status'] = 'ownership_mismatch_not_removed'
|
||||
failed = True
|
||||
continue
|
||||
# A failed remove is followed by a successful explicit inventory;
|
||||
# interrupted remove may actually have completed.
|
||||
try:
|
||||
command(['docker', 'rm', '--force', '--volumes', name])
|
||||
except Exception:
|
||||
pass
|
||||
remaining = command(['docker','container','ls','--all','--filter',
|
||||
'name=^/' + name + '$','--format','{{.Names}}']).decode().splitlines()
|
||||
if remaining:
|
||||
row['status'] = 'still_present'
|
||||
failed = True
|
||||
else:
|
||||
row['status'] = 'absence_verified'
|
||||
except Exception:
|
||||
failed = True
|
||||
if receipt is not None:
|
||||
receipt['container_cleanup_obligations'] = obligations
|
||||
if failed:
|
||||
raise Refused()
|
||||
|
||||
|
||||
|
||||
def native_checks(root, manifest, admission, receipt, cleanup_session=True):
|
||||
"""Owner wrapper provides a reviewed session after existing restore pipeline.
|
||||
|
||||
Admission must bind ciphertext, canonical private manifest digest, executable
|
||||
and trust bytes, exact image/target list, original issuer and selected consumer.
|
||||
None of these admission fields grant authority or carry a client secret.
|
||||
"""
|
||||
session = admission['session']
|
||||
# Validate ownership/containment before permitting either exec or cleanup.
|
||||
inspect_session(session)
|
||||
receipt.update(status='failed', source_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
|
||||
ciphertext_sha256=admission['ciphertext_sha256'], production_contacted=False,
|
||||
human_mfa_proven=False, downstream_consumer_reconnect_proven=False,
|
||||
container_cleanup_verified=False)
|
||||
try:
|
||||
for field in ('ciphertext_sha256', 'manifest_sha256', 'verifier_sha256', 'trust_sha256'):
|
||||
if not re.fullmatch('[a-f0-9]{64}', admission[field]):
|
||||
raise Refused()
|
||||
for field in ('client_id','secret_env','audience','scope','subject','tenant','roles','deny_scope'):
|
||||
if not isinstance(admission['consumer'].get(field), str) or not admission['consumer'][field].strip():
|
||||
raise Refused()
|
||||
payload = private_payload(root, manifest, admission['manifest_sha256'])
|
||||
result = run_verifier(session, admission, payload)
|
||||
required = {'native_service_registration_verified','wrong_unrelated_secret_explicitly_refused',
|
||||
'denied_scopes_refused','original_config_registry_policy_receipts_equal',
|
||||
'original_https_issuer_and_trust_preserved','original_public_signing_key_in_live_jwks'}
|
||||
if any(result.get(k) is not True for k in required) or not re.fullmatch('[a-f0-9]{64}', result.get('public_signing_spki_sha256','')):
|
||||
raise Refused()
|
||||
# Closed receipt allowlist: never copy arbitrary worker output.
|
||||
receipt.update({k: True for k in required})
|
||||
receipt['public_signing_spki_sha256'] = result['public_signing_spki_sha256']
|
||||
receipt['status'] = 'isolated_keycape_native_registration_checked'
|
||||
finally:
|
||||
try:
|
||||
if cleanup_session:
|
||||
cleanup(session, receipt)
|
||||
receipt['container_cleanup_verified'] = True
|
||||
except Exception:
|
||||
receipt['status'] = 'failed'
|
||||
raise
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env bash
|
||||
# Candidate owner launcher. Review and native fixture proof required before use.
|
||||
set +x
|
||||
set -euo pipefail
|
||||
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd -- "$ROOT"
|
||||
umask 077
|
||||
[[ $# == 6 ]] || { printf '%s\n' 'Usage: run-isolated-keycape.sh VERIFIED_TRANSFER_RECEIPT ADMISSION ADMISSION_SHA256 TLS_CERT TLS_KEY TRUST'; exit 1; }
|
||||
[[ "$3" =~ ^[a-f0-9]{64}$ ]] || { printf '%s\n' 'ERROR: exact reviewed admission digest required.'; exit 1; }
|
||||
for index in 1 2 4 5 6; do
|
||||
[[ -f "${!index}" && ! -L "${!index}" ]] || { printf '%s\n' 'ERROR: provide regular owner-controlled input files.'; exit 1; }
|
||||
done
|
||||
[[ -t 0 && -t 1 && -t 2 ]] || { printf '%s\n' 'ERROR: run in your own interactive terminal.'; exit 1; }
|
||||
for tool in python3 age age-keygen docker; do
|
||||
command -v "$tool" >/dev/null || { printf 'ERROR: required tool unavailable: %s\n' "$tool"; exit 1; }
|
||||
done
|
||||
python3 - <<'PY'
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
p=Path('tools/bootstrap-recovery/isolated-keycape-session.py')
|
||||
spec=importlib.util.spec_from_file_location('session',p)
|
||||
m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m)
|
||||
try:
|
||||
m.local.preflight()
|
||||
m.run(['docker','image','inspect',m.adapter.IMAGE])
|
||||
except Exception:
|
||||
raise SystemExit('ERROR: exact local Docker/images/tmpfs preflight failed; no key requested.')
|
||||
print('OK: fixed local target and cached pinned images available.')
|
||||
PY
|
||||
NK49_KC_RUN="$(mktemp -d /tmp/nk49-isolated-keycape-XXXXXXXX)"
|
||||
printf 'Safe evidence directory: %s\n' "$NK49_KC_RUN"
|
||||
printf '%s\n' 'Owner-reviewed candidate: isolated original issuer/trust and current registered service-client checks only.'
|
||||
printf '%s\n' 'No browser, downstream consumer or retained-factor MFA acceptance is claimed.'
|
||||
printf '%s\n' 'Enter only the independently held backup age key at the hidden prompt.'
|
||||
bash sso-mfa/bootstrap/sops-custody-unlock.sh \
|
||||
--expected-recipient age1zvryunvjhvpkmasskauga2heeg0ztnte9ymgppvjge36ekumk50syr3tsz \
|
||||
--prompt -- python3 tools/bootstrap-recovery/isolated-keycape-session.py \
|
||||
--transfer-receipt "$1" --admission "$2" --admission-sha256 "$3" \
|
||||
--tls-cert "$4" --tls-key "$5" --trust "$6" --receipt "$NK49_KC_RUN/recovery.json"
|
||||
printf 'Safe receipt: %s/recovery.json\n' "$NK49_KC_RUN"
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
# Discovery parity repair — integrated and verified
|
||||
|
||||
Canonical source and normal publication are complete:
|
||||
|
||||
| Owner | Implementation | Source work record |
|
||||
| --- | --- | --- |
|
||||
| Reuse Surface | `dd00b27` | `01af189` |
|
||||
| NetKingdom | `49ddde4` | `3223039` normal identifier assignment |
|
||||
| Railiance Bootstrap | `6927f5e` | `8d23103` |
|
||||
|
||||
The native validator now refuses stale owner/index projections, duplicate IDs or
|
||||
paths and malformed index shapes without tracebacks. There are **23 new CLI
|
||||
regression cases**, including null/list availability, malformed YAML/frontmatter
|
||||
and unvalidated index rows before Counter. Root independently passed all **30**
|
||||
parity/native-registry checks across 27 initial successes and three required
|
||||
filesystem-approved reruns. The sandbox write failures and initial isolated
|
||||
238-test history below remain historical evidence.
|
||||
|
||||
Current local cohort validation is **0 errors, 0 warnings**. Root forced fresh
|
||||
hosted composition at **2026-10-16 19:25:11 UTC**: all **70 reviewed IDs exactly
|
||||
once**, correct owners, no warning header, and both corrected owners' summary/tags
|
||||
match their authoritative entries. See `coordinator-hosted-refresh.json` and
|
||||
`integration-r2.json` for exact revisions and receipts. No further hosted readback
|
||||
is needed for this bounded correction. Source publication does not establish
|
||||
mandatory consumer fleet adoption or close every TSA-11 requirement.
|
||||
|
||||
Normal Reuse publication/consistency passed with existing warnings. Bootstrap
|
||||
source and record publication succeeded, while its consistency result retains
|
||||
11 historical failures and 36 warnings (zero automation errors). The unchanged
|
||||
generated-only hook refused automatic brief-only commits; no bypass was used.
|
||||
Public actions API requests returned HTTP404, so CI outcomes remain **unverified**.
|
||||
The final combined NetKingdom projection sync against browser publication
|
||||
`015694ee414c802dc2ef1fa0120dfed610086e69` completed exit 0, zero failures or
|
||||
automation errors, 29 existing/pre-repair warnings and five informational items.
|
||||
It indexed browser T10/T11/T12 and retained their source IDs through the normal
|
||||
tool; exact final receipt is `netkingdom-parity-consistency-final.json`.
|
||||
NetKingdom's original brief staged blob/mode and worktree were preserved with
|
||||
explicit staging isolation and race-checked restoration. Its current
|
||||
WORK-RECORDS was preserved amid concurrent recovery/browser owner work.
|
||||
|
||||
TSA-12 still requires the selected rein/profile acceptance, actual schedule
|
||||
activation and the first unassisted scheduled fire. No production action, model
|
||||
spend, credential access or external message was performed.
|
||||
|
||||
## Historical preparation and counterexamples
|
||||
|
||||
Prepared source candidate, 16 October 2026. No owner checkout or runtime changed.
|
||||
|
||||
The existing native `reuse-surface validate --fail-on-warnings` accepts index rows
|
||||
whose names, summaries, owner, status, domain, tags, consumption modes or vector
|
||||
no longer project their owner entry. It also accepts duplicate IDs/paths because
|
||||
it converts paths to sets. This is a gap in the current tool, not a new observer
|
||||
service. `.forgejo/workflows/ci.yml:37` already runs this validator as a mandatory
|
||||
job in Reuse Surface. Consumer fleet adoption is not claimed.
|
||||
|
||||
`reuse-native-validator.patch` extends that native command with parity and
|
||||
uniqueness warnings. Strict mode rejects them; normal mode preserves the existing
|
||||
warning policy. Equivalent vector whitespace remains accepted. It does not edit
|
||||
owner entries, maturity assessments, credentials or hosted registrations.
|
||||
|
||||
Actual current source has **3 differing fields across 2 capabilities**:
|
||||
|
||||
- NetKingdom bootstrap-recovery: summary.
|
||||
- Railiance Bootstrap bootstrap-sequencing: summary and tags.
|
||||
|
||||
`observed-drift.json` retains exact entry/index values. Native CLI evidence shows
|
||||
old exit 0, candidate exit 1, then exit 0 against corrected temporary indexes.
|
||||
The two `*-index-parity.patch` files correct only those index fields to their
|
||||
existing owner entries. No existing general index regeneration command was found;
|
||||
these are constrained source projections, not newly authored capability claims.
|
||||
Do not integrate the validator while silently ignoring its known cohort findings.
|
||||
Review both owner patches in the same source integration wave. No generated-only
|
||||
commit exception is implied; use the owners' normal publication workflow.
|
||||
|
||||
A separate **synthetic** duplicated row proves old exit 0 versus candidate exit 1.
|
||||
There is no observed duplicate in the actual reviewed cohort: 70/70 reviewed IDs
|
||||
appear exactly once across the current 60 local providers; those indexes contain
|
||||
71 total entries including an additional out-of-denominator owner entry.
|
||||
|
||||
Tests: 11 new native CLI cases; the full isolated suite had 237 pass and one
|
||||
missing-sibling-path failure. After resolving only the temporary federation
|
||||
manifest's relative local paths against `/home/worsch/reuse-surface`, the single
|
||||
failed composition test passed. All 238 tests have passed across those runs.
|
||||
The failed first run remains in `reuse-source.json`. No manifest edit belongs to
|
||||
the patch. Temporary reconstruction: `/tmp/netkingdom-improvement-loop-next-reuse`;
|
||||
corrected owner registry copies: `/tmp/netkingdom-improvement-loop-next-owners`.
|
||||
|
||||
After review/integration, verification uses existing commands:
|
||||
|
||||
```sh
|
||||
python3 -m pytest -q tests/test_index_parity.py tests/test_registry.py
|
||||
reuse-surface validate --root /home/worsch/net-kingdom --fail-on-warnings
|
||||
reuse-surface validate --root /home/worsch/railiance-bootstrap --fail-on-warnings
|
||||
```
|
||||
|
||||
The existing Activity schedule remains disabled. ACTIVITY-WP-0047-T02 specifically
|
||||
requires claiming-rein workstation checkout/hub acceptance, then owner release,
|
||||
admin-sync and manual trigger; T03 requires the first unassisted scheduled fire.
|
||||
The Oct15 rein candidate is rebuilt with startup proof but remains uninstalled
|
||||
and unadmitted in its owner record, with real-model acceptance open. This is its
|
||||
selected rein/profile dependency, not an invented dependency on completing the
|
||||
unrelated task-scoped protected operation. Exact source hashes are retained in
|
||||
`schedule-dependency-evidence.json`.
|
||||
|
||||
Kings Guard's cadence observer and Maturity Engine remain their existing owners;
|
||||
a deterministic registry projection check creates neither posture nor a second
|
||||
maturity assessor. The adopted coordination frame is 3600 seconds; source
|
||||
configuration does not establish source/receiver runtime admission. The NetKingdom
|
||||
knowledge pilot returned package 0.2.6, digest
|
||||
`8d1d6a9d4d07fec7b619618de7dfe2be2254c1790f85f4198c7fe19db9751c02`,
|
||||
withheld/orientation-only because sources changed. Current owner instructions,
|
||||
companion and source artifacts were reviewed directly for this bounded change.
|
||||
|
||||
Next authority gates: coordinator and affected-owner source review, normal owner
|
||||
publication and CI, then native hosted source refresh/readback if publication is
|
||||
admitted. TSA-11 remains open for full hosted accuracy/version/link requirements;
|
||||
TSA-12 remains open for accepted runtime activation, unassisted firing, checkpoint
|
||||
and changed-state delivery, owner remediation and native remeasurement. No
|
||||
production action, model spend, credential access or external message occurred.
|
||||
|
|
@ -0,0 +1,490 @@
|
|||
{
|
||||
"repo_slug": "railiance-bootstrap",
|
||||
"repo_path": "/home/worsch/railiance-bootstrap",
|
||||
"source_revision": "8d23103af2d5ff0da4eb5f612ea2bf09d8ff7344",
|
||||
"source_revision_reason": null,
|
||||
"issues": [
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager warning: .repo-classification.yaml: category is project but slug is not prj-*; not every category:project is a prj- repo",
|
||||
"file_path": ".repo-classification.yaml",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-1: task id 'T01' is not accepted by the canon kind registry",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-1",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-2: task id 'T02' is not accepted by the canon kind registry",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-2",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-3: task id 'T03' is not accepted by the canon kind registry",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-3",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-4: task id 'T04' is not accepted by the canon kind registry",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-4",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-5: task id 'T05' is not accepted by the canon kind registry",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-5",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md#task-block-1: task id 'T01' is not accepted by the canon kind registry",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md#task-block-1",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md#task-block-2: task id 'T02' is not accepted by the canon kind registry",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md#task-block-2",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md#task-block-3: task id 'T03' is not accepted by the canon kind registry",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md#task-block-3",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md#task-block-4: task id 'T04' is not accepted by the canon kind registry",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md#task-block-4",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-1: task id 'T01' is not accepted by the canon kind registry",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-1",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-2: task id 'T02' is not accepted by the canon kind registry",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-2",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-3: task id 'T03' is not accepted by the canon kind registry",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-3",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-4: task id 'T04' is not accepted by the canon kind registry",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-4",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-5: task id 'T05' is not accepted by the canon kind registry",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-5",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-6: task id 'T06' is not accepted by the canon kind registry",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-6",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: intakes/intakes.md#record-block-1: intake id 'RAIL-BS-IN-0001' is not accepted by the canon kind registry",
|
||||
"file_path": "intakes/intakes.md#record-block-1",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-1: task 'T01' has conflicting UUID assignments at ['workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-1', 'workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md#task-block-1', 'workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-1']",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-1",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-2: task 'T02' has conflicting UUID assignments at ['workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-2', 'workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md#task-block-2', 'workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-2']",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-2",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-3: task 'T03' has conflicting UUID assignments at ['workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-3', 'workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md#task-block-3', 'workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-3']",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-3",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-4: task 'T04' has conflicting UUID assignments at ['workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-4', 'workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md#task-block-4', 'workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-4']",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-4",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager contradictory: workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-5: task 'T05' has conflicting UUID assignments at ['workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-5', 'workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md#task-block-5']",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md#task-block-5",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-31",
|
||||
"message": "[intakes/intakes.md] id 'RAIL-BS-IN-0001' matches no kind in the canon work-record type registry \u2014 unregistered species become sidetracks needing manual reintegration (work-record-types_v0.1)",
|
||||
"file_path": "",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "FAIL",
|
||||
"check_id": "C-03",
|
||||
"message": "state_hub_workstream_id 7cbbe0d6\u2026 not found in DB (stale reference)",
|
||||
"file_path": "workplans/RAIL-BS-WP-0008-activity-core-wp0016-triage-output-deploy.md",
|
||||
"db_id": "7cbbe0d6-fea9-41c6-840c-46d0d8e8edde",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "FAIL",
|
||||
"check_id": "C-03",
|
||||
"message": "state_hub_workstream_id 2c9e8e96\u2026 not found in DB (stale reference)",
|
||||
"file_path": "workplans/RAIL-BS-WP-0009-activity-core-admin-sync-smoke.md",
|
||||
"db_id": "2c9e8e96-ec6a-433c-9e6d-0efbcd18679e",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "FAIL",
|
||||
"check_id": "C-03",
|
||||
"message": "state_hub_workstream_id 59155efb\u2026 not found in DB (stale reference)",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0001-dependency-management.md",
|
||||
"db_id": "59155efb-b461-4caa-ad7b-b3fce348db84",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "FAIL",
|
||||
"check_id": "C-03",
|
||||
"message": "state_hub_workstream_id 4c63dfc6\u2026 not found in DB (stale reference)",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md",
|
||||
"db_id": "4c63dfc6-9eac-4e79-9f77-8f644ad7147d",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "FAIL",
|
||||
"check_id": "C-03",
|
||||
"message": "state_hub_workstream_id 7ee9ee22\u2026 not found in DB (stale reference)",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md",
|
||||
"db_id": "7ee9ee22-1fae-4567-9194-8d70a9e0f45b",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "FAIL",
|
||||
"check_id": "C-03",
|
||||
"message": "state_hub_workstream_id 7e8b0c20\u2026 not found in DB (stale reference)",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md",
|
||||
"db_id": "7e8b0c20-51eb-40c9-9e3b-85dd380d7625",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "FAIL",
|
||||
"check_id": "C-03",
|
||||
"message": "state_hub_workstream_id b236de41\u2026 not found in DB (stale reference)",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0005-kubeconfig-delivery.md",
|
||||
"db_id": "b236de41-2f33-4ebc-bb84-5fcedb2982f8",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "FAIL",
|
||||
"check_id": "C-03",
|
||||
"message": "state_hub_workstream_id 6434f7cb\u2026 not found in DB (stale reference)",
|
||||
"file_path": "workplans/archived/260622-RAILIANCE-WP-0012-activity-core-cluster-owned-deploy-verify.md",
|
||||
"db_id": "6434f7cb-e13c-4c05-839b-197bb239d5cd",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "FAIL",
|
||||
"check_id": "C-03",
|
||||
"message": "state_hub_workstream_id a3abb83a\u2026 not found in DB (stale reference)",
|
||||
"file_path": "workplans/archived/260622-RAILIANCE-WP-0013-activity-core-verifier-evidence-hardening.md",
|
||||
"db_id": "a3abb83a-2d42-40f9-a5f6-1dbc36903436",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "FAIL",
|
||||
"check_id": "C-03",
|
||||
"message": "state_hub_workstream_id cb72d3ba\u2026 not found in DB (stale reference)",
|
||||
"file_path": "workplans/archived/260627-RAIL-BS-WP-0006-staged-promotion-lifecycle.md",
|
||||
"db_id": "cb72d3ba-1863-43c2-a2a5-49ac75fc2603",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "FAIL",
|
||||
"check_id": "C-03",
|
||||
"message": "state_hub_workstream_id a152ddda\u2026 not found in DB (stale reference)",
|
||||
"file_path": "workplans/archived/260701-RAILIANCE-WP-0014-activity-core-llm-connect-live-reconcile.md",
|
||||
"db_id": "a152ddda-d60a-4a65-9b9c-59e2db9ff2b7",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-28",
|
||||
"message": "9 unread inbox message(s) older than 3 day(s): prj-forgejo-org-refactor:37fe499a, prj-forgejo-org-refactor:5636187f, prj-forgejo-org-refactor:04d07b60, the-custodian:23172cb2, the-custodian:51e9eace, ...",
|
||||
"file_path": "",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-28",
|
||||
"message": "9 unread message(s) lack thread_id for supersession tracking: prj-forgejo-org-refactor:37fe499a, prj-forgejo-org-refactor:5636187f, prj-forgejo-org-refactor:04d07b60, the-custodian:23172cb2, the-custodian:51e9eace",
|
||||
"file_path": "",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'RAILIANCE-WP-0012' uses prefix 'RAILIANCE-WP' but repo canonical prefix is 'RAIL-BS-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/archived/260622-RAILIANCE-WP-0012-activity-core-cluster-owned-deploy-verify.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0012",
|
||||
"db_value": "RAIL-BS-WP",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'RAILIANCE-WP-0013' uses prefix 'RAILIANCE-WP' but repo canonical prefix is 'RAIL-BS-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/archived/260622-RAILIANCE-WP-0013-activity-core-verifier-evidence-hardening.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0013",
|
||||
"db_value": "RAIL-BS-WP",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'RAILIANCE-WP-0014' uses prefix 'RAILIANCE-WP' but repo canonical prefix is 'RAIL-BS-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/archived/260701-RAILIANCE-WP-0014-activity-core-llm-connect-live-reconcile.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0014",
|
||||
"db_value": "RAIL-BS-WP",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-27",
|
||||
"message": "Workplan id 'RAIL-BS-WP-0008' collides across repos: railiance-bootstrap/RAIL-BS-WP-0008-activity-core-wp0016-triage-output-deploy.md, railiance-cluster/RAIL-BS-WP-0008-activity-core-wp0016-triage-output-deploy.md",
|
||||
"file_path": "workplans/RAIL-BS-WP-0008-activity-core-wp0016-triage-output-deploy.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAIL-BS-WP-0008",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-27",
|
||||
"message": "Workplan id 'RAIL-BS-WP-0009' collides across repos: railiance-bootstrap/RAIL-BS-WP-0009-activity-core-admin-sync-smoke.md, railiance-cluster/RAIL-BS-WP-0009-activity-core-admin-sync-smoke.md",
|
||||
"file_path": "workplans/RAIL-BS-WP-0009-activity-core-admin-sync-smoke.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAIL-BS-WP-0009",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-27",
|
||||
"message": "Workplan id 'RAIL-BS-WP-0001' collides across repos: railiance-bootstrap/260622-RAIL-BS-WP-0001-dependency-management.md, railiance-cluster/260622-RAIL-BS-WP-0001-dependency-management.md",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0001-dependency-management.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAIL-BS-WP-0001",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-27",
|
||||
"message": "Workplan id 'RAIL-BS-WP-0002' collides across repos: railiance-bootstrap/260622-RAIL-BS-WP-0002-k3s-baseline.md, railiance-cluster/260622-RAIL-BS-WP-0002-k3s-baseline.md",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAIL-BS-WP-0002",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-27",
|
||||
"message": "Workplan id 'RAIL-BS-WP-0003' collides across repos: railiance-bootstrap/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md, railiance-cluster/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAIL-BS-WP-0003",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-27",
|
||||
"message": "Workplan id 'RAIL-BS-WP-0004' collides across repos: railiance-bootstrap/260622-RAIL-BS-WP-0004-safety-net.md, railiance-cluster/260622-RAIL-BS-WP-0004-safety-net.md",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAIL-BS-WP-0004",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-27",
|
||||
"message": "Workplan id 'RAIL-BS-WP-0005' collides across repos: railiance-bootstrap/260622-RAIL-BS-WP-0005-kubeconfig-delivery.md, railiance-cluster/260622-RAIL-BS-WP-0005-kubeconfig-delivery.md",
|
||||
"file_path": "workplans/archived/260622-RAIL-BS-WP-0005-kubeconfig-delivery.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAIL-BS-WP-0005",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-27",
|
||||
"message": "Workplan id 'RAIL-BS-WP-0006' collides across repos: railiance-bootstrap/260627-RAIL-BS-WP-0006-staged-promotion-lifecycle.md, railiance-cluster/260627-RAIL-BS-WP-0006-staged-promotion-lifecycle.md",
|
||||
"file_path": "workplans/archived/260627-RAIL-BS-WP-0006-staged-promotion-lifecycle.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAIL-BS-WP-0006",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
}
|
||||
],
|
||||
"fixes_applied": [
|
||||
"bindings: synced 12/12",
|
||||
"C-17 cleared: pushed 1 backlogged commit(s)",
|
||||
"push: pushed"
|
||||
],
|
||||
"summary": {
|
||||
"fail": 11,
|
||||
"automation_error": 0,
|
||||
"warn": 36,
|
||||
"info": 0
|
||||
},
|
||||
"result": "fail"
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,131 @@
|
|||
{
|
||||
"actual_owner_before_after": {
|
||||
"net-kingdom": {
|
||||
"old_validator": {
|
||||
"command": [
|
||||
"python3",
|
||||
"-m",
|
||||
"reuse_surface.cli",
|
||||
"validate",
|
||||
"--root",
|
||||
"/home/worsch/net-kingdom",
|
||||
"--fail-on-warnings"
|
||||
],
|
||||
"cwd": "/home/worsch/reuse-surface",
|
||||
"exit_code": 0,
|
||||
"stdout": "ok: validated 6 capability entries\n",
|
||||
"stderr": ""
|
||||
},
|
||||
"candidate_validator": {
|
||||
"command": [
|
||||
"python3",
|
||||
"-m",
|
||||
"reuse_surface.cli",
|
||||
"validate",
|
||||
"--root",
|
||||
"/home/worsch/net-kingdom",
|
||||
"--fail-on-warnings"
|
||||
],
|
||||
"cwd": "/tmp/netkingdom-improvement-loop-next-reuse",
|
||||
"exit_code": 1,
|
||||
"stdout": "",
|
||||
"stderr": "warning: index drift: registry/capabilities/capability.security.bootstrap-recovery.md: summary differs from entry\n"
|
||||
},
|
||||
"corrected_temporary_index": {
|
||||
"command": [
|
||||
"python3",
|
||||
"-m",
|
||||
"reuse_surface.cli",
|
||||
"validate",
|
||||
"--root",
|
||||
"/tmp/netkingdom-improvement-loop-next-owners/net-kingdom",
|
||||
"--fail-on-warnings"
|
||||
],
|
||||
"cwd": "/tmp/netkingdom-improvement-loop-next-reuse",
|
||||
"exit_code": 0,
|
||||
"stdout": "ok: validated 6 capability entries\n",
|
||||
"stderr": ""
|
||||
}
|
||||
},
|
||||
"railiance-bootstrap": {
|
||||
"old_validator": {
|
||||
"command": [
|
||||
"python3",
|
||||
"-m",
|
||||
"reuse_surface.cli",
|
||||
"validate",
|
||||
"--root",
|
||||
"/home/worsch/railiance-bootstrap",
|
||||
"--fail-on-warnings"
|
||||
],
|
||||
"cwd": "/home/worsch/reuse-surface",
|
||||
"exit_code": 0,
|
||||
"stdout": "ok: validated 1 capability entry\n",
|
||||
"stderr": ""
|
||||
},
|
||||
"candidate_validator": {
|
||||
"command": [
|
||||
"python3",
|
||||
"-m",
|
||||
"reuse_surface.cli",
|
||||
"validate",
|
||||
"--root",
|
||||
"/home/worsch/railiance-bootstrap",
|
||||
"--fail-on-warnings"
|
||||
],
|
||||
"cwd": "/tmp/netkingdom-improvement-loop-next-reuse",
|
||||
"exit_code": 1,
|
||||
"stdout": "",
|
||||
"stderr": "warning: index drift: registry/capabilities/capability.railiance.bootstrap-sequencing.md: summary differs from entry\nwarning: index drift: registry/capabilities/capability.railiance.bootstrap-sequencing.md: tags differs from entry\n"
|
||||
},
|
||||
"corrected_temporary_index": {
|
||||
"command": [
|
||||
"python3",
|
||||
"-m",
|
||||
"reuse_surface.cli",
|
||||
"validate",
|
||||
"--root",
|
||||
"/tmp/netkingdom-improvement-loop-next-owners/railiance-bootstrap",
|
||||
"--fail-on-warnings"
|
||||
],
|
||||
"cwd": "/tmp/netkingdom-improvement-loop-next-reuse",
|
||||
"exit_code": 0,
|
||||
"stdout": "ok: validated 1 capability entry\n",
|
||||
"stderr": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"synthetic_duplicate": {
|
||||
"old_validator": {
|
||||
"command": [
|
||||
"python3",
|
||||
"-m",
|
||||
"reuse_surface.cli",
|
||||
"validate",
|
||||
"--root",
|
||||
"/tmp/netkingdom-improvement-loop-next-duplicate",
|
||||
"--fail-on-warnings"
|
||||
],
|
||||
"cwd": "/home/worsch/reuse-surface",
|
||||
"exit_code": 0,
|
||||
"stdout": "ok: validated 1 capability entry\n",
|
||||
"stderr": ""
|
||||
},
|
||||
"candidate_validator": {
|
||||
"command": [
|
||||
"python3",
|
||||
"-m",
|
||||
"reuse_surface.cli",
|
||||
"validate",
|
||||
"--root",
|
||||
"/tmp/netkingdom-improvement-loop-next-duplicate",
|
||||
"--fail-on-warnings"
|
||||
],
|
||||
"cwd": "/tmp/netkingdom-improvement-loop-next-reuse",
|
||||
"exit_code": 1,
|
||||
"stdout": "",
|
||||
"stderr": "warning: index drift: duplicate id: capability.infotech.headless-browser (2 rows)\nwarning: index drift: duplicate path: registry/capabilities/capability.infotech.headless-browser.md (2 rows)\n"
|
||||
}
|
||||
},
|
||||
"proof_boundary": "Actual local source CLI behavior and corrected temporary owner indexes only. No owner checkout, hosted registration, schedule, model spend, credential or native acceptance change."
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
--- a/registry/indexes/capabilities.yaml
|
||||
+++ b/registry/indexes/capabilities.yaml
|
||||
@@ -67,8 +67,8 @@
|
||||
- source
|
||||
- id: capability.security.bootstrap-recovery
|
||||
name: Guarded bootstrap and recovery preparation
|
||||
- summary: Encrypted identity backup, independent retrieval and isolated recovery
|
||||
- verification with bootstrap handoff.
|
||||
+ summary: Guarded bootstrap custody, encrypted identity backup capture, independent
|
||||
+ retrieval and isolated recovery verification.
|
||||
domain: infotech
|
||||
status: draft
|
||||
owner: net-kingdom
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"commit": "0e298813c7d45ada98195bb1899f334b14612b06",
|
||||
"status": "MM .custodian-brief.md\n M WORK-RECORDS.md\n?? docs/evidence/2026-10-15-cadence-adoption/hourly-observation-capture/\n?? docs/evidence/2026-10-15-cadence-adoption/hourly-observation-inventory.jsonl\n?? docs/evidence/2026-10-15-cadence-adoption/hourly-observation-monitor.json\n?? intakes/NET-IN-0005-single-use-setup-link-mail-handoff.md\n",
|
||||
"index_sha256": "065f08f68df57cec83b1025c56cdfebfbe091a8593bb3b33e355dea92a6873a3"
|
||||
}
|
||||
|
|
@ -0,0 +1,364 @@
|
|||
{
|
||||
"repo_slug": "net-kingdom",
|
||||
"repo_path": "/home/worsch/net-kingdom",
|
||||
"source_revision": "015694ee414c802dc2ef1fa0120dfed610086e69",
|
||||
"source_revision_reason": null,
|
||||
"issues": [
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-24",
|
||||
"message": "Repo classification advisory: capability_tag 'security' is not a recommended family tag (allowed, check for synonym)",
|
||||
"file_path": "",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager warning: workplans/: repository uses more than one workplan prefix: ['NET-WP', 'NK-WP']",
|
||||
"file_path": "workplans/",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-33",
|
||||
"message": "WORK-RECORDS.md is missing or stale \u2014 regenerate via fix-consistency (CUST-WP-0061-T04 generated work-record index)",
|
||||
"file_path": "WORK-RECORDS.md",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": true
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-37",
|
||||
"message": "Residual workplan 'nk-wp-0011' has no origin/origin_ref provenance (not invented)",
|
||||
"file_path": "workplans/NK-WP-0011-enterprise-federation-saml.md",
|
||||
"db_id": "1075448f-d533-5f9e-94b7-c3adfe151a07",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'USER-WP-0020' is not linked to State Hub",
|
||||
"file_path": "workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md",
|
||||
"db_id": "",
|
||||
"file_value": "USER-WP-0020",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'KEY-WP-0004' is not linked to State Hub",
|
||||
"file_path": "workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md",
|
||||
"db_id": "",
|
||||
"file_value": "KEY-WP-0004",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'USER-WP-0020' is not linked to State Hub",
|
||||
"file_path": "workplans/NK-WP-0023-user-engine-portal-platform-integration.md",
|
||||
"db_id": "",
|
||||
"file_value": "USER-WP-0020",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'KEY-WP-0004' is not linked to State Hub",
|
||||
"file_path": "workplans/NK-WP-0023-user-engine-portal-platform-integration.md",
|
||||
"db_id": "",
|
||||
"file_value": "KEY-WP-0004",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'USER-WP-0021' is not linked to State Hub",
|
||||
"file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md",
|
||||
"db_id": "",
|
||||
"file_value": "USER-WP-0021",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'FLEX-WP-0009' is not linked to State Hub",
|
||||
"file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md",
|
||||
"db_id": "",
|
||||
"file_value": "FLEX-WP-0009",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'AUDIT-WP-0003' is not linked to State Hub",
|
||||
"file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md",
|
||||
"db_id": "",
|
||||
"file_value": "AUDIT-WP-0003",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'EMAIL-WP-0004' is not linked to State Hub",
|
||||
"file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md",
|
||||
"db_id": "",
|
||||
"file_value": "EMAIL-WP-0004",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'USER-WP-0022' is not linked to State Hub",
|
||||
"file_path": "workplans/NK-WP-0025-public-self-registration-and-application-jit.md",
|
||||
"db_id": "",
|
||||
"file_value": "USER-WP-0022",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'KEY-WP-0008' is not linked to State Hub",
|
||||
"file_path": "workplans/NK-WP-0025-public-self-registration-and-application-jit.md",
|
||||
"db_id": "",
|
||||
"file_value": "KEY-WP-0008",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'CSOC-WP-0003' is not linked to State Hub",
|
||||
"file_path": "workplans/NK-WP-0025-public-self-registration-and-application-jit.md",
|
||||
"db_id": "",
|
||||
"file_value": "CSOC-WP-0003",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-37",
|
||||
"message": "Residual workplan 'nk-wp-0027' has no origin/origin_ref provenance (not invented)",
|
||||
"file_path": "workplans/NK-WP-0027-reef-placement-reconciliation.md",
|
||||
"db_id": "965ad365-6b81-50a1-a2a3-2d0c1fcce0b4",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-37",
|
||||
"message": "Residual workplan 'nk-wp-0031' has no origin/origin_ref provenance (not invented)",
|
||||
"file_path": "workplans/NK-WP-0031-deterministic-posture-feedback.md",
|
||||
"db_id": "9d7b04f9-3803-5613-b7a5-8bd606c77f5a",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-37",
|
||||
"message": "Residual workplan 'nk-wp-0040' has no origin/origin_ref provenance (not invented)",
|
||||
"file_path": "workplans/NK-WP-0040-execution-attribution-receipt.md",
|
||||
"db_id": "e2533f3a-aa43-59b3-bff3-8e64b6149487",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Task dependency target 'NK-WP-0048-T12' of task 'NK-WP-0048-T06' is not linked to State Hub",
|
||||
"file_path": "workplans/NK-WP-0048-security-case-types.md#NK-WP-0048-T06",
|
||||
"db_id": "",
|
||||
"file_value": "NK-WP-0048-T12",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-11",
|
||||
"message": "Task 'NK-WP-0048-T10' has no state_hub_task_id",
|
||||
"file_path": "workplans/NK-WP-0048-security-case-types.md#NK-WP-0048-T10",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": true
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-11",
|
||||
"message": "Task 'NK-WP-0048-T11' has no state_hub_task_id",
|
||||
"file_path": "workplans/NK-WP-0048-security-case-types.md#NK-WP-0048-T11",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": true
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-11",
|
||||
"message": "Task 'NK-WP-0048-T12' has no state_hub_task_id",
|
||||
"file_path": "workplans/NK-WP-0048-security-case-types.md#NK-WP-0048-T12",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": true
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAIL-PL-WP-0002' is not linked to State Hub",
|
||||
"file_path": "workplans/archived/260702-NET-WP-0017-it-security-readiness-for-user-onboarding.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAIL-PL-WP-0002",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'nk-wp-0035@retired-20260908' (id=064c5e8b\u2026, status=archived) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "064c5e8b-7852-4d7f-a5fa-e26e406ab8e0",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'adhoc-2026-08-14@retired-20260827' (id=a5097121\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "a5097121-1461-59f8-81fa-e6ecef527337",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'net-kingdom-adhoc-2026-06-14@retired-20260827' (id=c6f3d6bf\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "c6f3d6bf-4916-490d-96ce-092d2776d7e7",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'net-kingdom-adhoc-2026-07-02@retired-20260827' (id=67c1c7ac\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "67c1c7ac-d8b1-41dc-a81a-4e43f1afd068",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'adhoc-2026-08-23@retired-20260827' (id=e25ba5fa\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "e25ba5fa-eaae-5039-8d93-a69c6d837fc4",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'NET-WP-0020' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md",
|
||||
"db_id": "",
|
||||
"file_value": "NET-WP-0020",
|
||||
"db_value": "NK-WP",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'NET-WP-0015' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/archived/260603-NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md",
|
||||
"db_id": "",
|
||||
"file_value": "NET-WP-0015",
|
||||
"db_value": "NK-WP",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'NET-WP-0016' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/archived/260603-NET-WP-0016-guided-security-bootstrap-experience.md",
|
||||
"db_id": "",
|
||||
"file_value": "NET-WP-0016",
|
||||
"db_value": "NK-WP",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'NET-WP-0018' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/archived/260604-NET-WP-0018-bootstrap-automation-and-rebuild-readiness.md",
|
||||
"db_id": "",
|
||||
"file_value": "NET-WP-0018",
|
||||
"db_value": "NK-WP",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'NET-WP-0017' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/archived/260702-NET-WP-0017-it-security-readiness-for-user-onboarding.md",
|
||||
"db_id": "",
|
||||
"file_value": "NET-WP-0017",
|
||||
"db_value": "NK-WP",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'NET-WP-0019' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/archived/260702-NET-WP-0019-t06-adjacent-user-lifecycle-dry-run-polish.md",
|
||||
"db_id": "",
|
||||
"file_value": "NET-WP-0019",
|
||||
"db_value": "NK-WP",
|
||||
"fixable": false
|
||||
}
|
||||
],
|
||||
"fixes_applied": [
|
||||
"bindings: synced 64/64",
|
||||
"C-33 fixed: regenerated WORK-RECORDS.md",
|
||||
"C-11 fixed: task 'NK-WP-0048-T10' \u2192 4b06ca97\u2026",
|
||||
"C-11 fixed: task 'NK-WP-0048-T11' \u2192 ef2a0119\u2026",
|
||||
"C-11 fixed: task 'NK-WP-0048-T12' \u2192 7ce4eee3\u2026",
|
||||
"brief: .custodian-brief.md updated",
|
||||
"push: pushed"
|
||||
],
|
||||
"summary": {
|
||||
"fail": 0,
|
||||
"automation_error": 0,
|
||||
"warn": 29,
|
||||
"info": 5
|
||||
},
|
||||
"result": "warn"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
WARN: git commit failed for writeback: Generated-only commits rejected: staged
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
[
|
||||
{
|
||||
"owner": "net-kingdom",
|
||||
"capability_id": "capability.security.bootstrap-recovery",
|
||||
"field": "summary",
|
||||
"index": "Encrypted identity backup, independent retrieval and isolated recovery verification with bootstrap handoff.",
|
||||
"entry": "Guarded bootstrap custody, encrypted identity backup capture, independent retrieval and isolated recovery verification.",
|
||||
"entry_path": "registry/capabilities/capability.security.bootstrap-recovery.md",
|
||||
"entry_sha256": "3dc7a15170af69f02ade2df7723aae8cf9164c20c47aff1ffa5abc82a480cdf6"
|
||||
},
|
||||
{
|
||||
"owner": "railiance-bootstrap",
|
||||
"capability_id": "capability.railiance.bootstrap-sequencing",
|
||||
"field": "summary",
|
||||
"index": "Bare-VM sequencing with NetKingdom identity backup recovery handoff.",
|
||||
"entry": "Bare-VM to estate sequence and handoff contract.",
|
||||
"entry_path": "registry/capabilities/capability.railiance.bootstrap-sequencing.md",
|
||||
"entry_sha256": "f0066cc31c3a39388827684ec016a94b007c22df18d03553b4dd118c9c7cf5a7"
|
||||
},
|
||||
{
|
||||
"owner": "railiance-bootstrap",
|
||||
"capability_id": "capability.railiance.bootstrap-sequencing",
|
||||
"field": "tags",
|
||||
"index": [
|
||||
"security",
|
||||
"netkingdom",
|
||||
"framework-reuse",
|
||||
"backup",
|
||||
"restore",
|
||||
"disaster-recovery"
|
||||
],
|
||||
"entry": [
|
||||
"security",
|
||||
"netkingdom",
|
||||
"framework-reuse"
|
||||
],
|
||||
"entry_path": "registry/capabilities/capability.railiance.bootstrap-sequencing.md",
|
||||
"entry_sha256": "f0066cc31c3a39388827684ec016a94b007c22df18d03553b4dd118c9c7cf5a7"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"reuse-surface": {
|
||||
"url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/reuse-surface/actions/runs?limit=10",
|
||||
"status": 404,
|
||||
"ci_acceptance": "unverified"
|
||||
},
|
||||
"net-kingdom": {
|
||||
"url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/net-kingdom/actions/runs?limit=10",
|
||||
"status": 404,
|
||||
"ci_acceptance": "unverified"
|
||||
},
|
||||
"railiance-bootstrap": {
|
||||
"url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/railiance-bootstrap/actions/runs?limit=10",
|
||||
"status": 404,
|
||||
"ci_acceptance": "unverified"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
--- a/registry/indexes/capabilities.yaml
|
||||
+++ b/registry/indexes/capabilities.yaml
|
||||
@@ -4,7 +4,7 @@
|
||||
capabilities:
|
||||
- id: capability.railiance.bootstrap-sequencing
|
||||
name: Bootstrap sequencing contracts
|
||||
- summary: Bare-VM sequencing with NetKingdom identity backup recovery handoff.
|
||||
+ summary: Bare-VM to estate sequence and handoff contract.
|
||||
domain: infotech
|
||||
status: draft
|
||||
owner: railiance-bootstrap
|
||||
@@ -12,9 +12,6 @@
|
||||
- security
|
||||
- netkingdom
|
||||
- framework-reuse
|
||||
- - backup
|
||||
- - restore
|
||||
- - disaster-recovery
|
||||
vector: D3 / A0 / C1 / R0
|
||||
path: registry/capabilities/capability.railiance.bootstrap-sequencing.md
|
||||
consumption_modes:
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"commit": "d1dcfde527afde15a6fbe7ae65a2d94b70c3c263",
|
||||
"status": "",
|
||||
"index_sha256": "abb5f57df8f01e4184dbcd0ed5f75d271e2eaef7e95a3753d3008d01009947c6"
|
||||
}
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
diff --git a/reuse_surface/cli.py b/reuse_surface/cli.py
|
||||
index 39ba2de..5c47322 100644
|
||||
--- a/reuse_surface/cli.py
|
||||
+++ b/reuse_surface/cli.py
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
+from collections import Counter
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -87,6 +88,7 @@ from reuse_surface.registry import (
|
||||
parse_vector,
|
||||
registry_paths,
|
||||
resolve_repo_root,
|
||||
+ vectors_match,
|
||||
)
|
||||
|
||||
|
||||
@@ -114,12 +116,48 @@ def _check_index_drift(
|
||||
repo_root: Path,
|
||||
) -> list[str]:
|
||||
warnings: list[str] = []
|
||||
- indexed_paths = {item["path"] for item in index.get("capabilities", [])}
|
||||
+ if not isinstance(index, dict) or not isinstance(index.get("capabilities"), list):
|
||||
+ return ["index drift: index must contain a capabilities list"]
|
||||
+ rows = []
|
||||
+ for position, row in enumerate(index["capabilities"]):
|
||||
+ if not isinstance(row, dict) or any(
|
||||
+ not isinstance(row.get(field), str) or not row[field]
|
||||
+ for field in ("id", "path")
|
||||
+ ):
|
||||
+ warnings.append(f"index drift: row {position} requires nonempty string id and path")
|
||||
+ continue
|
||||
+ rows.append(row)
|
||||
+ indexed_paths = {item["path"] for item in rows}
|
||||
file_paths = {str(path.relative_to(repo_root)) for path in entry_paths}
|
||||
for path in sorted(file_paths - indexed_paths):
|
||||
warnings.append(f"index drift: entry file not indexed: {path}")
|
||||
for path in sorted(indexed_paths - file_paths):
|
||||
warnings.append(f"index drift: index references missing file: {path}")
|
||||
+ for field in ("id", "path"):
|
||||
+ for value, count in sorted(Counter(row[field] for row in rows).items()):
|
||||
+ if count > 1:
|
||||
+ warnings.append(f"index drift: duplicate {field}: {value} ({count} rows)")
|
||||
+ for row in rows:
|
||||
+ path = repo_root / row["path"]
|
||||
+ if not path.is_file():
|
||||
+ continue
|
||||
+ try:
|
||||
+ entry = parse_front_matter(path)
|
||||
+ except (ValueError, yaml.YAMLError):
|
||||
+ continue # The entry validation pass reports malformed source.
|
||||
+ # The index projects the owner entry; index-only changes cannot replace it.
|
||||
+ for field in ("id", "name", "summary", "domain", "status", "owner", "tags"):
|
||||
+ if row.get(field) != entry.get(field):
|
||||
+ warnings.append(f"index drift: {row['path']}: {field} differs from entry")
|
||||
+ availability = entry.get("availability")
|
||||
+ modes = availability.get("consumption_modes") if isinstance(availability, dict) else None
|
||||
+ if row.get("consumption_modes") != modes:
|
||||
+ warnings.append(f"index drift: {row['path']}: consumption_modes differs from entry")
|
||||
+ try:
|
||||
+ if not vectors_match(row["vector"], entry):
|
||||
+ warnings.append(f"index drift: {row['path']}: vector differs from entry")
|
||||
+ except (KeyError, TypeError, AttributeError):
|
||||
+ warnings.append(f"index drift: {row['path']}: vector cannot be compared to entry")
|
||||
return warnings
|
||||
|
||||
|
||||
@@ -145,7 +183,7 @@ def _run_validate(
|
||||
for path in paths:
|
||||
try:
|
||||
data = parse_front_matter(path)
|
||||
- except ValueError as exc:
|
||||
+ except (ValueError, yaml.YAMLError) as exc:
|
||||
errors.append(str(exc))
|
||||
continue
|
||||
for error in sorted(validator.iter_errors(data), key=lambda e: e.path):
|
||||
@@ -155,8 +193,12 @@ def _run_validate(
|
||||
if not target:
|
||||
index_path = registry_paths(repo_root)["index"]
|
||||
if index_path.exists():
|
||||
- index = load_index_at(index_path)
|
||||
- warnings.extend(_check_index_drift(paths, index, repo_root))
|
||||
+ try:
|
||||
+ index = load_index_at(index_path)
|
||||
+ except (ValueError, yaml.YAMLError) as exc:
|
||||
+ errors.append(f"{index_path}: {exc}")
|
||||
+ else:
|
||||
+ warnings.extend(_check_index_drift(paths, index, repo_root))
|
||||
if relations and repo_root == ROOT:
|
||||
warnings.extend(check_relations())
|
||||
return errors, warnings, paths
|
||||
diff --git a/tests/test_index_parity.py b/tests/test_index_parity.py
|
||||
new file mode 100644
|
||||
index 0000000..3a2774b
|
||||
--- /dev/null
|
||||
+++ b/tests/test_index_parity.py
|
||||
@@ -0,0 +1,91 @@
|
||||
+"""Exercise the same strict native validator used by the existing Forgejo job."""
|
||||
+from __future__ import annotations
|
||||
+
|
||||
+import copy
|
||||
+import subprocess
|
||||
+import sys
|
||||
+from pathlib import Path
|
||||
+
|
||||
+import pytest
|
||||
+import yaml
|
||||
+
|
||||
+ROOT = Path(__file__).resolve().parent.parent
|
||||
+
|
||||
+
|
||||
+def fixture_registry(tmp_path):
|
||||
+ index = yaml.safe_load((ROOT / "registry/indexes/capabilities.yaml").read_text())
|
||||
+ row = copy.deepcopy(index["capabilities"][0])
|
||||
+ source = ROOT / row["path"]
|
||||
+ target = tmp_path / row["path"]
|
||||
+ target.parent.mkdir(parents=True)
|
||||
+ target.write_bytes(source.read_bytes())
|
||||
+ index["capabilities"] = [row]
|
||||
+ index_path = tmp_path / "registry/indexes/capabilities.yaml"
|
||||
+ index_path.parent.mkdir(parents=True)
|
||||
+ return index_path, index
|
||||
+
|
||||
+
|
||||
+def validate(root):
|
||||
+ return subprocess.run(
|
||||
+ [sys.executable, "-m", "reuse_surface.cli", "validate", "--root", str(root),
|
||||
+ "--fail-on-warnings"], cwd=ROOT, capture_output=True, text=True,
|
||||
+ )
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("field,value", [
|
||||
+ ("id", "capability.synthetic.stale"), ("name", "stale name"),
|
||||
+ ("summary", "stale summary"), ("domain", "stale-domain"),
|
||||
+ ("status", "retired"), ("owner", "wrong-owner"),
|
||||
+ ("tags", ["stale"]), ("consumption_modes", ["stale"]),
|
||||
+ ("vector", "D0 / A0 / C0 / R0"),
|
||||
+])
|
||||
+def test_existing_strict_validator_refuses_stale_projection(tmp_path, field, value):
|
||||
+ path, index = fixture_registry(tmp_path)
|
||||
+ path.write_text(yaml.safe_dump(index))
|
||||
+ assert validate(tmp_path).returncode == 0
|
||||
+ original_entry = (tmp_path / index["capabilities"][0]["path"]).read_bytes()
|
||||
+ index["capabilities"][0][field] = value
|
||||
+ path.write_text(yaml.safe_dump(index))
|
||||
+ result = validate(tmp_path)
|
||||
+ assert result.returncode == 1, result.stdout + result.stderr
|
||||
+ assert f"{field} differs from entry" in result.stderr
|
||||
+ assert (tmp_path / index["capabilities"][0]["path"]).read_bytes() == original_entry
|
||||
+
|
||||
+
|
||||
+def test_duplicate_id_and_path_cannot_satisfy_exactly_once(tmp_path):
|
||||
+ path, index = fixture_registry(tmp_path)
|
||||
+ index["capabilities"].append(copy.deepcopy(index["capabilities"][0]))
|
||||
+ path.write_text(yaml.safe_dump(index))
|
||||
+ result = validate(tmp_path)
|
||||
+ assert result.returncode == 1
|
||||
+ assert "duplicate id:" in result.stderr
|
||||
+ assert "duplicate path:" in result.stderr
|
||||
+
|
||||
+
|
||||
+def test_equivalent_vector_whitespace_is_allowed(tmp_path):
|
||||
+ path, index = fixture_registry(tmp_path)
|
||||
+ index["capabilities"][0]["vector"] = index["capabilities"][0]["vector"].replace(" ", "")
|
||||
+ path.write_text(yaml.safe_dump(index))
|
||||
+ assert validate(tmp_path).returncode == 0
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("source", ["null", "[]", "availability: null", "availability: []", "availability: ["])
|
||||
+def test_malformed_frontmatter_returns_failure_without_traceback(tmp_path, source):
|
||||
+ path, index = fixture_registry(tmp_path)
|
||||
+ path.write_text(yaml.safe_dump(index))
|
||||
+ (tmp_path / index["capabilities"][0]["path"]).write_text("---\n" + source + "\n---\n")
|
||||
+ result = validate(tmp_path)
|
||||
+ assert result.returncode == 1
|
||||
+ assert "Traceback" not in result.stderr
|
||||
+ assert "error:" in result.stderr
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("rows", [None, {}, [None], [[]], [{}], [{"id": [], "path": "x"}], [{"id": "x", "path": None}]])
|
||||
+def test_unvalidated_index_shape_returns_failure_without_traceback(tmp_path, rows):
|
||||
+ path, index = fixture_registry(tmp_path)
|
||||
+ index["capabilities"] = rows
|
||||
+ path.write_text(yaml.safe_dump(index))
|
||||
+ result = validate(tmp_path)
|
||||
+ assert result.returncode == 1
|
||||
+ assert "Traceback" not in result.stderr
|
||||
+ assert "index drift:" in result.stderr
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
--- a/reuse_surface/cli.py
|
||||
+++ b/reuse_surface/cli.py
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
+from collections import Counter
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -87,6 +88,7 @@
|
||||
parse_vector,
|
||||
registry_paths,
|
||||
resolve_repo_root,
|
||||
+ vectors_match,
|
||||
)
|
||||
|
||||
|
||||
@@ -120,6 +122,31 @@
|
||||
warnings.append(f"index drift: entry file not indexed: {path}")
|
||||
for path in sorted(indexed_paths - file_paths):
|
||||
warnings.append(f"index drift: index references missing file: {path}")
|
||||
+ rows = index.get("capabilities", [])
|
||||
+ for field in ("id", "path"):
|
||||
+ for value, count in sorted(Counter(row[field] for row in rows).items()):
|
||||
+ if count > 1:
|
||||
+ warnings.append(f"index drift: duplicate {field}: {value} ({count} rows)")
|
||||
+ for row in rows:
|
||||
+ path = repo_root / row["path"]
|
||||
+ if not path.is_file():
|
||||
+ continue
|
||||
+ try:
|
||||
+ entry = parse_front_matter(path)
|
||||
+ except (ValueError, yaml.YAMLError):
|
||||
+ continue # The entry validation pass reports malformed source.
|
||||
+ # The index projects the owner entry; index-only changes cannot replace it.
|
||||
+ for field in ("id", "name", "summary", "domain", "status", "owner", "tags"):
|
||||
+ if row.get(field) != entry.get(field):
|
||||
+ warnings.append(f"index drift: {row['path']}: {field} differs from entry")
|
||||
+ modes = entry.get("availability", {}).get("consumption_modes")
|
||||
+ if row.get("consumption_modes") != modes:
|
||||
+ warnings.append(f"index drift: {row['path']}: consumption_modes differs from entry")
|
||||
+ try:
|
||||
+ if not vectors_match(row["vector"], entry):
|
||||
+ warnings.append(f"index drift: {row['path']}: vector differs from entry")
|
||||
+ except (KeyError, TypeError, AttributeError):
|
||||
+ warnings.append(f"index drift: {row['path']}: vector cannot be compared to entry")
|
||||
return warnings
|
||||
|
||||
|
||||
--- /dev/null
|
||||
+++ b/tests/test_index_parity.py
|
||||
@@ -0,0 +1,69 @@
|
||||
+"""Exercise the same strict native validator used by the existing Forgejo job."""
|
||||
+from __future__ import annotations
|
||||
+
|
||||
+import copy
|
||||
+import subprocess
|
||||
+import sys
|
||||
+from pathlib import Path
|
||||
+
|
||||
+import pytest
|
||||
+import yaml
|
||||
+
|
||||
+ROOT = Path(__file__).resolve().parent.parent
|
||||
+
|
||||
+
|
||||
+def fixture_registry(tmp_path):
|
||||
+ index = yaml.safe_load((ROOT / "registry/indexes/capabilities.yaml").read_text())
|
||||
+ row = copy.deepcopy(index["capabilities"][0])
|
||||
+ source = ROOT / row["path"]
|
||||
+ target = tmp_path / row["path"]
|
||||
+ target.parent.mkdir(parents=True)
|
||||
+ target.write_bytes(source.read_bytes())
|
||||
+ index["capabilities"] = [row]
|
||||
+ index_path = tmp_path / "registry/indexes/capabilities.yaml"
|
||||
+ index_path.parent.mkdir(parents=True)
|
||||
+ return index_path, index
|
||||
+
|
||||
+
|
||||
+def validate(root):
|
||||
+ return subprocess.run(
|
||||
+ [sys.executable, "-m", "reuse_surface.cli", "validate", "--root", str(root),
|
||||
+ "--fail-on-warnings"], cwd=ROOT, capture_output=True, text=True,
|
||||
+ )
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("field,value", [
|
||||
+ ("id", "capability.synthetic.stale"), ("name", "stale name"),
|
||||
+ ("summary", "stale summary"), ("domain", "stale-domain"),
|
||||
+ ("status", "retired"), ("owner", "wrong-owner"),
|
||||
+ ("tags", ["stale"]), ("consumption_modes", ["stale"]),
|
||||
+ ("vector", "D0 / A0 / C0 / R0"),
|
||||
+])
|
||||
+def test_existing_strict_validator_refuses_stale_projection(tmp_path, field, value):
|
||||
+ path, index = fixture_registry(tmp_path)
|
||||
+ path.write_text(yaml.safe_dump(index))
|
||||
+ assert validate(tmp_path).returncode == 0
|
||||
+ original_entry = (tmp_path / index["capabilities"][0]["path"]).read_bytes()
|
||||
+ index["capabilities"][0][field] = value
|
||||
+ path.write_text(yaml.safe_dump(index))
|
||||
+ result = validate(tmp_path)
|
||||
+ assert result.returncode == 1, result.stdout + result.stderr
|
||||
+ assert f"{field} differs from entry" in result.stderr
|
||||
+ assert (tmp_path / index["capabilities"][0]["path"]).read_bytes() == original_entry
|
||||
+
|
||||
+
|
||||
+def test_duplicate_id_and_path_cannot_satisfy_exactly_once(tmp_path):
|
||||
+ path, index = fixture_registry(tmp_path)
|
||||
+ index["capabilities"].append(copy.deepcopy(index["capabilities"][0]))
|
||||
+ path.write_text(yaml.safe_dump(index))
|
||||
+ result = validate(tmp_path)
|
||||
+ assert result.returncode == 1
|
||||
+ assert "duplicate id:" in result.stderr
|
||||
+ assert "duplicate path:" in result.stderr
|
||||
+
|
||||
+
|
||||
+def test_equivalent_vector_whitespace_is_allowed(tmp_path):
|
||||
+ path, index = fixture_registry(tmp_path)
|
||||
+ index["capabilities"][0]["vector"] = index["capabilities"][0]["vector"].replace(" ", "")
|
||||
+ path.write_text(yaml.safe_dump(index))
|
||||
+ assert validate(tmp_path).returncode == 0
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"repo_slug": "reuse-surface",
|
||||
"repo_path": "/home/worsch/reuse-surface",
|
||||
"source_revision": "01af18964b170eaa87f8560ce9285518e8dc6d7e",
|
||||
"source_revision_reason": null,
|
||||
"issues": [
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-24",
|
||||
"message": "Repo classification advisory: capability_tag 'capability-registry' is not a recommended family tag (allowed, check for synonym)",
|
||||
"file_path": "",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-24",
|
||||
"message": "Repo classification advisory: capability_tag 'discovery' is not a recommended family tag (allowed, check for synonym)",
|
||||
"file_path": "",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-24",
|
||||
"message": "Repo classification advisory: capability_tag 'reuse' is not a recommended family tag (allowed, check for synonym)",
|
||||
"file_path": "",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-24",
|
||||
"message": "Repo classification advisory: capability_tag 'maturity' is not a recommended family tag (allowed, check for synonym)",
|
||||
"file_path": "",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-22",
|
||||
"message": "Task description drift 'REUSE-WP-ADHOC-2026-10-16-T01': file content differs from DB (file wins)",
|
||||
"file_path": "workplans/ADHOC-2026-10-16.md#REUSE-WP-ADHOC-2026-10-16-T01",
|
||||
"db_id": "63c0e025-f7aa-5849-ac2f-61128ceb7a0e",
|
||||
"file_value": "The existing strict validator accepted stale index metadata and duplicate IDs/paths. Extend its existing parity warnings",
|
||||
"db_value": "The existing strict validator accepted stale index metadata and duplicate IDs/paths. Extend its existing parity warnings",
|
||||
"fixable": true
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'CUST-WP-0081' is not linked to State Hub",
|
||||
"file_path": "workplans/REUSE-WP-0025-private-source-reader-boundary.md",
|
||||
"db_id": "",
|
||||
"file_value": "CUST-WP-0081",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-39",
|
||||
"message": "Wait task 'REUSE-WP-0025-T03' in workplan 'REUSE-WP-0025' has neither depends_on, needs_human: true, blocked_on: message-from:\u2026 nor wait_until \u2014 unqualified wait",
|
||||
"file_path": "workplans/REUSE-WP-0025-private-source-reader-boundary.md#REUSE-WP-0025-T03",
|
||||
"db_id": "",
|
||||
"file_value": "wait",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
}
|
||||
],
|
||||
"fixes_applied": [
|
||||
"bindings: synced 27/27",
|
||||
"C-17 cleared: pushed 1 backlogged commit(s)",
|
||||
"C-22 fixed: task 63c0e025\u2026 description updated",
|
||||
"push: pushed"
|
||||
],
|
||||
"summary": {
|
||||
"fail": 0,
|
||||
"automation_error": 0,
|
||||
"warn": 7,
|
||||
"info": 0
|
||||
},
|
||||
"result": "warn"
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"commit": "662782e65ade239e5194e16f4c4a902c792d8d44",
|
||||
"status": "",
|
||||
"original_cli_sha256": "da968735cb45dc9944c3a722234641e17cd49a6bef9aa91f40e78ddde3fb9c0e",
|
||||
"candidate_cli_sha256": "753abc928d63665df34b346e26386dcdc3617570d7eb979cb65fbdde2bd13efa",
|
||||
"adopted_consumer": ".forgejo/workflows/ci.yml:37 (mandatory validate --relations --fail-on-warnings)",
|
||||
"new_tests": 11,
|
||||
"full_suite_initial": {
|
||||
"passed": 237,
|
||||
"failed": 1,
|
||||
"reason": "test_federation_compose requires sibling indexes; isolation moved relative sibling root from /home/worsch to /tmp"
|
||||
},
|
||||
"full_suite_followup": {
|
||||
"command": "python3 -m pytest -q tests/test_registry.py::test_federation_compose",
|
||||
"passed": 1,
|
||||
"failed": 0,
|
||||
"temporary_fixture_adaptation": "Only temporary registry/federation/sources.yaml index paths resolved against original owner checkout; source patch has no manifest change. This fixes missing siblings after staging in /tmp. All 238 tests have passed across the full run and this targeted rerun."
|
||||
},
|
||||
"historical_preparation_only": true,
|
||||
"current_integration": {
|
||||
"receipt": "integration-r2.json",
|
||||
"canonical_source": "dd00b27b4ce77180b01c5756c2a3841819fcac76",
|
||||
"new_cli_tests": 23,
|
||||
"root_independent_checks_passed": 30,
|
||||
"publication": "normal consistency workflow completed",
|
||||
"ci": "unverified; public actions API404",
|
||||
"hosted": "root fresh readback19:25:11 matches both corrected owners"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,802 @@
|
|||
{
|
||||
"repositories": 60,
|
||||
"reviewed_ids": 70,
|
||||
"reviewed_exactly_once": 70,
|
||||
"all_provider_index_rows": 71,
|
||||
"errors": 0,
|
||||
"warnings": 3,
|
||||
"checks": [
|
||||
{
|
||||
"owner": "access-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "activity-core",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "agentic-resources",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "approval-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "audit-core",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "coordination-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "emergent-security",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "fin-hub",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "gate-house",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "glas-harness",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "info-tech-canon",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "informed-decision",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "issue-core",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "key-cape",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "kings-guard",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "maturity-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "net-kingdom",
|
||||
"entry_count": 6,
|
||||
"errors": [],
|
||||
"warnings": [
|
||||
"index drift: registry/capabilities/capability.security.bootstrap-recovery.md: summary differs from entry"
|
||||
]
|
||||
},
|
||||
{
|
||||
"owner": "ops-bridge",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "ops-mason",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "ops-warden",
|
||||
"entry_count": 3,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "policy-nexus",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "rail-knative",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "rail-kubernetes",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "railiance-apps",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "railiance-bootstrap",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [
|
||||
"index drift: registry/capabilities/capability.railiance.bootstrap-sequencing.md: summary differs from entry",
|
||||
"index drift: registry/capabilities/capability.railiance.bootstrap-sequencing.md: tags differs from entry"
|
||||
]
|
||||
},
|
||||
{
|
||||
"owner": "railiance-cluster",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "railiance-enablement",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "railiance-fabric",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "railiance-forge",
|
||||
"entry_count": 2,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "railiance-infra",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "railiance-master",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "railiance-platform",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "railiance-telemetry",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "rapp-openbao",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "rapp-playwright",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "rapp-policy-nexus",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "rapp-postgres",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "rapp-sbom-nexus",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "rapp-telemetry",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "rapp-tenant-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "rapp-user-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "reef-railiance",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "rein-aharness",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "rein-openweights",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "repo-manager",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "resource-control",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "reuse-surface",
|
||||
"entry_count": 3,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "risk-nexus",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "role-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "runbook-tutorials",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "sand-boxer",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "sbom-nexus",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "secrets-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "security-canon",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "state-hub",
|
||||
"entry_count": 2,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "tenant-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "the-custodian",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "user-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "whitehat-security",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
},
|
||||
{
|
||||
"owner": "zone-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
}
|
||||
],
|
||||
"proof_boundary": "Current local registry source only; warnings retain 3 field mismatches across 2 capabilities. Additional owner entries are outside the reviewed denominator. No hosted check or runtime acceptance.",
|
||||
"corrected_temporary_owner_projection": {
|
||||
"errors": 0,
|
||||
"warnings": 0,
|
||||
"checks": [
|
||||
{
|
||||
"owner": "access-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/access-engine"
|
||||
},
|
||||
{
|
||||
"owner": "activity-core",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/activity-core"
|
||||
},
|
||||
{
|
||||
"owner": "agentic-resources",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/agentic-resources"
|
||||
},
|
||||
{
|
||||
"owner": "approval-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/approval-engine"
|
||||
},
|
||||
{
|
||||
"owner": "audit-core",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/audit-core"
|
||||
},
|
||||
{
|
||||
"owner": "coordination-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/coordination-engine"
|
||||
},
|
||||
{
|
||||
"owner": "emergent-security",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/emergent-security"
|
||||
},
|
||||
{
|
||||
"owner": "fin-hub",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/fin-hub"
|
||||
},
|
||||
{
|
||||
"owner": "gate-house",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/gate-house"
|
||||
},
|
||||
{
|
||||
"owner": "glas-harness",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/glas-harness"
|
||||
},
|
||||
{
|
||||
"owner": "info-tech-canon",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/info-tech-canon"
|
||||
},
|
||||
{
|
||||
"owner": "informed-decision",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/informed-decision"
|
||||
},
|
||||
{
|
||||
"owner": "issue-core",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/issue-core"
|
||||
},
|
||||
{
|
||||
"owner": "key-cape",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/key-cape"
|
||||
},
|
||||
{
|
||||
"owner": "kings-guard",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/kings-guard"
|
||||
},
|
||||
{
|
||||
"owner": "maturity-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/maturity-engine"
|
||||
},
|
||||
{
|
||||
"owner": "net-kingdom",
|
||||
"entry_count": 6,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/tmp/netkingdom-improvement-loop-next-owners/net-kingdom"
|
||||
},
|
||||
{
|
||||
"owner": "ops-bridge",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/ops-bridge"
|
||||
},
|
||||
{
|
||||
"owner": "ops-mason",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/ops-mason"
|
||||
},
|
||||
{
|
||||
"owner": "ops-warden",
|
||||
"entry_count": 3,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/ops-warden"
|
||||
},
|
||||
{
|
||||
"owner": "policy-nexus",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/policy-nexus"
|
||||
},
|
||||
{
|
||||
"owner": "rail-knative",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/rail-knative"
|
||||
},
|
||||
{
|
||||
"owner": "rail-kubernetes",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/rail-kubernetes"
|
||||
},
|
||||
{
|
||||
"owner": "railiance-apps",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/railiance-apps"
|
||||
},
|
||||
{
|
||||
"owner": "railiance-bootstrap",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/tmp/netkingdom-improvement-loop-next-owners/railiance-bootstrap"
|
||||
},
|
||||
{
|
||||
"owner": "railiance-cluster",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/railiance-cluster"
|
||||
},
|
||||
{
|
||||
"owner": "railiance-enablement",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/railiance-enablement"
|
||||
},
|
||||
{
|
||||
"owner": "railiance-fabric",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/railiance-fabric"
|
||||
},
|
||||
{
|
||||
"owner": "railiance-forge",
|
||||
"entry_count": 2,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/railiance-forge"
|
||||
},
|
||||
{
|
||||
"owner": "railiance-infra",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/railiance-infra"
|
||||
},
|
||||
{
|
||||
"owner": "railiance-master",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/railiance-master"
|
||||
},
|
||||
{
|
||||
"owner": "railiance-platform",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/railiance-platform"
|
||||
},
|
||||
{
|
||||
"owner": "railiance-telemetry",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/railiance-telemetry"
|
||||
},
|
||||
{
|
||||
"owner": "rapp-openbao",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/rapp-openbao"
|
||||
},
|
||||
{
|
||||
"owner": "rapp-playwright",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/rapp-playwright"
|
||||
},
|
||||
{
|
||||
"owner": "rapp-policy-nexus",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/rapp-policy-nexus"
|
||||
},
|
||||
{
|
||||
"owner": "rapp-postgres",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/rapp-postgres"
|
||||
},
|
||||
{
|
||||
"owner": "rapp-sbom-nexus",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/rapp-sbom-nexus"
|
||||
},
|
||||
{
|
||||
"owner": "rapp-telemetry",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/rapp-telemetry"
|
||||
},
|
||||
{
|
||||
"owner": "rapp-tenant-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/rapp-tenant-engine"
|
||||
},
|
||||
{
|
||||
"owner": "rapp-user-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/rapp-user-engine"
|
||||
},
|
||||
{
|
||||
"owner": "reef-railiance",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/reef-railiance"
|
||||
},
|
||||
{
|
||||
"owner": "rein-aharness",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/rein-aharness"
|
||||
},
|
||||
{
|
||||
"owner": "rein-openweights",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/rein-openweights"
|
||||
},
|
||||
{
|
||||
"owner": "repo-manager",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/repo-manager"
|
||||
},
|
||||
{
|
||||
"owner": "resource-control",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/resource-control"
|
||||
},
|
||||
{
|
||||
"owner": "reuse-surface",
|
||||
"entry_count": 3,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/reuse-surface"
|
||||
},
|
||||
{
|
||||
"owner": "risk-nexus",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/risk-nexus"
|
||||
},
|
||||
{
|
||||
"owner": "role-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/role-engine"
|
||||
},
|
||||
{
|
||||
"owner": "runbook-tutorials",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/runbook-tutorials"
|
||||
},
|
||||
{
|
||||
"owner": "sand-boxer",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/sand-boxer"
|
||||
},
|
||||
{
|
||||
"owner": "sbom-nexus",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/sbom-nexus"
|
||||
},
|
||||
{
|
||||
"owner": "secrets-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/secrets-engine"
|
||||
},
|
||||
{
|
||||
"owner": "security-canon",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/security-canon"
|
||||
},
|
||||
{
|
||||
"owner": "state-hub",
|
||||
"entry_count": 2,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/state-hub"
|
||||
},
|
||||
{
|
||||
"owner": "tenant-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/tenant-engine"
|
||||
},
|
||||
{
|
||||
"owner": "the-custodian",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/the-custodian"
|
||||
},
|
||||
{
|
||||
"owner": "user-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/user-engine"
|
||||
},
|
||||
{
|
||||
"owner": "whitehat-security",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/whitehat-security"
|
||||
},
|
||||
{
|
||||
"owner": "zone-engine",
|
||||
"entry_count": 1,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"root": "/home/worsch/zone-engine"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"scope": "Source-only observation, 2026-10-16",
|
||||
"activation": {
|
||||
"definition_enabled": false,
|
||||
"activity_task": "ACTIVITY-WP-0047-T02",
|
||||
"blocking_seam": "Claiming rein workstation checkout and hub acceptance, then Activity owner release/admin-sync/manual trigger. No such acceptance in owner record.",
|
||||
"rein_record": "REINAH-WP-0008 says candidate818b1b7c not installed/admitted; startup receipt has model_run_proven:false. Existing owner memo/native execution gates remain."
|
||||
},
|
||||
"limits": "No requirement is invented that the unrelated task-scoped protected operation must complete first. The actual accepted schedule depends on its selected rein/profile, not a newly substituted scheduler.",
|
||||
"sources": {
|
||||
"activity-core/activity-definitions/daily-security-flow.md": {
|
||||
"sha256": "0281f0cd818b02d1cbd3a732dfb72145e8cb5e9881c4d80c6f6eb3d664da9375",
|
||||
"owner_head": "f9f8df039258a0d8b6fc29dc1f7c4bcc75fdc8c0"
|
||||
},
|
||||
"activity-core/workplans/ACTIVITY-WP-0047-daily-security-flow-adoption.md": {
|
||||
"sha256": "e0b83b570da688abb405bed165d81db7cf49bc7ff11ef9a8ad86cbee2dc651db",
|
||||
"owner_head": "f9f8df039258a0d8b6fc29dc1f7c4bcc75fdc8c0"
|
||||
},
|
||||
"rein-aharness/workplans/REINAH-WP-0008-simpler-metered-retry.md": {
|
||||
"sha256": "3f46cf1f278e35be994772cb2d7e6d95f570fb5bfba28c6b55dc89b663d25f89",
|
||||
"owner_head": "be5ef400861a5e31a31f29d173990c7a143fb189"
|
||||
},
|
||||
"rein-aharness/docs/evidence/2026-10-15-rebuilt-owner-runtime.json": {
|
||||
"sha256": "2c9001de6eda33a0e99aa7321c7401b9997e583eeaa557bf656b5252e68e3418",
|
||||
"owner_head": "be5ef400861a5e31a31f29d173990c7a143fb189"
|
||||
},
|
||||
"kings-guard/docs/PostureMaturityBoundary.md": {
|
||||
"sha256": "6bfb15b4d50297fd0c1fa91eda94143b85df537d2d777f8ae86774bd30bc412a",
|
||||
"owner_head": "09ada1d98100d4f3c6aa6e94e8ec5f41b7465c3b"
|
||||
},
|
||||
"maturity-engine/workplans/MAT-WP-0001-netkingdom-security-layer-engine.md": {
|
||||
"sha256": "bdb6b10162793cb80e45fa228b1d5e6f441eddbcc1a8d45680af4ac08f3b7a81",
|
||||
"owner_head": "2f1de542cc8f24a79452f906d99f95be39d8b6a9"
|
||||
},
|
||||
"coordination-engine/config/coordination-frame.json": {
|
||||
"sha256": "d15e092387f128ffdfd00b9815707a05ff77c5bb0ba4d9a428fbc37b8c440b67",
|
||||
"owner_head": "f658556690f3f6b7efe9c95a445222019182e402"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
from pathlib import Path
|
||||
import subprocess,hashlib,json,datetime
|
||||
ROOT=Path('/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/protected-execution-trusted-successor')
|
||||
REPO=Path('/home/worsch/railiance-platform')
|
||||
manifest=json.loads((ROOT/'budget-source-manifest-r1.json').read_text())
|
||||
patch=ROOT/'budget-source-slice-r1.patch'
|
||||
assert hashlib.sha256(patch.read_bytes()).hexdigest()==manifest['patch_sha256']
|
||||
base=subprocess.check_output(['git','rev-parse','HEAD'],cwd=REPO,text=True).strip()
|
||||
assert base==manifest['canonical_base_full'], 'canonical HEAD changed; re-review before mutation'
|
||||
for name,expected in manifest['files'].items():
|
||||
path=REPO/name
|
||||
actual=hashlib.sha256(path.read_bytes()).hexdigest() if path.exists() else None
|
||||
assert actual==expected['before_sha256'], name
|
||||
subprocess.run(['git','apply','--check',str(patch)],cwd=REPO,check=True)
|
||||
subprocess.run(['git','apply',str(patch)],cwd=REPO,check=True)
|
||||
files={}
|
||||
for name,expected in manifest['files'].items():
|
||||
digest=hashlib.sha256((REPO/name).read_bytes()).hexdigest()
|
||||
assert digest==expected['after_sha256'], name
|
||||
files[name]='sha256:'+digest
|
||||
subprocess.run(['git','diff','--check','--',*files],cwd=REPO,check=True)
|
||||
receipt={'repo':'railiance-platform','base_commit':base,'patch_sha256':'sha256:'+manifest['patch_sha256'],'integrated_at':datetime.datetime.now(datetime.timezone.utc).isoformat(),'files_sha256':files,'source_integrated':True,'committed':False,'published':False,'native_accepted':False,'review_scope':'fixed Unix native fixture budget and exact v2 source byte guard; full provider/native proof pending'}
|
||||
(ROOT/'canonical-integration.json').write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
print('Integrated five exact source files; unrelated WORK-RECORDS preserved. Commit/publication pending.')
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
"""Integrate exact reviewed recovery source; no native custody or grants."""
|
||||
import integrate_source_wave5 as integration
|
||||
|
||||
integration.TARGETS = (
|
||||
('railiance-platform', 'protected-execution-recovery', 'implementation-r2.patch',
|
||||
'61f516c876fd612b83300e9913f0e80aad836a50014310797913c7c4505e3114',
|
||||
'd870596cc72f74a5762cce2400802e2b351e903e', 'coordinator-files-r2.json', 'files'),
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
integration.main()
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
"""Apply only the two reviewed source patches; no commit, sync or live action."""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
from datetime import datetime, timezone
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
TARGETS = (
|
||||
("railiance-platform", "protected-execution", "platform-cleanup.patch",
|
||||
"be8de4975980e4b88b37ed98d3fdd509d513f85834efc3a5cb744410c4e95392"),
|
||||
("net-kingdom", "browser-case", "implementation.patch",
|
||||
"d32a7f51cbf3012bee3e4bb329f4259dea867eff1c59a2b01a65d72774488d05"),
|
||||
)
|
||||
|
||||
|
||||
def digest(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def main():
|
||||
prepared = []
|
||||
for repo, part, patch_name, expected_patch in TARGETS:
|
||||
checkout = Path("/home/worsch") / repo
|
||||
patch = ROOT / part / patch_name
|
||||
manifest = json.loads((patch.parent / "manifest.json").read_text())
|
||||
if digest(patch) != expected_patch:
|
||||
raise SystemExit(f"reviewed patch changed: {repo}")
|
||||
head = subprocess.check_output(["git", "-C", str(checkout), "rev-parse", "HEAD"], text=True).strip()
|
||||
if head != manifest["base_commit"]:
|
||||
raise SystemExit(f"owner revision changed: {repo}")
|
||||
files = manifest.get("files_sha256", manifest.get("files"))
|
||||
dirty = subprocess.check_output(["git", "-C", str(checkout), "diff", "HEAD", "--", *files])
|
||||
if dirty:
|
||||
raise SystemExit(f"targeted owner files contain independent changes: {repo}")
|
||||
subprocess.run(["git", "-C", str(checkout), "apply", "--check", str(patch)], check=True)
|
||||
prepared.append((repo, checkout, patch, files, expected_patch, head))
|
||||
for repo, checkout, patch, files, patch_hash, head in prepared:
|
||||
subprocess.run(["git", "-C", str(checkout), "apply", str(patch)], check=True)
|
||||
for name, expected in files.items():
|
||||
if digest(checkout / name) != expected.removeprefix("sha256:"):
|
||||
raise SystemExit(f"integrated file differs: {repo}/{name}")
|
||||
receipt = {"repo": repo, "base_commit": head, "patch_sha256": patch_hash,
|
||||
"integrated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"files_sha256": files, "source_integrated": True,
|
||||
"committed": False, "published": False, "native_accepted": False}
|
||||
(patch.parent / "canonical-integration.json").write_text(json.dumps(receipt, indent=2) + "\n")
|
||||
print(f"{repo}: exact reviewed source integrated ({len(files)} files)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
"""Integrate three exact reviewed source patches. No commit, deploy or admission."""
|
||||
from datetime import datetime, timezone
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
TARGETS = (
|
||||
("glas-harness", "agent-isolation", "glas-harness.patch",
|
||||
"83eeeb0017603aabae0ff49a23f96248f677f6c9f3e2c01b22f6d0416fca69c7",
|
||||
"dc88f302044c7c2b41795958527e64be8a83e3ab"),
|
||||
("railiance-platform", "protected-execution-wave2", "platform-native-fixture.patch",
|
||||
"c419ac0f6dba3e36f15dc51a6a36fbd87fe7d255cce40c33c7f4589d80355580",
|
||||
"69a792c070238273acd9a989e2707072a5ef55ed"),
|
||||
("secrets-engine", "decision-auth", "implementation-r2.patch",
|
||||
"98c4300c42fe93759a55297fe6629f1d4aeed249c3625f7fd37c8ab523a08ca9",
|
||||
"6bd7129eac94131b4cf689e47cbdc9899418beb3"),
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
prepared = []
|
||||
for repo, part, patch_name, expected_patch, expected_head in TARGETS:
|
||||
owner, folder = Path("/home/worsch") / repo, ROOT / part
|
||||
patch = folder / patch_name
|
||||
assert hashlib.sha256(patch.read_bytes()).hexdigest() == expected_patch, repo
|
||||
head = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=owner, text=True).strip()
|
||||
assert head == expected_head, f"owner revision changed: {repo}"
|
||||
if repo == "glas-harness":
|
||||
manifest = json.loads((folder / "source-manifest.json").read_text())
|
||||
files = {item["path"]: item["candidate_sha256"] for item in manifest["changes"]}
|
||||
elif repo == "railiance-platform":
|
||||
files = json.loads((folder / "manifest.json").read_text())["files_sha256"]
|
||||
else:
|
||||
files = json.loads((folder / "reviewed-r2-file-hashes.json").read_text())
|
||||
assert not subprocess.check_output(["git", "diff", "HEAD", "--", *files], cwd=owner), f"target files dirty: {repo}"
|
||||
for name in files:
|
||||
tracked = subprocess.run(["git", "ls-files", "--error-unmatch", name], cwd=owner, capture_output=True).returncode == 0
|
||||
assert tracked or not (owner / name).exists(), f"untracked target collision: {repo}/{name}"
|
||||
subprocess.run(["git", "apply", "--check", str(patch)], cwd=owner, check=True)
|
||||
prepared.append((repo, owner, folder, patch, expected_patch, head, files))
|
||||
for repo, owner, folder, patch, patch_hash, head, files in prepared:
|
||||
subprocess.run(["git", "apply", str(patch)], cwd=owner, check=True)
|
||||
for name, expected in files.items():
|
||||
assert hashlib.sha256((owner / name).read_bytes()).hexdigest() == expected, f"integrated file differs: {repo}/{name}"
|
||||
receipt = {"repo": repo, "base_commit": head, "patch_sha256": patch_hash,
|
||||
"integrated_at": datetime.now(timezone.utc).isoformat(), "files_sha256": files,
|
||||
"source_integrated": True, "committed": False, "published": False,
|
||||
"native_accepted": False}
|
||||
(folder / "canonical-integration.json").write_text(json.dumps(receipt, indent=2) + "\n")
|
||||
print(f"{repo}: {len(files)} exact reviewed source files integrated")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
"""Integrate reviewed source only; preserve all unrelated owner edits."""
|
||||
from datetime import datetime, timezone
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
TARGETS = (
|
||||
("approval-engine", "approval-reference", "implementation.patch",
|
||||
"a6bf77fa8b6916f423db7832f475e38747b828dd632a88f9631131e28e3230ee",
|
||||
"9491d1d3a7b6554fa04f8dc06f65d50e3d6ee4e1", "files"),
|
||||
("ops-mason", "mason-browser", "implementation.patch",
|
||||
"cfe7648ed119d83faca228a2f21611c9388de1ba166598cbeb64f40e81620067",
|
||||
"e4761b843cd881a672cbb33a5b451de31a61d730", "changed_source"),
|
||||
("railiance-platform", "protected-execution-wave3", "platform-grant-projection.patch",
|
||||
"d91fdbd68b328d430e0e2b0b7f44d552095b8688ae4942d398d2e126b3648ad5",
|
||||
"4021ca5e79d3f85197b26cc20d29c55771462cf1", "files_sha256"),
|
||||
("access-engine", "access-verifier-review", "producer-signing-r2.patch",
|
||||
"25e05b04f96cfe6f7d1abb11ab5d533b14a830c437136fc78aa381198ff8db37",
|
||||
"62ac2b4e99dec2005c7c407fe633612578a7907d", "source_files"),
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
prepared = []
|
||||
for repo, part, filename, expected, base, files_key in TARGETS:
|
||||
owner, folder = Path("/home/worsch") / repo, ROOT / part
|
||||
patch = folder / filename
|
||||
assert hashlib.sha256(patch.read_bytes()).hexdigest() == expected, repo
|
||||
head = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=owner, text=True).strip()
|
||||
assert head == base, f"owner revision changed: {repo}"
|
||||
manifest = "manifest-r2.json" if repo == "access-engine" else "manifest.json"
|
||||
files = json.loads((folder / manifest).read_text())[files_key]
|
||||
assert not subprocess.check_output(["git", "diff", "HEAD", "--", *files], cwd=owner), f"target files dirty: {repo}"
|
||||
for name in files:
|
||||
tracked = subprocess.run(["git", "ls-files", "--error-unmatch", name], cwd=owner, capture_output=True).returncode == 0
|
||||
assert tracked or not (owner / name).exists(), f"untracked target collision: {repo}/{name}"
|
||||
subprocess.run(["git", "apply", "--check", str(patch)], cwd=owner, check=True)
|
||||
prepared.append((repo, owner, folder, patch, expected, head, files))
|
||||
# Secrets R2 is deliberately still uncommitted after two public-ID scanner
|
||||
# false positives. Apply only the reviewed naming delta to those exact bytes.
|
||||
owner, folder = Path("/home/worsch/secrets-engine"), ROOT / "decision-auth"
|
||||
manifest = json.loads((folder / "integration-manifest-r3.json").read_text())
|
||||
patch = folder / "implementation-r3-delta.patch"
|
||||
expected = "b6c944fee615298a29dbd50ba0ad437ff76e6eab190f6168427e4ae5dbbfc59c"
|
||||
assert hashlib.sha256(patch.read_bytes()).hexdigest() == expected
|
||||
for name, hashes in manifest["files"].items():
|
||||
assert hashlib.sha256((owner / name).read_bytes()).hexdigest() == hashes["r2_sha256"], name
|
||||
subprocess.run(["git", "apply", "--check", str(patch)], cwd=owner, check=True)
|
||||
head = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=owner, text=True).strip()
|
||||
prepared.append(("secrets-engine", owner, folder, patch, expected, head,
|
||||
{name: hashes["r3_sha256"] for name, hashes in manifest["files"].items()}))
|
||||
for repo, owner, folder, patch, patch_hash, head, files in prepared:
|
||||
subprocess.run(["git", "apply", str(patch)], cwd=owner, check=True)
|
||||
for name, expected in files.items():
|
||||
assert hashlib.sha256((owner / name).read_bytes()).hexdigest() == expected.removeprefix("sha256:"), name
|
||||
receipt = {"repo": repo, "base_commit": head, "patch_sha256": patch_hash,
|
||||
"integrated_at": datetime.now(timezone.utc).isoformat(), "files_sha256": files,
|
||||
"source_integrated": True, "committed": False, "published": False,
|
||||
"native_accepted": False}
|
||||
name = "canonical-integration-r3.json" if repo == "secrets-engine" else "canonical-integration.json"
|
||||
(folder / name).write_text(json.dumps(receipt, indent=2) + "\n")
|
||||
print(f"{repo}: {len(files)} exact reviewed source files integrated")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
"""Apply four reviewed source packages; no install, credentials or live effects."""
|
||||
from datetime import datetime, timezone
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
TARGETS = (
|
||||
('secrets-engine', 'decision-package', 'implementation-r1.patch',
|
||||
'86179e607509d782f8a00012174c549997e0997ce4f84fc1078d245e4670a4ae',
|
||||
'c80a944105a00220cd3c1a007709e72d830dd08d', 'coordinator-file-hashes.json', None),
|
||||
('secrets-engine', 'decision-handoff', 'implementation-r2.patch',
|
||||
'1ef8b67e2352415ddbdadc86cd151a8b6caf76628350657ed3e674aefcf49945',
|
||||
'c80a944105a00220cd3c1a007709e72d830dd08d', 'integration-manifest-r2.json', 'files'),
|
||||
('railiance-platform', 'protected-execution-wave4', 'platform-confirmation-seam.patch',
|
||||
'f26a5e1f63f68c487930d3f076502f1bae73d16107d3be2537aeeda56e73628e',
|
||||
'd042d468df6d4085bb93a73ec26f4c4103554575', 'manifest.json', 'files_sha256'),
|
||||
('approval-engine', 'approval-confirmation', 'implementation.patch',
|
||||
'a2dfa6585a8b97fc6facec7b0815ffc3374869b8de51cc696d534c69643e3621',
|
||||
'8624fa20bbfb0c0329e5327b4dd8d8c05a312d7a', 'manifest.json', 'files'),
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
prepared = []
|
||||
for repo, part, filename, expected, base, manifest_name, files_key in TARGETS:
|
||||
owner, folder = Path('/home/worsch') / repo, ROOT / part
|
||||
patch = folder / filename
|
||||
assert hashlib.sha256(patch.read_bytes()).hexdigest() == expected, repo
|
||||
head = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=owner, text=True).strip()
|
||||
assert head == base, f'owner HEAD changed: {repo}'
|
||||
manifest = json.loads((folder / manifest_name).read_text())
|
||||
files = manifest[files_key] if files_key else manifest
|
||||
assert not subprocess.check_output(['git', 'diff', 'HEAD', '--', *files], cwd=owner), f'target files dirty: {repo}'
|
||||
for name in files:
|
||||
tracked = subprocess.run(['git', 'ls-files', '--error-unmatch', name], cwd=owner, capture_output=True).returncode == 0
|
||||
assert tracked or not (owner / name).exists(), f'untracked target collision: {repo}/{name}'
|
||||
subprocess.run(['git', 'apply', '--check', str(patch)], cwd=owner, check=True)
|
||||
prepared.append((repo, owner, folder, patch, expected, head, files))
|
||||
for repo, owner, folder, patch, patch_hash, head, files in prepared:
|
||||
subprocess.run(['git', 'apply', str(patch)], cwd=owner, check=True)
|
||||
for name, expected in files.items():
|
||||
assert hashlib.sha256((owner / name).read_bytes()).hexdigest() == expected, name
|
||||
receipt = {'repo': repo, 'base_commit': head, 'patch_sha256': patch_hash,
|
||||
'integrated_at': datetime.now(timezone.utc).isoformat(), 'files_sha256': files,
|
||||
'source_integrated': True, 'committed': False, 'published': False,
|
||||
'native_accepted': False}
|
||||
(folder / 'canonical-integration.json').write_text(json.dumps(receipt, indent=2) + '\n')
|
||||
print(f'{repo}/{folder.name}: {len(files)} reviewed files integrated')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
"""Integrate three reviewed source packages; never deploy or admit credentials."""
|
||||
from datetime import datetime, timezone
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
TARGETS = (
|
||||
('railiance-platform', 'protected-execution-wave5', 'platform-wave5.patch',
|
||||
'b4c44a452a1b62d0a2c114e2de60537666a139a387c478c663236b3aacdaadec',
|
||||
'5f30ddd90c33e7eb1b9efa1093c7324d83210cf2', 'manifest.json', 'files'),
|
||||
('access-engine', 'access-native-source', 'source-preparation-r2.patch',
|
||||
'cee7af8e1e8ee8b63f9d5bc5927a6d89ae6c1169b3ec23f59e0958664fc91dca',
|
||||
'ec21518e25df2a58444f19ff3413ce0b83383d3c', 'manifest-r2.json', 'source_files_sha256'),
|
||||
('secrets-engine', 'decision-native-preparation', 'implementation-r1.patch',
|
||||
'316daaba4afa309b2f13acf995f781d9fae1ea84e12837a8b494e31b8489dd46',
|
||||
'ff1a13dabd44ba02ddd6bc26ad75568f6d2e6225', 'integration-manifest-r1.json', 'files'),
|
||||
)
|
||||
|
||||
|
||||
def digest(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest() if path.exists() else None
|
||||
|
||||
|
||||
def main():
|
||||
prepared = []
|
||||
for repo, part, filename, patch_hash, head, manifest_name, key in TARGETS:
|
||||
owner, folder = Path('/home/worsch') / repo, ROOT / part
|
||||
assert not (folder / 'canonical-integration.json').exists(), 'already integrated'
|
||||
patch = folder / filename
|
||||
assert digest(patch) == patch_hash, repo
|
||||
assert subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=owner, text=True).strip() == head, repo
|
||||
files = json.loads((folder / manifest_name).read_text())[key]
|
||||
assert not subprocess.check_output(['git', 'diff', 'HEAD', '--', *files], cwd=owner), repo
|
||||
after = {}
|
||||
before = {}
|
||||
for name, expected in files.items():
|
||||
assert not Path(name).is_absolute() and '..' not in Path(name).parts
|
||||
tracked = subprocess.run(['git', 'ls-files', '--error-unmatch', name], cwd=owner, capture_output=True).returncode == 0
|
||||
assert tracked or not (owner / name).exists(), name
|
||||
before[name] = digest(owner / name)
|
||||
if isinstance(expected, dict):
|
||||
assert before[name] == expected['before_sha256'], name
|
||||
expected = expected['after_sha256']
|
||||
after[name] = expected
|
||||
subprocess.run(['git', 'apply', '--check', str(patch)], cwd=owner, check=True)
|
||||
prepared.append((repo, owner, folder, patch, patch_hash, head, before, after))
|
||||
for repo, owner, folder, patch, patch_hash, head, before, after in prepared:
|
||||
assert subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=owner, text=True).strip() == head
|
||||
assert all(digest(owner / name) == value for name, value in before.items())
|
||||
subprocess.run(['git', 'apply', str(patch)], cwd=owner, check=True)
|
||||
assert all(digest(owner / name) == value for name, value in after.items())
|
||||
receipt = {'repo': repo, 'base_commit': head, 'patch_sha256': 'sha256:' + patch_hash,
|
||||
'integrated_at': datetime.now(timezone.utc).isoformat(),
|
||||
'files_sha256': {name: 'sha256:' + value for name, value in after.items()},
|
||||
'source_integrated': True, 'committed': False, 'published': False,
|
||||
'native_accepted': False}
|
||||
(folder / 'canonical-integration.json').write_text(json.dumps(receipt, indent=2) + '\n')
|
||||
print(f'{repo}: {len(after)} reviewed source files integrated')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
"""Apply independently reviewed Approval adapter and Access B1 R2 source."""
|
||||
import integrate_source_wave5 as integration
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from datetime import datetime, timezone
|
||||
|
||||
integration.TARGETS = (
|
||||
('approval-engine', 'approval-go-fact', 'implementation.patch',
|
||||
'096de94db8afff6b429215d951a98b94cd501cc6a7e1c374420e546169df2179',
|
||||
'eece796b8bbee19551f6aa07ad389cbf83cabcdb', 'manifest.json', 'files'),
|
||||
('access-engine', 'access-native-source/b1', 'b1-r2-over-native-prep-r2.patch',
|
||||
'8880eb0f693f6793cfa6b3ea6f2466dbe67313e521a7f79c30180ca4880609b9',
|
||||
'919ce25a377300ef9749c85e41b608467bed02ad', 'manifest-r2.json', 'source_files_sha256'),
|
||||
)
|
||||
|
||||
def main():
|
||||
prepared = []
|
||||
wp = 'workplans/FLEX-WP-0024-decision-envelope-authenticity.md'
|
||||
managed = {'FLEX-WP-0024-T05': 'cfe4d275-9f02-5090-8391-06eea59d45c5',
|
||||
'FLEX-WP-0024-T06': '7f785b52-bf62-5884-8d34-670d70bc9023'}
|
||||
for repo, part, filename, patch_hash, head, manifest_name, key in integration.TARGETS:
|
||||
owner, folder = Path('/home/worsch') / repo, integration.ROOT / part
|
||||
patch = folder / filename
|
||||
assert not (folder / 'canonical-integration.json').exists()
|
||||
assert integration.digest(patch) == patch_hash
|
||||
assert subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=owner, text=True).strip() == head
|
||||
files = json.loads((folder / manifest_name).read_text())[key]
|
||||
before = {name: integration.digest(owner / name) for name in files}
|
||||
for name in files:
|
||||
tracked = subprocess.run(['git', 'ls-files', '--error-unmatch', name], cwd=owner, capture_output=True).returncode == 0
|
||||
assert tracked or not (owner / name).exists(), name
|
||||
if tracked:
|
||||
original = subprocess.check_output(['git', 'show', 'HEAD:' + name], cwd=owner)
|
||||
current = (owner / name).read_bytes()
|
||||
if repo == 'access-engine' and name == wp:
|
||||
without_ids = current
|
||||
for value in managed.values():
|
||||
line = f'state_hub_task_id: "{value}"\n'.encode()
|
||||
assert without_ids.count(line) == 1
|
||||
without_ids = without_ids.replace(line, b'')
|
||||
assert without_ids == original, 'only verified registrar IDs may differ'
|
||||
source = Path('/tmp/netkingdom-target-root-review/access-b1') / name
|
||||
reviewed = source.read_bytes()
|
||||
assert hashlib.sha256(reviewed).hexdigest() == files[name]
|
||||
for task, value in managed.items():
|
||||
anchor = ('id: ' + task + '\nstatus: ' + ('done' if task.endswith('T05') else 'progress') + '\npriority: high\n').encode()
|
||||
assert reviewed.count(anchor) == 1
|
||||
reviewed = reviewed.replace(anchor, anchor + f'state_hub_task_id: "{value}"\n'.encode())
|
||||
files[name] = hashlib.sha256(reviewed).hexdigest()
|
||||
else:
|
||||
assert current == original, name
|
||||
subprocess.run(['git', 'apply', '--check', str(patch)], cwd=owner, check=True)
|
||||
prepared.append((repo, owner, folder, patch, patch_hash, head, before, files))
|
||||
for repo, owner, folder, patch, patch_hash, head, before, files in prepared:
|
||||
assert subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=owner, text=True).strip() == head
|
||||
assert all(integration.digest(owner / name) == value for name, value in before.items())
|
||||
subprocess.run(['git', 'apply', str(patch)], cwd=owner, check=True)
|
||||
assert all(integration.digest(owner / name) == value for name, value in files.items())
|
||||
receipt = {'repo': repo, 'base_commit': head, 'patch_sha256': 'sha256:' + patch_hash,
|
||||
'integrated_at': datetime.now(timezone.utc).isoformat(),
|
||||
'files_sha256': {name: 'sha256:' + value for name, value in files.items()},
|
||||
'source_integrated': True, 'committed': False, 'published': False,
|
||||
'native_accepted': False,
|
||||
'preserved_registrar_ids': managed if repo == 'access-engine' else {}}
|
||||
(folder / 'canonical-integration.json').write_text(json.dumps(receipt, indent=2) + '\n')
|
||||
print(repo, 'reviewed source integrated; native acceptance unchanged')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
"""Apply exactly reviewed source; never deploy or confer runtime authority."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
PLAN = json.loads((ROOT / 'source-wave7-integration-plan.json').read_text())
|
||||
|
||||
def git(repo, *args):
|
||||
return subprocess.check_output(['git', *args], cwd=repo, text=True).strip()
|
||||
|
||||
def digest(path):
|
||||
return 'sha256:' + hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
def apply(repo, step):
|
||||
for patch in step['patches']:
|
||||
path = ROOT / patch['path']
|
||||
assert digest(path) == 'sha256:' + patch['sha256']
|
||||
subprocess.run(['git', 'apply', '--check', str(path)], cwd=repo, check=True)
|
||||
subprocess.run(['git', 'apply', str(path)], cwd=repo, check=True)
|
||||
for name, expected in step['files_sha256'].items():
|
||||
assert digest(repo / name) == expected, name
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--apply', action='store_true')
|
||||
args = parser.parse_args()
|
||||
# Check every base and target before any owner mutation. Reconstruct exact final
|
||||
# source independently in disposable directories, including dependent patches.
|
||||
for step in PLAN:
|
||||
repo = Path('/home/worsch') / step['repo']
|
||||
assert git(repo, 'rev-parse', 'HEAD') == step['base'], step['repo']
|
||||
assert not git(repo, 'status', '--porcelain', '--', *step['files_sha256']), step['repo']
|
||||
with tempfile.TemporaryDirectory(prefix='netkingdom-wave7-preflight-') as temp:
|
||||
target = Path(temp)
|
||||
for name in step['files_sha256']:
|
||||
source = repo / name
|
||||
if source.exists():
|
||||
assert source.is_file() and not source.is_symlink(), name
|
||||
(target / name).parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(source, target / name)
|
||||
apply(target, step)
|
||||
print('Preflight passed:', step['repo'], flush=True)
|
||||
if args.apply:
|
||||
for step in PLAN:
|
||||
repo = Path('/home/worsch') / step['repo']
|
||||
assert git(repo, 'rev-parse', 'HEAD') == step['base']
|
||||
assert not git(repo, 'status', '--porcelain', '--', *step['files_sha256'])
|
||||
apply(repo, step)
|
||||
subprocess.run(['git', 'diff', '--check', '--', *step['files_sha256']], cwd=repo, check=True)
|
||||
receipt = dict(step, integrated_at=datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
source_integrated=True, committed=False, published=False,
|
||||
native_accepted=False, scope='Reviewed source only; native custody and admission remain open')
|
||||
for patch in receipt['patches']:
|
||||
patch['sha256'] = 'sha256:' + patch['sha256']
|
||||
(ROOT / step['receipt']).write_text(json.dumps(receipt, indent=2) + '\n')
|
||||
print('Integrated exact source:', step['repo'], flush=True)
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
"""Apply exactly reviewed source; never deploy or confer runtime authority."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
PLAN = json.loads((ROOT / 'source-wave8-integration-plan.json').read_text())
|
||||
|
||||
def git(repo, *args):
|
||||
return subprocess.check_output(['git', *args], cwd=repo, text=True).strip()
|
||||
|
||||
def digest(path):
|
||||
return 'sha256:' + hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
def apply(repo, step):
|
||||
for patch in step['patches']:
|
||||
path = ROOT / patch['path']
|
||||
assert digest(path) == 'sha256:' + patch['sha256']
|
||||
subprocess.run(['git', 'apply', '--check', str(path)], cwd=repo, check=True)
|
||||
subprocess.run(['git', 'apply', str(path)], cwd=repo, check=True)
|
||||
for name, expected in step['files_sha256'].items():
|
||||
assert digest(repo / name) == expected, name
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--apply', action='store_true')
|
||||
args = parser.parse_args()
|
||||
# Check every base and target before any owner mutation. Reconstruct exact final
|
||||
# source independently in disposable directories, including dependent patches.
|
||||
for step in PLAN:
|
||||
repo = Path('/home/worsch') / step['repo']
|
||||
assert git(repo, 'rev-parse', 'HEAD') == step['base'], step['repo']
|
||||
assert all((digest(repo / f) if (repo / f).exists() else None) == h for f, h in step['before_sha256'].items()), step['repo']
|
||||
with tempfile.TemporaryDirectory(prefix='netkingdom-wave8-preflight-') as temp:
|
||||
target = Path(temp)
|
||||
for name in step['files_sha256']:
|
||||
source = repo / name
|
||||
if source.exists():
|
||||
assert source.is_file() and not source.is_symlink(), name
|
||||
(target / name).parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(source, target / name)
|
||||
apply(target, step)
|
||||
print('Preflight passed:', step['repo'], flush=True)
|
||||
if args.apply:
|
||||
for step in PLAN:
|
||||
repo = Path('/home/worsch') / step['repo']
|
||||
assert git(repo, 'rev-parse', 'HEAD') == step['base']
|
||||
assert all((digest(repo / f) if (repo / f).exists() else None) == h for f, h in step['before_sha256'].items()), step['repo']
|
||||
apply(repo, step)
|
||||
subprocess.run(['git', 'diff', '--check', '--', *step['files_sha256']], cwd=repo, check=True)
|
||||
receipt = dict(step, integrated_at=datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
source_integrated=True, committed=False, published=False,
|
||||
native_accepted=False, scope='Reviewed source only; native custody and admission remain open')
|
||||
for patch in receipt['patches']:
|
||||
patch['sha256'] = 'sha256:' + patch['sha256']
|
||||
(ROOT / step['receipt']).write_text(json.dumps(receipt, indent=2) + '\n')
|
||||
print('Integrated exact source:', step['repo'], flush=True)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
"""Integrate the reviewed Warden source revision; no live or trust changes."""
|
||||
from datetime import datetime, timezone
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
ROOT = Path(__file__).resolve().parent / "warden-browser"
|
||||
OWNER = Path("/home/worsch/ops-warden")
|
||||
EXPECTED = "1fb05b513c4db784f8691b5bc2790f5ca396071d4354615d2da7fc505c45684b"
|
||||
|
||||
|
||||
def main():
|
||||
manifest = json.loads((ROOT / "manifest.json").read_text())
|
||||
patch = ROOT / "implementation.patch"
|
||||
assert hashlib.sha256(patch.read_bytes()).hexdigest() == EXPECTED
|
||||
head = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=OWNER, text=True).strip()
|
||||
assert head == manifest["base_commit"], "owner HEAD changed"
|
||||
files = manifest["files"]
|
||||
assert not subprocess.check_output(["git", "diff", "HEAD", "--", *files], cwd=OWNER), "target files changed"
|
||||
for name in files:
|
||||
tracked = subprocess.run(["git", "ls-files", "--error-unmatch", name], cwd=OWNER, capture_output=True).returncode == 0
|
||||
assert tracked or not (OWNER / name).exists(), "untracked target collision"
|
||||
subprocess.run(["git", "apply", "--check", str(patch)], cwd=OWNER, check=True)
|
||||
subprocess.run(["git", "apply", str(patch)], cwd=OWNER, check=True)
|
||||
for name, expected in files.items():
|
||||
assert hashlib.sha256((OWNER / name).read_bytes()).hexdigest() == expected.removeprefix("sha256:"), name
|
||||
receipt = {"repo": "ops-warden", "base_commit": head, "patch_sha256": EXPECTED,
|
||||
"integrated_at": datetime.now(timezone.utc).isoformat(), "files_sha256": files,
|
||||
"source_integrated": True, "committed": False, "published": False,
|
||||
"native_accepted": False, "independent_tests": "102 passed in 12.91s"}
|
||||
(ROOT / "canonical-integration.json").write_text(json.dumps(receipt, indent=2) + "\n")
|
||||
print("ops-warden: five exact reviewed source files integrated")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
Detected: key-cape (/home/worsch/key-cape)
|
||||
ADR-001 Consistency Report
|
||||
Repo: key-cape
|
||||
Path: /home/worsch/key-cape
|
||||
==================================================================
|
||||
|
||||
WARNS (9):
|
||||
C-24
|
||||
Repo classification advisory: capability_tag 'security' is not a recommended family tag (allowed, check for synonym)
|
||||
C-11 [workplans/ADHOC-2026-09-27.md#ADHOC-2026-09-27-T02] [fixable]
|
||||
Task 'ADHOC-2026-09-27-T02' has no state_hub_task_id
|
||||
C-20 [workplans/KEY-WP-0007-user-engine-portal-oidc-client.md]
|
||||
Workplan dependency target 'USER-WP-0020' is not linked to State Hub
|
||||
file='USER-WP-0020' db=''
|
||||
C-20 [workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md]
|
||||
Workplan dependency target 'NK-WP-0025' is not linked to State Hub
|
||||
file='NK-WP-0025' db=''
|
||||
C-20 [workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md]
|
||||
Workplan dependency target 'NK-WP-0030' is not linked to State Hub
|
||||
file='NK-WP-0030' db=''
|
||||
C-20 [workplans/KEY-WP-0010-openbao-operator-loopback-callback.md]
|
||||
Workplan dependency target 'RAILIANCE-WP-0027' is not linked to State Hub
|
||||
file='RAILIANCE-WP-0027' db=''
|
||||
C-22 [workplans/KEY-WP-0038-forgejo-oidc-client.md#KEY-WP-0038-T02] [fixable]
|
||||
Task description drift 'KEY-WP-0038-T02': file content differs from DB (file wins)
|
||||
file='**Changed 2026-10-08 (founder direction):** no case-specific helper script.\nRegistration is the first case of the Securi' db='**Changed 2026-10-08 (founder direction):** no case-specific helper script.\nRegistration is the first case of the Securi'
|
||||
C-28
|
||||
2 unread inbox message(s) older than 3 day(s): the-custodian:23172cb2, the-custodian:51e9eace
|
||||
C-28
|
||||
2 unread message(s) lack thread_id for supersession tracking: the-custodian:23172cb2, the-custodian:51e9eace
|
||||
|
||||
INFOS (1):
|
||||
C-08
|
||||
Retired DB workstream 'keycape-implementation@retired-20260826' (id=2c9caad8…, status=archived) has no backing workplan file — projection tombstone, not live work
|
||||
|
||||
FIXES APPLIED (4):
|
||||
bindings: synced 44/44
|
||||
C-11 skipped: task 'ADHOC-2026-09-27-T02' in finished workstream — not created
|
||||
C-22 fixed: task ddcfb482… description updated
|
||||
push: pushed
|
||||
|
||||
==================================================================
|
||||
0 automation-error | 0 assessment-fail | 9 warn | 1 info
|
||||
RESULT: ✓ PASS (with warnings)
|
||||
==================================================================
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"schema": "keycape.offline-checker-owner-review-packet.v1",
|
||||
"owner_commit": "834b0d06a13bb2ece9cc98392cb39ba0e2167ecb",
|
||||
"files": {
|
||||
"README.md": "sha256:75ed6866b29cbb9f2c5c2a395da2afa25d021f248587c7055bd721e2c55b8685",
|
||||
"consistency-sync.txt": "sha256:6bd932221a782cf819827d791df8ff2dc43731947b7a8f2814702a1282ce2b5c",
|
||||
"owner-checks.json": "sha256:4669c8ac2fcee76926b1419a9fe6c5924abf354c7ebc1fd2ee1b697c6e4f5419",
|
||||
"owner-review.json": "sha256:aa9083e77ea08d166e8cec7c5d73362c19058567c5e80a576766af3745958dc0",
|
||||
"postimages/docs/offline-checker-owner-review-2026-10-16.json": "sha256:aa9083e77ea08d166e8cec7c5d73362c19058567c5e80a576766af3745958dc0",
|
||||
"postimages/docs/offline-checker-owner-review-2026-10-16.md": "sha256:75ed6866b29cbb9f2c5c2a395da2afa25d021f248587c7055bd721e2c55b8685",
|
||||
"postimages/workplans/KEY-WP-0038-forgejo-oidc-client.md": "sha256:dc8d88d462799e7e6e44da759a973d52e44102a271969a5daaa9d30ec015b064",
|
||||
"pre-state.json": "sha256:bd225b77370da39379eea5abc17883aa4e09d30f8093cfdb8caa79a063121873",
|
||||
"preimages/workplans/KEY-WP-0038-forgejo-oidc-client.md": "sha256:9c265970cc230bcbab0d5e07ff94c27c81b9e0c513eb015560e7c756ddcbf7e0",
|
||||
"progress-receipt.json": "sha256:c002472c56ba04866d10c981eb947f0efd4990e5747346b855e2159eddcc18e1",
|
||||
"publication.json": "sha256:b6011ec219e457a16847d37851bb0ed80466a55b0543c8a4b6ff511e879a4ac2",
|
||||
"publish_review.py": "sha256:c4814bc80c55f1b883a9eb1e198e4b42f84ba08c12b2774016dae1fafaac91b1",
|
||||
"review_checks.py": "sha256:a7d5302740797dde2e0e955b78f0aaa98947d048c58f357228deec6f40892d1d",
|
||||
"source-tests-first-attempt.txt": "sha256:9ecee48013519c9e9a0d8e9631574ca11dbf2514606f5914cd390b78c5a2e8ff",
|
||||
"source-tests.json": "sha256:e606adb20b943072d1052f8f9dffd89b78f73c4dd10bd761f29b6cec72fc21a0",
|
||||
"source-tests.txt": "sha256:0d08ecb4bee66fc9d3ca915a9fb508a4b3ac9b553ac013cf9030d8e2d2ff2051"
|
||||
},
|
||||
"backup_files_included": false
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"reviewer": "key-cape-owner (agent key-cape, codex)",
|
||||
"decision": "approved",
|
||||
"scope": "exact source/artifact offline configuration validation behavior only",
|
||||
"comment": "INTENT.md Tooling declaration, Purpose/Primary Utility and Strategic Boundaries; deterministic constrained IAM config validation and delivered-reference boundary. KEY-WP-0038-T02 remains wait. See bound narrative for explicit limits; this is an owner review, not human approval or admission.",
|
||||
"work_item": "KEY-WP-0038-T02",
|
||||
"source_revision": "3c71226a1a7b8a2bdb710e9c497e1a1f7f8a3987",
|
||||
"source_tree": "f0010b9cab17dbc3a787628a29657d98b900320f",
|
||||
"artifact_sha256": "sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5",
|
||||
"artifact_bytes": 13633409,
|
||||
"closure_sha256": "sha256:cb2f8ce0a3ee1efb7099ce00d139ebe00a23a8b356fba160ab8c4b067f5818f9",
|
||||
"review_narrative_sha256": "sha256:75ed6866b29cbb9f2c5c2a395da2afa25d021f248587c7055bd721e2c55b8685",
|
||||
"owner_checks_sha256": "sha256:4669c8ac2fcee76926b1419a9fe6c5924abf354c7ebc1fd2ee1b697c6e4f5419",
|
||||
"source_tests_receipt_sha256": "sha256:e606adb20b943072d1052f8f9dffd89b78f73c4dd10bd761f29b6cec72fc21a0",
|
||||
"candidate_inputs": {
|
||||
"README.md": "sha256:0df0fd7f263481ef706433c320af0d908f1b763557c3b0f5e4beca57408d7d5a",
|
||||
"build_checker.py": "sha256:ea58117252e7cc5328307d3551580dc251780db12e9e8c1600de88633ea24179",
|
||||
"build-receipt.json": "sha256:cc227c17375cd89edb9a1b0304e2cf277a8c8d46370cd71f1c071da57931d3de",
|
||||
"source-toolchain-module-closure.json": "sha256:cb2f8ce0a3ee1efb7099ce00d139ebe00a23a8b356fba160ab8c4b067f5818f9",
|
||||
"artifact-build-info.txt": "sha256:025391cc0fc013352783eedadd34a9bdaf6dc6cf10d5d2cebec7099598a8300a",
|
||||
"verification.json": "sha256:1655501a3124cbb3ebd88cd440d96592ccfd1138e01724b79eaafff69031e464"
|
||||
},
|
||||
"limits": [
|
||||
"two non-secret URL environment overrides remain inputs",
|
||||
"omitted/empty env-names does not check provision",
|
||||
"policy-store flag is caller assertion",
|
||||
"permissive server YAML semantics",
|
||||
"no executable custody, launch descriptors or native/type admission",
|
||||
"no actual identity, signing, upstream or group/consumer proof"
|
||||
],
|
||||
"native_admission": false,
|
||||
"production_authorization": false,
|
||||
"other_owner_roles_reviewed": []
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
# KeyCape owner review: exact offline configuration checker
|
||||
|
||||
Reviewer: `key-cape-owner (agent key-cape, codex)`
|
||||
Decision: `approved` for the bounded source/artifact behavior below
|
||||
Work item: `KEY-WP-0038-T02` (remains `wait`)
|
||||
Date: 2026-10-16
|
||||
|
||||
KeyCape endorses the offline configuration validation behavior of published
|
||||
source `3c71226a1a7b8a2bdb710e9c497e1a1f7f8a3987`, `src` tree
|
||||
`f0010b9cab17dbc3a787628a29657d98b900320f`, and the Linux/amd64 Go 1.23.0
|
||||
artifact SHA256 `be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5`
|
||||
(13,633,409 bytes). This review is an owner judgement, not human approval,
|
||||
production authorization or native admission.
|
||||
|
||||
The INTENT.md basis is KeyCape's Tooling declaration, Purpose/Primary Utility and
|
||||
Strategic Boundaries: deterministic profile-constrained authentication tooling,
|
||||
explicit configuration checks, delivered secret references, and separation from
|
||||
authorization decisions. The review stays within these responsibilities.
|
||||
|
||||
## Source and build evidence
|
||||
|
||||
The candidate's source archive matches the exact published revision and src tree.
|
||||
Independent owner checks matched all 108 archived source files, 9876 cached
|
||||
Go toolchain files and every file in the ten selected module source trees against
|
||||
the closure. The closure JSON SHA256 is
|
||||
`cb2f8ce0a3ee1efb7099ce00d139ebe00a23a8b356fba160ab8c4b067f5818f9`;
|
||||
archive SHA256 is `770a15933e6c45d4b94beb37d4322fd4ae1436d8193dfb7f5655bbc851f3fc73`.
|
||||
The actual executable matches the artifact above. Its command is the existing
|
||||
`./cmd/keycape` target, also built by the owner Makefile/Dockerfile. The retained
|
||||
build uses explicit environment, cached Go 1.23.0, readonly module resolution,
|
||||
CGO disabled, fixed Linux/amd64/v1, trimpath and buildvcs=false, GOPROXY=off and
|
||||
GOSUMDB=off. These are deterministic local input checks, not independent
|
||||
attestation of upstream toolchain/module provenance or host custody.
|
||||
|
||||
`main.go` dispatches `check-config` first, before server config processing,
|
||||
private-key loading, adapter construction, policy-store opening or listener
|
||||
startup. `checkconfig.go` calls the same `config.Load`, `ValidateConfig`, global
|
||||
lifetime parse, and registry conversion used by startup, with a constant
|
||||
placeholder in place of client-secret lookup. `policy.CheckClients` checks
|
||||
registry shape without opening the policy store. Source inspection found no
|
||||
application init path that reads credentials or contacts an upstream for this
|
||||
command. No KeyCape private-key read, client-secret lookup, upstream request,
|
||||
listener, real authentication or signing occurs in this branch.
|
||||
|
||||
Fourteen direct executable checks passed: valid confidential/public registrations
|
||||
without any actual secret/key; bad port/lifetime/query redirect/browser roles;
|
||||
missing declared env name; omitted and empty env-name semantics; required policy
|
||||
client presence with and without policy assertion; non-secret URL environment
|
||||
overlay; synthetic secret non-emission; permissive unknown YAML field handling;
|
||||
and success with an unopened signing-key FIFO plus unused policy path. The three
|
||||
relevant package suites (`cmd/keycape`, `internal/config`, `internal/server/policy`)
|
||||
passed. The first package-suite attempt failed because the candidate src-only
|
||||
archive omitted two committed external config fixtures. It is retained. Rerun
|
||||
added only those fixture paths archived from the exact reviewed revision.
|
||||
The coordinator separately reports reproducing the same artifact and eight
|
||||
adapter fixture checks; that is supporting coordinator evidence, not an owner
|
||||
claim of native or caller integration acceptance.
|
||||
|
||||
## Contract and limitations
|
||||
|
||||
- Always pass the explicit synthetic/config path. This command loads the supplied
|
||||
YAML file; it cannot guarantee the caller supplied only non-secret contents.
|
||||
- `config.Load` reads the non-secret `KEYCAPE_ACCOUNT_PORTAL_URL` and
|
||||
`KEYCAPE_BROWSER_LOGOUT_URL` environment overrides. Validation therefore is not
|
||||
independent of the caller environment. The caller must intentionally supply
|
||||
the desired URL configuration in a minimal environment. This endorsement does
|
||||
not certify the current NetKingdom subprocess environment or its correction.
|
||||
- `--env-names` checks names only when its value is nonempty. Omitted/empty means
|
||||
list required names without proving their provision. Even nonempty assertions
|
||||
prove neither Secret existence, values, mount/ESO delivery nor readable custody.
|
||||
- `--policy-store` asserts the target enables that store and checks its required
|
||||
clients. It proves no path permissions, lock availability or stored state.
|
||||
- The loader follows existing server YAML semantics, including acceptance of
|
||||
unknown fields. Validation is the implemented startup subset, not a complete
|
||||
OIDC/profile policy or schema audit. It does not strengthen every currently
|
||||
accepted redirect/grant/client field.
|
||||
- No key existence/parsing, policy-file access, port binding, upstream readiness,
|
||||
live config equivalence, browser flow, token claims, group enforcement, secret
|
||||
minting/delivery, consumer login or actual human/agent/service identity is proved.
|
||||
- Exact byte identity does not establish immutable executable storage, safe file
|
||||
descriptors, launch custody, type/version acknowledgement, descriptor step-set
|
||||
composition, Warden admission or parent-session revocation. Those remain with
|
||||
the respective owners. No other owner role is endorsed here.
|
||||
|
||||
No blocking defect was found within this exact offline checker scope. The broader
|
||||
browser registration and identity tasks remain open. No production configuration,
|
||||
credentials, cluster mutations, external messages or paid calls were used.
|
||||
|
||||
Reproduction and detailed local receipts are in
|
||||
`the-custodian/docs/changes/netkingdom-target-2026-10-16/keycape-checker-owner-review/`:
|
||||
`review_checks.py`, `owner-checks.json`, `source-tests.json`, `source-tests.txt`
|
||||
and `source-tests-first-attempt.txt`. Original candidate build/closure receipts
|
||||
remain in the adjacent `browser-checker-artifact-next/` packet, including failed
|
||||
closure collection attempts.
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
---
|
||||
id: KEY-WP-0038
|
||||
type: workplan
|
||||
title: "Forgejo as a NetKingdom OIDC client, so the forge can close"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: active
|
||||
flavor: implementation
|
||||
owner: claude-code
|
||||
topic_slug: railiance
|
||||
created: "2026-10-08"
|
||||
updated: "2026-10-16"
|
||||
depends_on: []
|
||||
related_repos:
|
||||
- prj-forgejo-org-refactor
|
||||
- railiance-apps
|
||||
- railiance-platform
|
||||
state_hub_workstream_id: "098e3e2c-a138-560e-b2d3-3a15eb49d8a9"
|
||||
---
|
||||
|
||||
# KEY-WP-0038 - Forgejo as a NetKingdom OIDC client, so the forge can close
|
||||
|
||||
## Why
|
||||
|
||||
`forgejo.coulomb.social` is closing: private by default, no anonymous access
|
||||
(prj-forgejo-org-refactor ORGREF-WP-0002, decision
|
||||
`decisions/forge-visibility-policy.md`). The founder's condition is that Forgejo
|
||||
sign-in runs through NetKingdom **before** anything closes, so that every human,
|
||||
agent and service reader has a governed identity rather than an ad-hoc local
|
||||
account. This plan is ORGREF-WP-0002-T04. Its Forgejo-side counterpart is
|
||||
railiance-apps `RAPPS-WP-0016` (ORGREF-WP-0002-T05), which owns the Forgejo
|
||||
Helm release.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Registration follows the existing human browser-client pattern
|
||||
(`docs/approval-engine-provisioning-request.yaml`, `human_registration`) and the
|
||||
client fields validated in KEY-WP-0028. Redirect URIs are exact, never
|
||||
wildcards.
|
||||
- The client secret goes to OpenBao through railiance-platform custody. No secret
|
||||
value in git, State Hub, logs or chat.
|
||||
- Founder guardrail for the whole closure: tighten gradually, never lock anyone
|
||||
out, never risk assets. Nothing in this plan changes who can read the forge
|
||||
today. It only makes the NetKingdom path available.
|
||||
|
||||
## T01 — Define what Forgejo receives
|
||||
|
||||
```task
|
||||
id: KEY-WP-0038-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "5f019d13-1fa3-5b88-a27a-75a3339ca96e"
|
||||
```
|
||||
|
||||
Write the claim contract Forgejo consumes, as a non-secret request packet under
|
||||
`docs/`:
|
||||
|
||||
- issuer `https://kc.coulomb.social`; confidential client `forgejo`; authorization
|
||||
code with PKCE; scopes `openid profile email groups`
|
||||
- redirect URI
|
||||
`https://forgejo.coulomb.social/user/oauth2/netkingdom/callback`. The
|
||||
authentication-source name `netkingdom` must match railiance-apps'
|
||||
configuration exactly.
|
||||
- subject and username: canonical directory subject, plus a stable
|
||||
`preferred_username` that Forgejo can match to the existing local accounts
|
||||
(founder and agent accounts) for linking
|
||||
- `groups` naming the Forgejo organisations and teams to map to (for example
|
||||
`forge:coulomb:owners`, `forge:coulomb:readers`), and `principal_type` so that
|
||||
human and service principals stay distinguishable
|
||||
- MFA: forge administrators and owners require assurance with a fresh second
|
||||
factor; plain readers follow the client-aware MFA policy (KEY-WP-0008)
|
||||
|
||||
Acceptance: the founder accepts the packet. The group names are agreed with
|
||||
railiance-apps.
|
||||
|
||||
2026-10-08: packet drafted at `docs/forgejo-oidc-client-request.yaml`. It covers a
|
||||
confidential client `forgejo` with PKCE and `mfaRequired: true`, the exact
|
||||
callback, scopes, a custody path, the claims as emitted at this revision, new
|
||||
LLDAP groups `forge-admins`, `forge-coulomb-owners` and `forge-coulomb-readers`
|
||||
with a required-claim gate, no automatic account creation, and linking of local
|
||||
accounts. It also proposes that agents and services use scoped tokens bound to
|
||||
service identities, not browser sign-in.
|
||||
|
||||
2026-10-08: accepted by the founder (in session). railiance-apps' RAPPS-WP-0016
|
||||
uses the same authentication-source name `netkingdom` and the same group names,
|
||||
so the agreement condition holds. Before T02, the LLDAP groups `forge-admins`,
|
||||
`forge-coulomb-owners` and `forge-coulomb-readers` must exist, with the founder
|
||||
in the first two.
|
||||
|
||||
## T02 — Register the client and hand over its secret
|
||||
|
||||
```task
|
||||
id: KEY-WP-0038-T02
|
||||
status: wait
|
||||
depends_on: [KEY-WP-0038-T01, NK-WP-0048-T06]
|
||||
blocking_reason: "Registration runs as the first KnownSecurityCase of type oidc-browser-relying-party (NK-WP-0048-T06), not as a hand-built rollout."
|
||||
priority: high
|
||||
state_hub_task_id: "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30"
|
||||
```
|
||||
|
||||
**Changed 2026-10-08 (founder direction):** no case-specific helper script.
|
||||
Registration is the first case of the SecurityCaseType `oidc-browser-relying-party`
|
||||
(net-kingdom NK-WP-0048, canon `security-case-types_v0.1`). The parameters
|
||||
accepted in T01 become the case; ops-mason materializes it (MASON-WP-0010) and
|
||||
ops-warden follows through (WARDEN-WP-0044). The registration capability is
|
||||
net-kingdom's (`net-kingdom.keycape-client-registration`); key-cape's part is its
|
||||
field rules and reviewing the type's composition.
|
||||
|
||||
The original intent below still describes what must result.
|
||||
|
||||
Register `forgejo` from the packet. Mint the client secret straight into custody
|
||||
(OpenBao path `platform/workloads/keycape/forgejo`, field `CLIENT_SECRET`, derived
|
||||
by the case type since net-kingdom 074b3a0; custody owner railiance-platform,
|
||||
consumers KeyCape via ESO and Forgejo). Record a receipt with no
|
||||
secret value.
|
||||
|
||||
Acceptance: discovery metadata lists the client's redirect URI. The secret is
|
||||
readable by Forgejo's custody path only.
|
||||
|
||||
2026-10-10: key-cape reviewed the type's composition
|
||||
(`docs/oidc-browser-relying-party-type-review.md`). net-kingdom fixed the findings
|
||||
in `61ab6bc`, a 1.0 patch: redirects with `?` or `#` refused, the groups scope
|
||||
required, and descriptions corrected. A new 1.1 (`e2174ce`, founder go) adds the
|
||||
`consumer-enforces-groups` verification. For Forgejo, that is the required-claim
|
||||
setting railiance-apps configures. `security-cases/forgejo-netkingdom-oidc.yaml`
|
||||
is re-pinned to @1.1, with parameters unchanged. KeyCape added `keycape
|
||||
check-config` (`c528daa`), which register-client runs before the restart. T02 still
|
||||
waits on NK-WP-0048-T06, which needs both executors' light paths (MASON-WP-0010-T03,
|
||||
WARDEN-WP-0044-T03).
|
||||
|
||||
### Exact offline checker owner review — 2026-10-16
|
||||
|
||||
KeyCape owner review under KEY-WP-0038-T02 endorses only the offline config
|
||||
validation behavior of source `3c71226a1a7b8a2bdb710e9c497e1a1f7f8a3987`,
|
||||
`src` tree `f0010b9cab17dbc3a787628a29657d98b900320f`, compiled artifact
|
||||
`sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5`.
|
||||
The exact source/archive, 108 source files, 9876 cached Go 1.23.0 toolchain
|
||||
files and all ten selected module trees matched the candidate closure. Fourteen
|
||||
actual artifact checks and the cmd/config/policy Go tests passed using only
|
||||
synthetic inputs. The check dispatches before private-key loading, upstream
|
||||
adapter construction or server startup and uses placeholder client secrets.
|
||||
|
||||
[Owner review](../docs/offline-checker-owner-review-2026-10-16.md) records the
|
||||
exact evidence and limits: non-secret portal/logout URL environment overlays,
|
||||
optional/nonempty env-name assertion and policy-store assertion, permissive YAML
|
||||
loading, and no key/custody/readiness or authentication proof. It does not endorse
|
||||
the caller's inherited environment, executable storage/custody, the NetKingdom
|
||||
descriptor/type, Warden admission or any browser/native identity. Parent review
|
||||
identified caller environment inheritance; NetKingdom owns its correction and
|
||||
regression checks. T02 remains `wait`; T03 and all live acceptance gates remain
|
||||
unchanged. No live config, credentials, cluster objects or paid calls were used.
|
||||
|
||||
## T03 — Prove the claims for each kind of identity
|
||||
|
||||
```task
|
||||
id: KEY-WP-0038-T03
|
||||
status: wait
|
||||
depends_on: [KEY-WP-0038-T02]
|
||||
blocking_reason: "Claims are verified against the registered client."
|
||||
priority: high
|
||||
state_hub_task_id: "40ddaa29-7245-508c-ad48-ddc063e25115"
|
||||
```
|
||||
|
||||
Before Forgejo uses the client, verify the issued ID token and UserInfo for one
|
||||
human (the founder, with MFA), one agent identity and one service identity:
|
||||
subject, `preferred_username`, `groups`, `principal_type`, assurance. Record the
|
||||
claims, minus tokens, as evidence.
|
||||
|
||||
Decide with railiance-apps whether agents and services sign in to Forgejo at
|
||||
all, or receive scoped Forgejo tokens bound to a NetKingdom service identity
|
||||
(ORGREF-WP-0002-T06). Record the decision.
|
||||
|
||||
Acceptance: evidence for all three principals; the agent and service decision is
|
||||
recorded. Then hand over to RAPPS-WP-0016.
|
||||
|
||||
## T04 — Implement the `keycape-client` entry point
|
||||
|
||||
```task
|
||||
id: KEY-WP-0038-T04
|
||||
status: cancel
|
||||
priority: high
|
||||
state_hub_task_id: "583cf1f1-16dc-515f-83bc-1efc45024a89"
|
||||
```
|
||||
|
||||
**Cancelled 2026-10-08: moved to net-kingdom NK-WP-0048-T09 (done).** The live
|
||||
registration machinery (contained kubectl, cluster and issuer pins) already lives
|
||||
in net-kingdom `sso-mfa/k8s/keycape/`, where KeyCape is deployed; a copy here
|
||||
would drift. The entry point is `net-kingdom/sso-mfa/k8s/keycape/keycape-client.py`
|
||||
and the declaration is `net-kingdom.keycape-client-registration`; key-cape's draft
|
||||
declaration is removed. key-cape still owns the client field rules (canonical
|
||||
model) and their enforcement at authorization time.
|
||||
|
||||
`capabilities/playbooks/key-cape.oidc-client-registration.yaml` (contract PASS,
|
||||
`maturity: planned`) declares client registration as a playbook capability, so
|
||||
that the SecurityCaseType `oidc-browser-relying-party` (net-kingdom NK-WP-0048)
|
||||
can compose it. Implement the entry point it names, `tools/keycape-client/`:
|
||||
|
||||
- `keycape-client register --client-file <rendered-client.yaml>` with modes
|
||||
`inspect`, `dry-run`, `apply` and `remove`; adds, updates or removes exactly one
|
||||
client in the live registry; idempotent; never prints or writes secret values;
|
||||
a confidential client's secret is read from its custody reference
|
||||
- one implementation replacing the per-client helpers in net-kingdom
|
||||
`sso-mfa/k8s/keycape/` (register-coulomb-social, portal-client-rollout,
|
||||
vergabe-client-rollout, secrets-engine-openbao-client-rollout), keeping their
|
||||
safeguards (byte-preserving merge, compare-and-swap, cluster pin, safe receipts)
|
||||
and their tests as regression cases
|
||||
- client field validation by the KEY-WP-0028 rules
|
||||
- then set the declaration's `maturity` to `implemented`
|
||||
|
||||
Longer term, loading clients from a non-secret registry with secrets referenced
|
||||
from custody would remove the type's only attended step (see the type README,
|
||||
"Attended steps").
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"head": "3c71226a1a7b8a2bdb710e9c497e1a1f7f8a3987",
|
||||
"status": "M .custodian-brief.md\n M WORK-RECORDS.md\n",
|
||||
"staged_patch_sha256": "41634d8875c0dd90aa489f77831808d517f7fa23be34d9231b099c6157dbd6a3",
|
||||
"staged_entries": "100644 679d1c58d782fa5c3b61d40ab1126cabd86a8ad9 0\t.custodian-brief.md\n100644 d775a2c088659e6d4f483c01f3ed88a25495f6f8 0\tWORK-RECORDS.md\n",
|
||||
"files": {
|
||||
".custodian-brief.md": "0e1be7c07f8ce9ca7f7d92dd16b6b2c2962805f34c2a083aaf0ad72722da4c91",
|
||||
"WORK-RECORDS.md": "02f21a83afddc6de59a7432ecaee6f03c7a800d3f9b321d837d5a5782ccbc692",
|
||||
"workplans/KEY-WP-0038-forgejo-oidc-client.md": "9c265970cc230bcbab0d5e07ff94c27c81b9e0c513eb015560e7c756ddcbf7e0"
|
||||
},
|
||||
"workplan_sha256": "9c265970cc230bcbab0d5e07ff94c27c81b9e0c513eb015560e7c756ddcbf7e0"
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
---
|
||||
id: KEY-WP-0038
|
||||
type: workplan
|
||||
title: "Forgejo as a NetKingdom OIDC client, so the forge can close"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: active
|
||||
flavor: implementation
|
||||
owner: claude-code
|
||||
topic_slug: railiance
|
||||
created: "2026-10-08"
|
||||
updated: "2026-10-14"
|
||||
depends_on: []
|
||||
related_repos:
|
||||
- prj-forgejo-org-refactor
|
||||
- railiance-apps
|
||||
- railiance-platform
|
||||
state_hub_workstream_id: "098e3e2c-a138-560e-b2d3-3a15eb49d8a9"
|
||||
---
|
||||
|
||||
# KEY-WP-0038 - Forgejo as a NetKingdom OIDC client, so the forge can close
|
||||
|
||||
## Why
|
||||
|
||||
`forgejo.coulomb.social` is closing: private by default, no anonymous access
|
||||
(prj-forgejo-org-refactor ORGREF-WP-0002, decision
|
||||
`decisions/forge-visibility-policy.md`). The founder's condition is that Forgejo
|
||||
sign-in runs through NetKingdom **before** anything closes, so that every human,
|
||||
agent and service reader has a governed identity rather than an ad-hoc local
|
||||
account. This plan is ORGREF-WP-0002-T04. Its Forgejo-side counterpart is
|
||||
railiance-apps `RAPPS-WP-0016` (ORGREF-WP-0002-T05), which owns the Forgejo
|
||||
Helm release.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Registration follows the existing human browser-client pattern
|
||||
(`docs/approval-engine-provisioning-request.yaml`, `human_registration`) and the
|
||||
client fields validated in KEY-WP-0028. Redirect URIs are exact, never
|
||||
wildcards.
|
||||
- The client secret goes to OpenBao through railiance-platform custody. No secret
|
||||
value in git, State Hub, logs or chat.
|
||||
- Founder guardrail for the whole closure: tighten gradually, never lock anyone
|
||||
out, never risk assets. Nothing in this plan changes who can read the forge
|
||||
today. It only makes the NetKingdom path available.
|
||||
|
||||
## T01 — Define what Forgejo receives
|
||||
|
||||
```task
|
||||
id: KEY-WP-0038-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "5f019d13-1fa3-5b88-a27a-75a3339ca96e"
|
||||
```
|
||||
|
||||
Write the claim contract Forgejo consumes, as a non-secret request packet under
|
||||
`docs/`:
|
||||
|
||||
- issuer `https://kc.coulomb.social`; confidential client `forgejo`; authorization
|
||||
code with PKCE; scopes `openid profile email groups`
|
||||
- redirect URI
|
||||
`https://forgejo.coulomb.social/user/oauth2/netkingdom/callback`. The
|
||||
authentication-source name `netkingdom` must match railiance-apps'
|
||||
configuration exactly.
|
||||
- subject and username: canonical directory subject, plus a stable
|
||||
`preferred_username` that Forgejo can match to the existing local accounts
|
||||
(founder and agent accounts) for linking
|
||||
- `groups` naming the Forgejo organisations and teams to map to (for example
|
||||
`forge:coulomb:owners`, `forge:coulomb:readers`), and `principal_type` so that
|
||||
human and service principals stay distinguishable
|
||||
- MFA: forge administrators and owners require assurance with a fresh second
|
||||
factor; plain readers follow the client-aware MFA policy (KEY-WP-0008)
|
||||
|
||||
Acceptance: the founder accepts the packet. The group names are agreed with
|
||||
railiance-apps.
|
||||
|
||||
2026-10-08: packet drafted at `docs/forgejo-oidc-client-request.yaml`. It covers a
|
||||
confidential client `forgejo` with PKCE and `mfaRequired: true`, the exact
|
||||
callback, scopes, a custody path, the claims as emitted at this revision, new
|
||||
LLDAP groups `forge-admins`, `forge-coulomb-owners` and `forge-coulomb-readers`
|
||||
with a required-claim gate, no automatic account creation, and linking of local
|
||||
accounts. It also proposes that agents and services use scoped tokens bound to
|
||||
service identities, not browser sign-in.
|
||||
|
||||
2026-10-08: accepted by the founder (in session). railiance-apps' RAPPS-WP-0016
|
||||
uses the same authentication-source name `netkingdom` and the same group names,
|
||||
so the agreement condition holds. Before T02, the LLDAP groups `forge-admins`,
|
||||
`forge-coulomb-owners` and `forge-coulomb-readers` must exist, with the founder
|
||||
in the first two.
|
||||
|
||||
## T02 — Register the client and hand over its secret
|
||||
|
||||
```task
|
||||
id: KEY-WP-0038-T02
|
||||
status: wait
|
||||
depends_on: [KEY-WP-0038-T01, NK-WP-0048-T06]
|
||||
blocking_reason: "Registration runs as the first KnownSecurityCase of type oidc-browser-relying-party (NK-WP-0048-T06), not as a hand-built rollout."
|
||||
priority: high
|
||||
state_hub_task_id: "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30"
|
||||
```
|
||||
|
||||
**Changed 2026-10-08 (founder direction):** no case-specific helper script.
|
||||
Registration is the first case of the SecurityCaseType `oidc-browser-relying-party`
|
||||
(net-kingdom NK-WP-0048, canon `security-case-types_v0.1`). The parameters
|
||||
accepted in T01 become the case; ops-mason materializes it (MASON-WP-0010) and
|
||||
ops-warden follows through (WARDEN-WP-0044). The registration capability is
|
||||
net-kingdom's (`net-kingdom.keycape-client-registration`); key-cape's part is its
|
||||
field rules and reviewing the type's composition.
|
||||
|
||||
The original intent below still describes what must result.
|
||||
|
||||
Register `forgejo` from the packet. Mint the client secret straight into custody
|
||||
(OpenBao path `platform/workloads/keycape/forgejo`, field `CLIENT_SECRET`, derived
|
||||
by the case type since net-kingdom 074b3a0; custody owner railiance-platform,
|
||||
consumers KeyCape via ESO and Forgejo). Record a receipt with no
|
||||
secret value.
|
||||
|
||||
Acceptance: discovery metadata lists the client's redirect URI. The secret is
|
||||
readable by Forgejo's custody path only.
|
||||
|
||||
2026-10-10: key-cape reviewed the type's composition
|
||||
(`docs/oidc-browser-relying-party-type-review.md`). net-kingdom fixed the findings
|
||||
in `61ab6bc`, a 1.0 patch: redirects with `?` or `#` refused, the groups scope
|
||||
required, and descriptions corrected. A new 1.1 (`e2174ce`, founder go) adds the
|
||||
`consumer-enforces-groups` verification. For Forgejo, that is the required-claim
|
||||
setting railiance-apps configures. `security-cases/forgejo-netkingdom-oidc.yaml`
|
||||
is re-pinned to @1.1, with parameters unchanged. KeyCape added `keycape
|
||||
check-config` (`c528daa`), which register-client runs before the restart. T02 still
|
||||
waits on NK-WP-0048-T06, which needs both executors' light paths (MASON-WP-0010-T03,
|
||||
WARDEN-WP-0044-T03).
|
||||
|
||||
## T03 — Prove the claims for each kind of identity
|
||||
|
||||
```task
|
||||
id: KEY-WP-0038-T03
|
||||
status: wait
|
||||
depends_on: [KEY-WP-0038-T02]
|
||||
blocking_reason: "Claims are verified against the registered client."
|
||||
priority: high
|
||||
state_hub_task_id: "40ddaa29-7245-508c-ad48-ddc063e25115"
|
||||
```
|
||||
|
||||
Before Forgejo uses the client, verify the issued ID token and UserInfo for one
|
||||
human (the founder, with MFA), one agent identity and one service identity:
|
||||
subject, `preferred_username`, `groups`, `principal_type`, assurance. Record the
|
||||
claims, minus tokens, as evidence.
|
||||
|
||||
Decide with railiance-apps whether agents and services sign in to Forgejo at
|
||||
all, or receive scoped Forgejo tokens bound to a NetKingdom service identity
|
||||
(ORGREF-WP-0002-T06). Record the decision.
|
||||
|
||||
Acceptance: evidence for all three principals; the agent and service decision is
|
||||
recorded. Then hand over to RAPPS-WP-0016.
|
||||
|
||||
## T04 — Implement the `keycape-client` entry point
|
||||
|
||||
```task
|
||||
id: KEY-WP-0038-T04
|
||||
status: cancel
|
||||
priority: high
|
||||
state_hub_task_id: "583cf1f1-16dc-515f-83bc-1efc45024a89"
|
||||
```
|
||||
|
||||
**Cancelled 2026-10-08: moved to net-kingdom NK-WP-0048-T09 (done).** The live
|
||||
registration machinery (contained kubectl, cluster and issuer pins) already lives
|
||||
in net-kingdom `sso-mfa/k8s/keycape/`, where KeyCape is deployed; a copy here
|
||||
would drift. The entry point is `net-kingdom/sso-mfa/k8s/keycape/keycape-client.py`
|
||||
and the declaration is `net-kingdom.keycape-client-registration`; key-cape's draft
|
||||
declaration is removed. key-cape still owns the client field rules (canonical
|
||||
model) and their enforcement at authorization time.
|
||||
|
||||
`capabilities/playbooks/key-cape.oidc-client-registration.yaml` (contract PASS,
|
||||
`maturity: planned`) declares client registration as a playbook capability, so
|
||||
that the SecurityCaseType `oidc-browser-relying-party` (net-kingdom NK-WP-0048)
|
||||
can compose it. Implement the entry point it names, `tools/keycape-client/`:
|
||||
|
||||
- `keycape-client register --client-file <rendered-client.yaml>` with modes
|
||||
`inspect`, `dry-run`, `apply` and `remove`; adds, updates or removes exactly one
|
||||
client in the live registry; idempotent; never prints or writes secret values;
|
||||
a confidential client's secret is read from its custody reference
|
||||
- one implementation replacing the per-client helpers in net-kingdom
|
||||
`sso-mfa/k8s/keycape/` (register-coulomb-social, portal-client-rollout,
|
||||
vergabe-client-rollout, secrets-engine-openbao-client-rollout), keeping their
|
||||
safeguards (byte-preserving merge, compare-and-swap, cluster pin, safe receipts)
|
||||
and their tests as regression cases
|
||||
- client field validation by the KEY-WP-0028 rules
|
||||
- then set the declaration's `maturity` to `implemented`
|
||||
|
||||
Longer term, loading clients from a non-secret registry with secrets referenced
|
||||
from custody would remove the type's only attended step (see the type README,
|
||||
"Attended steps").
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"id": "3ca7c13f-bf66-47cd-9915-98f6c84f94f6",
|
||||
"created_at": "2026-10-16T20:08:56.912300Z",
|
||||
"workplan_id": "098e3e2c-a138-560e-b2d3-3a15eb49d8a9",
|
||||
"task_id": "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30"
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
"""Race-checked owner docs publication; preserve prior staging and local files."""
|
||||
import hashlib, json, pathlib, subprocess, tempfile
|
||||
|
||||
P = pathlib.Path(__file__).resolve().parent
|
||||
R = pathlib.Path('/home/worsch/key-cape')
|
||||
before = json.loads((P / 'pre-state.json').read_text())
|
||||
git = lambda *args: subprocess.check_output(['git', '-C', str(R), *args])
|
||||
digest = lambda b: hashlib.sha256(b).hexdigest()
|
||||
assert git('rev-parse', 'HEAD').decode().strip() == before['head'], 'HEAD raced'
|
||||
assert git('status', '--porcelain=v1').decode() == before['status'], 'status raced'
|
||||
assert digest(git('diff', '--cached', '--binary')) == before['staged_patch_sha256'], 'staging raced'
|
||||
for name, sha in before['files'].items():
|
||||
assert digest((R / name).read_bytes()) == sha, 'file raced: ' + name
|
||||
paths = ['docs/offline-checker-owner-review-2026-10-16.md', 'docs/offline-checker-owner-review-2026-10-16.json', 'workplans/KEY-WP-0038-forgejo-oidc-client.md']
|
||||
assert not any((R / n).exists() for n in paths[:2]), 'review destination exists'
|
||||
# Separate /tmp backup of user-owned files; never include backups in packet.
|
||||
backup = pathlib.Path(tempfile.mkdtemp(prefix='keycape-review-preserve-'))
|
||||
preserve = {n: (R / n).read_bytes() for n in ['.custodian-brief.md', 'WORK-RECORDS.md']}
|
||||
for n, data in preserve.items():
|
||||
(backup / n).write_bytes(data)
|
||||
for name in paths:
|
||||
(R / name).write_bytes((P / 'postimages' / name).read_bytes())
|
||||
subprocess.run(['git', '-C', str(R), 'diff', '--check'], check=True)
|
||||
subprocess.run(['git', '-C', str(R), 'add', '--', *paths], check=True)
|
||||
subprocess.run(['git', '-C', str(R), 'commit', '--only', '-m', 'Review exact offline checker behavior for browser registration', '--', *paths], check=True)
|
||||
commit = git('rev-parse', 'HEAD').decode().strip()
|
||||
assert digest(git('diff', '--cached', '--binary')) == before['staged_patch_sha256'], 'original staged diff changed'
|
||||
assert git('ls-files', '--stage', '.custodian-brief.md', 'WORK-RECORDS.md').decode() == before['staged_entries'], 'original index entries changed'
|
||||
for n, data in preserve.items():
|
||||
assert (R / n).read_bytes() == data, 'original worktree changed'
|
||||
subprocess.run(['git', '-C', str(R), 'push', 'origin', 'HEAD:main'], check=True)
|
||||
# Required owner file -> hub sync; keep its generated output off the user index.
|
||||
sync = subprocess.run(['statehub', 'fix-consistency'], cwd=R, capture_output=True, text=True)
|
||||
(P / 'consistency-sync.txt').write_text(sync.stdout + sync.stderr)
|
||||
assert git('ls-files', '--stage', '.custodian-brief.md', 'WORK-RECORDS.md').decode() == before['staged_entries'], 'sync changed original index entries'
|
||||
for n, data in preserve.items():
|
||||
# These are the sync's known generated outputs, restored to exact pre-state.
|
||||
if (R / n).read_bytes() != data:
|
||||
(R / n).write_bytes(data)
|
||||
assert digest(git('diff', '--cached', '--binary')) == before['staged_patch_sha256']
|
||||
assert git('status', '--porcelain=v1').decode() == before['status']
|
||||
assert all((R / n).read_bytes() == data for n, data in preserve.items())
|
||||
receipt = {'commit': commit, 'published_origin': git('rev-parse', 'origin/main').decode().strip(), 'paths': paths, 'original_staged_patch_preserved': True, 'original_generated_worktree_preserved': True, 'post_status': git('status', '--porcelain=v1').decode(), 'consistency_exit_code': sync.returncode, 'hooks_bypassed': False, 'native_admission': False}
|
||||
(P / 'publication.json').write_text(json.dumps(receipt, indent=2) + '\n')
|
||||
print(json.dumps(receipt, indent=2))
|
||||
raise SystemExit(sync.returncode)
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
"""Owner offline checks against exact candidate; synthetic inputs only."""
|
||||
import hashlib, json, os, pathlib, subprocess, tempfile
|
||||
|
||||
HERE = pathlib.Path(__file__).resolve().parent
|
||||
CANDIDATE = HERE.parent / 'browser-checker-artifact-next'
|
||||
OWNER = pathlib.Path('/home/worsch/key-cape')
|
||||
BUILD = pathlib.Path('/tmp/nk-checker-artifact-next-r3')
|
||||
sha = lambda b: 'sha256:' + hashlib.sha256(b).hexdigest()
|
||||
receipt = json.loads((CANDIDATE / 'build-receipt.json').read_text())
|
||||
closure_bytes = (CANDIDATE / 'source-toolchain-module-closure.json').read_bytes()
|
||||
assert sha(closure_bytes) == receipt['closure_sha256']
|
||||
closure = json.loads(closure_bytes)
|
||||
verified = {}
|
||||
for label, root, files in [
|
||||
('source', BUILD / 'src', closure['source']),
|
||||
('toolchain', pathlib.Path(receipt['go_binary']).parent.parent, closure['toolchain']),
|
||||
] + [(m['path'], pathlib.Path(receipt['environment']['GOMODCACHE']) / (m['path'].replace('Azure', '!azure') + '@' + m['version']), m['files']) for m in closure['modules']]:
|
||||
actual = {str(p.relative_to(root)): sha(p.read_bytes()) for p in root.rglob('*') if p.is_file()}
|
||||
assert actual == files, label
|
||||
verified[label] = len(files)
|
||||
archive = subprocess.check_output(['git', '-C', str(OWNER), 'archive', '--format=tar', receipt['keycape_revision'], 'src'])
|
||||
assert sha(archive) == receipt['archive_sha256']
|
||||
assert sha((BUILD / 'keycape').read_bytes()) == receipt['artifact_sha256']
|
||||
assert subprocess.check_output(['git', '-C', str(OWNER), 'rev-parse', receipt['keycape_revision'] + ':src'], text=True).strip() == receipt['keycape_source_tree']
|
||||
|
||||
base = '''issuer: https://synthetic.example.invalid
|
||||
port: 8080
|
||||
privateKeyPem: /nonexistent/synthetic-key.pem
|
||||
clients:
|
||||
- clientId: user-engine-portal
|
||||
displayName: Portal
|
||||
redirectUris: ["https://portal.example.invalid/cb"]
|
||||
allowedScopes: ["openid"]
|
||||
grantTypes: ["authorization_code"]
|
||||
clientType: public
|
||||
- clientId: vergabe-demo-company
|
||||
displayName: Vergabe
|
||||
redirectUris: ["https://vergabe.example.invalid/cb"]
|
||||
allowedScopes: ["openid"]
|
||||
grantTypes: ["authorization_code"]
|
||||
clientType: confidential
|
||||
secretRef: env:SYNTHETIC_CLIENT_SECRET
|
||||
'''
|
||||
cases = [
|
||||
('valid_without_secret_or_key', base, ['--env-names', 'SYNTHETIC_CLIENT_SECRET', '--policy-store'], {}, True),
|
||||
('bad_port', base.replace('port: 8080', 'port: 0'), [], {}, False),
|
||||
('bad_global_lifetime', base + 'tokenLifetime: bogus\n', [], {}, False),
|
||||
('bad_redirect_query', base.replace('/cb', '/cb?x=1', 1), [], {}, False),
|
||||
('ignored_browser_roles', base.replace(' clientType: public', ' roles: [admin]\n clientType: public', 1), [], {}, False),
|
||||
('missing_declared_env', base, ['--env-names', 'OTHER'], {}, False),
|
||||
('omitted_env_names_only_lists', base, [], {}, True),
|
||||
('empty_env_names_only_lists', base, ['--env-names', ''], {}, True),
|
||||
('missing_policy_client', base.replace('clientId: user-engine-portal', 'clientId: renamed'), ['--policy-store'], {}, False),
|
||||
('policy_client_not_asserted', base.replace('clientId: user-engine-portal', 'clientId: renamed'), [], {}, True),
|
||||
('nonsecret_environment_overlay', base, [], {'KEYCAPE_ACCOUNT_PORTAL_URL': 'http://invalid.example.invalid'}, False),
|
||||
('synthetic_secret_not_emitted', base, ['--env-names', 'SYNTHETIC_CLIENT_SECRET'], {'SYNTHETIC_CLIENT_SECRET': 'synthetic-value-never-emit'}, True),
|
||||
('unknown_yaml_field_same_loader', base + 'unknownReviewField: true\n', [], {}, True),
|
||||
]
|
||||
results = []
|
||||
with tempfile.TemporaryDirectory(prefix='keycape-owner-offline-') as tmp:
|
||||
config = pathlib.Path(tmp) / 'config.yaml'
|
||||
fifo = pathlib.Path(tmp) / 'unreadable-key-fifo'
|
||||
os.mkfifo(fifo)
|
||||
cases.append(('key_fifo_and_policy_path_not_read', base.replace('/nonexistent/synthetic-key.pem', str(fifo)), ['--policy-store'], {'KEYCAPE_POLICY_PATH': str(fifo)}, True))
|
||||
for name, content, args, extra, valid in cases:
|
||||
config.write_text(content)
|
||||
result = subprocess.run([str(BUILD / 'keycape'), 'check-config', '--config', str(config), *args], env={'PATH': '/usr/bin:/bin', **extra}, capture_output=True, text=True, timeout=5)
|
||||
report = json.loads(result.stdout)
|
||||
assert report['valid'] is valid and result.returncode == (0 if valid else 1), name
|
||||
assert 'synthetic-value-never-emit' not in result.stdout + result.stderr
|
||||
results.append({'name': name, 'exit_code': result.returncode, 'report': report})
|
||||
output = {'schema': 'keycape.offline-checker-owner-checks.v1', 'source_revision': receipt['keycape_revision'], 'source_tree': receipt['keycape_source_tree'], 'artifact_sha256': receipt['artifact_sha256'], 'closure_sha256': receipt['closure_sha256'], 'closure_verified_files': verified, 'checks': results, 'passed': len(results), 'native_admission': False}
|
||||
(HERE / 'owner-checks.json').write_text(json.dumps(output, indent=2) + '\n')
|
||||
print(json.dumps({'passed': len(results), 'closure_verified_files': verified}))
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
--- FAIL: TestServiceRegistrationAudienceAndScopeIsolation (0.00s)
|
||||
clients_test.go:15: config: read "../../../config/service-clients.example.yaml": open ../../../config/service-clients.example.yaml: no such file or directory
|
||||
--- FAIL: TestServiceRegistrationTenantsAreExactPerDecision (0.00s)
|
||||
clients_test.go:55: config: read "../../../config/service-clients.example.yaml": open ../../../config/service-clients.example.yaml: no such file or directory
|
||||
--- FAIL: TestApproverRegistrationShapeIsExact (0.00s)
|
||||
clients_test.go:95: config: read "../../../config/service-clients.example.yaml": open ../../../config/service-clients.example.yaml: no such file or directory
|
||||
--- FAIL: TestSittingRequesterRegistrationShapeIsExact (0.00s)
|
||||
clients_test.go:164: config: read "../../../config/service-clients.example.yaml": open ../../../config/service-clients.example.yaml: no such file or directory
|
||||
FAIL
|
||||
FAIL keycape/cmd/keycape 0.028s
|
||||
--- FAIL: TestServiceClientExampleContracts (0.00s)
|
||||
config_test.go:381: load service client examples: config: read "../../../config/service-clients.example.yaml": open ../../../config/service-clients.example.yaml: no such file or directory
|
||||
--- FAIL: TestDevConfigOpenBaoAdminAdmitsOperatorTunnelCallback (0.00s)
|
||||
config_test.go:416: load dev config: config: read "../../../config/dev-config.yaml": open ../../../config/dev-config.yaml: no such file or directory
|
||||
FAIL
|
||||
FAIL keycape/internal/config 0.102s
|
||||
ok keycape/internal/server/policy 0.184s
|
||||
FAIL
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
ok keycape/cmd/keycape 0.025s
|
||||
ok keycape/internal/config 0.076s
|
||||
ok keycape/internal/server/policy (cached)
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
# Mason browser-case source compatibility
|
||||
|
||||
Owner work: **MASON-WP-0010-T03**, first consumer **NK-WP-0048-T06 / TSA-04**.
|
||||
Isolated checkout `/tmp/netkingdom-mason-browser-20261016`, source base
|
||||
`e4761b843cd881a672cbb33a5b451de31a61d730`; canonical owner dirt preserved.
|
||||
|
||||
The actual Python APIs already accept an explicit `types_root` and can generate
|
||||
the sixth consumer role. The shipped CLI did not expose that argument. The patch
|
||||
passes `--types-root` through `plan`, `build` and `structure-request`, retaining
|
||||
NetKingdom's exact-version loader, default 1.1 selection and current admission.
|
||||
No new descriptor schema or structure protocol was introduced.
|
||||
|
||||
`forgejo-1.2-draft-plan.md` and `forgejo-1.2-source-render.json` are actual source
|
||||
projections of the acknowledged 1.2 type and its proposed Forgejo example. They
|
||||
have six objects and the dedicated consumer ESO role in namespace `forgejo`.
|
||||
They are not a reviewed plan, build bundle, structural Request or native receipt.
|
||||
The actual 1.2 case has no acceptance; this worker did not fabricate one to make
|
||||
build/request preparation succeed. The legacy 1.1 bundle remains historical,
|
||||
including its known incorrect consumer workload target. The current generator
|
||||
already fixes that target; reproducing the historical file as new evidence would
|
||||
be incorrect.
|
||||
|
||||
Validation: **50 passed** (six new actual-source checks plus 44 existing plan,
|
||||
build and structure tests), `git diff --check` passes. Ruff was unavailable.
|
||||
New checks cover actual six-object draft rendering; default-root version refusal;
|
||||
ordinary unimplemented refusal; proposed-case build refusal before any output;
|
||||
historical bundle refusal under 1.2; and malformed consumer namespace refusal by
|
||||
the actual NetKingdom validator. No live calls, credentials or effects occurred.
|
||||
|
||||
`implementation.patch` changes only the Mason CLI, tests, existing owner document
|
||||
and existing workplan. Apply after review to ops-mason, rerun the scoped command
|
||||
in `validation.json`, then run owner State Hub consistency through the coordinator.
|
||||
No task status or acceptance promotion is included. No duplicate workplan exists.
|
||||
|
||||
## Contracts still required from the existing owners
|
||||
|
||||
- NetKingdom and the actual case acceptors: the exact accepted 1.2 case and reviewed
|
||||
current plan before Mason builds a six-object bundle/Request; do not carry
|
||||
historical 1.1 approval forward or label an offline plan reviewed.
|
||||
- Platform + Mason: authenticate `ops-mason.case-structure-request.v1` observations
|
||||
for each exact policy, role and fixed layout; distinguish created from matched
|
||||
preexisting objects; reconcile CCR41/CCR42 source bindings and owner admission.
|
||||
`StatusBinding` needs independent owner authentication binding the accepted and
|
||||
materialized raw case hashes. The injected test verifier supplies no authority.
|
||||
- KeyCape + NetKingdom: accepted exact adapter source/descriptor for groups,
|
||||
client registration and environment/registry checks, with current case/type
|
||||
pins, native UID/resource-version preconditions, attributed observations and
|
||||
created-only undo. No descriptor or owner assent is authored here.
|
||||
- Platform: broker mint descriptor with exact actor/lane/source/catalog/attempt
|
||||
pins and separate broker-grant/parent-session revocation evidence; metadata-only
|
||||
delivery/status/undo adapter with created-object ownership and token revocation
|
||||
or independently proved expiry. Already-present outcomes require reconciliation.
|
||||
- Forge/consumer owner (railiance-apps): admitted consumer ESO binding/deployment
|
||||
and Forgejo configuration adapter or authenticated receipt-only step; independent
|
||||
successful sign-in, denied-group, revocation and rollback observations.
|
||||
- NetKingdom + Warden: actual owner-accepted descriptors in a reviewed type
|
||||
revision and authenticated admission of original receipts. A source checksum,
|
||||
unsigned journal row or generated inventory is not native acceptance.
|
||||
|
||||
The first case remains unmaterialized/unaccepted for native completion. Remaining
|
||||
work stays in MASON-WP-0010-T03, NK-WP-0048-T06 and their existing owner records.
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
---
|
||||
id: case-forgejo-netkingdom-oidc
|
||||
demand_source: SecurityCase forgejo-netkingdom-oidc (owner railiance-apps)
|
||||
consumer_repo: railiance-apps
|
||||
credential_type: security-case
|
||||
derived_from_type: oidc-browser-relying-party@1.2
|
||||
render_digest: sha256:c0afd5576cdae0cbc807eccb2b9fa87ac668f15e32ec7cf79d295bdb23c75b35
|
||||
status: draft
|
||||
case_acceptance: null
|
||||
reference_case: true
|
||||
approved_by: null
|
||||
approved_at: null
|
||||
---
|
||||
|
||||
# Construction plan (derived-from-type): forgejo-netkingdom-oidc
|
||||
|
||||
Rendered from `oidc-browser-relying-party@1.2` (Browser OIDC relying party for an internal service). Do not edit by hand:
|
||||
re-run `ops-mason plan --case` instead. Render digest `sha256:c0afd5576cdae0cbc807eccb2b9fa87ac668f15e32ec7cf79d295bdb23c75b35`.
|
||||
|
||||
**Reference case:** the type is `acknowledged`, not yet `implemented`. This case
|
||||
is the one whose live materialization takes the type to `implemented`.
|
||||
|
||||
## 1. Demand
|
||||
|
||||
Let an internal web service sign its human users in through NetKingdom (KeyCape): an exact-redirect OIDC client, optional MFA pin, the LLDAP groups the service maps to its own roles, and, for confidential clients, a client secret minted straight into OpenBao custody. Replaces the per-client rollout scripts in sso-mfa/k8s/keycape. First case: Forgejo (KEY-WP-0038).
|
||||
|
||||
## 2. Existing-structure survey
|
||||
|
||||
Survey source: none (metadata only).
|
||||
|
||||
No collision with existing policies, roles, KV paths or catalog entries.
|
||||
|
||||
## 3. Objects (from the type's materialization)
|
||||
|
||||
| Item | Kind | Name |
|
||||
|---|---|---|
|
||||
| client-secret-path | kv_path | `platform/workloads/keycape/forgejo` |
|
||||
| keycape-reader-policy | openbao_policy | `workload-kv-read-keycape-forgejo-eso` |
|
||||
| consumer-reader-policy | openbao_policy | `workload-kv-read-keycape-forgejo-forgejo` |
|
||||
| eso-role | openbao_role | `external-secrets-keycape-forgejo` |
|
||||
| consumer-eso-role | openbao_role | `external-secrets-keycape-forgejo-forgejo` |
|
||||
| catalog-entry | catalog_entry | `forgejo-keycape-client` |
|
||||
|
||||
### client-secret-path
|
||||
|
||||
```
|
||||
field CLIENT_SECRET; minted by the credential broker, never written by hand
|
||||
```
|
||||
|
||||
### keycape-reader-policy
|
||||
|
||||
```
|
||||
path "platform/data/workloads/keycape/forgejo" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
```
|
||||
|
||||
### consumer-reader-policy
|
||||
|
||||
```
|
||||
path "platform/data/workloads/keycape/forgejo" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
```
|
||||
|
||||
### eso-role
|
||||
|
||||
```
|
||||
auth: kubernetes
|
||||
bound_service_account_names: [external-secrets]
|
||||
bound_service_account_namespaces: [external-secrets]
|
||||
token_policies: [workload-kv-read-keycape-forgejo-eso]
|
||||
token_no_default_policy: true
|
||||
token_ttl: 15m
|
||||
token_max_ttl: 15m
|
||||
```
|
||||
|
||||
### consumer-eso-role
|
||||
|
||||
```
|
||||
auth: kubernetes
|
||||
bound_service_account_names: [forgejo-keycape-forgejo-eso]
|
||||
bound_service_account_namespaces: [forgejo]
|
||||
audience: openbao
|
||||
token_policies: [workload-kv-read-keycape-forgejo-forgejo]
|
||||
token_no_default_policy: true
|
||||
token_ttl: 15m
|
||||
token_max_ttl: 15m
|
||||
```
|
||||
|
||||
### catalog-entry
|
||||
|
||||
```
|
||||
id: forgejo-keycape-client
|
||||
owner_repo: key-cape
|
||||
warden_executes: false
|
||||
risk: high
|
||||
status: active once verification passes
|
||||
workload_ref: applicable, name forgejo, declaration_ref of the consumer's published declaration or the explicit unknown form, never inferred
|
||||
path_template: platform/workloads/keycape/forgejo
|
||||
field: CLIENT_SECRET
|
||||
delegation: native, intended_owner key-cape
|
||||
rotation: mint-secret then verification; automatable false until a run has been proven
|
||||
```
|
||||
|
||||
## 4. Review notes
|
||||
|
||||
Shape reviewed once, when the type was acknowledged; this review checks only
|
||||
this case's names against existing structure (section 2).
|
||||
|
||||
Follow-through (ops-warden):
|
||||
|
||||
- ensure-groups — net-kingdom (attended): identity-provisioner ensure_service_groups, run inside the provisioner pod through the session's contained cluster access (groups forge-admins,forge-coulomb-owners,forge-coulomb-readers; members exactly forge-admins=tegwick;forge-coulomb-owners=tegwick as recorded in the accepted case, nothing else)
|
||||
- mint-secret — railiance-platform: railiance-platform credential broker: generate a client secret inside custody at bao:platform/workloads/keycape/forgejo
|
||||
- deliver-secret — railiance-platform: ExternalSecret sso/keycape-forgejo-client (key client-secret) from bao:platform/workloads/keycape/forgejo
|
||||
- bind-env — net-kingdom (attended): patch Deployment sso/keycape: env KEYCAPE_FORGEJO_CLIENT_SECRET from sso/keycape-forgejo-client
|
||||
- register-client — net-kingdom (attended): sso-mfa/k8s/keycape/keycape-client.py apply --client-file <client-from-case> --case-acceptance <record> --check-config <keycape binary> (net-kingdom.keycape-client-registration)
|
||||
- deliver-consumer-secret — railiance-apps: Owner applies dedicated ServiceAccount forgejo/forgejo-keycape-forgejo-eso (automount=false, no legacy tokens), namespace SecretStore openbao-keycape-forgejo-consumer with serviceAccountRef and audiences [openbao], and ExternalSecret forgejo-netkingdom-oidc-client with Owner target, Retain remote deletion and only CLIENT_SECRET from workloads/keycape/forgejo. Admission validates actual installed ESO TokenRequest support and common controller RBAC before any owner-approved apply.
|
||||
- hand-over — railiance-apps: configure forgejo with issuer https://kc.coulomb.social, client forgejo and custody bao:platform/workloads/keycape/forgejo, and make it require one of forge-admins,forge-coulomb-owners,forge-coulomb-readers in the groups claim before sign-in completes
|
||||
|
||||
Verification:
|
||||
|
||||
- authorize-accepts-redirects (present): GET https://kc.coulomb.social/authorize with client forgejo and each of https://forgejo.coulomb.social/user/oauth2/netkingdom/callback returns the login page
|
||||
- authorize-refuses-other-redirect (deny): GET https://kc.coulomb.social/authorize with client forgejo and an unregistered redirect returns an OAuth error
|
||||
- custody-denied-to-default (deny): a default-only OpenBao identity has no read capability on bao:platform/workloads/keycape/forgejo
|
||||
- custody-allowed-to-consumer (allow): policy workload-kv-read-keycape-forgejo-forgejo grants read on bao:platform/workloads/keycape/forgejo (capabilities check only)
|
||||
- groups-present (present): LLDAP groups forge-admins,forge-coulomb-owners,forge-coulomb-readers exist, with exactly the memberships of the accepted case added
|
||||
- consumer-enforces-groups (present): forgejo's OIDC source for forgejo (owner railiance-apps) requires one of forge-admins,forge-coulomb-owners,forge-coulomb-readers in the groups claim (configuration read, no secret value)
|
||||
- secret-delivered (present): the delivery for sso/keycape-forgejo-client reports Ready=True (status only)
|
||||
- mfa-pinned (present): keycape-client.py inspect for forgejo shows the MFA pin on the registered client (non-secret client view)
|
||||
- consumer-secret-delivered (present): forgejo SecretStore openbao-keycape-forgejo-consumer and ExternalSecret forgejo-netkingdom-oidc-client Ready=True, exact dedicated SA/audience role binding, fixed CLIENT_SECRET mapping; no Secret value read
|
||||
|
||||
Rollback, in order:
|
||||
|
||||
- remove the OIDC source for forgejo from forgejo (owner railiance-apps)
|
||||
- After reverting consumer secret reference, delete only authenticated created ExternalSecret/owned target by UID and ownerReference, then SecretStore and dedicated SA; preserve preexisting-matched resources and never delete a shared target. Retain audit and ownership receipts; removing SecretStore/SA alone is not immediate OpenBao token revocation.
|
||||
- keycape-client.py apply --operation remove for forgejo
|
||||
- remove env KEYCAPE_FORGEJO_CLIENT_SECRET from Deployment sso/keycape, after register-client is reversed
|
||||
- delete ExternalSecret sso/keycape-forgejo-client and its target
|
||||
- covered by client-secret-path: one metadata delete, after deliver-secret is reversed
|
||||
- identity-provisioner revoke_service_groups inside the provisioner pod (attended, as ensure-groups) with the case's memberships; delete_if_empty only if no other case uses forge-admins,forge-coulomb-owners,forge-coulomb-readers
|
||||
- delete metadata and all versions at platform/workloads/keycape/forgejo, after the ExternalSecret is gone (this also reverses mint-secret)
|
||||
- delete Kubernetes-auth role external-secrets-keycape-forgejo
|
||||
- delete policy workload-kv-read-keycape-forgejo-eso, after eso-role
|
||||
- Delete only this case-created role external-secrets-keycape-forgejo-forgejo, before deleting consumer-reader-policy; revoke case-issued OpenBao tokens or prove expiration before declaring deactivation.
|
||||
- delete policy workload-kv-read-keycape-forgejo-forgejo
|
||||
- remove catalog entry forgejo-keycape-client
|
||||
|
||||
## 5. Case acceptance (replaces the executive summary)
|
||||
|
||||
Not accepted yet. The case must carry its acceptance record before a build.
|
||||
|
|
@ -0,0 +1,385 @@
|
|||
{
|
||||
"apiVersion": "netkingdom.io/security-case-render/v0.1",
|
||||
"kind": "SecurityCaseRender",
|
||||
"case": "forgejo-netkingdom-oidc",
|
||||
"type": "oidc-browser-relying-party@1.2",
|
||||
"composition": {
|
||||
"apiVersion": "netkingdom.io/security-scenario-composition/v0.1",
|
||||
"kind": "SecurityScenarioComposition",
|
||||
"scenario": "scenario:case:forgejo-netkingdom-oidc",
|
||||
"authority": "netkingdom",
|
||||
"composition_digest": "sha256:f51e45eab2f2c15c93e2f7b8ef7d8fcc1daf1059da5749429ff5678b45c5214f",
|
||||
"requested_capabilities": [
|
||||
"c1.lightweight-sso"
|
||||
],
|
||||
"capability_providers": {
|
||||
"c1.lightweight-sso": "net-kingdom.keycape-client-registration"
|
||||
},
|
||||
"initial_trust": [
|
||||
"runtime_identity_trust",
|
||||
"runtime_secret_trust"
|
||||
],
|
||||
"planned_final_trust": [
|
||||
"runtime_identity_trust",
|
||||
"runtime_secret_trust",
|
||||
"tenant_onboarding_trust"
|
||||
],
|
||||
"execution": {
|
||||
"mode": "plan-only",
|
||||
"permitted": false,
|
||||
"reason": "Composition does not authorize or perform provider execution."
|
||||
},
|
||||
"execution_steps": [
|
||||
{
|
||||
"order": 1,
|
||||
"declaration_id": "net-kingdom.keycape-client-registration",
|
||||
"executor_owner": "net-kingdom",
|
||||
"repo": "net-kingdom",
|
||||
"capabilities": [
|
||||
"c1.lightweight-sso"
|
||||
],
|
||||
"entry_point": {
|
||||
"path": "sso-mfa/k8s/keycape/keycape-client.py",
|
||||
"type": "cli-sequence",
|
||||
"invocation": "keycape-client.py apply --client-file <client.yaml> --case-acceptance <record> --expected-uid <uid> --expected-resource-version <rv>",
|
||||
"description": "Add or remove exactly one client in the live KeyCape client registry without exporting secret values: contained kubectl, cluster and issuer pins, byte-preserving merge, compare-and-swap, read-back. Replaces the per-client helpers beside it. Field rules follow key-cape's canonical model (KEY-WP-0028). Modes: inspect, dry-run, apply; operations add and remove."
|
||||
},
|
||||
"parameters": {
|
||||
"allowed_scopes": {
|
||||
"value": "openid,profile,email,groups",
|
||||
"source": "override",
|
||||
"sensitivity": "security_sensitive",
|
||||
"tuning_authority": "netkingdom_tunable"
|
||||
},
|
||||
"client_id": {
|
||||
"value": "forgejo",
|
||||
"source": "override",
|
||||
"sensitivity": "operational",
|
||||
"tuning_authority": "netkingdom_tunable"
|
||||
},
|
||||
"client_type": {
|
||||
"value": "confidential",
|
||||
"source": "override",
|
||||
"sensitivity": "security_sensitive",
|
||||
"tuning_authority": "netkingdom_tunable"
|
||||
},
|
||||
"display_name": {
|
||||
"value": "Forgejo source forge",
|
||||
"source": "override",
|
||||
"sensitivity": "public",
|
||||
"tuning_authority": "netkingdom_tunable"
|
||||
},
|
||||
"mfa_required": {
|
||||
"value": "true",
|
||||
"source": "override",
|
||||
"sensitivity": "security_sensitive",
|
||||
"tuning_authority": "netkingdom_tunable"
|
||||
},
|
||||
"redirect_uris": {
|
||||
"value": "https://forgejo.coulomb.social/user/oauth2/netkingdom/callback",
|
||||
"source": "override",
|
||||
"sensitivity": "security_sensitive",
|
||||
"tuning_authority": "netkingdom_tunable"
|
||||
}
|
||||
},
|
||||
"requires_trust": [
|
||||
{
|
||||
"state": "runtime_secret_trust",
|
||||
"readiness_checks": []
|
||||
},
|
||||
{
|
||||
"state": "runtime_identity_trust",
|
||||
"readiness_checks": []
|
||||
}
|
||||
],
|
||||
"satisfies_trust": [
|
||||
{
|
||||
"state": "tenant_onboarding_trust",
|
||||
"readiness_checks": [
|
||||
{
|
||||
"id": "client-authorize-accepts-registered-redirect",
|
||||
"description": "The authorization endpoint accepts the client with each registered redirect URI.",
|
||||
"evidence": "GET /authorize with client_id and each redirect_uri returns the login page, not an error."
|
||||
},
|
||||
{
|
||||
"id": "client-authorize-refuses-other-redirect",
|
||||
"description": "The authorization endpoint refuses an unregistered redirect URI for the client.",
|
||||
"evidence": "GET /authorize with an unregistered redirect_uri returns an OAuth error."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"readiness_obligations": [
|
||||
{
|
||||
"state": "tenant_onboarding_trust",
|
||||
"checks": [
|
||||
{
|
||||
"id": "client-authorize-accepts-registered-redirect",
|
||||
"description": "The authorization endpoint accepts the client with each registered redirect URI.",
|
||||
"evidence": "GET /authorize with client_id and each redirect_uri returns the login page, not an error."
|
||||
},
|
||||
{
|
||||
"id": "client-authorize-refuses-other-redirect",
|
||||
"description": "The authorization endpoint refuses an unregistered redirect URI for the client.",
|
||||
"evidence": "GET /authorize with an unregistered redirect_uri returns an OAuth error."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"responsibility_map": [
|
||||
{
|
||||
"declaration_id": "net-kingdom.keycape-client-registration",
|
||||
"resource_kind": "identities",
|
||||
"owner": "net-kingdom",
|
||||
"resources": [
|
||||
"oidc:static-clients"
|
||||
],
|
||||
"repo_owns": "Live registry change for exactly this client, its idempotency and removal; field rules are key-cape's canonical model.",
|
||||
"netkingdom_orchestrates": "Supply the case's validated, non-secret client fields."
|
||||
},
|
||||
{
|
||||
"declaration_id": "net-kingdom.keycape-client-registration",
|
||||
"resource_kind": "roles_scopes_policies",
|
||||
"owner": "key-cape",
|
||||
"resources": [
|
||||
"iam-profile:client-scope-allowlists"
|
||||
],
|
||||
"repo_owns": "Enforce exact redirect URIs, allowed scopes and the MFA pin at authorization time.",
|
||||
"netkingdom_orchestrates": "Choose scopes and MFA posture per SecurityCaseType."
|
||||
}
|
||||
]
|
||||
},
|
||||
"materialization": [
|
||||
{
|
||||
"id": "client-secret-path",
|
||||
"kind": "kv_path",
|
||||
"template": "platform/workloads/keycape/forgejo",
|
||||
"body": "field CLIENT_SECRET; minted by the credential broker, never written by hand"
|
||||
},
|
||||
{
|
||||
"id": "keycape-reader-policy",
|
||||
"kind": "openbao_policy",
|
||||
"template": "workload-kv-read-keycape-forgejo-eso",
|
||||
"body": "path \"platform/data/workloads/keycape/forgejo\" {\n capabilities = [\"read\"]\n}\n",
|
||||
"description": "KeyCape reads its client secret only through ESO. No metadata, list, parent or sibling access."
|
||||
},
|
||||
{
|
||||
"id": "consumer-reader-policy",
|
||||
"kind": "openbao_policy",
|
||||
"template": "workload-kv-read-keycape-forgejo-forgejo",
|
||||
"body": "path \"platform/data/workloads/keycape/forgejo\" {\n capabilities = [\"read\"]\n}\n",
|
||||
"description": "The consuming service reads the same secret. No metadata, list, parent or sibling access."
|
||||
},
|
||||
{
|
||||
"id": "eso-role",
|
||||
"kind": "openbao_role",
|
||||
"template": "external-secrets-keycape-forgejo",
|
||||
"body": "auth: kubernetes\nbound_service_account_names: [external-secrets]\nbound_service_account_namespaces: [external-secrets]\ntoken_policies: [workload-kv-read-keycape-forgejo-eso]\ntoken_no_default_policy: true\ntoken_ttl: 15m\ntoken_max_ttl: 15m\n",
|
||||
"description": "The Kubernetes-auth role deliver-secret uses; bound to the ESO service account only."
|
||||
},
|
||||
{
|
||||
"id": "consumer-eso-role",
|
||||
"kind": "openbao_role",
|
||||
"template": "external-secrets-keycape-forgejo-forgejo",
|
||||
"body": "auth: kubernetes\nbound_service_account_names: [forgejo-keycape-forgejo-eso]\nbound_service_account_namespaces: [forgejo]\naudience: openbao\ntoken_policies: [workload-kv-read-keycape-forgejo-forgejo]\ntoken_no_default_policy: true\ntoken_ttl: 15m\ntoken_max_ttl: 15m\n",
|
||||
"description": "Consumer ESO requests a token for this separate reference identity. Common ESO controller remains trusted and can request tokens; no cryptographic controller isolation is claimed."
|
||||
},
|
||||
{
|
||||
"id": "catalog-entry",
|
||||
"kind": "catalog_entry",
|
||||
"template": "forgejo-keycape-client",
|
||||
"body": "id: forgejo-keycape-client\nowner_repo: key-cape\nwarden_executes: false\nrisk: high\nstatus: active once verification passes\nworkload_ref: applicable, name forgejo, declaration_ref of the consumer's published declaration or the explicit unknown form, never inferred\npath_template: platform/workloads/keycape/forgejo\nfield: CLIENT_SECRET\ndelegation: native, intended_owner key-cape\nrotation: mint-secret then verification; automatable false until a run has been proven\n",
|
||||
"description": "One entry per case (ops-mason item 5, ops-warden R4'). It carries the consumer's workload reference, which ADR-0009 needs for zone selection."
|
||||
}
|
||||
],
|
||||
"follow_through": [
|
||||
{
|
||||
"id": "ensure-groups",
|
||||
"owner": "net-kingdom",
|
||||
"entry_point": "identity-provisioner ensure_service_groups, run inside the provisioner pod through the session's contained cluster access (groups forge-admins,forge-coulomb-owners,forge-coulomb-readers; members exactly forge-admins=tegwick;forge-coulomb-owners=tegwick as recorded in the accepted case, nothing else)",
|
||||
"auth": "operator cluster access in the attended register-client session; the in-pod call uses the provisioner's own directory binding, and no provisioner or user-engine token is read or sent",
|
||||
"lane": "key-cape-oidc-login",
|
||||
"warden_executes": false,
|
||||
"attended": true,
|
||||
"value_handling": "none",
|
||||
"attended_justification": "The provisioner's only HTTP credential is user-engine's runtime token, and its NetworkPolicy admits only user-engine pods. A proxy lane would act as user-engine (ops-warden review R8; canon section 3; ops-warden ADR-0002). Running it in the session register-client already needs adds no attended session. It can become unattended once the provisioner has an operator credential of its own and an admitted path."
|
||||
},
|
||||
{
|
||||
"id": "mint-secret",
|
||||
"owner": "railiance-platform",
|
||||
"entry_point": "railiance-platform credential broker: generate a client secret inside custody at bao:platform/workloads/keycape/forgejo",
|
||||
"auth": "the caller's broker grant for the case's KV path, through its lane; the value is generated in custody and never returned",
|
||||
"lane": "keycape-client-secret-generate",
|
||||
"warden_executes": true,
|
||||
"attended": false,
|
||||
"value_handling": "generated_in_custody"
|
||||
},
|
||||
{
|
||||
"id": "deliver-secret",
|
||||
"owner": "railiance-platform",
|
||||
"entry_point": "ExternalSecret sso/keycape-forgejo-client (key client-secret) from bao:platform/workloads/keycape/forgejo",
|
||||
"auth": "railiance-platform ESO delivery through role external-secrets-keycape-forgejo; no value leaves custody",
|
||||
"warden_executes": false,
|
||||
"attended": false,
|
||||
"value_handling": "none",
|
||||
"ready_check": "the delivery for sso/keycape-forgejo-client reports Ready=True (status only, no value read)"
|
||||
},
|
||||
{
|
||||
"id": "bind-env",
|
||||
"owner": "net-kingdom",
|
||||
"entry_point": "patch Deployment sso/keycape: env KEYCAPE_FORGEJO_CLIENT_SECRET from sso/keycape-forgejo-client",
|
||||
"auth": "operator kube credentials for namespace sso",
|
||||
"lane": "key-cape-oidc-login",
|
||||
"warden_executes": false,
|
||||
"attended": true,
|
||||
"value_handling": "none",
|
||||
"attended_justification": "A change to the live identity provider's Deployment. It runs in the same attended operator session as register-client, before it, because KeyCape resolves a confidential client's secret at start and fails closed if the binding is missing."
|
||||
},
|
||||
{
|
||||
"id": "register-client",
|
||||
"owner": "net-kingdom",
|
||||
"entry_point": "sso-mfa/k8s/keycape/keycape-client.py apply --client-file <client-from-case> --case-acceptance <record> --check-config <keycape binary> (net-kingdom.keycape-client-registration)",
|
||||
"auth": "operator write access to the live KeyCape client registry in namespace sso",
|
||||
"lane": "key-cape-oidc-login",
|
||||
"warden_executes": false,
|
||||
"attended": true,
|
||||
"value_handling": "none",
|
||||
"attended_justification": "The live KeyCape client registry is a Kubernetes Secret, and agents do not read or write Secrets. Until KeyCape loads clients from a non-secret registry with secrets referenced from custody, one attended operator session applies the rendered registration. ops-warden groups all attended steps of a run into that one session. KeyCape loads clients at startup and keeps login, authorization and handoff state in memory, so the restart drops in-flight logins and sessions for every client; runs go in a quiet window (key-cape type review R4). The rendered registry must pass KeyCape's own startup checks (keycape check-config, key-cape c528daa, which keycape-client.py runs before it patches) before the restart: a refused entry stops the single replica and every client loses sign-in (R1)."
|
||||
},
|
||||
{
|
||||
"id": "deliver-consumer-secret",
|
||||
"owner": "railiance-apps",
|
||||
"entry_point": "Owner applies dedicated ServiceAccount forgejo/forgejo-keycape-forgejo-eso (automount=false, no legacy tokens), namespace SecretStore openbao-keycape-forgejo-consumer with serviceAccountRef and audiences [openbao], and ExternalSecret forgejo-netkingdom-oidc-client with Owner target, Retain remote deletion and only CLIENT_SECRET from workloads/keycape/forgejo. Admission validates actual installed ESO TokenRequest support and common controller RBAC before any owner-approved apply.",
|
||||
"auth": "Consumer owner contained Kubernetes deployment authority; ESO authenticates to OpenBao role external-secrets-keycape-forgejo-forgejo with a TokenRequest for the dedicated namespaced serviceAccountRef; controller remains trusted",
|
||||
"warden_executes": false,
|
||||
"attended": false,
|
||||
"value_handling": "none",
|
||||
"ready_check": "Namespace SecretStore and ExternalSecret both report Ready=True; dedicated SA has no legacy token refs; status and fixed metadata only, never Secret values."
|
||||
},
|
||||
{
|
||||
"id": "hand-over",
|
||||
"owner": "railiance-apps",
|
||||
"entry_point": "configure forgejo with issuer https://kc.coulomb.social, client forgejo and custody bao:platform/workloads/keycape/forgejo, and make it require one of forge-admins,forge-coulomb-owners,forge-coulomb-readers in the groups claim before sign-in completes",
|
||||
"auth": "the consumer owner's own deployment path",
|
||||
"warden_executes": false,
|
||||
"attended": false,
|
||||
"value_handling": "none"
|
||||
}
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"id": "authorize-accepts-redirects",
|
||||
"check": "GET https://kc.coulomb.social/authorize with client forgejo and each of https://forgejo.coulomb.social/user/oauth2/netkingdom/callback returns the login page",
|
||||
"expect": "present",
|
||||
"reads_secret_value": false
|
||||
},
|
||||
{
|
||||
"id": "authorize-refuses-other-redirect",
|
||||
"check": "GET https://kc.coulomb.social/authorize with client forgejo and an unregistered redirect returns an OAuth error",
|
||||
"expect": "deny",
|
||||
"reads_secret_value": false
|
||||
},
|
||||
{
|
||||
"id": "custody-denied-to-default",
|
||||
"check": "a default-only OpenBao identity has no read capability on bao:platform/workloads/keycape/forgejo",
|
||||
"expect": "deny",
|
||||
"reads_secret_value": false
|
||||
},
|
||||
{
|
||||
"id": "custody-allowed-to-consumer",
|
||||
"check": "policy workload-kv-read-keycape-forgejo-forgejo grants read on bao:platform/workloads/keycape/forgejo (capabilities check only)",
|
||||
"expect": "allow",
|
||||
"reads_secret_value": false
|
||||
},
|
||||
{
|
||||
"id": "groups-present",
|
||||
"check": "LLDAP groups forge-admins,forge-coulomb-owners,forge-coulomb-readers exist, with exactly the memberships of the accepted case added",
|
||||
"expect": "present",
|
||||
"reads_secret_value": false
|
||||
},
|
||||
{
|
||||
"id": "consumer-enforces-groups",
|
||||
"check": "forgejo's OIDC source for forgejo (owner railiance-apps) requires one of forge-admins,forge-coulomb-owners,forge-coulomb-readers in the groups claim (configuration read, no secret value)",
|
||||
"expect": "present",
|
||||
"reads_secret_value": false
|
||||
},
|
||||
{
|
||||
"id": "secret-delivered",
|
||||
"check": "the delivery for sso/keycape-forgejo-client reports Ready=True (status only)",
|
||||
"expect": "present",
|
||||
"reads_secret_value": false
|
||||
},
|
||||
{
|
||||
"id": "mfa-pinned",
|
||||
"check": "keycape-client.py inspect for forgejo shows the MFA pin on the registered client (non-secret client view)",
|
||||
"expect": "present",
|
||||
"reads_secret_value": false
|
||||
},
|
||||
{
|
||||
"id": "consumer-secret-delivered",
|
||||
"check": "forgejo SecretStore openbao-keycape-forgejo-consumer and ExternalSecret forgejo-netkingdom-oidc-client Ready=True, exact dedicated SA/audience role binding, fixed CLIENT_SECRET mapping; no Secret value read",
|
||||
"expect": "present",
|
||||
"reads_secret_value": false
|
||||
}
|
||||
],
|
||||
"rollback": [
|
||||
{
|
||||
"reverses": "hand-over",
|
||||
"action": "remove the OIDC source for forgejo from forgejo (owner railiance-apps)"
|
||||
},
|
||||
{
|
||||
"reverses": "deliver-consumer-secret",
|
||||
"action": "After reverting consumer secret reference, delete only authenticated created ExternalSecret/owned target by UID and ownerReference, then SecretStore and dedicated SA; preserve preexisting-matched resources and never delete a shared target. Retain audit and ownership receipts; removing SecretStore/SA alone is not immediate OpenBao token revocation."
|
||||
},
|
||||
{
|
||||
"reverses": "register-client",
|
||||
"action": "keycape-client.py apply --operation remove for forgejo"
|
||||
},
|
||||
{
|
||||
"reverses": "bind-env",
|
||||
"action": "remove env KEYCAPE_FORGEJO_CLIENT_SECRET from Deployment sso/keycape, after register-client is reversed"
|
||||
},
|
||||
{
|
||||
"reverses": "deliver-secret",
|
||||
"action": "delete ExternalSecret sso/keycape-forgejo-client and its target"
|
||||
},
|
||||
{
|
||||
"reverses": "mint-secret",
|
||||
"action": "covered by client-secret-path: one metadata delete, after deliver-secret is reversed"
|
||||
},
|
||||
{
|
||||
"reverses": "ensure-groups",
|
||||
"action": "identity-provisioner revoke_service_groups inside the provisioner pod (attended, as ensure-groups) with the case's memberships; delete_if_empty only if no other case uses forge-admins,forge-coulomb-owners,forge-coulomb-readers"
|
||||
},
|
||||
{
|
||||
"reverses": "client-secret-path",
|
||||
"action": "delete metadata and all versions at platform/workloads/keycape/forgejo, after the ExternalSecret is gone (this also reverses mint-secret)"
|
||||
},
|
||||
{
|
||||
"reverses": "eso-role",
|
||||
"action": "delete Kubernetes-auth role external-secrets-keycape-forgejo"
|
||||
},
|
||||
{
|
||||
"reverses": "keycape-reader-policy",
|
||||
"action": "delete policy workload-kv-read-keycape-forgejo-eso, after eso-role"
|
||||
},
|
||||
{
|
||||
"reverses": "consumer-eso-role",
|
||||
"action": "Delete only this case-created role external-secrets-keycape-forgejo-forgejo, before deleting consumer-reader-policy; revoke case-issued OpenBao tokens or prove expiration before declaring deactivation."
|
||||
},
|
||||
{
|
||||
"reverses": "consumer-reader-policy",
|
||||
"action": "delete policy workload-kv-read-keycape-forgejo-forgejo"
|
||||
},
|
||||
{
|
||||
"reverses": "catalog-entry",
|
||||
"action": "remove catalog entry forgejo-keycape-client"
|
||||
}
|
||||
],
|
||||
"execution": {
|
||||
"permitted": false
|
||||
},
|
||||
"digest": "sha256:c0afd5576cdae0cbc807eccb2b9fa87ac668f15e32ec7cf79d295bdb23c75b35"
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
diff --git a/docs/forgejo-structural-materialization-source-v1.md b/docs/forgejo-structural-materialization-source-v1.md
|
||||
index 9175c5c..c4be07f 100644
|
||||
--- a/docs/forgejo-structural-materialization-source-v1.md
|
||||
+++ b/docs/forgejo-structural-materialization-source-v1.md
|
||||
@@ -115,3 +115,31 @@ binding/application; mint is removed from its structural readiness blocker.
|
||||
WARDEN-WP-0044-T03 and NK-WP-0048-T06 retain native follow-through, full current
|
||||
run digest/machine source binding, verification and first live-case acceptance.
|
||||
No case/type status, credential, policy, catalog activation or workload was changed.
|
||||
+
|
||||
+## Explicit source-root selection — 2026-10-16
|
||||
+
|
||||
+`plan`, `build` and `structure-request` accept `--types-root` and pass it to
|
||||
+NetKingdom's existing exact-version loader. The default remains
|
||||
+`<netkingdom-root>/security-case-types`; a requested version never falls forward
|
||||
+or backward. Provider declarations still resolve from the NetKingdom repository's
|
||||
+siblings, independently of the selected type root.
|
||||
+
|
||||
+Prepare the acknowledged 1.2 source projection without a live survey:
|
||||
+
|
||||
+```bash
|
||||
+ops-mason plan --case ../net-kingdom/security-case-type-revisions/1.2/oidc-browser-relying-party/examples/forgejo.yaml --types-root ../net-kingdom/security-case-type-revisions/1.2 --no-live --allow-unimplemented
|
||||
+```
|
||||
+
|
||||
+This actual case is proposed and has no acceptance. Its six-object draft includes
|
||||
+the dedicated consumer ESO role bound to namespace `forgejo` and service account
|
||||
+`forgejo-keycape-forgejo-eso`. Source-root selection grants no acceptance: normal
|
||||
+admission still refuses the acknowledged type; `build` refuses the proposed case,
|
||||
+and `structure-request` refuses the historical 1.1 plan/bundle for it. A current
|
||||
+accepted case and reviewed plan are needed before regenerating a build/request.
|
||||
+Do not repin historical acceptance or relabel an offline plan as reviewed.
|
||||
+
|
||||
+Six integration checks exercise these paths against the actual owner validator
|
||||
+and source package, including malformed namespace refusal. This CLI compatibility
|
||||
+change does not supply the owner-native contracts listed above. Machine descriptors,
|
||||
+source receipt authentication, current CCR42 binding and native six-object proof
|
||||
+remain with MASON-WP-0010-T03 / NK-WP-0048-T06 and their existing owners.
|
||||
diff --git a/src/ops_mason/cli.py b/src/ops_mason/cli.py
|
||||
index 2f0210a..3731077 100644
|
||||
--- a/src/ops_mason/cli.py
|
||||
+++ b/src/ops_mason/cli.py
|
||||
@@ -53,6 +53,7 @@ def _parser() -> argparse.ArgumentParser:
|
||||
help="skip the live OpenBao survey (marks the plan's survey source 'none')",
|
||||
)
|
||||
plan.add_argument("--netkingdom-root", type=Path, default=None)
|
||||
+ plan.add_argument("--types-root", type=Path, help="explicit exact-version SecurityCaseType root")
|
||||
plan.add_argument(
|
||||
"--allow-unimplemented",
|
||||
action="store_true",
|
||||
@@ -74,6 +75,7 @@ def _parser() -> argparse.ArgumentParser:
|
||||
help="reference case only: build against an acknowledged type",
|
||||
)
|
||||
build.add_argument("--netkingdom-root", type=Path, default=None)
|
||||
+ build.add_argument("--types-root", type=Path, help="explicit exact-version SecurityCaseType root")
|
||||
structure = families.add_parser(
|
||||
"structure-request", help="read-only typed structural evidence request"
|
||||
)
|
||||
@@ -81,6 +83,7 @@ def _parser() -> argparse.ArgumentParser:
|
||||
structure.add_argument("--plan", required=True, type=Path)
|
||||
structure.add_argument("--bundle", required=True, type=Path)
|
||||
structure.add_argument("--netkingdom-root", type=Path)
|
||||
+ structure.add_argument("--types-root", type=Path, help="explicit exact-version SecurityCaseType root")
|
||||
structure.add_argument("--allow-unimplemented", action="store_true")
|
||||
return parser
|
||||
|
||||
@@ -101,6 +104,7 @@ def _build(args) -> int:
|
||||
args.plan,
|
||||
out,
|
||||
nk_root=nk_root,
|
||||
+ types_root=args.types_root,
|
||||
allow_unimplemented=args.allow_unimplemented,
|
||||
)
|
||||
except (
|
||||
@@ -134,6 +138,7 @@ def _plan(args) -> int:
|
||||
doc, collisions, rendered = case_plan.plan_from_case(
|
||||
args.case,
|
||||
nk_root=nk_root,
|
||||
+ types_root=args.types_root,
|
||||
structure=case_plan.Structure() if args.no_live else None,
|
||||
allow_unimplemented=args.allow_unimplemented,
|
||||
)
|
||||
@@ -171,6 +176,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
args.plan,
|
||||
args.bundle,
|
||||
nk_root=args.netkingdom_root or case_plan.DEFAULT_NK_ROOT,
|
||||
+ types_root=args.types_root,
|
||||
allow_unimplemented=args.allow_unimplemented,
|
||||
)
|
||||
except Exception:
|
||||
diff --git a/tests/test_case_version_source.py b/tests/test_case_version_source.py
|
||||
new file mode 100644
|
||||
index 0000000..49aee70
|
||||
--- /dev/null
|
||||
+++ b/tests/test_case_version_source.py
|
||||
@@ -0,0 +1,84 @@
|
||||
+"""Actual NetKingdom 1.2 source selection; no accepted/native case fixture."""
|
||||
+import json
|
||||
+from pathlib import Path
|
||||
+
|
||||
+import pytest
|
||||
+import yaml
|
||||
+
|
||||
+from ops_mason import case_plan, cli
|
||||
+
|
||||
+NK = case_plan.DEFAULT_NK_ROOT
|
||||
+TYPES = NK / "security-case-type-revisions/1.2"
|
||||
+CASE = TYPES / "oidc-browser-relying-party/examples/forgejo.yaml"
|
||||
+pytestmark = pytest.mark.skipif(not CASE.is_file(), reason="NetKingdom 1.2 source absent")
|
||||
+
|
||||
+
|
||||
+def test_explicit_source_root_renders_actual_six_object_draft(tmp_path, capsys):
|
||||
+ output = tmp_path / "plan.md"
|
||||
+ assert cli.main(["plan", "--case", str(CASE), "--types-root", str(TYPES),
|
||||
+ "--no-live", "--allow-unimplemented", "--out", str(output)]) == 0
|
||||
+ plan = output.read_text()
|
||||
+ front = yaml.safe_load(plan.split("---\n", 2)[1])
|
||||
+ assert front["status"] == "draft"
|
||||
+ assert front["case_acceptance"] is None
|
||||
+ assert front["derived_from_type"] == "oidc-browser-relying-party@1.2"
|
||||
+ assert front["reference_case"] is True
|
||||
+ case, doc, rendered = case_plan.render(CASE, types_root=TYPES, allow_unimplemented=True)
|
||||
+ assert case["spec"]["status"] == "proposed"
|
||||
+ assert "acceptance" not in case["spec"]
|
||||
+ assert doc["spec"]["status"] == "acknowledged"
|
||||
+ assert len(rendered["materialization"]) == 6
|
||||
+ role = next(item for item in rendered["materialization"]
|
||||
+ if item["id"] == "consumer-eso-role")
|
||||
+ body = yaml.safe_load(role["body"])
|
||||
+ assert body["bound_service_account_namespaces"] == ["forgejo"]
|
||||
+ assert body["bound_service_account_names"] == ["forgejo-keycape-forgejo-eso"]
|
||||
+ assert front["render_digest"] == rendered["digest"]
|
||||
+ assert json.loads(capsys.readouterr().err)["ok"] is True
|
||||
+
|
||||
+
|
||||
+def test_explicit_root_does_not_waive_implemented_gate(tmp_path, capsys):
|
||||
+ output = tmp_path / "plan.md"
|
||||
+ assert cli.main(["plan", "--case", str(CASE), "--types-root", str(TYPES),
|
||||
+ "--no-live", "--out", str(output)]) == 2
|
||||
+ assert not output.exists()
|
||||
+ assert "only implemented types" in capsys.readouterr().err
|
||||
+
|
||||
+
|
||||
+def test_missing_explicit_root_never_falls_forward_to_12(tmp_path, capsys):
|
||||
+ output = tmp_path / "plan.md"
|
||||
+ assert cli.main(["plan", "--case", str(CASE), "--no-live",
|
||||
+ "--allow-unimplemented", "--out", str(output)]) == 2
|
||||
+ assert not output.exists()
|
||||
+ assert "not 1.2" in capsys.readouterr().err
|
||||
+
|
||||
+
|
||||
+def test_proposed_case_cannot_build_even_with_selected_acknowledged_source(tmp_path, capsys):
|
||||
+ output = tmp_path / "bundle"
|
||||
+ assert cli.main(["build", "--case", str(CASE), "--types-root", str(TYPES),
|
||||
+ "--allow-unimplemented", "--plan", str(tmp_path / "absent-plan.md"),
|
||||
+ "--out", str(output)]) == 2
|
||||
+ assert not output.exists()
|
||||
+ assert "case is not accepted" in capsys.readouterr().err
|
||||
+
|
||||
+
|
||||
+def test_historical_bundle_cannot_attest_12_inventory(tmp_path, capsys):
|
||||
+ owner = Path("/home/worsch/ops-mason")
|
||||
+ assert cli.main(["structure-request", "--case", str(CASE), "--types-root", str(TYPES),
|
||||
+ "--allow-unimplemented", "--plan", str(owner / "plans/case-forgejo-netkingdom-oidc.md"),
|
||||
+ "--bundle", str(owner / "builds/case-forgejo-netkingdom-oidc")]) == 2
|
||||
+ captured = capsys.readouterr()
|
||||
+ assert captured.out == ""
|
||||
+ assert json.loads(captured.err)["error"] == "structural-source-preparation-refused"
|
||||
+
|
||||
+
|
||||
+def test_actual_validator_refuses_invalid_consumer_namespace(tmp_path, capsys):
|
||||
+ case = yaml.safe_load(CASE.read_text())
|
||||
+ case["spec"]["parameters"]["consumer_namespace"] = "../../other"
|
||||
+ source = tmp_path / "case.yaml"
|
||||
+ source.write_text(yaml.safe_dump(case))
|
||||
+ assert cli.main(["plan", "--case", str(source), "--types-root", str(TYPES),
|
||||
+ "--no-live", "--allow-unimplemented"]) == 2
|
||||
+ captured = capsys.readouterr()
|
||||
+ assert captured.out == ""
|
||||
+ assert "consumer_namespace" in captured.err
|
||||
diff --git a/workplans/MASON-WP-0010-security-case-materialization.md b/workplans/MASON-WP-0010-security-case-materialization.md
|
||||
index 3fb2e46..4b1a1fd 100644
|
||||
--- a/workplans/MASON-WP-0010-security-case-materialization.md
|
||||
+++ b/workplans/MASON-WP-0010-security-case-materialization.md
|
||||
@@ -247,3 +247,14 @@ parameter/acceptance field; it writes and signs nothing. Details and exact nativ
|
||||
`docs/forgejo-structural-materialization-source-v1.md`. T03 remains progress;
|
||||
case remains accepted@1.1, not materialized/live. CCR42 consumer auth remains
|
||||
in_flight, and native owner evidence is not manufactured by source preparation.
|
||||
+
|
||||
+2026-10-16 (T03 source compatibility): `plan`, `build` and
|
||||
+`structure-request` now expose explicit `--types-root`, passing it to the existing
|
||||
+exact-version NetKingdom API. Actual acknowledged 1.2 source renders the proposed
|
||||
+Forgejo case into a six-object **draft**, preserving its absent acceptance.
|
||||
+Six actual-source integration checks prove the root selection, dedicated consumer
|
||||
+namespace/SA, ordinary unimplemented refusal, proposed build refusal, historical
|
||||
+1.1 bundle refusal and malformed namespace refusal. Existing 44 plan/build/
|
||||
+structure tests pass. No live survey, execution, case/type status, acceptance,
|
||||
+native owner evidence or CCR was changed. T03 remains progress; actual accepted
|
||||
+1.2 case/reviewed source and authenticated native owner evidence remain required.
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"scope": "MASON-WP-0010-T03 source compatibility only",
|
||||
"owner": "ops-mason",
|
||||
"base_revision": "e4761b843cd881a672cbb33a5b451de31a61d730",
|
||||
"isolated_checkout": "/tmp/netkingdom-mason-browser-20261016",
|
||||
"netkingdom_revision": "c31d21ba7d15624940e8858cd5f31ceb57d82081",
|
||||
"changed_source": {
|
||||
"src/ops_mason/cli.py": "sha256:9cb202051a1c329e92a64841655325f7a43d9906d23f62c8bb9c242cd96fe5db",
|
||||
"tests/test_case_version_source.py": "sha256:e07e6f64cbf0237117cb04dc888f58ef7601d6f50e3accd60a7884c7f19d8a1e",
|
||||
"docs/forgejo-structural-materialization-source-v1.md": "sha256:7c893fef2bd8c3726f0ed1b7c4e256142cecb3a11c0a25e8c631a850dc3eb71e",
|
||||
"workplans/MASON-WP-0010-security-case-materialization.md": "sha256:7cfb52adc2e03de92a654678b852f6f35ebdcd60d7309a77bcdac5e972b6c462"
|
||||
},
|
||||
"exact_inputs": {
|
||||
"tools/security-case-types/security_case_validator.py": "sha256:cbb9ab532d328f78cf37aca57891400e2e65d63a8ab3c5fe7bd1a4ff21dee56f",
|
||||
"security-case-type-revisions/1.2/oidc-browser-relying-party/type.yaml": "sha256:9e60ff569fb0d860a2bbde7bbb0991a54d4b0688f62b56df0fa5d161fd624578",
|
||||
"security-case-type-revisions/1.2/oidc-browser-relying-party/examples/forgejo.yaml": "sha256:1c868c1da7d9077fae97bc6bc029706ec3dc418cfeaae30dc0f92b5b4ba6f2ab"
|
||||
},
|
||||
"artifacts": {
|
||||
"README.md": "sha256:9d8891682c62b7736f51f2d6f239fa84d47aa147c5f855850ecdcf1388dd7b6a",
|
||||
"forgejo-1.2-draft-plan.md": "sha256:f61005f9da3651ca4ca4770801d2aa923e80cf1a831fb9f2b692f9b03d5e6066",
|
||||
"forgejo-1.2-source-render.json": "sha256:c4b57333980f5469953bc7fed1f1c2188b89b06fd69e542fc72b60a782c36cd0",
|
||||
"implementation.patch": "sha256:cfe7648ed119d83faca228a2f21611c9388de1ba166598cbeb64f40e81620067",
|
||||
"validation.json": "sha256:44e5d9b8a3b4a4ab13a3a91aaa29927dc9a144217346091fcf2d19287c3731f5"
|
||||
},
|
||||
"acceptance_created": false,
|
||||
"native_materialization_verified": false,
|
||||
"execution_permitted": false,
|
||||
"canonical_owner_checkout_edited": false,
|
||||
"canonical_dirty_paths_preserved": [
|
||||
".custodian-brief.md",
|
||||
"WORK-RECORDS.md",
|
||||
"workplans/MASON-WP-0013-reuse-surface-private-source-reader.md"
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue