diff --git a/.kaizen/agents/custodian-codex/supervision.json b/.kaizen/agents/custodian-codex/supervision.json index 3af230dc..ef3df468 100644 --- a/.kaizen/agents/custodian-codex/supervision.json +++ b/.kaizen/agents/custodian-codex/supervision.json @@ -1247,8 +1247,8 @@ "current_revision": 2, "current_digest_sha256": "d22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237", "current_proposal_ref": "docs/changes/2026-11-01-scanner-json-digest/native-r2/PLAN.md", - "disposition": "pending", - "outcome": "not_executed", + "disposition": "unscored", + "outcome": "failed", "target": "railiance01 forgejo/forge; paired repo-manager/railiance-forge/Apps source", "expected_result": "Equivalent JSON filename/exact lowercase SHA256 accepted; unchanged full scanner coverage and actual-key refusal", "verification_ref": "docs/changes/2026-11-01-scanner-json-digest/native-r2/test-validation.json", @@ -1266,8 +1266,8 @@ "supervisor": "Bernd Worsch", "approved_revision": null, "approved_digest_sha256": null, - "executed_revision": null, - "executed_digest_sha256": null, + "executed_revision": 2, + "executed_digest_sha256": "d22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237", "refinement_or_rescue": true, "promotion_evidence": false, "revisions": [ @@ -1281,6 +1281,18 @@ "digest_sha256": "d22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237", "basis": "founder preflight enables exact native preparation and six source followthrough files; offline fixture refinement retained" } + ], + "execution_ref": "docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/outcome.json", + "verified_outcome": "Founder executed exact reviewed revision2; preflight command failed before staging/native mutation. Exact failed read not yet known; value-free read-only replay prepared.", + "recovery": "No native mutation; original failure receipt and packet preserved; awaiting founder read-only diagnostic result.", + "supervisor_execution_evidence": "User returned bash /tmp/forge-scanner-rollout.sh failure output in active conversation, 2026-11-02", + "attempts": [ + { + "revision": 2, + "packet_sha256": "d22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237", + "outcome_ref": "docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/outcome.json", + "outcome": "failed; before native mutation" + } ] } ], diff --git a/docs/changes/2026-11-01-scanner-json-digest/README.md b/docs/changes/2026-11-01-scanner-json-digest/README.md index 4093d1d1..fc46f041 100644 --- a/docs/changes/2026-11-01-scanner-json-digest/README.md +++ b/docs/changes/2026-11-01-scanner-json-digest/README.md @@ -15,3 +15,7 @@ The exact native preparation is now [revision 2](native-r2/PLAN.md), under the s Native activation awaits exact founder approval under `docs/agent-autonomy-decision.md` ("The supervisor approves that proposal or performs it through the privileged execution path."). The agent must not borrow the founder admin kubeconfig. The prepared founder launcher is `/tmp/forge-scanner-rollout.sh`. Its sealed runner refuses unknown initial identity/guard/hook/source state and any packet drift. The staged scanner in repo-manager and live receiver have not been altered by this candidate. Independent clean-file commits continue under the original policy. + +## Current execution outcome, 2026-11-02 + +The founder ran the exact revision-2 launcher. It failed during preflight with a suppressed Kubernetes command error, before staging or native mutation (`native_mutation: false`, no events). The exact failed read is not yet identified. Failure evidence is preserved in `attempt-r2-preflight/`; the reviewed packet is unchanged. A separate read-only diagnostic labels each check and outputs fixed failure categories; three tests passed, including raw-diagnostic suppression and receipt preservation. Founder invocation: `bash /tmp/forge-scanner-diagnose.sh`. Do not retry the old apply while this failure is unresolved; its existing receipt prevents an unreviewed retry. diff --git a/docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/diagnose.py b/docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/diagnose.py new file mode 100644 index 00000000..805b155f --- /dev/null +++ b/docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/diagnose.py @@ -0,0 +1,119 @@ +"""Founder-run read-only replay of sealed r2 preflight, with value-free failures. + +Never invokes apply, staging, patch, diff, server dry-run or rollback. Never +prints command arguments, shell diagnostics, Kubernetes objects or credentials. +The original failed execution receipt is not modified. +""" +from pathlib import Path +import importlib.util +import json +import re +import subprocess + +HERE = Path(__file__).resolve().parent +ORIGINAL = HERE.parent / 'native-r2' +EXPECTED_PACKET = 'd22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237' +OUTPUT = Path('/tmp/forge-scanner-diagnostic-receipt.json') + + +def failure_category(returncode, stderr): + # Fixed labels only. Raw stderr stays in memory and is discarded. + if returncode == 0: + return 'passed' + for needle, label in [(b'Forbidden', 'forbidden'), (b'Unauthorized', 'unauthorized'), + (b'certificate has expired', 'certificate-expired'), + (b'certificate signed by unknown authority', 'certificate-authority'), + (b'connection refused', 'connection-refused'), + (b'no such host', 'name-resolution'), + (b'context deadline exceeded', 'deadline-exceeded'), + (b'NotFound', 'not-found')]: + if needle in stderr: + return label + if re.search(rb'command terminated with exit code [0-9]+', stderr): + return 'remote-check-nonzero' + return 'unclassified-command-failure' + + +def step_label(args, cfg, inventory): + if 'exec' in args: + # Exact known shell only, including the literal check argument. + ensure(args[-1:] in [('check',), ('sh',)], 'unexpected-exec-tail') + if cfg['guard_command'][2] in args and args[-1] == 'check': + return 'installed-artifact-guard' + if inventory in args: + return 'existing-repository-hook-inventory' + raise RuntimeError('unexpected-exec-script') + if args[:2] == ('get', 'nodes'): + return 'node-identity-read' + for resource, label in [('deployment', 'deployment-read'), ('replicasets', 'replicaset-read'), + ('pods', 'pod-list-read'), ('pod', 'initial-pod-uid-read'), + ('configmap', 'old-public-hook-read')]: + if args[:4] == ('-n', 'forgejo', 'get', resource): + return label + raise RuntimeError('unexpected-read-command') + + +def ensure(condition, reason): + if not condition: + raise RuntimeError(reason) + + +def main(): + spec = importlib.util.spec_from_file_location('sealed_r2_rollout', ORIGINAL / 'rollout.py') + module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module) + ensure(module.verify_packet() == EXPECTED_PACKET, 'reviewed-packet-drift') + cfg = json.loads((ORIGINAL / 'inputs.json').read_bytes()) + receipt = {'read_only': True, 'replayed_packet_sha256': EXPECTED_PACKET, 'steps': [], + 'native_mutation': False, 'original_failed_receipt_preserved': True} + + def save(): + OUTPUT.write_text(json.dumps(receipt, indent=2) + '\n') + + class Diagnostic(module.Rollout): + def k(self, *args, data=None, timeout=60): + ensure(data is None, 'unexpected-stdin') + label = step_label(args, cfg, module.inventory_script()) + try: + q = subprocess.run(['kubectl', *args], capture_output=True, timeout=min(timeout, 60)) + except subprocess.TimeoutExpired: + receipt['steps'].append({'step': label, 'category': 'timeout'}) + save(); print(json.dumps(receipt['steps'][-1]), flush=True) + raise RuntimeError('read-only-command-timeout') + item = {'step': label, 'returncode': q.returncode, + 'category': failure_category(q.returncode, q.stderr)} + if q.returncode: + match = re.search(rb'command terminated with exit code ([0-9]+)', q.stderr) + if match: + item['remote_exit_code'] = int(match[1]) + receipt['steps'].append(item); save(); print(json.dumps(item), flush=True) + ensure(q.returncode == 0, 'read-only-step-failed:' + label) + return q.stdout + + def event(self, name, **details): + # No writes to the native r2 receipt; details contain public counters only. + receipt['checks'] = {'event': name, **details}; save() + + def dry_run(self, d): + receipt['server_dry_run'] = 'intentionally-not-replayed' + + def ready_pod(self, d, seconds=180): + return super().ready_pod(d, seconds=10) + + try: + Diagnostic(cfg).preflight() + receipt['result'] = 'read-only-preflight-passed' + except Exception as exc: + receipt['result'] = 'read-only-preflight-refused' + receipt['reason'] = str(exc) if isinstance(exc, RuntimeError) else type(exc).__name__ + save() + print(json.dumps({'result': receipt['result'], 'reason': receipt.get('reason'), + 'receipt': str(OUTPUT), 'native_mutation': False}), flush=True) + return 0 if receipt['result'] == 'read-only-preflight-passed' else 1 + + +if __name__ == '__main__': + try: + raise SystemExit(main()) + except Exception as error: + print(json.dumps({'result': 'diagnostic-refused', 'reason': str(error) if isinstance(error, RuntimeError) else type(error).__name__})) + raise SystemExit(1) diff --git a/docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/outcome.json b/docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/outcome.json new file mode 100644 index 00000000..a262f52e --- /dev/null +++ b/docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/outcome.json @@ -0,0 +1,17 @@ +{ + "proposal_id": "SYNC-SCANNER-JSON-DIGEST-20261101", + "executed_revision": 2, + "executed_packet_sha256": "d22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237", + "execution_identity": "founder; attended terminal", + "execution_evidence": "User returned bash /tmp/forge-scanner-rollout.sh failure output in active conversation, 2026-11-02", + "outcome": "failed", + "reported_reason": "kubectl-command-failed; raw diagnostics withheld", + "verified_phase": "preflight; exact failed read/remote check not yet identified", + "native_mutation": false, + "receipt_sha256": "35e4ba1b09065ec765762d4238235cb02286c42e19baad0f0ae505167c44a500", + "recovery": "No native mutation, no rollback required; original receipt and sealed packet preserved. Read-only diagnostic prepared.", + "diagnostic_sha256": "301919aa82b9e9ae5f1a2d55175d2e988ba4c4ac955598651d7f25ba71ddc21c", + "tests": "3 passed; failure label/redaction, command restrictions and preservation of original receipt", + "promotion_evidence": false, + "score": "unscored; no signed approval or separate approval evidence backfilled" +} diff --git a/docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/rollout-receipt.json b/docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/rollout-receipt.json new file mode 100644 index 00000000..33e588fb --- /dev/null +++ b/docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/rollout-receipt.json @@ -0,0 +1 @@ +{"proposal_id": "SYNC-SCANNER-JSON-DIGEST-20261101", "revision": 2, "events": [], "native_mutation": false, "packet_sha256": "d22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237", "mode": "apply"} diff --git a/docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/test_diagnose.py b/docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/test_diagnose.py new file mode 100644 index 00000000..cc633169 --- /dev/null +++ b/docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/test_diagnose.py @@ -0,0 +1,54 @@ +"""Value-free diagnostics and refusal boundaries; no cluster access.""" +import contextlib +import importlib.util +import io +import json +from pathlib import Path +import tempfile +import unittest +from unittest.mock import patch + +HERE = Path(__file__).resolve().parent +spec = importlib.util.spec_from_file_location('read_only_diagnostic', HERE / 'diagnose.py') +d = importlib.util.module_from_spec(spec); spec.loader.exec_module(d) + + +class Tests(unittest.TestCase): + def test_error_categories_never_echo_raw_diagnostics(self): + sentinel = b'PRIVATE_SENTINEL_DO_NOT_EMIT' + for raw, expected in [(b'Forbidden: ' + sentinel, 'forbidden'), + (b'command terminated with exit code 1 ' + sentinel, 'remote-check-nonzero'), + (sentinel, 'unclassified-command-failure')]: + self.assertEqual(d.failure_category(1, raw), expected) + + def test_only_expected_reads_and_exact_check_scripts_are_labeled(self): + cfg = {'guard_command': ['sh', '-c', 'EXACT_PUBLIC_GUARD']} + self.assertEqual(d.step_label(('get', 'nodes', '-o', 'json'), cfg, 'INVENTORY'), 'node-identity-read') + self.assertEqual(d.step_label(('-n', 'forgejo', 'exec', '-i', 'pod', '--', 'sh', '-c', 'EXACT_PUBLIC_GUARD', 'sh', 'check'), cfg, 'INVENTORY'), 'installed-artifact-guard') + self.assertEqual(d.step_label(('-n', 'forgejo', 'exec', '-i', 'pod', '--', 'sh', '-c', 'INVENTORY', 'sh'), cfg, 'INVENTORY'), 'existing-repository-hook-inventory') + for argv in [('create', '-f', '-'), ('patch', 'deployment', 'forge'), ('get', 'secrets'), + ('exec', 'pod', '--', 'sh', '-c', 'OTHER', 'sh'), + ('exec', 'pod', '--', 'sh', '-c', 'EXACT_PUBLIC_GUARD', 'sh', 'apply')]: + with self.subTest(argv=argv), self.assertRaises(RuntimeError): d.step_label(argv, cfg, 'INVENTORY') + + def test_failed_read_is_labeled_and_original_native_receipt_is_preserved(self): + native = Path('/tmp/forge-scanner-rollout-receipt.json') + before = native.read_bytes() if native.exists() else None + class Failed: + stdout = b'PRIVATE_STDOUT_SENTINEL' + stderr = b'Forbidden: PRIVATE_STDERR_SENTINEL' + returncode = 1 + with tempfile.TemporaryDirectory() as temp, patch.object(d, 'OUTPUT', Path(temp) / 'receipt.json'), patch.object(d.subprocess, 'run', return_value=Failed()) as command: + captured = io.StringIO() + with contextlib.redirect_stdout(captured): result = d.main() + text = captured.getvalue(); receipt = json.loads(d.OUTPUT.read_bytes()) + self.assertEqual(result, 1) + self.assertEqual(command.call_count, 1) + self.assertEqual(receipt['steps'], [{'step': 'node-identity-read', 'returncode': 1, 'category': 'forbidden'}]) + self.assertFalse(receipt['native_mutation']) + self.assertNotIn('PRIVATE_', text) + self.assertNotIn('PRIVATE_', d.OUTPUT.read_text()) + self.assertEqual(native.read_bytes() if native.exists() else None, before) + + +if __name__ == '__main__': unittest.main() diff --git a/docs/repository-sync-2026-11-01.md b/docs/repository-sync-2026-11-01.md index 3b77e229..4dcf1955 100644 --- a/docs/repository-sync-2026-11-01.md +++ b/docs/repository-sync-2026-11-01.md @@ -79,3 +79,7 @@ Founder read-only preflight succeeded: expected forge/node/pod and old adapter/G Revision 2 of SYNC-SCANNER-JSON-DIGEST-20261101 is concretely prepared and sealed by packet SHA256 `d22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237`. Its reviewed scope is the paired JSON filename/exact lowercase SHA256 correction, versioned native artifact, guarded hook migration, two-field deployment patch and six owner source files. Native server checks remain pending execution. Thirteen isolated filesystem/real receiver tests and five controller tests passed, including rollback, partial write recovery, unknown drift refusal and intermediate secret/quarantine coverage. The first offline attempt stopped because an old temporary fixture was missing; the replacement explicit scanner-only dispatcher and native real-dispatcher distinction are recorded. Original revision 1, failures and unscored receipts remain intact. The founder reviews `native-r2/PLAN.md` and the sealed source diffs, then may approve this exact revision and perform `/tmp/forge-scanner-rollout.sh` through the attended admin lane. Expected Recreate interruption is 1–3 minutes, with guarded recovery; no new resource commitment. Risk and founder time are explicitly unpriced beyond an illustrative EUR100 recovery hour, requiring supervised judgement. Remaining UUID/fingerprint/prose findings and all other held publication remain open in CUST-WP-0083. No native policy or owner scanner source was changed during preparation. + +### Revision-2 native attempt failed before mutation, 2026-11-02 + +The founder executed `/tmp/forge-scanner-rollout.sh` and returned `kubectl-command-failed; raw diagnostics withheld`. Its local receipt binds the exact reviewed packet, mode apply, `native_mutation: false`, and no events: it stopped during the read-only preflight, before staging or server dry-run. Exact failed command remains unknown. Preserved receipt/outcome and the separate tested read-only diagnostic are in `docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/`. The diagnostic reports step labels and safe categories, rejects unexpected commands, suppresses raw stdout/stderr and preserves the native receipt; 3 tests passed. Neither the sealed revision2 packet nor six owner-source preimages changed. Source followthrough and all held publication remain pending; failed execution stays unscored in supervision evidence. diff --git a/workplans/CUST-WP-0083-fleet-sync-and-scanner-remediation.md b/workplans/CUST-WP-0083-fleet-sync-and-scanner-remediation.md index 8559a64f..29308492 100644 --- a/workplans/CUST-WP-0083-fleet-sync-and-scanner-remediation.md +++ b/workplans/CUST-WP-0083-fleet-sync-and-scanner-remediation.md @@ -59,12 +59,15 @@ status: wait decision_id: "5410707c-ce73-4f80-9c03-805102c31c58" priority: high needs_human: true -intervention_note: "Read-only preflight received. Review/approve SYNC-SCANNER-JSON-DIGEST-20261101 revision 2, packet SHA256 d22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237; exact rollout, rollback and six owner source changes are in docs/changes/2026-11-01-scanner-json-digest/native-r2/PLAN.md. Native action remains unexecuted." +intervention_note: "Revision 2 preflight failed before native mutation. Run bash /tmp/forge-scanner-diagnose.sh in the founder terminal and return its value-free read-only diagnostic output; original receipt and exact packet are preserved." state_hub_task_id: "589e1a54-9a4e-5f90-bd29-5c61a6cf8081" ``` Human-lane checkpoint: the founder successfully returned `/tmp/forge-scanner-preflight-receipt.json` on 2026-11-02. It verifies exact node/deployment/pod identities and old artifacts/template, Recreate strategy and 68% CPU/76% memory requests. The exact revision-2 packet is prepared at `docs/changes/2026-11-01-scanner-json-digest/native-r2/PLAN.md`, SHA256 `d22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237`. Its 13 isolated filesystem/real receiver cases and five controller tests passed; all six owner preimages/candidate digests and the two-field Apps semantic change were verified. Initial missing offline dispatcher fixture failure is retained. Server dry-run/diff, live probes and activation are prepared execution prerequisites, not completed native evidence. Await exact founder approval and attended native execution; preserve all old artifacts and guarded rollback, source followthrough and normal publication. Neither local runtime scanner nor live receiver has changed. No admin credential borrowing, invented owner approval, promotion or scored approval. + +Execution update, 2026-11-02: the founder ran the exact revision-2 launcher, which failed on a Kubernetes command during preflight before any staging/native mutation. The failure receipt and outcome are preserved in `docs/changes/2026-11-01-scanner-json-digest/attempt-r2-preflight/`. The wrapper suppressed the failed command label as well as raw diagnostics; a separate tested read-only replay now reports step labels and fixed error categories. Await `/tmp/forge-scanner-diagnose.sh` output from the founder terminal. Original packet stays sealed; no failed trial is counted as a success and no further mutation or source followthrough is authorized by this diagnosis. + ## Verify complete remote publication and report exceptions ```task