diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/fresh-readonly-preflight.json b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/fresh-readonly-preflight.json new file mode 100644 index 00000000..07c885e4 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/fresh-readonly-preflight.json @@ -0,0 +1,46 @@ +{ + "schema": "state95.guard-successor-readonly-preflight.v1", + "observed_at": "2026-10-17T00:33:19.687847+00:00", + "target": "railiance01/92.205.62.239", + "node_identity_verified": true, + "kubeconfig": "/home/worsch/.kube/config-railiance01", + "helm_revision": 76, + "current_image_manifest": "sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015", + "health": { + "status": "ok", + "db": "connected", + "instance_role": "primary", + "instance_label": "railiance01", + "schema": { + "status": "ok", + "applied": "c4d5e6f7a8b9", + "expected": "c4d5e6f7a8b9" + } + }, + "headroom": { + "schema": "state-hub.release-headroom-preflight.v1", + "ok": true, + "observed_at": "2026-10-17T00:33:19.561580+00:00", + "freshness_seconds": 0, + "remaining_cpu_m": 420, + "remaining_memory_bytes": 6535979008, + "pending_unrelated_cpu_m": 0, + "api_surge_cpu_m": 100, + "mcp_surge_cpu_m": 10, + "migrate_cpu_m": 50, + "atomic": true, + "reasons": [], + "notes": [ + "Aggregate remaining millicores is not a kube-scheduler guarantee.", + "Preflight does not lower requests and does not start Helm." + ], + "hook_order": [ + "pre-upgrade migrate job (helm.sh/hook-weight -5)", + "API RollingUpdate maxSurge=1 maxUnavailable=0", + "MCP RollingUpdate maxSurge=1 maxUnavailable=0" + ] + }, + "migration_job_and_pods_absent": true, + "runtime_readback_ref": "../receiver-rollout-r4/final-runtime-check.json", + "live_changes": false +} diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/pending-decision.json b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/pending-decision.json new file mode 100644 index 00000000..316dfd6d --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/pending-decision.json @@ -0,0 +1,18 @@ +{ + "workplan_id": "79579572-8120-5d3b-aca5-dd4aff60d058", + "id": "df703266-6719-4c04-ae1b-ba9afd3bca1c", + "topic_id": null, + "title": "Review exact frozen-binding API guard successor and retained baseline exception", + "description": "Pending proposal STATE-WP-0095-T10-frozen-bindings-guard-r1 revision1 SHA25681d365926fc585a186955d8c98ee83501fd3cec12135b2256d71b46a75097932. Image main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1; frozen chart b5c0663d830fc8d3b65080f237a0be8d01f8b2f7fec9dc33aeba91b1b6b281d4. Eight-identity image-only server dry-run and explicit target/headroom/schema pass. Verified installed checker maintenance preserves all18 CURRENT rows; prior three-parent hash failures remain historical exceptions, never restored or backfilled. Requested ADMINISTER @ realm:kubernetes/railiance01 activation=APPROVED includes exact new Job conditional recovery, rollback76 after verified quiescence, current-baseline successor acceptance, unpriced cost/risk and attended stop for unknown mapping. Full review: the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/FINAL-PROPOSAL.md. No new runtime is approved or executed.", + "decision_type": "pending", + "status": "open", + "rationale": "Agent-prepared exact new-code proposal. Only the founder may resolve approval; prior receiver revision4 approval does not cover this new image.", + "decided_by": null, + "decided_at": null, + "deadline": null, + "escalation_note": null, + "superseded_by": null, + "created_at": "2026-10-17T00:37:26.205185Z", + "updated_at": "2026-10-17T00:37:26.205185Z", + "workstream_id": "79579572-8120-5d3b-aca5-dd4aff60d058" +} diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/probe-helper-preparation-check.json b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/probe-helper-preparation-check.json new file mode 100644 index 00000000..bfd40a7e --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/probe-helper-preparation-check.json @@ -0,0 +1,7 @@ +{ + "status": "prepared-helper-contract-checked", + "actual_workplan_binding_schema_accepts_exact_three": true, + "commit_interception_checked_with_actual_unbound_AsyncSession": true, + "DB_connected": false, + "native_probes_executed": false +} diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/proposal-metadata.json b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/proposal-metadata.json new file mode 100644 index 00000000..59868071 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/proposal-metadata.json @@ -0,0 +1,103 @@ +{ + "schema": "custodian.supervised-runtime-proposal.v1", + "proposal_id": "STATE-WP-0095-T10-frozen-bindings-guard-r1", + "revision": 1, + "status": "pending_supervisor_approval_not_executed", + "prepared_at": "2026-10-17T00:37:11.095872+00:00", + "owner_task_id": "STATE-WP-0095-T10", + "owner_task_uuid": "88585c69-d9b9-5e1f-a256-6416ff71e824", + "target": "railiance01/92.205.62.239 state-hub release API/MCP and exact-new-trial migration Job", + "authority_requested": "ADMINISTER @ realm:kubernetes/railiance01 activation=APPROVED", + "source_commit": "c103cbf3c8bed5dba87a03e320595ddded9301a3", + "image": "forgejo.coulomb.social/coulomb/state-hub:main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1", + "image_digest": "sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1", + "chart_archive": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/revisions/r4-rollout-review/state-hub-0.1.0.tgz", + "chart_sha256": "b5c0663d830fc8d3b65080f237a0be8d01f8b2f7fec9dc33aeba91b1b6b281d4", + "remote_owned_chart": "/tmp/statehub-binding-guard-b5c0663d830f/state-hub-0.1.0.tgz", + "expected_prior_helm_revision": 76, + "expected_schema": "c4d5e6f7a8b9", + "migration_files_changed_since_deployed_source": false, + "exact_upgrade_argv": [ + "ssh", + "railiance01", + "KUBECONFIG=/etc/rancher/k3s/k3s.yaml helm -n state-hub upgrade state-hub /tmp/statehub-binding-guard-b5c0663d830f/state-hub-0.1.0.tgz --reuse-values --set-string image.tag=main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1 --atomic --timeout 3m" + ], + "staging": "Create exact new private owned directory, refuse unexpected existing ownership/content, copy only frozen chart, verify remote SHA before execution and remove only owned exact archive/directory after proof.", + "readiness_refresh_required_before_execution": true, + "native_probe_cases": 8, + "verification": [ + "Delivered manifest and guard module hashes, API/MCP Ready, primary/railiance01 and expected schema.", + "Fresh CURRENT full historical-row hashes before and after; enforce rollback and commit interception for eight exact native cases, then fresh-transaction comparison including ordinary parent.", + "Verified installed local CLI guard, ordinary consistency/fast projection between current snapshots, archive bytes and unrelated staged work preserved; source/remote/Hub IDs/statuses read back." + ], + "acceptance_disposition_requested": "Use current hashed baseline for successor acceptance; retain the earlier three-parent mismatch as a historical exception, without guessed restoration or backfilling its failed result. T07/RMGR19T06 close only after new receiving and corrected pipeline pass with exception recorded.", + "conditional_recovery_included": true, + "rollback_helm_revision": 76, + "recovery_limits": "Exact observed new Job UID/image/ownership and bound Pods only; foreground UID-precondition cancellation after revalidation. Verify private owner DB connection/transaction/lock quiescence; unknown mapping/mixed state stops for attended recovery. No schema downgrade or broad backend termination.", + "cost_estimate_eur": null, + "risk_estimate_eur": null, + "cost_and_risk_disposition": "Unpriced attributable cost and credible loss/outage exposure on existing infrastructure require supervised founder acceptance. Helm timeout is not Job/outage bound.", + "autopilot_grant": false, + "bound_receipts": [ + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/FINAL-PROPOSAL.md", + "sha256": "7addca35af1973be4a2f2c1aeec658a00b581108da03e6509e645045f2767ae4" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/RECOVERY.md", + "sha256": "dc72dea7cdf519a712ee8f6d28fc6aa5667b44655d948f2c0160869383f33cef" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/native_binding_probes.py", + "sha256": "ef5c07affb15d20b4194edda68fd14b4d0fee1e9dab857e3242c3a1a1e49046f" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/probe-helper-preparation-check.json", + "sha256": "e11f48b18cdb57e1b8f2bddcf7e6ffc7473ab2ab2b031c3c1a26afae609f831c" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/watch_rollout.py", + "sha256": "a496da3c9a415ac800921ce755446be4bb55bc17e4e6b913143a5d6baf48f815" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/reuse_values_dryrun.py", + "sha256": "5e653ae8439c773b27a167464b620805055e9d820a907761ab30652e65fae200" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/exact-server-dryrun.json", + "sha256": "2bfb02130876aeb71a773585dae8b446ad5c671050ebc14d0fc7bcce86dffea5" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/fresh-readonly-preflight.json", + "sha256": "01bbdc94ebd6fb266ab21bdc0851eac6eddeafbabcd7105cce23806931ebfbce" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-image-c103cbf-inspection.json", + "sha256": "21b4ddd3fa65743bed5eae568e0c08f2d150fcecacb7949263cef06bab99a683" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/before-verified-client.json", + "sha256": "ee41be2d74085603abf2bfe2c91c1bb430b4ea0539a6cbf988528b18f3f5ef46" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/after-verified-client.json", + "sha256": "8cd8921271cd647fe40dde0f00bc55b714313426e14091397d8fd11314c5e610" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/verified-loaded-client.json", + "sha256": "0745b5df92d2533714e2a87a43c73b3b685e7bab7bcf4444f7a5fbd82dd4722f" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/verified-client-cluster-projection.json", + "sha256": "c25b43575273df0e648842d447979aca1b41ade16fcdaf9f5f870024e7ca06e9" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/verified-client-cluster-consistency.json", + "sha256": "fd8a7d58d9e08b4866420ad77d01fbc7c28c0c2c9610ffbe95ddc77dddd9b1a6" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/ordinary-pipeline-preservation-finding.json", + "sha256": "43d4adc0ffbf61345865eebb3f8948f82a3c33a28fd0436eccd08bc93ba973c1" + } + ] +} diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/reuse_values_dryrun.py b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/reuse_values_dryrun.py new file mode 100644 index 00000000..d1ea3cba --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/reuse_values_dryrun.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +"""Read-only exact owner Helm reuse-values render/diff; no raw output retained.""" +import copy +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess +import yaml +OWNER=Path('/home/worsch/state-hub') +KUBE='/home/worsch/.kube/config-railiance01' +CHART='/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/revisions/r4-rollout-review/state-hub-0.1.0.tgz' +CHART_SHA256='b5c0663d830fc8d3b65080f237a0be8d01f8b2f7fec9dc33aeba91b1b6b281d4' +TAG='main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1' +HELM=['helm','--kubeconfig',KUBE,'-n','state-hub'] +DRYRUN=HELM+['upgrade','state-hub',CHART,'--reuse-values','--set-string','image.tag='+TAG, + '--dry-run=server','--hide-secret','--no-hooks','--timeout','3m'] +PUBLIC_COORDINATES={'OPENBAO_K8S_TOKEN_PATH':'/var/run/secrets/openbao/token', + 'FORGE_READ_SECRET_PATH':'platform/data/workloads/state-hub/forge-derivation', + 'FORGE_READ_SECRET_KEY':'FORGE_READ_TOKEN'} +ALLOWED={'ConfigMap','Deployment','Job','Service','ServiceAccount','Namespace','Ingress'} +LIMIT=2*1024*1024 +class Refusal(RuntimeError):pass + +def digest(obj):return 'sha256:'+hashlib.sha256(json.dumps(obj,sort_keys=True,separators=(',',':')).encode()).hexdigest() + +def parse(raw): + if len(raw)>LIMIT:raise Refusal('render_size_refused') + result=[] + for obj in yaml.safe_load_all(raw): + if not obj:continue + if not isinstance(obj,dict) or obj.get('kind') not in ALLOWED:raise Refusal('unexpected_or_secret_resource_refused') + result.append(obj) + return result + +def sections(raw): + text=raw.decode() + if '\nMANIFEST:\n' not in text:raise Refusal('helm_render_sections_unrecognized') + before,manifest=text.split('\nMANIFEST:\n',1) + manifest=manifest.split('\nNOTES:',1)[0] + hooks=before.split('\nHOOKS:\n',1)[1] if '\nHOOKS:\n' in before else '' + return parse(hooks)+parse(manifest) + +def identity(obj): + meta=obj.get('metadata') or {} + return (obj['apiVersion'],obj['kind'],meta.get('namespace') or ('' if obj['kind']=='Namespace' else 'state-hub'),meta['name']) + +def diff_paths(a,b,prefix=''): + if type(a)!=type(b):return [prefix] + if isinstance(a,dict): + paths=[] + for key in sorted(set(a)|set(b)): + path=prefix+'/'+key.replace('~','~0').replace('/','~1') + paths.extend([path] if key not in a or key not in b else diff_paths(a[key],b[key],path)) + return paths + if isinstance(a,list): + if len(a)!=len(b):return [prefix] + return [p for i,(x,y) in enumerate(zip(a,b)) for p in diff_paths(x,y,prefix+'/'+str(i))] + return [] if a==b else [prefix] + +def compare(old,new): + baseline={identity(o):o for o in old};intended={identity(o):o for o in new} + if len(baseline)!=len(old) or len(intended)!=len(new):raise Refusal('duplicate_object_identity') + rows=[] + for key in sorted(set(baseline)|set(intended)): + a=baseline.get(key);b=intended.get(key);paths=diff_paths(a,b) + images=[] + if b and b['kind'] in {'Deployment','Job'}: + spec=b.get('spec',{}).get('template',{}).get('spec',{}) + for container in (spec.get('containers',[])+spec.get('initContainers',[])): + image=container.get('image','') + if not re.fullmatch(r'forgejo\.coulomb\.social/coulomb/state-hub:main-[0-9a-f]{7}(?:@sha256:[0-9a-f]{64})?',image):raise Refusal('unexpected_image_refused') + images.append(image) + for env in container.get('env',[]): + if 'value' in env and re.search(r'PASSWORD|TOKEN|SECRET|DATABASE_URL',env.get('name',''),re.I): + if env.get('name') not in PUBLIC_COORDINATES or env['value']!=PUBLIC_COORDINATES[env['name']]:raise Refusal('literal_sensitive_env_refused') + rows.append({'api_version':key[0],'kind':key[1],'namespace':key[2],'name':key[3], + 'intended_images':images,'intended_spec_sha256':digest(b.get('spec',{})) if b else None, + 'baseline_object_sha256':digest(a) if a else None,'intended_object_sha256':digest(b) if b else None, + 'changed_paths':paths}) + image_path=re.compile(r'^/spec/template/spec/(?:initContainers|containers)/[0-9]+/image$') + only_images=all(image_path.fullmatch(p) for row in rows for p in row['changed_paths']) + return {'objects':rows,'changed_object_count':sum(bool(r['changed_paths']) for r in rows), + 'intended_diff_only_images':only_images,'object_identity_set_unchanged':set(baseline)==set(intended)} + +def command(args): + env=dict(os.environ);env['KUBECONFIG']=KUBE + p=subprocess.run(args,cwd=OWNER,env=env,capture_output=True,timeout=45) + if len(p.stdout)>LIMIT:raise Refusal('command_output_size_refused') + if p.returncode: + lower=p.stderr.decode(errors='replace').lower() + label=next((label for marker,label in [('reject-secret-last-applied','secret_annotation_policy_refused'),('forbidden','admission_or_auth_refused'),('cannot patch','patch_validation_refused'),('is invalid','resource_validation_refused'),('timeout','bounded_timeout'),('connection refused','api_unavailable')] if marker in lower),'unknown_refusal') + raise Refusal(label) + return p.stdout + +def main(): + result={'schema':'statehub.exact-reuse-values-readonly-diff.v1','status':'failed', + 'dryrun_argv':DRYRUN,'production_mutations':False,'hooks_executed':False, + 'raw_helm_values_release_content_logs_retained':False, + 'limitations':['Dry-run admission/render only, not an actual image pull/migration/deployment.', + 'Helm timeout does not stop a later live migration Job; exact scoped cancellation/quiescence remains required.']} + try: + archive=Path(CHART) + if archive.is_symlink() or not archive.is_file() or hashlib.sha256(archive.read_bytes()).hexdigest()!=CHART_SHA256:raise Refusal('reviewed_chart_archive_drift') + result['chart_archive_sha256']='sha256:'+CHART_SHA256 + nodes=json.loads(command(['kubectl','--kubeconfig',KUBE,'get','nodes','-o','json'])) + if not any(a.get('address')=='92.205.62.239' for n in nodes.get('items',[]) for a in n.get('status',{}).get('addresses',[])):raise Refusal('target_identity_refused') + result['stage']='baseline_release_manifest' + old=parse(command(HELM+['get','manifest','state-hub'])) + result['stage']='baseline_release_hooks' + old+=parse(command(HELM+['get','hooks','state-hub'])) + result['stage']='exact_upgrade_server_dryrun' + new=sections(command(DRYRUN)) + result.update(compare(old,new));result['status']='passed_images_only' if result['intended_diff_only_images'] and result['object_identity_set_unchanged'] else 'review_nonimage_delta_required' + except (Refusal,subprocess.TimeoutExpired,ValueError,yaml.YAMLError) as error: + result['error_class']=type(error).__name__ + result['error_label']=str(error) if isinstance(error,Refusal) else 'bounded_parse_or_command_failure' + print(json.dumps(result,indent=2)) + return int(result['status']!='passed_images_only') +if __name__=='__main__':raise SystemExit(main()) diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/watch_rollout.py b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/watch_rollout.py new file mode 100644 index 00000000..c680ddc5 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r1/watch_rollout.py @@ -0,0 +1,57 @@ +"""Observe the approved release Job and owned Pods; never mutate resources.""" +from pathlib import Path +from datetime import datetime, timezone +import json +import subprocess +import time + +BASE = Path(__file__).resolve().parent +KUBE = ['kubectl', '--kubeconfig', '/home/worsch/.kube/config-railiance01', '-n', 'state-hub'] +IMAGE = 'forgejo.coulomb.social/coulomb/state-hub:main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1' +def get(args): + result = subprocess.run(KUBE + args, capture_output=True, timeout=10) + if result.returncode: + return None + return json.loads(result.stdout) if result.stdout.strip() else {} + +result = {'schema': 'state95.approved-job-observation.v1', 'mutation': False, + 'started_at': datetime.now(timezone.utc).isoformat(), 'jobs': {}, 'poll_failures': 0} +deadline = time.monotonic() + 240 +while time.monotonic() < deadline: + try: + job = get(['get', 'job', 'state-hub-migrate', '--ignore-not-found', '-o', 'json']) + if job is None: + result['poll_failures'] += 1 + elif job: + meta = job['metadata'] + images = [c['image'] for c in job['spec']['template']['spec']['containers']] + if (images != [IMAGE] or meta.get('labels', {}).get('app.kubernetes.io/instance') != 'state-hub' + or meta.get('labels', {}).get('app.kubernetes.io/managed-by') != 'Helm' + or meta.get('annotations', {}).get('helm.sh/hook') != 'pre-install,pre-upgrade'): + result['unexpected_job_identity'] = True + break + entry = result['jobs'].setdefault(meta['uid'], {'uid': meta['uid'], 'name': meta['name'], + 'created_at': meta['creationTimestamp'], 'image': IMAGE, 'managed_by': 'Helm', + 'release': 'state-hub', 'hook': 'pre-install,pre-upgrade', 'pods': {}}) + entry['last_status'] = {k: job.get('status', {}).get(k) for k in ['active', 'succeeded', 'failed']} + pods = get(['get', 'pods', '-l', 'job-name=state-hub-migrate', '-o', 'json']) + if pods: + for pod in pods['items']: + owners = pod['metadata'].get('ownerReferences', []) + if not any(o['uid'] == meta['uid'] and o['kind'] == 'Job' for o in owners): + continue + entry['pods'][pod['metadata']['uid']] = {'name': pod['metadata']['name'], + 'uid': pod['metadata']['uid'], 'owner_job_uid': meta['uid'], + 'pod_ip': pod.get('status', {}).get('podIP'), 'phase': pod.get('status', {}).get('phase')} + elif result['jobs']: + result['job_absent_after_observation'] = True + (BASE / 'job-observation.json').write_text(json.dumps(result, indent=2) + '\n') + if (BASE / 'helm-return.json').exists(): + result['helm_return_observed'] = True + break + except (subprocess.TimeoutExpired, ValueError, KeyError): + result['poll_failures'] += 1 + time.sleep(0.7) +result['ended_at'] = datetime.now(timezone.utc).isoformat() +(BASE / 'job-observation.json').write_text(json.dumps(result, indent=2) + '\n') +print(json.dumps({'observed_trial_jobs': len(result['jobs']), 'mutation': False})) diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/FINAL-PROPOSAL.md b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/FINAL-PROPOSAL.md new file mode 100644 index 00000000..7daf4025 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/FINAL-PROPOSAL.md @@ -0,0 +1,25 @@ +# Deploy the frozen-binding API guard + +Proposal **STATE-WP-0095-T10-frozen-bindings-guard-r1**, revision 2. Pending founder approval for `ADMINISTER @ realm:kubernetes/railiance01`, `activation=APPROVED`. + +The approved receiver rollout is healthy at Helm 76. Its later ordinary maintenance path exposed a separate defect: the legacy binding endpoint refreshed three frozen parent rows. All fifteen task rows and the archive bytes stayed unchanged. The original failed full-row comparison is retained; the original parent preimages were not saved and will not be guessed. + +The source fixes are published in State Hub c103cbf3c8bed5dba87a03e320595ddded9301a3. They protect the exact three bound parents and fifteen task UUIDs in the checker, repair engine and API binding endpoint. Ordinary inbound dependencies remain allowed. The installed checker is now hash-verified against that source. Its actual maintenance rerun omitted all three frozen bindings and preserved all eighteen **current** full rows. The unrelated staged brief accidentally included by the old writeback helper was restored through a forward commit; its original index/worktree state is preserved. Five real Git tests verify the corrected file-scoped commit behavior. Independent guard review passed 109 isolated cases; canonical guard tests passed 102. These are separate from new runtime acceptance. + +Promote this verified CI artifact through the owner Helm path: + +`forgejo.coulomb.social/coulomb/state-hub:main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1` + +Use the already frozen chart archive with SHA256 b5c0663d830fc8d3b65080f237a0be8d01f8b2f7fec9dc33aeba91b1b6b281d4, preserving release values. Its fresh server dry-run passes: eight identities, three image changes. Public read-only image inspection verifies the selected guard/receiver/migration files and CI build ID; package metadata and hub-core source hashes match the deployed image. Fresh target/schema/readiness/headroom checks pass. All are refreshed before actual execution. Floating build origins and complete application/concurrent-writer acceptance remain outside these byte checks. + +The exact upgrade is bound in proposal-metadata.json: stage the hash-verified archive in the specified owned remote directory, then use `--reuse-values --set-string image.tag= --atomic --timeout 3m`. Observe the exact new Job and owned Pods. Schema is already c4d5e6f7a8b9, and no migration file changed, but the existing migration hook and its recovery limitations still apply. + +Verify delivered digest and module hashes, API/MCP readiness, schema and full-row preservation from a fresh **current** baseline. Run the eight prepared native cases only inside rollback transactions with commit interception enforced, then compare a fresh transaction. The prepared probe helper covers all three parents, ordinary-first invalid batches and expected-task/ownership/status refusals; its actual schema and commit-interception contracts are checked without a database connection. New probes are not yet executed. Rerun the verified-client ordinary maintenance path between current hash snapshots, preserve archive bytes and unrelated staged files, and read back source/remote/Hub bindings. + +Approval includes using the current hashed rows for successor acceptance while retaining the prior parent-row mismatch as an explicit historical exception. It does not turn that failed comparison into a pass or restore its unavailable preimages. T07 and RMGR19T06 close only after the new receiving and corrected whole-pipeline checks pass with that exception recorded. All sixteen NetKingdom native target gates remain separate. + +Approval must also cover [the exact conditional recovery](RECOVERY.md): only the newly observed owned Job may be cancelled with a UID precondition; verify database connection/lock quiescence before rollback to Helm **76**. Unknown mapping or mixed state stops for attended owner recovery. Helm timeout is not an outage or Job-lifetime bound; rollback retains schema. + +Attributable EUR cost and credible loss exposure remain unpriced on the existing cluster. This needs scoped supervised approval and creates no autopilot, credential or monetary grant. + +Revision 2 changes only the probe helper’s representation of verified source hashes to explicit module/SHA256 records, resolving the normal scanner’s hash-metadata false positive. Runtime image, commands, case logic, acceptance and recovery scope are unchanged. Original revision 1 and its digest remain recorded; it was never approved or executed. diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/RECOVERY.md b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/RECOVERY.md new file mode 100644 index 00000000..7d699db3 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/RECOVERY.md @@ -0,0 +1,17 @@ +# Guard successor: conditional recovery + +Proposal STATE-WP-0095-T10-frozen-bindings-guard-r1, revision 1. + +The prior deployed release is Helm 76, with image manifest c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015 and schema c4d5e6f7a8b9. Refresh this baseline before execution. The successor's migration files are unchanged, but its existing pre-upgrade migration hook still runs. Its three-minute Helm wait does not stop the Job or bound outage, and the Job has no active deadline. + +Observe the new Job UID, creation timestamp, exact successor image, Helm release/hook ownership and owned Pod UIDs/IPs using watch_rollout.py. If Helm fails or times out with this trial Job active, revalidate all identities immediately. Approval includes deleting only this exact new Job with a UID precondition and foreground propagation, and observing its bound Pods terminate. A reused name, foreign ownership, unexpected image, unobserved UID or ambiguous mapping stops automatic recovery. No name-only or blanket deletion, finalizer removal or broad database cancellation is authorized. + +Through the existing private database-owner execution path, bind migration connections to the captured Pod/IP and start window, and verify those exact connections, transactions and locks are gone. Retain only connection-identity/state/lock-count metadata. Query text, parameters, credentials and DSNs remain private. If pooling/NAT, missing observation or access makes this mapping unknown, stop for attended database-owner recovery. Pod disappearance alone is insufficient. + +After verified quiescence, inspect actual schema and release state. If application rollback is needed, the approved prior target is **76**, retaining frozen Forge receiving support. Use the normal owner SSH path, KUBECONFIG=/etc/rancher/k3s/k3s.yaml, and: + +``` +helm -n state-hub rollback state-hub 76 --no-hooks --wait --timeout 3m +``` + +Verify image, readiness, schema, full-row hashes and remaining Job/Pod state. The database retains its additive schema; do not downgrade or invent old parent values. The verified installed client guard remains in use. Removing receiving support or reverting the client guard is outside this recovery. Unknown/mixed state stops for attended handling; a failed or rescued attempt remains a failed/refined outcome. diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/exact-server-dryrun.json b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/exact-server-dryrun.json new file mode 100644 index 00000000..ce1a1f9f --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/exact-server-dryrun.json @@ -0,0 +1,136 @@ +{ + "schema": "statehub.exact-reuse-values-readonly-diff.v1", + "status": "passed_images_only", + "dryrun_argv": [ + "helm", + "--kubeconfig", + "/home/worsch/.kube/config-railiance01", + "-n", + "state-hub", + "upgrade", + "state-hub", + "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/revisions/r4-rollout-review/state-hub-0.1.0.tgz", + "--reuse-values", + "--set-string", + "image.tag=main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1", + "--dry-run=server", + "--hide-secret", + "--no-hooks", + "--timeout", + "3m" + ], + "production_mutations": false, + "hooks_executed": false, + "raw_helm_values_release_content_logs_retained": false, + "limitations": [ + "Dry-run admission/render only, not an actual image pull/migration/deployment.", + "Helm timeout does not stop a later live migration Job; exact scoped cancellation/quiescence remains required." + ], + "chart_archive_sha256": "sha256:b5c0663d830fc8d3b65080f237a0be8d01f8b2f7fec9dc33aeba91b1b6b281d4", + "stage": "exact_upgrade_server_dryrun", + "objects": [ + { + "api_version": "apps/v1", + "kind": "Deployment", + "namespace": "state-hub", + "name": "state-hub", + "intended_images": [ + "forgejo.coulomb.social/coulomb/state-hub:main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1" + ], + "intended_spec_sha256": "sha256:df32d106c28b270c67aa683c8a4f20b26c430b121420c4023d34d23ede0eaed9", + "baseline_object_sha256": "sha256:01e7278447f3204ec8abfd9a7b5b6be61850995b3e792fc1f97fbc3207536d69", + "intended_object_sha256": "sha256:7b7c78921b071732c8e7c4c062bdbd26787498581102ea9916b07c1f57887a73", + "changed_paths": [ + "/spec/template/spec/containers/0/image" + ] + }, + { + "api_version": "apps/v1", + "kind": "Deployment", + "namespace": "state-hub", + "name": "state-hub-mcp", + "intended_images": [ + "forgejo.coulomb.social/coulomb/state-hub:main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1" + ], + "intended_spec_sha256": "sha256:50ae88c8c57919bf745a772a8ff34ea7e469751a403ac39d1d01ab2e9d3a27db", + "baseline_object_sha256": "sha256:9a4376e004060a4ec2c76784fd50ec6ce05f61b3793f6458625d88122a7b8713", + "intended_object_sha256": "sha256:b78c66a3e27bca60ff4dd4f51c1bbe789a4e246be3d1e7e6fb9659ffdce80127", + "changed_paths": [ + "/spec/template/spec/containers/0/image" + ] + }, + { + "api_version": "batch/v1", + "kind": "Job", + "namespace": "state-hub", + "name": "state-hub-migrate", + "intended_images": [ + "forgejo.coulomb.social/coulomb/state-hub:main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1" + ], + "intended_spec_sha256": "sha256:2eb273b5e2ca5538fa1b13a8cf8cd10d259ad7cd3510bce1e86075f8c4c4a915", + "baseline_object_sha256": "sha256:6909dca9acd5e1b816491b91b6a32ef6d934e288e2d8d0d3d66fac9fe67468ec", + "intended_object_sha256": "sha256:208b52d9b958b9d4490182bda4d0da27b4a6f0961f6bf182c34ef4b640fcbb5e", + "changed_paths": [ + "/spec/template/spec/containers/0/image" + ] + }, + { + "api_version": "v1", + "kind": "ConfigMap", + "namespace": "state-hub", + "name": "state-hub-classification-allowed", + "intended_images": [], + "intended_spec_sha256": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", + "baseline_object_sha256": "sha256:924d16a0a6b4ba8a46b59f0990dcc2adc6dd813cf5d7d83bdb389844068d7cfd", + "intended_object_sha256": "sha256:924d16a0a6b4ba8a46b59f0990dcc2adc6dd813cf5d7d83bdb389844068d7cfd", + "changed_paths": [] + }, + { + "api_version": "v1", + "kind": "ConfigMap", + "namespace": "state-hub", + "name": "state-hub-config", + "intended_images": [], + "intended_spec_sha256": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", + "baseline_object_sha256": "sha256:26707b20933b4f6de78f9059c0efec6df7f0e90c8f1a67db6d2f1ac72e674560", + "intended_object_sha256": "sha256:26707b20933b4f6de78f9059c0efec6df7f0e90c8f1a67db6d2f1ac72e674560", + "changed_paths": [] + }, + { + "api_version": "v1", + "kind": "Service", + "namespace": "state-hub", + "name": "state-hub", + "intended_images": [], + "intended_spec_sha256": "sha256:da5895e6aee1f619d16c07dafd4342cf7ec8f9de0944a84979c624a4e9eea22a", + "baseline_object_sha256": "sha256:62098bbc3ef6265e072f416ecb11de85d59fce7e2e0d22b2616bd233e720dafe", + "intended_object_sha256": "sha256:62098bbc3ef6265e072f416ecb11de85d59fce7e2e0d22b2616bd233e720dafe", + "changed_paths": [] + }, + { + "api_version": "v1", + "kind": "Service", + "namespace": "state-hub", + "name": "state-hub-mcp", + "intended_images": [], + "intended_spec_sha256": "sha256:4e04175b03bcd75833109d6843e282cee79d73eae2bc85e36097264b832ba26f", + "baseline_object_sha256": "sha256:ab096d239c9cd72e6177a260d9b4d548a7d5b7c0a285c3a11b7267193e336bba", + "intended_object_sha256": "sha256:ab096d239c9cd72e6177a260d9b4d548a7d5b7c0a285c3a11b7267193e336bba", + "changed_paths": [] + }, + { + "api_version": "v1", + "kind": "ServiceAccount", + "namespace": "state-hub", + "name": "state-hub", + "intended_images": [], + "intended_spec_sha256": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", + "baseline_object_sha256": "sha256:750594b51a534d603f50640d02a3c4eec79cc52fe07d5f442102d37b7299adb8", + "intended_object_sha256": "sha256:750594b51a534d603f50640d02a3c4eec79cc52fe07d5f442102d37b7299adb8", + "changed_paths": [] + } + ], + "changed_object_count": 3, + "intended_diff_only_images": true, + "object_identity_set_unchanged": true +} diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/fresh-readonly-preflight.json b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/fresh-readonly-preflight.json new file mode 100644 index 00000000..07c885e4 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/fresh-readonly-preflight.json @@ -0,0 +1,46 @@ +{ + "schema": "state95.guard-successor-readonly-preflight.v1", + "observed_at": "2026-10-17T00:33:19.687847+00:00", + "target": "railiance01/92.205.62.239", + "node_identity_verified": true, + "kubeconfig": "/home/worsch/.kube/config-railiance01", + "helm_revision": 76, + "current_image_manifest": "sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015", + "health": { + "status": "ok", + "db": "connected", + "instance_role": "primary", + "instance_label": "railiance01", + "schema": { + "status": "ok", + "applied": "c4d5e6f7a8b9", + "expected": "c4d5e6f7a8b9" + } + }, + "headroom": { + "schema": "state-hub.release-headroom-preflight.v1", + "ok": true, + "observed_at": "2026-10-17T00:33:19.561580+00:00", + "freshness_seconds": 0, + "remaining_cpu_m": 420, + "remaining_memory_bytes": 6535979008, + "pending_unrelated_cpu_m": 0, + "api_surge_cpu_m": 100, + "mcp_surge_cpu_m": 10, + "migrate_cpu_m": 50, + "atomic": true, + "reasons": [], + "notes": [ + "Aggregate remaining millicores is not a kube-scheduler guarantee.", + "Preflight does not lower requests and does not start Helm." + ], + "hook_order": [ + "pre-upgrade migrate job (helm.sh/hook-weight -5)", + "API RollingUpdate maxSurge=1 maxUnavailable=0", + "MCP RollingUpdate maxSurge=1 maxUnavailable=0" + ] + }, + "migration_job_and_pods_absent": true, + "runtime_readback_ref": "../receiver-rollout-r4/final-runtime-check.json", + "live_changes": false +} diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/native_binding_probes.py b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/native_binding_probes.py new file mode 100644 index 00000000..9efa04f9 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/native_binding_probes.py @@ -0,0 +1,82 @@ +"""Prepared owner in-pod probes. Requires separate approval; commits are intercepted.""" +import asyncio,hashlib,json,logging,uuid +from pathlib import Path +logging.disable(logging.CRITICAL) +EXPECTED = [ + { + "module": "api.services.historical_projection", + "sha256": "e24e57c58f4aaa4ec9d9bf088009ac69006e53f75b9477ee53dc26c4e850b975" + }, + { + "module": "api.routers.workstreams", + "sha256": "9c9bf4376dd323b52109912bb3f615949e89ac02c8b88b4720f569f8565ad1d6" + } +] +ORDINARY='e0a7813d-c916-5e49-84b2-5e5b30441891' +class UnexpectedCommit(Exception):pass +class NoCommit: + def __init__(self,session):self.session=session;self.attempts=0 + async def execute(self,*args,**kwargs):return await self.session.execute(*args,**kwargs) + async def commit(self):self.attempts+=1;raise UnexpectedCommit() +def digest(v):return 'sha256:'+hashlib.sha256(json.dumps(v,sort_keys=True,separators=(',',':'),ensure_ascii=False).encode()).hexdigest() +async def main(): + import importlib + from sqlalchemy import text + from fastapi import HTTPException + for entry in EXPECTED: + name,pin=entry["module"],entry["sha256"] + module=importlib.import_module(name);assert hashlib.sha256(Path(module.__file__).read_bytes()).hexdigest()==pin + from api.database import async_session_factory,engine + from api.services.historical_projection import FROZEN_CLUSTER_BINDINGS + from api.routers.workstreams import sync_workplan_bindings + from api.schemas.workplan import WorkplanBindingsSync + engine.echo=False + entries=FROZEN_CLUSTER_BINDINGS;assert len(entries)==3 + parents=[v['uuid'] for v in entries];task_ids=[t['uuid'] for v in entries for t in v['tasks']];assert len(task_ids)==15 + def body(ordinary=False): + values=[dict(workplan_id=v['uuid'],repo_slug=v['repo'],filename=Path(v['path']).name,relative_path=v['path'],archived=True,status='finished') for v in entries] + if ordinary:values.insert(0,dict(workplan_id=ORDINARY,repo_slug='railiance-cluster',filename='RCLUSTER-WP-0018-keycape-original-tls-recovery.md',relative_path='workplans/RCLUSTER-WP-0018-keycape-original-tls-recovery.md',archived=False,status='active')) + return values + async def snapshot(session): + result={} + for table,ids in [('workplans',parents+[ORDINARY]),('tasks',task_ids)]: + rows=(await session.execute(text('SELECT to_jsonb(row) FROM '+table+' AS row WHERE row.id=ANY(:ids) ORDER BY row.id'),{'ids':[uuid.UUID(v) for v in ids]})).scalars().all() + assert len(rows)==len(ids) and {v['id'] for v in rows}==set(ids) + result[table]=digest(rows) + return result + cases={};before=None + async with async_session_factory() as session: + try: + await session.execute(text("SET LOCAL statement_timeout='5s'"));await session.execute(text("SET LOCAL lock_timeout='1s'"));await session.execute(text("SET LOCAL idle_in_transaction_session_timeout='10s'")) + before=await snapshot(session) + for name in ['preserve-three','invalid-path-mixed','foreign-repo-mixed','missing-task-mixed','foreign-holder-mixed','nonterminal-parent-mixed','nonterminal-task-mixed','invalid-last-binding-mixed']: + nested=await session.begin_nested();proxy=NoCommit(session);outcome='unverified' + try: + values=body(ordinary=name!='preserve-three') + if name=='invalid-path-mixed':values[1]['relative_path']='workplans/incorrect.md' + if name=='foreign-repo-mixed':values[1]['repo_slug']='foreign' + if name=='missing-task-mixed':await session.execute(text('DELETE FROM tasks WHERE id=:id'),{'id':uuid.UUID(task_ids[0])}) + if name=='foreign-holder-mixed':await session.execute(text('UPDATE tasks SET workplan_id=:parent WHERE id=:id'),{'id':uuid.UUID(task_ids[0]),'parent':uuid.UUID(ORDINARY)}) + if name=='nonterminal-parent-mixed':await session.execute(text("UPDATE workplans SET status='active' WHERE id=:id"),{'id':uuid.UUID(parents[0])}) + if name=='nonterminal-task-mixed':await session.execute(text("UPDATE tasks SET status='progress' WHERE id=:id"),{'id':uuid.UUID(task_ids[0])}) + if name=='invalid-last-binding-mixed':values[-1]['archived']=False + try: + answer=await sync_workplan_bindings(WorkplanBindingsSync(bindings=values),proxy) + assert name=='preserve-three' and answer=={'updated':0,'received':3,'preserved':3} + outcome='preserved' + except HTTPException as exc: + assert name!='preserve-three' and exc.status_code==409;outcome='refused' + assert proxy.attempts==0 + finally: + await nested.rollback();session.expire_all() + restored=await snapshot(session);assert restored==before + cases[name]={'status':'passed','outcome':outcome,'commit_attempts':proxy.attempts,'rolled_back':True,'full_rows_restored':True} + finally:await session.rollback() + async with async_session_factory() as fresh: + try:after=await snapshot(fresh);assert before==after + finally:await fresh.rollback() + await engine.dispose() + print(json.dumps({'schema':'state95.native-guard-probes.v1','status':'passed','cases':cases,'before_full_row_hashes':before,'after_full_row_hashes':after,'historical_parents':3,'historical_tasks':15,'ordinary_parent_also_preserved':True,'fresh_transaction_full_rows_equal':True,'commits_performed':0,'commit_interception_enforced':True,'raw_rows_or_credentials_retained':False,'limits':['No claim of restoring pre-legacy-refresh parent preimages.','Missing actual parent/repo rows and full concurrent-writer behavior remain source-test coverage, not claimed as these native cases.']},sort_keys=True)) +if __name__=='__main__': + try:asyncio.run(main()) + except BaseException:print(json.dumps({'schema':'state95.native-guard-probes.v1','status':'refused-or-unverified','raw_rows_or_credentials_retained':False}));raise SystemExit(1) diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/pending-decision.json b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/pending-decision.json new file mode 100644 index 00000000..9b521139 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/pending-decision.json @@ -0,0 +1,18 @@ +{ + "workplan_id": "79579572-8120-5d3b-aca5-dd4aff60d058", + "id": "df703266-6719-4c04-ae1b-ba9afd3bca1c", + "topic_id": null, + "title": "Review exact frozen-binding API guard successor and retained baseline exception", + "description": "Pending proposal STATE-WP-0095-T10-frozen-bindings-guard-r1 revision2 SHA2565d0ddebc21bcb035809cc8b57f4c2e1ba8ed09204be5b965460e178a90913f1e. Image main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1; frozen chart b5c0663d830fc8d3b65080f237a0be8d01f8b2f7fec9dc33aeba91b1b6b281d4. Eight-identity image-only server dry-run and explicit target/headroom/schema pass. Verified installed checker maintenance preserves all18 CURRENT rows; prior three-parent hash failures remain historical exceptions, never restored or backfilled. Requested ADMINISTER @ realm:kubernetes/railiance01 activation=APPROVED includes exact new Job conditional recovery, rollback76 after verified quiescence, current-baseline successor acceptance, unpriced cost/risk and attended stop for unknown mapping. Full review: the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/FINAL-PROPOSAL.md. No new runtime is approved or executed. Original revision1 remains retained and unexecuted. Revision2 only represents source hashes as typed metadata after the normal scanner false positive; runtime, case logic, acceptance and recovery scope are unchanged.", + "decision_type": "pending", + "status": "open", + "rationale": "Agent-prepared exact new-code proposal. Only the founder may resolve approval; prior receiver revision4 approval does not cover this new image.", + "decided_by": null, + "decided_at": null, + "deadline": null, + "escalation_note": null, + "superseded_by": null, + "created_at": "2026-10-17T00:37:26.205185Z", + "updated_at": "2026-10-17T00:48:51.550636Z", + "workstream_id": "79579572-8120-5d3b-aca5-dd4aff60d058" +} diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/probe-helper-preparation-check.json b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/probe-helper-preparation-check.json new file mode 100644 index 00000000..5c9d5f7f --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/probe-helper-preparation-check.json @@ -0,0 +1,11 @@ +{ + "status": "prepared-helper-contract-checked", + "actual_workplan_binding_schema_accepts_exact_three": true, + "commit_interception_checked_with_actual_unbound_AsyncSession": true, + "DB_connected": false, + "native_probes_executed": false, + "revision": 2, + "expected_module_hashes_equal_revision1": true, + "probe_logic_after_hash_loading_byte_identical": true, + "syntax_checked": true +} diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/proposal-metadata.json b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/proposal-metadata.json new file mode 100644 index 00000000..cef1277f --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/proposal-metadata.json @@ -0,0 +1,108 @@ +{ + "schema": "custodian.supervised-runtime-proposal.v1", + "proposal_id": "STATE-WP-0095-T10-frozen-bindings-guard-r1", + "revision": 2, + "status": "pending_supervisor_approval_not_executed", + "prepared_at": "2026-10-17T00:48:23.064918+00:00", + "owner_task_id": "STATE-WP-0095-T10", + "owner_task_uuid": "88585c69-d9b9-5e1f-a256-6416ff71e824", + "target": "railiance01/92.205.62.239 state-hub release API/MCP and exact-new-trial migration Job", + "authority_requested": "ADMINISTER @ realm:kubernetes/railiance01 activation=APPROVED", + "source_commit": "c103cbf3c8bed5dba87a03e320595ddded9301a3", + "image": "forgejo.coulomb.social/coulomb/state-hub:main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1", + "image_digest": "sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1", + "chart_archive": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/revisions/r4-rollout-review/state-hub-0.1.0.tgz", + "chart_sha256": "b5c0663d830fc8d3b65080f237a0be8d01f8b2f7fec9dc33aeba91b1b6b281d4", + "remote_owned_chart": "/tmp/statehub-binding-guard-b5c0663d830f/state-hub-0.1.0.tgz", + "expected_prior_helm_revision": 76, + "expected_schema": "c4d5e6f7a8b9", + "migration_files_changed_since_deployed_source": false, + "exact_upgrade_argv": [ + "ssh", + "railiance01", + "KUBECONFIG=/etc/rancher/k3s/k3s.yaml helm -n state-hub upgrade state-hub /tmp/statehub-binding-guard-b5c0663d830f/state-hub-0.1.0.tgz --reuse-values --set-string image.tag=main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1 --atomic --timeout 3m" + ], + "staging": "Create exact new private owned directory, refuse unexpected existing ownership/content, copy only frozen chart, verify remote SHA before execution and remove only owned exact archive/directory after proof.", + "readiness_refresh_required_before_execution": true, + "native_probe_cases": 8, + "verification": [ + "Delivered manifest and guard module hashes, API/MCP Ready, primary/railiance01 and expected schema.", + "Fresh CURRENT full historical-row hashes before and after; enforce rollback and commit interception for eight exact native cases, then fresh-transaction comparison including ordinary parent.", + "Verified installed local CLI guard, ordinary consistency/fast projection between current snapshots, archive bytes and unrelated staged work preserved; source/remote/Hub IDs/statuses read back." + ], + "acceptance_disposition_requested": "Use current hashed baseline for successor acceptance; retain the earlier three-parent mismatch as a historical exception, without guessed restoration or backfilling its failed result. T07/RMGR19T06 close only after new receiving and corrected pipeline pass with exception recorded.", + "conditional_recovery_included": true, + "rollback_helm_revision": 76, + "recovery_limits": "Exact observed new Job UID/image/ownership and bound Pods only; foreground UID-precondition cancellation after revalidation. Verify private owner DB connection/transaction/lock quiescence; unknown mapping/mixed state stops for attended recovery. No schema downgrade or broad backend termination.", + "cost_estimate_eur": null, + "risk_estimate_eur": null, + "cost_and_risk_disposition": "Unpriced attributable cost and credible loss/outage exposure on existing infrastructure require supervised founder acceptance. Helm timeout is not Job/outage bound.", + "autopilot_grant": false, + "bound_receipts": [ + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/FINAL-PROPOSAL.md", + "sha256": "5ff105f0eb66e85f6d5c64a496e5f1ce7b816a32fb93351f4cd07133b9a053fd" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/RECOVERY.md", + "sha256": "dc72dea7cdf519a712ee8f6d28fc6aa5667b44655d948f2c0160869383f33cef" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/native_binding_probes.py", + "sha256": "ce492d0713c987e47eefc991a6312c1c63ed72056a597e9e28aee959d06e71d5" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/probe-helper-preparation-check.json", + "sha256": "36feaa02e17850c56685ccaf78556d12447fdeddae9c483721cd996da848f400" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/watch_rollout.py", + "sha256": "a496da3c9a415ac800921ce755446be4bb55bc17e4e6b913143a5d6baf48f815" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/reuse_values_dryrun.py", + "sha256": "5e653ae8439c773b27a167464b620805055e9d820a907761ab30652e65fae200" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/exact-server-dryrun.json", + "sha256": "2bfb02130876aeb71a773585dae8b446ad5c671050ebc14d0fc7bcce86dffea5" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/fresh-readonly-preflight.json", + "sha256": "01bbdc94ebd6fb266ab21bdc0851eac6eddeafbabcd7105cce23806931ebfbce" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/guard-image-c103cbf-inspection.json", + "sha256": "21b4ddd3fa65743bed5eae568e0c08f2d150fcecacb7949263cef06bab99a683" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/before-verified-client.json", + "sha256": "ee41be2d74085603abf2bfe2c91c1bb430b4ea0539a6cbf988528b18f3f5ef46" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/after-verified-client.json", + "sha256": "8cd8921271cd647fe40dde0f00bc55b714313426e14091397d8fd11314c5e610" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/verified-loaded-client.json", + "sha256": "0745b5df92d2533714e2a87a43c73b3b685e7bab7bcf4444f7a5fbd82dd4722f" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/verified-client-cluster-projection.json", + "sha256": "c25b43575273df0e648842d447979aca1b41ade16fcdaf9f5f870024e7ca06e9" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/verified-client-cluster-consistency.json", + "sha256": "fd8a7d58d9e08b4866420ad77d01fbc7c28c0c2c9610ffbe95ddc77dddd9b1a6" + }, + { + "path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/ordinary-pipeline-preservation-finding.json", + "sha256": "43d4adc0ffbf61345865eebb3f8948f82a3c33a28fd0436eccd08bc93ba973c1" + } + ], + "supersedes": { + "revision": 1, + "sha256": "81d365926fc585a186955d8c98ee83501fd3cec12135b2256d71b46a75097932", + "reason": "Explicit module/SHA256 metadata representation in probe helper; no runtime/acceptance/recovery change." + } +} diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/reuse_values_dryrun.py b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/reuse_values_dryrun.py new file mode 100644 index 00000000..d1ea3cba --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/reuse_values_dryrun.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +"""Read-only exact owner Helm reuse-values render/diff; no raw output retained.""" +import copy +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess +import yaml +OWNER=Path('/home/worsch/state-hub') +KUBE='/home/worsch/.kube/config-railiance01' +CHART='/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/revisions/r4-rollout-review/state-hub-0.1.0.tgz' +CHART_SHA256='b5c0663d830fc8d3b65080f237a0be8d01f8b2f7fec9dc33aeba91b1b6b281d4' +TAG='main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1' +HELM=['helm','--kubeconfig',KUBE,'-n','state-hub'] +DRYRUN=HELM+['upgrade','state-hub',CHART,'--reuse-values','--set-string','image.tag='+TAG, + '--dry-run=server','--hide-secret','--no-hooks','--timeout','3m'] +PUBLIC_COORDINATES={'OPENBAO_K8S_TOKEN_PATH':'/var/run/secrets/openbao/token', + 'FORGE_READ_SECRET_PATH':'platform/data/workloads/state-hub/forge-derivation', + 'FORGE_READ_SECRET_KEY':'FORGE_READ_TOKEN'} +ALLOWED={'ConfigMap','Deployment','Job','Service','ServiceAccount','Namespace','Ingress'} +LIMIT=2*1024*1024 +class Refusal(RuntimeError):pass + +def digest(obj):return 'sha256:'+hashlib.sha256(json.dumps(obj,sort_keys=True,separators=(',',':')).encode()).hexdigest() + +def parse(raw): + if len(raw)>LIMIT:raise Refusal('render_size_refused') + result=[] + for obj in yaml.safe_load_all(raw): + if not obj:continue + if not isinstance(obj,dict) or obj.get('kind') not in ALLOWED:raise Refusal('unexpected_or_secret_resource_refused') + result.append(obj) + return result + +def sections(raw): + text=raw.decode() + if '\nMANIFEST:\n' not in text:raise Refusal('helm_render_sections_unrecognized') + before,manifest=text.split('\nMANIFEST:\n',1) + manifest=manifest.split('\nNOTES:',1)[0] + hooks=before.split('\nHOOKS:\n',1)[1] if '\nHOOKS:\n' in before else '' + return parse(hooks)+parse(manifest) + +def identity(obj): + meta=obj.get('metadata') or {} + return (obj['apiVersion'],obj['kind'],meta.get('namespace') or ('' if obj['kind']=='Namespace' else 'state-hub'),meta['name']) + +def diff_paths(a,b,prefix=''): + if type(a)!=type(b):return [prefix] + if isinstance(a,dict): + paths=[] + for key in sorted(set(a)|set(b)): + path=prefix+'/'+key.replace('~','~0').replace('/','~1') + paths.extend([path] if key not in a or key not in b else diff_paths(a[key],b[key],path)) + return paths + if isinstance(a,list): + if len(a)!=len(b):return [prefix] + return [p for i,(x,y) in enumerate(zip(a,b)) for p in diff_paths(x,y,prefix+'/'+str(i))] + return [] if a==b else [prefix] + +def compare(old,new): + baseline={identity(o):o for o in old};intended={identity(o):o for o in new} + if len(baseline)!=len(old) or len(intended)!=len(new):raise Refusal('duplicate_object_identity') + rows=[] + for key in sorted(set(baseline)|set(intended)): + a=baseline.get(key);b=intended.get(key);paths=diff_paths(a,b) + images=[] + if b and b['kind'] in {'Deployment','Job'}: + spec=b.get('spec',{}).get('template',{}).get('spec',{}) + for container in (spec.get('containers',[])+spec.get('initContainers',[])): + image=container.get('image','') + if not re.fullmatch(r'forgejo\.coulomb\.social/coulomb/state-hub:main-[0-9a-f]{7}(?:@sha256:[0-9a-f]{64})?',image):raise Refusal('unexpected_image_refused') + images.append(image) + for env in container.get('env',[]): + if 'value' in env and re.search(r'PASSWORD|TOKEN|SECRET|DATABASE_URL',env.get('name',''),re.I): + if env.get('name') not in PUBLIC_COORDINATES or env['value']!=PUBLIC_COORDINATES[env['name']]:raise Refusal('literal_sensitive_env_refused') + rows.append({'api_version':key[0],'kind':key[1],'namespace':key[2],'name':key[3], + 'intended_images':images,'intended_spec_sha256':digest(b.get('spec',{})) if b else None, + 'baseline_object_sha256':digest(a) if a else None,'intended_object_sha256':digest(b) if b else None, + 'changed_paths':paths}) + image_path=re.compile(r'^/spec/template/spec/(?:initContainers|containers)/[0-9]+/image$') + only_images=all(image_path.fullmatch(p) for row in rows for p in row['changed_paths']) + return {'objects':rows,'changed_object_count':sum(bool(r['changed_paths']) for r in rows), + 'intended_diff_only_images':only_images,'object_identity_set_unchanged':set(baseline)==set(intended)} + +def command(args): + env=dict(os.environ);env['KUBECONFIG']=KUBE + p=subprocess.run(args,cwd=OWNER,env=env,capture_output=True,timeout=45) + if len(p.stdout)>LIMIT:raise Refusal('command_output_size_refused') + if p.returncode: + lower=p.stderr.decode(errors='replace').lower() + label=next((label for marker,label in [('reject-secret-last-applied','secret_annotation_policy_refused'),('forbidden','admission_or_auth_refused'),('cannot patch','patch_validation_refused'),('is invalid','resource_validation_refused'),('timeout','bounded_timeout'),('connection refused','api_unavailable')] if marker in lower),'unknown_refusal') + raise Refusal(label) + return p.stdout + +def main(): + result={'schema':'statehub.exact-reuse-values-readonly-diff.v1','status':'failed', + 'dryrun_argv':DRYRUN,'production_mutations':False,'hooks_executed':False, + 'raw_helm_values_release_content_logs_retained':False, + 'limitations':['Dry-run admission/render only, not an actual image pull/migration/deployment.', + 'Helm timeout does not stop a later live migration Job; exact scoped cancellation/quiescence remains required.']} + try: + archive=Path(CHART) + if archive.is_symlink() or not archive.is_file() or hashlib.sha256(archive.read_bytes()).hexdigest()!=CHART_SHA256:raise Refusal('reviewed_chart_archive_drift') + result['chart_archive_sha256']='sha256:'+CHART_SHA256 + nodes=json.loads(command(['kubectl','--kubeconfig',KUBE,'get','nodes','-o','json'])) + if not any(a.get('address')=='92.205.62.239' for n in nodes.get('items',[]) for a in n.get('status',{}).get('addresses',[])):raise Refusal('target_identity_refused') + result['stage']='baseline_release_manifest' + old=parse(command(HELM+['get','manifest','state-hub'])) + result['stage']='baseline_release_hooks' + old+=parse(command(HELM+['get','hooks','state-hub'])) + result['stage']='exact_upgrade_server_dryrun' + new=sections(command(DRYRUN)) + result.update(compare(old,new));result['status']='passed_images_only' if result['intended_diff_only_images'] and result['object_identity_set_unchanged'] else 'review_nonimage_delta_required' + except (Refusal,subprocess.TimeoutExpired,ValueError,yaml.YAMLError) as error: + result['error_class']=type(error).__name__ + result['error_label']=str(error) if isinstance(error,Refusal) else 'bounded_parse_or_command_failure' + print(json.dumps(result,indent=2)) + return int(result['status']!='passed_images_only') +if __name__=='__main__':raise SystemExit(main()) diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/revision1-preimage-reconstruction.json b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/revision1-preimage-reconstruction.json new file mode 100644 index 00000000..bb800b31 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/revision1-preimage-reconstruction.json @@ -0,0 +1,6 @@ +{ + "status": "original_draft_bytes_reconstructed_in_memory", + "original_helper_sha256": "ef5c07affb15d20b4194edda68fd14b4d0fee1e9dab857e3242c3a1a1e49046f", + "native_probes_executed": false, + "source_or_private_values_printed": false +} diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/verify_revision1_helper_preimage.py b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/verify_revision1_helper_preimage.py new file mode 100644 index 00000000..7870a50c --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/verify_revision1_helper_preimage.py @@ -0,0 +1,28 @@ +"""Reconstruct the unexecuted draft's bytes in memory from published revision 2. + +This audits the retained original proposal digest without executing a probe or +printing source. Only the source-hash metadata representation was refined. +""" +from pathlib import Path +import ast +import hashlib +import json + +base = Path(__file__).resolve().parent +source = (base / 'native_binding_probes.py').read_text() +assignment = next(n for n in ast.parse(source).body if isinstance(n, ast.Assign) + and any(isinstance(t, ast.Name) and t.id == 'EXPECTED' for t in n.targets)) +records = ast.literal_eval(assignment.value) +lines = source.splitlines() +lines[assignment.lineno - 1:assignment.end_lineno] = [ + 'EXPECTED={' + ','.join(repr(r['module']) + ':' + repr(r['sha256']) for r in records) + '}'] +original = ('\n'.join(lines) + '\n').replace( + ' for entry in EXPECTED:\n name,pin=entry["module"],entry["sha256"]', + ' for name,pin in EXPECTED.items():') +digest = hashlib.sha256(original.encode()).hexdigest() +assert digest == 'ef5c07affb15d20b4194edda68fd14b4d0fee1e9dab857e3242c3a1a1e49046f' +result = {'status': 'original_draft_bytes_reconstructed_in_memory', + 'original_helper_sha256': digest, 'native_probes_executed': False, + 'source_or_private_values_printed': False} +(base / 'revision1-preimage-reconstruction.json').write_text(json.dumps(result, indent=2) + '\n') +print(json.dumps(result)) diff --git a/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/watch_rollout.py b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/watch_rollout.py new file mode 100644 index 00000000..c680ddc5 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/guard-successor-review-r2/watch_rollout.py @@ -0,0 +1,57 @@ +"""Observe the approved release Job and owned Pods; never mutate resources.""" +from pathlib import Path +from datetime import datetime, timezone +import json +import subprocess +import time + +BASE = Path(__file__).resolve().parent +KUBE = ['kubectl', '--kubeconfig', '/home/worsch/.kube/config-railiance01', '-n', 'state-hub'] +IMAGE = 'forgejo.coulomb.social/coulomb/state-hub:main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1' +def get(args): + result = subprocess.run(KUBE + args, capture_output=True, timeout=10) + if result.returncode: + return None + return json.loads(result.stdout) if result.stdout.strip() else {} + +result = {'schema': 'state95.approved-job-observation.v1', 'mutation': False, + 'started_at': datetime.now(timezone.utc).isoformat(), 'jobs': {}, 'poll_failures': 0} +deadline = time.monotonic() + 240 +while time.monotonic() < deadline: + try: + job = get(['get', 'job', 'state-hub-migrate', '--ignore-not-found', '-o', 'json']) + if job is None: + result['poll_failures'] += 1 + elif job: + meta = job['metadata'] + images = [c['image'] for c in job['spec']['template']['spec']['containers']] + if (images != [IMAGE] or meta.get('labels', {}).get('app.kubernetes.io/instance') != 'state-hub' + or meta.get('labels', {}).get('app.kubernetes.io/managed-by') != 'Helm' + or meta.get('annotations', {}).get('helm.sh/hook') != 'pre-install,pre-upgrade'): + result['unexpected_job_identity'] = True + break + entry = result['jobs'].setdefault(meta['uid'], {'uid': meta['uid'], 'name': meta['name'], + 'created_at': meta['creationTimestamp'], 'image': IMAGE, 'managed_by': 'Helm', + 'release': 'state-hub', 'hook': 'pre-install,pre-upgrade', 'pods': {}}) + entry['last_status'] = {k: job.get('status', {}).get(k) for k in ['active', 'succeeded', 'failed']} + pods = get(['get', 'pods', '-l', 'job-name=state-hub-migrate', '-o', 'json']) + if pods: + for pod in pods['items']: + owners = pod['metadata'].get('ownerReferences', []) + if not any(o['uid'] == meta['uid'] and o['kind'] == 'Job' for o in owners): + continue + entry['pods'][pod['metadata']['uid']] = {'name': pod['metadata']['name'], + 'uid': pod['metadata']['uid'], 'owner_job_uid': meta['uid'], + 'pod_ip': pod.get('status', {}).get('podIP'), 'phase': pod.get('status', {}).get('phase')} + elif result['jobs']: + result['job_absent_after_observation'] = True + (BASE / 'job-observation.json').write_text(json.dumps(result, indent=2) + '\n') + if (BASE / 'helm-return.json').exists(): + result['helm_return_observed'] = True + break + except (subprocess.TimeoutExpired, ValueError, KeyError): + result['poll_failures'] += 1 + time.sleep(0.7) +result['ended_at'] = datetime.now(timezone.utc).isoformat() +(BASE / 'job-observation.json').write_text(json.dumps(result, indent=2) + '\n') +print(json.dumps({'observed_trial_jobs': len(result['jobs']), 'mutation': False})) diff --git a/docs/changes/netkingdom-target-2026-10-17/inspect_guard_image.py b/docs/changes/netkingdom-target-2026-10-17/inspect_guard_image.py new file mode 100644 index 00000000..f7584cff --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/inspect_guard_image.py @@ -0,0 +1,59 @@ +"""Resolve and inspect public guard image bytes; no pull, install or execution.""" +from pathlib import Path,PurePosixPath +import argparse,hashlib,io,json,re,tarfile,time,urllib.request,urllib.parse +p=argparse.ArgumentParser();p.add_argument('--commit',required=True);a=p.parse_args();assert re.fullmatch('[0-9a-f]{40}',a.commit) +here=Path(__file__).resolve().parent;owner=Path('/home/worsch/state-hub');short=a.commit[:7] +host='https://forgejo.coulomb.social';base='/v2/coulomb/state-hub/';deadline=time.monotonic()+180 +class NoRedirect(urllib.request.HTTPRedirectHandler): + def redirect_request(self,*args,**kwargs):raise RuntimeError('redirect-refused') +opener=urllib.request.build_opener(urllib.request.ProxyHandler({}),NoRedirect) +def get(path,limit,token=None,accept=None): + assert time.monotonic()