Narrow scanner preflight failure to repository-hook inventory
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Reject generated-only commits / generated-commit-guard (push) Successful in 12s

This commit is contained in:
codex 2026-11-02 01:45:13 +01:00
parent a2e57251ac
commit 6696366f27
8 changed files with 214 additions and 5 deletions

View file

@ -1283,8 +1283,8 @@
}
],
"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.",
"verified_outcome": "Founder exact revision2 failed before mutation. Follow-up read-only diagnostic: identities/readiness/old hook/artifact guard passed; existing repository hook inventory failed with remote exit1. Await specific filesystem condition; original failed execution retained.",
"recovery": "No native mutation or source activation; original packet and both receipts preserved. Read-only inventory-condition diagnostic prepared; five diagnostic tests passed.",
"supervisor_execution_evidence": "User returned bash /tmp/forge-scanner-rollout.sh failure output in active conversation, 2026-11-02",
"attempts": [
{

View file

@ -19,3 +19,5 @@ The staged scanner in repo-manager and live receiver have not been altered by th
## 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.
The first diagnostic was returned: node/deployment/pod identity, readiness, old public hook and artifact guard all passed; existing repository hook inventory failed with remote exit1. Its selected receipt is preserved in `attempt-r2-preflight/diagnostic-receipt.json`. The pending request is now `bash /tmp/forge-scanner-inventory-diagnose.sh` to identify the specific filesystem condition without reading out hook contents. Five diagnostic tests pass, including real disposable filesystem cases.

View file

@ -0,0 +1,56 @@
{
"read_only": true,
"replayed_packet_sha256": "d22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237",
"steps": [
{
"step": "node-identity-read",
"returncode": 0,
"category": "passed"
},
{
"step": "deployment-read",
"returncode": 0,
"category": "passed"
},
{
"step": "replicaset-read",
"returncode": 0,
"category": "passed"
},
{
"step": "pod-list-read",
"returncode": 0,
"category": "passed"
},
{
"step": "deployment-read",
"returncode": 0,
"category": "passed"
},
{
"step": "initial-pod-uid-read",
"returncode": 0,
"category": "passed"
},
{
"step": "old-public-hook-read",
"returncode": 0,
"category": "passed"
},
{
"step": "installed-artifact-guard",
"returncode": 0,
"category": "passed"
},
{
"step": "existing-repository-hook-inventory",
"returncode": 1,
"category": "remote-check-nonzero",
"remote_exit_code": 1
}
],
"native_mutation": false,
"original_failed_receipt_preserved": true,
"result": "read-only-preflight-refused",
"reason": "read-only-step-failed:existing-repository-hook-inventory"
}

View file

@ -0,0 +1,91 @@
"""Founder-run read-only explanation of inventory conditions; no hook contents."""
from pathlib import Path
import importlib.util
import json
import re
import subprocess
HERE = Path(__file__).resolve().parent
ORIGINAL = HERE.parent / 'native-r2'
EXPECTED = 'd22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237'
CONDITIONS = {'directory-missing', 'symlink', 'hook-symlink-present', 'symlink-query-failed',
'dispatcher-missing', 'dispatcher-not-executable', 'scanner-fragment-missing',
'hook-metadata-command-failed', 'repository-limit', 'no-repositories', 'wrong-uid'}
SCRIPT = r'''set +vx
set -u
export PATH=/usr/bin:/bin
issue() { printf 'ISSUE\t%s\t%s\n' "$1" "$2"; }
check_directory() {
test -d "$1" || issue directory-missing "$1"
test ! -L "$1" || issue symlink "$1"
}
test "$(id -u)" = 1000 || issue wrong-uid /data
for parent in /data /data/git /data/git/gitea-repositories; do check_directory "$parent"; done
count=0
for repo in /data/git/gitea-repositories/*/*.git; do
test -d "$repo" || continue
count=$((count+1))
if test "$count" -gt 500; then issue repository-limit /data/git/gitea-repositories; break; fi
for parent in "${repo%/*}" "$repo" "$repo/hooks" "$repo/hooks/pre-receive.d"; do check_directory "$parent"; done
links=$(find "$repo/hooks" -type l -print -quit 2>/dev/null)
code=$?
if test "$code" != 0; then issue symlink-query-failed "$repo/hooks"
elif test -n "$links"; then issue hook-symlink-present "$repo/hooks"; fi
test -f "$repo/hooks/pre-receive" || issue dispatcher-missing "$repo/hooks/pre-receive"
test -x "$repo/hooks/pre-receive" || issue dispatcher-not-executable "$repo/hooks/pre-receive"
test -f "$repo/hooks/pre-receive.d/10-custodian-secret-scan" || issue scanner-fragment-missing "$repo/hooks/pre-receive.d/10-custodian-secret-scan"
if ! find "$repo/hooks" -type f ! -name '*.sample' -exec sh -c '
for file do
stat -c %a "$file" >/dev/null || exit 1
stat -c %u "$file" >/dev/null || exit 1
stat -c %g "$file" >/dev/null || exit 1
sha256sum "$file" >/dev/null || exit 1
done
' sh {} + >/dev/null 2>&1; then issue hook-metadata-command-failed "$repo/hooks"; fi
done
test "$count" -gt 0 || issue no-repositories /data/git/gitea-repositories
printf 'COUNT\t%s\n' "$count"
'''
def parse(raw):
issues = []; count = None
for line in raw.decode().splitlines():
parts = line.split('\t')
if len(parts) == 2 and parts[0] == 'COUNT' and parts[1].isdigit():
if count is not None: raise RuntimeError('duplicate-counter')
count = int(parts[1]); continue
if len(parts) != 3 or parts[0] != 'ISSUE' or parts[1] not in CONDITIONS:
raise RuntimeError('unexpected-diagnostic-output; raw withheld')
path = parts[2]
if not re.fullmatch(r'/data(?:/[a-zA-Z0-9_.-]+)*', path) or '..' in path.split('/'):
path = 'unsafe-path-withheld'
issues.append({'condition': parts[1], 'path': path})
if count is None: raise RuntimeError('missing-counter')
return {'read_only': True, 'repositories_examined': count, 'issues': issues, 'native_mutation': False}
def main():
spec = importlib.util.spec_from_file_location('reviewed_r2_inventory', ORIGINAL / 'rollout.py')
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
if m.verify_packet() != EXPECTED: raise RuntimeError('reviewed-packet-drift')
c = json.loads((ORIGINAL / 'inputs.json').read_bytes())
def k(*args):
q = subprocess.run(['kubectl', *args], capture_output=True, timeout=90)
if q.returncode: raise RuntimeError('read-only-command-failed; raw diagnostics withheld')
return q.stdout
for resource, name, expected in [('deployment', c['deployment_name'], c['deployment_uid']), ('pod', c['pod'], c['pod_uid'])]:
if k('-n', 'forgejo', 'get', resource, name, '-o', 'jsonpath={.metadata.uid}').decode() != expected:
raise RuntimeError('target-identity-drift')
result = parse(k('-n', 'forgejo', 'exec', '-i', c['pod'], '-c', c['container'], '--', 'sh', '-c', SCRIPT))
result['replayed_packet_sha256'] = EXPECTED
out = Path('/tmp/forge-scanner-inventory-diagnostic-receipt.json')
out.write_text(json.dumps(result, indent=2) + '\n')
print(json.dumps(result, indent=2)); print('receipt:', str(out))
if __name__ == '__main__':
try: main()
except Exception as exc:
print(json.dumps({'result': 'read-only-diagnostic-refused', 'reason': str(exc) if isinstance(exc, RuntimeError) else type(exc).__name__}))
raise SystemExit(1)

View file

@ -13,5 +13,19 @@
"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"
"score": "unscored; no signed approval or separate approval evidence backfilled",
"followup_diagnostic": {
"receipt_ref": "diagnostic-receipt.json",
"passed_checks": [
"node/deployment/pod identities",
"readiness",
"old public hook",
"installed artifact guard"
],
"failed_check": "existing-repository-hook-inventory",
"category": "remote-check-nonzero",
"remote_exit_code": 1,
"next_check": "inventory_conditions.py; read-only filesystem condition report",
"diagnostic_tests": "5 passed in total, including actual disposable filesystem fixtures"
}
}

View file

@ -0,0 +1,44 @@
"""Exercise actual read-only shell against disposable filesystem fixtures."""
import importlib.util
from pathlib import Path
import subprocess
import tempfile
import unittest
HERE = Path(__file__).resolve().parent
spec = importlib.util.spec_from_file_location('inventory_conditions', HERE / 'inventory_conditions.py')
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
class Tests(unittest.TestCase):
def test_clean_missing_fragment_nonexecutable_dispatcher_and_symlink(self):
with tempfile.TemporaryDirectory() as temp:
data = Path(temp) / 'data'
hooks = data / 'git/gitea-repositories/coulomb/example.git/hooks'
fragments = hooks / 'pre-receive.d'; fragments.mkdir(parents=True)
dispatcher = hooks / 'pre-receive'; dispatcher.write_text('#!/bin/sh\nexit 0\n'); dispatcher.chmod(0o755)
fragment = fragments / '10-custodian-secret-scan'; fragment.write_text('#!/bin/sh\nexit 0\n'); fragment.chmod(0o775)
script = m.SCRIPT.replace('/data', str(data))
def issues():
q = subprocess.run(['sh', '-c', script], capture_output=True, timeout=10)
self.assertEqual(q.returncode, 0)
result = m.parse(q.stdout)
self.assertEqual(result['repositories_examined'], 1)
return {x['condition'] for x in result['issues']} - {'wrong-uid'}
self.assertEqual(issues(), set())
fragment.unlink(); self.assertEqual(issues(), {'scanner-fragment-missing'})
fragment.write_text('#!/bin/sh\nexit 0\n'); fragment.chmod(0o775)
dispatcher.chmod(0o644); self.assertEqual(issues(), {'dispatcher-not-executable'})
dispatcher.chmod(0o755)
original = hooks / 'saved.d'; fragments.rename(original); fragments.symlink_to(original, target_is_directory=True)
self.assertEqual(issues(), {'symlink', 'hook-symlink-present'})
self.assertTrue((original / fragment.name).exists())
def test_parser_rejects_unframed_data_and_hides_unsafe_paths(self):
with self.assertRaisesRegex(RuntimeError, '^unexpected-diagnostic-output; raw withheld$'):
m.parse(b'PRIVATE_SENTINEL\n')
result = m.parse(b'ISSUE\tsymlink\t/elsewhere/PRIVATE_SENTINEL\nCOUNT\t1\n')
self.assertEqual(result['issues'][0]['path'], 'unsafe-path-withheld')
if __name__ == '__main__': unittest.main()

View file

@ -83,3 +83,5 @@ The founder reviews `native-r2/PLAN.md` and the sealed source diffs, then may ap
### 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.
Follow-up diagnostic returned: eight preliminary checks passed; existing repository hook inventory failed with remote exit1. The next read-only diagnostic reports the exact rejected filesystem condition and public path, while leaving hooks and both prior receipts intact. Five diagnostic tests passed, including missing fragments, executable checks and symlink fixtures. Founder request: `bash /tmp/forge-scanner-inventory-diagnose.sh`. No native or owner scanner-source change.

View file

@ -59,14 +59,14 @@ status: wait
decision_id: "5410707c-ce73-4f80-9c03-805102c31c58"
priority: high
needs_human: true
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."
intervention_note: "Revision 2 failed before mutation: identities/readiness/template/artifact guard passed, repository hook inventory exited1. Run bash /tmp/forge-scanner-inventory-diagnose.sh in the founder terminal and return its read-only condition report."
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.
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. The founder returned that diagnostic: all identity/readiness/template/artifact checks passed, but repository hook inventory returned remote exit1. Await `bash /tmp/forge-scanner-inventory-diagnose.sh` output for the specific failed filesystem condition. This second read-only check reports only condition labels/public paths; five diagnostic tests now pass, including actual disposable filesystem fixtures. 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