diff --git a/.github/workflows/c7-total-bounded.yml b/.github/workflows/c7-total-bounded.yml index 3d025b1ae..a19097df4 100644 --- a/.github/workflows/c7-total-bounded.yml +++ b/.github/workflows/c7-total-bounded.yml @@ -5,6 +5,7 @@ name: C7-total bounded-difference checks paths: - 'frontiers/c7_total_bounded_20260929/**' - '.github/workflows/c7-total-bounded.yml' + - 'tests/test_c7_total_workflow.py' workflow_dispatch: permissions: @@ -21,6 +22,13 @@ jobs: - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 with: python-version: '3.11.16' + - name: Exercise C7 wrapper rejection contracts in both modes + env: + PYTHONDONTWRITEBYTECODE: '1' + run: | + set -euo pipefail + python -B -S -m unittest discover -s tests -p test_c7_total_workflow.py -v + python -B -O -S -m unittest discover -s tests -p test_c7_total_workflow.py -v - name: Verify sources, replay both modes, reject mutants env: PYTHONDONTWRITEBYTECODE: '1' @@ -43,17 +51,29 @@ jobs: raise ValueError('source identity mismatch: ' + entry['path']) script = str(root / 'exponent_check.py') expected = (root / 'RESULTS.json').read_bytes() - mutants = ['M1', 'M2', 'M3'] + # Independently measured from unchanged exponent_check.py blob 0f9d4cf4. + # Exact FAIL lines (including newline), not a substring or learned oracle. + failures = { + 'M1': b'FAIL T_pieceB1_7_12\n', + 'M2': b'FAIL T_threshold_is_r_equals_k\n', + 'M3': b'FAIL K_62prime_layer_integral_exact\n', + } for flags in (['-B', '-S'], ['-B', '-O', '-S']): out = subprocess.run([sys.executable, *flags, script], - check=True, capture_output=True, timeout=300).stdout - if out != expected: - raise RuntimeError('output differs from RESULTS.json under ' + ' '.join(flags)) - for m in mutants: - rc = subprocess.run([sys.executable, *flags, script, '--mutant', m], - capture_output=True, timeout=300).returncode - if rc != 1: - raise RuntimeError('mutant ' + m + ' not rejected') + check=True, capture_output=True, timeout=300) + if out.stdout != expected or out.stderr != b'': + raise RuntimeError('baseline output or stderr differs under ' + ' '.join(flags)) + print(json.dumps({'stage': 'baseline', 'flags': flags, 'returncode': out.returncode, + 'stdout_sha256': hashlib.sha256(out.stdout).hexdigest(), + 'stderr_sha256': hashlib.sha256(out.stderr).hexdigest()}), flush=True) + for m, diagnostic in failures.items(): + out = subprocess.run([sys.executable, *flags, script, '--mutant', m], + capture_output=True, timeout=300) + if out.returncode != 1 or out.stdout != diagnostic or out.stderr != b'': + raise RuntimeError('mutant ' + m + ' did not report its intended rejection') + print(json.dumps({'stage': m, 'flags': flags, 'returncode': out.returncode, + 'stdout_sha256': hashlib.sha256(out.stdout).hexdigest(), + 'stderr_sha256': hashlib.sha256(out.stderr).hexdigest()}), flush=True) print(json.dumps({'scientific_effect': 'NONE', 'scientific_acceptance': False})) PY git diff --exit-code diff --git a/reviews/c7_total_protocol_20261006/SCOPE.md b/reviews/c7_total_protocol_20261006/SCOPE.md new file mode 100644 index 000000000..8d49b560d --- /dev/null +++ b/reviews/c7_total_protocol_20261006/SCOPE.md @@ -0,0 +1,91 @@ +# C7 total-bounded workflow protocol repair + +Dylan Roy — delegated AI engineering. Actual author OpenAI / GPT-6 Astra Pro, +`c7-protocol-followthrough-20261006-r3`; pickup Math-#322/6015371102. +Scientific effect NONE; organizational-independence credit 0. No author self-merge. + +## Scope and design + +This changes only the existing C7 workflow and adds its focused regression file +and this note. No new helper or validation framework. The mathematical packet, +its checker, proof, erratum, results, manifest and reading notes remain unchanged. +The original workflow ignores baseline stderr and accepts M1/M2/M3 solely on +exit 1. The replacement also requires empty stderr and each independently +measured exact stdout line, including its single LF. Baseline stdout remains +byte-exact against the original RESULTS.json. No substring, whitespace trimming, +JSON coercion or child-generated oracle is used. The existing eight-command +inventory, inherited working directory, 300-second subprocess budgets, +10-minute job budget, action/Python pins and read-only permissions remain. +Launch and timeout exceptions are not caught or suppressed. Successful command +records now print stage, flags, exit and stream hashes. The added regression step +runs before the original source-check/replay step, in both Python modes. + +## Authenticated original sources + +Source cut: `11f9127f00ce922e5d39b0da42af76134eb78a21`. + +| Path | Bytes | Git blob | +|---|---:|---| +| .github/workflows/c7-total-bounded.yml |2662|3d025b1aefdaaf43a62acbbf2b318bfc8b687ecf| +| frontiers/c7_total_bounded_20260929/exponent_check.py |7736|0f9d4cf4b7f2d79bb5fa338fc8e1bc77506dd8c9| +| frontiers/c7_total_bounded_20260929/RESULTS.json |1056|52df86b1712f22d629a5e630aa199dca3f6d4b9e| +| frontiers/c7_total_bounded_20260929/SOURCE_FILES.json |unchanged|9da912e8db14792d841593e08a518743a51c9dd6| + +Native eight-case characterization used unchanged checker bytes on local +CPython 3.13.5. Each baseline/M1/M2/M3 stdout and stderr matched between normal +and optimized child modes. Baseline SHA256 is the existing manifest's +`dd5c89362090bd2ff62080e363b1b54a79ed3fcfb28f93b24d182ecd6ec20777`. +The failure lines are: + +- M1: `FAIL T_pieceB1_7_12` plus LF, exit 1, empty stderr. +- M2: `FAIL T_threshold_is_r_equals_k` plus LF, exit 1, empty stderr. +- M3: `FAIL K_62prime_layer_integral_exact` plus LF, exit 1, empty stderr. + +Eight further genuine commands through the changed process-loop extraction +matched all original exits and stream hashes. That extraction omits the packet +preflight and is explicitly a selected-source replay, not a complete six-file +packet authentication. Actual full-packet execution must come from dedicated CI. + +## Test-first evidence + +The unchanged published regression file has seven methods and 107 distinct +whole-shell scenarios. Real temporary Git repositories, actual Bash and child +Python processes, no subprocess mocks. Each synthetic packet is committed with +its own correct manifest before any intentional corruption. An external trace +must reach the intended stage and stop there; five source guards stop before +children; the dirty-tree control reaches all eight commands before Git rejects. +Synthetic signing and maintenance are disabled only on its disposable commit; +no real user Git configuration, signer or credential is modified. + +Full original runs in BOTH outer modes: 75 intended assertion failures, zero +errors, 107 fixture records and 722 child commands each. Of the 74 noncanonical +output admissions, 18 concern formatting alone (missing LF, CRLF, extra LF); +56 concern crashes, silence, wrong/noisy output, wrong reasons or stderr. The +remaining assertion is absent regression wiring. The valid fixture passes, and +32 existing exit/source/dirty-tree controls reject. These categories do not +represent 74 independent bugs or 74 semantic theorem failures. + +Full repaired runs in BOTH outer modes: seven methods OK, all 107 expected +outcomes, one valid admission and 106 correct rejections, 496 child commands per +mode. Same test bytes/scenario keys in all four runs: 428 fixture records and +2436 child invocations total, not 428 distinct scenarios. One initial normal RED +attempt hit the external tool deadline; its partial records are retained and +excluded. All four counted runs completed without partitioning or skips. + +## Preserved boundaries and remaining requirements + +The original source preflight (including import, 972 indented bytes) has SHA256 +`7b6f4f2b7e841fedc5e5dd2fd1f3aae62ad6a142900ca7dc46d804afb093113d`. +The original final JSON/Git postflight (135 bytes) has SHA256 +`502344625c8ab44b2065a9ffda2f0acb2cb5d8c7ee1974169a6baf2a7a31d490`. +Both remain byte-identical. Existing manifest consumption metadata is not newly +asserted to be an executed dependency check. No unknown-label control is added: +this proposal preserves the original M1/M2/M3 invocation scope, not a new CLI. + +A distinct nonauthor engineering review, actual dedicated/general/applicable +current-candidate checks, and a separate eligible guarded integration are still +required. Historical evidence is not rebound to a later base. #322 remains open +for other wrappers. Direct Git clone failed DNS; there is no full local checkout, +full local repository suite, local Lean, live dependency-fetch or mathematical +re-review claim. This is not a hostile-source sandbox, output-memory bound, +durable-output guarantee, theorem acceptance or global audit clearance. diff --git a/tests/test_c7_total_workflow.py b/tests/test_c7_total_workflow.py new file mode 100644 index 000000000..9b57e3493 --- /dev/null +++ b/tests/test_c7_total_workflow.py @@ -0,0 +1,204 @@ +"""Run the actual C7 workflow shell against real committed synthetic packets. + +These tests validate the wrapper protocol, not the Gaussian theorem. The expected +mutant messages were independently measured from checker blob 0f9d4cf4. +""" +import base64 +import hashlib +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[1] +WORKFLOW = ROOT / '.github/workflows/c7-total-bounded.yml' +PACKET = Path('frontiers/c7_total_bounded_20260929') +STAGES = ('baseline', 'M1', 'M2', 'M3') +ORDER = [[mode, label] for mode in (0, 1) for label in STAGES] +FAILURES = { + 'M1': b'FAIL T_pieceB1_7_12\n', + 'M2': b'FAIL T_threshold_is_r_equals_k\n', + 'M3': b'FAIL K_62prime_layer_integral_exact\n', +} +BASELINE = b'{"synthetic": true, "passed": true}\n' + + +def workflow_shell(): + """Extract the complete verification step; do not replace its source guards.""" + text = WORKFLOW.read_text() + marker = ' - name: Verify sources, replay both modes, reject mutants\n' + if text.count(marker) != 1: + raise ValueError('unique verification step required') + tail = text.split(marker, 1)[1].split(' run: |\n', 1)[1] + lines = [] + for line in tail.splitlines(): + if line and not line.startswith(' '): + break + lines.append(line[10:] if line else '') + shell = '\n'.join(lines) + '\n' + if not shell.startswith('set -euo pipefail\n') or not shell.endswith('git diff --exit-code\n'): + raise ValueError('complete strict shell/postflight required') + return shell + + +def synthetic_checker(): + return 'FAILURES = ' + repr(FAILURES) + '\nBASELINE = ' + repr(BASELINE) + r''' +import json, os, signal, sys +from pathlib import Path +args = sys.argv[1:] +label = 'baseline' if not args else args[1] if len(args) == 2 and args[0] == '--mutant' else 'INVALID' +if label not in ('baseline', 'M1', 'M2', 'M3'): + raise RuntimeError('unexpected invocation') +with open(os.environ['C7_TRACE'], 'a') as stream: + stream.write(json.dumps([sys.flags.optimize, label]) + '\n') +code = 0 if label == 'baseline' else 1 +out = BASELINE if label == 'baseline' else FAILURES[label] +err = b'' +case = os.environ['C7_CASE'] +if case == 'dirty': + Path(os.environ['C7_RESULTS']).write_bytes(b'changed after preflight\n') +if label == os.environ['C7_STAGE'] and sys.flags.optimize == int(os.environ['C7_MODE']): + if case == 'crash': raise RuntimeError('unrelated synthetic crash') + if case == 'signal': os.kill(os.getpid(), signal.SIGTERM) + if case == 'silent': out = b'' + elif case == 'stderr': err = b'unexpected stderr\n' + elif case == 'wrong-reason': out = b'FAIL an_unrelated_check\n' + elif case == 'other-mutant': out = FAILURES['M2' if label != 'M2' else 'M3'] + elif case == 'prefix': out = b'prefix ' + out + elif case == 'suffix': out += b'additional report\n' + elif case == 'no-newline': out = out.rstrip(b'\n') + elif case == 'crlf': out = out.replace(b'\n', b'\r\n') + elif case == 'json': out = b'{"passed": false}\n' + elif case == 'bad-utf8': out = b'\xff' + out + elif case == 'extra-newline': out += b'\n' + elif case == 'wrong-exit': code = 1 if label == 'baseline' else 0 + elif case == 'exit-two': code = 2 +sys.stdout.buffer.write(out) +sys.stderr.buffer.write(err) +sys.exit(code) +''' + + +@unittest.skipUnless(os.name == 'posix', 'workflow requires Bash and POSIX processes') +class C7TotalWorkflowTests(unittest.TestCase): + def fixture(self, case='valid', stage='baseline', mode=0, guard=None): + with tempfile.TemporaryDirectory(prefix='c7-protocol-') as temp: + home = Path(temp) + repo = home / 'repo' + packet = repo / PACKET + packet.mkdir(parents=True) + trace = home / 'calls.jsonl' + (packet / 'exponent_check.py').write_text(synthetic_checker()) + (packet / 'RESULTS.json').write_bytes(BASELINE) + files = [] + for path in sorted(packet.iterdir()): + data = path.read_bytes() + files.append({'path': path.name, 'bytes': len(data), + 'sha256': hashlib.sha256(data).hexdigest()}) + manifest = packet / 'SOURCE_FILES.json' + manifest.write_text(json.dumps({'files': files})) + env = {k: v for k, v in os.environ.items() if not k.startswith('GIT_')} + env.update(HOME=str(home), GIT_CONFIG_NOSYSTEM='1', GIT_CONFIG_GLOBAL=os.devnull, + GIT_AUTHOR_NAME='Synthetic fixture', GIT_AUTHOR_EMAIL='fixture@example.invalid', + GIT_COMMITTER_NAME='Synthetic fixture', GIT_COMMITTER_EMAIL='fixture@example.invalid', + PYTHONDONTWRITEBYTECODE='1', C7_TRACE=str(trace), C7_CASE=case, + C7_STAGE=stage, C7_MODE=str(mode), C7_RESULTS=str(packet / 'RESULTS.json')) + env['PATH'] = str(Path(sys.executable).parent) + os.pathsep + env.get('PATH', '') + for args in (['init', '-q'], ['add', '.'], + ['-c', 'commit.gpgsign=false', '-c', 'maintenance.auto=false', + '-c', 'core.hooksPath=' + os.devnull, 'commit', '-qm', 'synthetic C7 packet']): + subprocess.run(['git', *args], cwd=repo, env=env, check=True, + capture_output=True, timeout=15) + if guard == 'hash': + (packet / 'RESULTS.json').write_bytes(b'X' + BASELINE[1:]) + elif guard == 'size': + files[0]['bytes'] += 1 + manifest.write_text(json.dumps({'files': files})) + elif guard == 'extra': + (packet / 'unexpected').write_bytes(b'extra') + elif guard == 'missing': + (packet / 'RESULTS.json').unlink() + elif guard == 'symlink': + (home / 'target').write_bytes(BASELINE) + (packet / 'RESULTS.json').unlink() + (packet / 'RESULTS.json').symlink_to(home / 'target') + result = subprocess.run(['bash', '--noprofile', '--norc', '-c', workflow_shell()], + cwd=repo, env=env, capture_output=True, timeout=20) + calls = [json.loads(row) for row in trace.read_text().splitlines()] if trace.exists() else [] + if saved := os.environ.get('C7_EVIDENCE'): + dest = Path(saved) + dest.mkdir(parents=True, exist_ok=True) + key = '-'.join((case, stage, str(mode), str(guard))) + record = {'case': case, 'stage': stage, 'mode': mode, 'guard': guard, + 'returncode': result.returncode, 'calls': calls, + 'stdout_base64': base64.b64encode(result.stdout).decode(), + 'stderr_base64': base64.b64encode(result.stderr).decode()} + target = dest / (key + '.json') + with target.open('x') as stream: + json.dump(record, stream, sort_keys=True) + stream.write('\n') + return result, calls + + def rejected_at(self, case, stage, mode): + result, calls = self.fixture(case, stage, mode) + target = ORDER.index([mode, stage]) + 1 + self.assertIn([mode, stage], calls, 'target must actually execute') + self.assertNotEqual(result.returncode, 0, 'invalid admission: ' + repr((case, stage, mode))) + self.assertEqual(calls, ORDER[:target], 'reject at intended stage, before later children') + + def test_valid_complete_inventory(self): + result, calls = self.fixture() + self.assertEqual(result.returncode, 0, result.stderr.decode(errors='replace')) + self.assertEqual(calls, ORDER) + self.assertEqual(result.stderr, b'') + + def test_each_mutant_requires_exact_reason_and_clean_stderr(self): + for case in ('crash', 'silent', 'stderr', 'wrong-reason', 'other-mutant', 'prefix', + 'suffix', 'no-newline', 'crlf', 'json', 'bad-utf8', 'extra-newline'): + for mode in (0, 1): + for stage in STAGES[1:]: + with self.subTest(case=case, mode=mode, stage=stage): + self.rejected_at(case, stage, mode) + + def test_mutant_wrong_exit_and_signal(self): + for case in ('wrong-exit', 'exit-two', 'signal'): + for mode in (0, 1): + for stage in STAGES[1:]: + with self.subTest(case=case, mode=mode, stage=stage): + self.rejected_at(case, stage, mode) + + def test_baseline_bytes_exit_and_stderr(self): + for case in ('silent', 'stderr', 'wrong-exit', 'suffix', 'no-newline'): + for mode in (0, 1): + with self.subTest(case=case, mode=mode): + self.rejected_at(case, 'baseline', mode) + + def test_source_guards_precede_children(self): + for guard in ('hash', 'size', 'extra', 'missing', 'symlink'): + with self.subTest(guard=guard): + result, calls = self.fixture(guard=guard) + self.assertNotEqual(result.returncode, 0) + self.assertEqual(calls, []) + expected = (b'source identity mismatch' if guard in ('hash', 'size') else + b'flat regular source required' if guard == 'symlink' else + b'packet tree differs from manifest') + self.assertIn(expected, result.stderr) + + def test_tracked_mutation_rejected_after_full_replay(self): + result, calls = self.fixture('dirty') + self.assertNotEqual(result.returncode, 0) + self.assertEqual(calls, ORDER) + self.assertIn(b'changed after preflight', result.stdout) + + def test_regression_wiring(self): + text = WORKFLOW.read_text() + self.assertIn("- 'tests/test_c7_total_workflow.py'", text) + for flags in ('-B -S', '-B -O -S'): + self.assertIn('python ' + flags + ' -m unittest discover -s tests -p test_c7_total_workflow.py -v', text) + + +if __name__ == '__main__': + unittest.main()