Preserve revision-4 access failure and add safe founder-context diagnostics

This commit is contained in:
codex 2026-11-02 10:23:28 +01:00
parent 1354a11f55
commit fa81fcf88d
8 changed files with 176 additions and 16 deletions

View file

@ -35,3 +35,5 @@ The founder executed approved revision3: the missing-fragment repair and171-repo
[Revision4](native-r4/PLAN.md), packet SHA256 `64c73100f2347470fda1b8b3194dbf6a8009291d9d80223af463b3c5ecab52fd`, corrects numeric mode handling and exact known group-write restoration, reuses the existing verified immutable ConfigMap, and has a fresh execution/source gate. Eight shell image cases, a four-case171-repository/1539-hook controller run, three controller checks and runtime self-test passed. Candidate scanner/profile and six owner source hashes are unchanged. Await exact revision4 review; no native revision4 action has run. The proposed founder launcher is `/tmp/forge-scanner-rollout-r4.sh`.
Founder approval recorded for exact revision4 packet `64c73100f2347470fda1b8b3194dbf6a8009291d9d80223af463b3c5ecab52fd` via User: Approve exact revision4; request_user_input_async call_f68b7e7de81a4c089e8864b3d66773a9 item0,2026-11-02. Attended execution pending at `/tmp/forge-scanner-rollout-r4.sh`; approval receipt `docs/changes/2026-11-01-scanner-json-digest/native-r4-approval.json`. Earlier failures and exact approvals remain preserved; no execution success is inferred from approval.
Current revision4 execution attempt failed at first get:nodes, before mutation. Packet/approval are unchanged and its failed receipt is preserved in `attempt-r4-access/`. The local bridge/API transport responds, public server certificate is not expired, and a tested read-only access classifier is ready at `bash /tmp/forge-scanner-access-diagnose.sh`. Await selected founder-context status before any unchanged retry; no renewed approval is requested.

View file

@ -0,0 +1,79 @@
"""Founder-run selected access diagnostics, never config/credentials/raw errors.
No mutation or receipt reset. Reads selected node and self-identity metadata with
the catalogued attended context. The rollout packet and failed receipt stay intact.
"""
from pathlib import Path
import importlib.util
import json
import os
import subprocess
HERE=Path(__file__).resolve().parent
R4=HERE.parent/'native-r4'
EXPECTED='64c73100f2347470fda1b8b3194dbf6a8009291d9d80223af463b3c5ecab52fd'
ADMIN=Path('/home/worsch/.kube/config-railiance01')
OUTPUT=Path('/tmp/forge-scanner-access-diagnostic-receipt.json')
def category(returncode,stderr):
if returncode==0:return 'passed'
lower=stderr.lower()
groups=[('authentication-required',[b'the server has asked for the client to provide credentials',b'must be logged in to the server',b'unauthorized',b'invalid bearer token']),
('authorization-denied',[b'forbidden']),
('certificate-validity-failure',[b'certificate has expired',b'certificate is not yet valid',b'not yet valid']),
('certificate-verification-failure',[b'x509:',b'failed to verify certificate',b'certificate signed by unknown authority']),
('client-certificate-required',[b'tls: certificate required',b'client certificate required']),
('connection-refused',[b'connection refused']),
('transport-timeout',[b'i/o timeout',b'context deadline exceeded',b'tls handshake timeout',b'timed out']),
('name-resolution',[b'no such host']),
('context-selection-failure',[b'current-context is not set',b'context was not found',b'no context exists',b'does not exist']),
('configuration-unavailable',[b'error loading config file',b'no configuration has been provided',b'no such file or directory']),
('configuration-parse-failure',[b'yaml:',b'invalid configuration']),
('credential-plugin-failure',[b'getting credentials: exec',b'exec plugin',b'no auth provider found']),
('api-unavailable',[b'serviceunavailable',b'bad gateway',b'currently unable to handle the request',b'connection reset by peer',b'eof']),
('protocol-mismatch',[b'http response to https client'])]
for name,needles in groups:
if any(n in lower for n in needles):return name
return 'unclassified-command-failure'
def selected_nodes(raw,cfg):
data=json.loads(raw);items=data.get('items',[])
matched=len(items)==1 and items[0].get('metadata',{}).get('uid')==cfg['node_uid'] and any(x.get('address')=='92.205.62.239' for x in items[0].get('status',{}).get('addresses',[]))
return {'node_count':len(items),'target_node_matches':matched}
def selected_identity(raw):
data=json.loads(raw);user=data.get('status',{}).get('userInfo',{})
return {'self_identity_read':True,'identity_is_builder':user.get('username')=='ops-mason-builder',
'identity_in_system_masters': 'system:masters' in user.get('groups',[])}
def main():
s=importlib.util.spec_from_file_location('reviewed_r4',R4/'rollout.py');m=importlib.util.module_from_spec(s);s.loader.exec_module(m)
if m.verify_packet()!=EXPECTED:raise RuntimeError('reviewed-packet-drift')
cfg=json.loads((R4.parent/'native-r2/inputs.json').read_bytes())
env=dict(os.environ,KUBECONFIG=str(ADMIN))
receipt={'read_only':True,'native_mutation':False,'rollout_packet_sha256':EXPECTED,
'catalog_route':'kubernetes-railiance01-admin','admin_file_present':ADMIN.is_file(),
'admin_file_readable':os.access(ADMIN,os.R_OK),'checks':[]}
for label,args,select in [('get-nodes',['get','nodes','-o','json'],lambda raw:selected_nodes(raw,cfg)),
('self-identity',['auth','whoami','-o','json'],selected_identity)]:
try:
q=subprocess.run(['kubectl','--request-timeout=8s',*args],capture_output=True,timeout=15,env=env)
item={'operation':label,'returncode':q.returncode,'category':category(q.returncode,q.stderr)}
if q.returncode==0:
try:item.update(select(q.stdout))
except (ValueError,TypeError,KeyError,AttributeError):item['category']='unexpected-response; raw withheld'
except subprocess.TimeoutExpired:item={'operation':label,'category':'client-command-timeout'}
receipt['checks'].append(item)
OUTPUT.write_text(json.dumps(receipt,indent=2)+'\n')
print(json.dumps(receipt,indent=2));print('receipt:',str(OUTPUT))
if __name__=='__main__':
try:main()
except Exception as error:
print(json.dumps({'result':'read-only-diagnostic-refused','reason':str(error) if isinstance(error,RuntimeError) else type(error).__name__}))
raise SystemExit(1)

View file

@ -0,0 +1,15 @@
{
"proposal_id": "SYNC-SCANNER-JSON-DIGEST-20261101",
"revision": 4,
"events": [
{
"event": "command-failed",
"operation": "get:nodes",
"category": "command-failed"
}
],
"native_mutation": false,
"packet_sha256": "64c73100f2347470fda1b8b3194dbf6a8009291d9d80223af463b3c5ecab52fd",
"reused_r2_packet_sha256": "d22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237",
"mode": "apply"
}

View file

@ -0,0 +1,22 @@
{
"proposal_id": "SYNC-SCANNER-JSON-DIGEST-20261101",
"revision": 4,
"packet_sha256": "64c73100f2347470fda1b8b3194dbf6a8009291d9d80223af463b3c5ecab52fd",
"outcome": "failed at first Kubernetes read",
"native_mutation": false,
"reported_reason": "kubernetes-command-failed:get:nodes",
"failed_receipt_sha256": "8c876dd2ac8d21a1c4b2a1823802f8a318328d8e16ebb7191160d87525bd50f6",
"authorization": "exact revision4 approval remains valid; no code or scope change",
"transport_observation": {
"bridge": "connected",
"anonymous_api_http_status": 401,
"identity_verified": false,
"server_certificate_expired": false,
"server_certificate_not_after": "2027-03-10T00:47:00Z",
"credentials_used": false
},
"next_step": "founder-run read-only access diagnostic; determine credentials/configuration/transport error without raw output",
"diagnostic_sha256": "4df7a7a8d88abada354dcd59f6cd452e20d9bfe67aac0813991ea0e2545599c7",
"diagnostic_tests": 3,
"approved_packet_changed": false
}

View file

@ -0,0 +1,26 @@
import importlib.util
from pathlib import Path
import json
import unittest
p=Path(__file__).resolve().parent/'diagnose_access.py';s=importlib.util.spec_from_file_location('access_probe',p);d=importlib.util.module_from_spec(s);s.loader.exec_module(d)
class Tests(unittest.TestCase):
def test_authentication_challenge_without_unauthorized_word(self):
raw=b'could not get API group list: the server has asked for the client to provide credentials PRIVATE_SENTINEL'
self.assertEqual(d.category(1,raw),'authentication-required')
def test_categories_do_not_echo_raw_errors(self):
for raw,expected in [(b'x509: certificate has expired PRIVATE_SENTINEL','certificate-validity-failure'),
(b'Forbidden PRIVATE_SENTINEL','authorization-denied'),
(b'connect: connection refused PRIVATE_SENTINEL','connection-refused'),
(b'PRIVATE_SENTINEL','unclassified-command-failure')]:
self.assertEqual(d.category(1,raw),expected)
def test_selected_success_fields_do_not_emit_credentials_or_full_objects(self):
raw=json.dumps({'items':[{'metadata':{'uid':'expected','annotations':{'unrelated':'PRIVATE_SENTINEL'}},'status':{'addresses':[{'address':'92.205.62.239'}]}}]}).encode()
self.assertEqual(d.selected_nodes(raw,{'node_uid':'expected'}),{'node_count':1,'target_node_matches':True})
raw=json.dumps({'status':{'userInfo':{'username':'ops-mason-builder','groups':['PRIVATE_SENTINEL']},'unrelated':'PRIVATE_SENTINEL'}}).encode()
result=d.selected_identity(raw);self.assertTrue(result['identity_is_builder']);self.assertNotIn('PRIVATE_SENTINEL',json.dumps(result))
if __name__=='__main__':unittest.main()