From 4016f3bdeefeb389b59e28722d1e897285dbf3d2 Mon Sep 17 00:00:00 2001 From: codex Date: Sat, 17 Oct 2026 13:27:00 +0200 Subject: [PATCH] Record failed runtime trial and reviewed verification proposal (3/4) --- .../cleanup-upload-staging.py | 31 +++ .../execute-verify.py | 151 +++++++++++ .../final-readonly-dry-run.json | 1 + .../frozen-native-argv.json | 181 +++++++++++++ .../local-fixture-results.json | 30 +++ .../process_supervision.py | 239 ++++++++++++++++++ .../r2-baseline.json | 123 +++++++++ .../r2-to-r3-supervisor.patch | 136 ++++++++++ .../r2-zombie-counterexample.json | 1 + .../native-runtime-install-r3/reproduce-r2.py | 37 +++ .../retained-fixture-refinement.json | 9 + .../native-runtime-install-r3/runtime.py | 52 ++++ .../source-contract-pins.json | 25 ++ .../test_process_supervision.py | 130 ++++++++++ .../test_receipt_cleanup.py | 72 ++++++ .../verify-installed.py | 208 +++++++++++++++ 16 files changed, 1426 insertions(+) create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/cleanup-upload-staging.py create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/execute-verify.py create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/final-readonly-dry-run.json create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/frozen-native-argv.json create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/local-fixture-results.json create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/process_supervision.py create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/r2-baseline.json create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/r2-to-r3-supervisor.patch create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/r2-zombie-counterexample.json create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/reproduce-r2.py create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/retained-fixture-refinement.json create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/runtime.py create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/source-contract-pins.json create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/test_process_supervision.py create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/test_receipt_cleanup.py create mode 100644 docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/verify-installed.py diff --git a/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/cleanup-upload-staging.py b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/cleanup-upload-staging.py new file mode 100644 index 0000000..f39b207 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/cleanup-upload-staging.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +"""Conditional cleanup only when the durable receipt proves exec never started.""" +import argparse,json,shlex,subprocess +from pathlib import Path +HERE=Path(__file__).resolve().parent + +def main(): + p=argparse.ArgumentParser(description=__doc__);p.add_argument('--apply',action='store_true');args=p.parse_args() + receipt=json.loads((HERE/'execution-receipt.json').read_text()) + if not (receipt.get('status')=='staging_created' and receipt.get('phase')=='before_transfer' + and receipt.get('revision')==3 and receipt.get('proposal_id')=='REINAH-WP-0008-T07-install-f599-r1'): + raise ValueError('not_a_preexecution_upload_failure') + inode=receipt['staging_identity']['inode'] + if not args.apply: + print(json.dumps({'status':'conditional_cleanup_review','inode':inode,'native_mutations':0}));return 0 + code=r'''import json,os,shutil,stat,sys +from pathlib import Path +p=Path('/home/tegwick/.helixforge-factory/runtime-verify-f599-r3-20261017');s=p.lstat() +assert os.getuid()==1000 and os.getgid()==1000 and stat.S_ISDIR(s.st_mode) and s.st_uid==1000 and s.st_gid==1000 and stat.S_IMODE(s.st_mode)==0o700 and s.st_dev==2049 and s.st_ino==int(sys.argv[1]) +assert {f.name for f in p.iterdir()} <= {'runtime.py','verify-installed.py','process_supervision.py'} +for f in p.iterdir(): + s=f.lstat();assert stat.S_ISREG(s.st_mode) and s.st_uid==1000 and not stat.S_ISLNK(s.st_mode) +shutil.rmtree(p);print(json.dumps({'status':'owned_upload_staging_removed','absent':not p.exists()}))''' + cp=subprocess.run(['ssh','-o','BatchMode=yes','-o','ConnectTimeout=15','railiance01','/usr/bin/python3 -I -B -c '+shlex.quote(code)+' '+shlex.quote(str(inode))],capture_output=True,text=True,timeout=30) + if cp.returncode:raise RuntimeError('conditional_cleanup_refused_or_uncertain') + result=json.loads(cp.stdout);(HERE/'upload-cleanup-receipt.json').write_text(json.dumps(result,indent=2)+'\n');print(json.dumps(result));return 0 + +if __name__=='__main__': + try:raise SystemExit(main()) + except Exception as exc: + print(json.dumps({'status':'cleanup_refused_or_uncertain','failure_type':type(exc).__name__}));raise SystemExit(1) from None diff --git a/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/execute-verify.py b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/execute-verify.py new file mode 100644 index 0000000..f01b302 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/execute-verify.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +"""Verification-only orchestration. Fresh exact revision-3 founder approval required.""" +import argparse +from datetime import datetime, timezone +import hashlib +import json +import os +from pathlib import Path +import shlex +import subprocess +import sys + +HERE=Path(__file__).resolve().parent +DIGEST='f59959b605a51abd2901becde14e7abd852c2bd3f848004b51d250d1cb81af6d' +STAGE='/home/tegwick/.helixforge-factory/runtime-verify-f599-r3-20261017' +STORE='/home/tegwick/.helixforge-factory/runtimes' +DURABLE_RECEIPT=STAGE+'.receipt.json' + +READ_TARGET=r'''import json,os,pwd,stat,subprocess,shutil,time,ctypes,gzip,sys +from pathlib import Path +store=Path('/home/tegwick/.helixforge-factory/runtimes'); stage=Path('/home/tegwick/.helixforge-factory/runtime-verify-f599-r3-20261017');digest='f59959b605a51abd2901becde14e7abd852c2bd3f848004b51d250d1cb81af6d' +def meta(p): + try: + s=p.lstat();return {'uid':s.st_uid,'gid':s.st_gid,'mode':oct(stat.S_IMODE(s.st_mode)),'device':s.st_dev,'inode':s.st_ino,'directory':stat.S_ISDIR(s.st_mode),'symlink':stat.S_ISLNK(s.st_mode)} + except FileNotFoundError:return None +cp=subprocess.run(['/usr/local/bin/kubectl','get','nodes','-o','json'],capture_output=True,timeout=20) +assert cp.returncode==0 +nodes=json.loads(cp.stdout)['items'];assert len(nodes)==1 +mem=dict(x.split(':',1) for x in Path('/proc/meminfo').read_text().splitlines()) +read_cpu=lambda:[int(x) for x in Path('/proc/stat').read_text().splitlines()[0].split()[1:9]] +a=read_cpu();time.sleep(1);b=read_cpu();d=[y-x for x,y in zip(a,b)];busy=1-(d[3]+d[4])/sum(d) +help_result=subprocess.run(['/usr/bin/bwrap','--help'],stdout=subprocess.PIPE,stderr=subprocess.DEVNULL,timeout=10) +version=subprocess.run(['/usr/bin/bwrap','--version'],stdout=subprocess.PIPE,stderr=subprocess.DEVNULL,timeout=10) +manual=gzip.decompress(Path('/usr/share/man/man1/bwrap.1.gz').read_bytes()).decode();i=manual.find('die\\-with\\-parent') +fd=os.pidfd_open(os.getpid());os.close(fd);value=ctypes.c_int();subreaper_get=ctypes.CDLL(None).prctl(37,ctypes.byref(value),0,0,0) +reader={};exec(compile(sys.argv[1],'reviewed_public_digest_reader','exec'),reader) +native_digest=reader['runtime_digest'](store/digest) +print(json.dumps({'installed_complete_digest':native_digest,'durable_receipt':meta(Path(str(stage)+'.receipt.json')),'bwrap_version':version.stdout.decode().strip(),'bwrap_die_with_parent_supported':help_result.returncode==0 and b'--die-with-parent' in help_result.stdout,'primary_manual_contract_excerpt':manual[max(0,i-20):i+430] if i>=0 else 'absent','pidfd_available':True,'subreaper_get_supported':subreaper_get==0,'account':pwd.getpwuid(os.getuid()).pw_name,'uid':os.getuid(),'gid':os.getgid(),'store':meta(store),'destination':meta(store/digest),'stage':meta(stage),'memory_available_bytes':int(mem['MemAvailable'].split()[0])*1024,'disk_free_bytes':shutil.disk_usage(store).free,'load_average':list(os.getloadavg()),'cpu_count':os.cpu_count(),'cpu_busy_fraction':busy,'cpu_sample_seconds':1,'node_addresses':nodes[0]['status']['addresses'],'node_ready':any(c['type']=='Ready' and c['status']=='True' for c in nodes[0]['status']['conditions'])}))''' + + +def sha(path): + with path.open('rb') as stream:return 'sha256:'+hashlib.file_digest(stream,'sha256').hexdigest() + + +def command(argv,timeout=60): + cp=subprocess.run(argv,stdout=subprocess.PIPE,stderr=subprocess.DEVNULL,timeout=timeout) + if cp.returncode:raise RuntimeError('command_failed') + return cp.stdout + + +def ssh(code,args=(),timeout=60): + remote='/usr/bin/python3 -I -B -c '+shlex.quote(code) + if args:remote+=' '+' '.join(shlex.quote(str(x)) for x in args) + return json.loads(command(['ssh','-o','BatchMode=yes','-o','ConnectTimeout=15','railiance01',remote],timeout)) + + +def preflight(): + inputs=json.loads((HERE/'action-inputs.json').read_text()) + for name,expected in inputs['helper_hashes'].items(): + if sha(HERE/name)!=expected:raise ValueError('helper_changed') + observation=ssh(READ_TARGET,args=((HERE/'runtime.py').read_text(),)) + store=observation['store'] + expected={'device':2049,'inode':4751222} + checks={ + 'bwrap_parent_death_contract':observation['bwrap_die_with_parent_supported'] and observation['bwrap_version']=='bubblewrap 0.9.0' and 'PR_SET_PDEATHSIG' in observation['primary_manual_contract_excerpt'], + 'supervision_primitives':observation['pidfd_available'] and observation['subreaper_get_supported'], + 'account':observation['account']=='tegwick' and observation['uid']==1000 and observation['gid']==1000, + 'protected_store':store['uid']==1000 and store['gid']==1000 and store['mode']=='0o700' and store['directory'] and not store['symlink'] and store['device']==expected['device'] and store['inode']==expected['inode'], + 'exact_installed_runtime':observation['destination'] is not None and observation['destination']['directory'] and not observation['destination']['symlink'] and observation['destination']['uid']==1000 and observation['destination']['gid']==1000 and observation['installed_complete_digest']==DIGEST, + 'durable_receipt_absent':observation['durable_receipt'] is None, + 'staging_absent':observation['stage'] is None, + 'exact_ready_node':observation['node_ready'] and any(x['address']=='92.205.62.239' for x in observation['node_addresses']), + 'disk_headroom':observation['disk_free_bytes']>=2*1024**3, + 'memory_headroom':observation['memory_available_bytes']>=1024**3, + 'load_headroom':observation['load_average'][0]<=2*observation['cpu_count'], + 'cpu_headroom':observation['cpu_busy_fraction']<=0.85, + } + observation['checks']=checks + return inputs,observation + + +def save(path,value): + fd=os.open(path,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600) + with os.fdopen(fd,'w') as stream:json.dump(value,stream,indent=2);stream.write('\n') + + +def main(): + parser=argparse.ArgumentParser(description=__doc__) + parser.add_argument('--apply',action='store_true');parser.add_argument('--dry-run',action='store_true') + args=parser.parse_args() + if args.apply and args.dry_run:raise ValueError('conflicting_modes') + inputs,observation=preflight() + if not args.apply: + report={'status':'read_only_dry_run_passed' if all(observation['checks'].values()) else 'read_only_dry_run_blocked','proposal_id':inputs['proposal_id'],'revision':3, + 'observed_at':datetime.now(timezone.utc).isoformat(),'target':observation, + 'installed_runtime_complete_digest_verified':observation['installed_complete_digest']==DIGEST, + 'remote_mutations':0,'provider_calls':0,'private_configuration_reads':False, + 'next_exact_command':'python3 -I -B '+str(HERE/'execute-verify.py')+' --apply'} + print(json.dumps(report,sort_keys=True));return 0 if all(observation['checks'].values()) else 2 + if not all(observation['checks'].values()):raise ValueError('target_headroom_or_identity_refused') + receipt=HERE/'execution-receipt.json' + if receipt.exists():raise ValueError('prior_attempt_requires_reconciliation') + save(receipt,{'status':'intent_recorded','proposal_id':inputs['proposal_id'],'revision':3, + 'automatic_retry':False,'observed_at':datetime.now(timezone.utc).isoformat()}) + # No native attempt is retried by this helper. Intent survives an SSH loss. + create=r'''import json,os,stat,time +from pathlib import Path +assert os.getuid()==1000 and os.getgid()==1000 +store=Path('/home/tegwick/.helixforge-factory/runtimes');s=store.lstat() +assert stat.S_ISDIR(s.st_mode) and s.st_uid==1000 and stat.S_IMODE(s.st_mode)==0o700 and s.st_ino==4751222 and s.st_dev==2049 +assert (store/'f59959b605a51abd2901becde14e7abd852c2bd3f848004b51d250d1cb81af6d').is_dir() +assert not Path('/home/tegwick/.helixforge-factory/runtime-verify-f599-r3-20261017.receipt.json').exists() +read_cpu=lambda:[int(x) for x in Path('/proc/stat').read_text().splitlines()[0].split()[1:9]] +a=read_cpu();time.sleep(1);b=read_cpu();d=[y-x for x,y in zip(a,b)];assert 1-(d[3]+d[4])/sum(d)<=0.85 +stage=Path('/home/tegwick/.helixforge-factory/runtime-verify-f599-r3-20261017');stage.mkdir(mode=0o700) +s=stage.lstat();print(json.dumps({'inode':s.st_ino,'device':s.st_dev}))''' + created=ssh(create) + receipt.write_text(json.dumps({'status':'staging_created','phase':'before_transfer','proposal_id':inputs['proposal_id'],'revision':3,'automatic_retry':False,'staging_identity':created,'staging_path':STAGE},indent=2)+'\n') + command(['scp','-q',str(HERE/'runtime.py'),str(HERE/'verify-installed.py'),str(HERE/'process_supervision.py'),'railiance01:'+STAGE+'/'],120) + execute=r'''import hashlib,json,os,stat,sys +from pathlib import Path +stage=Path('/home/tegwick/.helixforge-factory/runtime-verify-f599-r3-20261017');s=stage.lstat() +assert stat.S_ISDIR(s.st_mode) and s.st_uid==1000 and stat.S_IMODE(s.st_mode)==0o700 and s.st_ino==int(sys.argv[1]) and s.st_dev==2049 +expected=json.loads(sys.argv[2]);assert {p.name for p in stage.iterdir()}==set(expected) +for name,want in expected.items(): + p=stage/name;s=p.lstat();assert stat.S_ISREG(s.st_mode) and s.st_uid==1000 and not stat.S_ISLNK(s.st_mode) + with p.open('rb') as stream:assert hashlib.file_digest(stream,'sha256').hexdigest()==want + p.chmod(0o600) +os.execve('/usr/bin/python3',['python3','-I','-B',str(stage/'verify-installed.py'),'--stage-inode',sys.argv[1]],{'HOME':'/home/tegwick','PATH':'/usr/bin:/bin','LANG':'C.UTF-8','PYTHONDONTWRITEBYTECODE':'1'})''' + hashes={'runtime.py':inputs['helper_hashes']['runtime.py'].removeprefix('sha256:'), + 'verify-installed.py':inputs['helper_hashes']['verify-installed.py'].removeprefix('sha256:'), + 'process_supervision.py':inputs['helper_hashes']['process_supervision.py'].removeprefix('sha256:')} + # Parse the structured final helper return even when its action failed. + remote='/usr/bin/python3 -I -B -c '+shlex.quote(execute)+' '+shlex.quote(str(created['inode']))+' '+shlex.quote(json.dumps(hashes,separators=(',',':'))) + receipt.write_text(json.dumps({'status':'remote_execution_started','proposal_id':inputs['proposal_id'],'revision':3,'automatic_retry':False,'staging_identity':created,'staging_path':STAGE},indent=2)+'\n') + cp=subprocess.run(['ssh','-o','BatchMode=yes','-o','ConnectTimeout=15','railiance01',remote],stdout=subprocess.PIPE,stderr=subprocess.DEVNULL,timeout=600) + try:result=json.loads(cp.stdout) + except Exception:raise RuntimeError('remote_outcome_uncertain') from None + result['ssh_exit_code']=cp.returncode;result['staging_identity']=created + # Replace only our exact local intent receipt, never a previous attempt. + receipt.write_text(json.dumps(result,indent=2)+'\n');receipt.chmod(0o600) + print(json.dumps(result,sort_keys=True)) + return 0 if cp.returncode==0 and result.get('status')=='installed_runtime_offline_rehearsal_verified' else 1 + + +if __name__=='__main__': + try:raise SystemExit(main()) + except Exception as exc: + print(json.dumps({'status':'failed_or_uncertain','failure_type':type(exc).__name__, + 'automatic_retry':False,'private_diagnostics_suppressed':True})) + raise SystemExit(1) from None diff --git a/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/final-readonly-dry-run.json b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/final-readonly-dry-run.json new file mode 100644 index 0000000..e22fced --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/final-readonly-dry-run.json @@ -0,0 +1 @@ +{"installed_runtime_complete_digest_verified": true, "next_exact_command": "python3 -I -B /home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/execute-verify.py --apply", "observed_at": "2026-10-17T08:41:54.503313+00:00", "private_configuration_reads": false, "proposal_id": "REINAH-WP-0008-T07-install-f599-r1", "provider_calls": 0, "remote_mutations": 0, "revision": 3, "status": "read_only_dry_run_passed", "target": {"account": "tegwick", "bwrap_die_with_parent_supported": true, "bwrap_version": "bubblewrap 0.9.0", "checks": {"account": true, "bwrap_parent_death_contract": true, "cpu_headroom": true, "disk_headroom": true, "durable_receipt_absent": true, "exact_installed_runtime": true, "exact_ready_node": true, "load_headroom": true, "memory_headroom": true, "protected_store": true, "staging_absent": true, "supervision_primitives": true}, "cpu_busy_fraction": 0.22164948453608246, "cpu_count": 4, "cpu_sample_seconds": 1, "destination": {"device": 2049, "directory": true, "gid": 1000, "inode": 4743998, "mode": "0o755", "symlink": false, "uid": 1000}, "disk_free_bytes": 123064221696, "durable_receipt": null, "gid": 1000, "installed_complete_digest": "f59959b605a51abd2901becde14e7abd852c2bd3f848004b51d250d1cb81af6d", "load_average": [0.4267578125, 1.38232421875, 1.736328125], "memory_available_bytes": 7668154368, "node_addresses": [{"address": "92.205.62.239", "type": "InternalIP"}, {"address": "2a00:1169:111:6d70::", "type": "InternalIP"}, {"address": "239.62.205.92.host.secureserver.net", "type": "Hostname"}], "node_ready": true, "pidfd_available": true, "primary_manual_contract_excerpt": ")\\&.\n.RE\n.PP\n\\fB\\-\\-die\\-with\\-parent\\fR\n.RS 4\nEnsures child process (COMMAND) dies when bwrap\\*(Aqs parent dies\\&. Kills (SIGKILL) all bwrap sandbox processes in sequence from parent to child including COMMAND process when bwrap or bwrap\\*(Aqs parent dies\\&. See prctl, PR_SET_PDEATHSIG\\&.\n.RE\n.PP\n\\fB\\-\\-as\\-pid\\-1\\fR\n.RS 4\nDo not create a process with PID=1 in the sandbox to reap child processes\\&.\n.RE\n.PP\n\\fB\\-\\-cap\\-add \\fR\\fBCAP\\fR\n.RS 4\nAdd ", "stage": null, "store": {"device": 2049, "directory": true, "gid": 1000, "inode": 4751222, "mode": "0o700", "symlink": false, "uid": 1000}, "subreaper_get_supported": true, "uid": 1000}} diff --git a/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/frozen-native-argv.json b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/frozen-native-argv.json new file mode 100644 index 0000000..fc14c45 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/frozen-native-argv.json @@ -0,0 +1,181 @@ +{ + "source_api": "owner_rehearsal.rehearse(runtime, run=bounded_run, bwrap_bin=/usr/bin/bwrap)", + "source_sha256": "sha256:192f654cad4a5f065c7faa5e35b03493297a472e8d7fe641be2c42c5fb00dcc3", + "captured_using_existing_runner_seam": true, + "launcher_executions": 0, + "steps": [ + { + "argv": [ + "/usr/bin/bwrap", + "--die-with-parent", + "--unshare-user", + "--unshare-pid", + "--unshare-ipc", + "--unshare-uts", + "--unshare-cgroup", + "--unshare-net", + "--tmpfs", + "/", + "--proc", + "/proc", + "--dev", + "/dev", + "--clearenv", + "--ro-bind", + "/usr", + "/usr", + "--ro-bind", + "/bin", + "/bin", + "--ro-bind", + "/lib", + "/lib", + "--ro-bind", + "/lib64", + "/lib64", + "--ro-bind", + "/etc/resolv.conf", + "/etc/resolv.conf", + "--ro-bind", + "/etc/ssl/certs", + "/etc/ssl/certs", + "--dir", + "/opt", + "--dir", + "/opt/sandboxer", + "--ro-bind", + "/home/tegwick/.helixforge-factory/runtimes/f59959b605a51abd2901becde14e7abd852c2bd3f848004b51d250d1cb81af6d", + "/opt/sandboxer/runtime", + "--dir", + "/tmp", + "--setenv", + "HOME", + "/tmp", + "--setenv", + "PATH", + "/opt/sandboxer/runtime/bin:/usr/bin:/bin", + "--chdir", + "/tmp", + "/opt/sandboxer/runtime/bin/rein-aharness", + "--help" + ], + "timeout_seconds": 60 + }, + { + "argv": [ + "/usr/bin/bwrap", + "--die-with-parent", + "--unshare-user", + "--unshare-pid", + "--unshare-ipc", + "--unshare-uts", + "--unshare-cgroup", + "--unshare-net", + "--tmpfs", + "/", + "--proc", + "/proc", + "--dev", + "/dev", + "--clearenv", + "--ro-bind", + "/usr", + "/usr", + "--ro-bind", + "/bin", + "/bin", + "--ro-bind", + "/lib", + "/lib", + "--ro-bind", + "/lib64", + "/lib64", + "--ro-bind", + "/etc/resolv.conf", + "/etc/resolv.conf", + "--ro-bind", + "/etc/ssl/certs", + "/etc/ssl/certs", + "--dir", + "/opt", + "--dir", + "/opt/sandboxer", + "--ro-bind", + "/home/tegwick/.helixforge-factory/runtimes/f59959b605a51abd2901becde14e7abd852c2bd3f848004b51d250d1cb81af6d", + "/opt/sandboxer/runtime", + "--dir", + "/tmp", + "--setenv", + "HOME", + "/tmp", + "--setenv", + "PATH", + "/opt/sandboxer/runtime/bin:/usr/bin:/bin", + "--chdir", + "/tmp", + "/opt/sandboxer/runtime/bin/glas-harness", + "--help" + ], + "timeout_seconds": 60 + }, + { + "argv": [ + "/usr/bin/bwrap", + "--die-with-parent", + "--unshare-user", + "--unshare-pid", + "--unshare-ipc", + "--unshare-uts", + "--unshare-cgroup", + "--unshare-net", + "--tmpfs", + "/", + "--proc", + "/proc", + "--dev", + "/dev", + "--clearenv", + "--ro-bind", + "/usr", + "/usr", + "--ro-bind", + "/bin", + "/bin", + "--ro-bind", + "/lib", + "/lib", + "--ro-bind", + "/lib64", + "/lib64", + "--ro-bind", + "/etc/resolv.conf", + "/etc/resolv.conf", + "--ro-bind", + "/etc/ssl/certs", + "/etc/ssl/certs", + "--dir", + "/opt", + "--dir", + "/opt/sandboxer", + "--ro-bind", + "/home/tegwick/.helixforge-factory/runtimes/f59959b605a51abd2901becde14e7abd852c2bd3f848004b51d250d1cb81af6d", + "/opt/sandboxer/runtime", + "--dir", + "/tmp", + "--setenv", + "HOME", + "/tmp", + "--setenv", + "PATH", + "/opt/sandboxer/runtime/bin:/usr/bin:/bin", + "--chdir", + "/tmp", + "/opt/sandboxer/runtime/bin/claude", + "--version" + ], + "timeout_seconds": 60 + } + ], + "runtime_source_file_unchanged": true, + "complete_runtime_digest_verified_after_bytecode_refinement": true +} diff --git a/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/local-fixture-results.json b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/local-fixture-results.json new file mode 100644 index 0000000..658fe57 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/local-fixture-results.json @@ -0,0 +1,30 @@ +{ + "results": [ + { + "argv": [ + "/usr/bin/python3", + "-B", + "docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/test_process_supervision.py" + ], + "exit_code": 0, + "stdout": "", + "stderr_test_framework_only": "........\n----------------------------------------------------------------------\nRan 8 tests in 3.212s\n\nOK\n" + }, + { + "argv": [ + "/usr/bin/python3", + "-B", + "docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/test_receipt_cleanup.py" + ], + "exit_code": 0, + "stdout": "", + "stderr_test_framework_only": "..\n----------------------------------------------------------------------\nRan 2 tests in 0.024s\n\nOK\n" + } + ], + "total_cases": 10, + "native_runtime_or_namespace_launches": 0, + "provider_calls": 0, + "application_stderr": "discarded", + "earlier_fixture_expectation_refinement": "retained-fixture-refinement.json", + "observed_at": "2026-10-17T08:42:30.244972+00:00" +} diff --git a/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/process_supervision.py b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/process_supervision.py new file mode 100644 index 0000000..e0f811c --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/process_supervision.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +"""One-action private process supervisor; no credentials, network or runtime policy.""" +import argparse +import base64 +import ctypes +import json +import os +from pathlib import Path +import select +import signal +import stat +import subprocess +import sys +import time +import uuid + +MAX_OUTPUT=65536 +ENV={'HOME':'/home/tegwick','PATH':'/usr/bin:/bin','LANG':'C.UTF-8','PYTHONDONTWRITEBYTECODE':'1'} + + +def facts(pid): + try: + path=Path('/proc')/str(pid);raw=(path/'stat').read_text();v=raw[raw.rfind(')')+2:].split() + return {'pid':pid,'ppid':int(v[1]),'pgrp':int(v[2]),'sid':int(v[3]),'start':int(v[19]),'uid':path.stat().st_uid,'state':v[0]} + except (FileNotFoundError,ProcessLookupError):return None + + +def write(path,value): + temp=path.with_name('.'+path.name+'.'+uuid.uuid4().hex) + fd=os.open(temp,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600) + with os.fdopen(fd,'w') as f:json.dump(value,f);f.flush();os.fsync(f.fileno()) + temp.replace(path) + + +def dead(fd): + p=select.poll();p.register(fd,select.POLLIN);return bool(p.poll(0)) + + +def descendants(parent): + rows={} + for p in Path('/proc').iterdir(): + if p.name.isdigit(): + row=facts(int(p.name)) + if row:rows[row['pid']]=row + selected={parent};changed=True + while changed: + before=len(selected);selected.update(r['pid'] for r in rows.values() if r['ppid'] in selected) + changed=len(selected)!=before + return {p:rows[p] for p in selected-{parent} if p in rows} + + +def quiesce(parent,children): + """Signal only pidfd-bound actual descendants; subreaper adopts escaped sessions.""" + uncertain=False + for sig,limit in ((signal.SIGTERM,0.5),(signal.SIGKILL,3.0)): + end=time.monotonic()+limit + while time.monotonic()=16 or abort:raise ValueError() + owner_fd=os.pidfd_open(owner['pid']) + bound=facts(owner['pid']) + if bound is None or bound['start']!=owner['start'] or bound['uid']!=owner['uid']: + os.close(owner_fd);raise ValueError() + supervisor_pid=os.getpid() + child=subprocess.Popen(r['argv'],stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE if r['capture'] else subprocess.DEVNULL, + stderr=subprocess.DEVNULL,env=ENV,start_new_session=True, + preexec_fn=lambda parent=supervisor_pid:parent_death(parent)) + jobs[key]={'p':child,'request':r,'owner_fd':owner_fd,'deadline':time.monotonic()+r['timeout'], + 'output':bytearray(),'eof':not r['capture'],'done':False,'leader':facts(child.pid)} + write(directory/'public-journal.json',{'jobs':job_report(jobs),'reaped_orphan_zombies':reaped[-16:]}) + if child.stdout:os.set_blocking(child.stdout.fileno(),False) + except Exception: + abort=abort or 'request_mapping_refused';uncertain=True + write(directory/('result-'+key+'.json'),{'returncode':125,'quiescent':False,'status':'mapping_refused'}) + for key,job in list(jobs.items()): + if job['done']:continue + p=job['p'] + if p.stdout and not job['eof']: + try: + raw=os.read(p.stdout.fileno(),MAX_OUTPUT+1) + if not raw:job['eof']=True + else: + job['output'].extend(raw) + if len(job['output'])>MAX_OUTPUT:abort=abort or 'output_bound' + except BlockingIOError:pass + if dead(job['owner_fd']):abort=abort or 'caller_lost' + if time.monotonic()>=job['deadline']:abort=abort or 'timeout' + if p.poll() is not None and job['eof'] and not abort: + # An adopted zombie is already dead, but must be waited + # before claiming quiescence. Do not steal a Popen leader's + # exit status or dismiss a genuinely live leftover. + own=descendants(os.getpid()) + leaders={x['p'].pid for x in jobs.values()} + reaped.extend(reap_orphan_zombies(own,leaders)) + own=descendants(os.getpid()) + active={x['p'].pid for x in jobs.values() if not x['done']} + leftover=[row for row in own.values() if row['sid']==p.pid or + (row['ppid']==os.getpid() and row['pid'] not in active)] + if leftover:abort='leader_left_descendants' + else: + write(directory/('result-'+key+'.json'),{'returncode':p.returncode,'quiescent':True,'status':'completed', + 'output_b64':base64.b64encode(job['output']).decode() if job['request']['capture'] else ''}) + job['done']=True;job['status']='completed';os.close(job['owner_fd']) + write(directory/'public-journal.json',{'jobs':job_report(jobs),'reaped_orphan_zombies':reaped[-16:]}) + if abort: + ok=quiesce(os.getpid(),[x['p'] for x in jobs.values()]) and not uncertain + for key,job in jobs.items(): + if not job['done']: + write(directory/('result-'+key+'.json'),{'returncode':124 if abort=='timeout' else 125, + 'quiescent':ok,'status':abort,'output_b64':''});job['done']=True;job['status']=abort + write(directory/'quiescent.json',{'quiescent':ok,'reason':abort,'no_live_or_waitable_descendants':ok, + 'stderr_suppressed':True,'private_values_emitted':False,'jobs':job_report(jobs), + 'reaped_orphan_zombies':reaped[-16:],'leftover_at_failure':leftover[:16]}) + return 0 if ok else 1 + time.sleep(0.01) + except BaseException: + ok=quiesce(os.getpid(),[x['p'] for x in jobs.values()]) + write(directory/'quiescent.json',{'quiescent':False,'reason':'supervisor_exception','best_effort_stopped':ok, + 'jobs':job_report(jobs),'reaped_orphan_zombies':reaped[-16:],'leftover_at_failure':leftover[:16]}) + return 1 + + +class Supervisor: + def __init__(self,directory): + self.directory=Path(directory);self.directory.mkdir(mode=0o700) + write(self.directory/'root.json',facts(os.getpid())) + fd=os.pidfd_open(os.getpid()) + try:self.process=subprocess.Popen([sys.executable,'-I','-B',str(Path(__file__).resolve()),'--watchdog',str(self.directory),'--parent-fd',str(fd)], + stdin=subprocess.DEVNULL,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,pass_fds=(fd,),start_new_session=True,env=ENV) + finally:os.close(fd) + end=time.monotonic()+5 + while not (self.directory/'ready.json').exists(): + if self.process.poll() is not None or time.monotonic()>end:raise RuntimeError('supervisor_start_failed') + time.sleep(0.01) + def run(self,argv,timeout,*,capture=False,label='job'):return request(self.directory,argv,timeout,capture=capture,label=label) + def close(self): + write(self.directory/'shutdown',{}) + try:self.process.wait(timeout=8) + except subprocess.TimeoutExpired:raise RuntimeError('supervisor_quiescence_unknown') from None + report=json.loads((self.directory/'quiescent.json').read_text()) + if self.process.returncode or report.get('quiescent') is not True:raise RuntimeError('descendant_quiescence_unverified') + return report + + +def request(directory,argv,timeout,*,capture=False,label='job'): + directory=Path(directory);key=uuid.uuid4().hex + if (directory/'quiescent.json').exists():raise RuntimeError('supervision_already_stopped') + write(directory/('request-'+key+'.json'),{'owner':facts(os.getpid()),'argv':list(argv),'timeout':timeout,'capture':capture,'label':label}) + end=time.monotonic()+timeout+10 + result=directory/('result-'+key+'.json') + while not result.exists(): + if time.monotonic()>end:raise RuntimeError('supervision_reply_unknown') + time.sleep(0.01) + report=json.loads(result.read_text()) + if report.get('quiescent') is not True:raise RuntimeError('descendant_quiescence_unverified') + return report['returncode'],base64.b64decode(report.get('output_b64',''),validate=True) + + +def main(): + p=argparse.ArgumentParser();p.add_argument('--watchdog');p.add_argument('--parent-fd',type=int);a=p.parse_args() + return watchdog(a.watchdog,a.parent_fd) + +if __name__=='__main__': + try:raise SystemExit(main()) + except Exception:raise SystemExit(1) from None diff --git a/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/r2-baseline.json b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/r2-baseline.json new file mode 100644 index 0000000..07c089e --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/r2-baseline.json @@ -0,0 +1,123 @@ +{ + "proposal_id": "REINAH-WP-0008-T07-install-f599-r1", + "revision": 2, + "actual_status": "failed/partial installed; launcher verification unproven", + "files": [ + { + "path": "FINAL-PROPOSAL.md", + "sha256": "sha256:44b17589c323e1a966caf6c1b1def123092ee289b929c8ea014bc84109103b1d" + }, + { + "path": "PROCESS-CONTRACT.md", + "sha256": "sha256:ed79d14bde467e8893c341419c37fa2992184597fc9015c2e7d81fb4ece63fd0" + }, + { + "path": "action-inputs.json", + "sha256": "sha256:ce7f5a25be440561f5179ae9980d85eb693a11b58d2012c8aea24b8bae04ab6f" + }, + { + "path": "artifact-manifest.json", + "sha256": "sha256:aea5dbfe0742fe0bb6c36a41ca7b0d70473683b2731f92cc279d43929e5c35c9" + }, + { + "path": "build-transfer.py", + "sha256": "sha256:99914629f354d4b6d5a4750850b32ec2cb64ac5ce18e86702d9157ea6f6b0124" + }, + { + "path": "cleanup-upload-staging.py", + "sha256": "sha256:cec7eab2b25e4a0077ec45c055d7ba9542bc8e8724b53f2117be61c913269188" + }, + { + "path": "cpu-readonly-observation.json", + "sha256": "sha256:c424ecb281baf128d6c79f1e6c6f678093f9b5e9c84a3454f9891fe6197b2e95" + }, + { + "path": "executable-readonly-observation.json", + "sha256": "sha256:3ee73cdeeb4f82a55b31d19c6a24aacc4199e2e8647335d65b4e8f2e7d079c18" + }, + { + "path": "execute-install.py", + "sha256": "sha256:44d93406ce62cc1f08ca9c7deb3eed6f45c74d61efd978f7b0acec31ba04cdc5" + }, + { + "path": "execution-receipt.json", + "sha256": "sha256:59969ad9992ac289a553e1076209ba0d04b6fa7cd171bfad83dcc1f96d80a1c6" + }, + { + "path": "final-readonly-dry-run.json", + "sha256": "sha256:17144cdb22f975212ea02a5109c94740c430d79272ef9d919ce57dec68c2d880" + }, + { + "path": "frozen-native-argv.json", + "sha256": "sha256:0efb13aa725111cc227453bc7fc40eaf039c83cbd291ca7cb0409daf6b1e6c55" + }, + { + "path": "install-verified.py", + "sha256": "sha256:c6895881b83b3f5d5408c646040368cc5c59a99e1495fa0914b9183e087a6831" + }, + { + "path": "local-bytecode-refinement.json", + "sha256": "sha256:08c92506d6abefe15aff4a9b7908ab8bee425e7792abc6095e1dd04101a2a540" + }, + { + "path": "local-transfer-check.py", + "sha256": "sha256:97fbec826d517b1746a2ec5ef67e5aab053777690977abdddc8b33ec5804e22e" + }, + { + "path": "local-transfer-verification.json", + "sha256": "sha256:2331b854389b4c15f5adce1f62aa0846e9f47dfda351a7ef166c90b5e9b2737c" + }, + { + "path": "pre-absolute-path-dry-run.json", + "sha256": "sha256:9ea47b71b8636e4beb7dadf595f1d08bde261c9986986a1335d0c00d581a6d0c" + }, + { + "path": "process-fixture-results.json", + "sha256": "sha256:fc72ce36758ea40010aeb7c4ce97c167d09784fde63eeb9b2ccaa3428dc9a831" + }, + { + "path": "process_supervision.py", + "sha256": "sha256:4d990ee05003040e74bfdac7b2e49a3ef1512d4a054b3ff2317647b3670dd1bf" + }, + { + "path": "r1-preservation.json", + "sha256": "sha256:50848ad45fda68f43e11710bb04b0793717a5d0659e3fbcf8003708ee5ccb542" + }, + { + "path": "r1-to-r2.patch", + "sha256": "sha256:63f35421b67576188ff92d920319a23aa0a6606b9a2137d1061687c77126b541" + }, + { + "path": "r2-initial-readonly-dry-run.json", + "sha256": "sha256:631c6f0d8c0f66f51509751094937fd909821d4f45a042fd77f9d10f237115b8" + }, + { + "path": "retained-headroom-refusal.json", + "sha256": "sha256:89c847b4a75f138ff2464fc8291cf36939d293a777fd41ffd41d72f96ba9a2f8" + }, + { + "path": "retained-local-source-refusal.json", + "sha256": "sha256:fdd3b584da6ebedc173ccc5f8879c42dbd821d4b98338337fbef809a403485ba" + }, + { + "path": "runtime.py", + "sha256": "sha256:19b5fb687288bbc836978b0251599dc1e4d1593e8b5efee85a6abb6a7f388412" + }, + { + "path": "target-readonly-addendum.json", + "sha256": "sha256:9588f51b353093d178b8c52892ca627c2317fab761591715a0765603a4182a8f" + }, + { + "path": "target-readonly-observation.json", + "sha256": "sha256:ba4012303e5634686208a818b221113a1a670c31ed29269d8a4cc98308616275" + }, + { + "path": "test_process_supervision.py", + "sha256": "sha256:7ea89d9dee17d6ac4b554e25c083ac43d9b94039a4bcbe411d0e2ccbfb55a180" + }, + { + "path": "transfer-archive.json", + "sha256": "sha256:3222ccd39ea968353ea5e58ec36f0e3ca35b608020444243b053074a36abfe0b" + } + ] +} diff --git a/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/r2-to-r3-supervisor.patch b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/r2-to-r3-supervisor.patch new file mode 100644 index 0000000..8ec42e1 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/r2-to-r3-supervisor.patch @@ -0,0 +1,136 @@ +--- r2/process_supervision.py ++++ r3/process_supervision.py +@@ -22,7 +22,7 @@ + try: + path=Path('/proc')/str(pid);raw=(path/'stat').read_text();v=raw[raw.rfind(')')+2:].split() + return {'pid':pid,'ppid':int(v[1]),'pgrp':int(v[2]),'sid':int(v[3]),'start':int(v[19]),'uid':path.stat().st_uid,'state':v[0]} +- except FileNotFoundError:return None ++ except (FileNotFoundError,ProcessLookupError):return None + + + def write(path,value): +@@ -88,6 +88,24 @@ + if os.getppid()!=expected:os._exit(125) + + ++def reap_orphan_zombies(rows,leaders): ++ """Reap only dead adopted direct children; preserve every Popen wait status.""" ++ reaped=[] ++ for pid,row in rows.items(): ++ if row['ppid']!=os.getpid() or row['state']!='Z' or pid in leaders:continue ++ current=facts(pid) ++ if current is None:continue ++ if current!=row or row['uid']!=os.getuid():raise RuntimeError('orphan_mapping_unknown') ++ waited,status=os.waitpid(pid,os.WNOHANG) ++ if waited:reaped.append({'facts':row,'exit_code':os.waitstatus_to_exitcode(status)}) ++ return reaped ++ ++ ++def job_report(jobs): ++ return [{'label':j['request']['label'],'leader':j['leader'],'returncode':j['p'].returncode, ++ 'status':j.get('status','running')} for j in jobs.values()][:16] ++ ++ + def watchdog(directory,parent_fd): + directory=Path(directory) + if ctypes.CDLL(None,use_errno=True).prctl(36,1,0,0,0)!=0:raise RuntimeError('subreaper_unavailable') +@@ -95,7 +113,7 @@ + current=facts(root['pid']) + if current is None or current['start']!=root['start'] or current['uid']!=os.getuid() or dead(parent_fd):return 1 + write(directory/'ready.json',facts(os.getpid())) +- jobs={};abort=None;uncertain=False ++ jobs={};abort=None;uncertain=False;reaped=[];leftover=[] + try: + while True: + if dead(parent_fd):abort='controller_lost' +@@ -109,15 +127,21 @@ + if owner['pid']!=root['pid'] and owner['pid'] not in descendants(os.getpid()):raise ValueError() + if type(r['timeout']) not in (int,float) or not 0=16 or abort:raise ValueError() + owner_fd=os.pidfd_open(owner['pid']) ++ bound=facts(owner['pid']) ++ if bound is None or bound['start']!=owner['start'] or bound['uid']!=owner['uid']: ++ os.close(owner_fd);raise ValueError() + supervisor_pid=os.getpid() + child=subprocess.Popen(r['argv'],stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE if r['capture'] else subprocess.DEVNULL, + stderr=subprocess.DEVNULL,env=ENV,start_new_session=True, + preexec_fn=lambda parent=supervisor_pid:parent_death(parent)) + jobs[key]={'p':child,'request':r,'owner_fd':owner_fd,'deadline':time.monotonic()+r['timeout'], +- 'output':bytearray(),'eof':not r['capture'],'done':False} ++ 'output':bytearray(),'eof':not r['capture'],'done':False,'leader':facts(child.pid)} ++ write(directory/'public-journal.json',{'jobs':job_report(jobs),'reaped_orphan_zombies':reaped[-16:]}) + if child.stdout:os.set_blocking(child.stdout.fileno(),False) + except Exception: + abort=abort or 'request_mapping_refused';uncertain=True +@@ -136,27 +160,37 @@ + if dead(job['owner_fd']):abort=abort or 'caller_lost' + if time.monotonic()>=job['deadline']:abort=abort or 'timeout' + if p.poll() is not None and job['eof'] and not abort: +- # A returned leader is not evidence that its children exited. ++ # An adopted zombie is already dead, but must be waited ++ # before claiming quiescence. Do not steal a Popen leader's ++ # exit status or dismiss a genuinely live leftover. + own=descendants(os.getpid()) +- leaders={x['p'].pid for x in jobs.values() if not x['done']} +- if any(row['sid']==p.pid or (row['ppid']==os.getpid() and row['pid'] not in leaders) for row in own.values()):abort='leader_left_descendants' ++ leaders={x['p'].pid for x in jobs.values()} ++ reaped.extend(reap_orphan_zombies(own,leaders)) ++ own=descendants(os.getpid()) ++ active={x['p'].pid for x in jobs.values() if not x['done']} ++ leftover=[row for row in own.values() if row['sid']==p.pid or ++ (row['ppid']==os.getpid() and row['pid'] not in active)] ++ if leftover:abort='leader_left_descendants' + else: + write(directory/('result-'+key+'.json'),{'returncode':p.returncode,'quiescent':True,'status':'completed', + 'output_b64':base64.b64encode(job['output']).decode() if job['request']['capture'] else ''}) +- job['done']=True;os.close(job['owner_fd']) ++ job['done']=True;job['status']='completed';os.close(job['owner_fd']) ++ write(directory/'public-journal.json',{'jobs':job_report(jobs),'reaped_orphan_zombies':reaped[-16:]}) + if abort: + ok=quiesce(os.getpid(),[x['p'] for x in jobs.values()]) and not uncertain + for key,job in jobs.items(): + if not job['done']: + write(directory/('result-'+key+'.json'),{'returncode':124 if abort=='timeout' else 125, +- 'quiescent':ok,'status':abort,'output_b64':''});job['done']=True ++ 'quiescent':ok,'status':abort,'output_b64':''});job['done']=True;job['status']=abort + write(directory/'quiescent.json',{'quiescent':ok,'reason':abort,'no_live_or_waitable_descendants':ok, +- 'stderr_suppressed':True,'process_values_emitted':False}) ++ 'stderr_suppressed':True,'private_values_emitted':False,'jobs':job_report(jobs), ++ 'reaped_orphan_zombies':reaped[-16:],'leftover_at_failure':leftover[:16]}) + return 0 if ok else 1 + time.sleep(0.01) + except BaseException: + ok=quiesce(os.getpid(),[x['p'] for x in jobs.values()]) +- write(directory/'quiescent.json',{'quiescent':False,'reason':'supervisor_exception','best_effort_stopped':ok}) ++ write(directory/'quiescent.json',{'quiescent':False,'reason':'supervisor_exception','best_effort_stopped':ok, ++ 'jobs':job_report(jobs),'reaped_orphan_zombies':reaped[-16:],'leftover_at_failure':leftover[:16]}) + return 1 + + +@@ -172,7 +206,7 @@ + while not (self.directory/'ready.json').exists(): + if self.process.poll() is not None or time.monotonic()>end:raise RuntimeError('supervisor_start_failed') + time.sleep(0.01) +- def run(self,argv,timeout,*,capture=False):return request(self.directory,argv,timeout,capture=capture) ++ def run(self,argv,timeout,*,capture=False,label='job'):return request(self.directory,argv,timeout,capture=capture,label=label) + def close(self): + write(self.directory/'shutdown',{}) + try:self.process.wait(timeout=8) +@@ -182,9 +216,10 @@ + return report + + +-def request(directory,argv,timeout,*,capture=False): ++def request(directory,argv,timeout,*,capture=False,label='job'): + directory=Path(directory);key=uuid.uuid4().hex +- write(directory/('request-'+key+'.json'),{'owner':facts(os.getpid()),'argv':list(argv),'timeout':timeout,'capture':capture}) ++ if (directory/'quiescent.json').exists():raise RuntimeError('supervision_already_stopped') ++ write(directory/('request-'+key+'.json'),{'owner':facts(os.getpid()),'argv':list(argv),'timeout':timeout,'capture':capture,'label':label}) + end=time.monotonic()+timeout+10 + result=directory/('result-'+key+'.json') + while not result.exists(): diff --git a/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/r2-zombie-counterexample.json b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/r2-zombie-counterexample.json new file mode 100644 index 0000000..e090681 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/r2-zombie-counterexample.json @@ -0,0 +1 @@ +{"actual_public_process_trace": {"leader_pid": 6, "pid": 7, "ppid": 6, "start": 13166679, "state": "Z", "uid": 1000}, "actual_quiescence": {"no_live_or_waitable_descendants": true, "process_values_emitted": false, "quiescent": true, "reason": "leader_left_descendants", "stderr_suppressed": true}, "actual_returncode": 125, "causal_limit": "proves R2 source zombie false positive; native R2 trigger remains unproven because trace was deleted", "expected_after_dead_orphan_reap": 0, "fixture": "leader exits zero after proving its only child is already-dead zombie", "native_or_namespace_or_provider_calls": 0, "subject": "exact frozen R2 supervisor", "subject_sha256": "sha256:4d990ee05003040e74bfdac7b2e49a3ef1512d4a054b3ff2317647b3670dd1bf"} diff --git a/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/reproduce-r2.py b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/reproduce-r2.py new file mode 100644 index 0000000..085ed19 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/reproduce-r2.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +"""Exact frozen R2 zombie counterexample; disposable local processes only.""" +import hashlib +import importlib.util +import json +from pathlib import Path +import sys +import tempfile + +sys.dont_write_bytecode=True + +HERE=Path(__file__).resolve().parent +SUBJECT=HERE.with_name('native-runtime-install-r2')/'process_supervision.py' +SPEC=importlib.util.spec_from_file_location('exact_r2',SUBJECT) +S=importlib.util.module_from_spec(SPEC);SPEC.loader.exec_module(S) +CODE='''import os,time,json,sys +from pathlib import Path +pid=os.fork() +if pid==0:os._exit(0) +for _ in range(100): + text=(Path('/proc')/str(pid)/'stat').read_text();v=text[text.rfind(')')+2:].split() + if v[0]=='Z':break + time.sleep(.005) +assert v[0]=='Z' +Path(sys.argv[1]).write_text(json.dumps({'pid':pid,'ppid':int(v[1]),'state':v[0],'start':int(v[19]),'uid':os.getuid(),'leader_pid':os.getpid()})) +''' +with tempfile.TemporaryDirectory(prefix='nk-exact-r2-zombie-') as d: + base=Path(d);trace=base/'public-process-trace.json';supervisor=S.Supervisor(base/'state') + code,output=supervisor.run([sys.executable,'-B','-c',CODE,str(trace)],2,capture=True) + report=supervisor.close() + result={'subject':'exact frozen R2 supervisor','subject_sha256':'sha256:'+hashlib.sha256(SUBJECT.read_bytes()).hexdigest(), + 'fixture':'leader exits zero after proving its only child is already-dead zombie', + 'actual_public_process_trace':json.loads(trace.read_text()),'actual_returncode':code,'actual_quiescence':report, + 'expected_after_dead_orphan_reap':0,'native_or_namespace_or_provider_calls':0, + 'causal_limit':'proves R2 source zombie false positive; native R2 trigger remains unproven because trace was deleted'} + assert code==125 and report['reason']=='leader_left_descendants' and report['quiescent'] + print(json.dumps(result,sort_keys=True)) diff --git a/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/retained-fixture-refinement.json b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/retained-fixture-refinement.json new file mode 100644 index 0000000..1c84a7f --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/retained-fixture-refinement.json @@ -0,0 +1,9 @@ +{ + "first_run": "7 passed, 1 fixture expectation failed", + "case": "true live orphan", + "observed": 124, + "initial_expected": 125, + "cause": "live child inherited captured stdout and held EOF open, so existing whole-job timeout correctly stopped it before normal-completion orphan classification", + "refinement": "disable capture for this case to exercise immediate leader_left_descendants classification; actual runtime launcher capture remains false", + "native_actions": 0 +} diff --git a/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/runtime.py b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/runtime.py new file mode 100644 index 0000000..231fb72 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/runtime.py @@ -0,0 +1,52 @@ +"""Digest-pinned, owner-configured Python runtimes for bwrap.""" + +from __future__ import annotations + +import hashlib +import json +import os +import stat +from pathlib import Path + +RUNTIME_MOUNT = "/opt/sandboxer/runtime" + + +def runtime_digest(root: Path) -> str: + """Hash the complete artifact, including modes and internal symlink targets.""" + if not root.is_absolute() or root.resolve(strict=True) != root or not root.is_dir(): + raise ValueError("runtime path must be a canonical absolute directory") + records = [] + for path in sorted(root.rglob("*")): + mode = path.lstat().st_mode + if stat.S_ISLNK(mode): + target = os.readlink(path) + if Path(target).is_absolute() or not path.resolve(strict=True).is_relative_to(root): + raise ValueError("runtime symlink escapes the artifact") + value = ["symlink", target] + elif stat.S_ISREG(mode): + with path.open("rb") as stream: + value = ["file", hashlib.file_digest(stream, "sha256").hexdigest()] + elif stat.S_ISDIR(mode): + value = ["directory"] + else: + raise ValueError("runtime contains an unsupported special file") + records.append([path.relative_to(root).as_posix(), stat.S_IMODE(mode), *value]) + return hashlib.sha256(json.dumps(records, separators=(",", ":")).encode()).hexdigest() + + +def verified_runtime(config: dict) -> Path | None: + """Only owner extension configuration can select a runtime artifact.""" + runtime = config.get("runtime") + if runtime is None: + return None + if not isinstance(runtime, dict) or set(runtime) != {"path", "sha256"}: + raise ValueError("runtime requires exactly path and sha256") + if not isinstance(runtime["path"], str) or not isinstance(runtime["sha256"], str): + raise ValueError("runtime path and sha256 must be strings") + root = Path(runtime["path"]) + if (not (root / "pyvenv.cfg").is_file() or not (root / "bin/python3").is_file() + or (root / ".git").exists()): + raise ValueError("runtime must be a standalone Python environment, not a checkout") + if runtime_digest(root) != runtime["sha256"]: + raise ValueError("runtime artifact digest does not match owner configuration") + return root diff --git a/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/source-contract-pins.json b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/source-contract-pins.json new file mode 100644 index 0000000..d34918d --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/source-contract-pins.json @@ -0,0 +1,25 @@ +{ + "owner_task": "HARNESS-WP-0008-T07", + "canonical_owner_source_edited": false, + "source_refs": [ + { + "path": "/home/worsch/rein-aharness/AGENTS.md", + "sha256": "sha256:da71c6e3d76d7c867b2db6e67cd919fba594acc3804d3784dd744438103560c2" + }, + { + "path": "/home/worsch/rein-aharness/workplans/REINAH-WP-0008-simpler-metered-retry.md", + "sha256": "sha256:1118924bb159f8a52a37ace8b8663c75274b89928f7a8b957638428af224b054" + }, + { + "path": "/home/worsch/sand-boxer/docs/bwrap-runtime.md", + "sha256": "sha256:3b45a7f9a8e7f496f6b4102cd2508826c17a7d61b7578ede1a4c63215c467134" + }, + { + "path": "/tmp/netkingdom-runtime-canonical-20261016/runtime/lib/python3.12/site-packages/rein_aharness/owner_rehearsal.py", + "sha256": "sha256:192f654cad4a5f065c7faa5e35b03493297a472e8d7fe641be2c42c5fb00dcc3" + } + ], + "r2_existing_bytes_unchanged": true, + "r2_native_cause": "unproven; exact source zombie bug demonstrated only", + "r3_no_execution_receipt": true +} diff --git a/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/test_process_supervision.py b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/test_process_supervision.py new file mode 100644 index 0000000..5d4d41b --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/native-runtime-install-r3/test_process_supervision.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +"""Disposable actual process-tree oracles; never invoke runtime, SSH or bwrap.""" +import ctypes +import importlib.util +import json +import os +from pathlib import Path +import select +import signal +import subprocess +import sys +import tempfile +import time +import unittest + +HERE=Path(__file__).resolve().parent +SPEC=importlib.util.spec_from_file_location('supervised',HERE/'process_supervision.py') +S=importlib.util.module_from_spec(SPEC);SPEC.loader.exec_module(S) +CANARY='fixture-private-stderr-must-not-appear' +# A session-escaping grandchild remains owned by the subreaper even after the +# intermediate process exits. No namespace or application launcher is involved. +LEAF="import os,time,sys;from pathlib import Path;Path(sys.argv[1]).write_text(str(os.getpid()));print(sys.argv[2],file=sys.stderr,flush=True);time.sleep(30)" +LEADER="import subprocess,sys,time;subprocess.Popen([sys.executable,'-c',sys.argv[1],sys.argv[2],sys.argv[3]],start_new_session=True);time.sleep(30)" + + +def wait_until(predicate,timeout=8): + end=time.monotonic()+timeout + while time.monotonic()= 2*1024**3,'disk_headroom') + mem=dict(x.split(':',1) for x in Path('/proc/meminfo').read_text().splitlines()) + require(int(mem['MemAvailable'].split()[0])*1024>=1024**3,'memory_headroom') + require(os.getloadavg()[0]<=2*(os.cpu_count() or 1),'load_headroom') + read_cpu=lambda:[int(x) for x in Path('/proc/stat').read_text().splitlines()[0].split()[1:9]] + a=read_cpu();time.sleep(1);b=read_cpu();d=[y-x for x,y in zip(a,b)] + require(1-(d[3]+d[4])/sum(d)<=0.85,'cpu_headroom') + + +def runtime_reader(): + p=STAGE/'runtime.py';protected(p,mode=0o600) + require(file_digest(p)==RUNTIME_MODULE,'runtime_reader_pin') + spec=importlib.util.spec_from_file_location('approved_runtime_reader',p) + module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module) + return module + + +def expired(signum, frame): + raise TimeoutError('bounded_stage_deadline') + + +def rehearse_phase(supervision): + from rein_aharness import owner_rehearsal + require(Path(owner_rehearsal.__file__).resolve().is_relative_to(DESTINATION) + and file_digest(Path(owner_rehearsal.__file__))==REHEARSAL_MODULE,'rehearsal_source_pin') + require(owner_rehearsal.SYSTEM_RO_BINDS==('/usr','/bin','/lib','/lib64','/etc/resolv.conf','/etc/ssl/certs') + and all(Path(p).exists() for p in owner_rehearsal.SYSTEM_RO_BINDS),'frozen_system_bind_contract') + signal.signal(signal.SIGALRM,expired);signal.alarm(240) + # Keep the frozen owner launch contract; use its supported Runner seam. + def bounded_run(argv,timeout): + require(argv[0]=='/usr/bin/bwrap' and '--die-with-parent' not in argv,'bwrap_runner_contract') + return supervision_request(supervision,[argv[0],'--die-with-parent',*argv[1:]],timeout,label=Path(argv[-2]).name)[0] + try:steps=owner_rehearsal.rehearse(DESTINATION,run=bounded_run,bwrap_bin='/usr/bin/bwrap') + finally:signal.alarm(0) + require([x.name for x in steps]==['rein-aharness','glas-harness','claude'],'rehearsal_steps') + report={x.name:x.exit_code for x in steps} + print(json.dumps({'status':'rehearsed' if all(x.ok for x in steps) else 'rehearsal_failed','steps':report, + 'model_calls':0,'credential_reads':False,'owner_configuration_read':False},sort_keys=True)) + return 0 if all(x.ok for x in steps) else 1 + + +def supervision_module(): + spec=importlib.util.spec_from_file_location('approved_process_supervision',STAGE/'process_supervision.py') + module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module) + return module + + +def supervision_request(directory,argv,timeout,*,label): + return supervision_module().request(directory,argv,timeout,label=label) + + +def phase(supervisor, argv, timeout): + code,output=supervisor.run(argv,timeout,capture=True,label='rehearsal-phase') + try:result=json.loads(output) + except Exception:raise ValueError('phase_receipt_unavailable') from None + return code,result + + +def public_snapshot(): + p=STAGE/'supervision'/'public-journal.json' + if not p.exists():return {'status':'unavailable'} + protected(p,mode=0o600);require(p.stat().st_size<=65536,'journal_bound') + return json.loads(p.read_text()) + + +def persist(fd,receipt): + # The descriptor remains bound to the one exclusively created public receipt. + data=(json.dumps(receipt,sort_keys=True)+'\n').encode() + require(len(data)<=65536,'durable_receipt_bound') + require(os.fstat(fd).st_size==0,'durable_receipt_already_written') + with os.fdopen(os.dup(fd),'wb') as stream:stream.write(data);stream.flush();os.fsync(stream.fileno()) + + +def main(): + parser=argparse.ArgumentParser(description=__doc__) + parser.add_argument('--stage-inode',type=int) + parser.add_argument('--phase',choices=('rehearse',)) + parser.add_argument('--supervision-dir') + args=parser.parse_args() + if args.phase: + require(args.supervision_dir==str(STAGE/'supervision'),'supervision_path') + return rehearse_phase(args.supervision_dir) + receipt={'proposal_id':'REINAH-WP-0008-T07-install-f599-r1','revision':3,'status':'failed', + 'model_calls':0,'credential_reads':False,'owner_configuration_read':False, + 'renewal_or_catalog_or_queue_mutation':False,'install_calls':0,'destination':str(DESTINATION), + 'durable_public_receipt':str(DURABLE_RECEIPT)} + identity=None;supervisor=None;quiescent=False;receipt_fd=None;durable=False + try: + observed=protected(STAGE,directory=True,mode=0o700) + require(observed.st_ino==args.stage_inode and observed.st_dev==2049,'stage_identity') + identity=observed + require(not DURABLE_RECEIPT.exists() and not DURABLE_RECEIPT.is_symlink(),'receipt_already_present') + os.nice(19) + supervisor=supervision_module().Supervisor(STAGE/'supervision') + identify(supervisor);protected(DESTINATION,directory=True) + require({p.name for p in STAGE.iterdir()}=={'runtime.py','verify-installed.py','process_supervision.py','supervision'},'stage_inventory') + require(runtime_reader().runtime_digest(DESTINATION)==DIGEST,'installed_complete_digest') + receipt['installed_complete_digest_verified']=True + receipt['phase']='offline_rehearsal' + code,result=phase(supervisor,[str(DESTINATION/'bin/python3'),'-I','-B',str(STAGE/'verify-installed.py'),'--phase','rehearse','--supervision-dir',str(STAGE/'supervision')],260) + receipt['rehearsal']=result;require(code==0 and result['status']=='rehearsed','rehearsal_failed') + require(runtime_reader().runtime_digest(DESTINATION)==DIGEST,'post_rehearsal_complete_digest') + receipt['post_rehearsal_complete_digest_verified']=True + receipt['status']='installed_runtime_offline_rehearsal_verified' + except Exception as exc: + receipt['failure_type']=type(exc).__name__ + if isinstance(exc,ValueError) and str(exc).replace('_','').isalnum():receipt['failure_code']=str(exc) + finally: + if supervisor is not None: + try: + receipt['process_supervision']=supervisor.close();quiescent=True + except Exception: + receipt['process_supervision']={'quiescent':False,'status':'cleanup_unverified'} + receipt['status']='failed' + if identity is not None: + try:receipt['public_job_journal']=public_snapshot() + except Exception:receipt['public_job_journal']={'status':'unavailable_or_invalid'};receipt['status']='failed' + receipt['launcher_status']=[] + jobs=receipt.get('process_supervision',{}).get('jobs',receipt.get('public_job_journal',{}).get('jobs',[])) + for name in ('rein-aharness','glas-harness','claude'): + found=[j for j in jobs if j['label']==name] + receipt['launcher_status'].append({'name':name,'status':found[0]['status'] if len(found)==1 else 'not_started_or_unverified', + 'exit_code':found[0]['returncode'] if len(found)==1 else None}) + receipt['owned_staging_removed']=False;receipt['cleanup_status']='pending' + receipt['native_receipt_scope']='immutable precleanup checkpoint; final cleanup outcome in local execution receipt' + receipt['durable_receipt_before_cleanup']=True + try: + receipt_fd=os.open(DURABLE_RECEIPT,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600) + persist(receipt_fd,receipt) + directory_fd=os.open(DURABLE_RECEIPT.parent,os.O_RDONLY|os.O_DIRECTORY) + try:os.fsync(directory_fd) + finally:os.close(directory_fd) + durable=True + except Exception: + receipt['status']='failed';receipt['durable_receipt_before_cleanup']=False + if quiescent and durable: + try: + current=protected(STAGE,directory=True,mode=0o700) + require((current.st_dev,current.st_ino)==(identity.st_dev,identity.st_ino),'cleanup_stage_identity') + require({p.name for p in STAGE.iterdir()} <= {'runtime.py','verify-installed.py','process_supervision.py','supervision'},'cleanup_unexpected_stage_member') + shutil.rmtree(STAGE) + receipt['owned_staging_removed']=not STAGE.exists() and not STAGE.is_symlink() + receipt['cleanup_status']='removed_after_durable_receipt_and_quiescence' + except Exception:receipt['cleanup_status']='failed';receipt['status']='failed' + else: + receipt['cleanup_status']='retained_for_quiescence_or_receipt_review' + if receipt_fd is not None:os.close(receipt_fd) + print(json.dumps(receipt,sort_keys=True)) + return 0 if receipt['status']=='installed_runtime_offline_rehearsal_verified' else 1 + + +if __name__=='__main__': + try:raise SystemExit(main()) + except Exception as exc: + print(json.dumps({'status':'failed','failure_type':type(exc).__name__,'private_diagnostics_suppressed':True})) + raise SystemExit(1) from None