diff --git a/.github/workflows/cusp-second-order.yml b/.github/workflows/cusp-second-order.yml index 7060635b1..459d93fea 100644 --- a/.github/workflows/cusp-second-order.yml +++ b/.github/workflows/cusp-second-order.yml @@ -5,6 +5,7 @@ name: Cusp second-order exact controls paths: - 'frontiers/cusp_second_order_20261001/**' - '.github/workflows/cusp-second-order.yml' + - 'tests/test_cusp_workflow_protocol.py' # bound merged sources (consumed in SOURCES.json) and the pinned unmerged dependencies (#191, #198; verified by blob) - 'frontiers/remainder_vanishing_20260930/PROOF.md' - 'frontiers/remainder_rate_20260930/PROOF.md' @@ -32,6 +33,10 @@ jobs: - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 with: python-version: '3.11.16' + - name: Cusp workflow protocol regressions + run: | + python -B -S -m unittest discover -s tests -p test_cusp_workflow_protocol.py -v + python -B -O -S -m unittest discover -s tests -p test_cusp_workflow_protocol.py -v - name: Verify sources, replay both modes, reject mutants env: PYTHONDONTWRITEBYTECODE: '1' @@ -81,19 +86,33 @@ jobs: if len(data) != entry['bytes'] or hashlib.sha256(data).hexdigest() != entry['sha256'] or hashlib.sha1(framed).hexdigest() != entry['git_blob']: raise ValueError('consumed unmerged dependency differs from the pinned blob (rebind required): ' + entry['path'] + ' via ' + where) print(json.dumps({'consumed_unmerged_verified': entry['path'], 'git_blob': entry['git_blob'], 'via': where})) + # Complete immutable reports from the original five mutants, characterized + # in both modes at 11f9127f (run 37465152509/1). These are not exit-only + # expectations or reports generated by the current mutant as its own oracle. + contracts = { + 'M1': (1536, '9302b83844a9c6d9a2b9f65bfc68ff9c96b7a1e610447b796bee130f08bd625b', 'C2 elder window'), + 'M2': (1514, '0b55d471950c3a2dc4914d78ac6810ddffd4c903ae128e4ff0e2d333fe969852', 'C6 elder cusp integral'), + 'M3': (1571, '72f2b370a2ff1a1202852e8eb328f7b3b86cb8df5fb520fdc20b69b325bbef25', 'C4 cusp limit field'), + 'M4': (1604, '056b3738ae7319b50377c1f30c428e1f55ac5d331db890d30a81776c8507ae56', 'C3 fiber identity'), + 'M5': (1456, '3e863170bff01721399a056d94f651427a064bfc86e032e9cfe39d54b218c963', 'C8 determinant derivative'), + } + if manifest['mutants'] != list(contracts): + raise ValueError('cusp mutant inventory differs from the complete ordered contract') script = str(root / 'cusp_check.py') expected = (root / 'RESULTS.json').read_bytes() for flags in (['-B', '-S'], ['-B', '-O', '-S']): - out = subprocess.run([sys.executable, *flags, script], check=True, capture_output=True, timeout=600).stdout - if out != expected: - raise RuntimeError('output differs from RESULTS.json under ' + ' '.join(flags)) + baseline = subprocess.run([sys.executable, *flags, script], check=True, capture_output=True, timeout=600) + if baseline.stdout != expected or baseline.stderr: + raise RuntimeError('baseline streams differ from RESULTS.json/empty stderr under ' + ' '.join(flags)) for m in manifest['mutants']: - rc = subprocess.run([sys.executable, *flags, script, '--mutant', m], capture_output=True, timeout=600).returncode - if rc != 1: - raise RuntimeError('mutant ' + m + ' not rejected') - rc = subprocess.run([sys.executable, *flags, script, '--mutant', 'M9'], capture_output=True, timeout=600).returncode - if rc != 2: - raise RuntimeError('unknown mutant label not rejected with exit 2') + result = subprocess.run([sys.executable, *flags, script, '--mutant', m], capture_output=True, timeout=600) + size, sha256, reason = contracts[m] + if (result.returncode != 1 or result.stderr or len(result.stdout) != size + or hashlib.sha256(result.stdout).hexdigest() != sha256): + raise RuntimeError('mutant ' + m + ' did not emit its exact ' + reason + ' rejection report') + unknown = subprocess.run([sys.executable, *flags, script, '--mutant', 'M9'], capture_output=True, timeout=600) + if unknown.returncode != 2 or unknown.stdout or unknown.stderr != b'unknown mutant label\n': + raise RuntimeError('unknown mutant label did not emit the complete expected exit-2 diagnostic') print(json.dumps({'scientific_effect': 'NONE', 'mathematical_acceptance': False})) PY git diff --exit-code diff --git a/reviews/cusp_workflow_protocol_20261006/SCOPE.md b/reviews/cusp_workflow_protocol_20261006/SCOPE.md new file mode 100644 index 000000000..50fe56624 --- /dev/null +++ b/reviews/cusp_workflow_protocol_20261006/SCOPE.md @@ -0,0 +1,126 @@ +# Cusp workflow: complete deterministic rejection reports + +Dylan Roy — delegated AI engineering. Actual author: OpenAI / GPT-6 Astra Pro, +`rfgate-followthrough-cusp-contract-20261006`; pickup Math-#322/6016381591, +characterization/implementation notice 6016664543. Scientific effect **NONE**; +source/report exposed, organizational-independence credit 0. No author self-merge. + +## Original source and observed defect + +Source cut `11f9127f00ce922e5d39b0da42af76134eb78a21`: + +| File | Bytes | Git blob | +|---|---:|---| +| `.github/workflows/cusp-second-order.yml` |5776|7060635b18af66cf0802f49e44e692b730fa3b8c| +| `frontiers/cusp_second_order_20261001/cusp_check.py` |35169|98ac5070c26655bd3aa979a86319e655462156ae| +| packet `SOURCES.json` |8629|5d7678b033856407b472ed687f1f2ea6390e90ab| +| packet `RESULTS.json` |1596|c5cc870726dc17e1a1abd5765f01b3ff85b46f07| + +The original wrapper checks baseline stdout but ignores stderr. It accepts each +named mutant solely by exit 1 and M9 solely by exit 2; it does not independently +require the complete ordered mutant inventory. A crash or wrong report can thus +count as the expected negative control. This is an engineering finding, not a +counterexample to the original mathematical arguments or finite identities. + +## Genuine reports, not guessed diagnostics + +Isolated execution snapshot `b05b0ddc6f982ee68e73b5d1d5e7317e1c1acb69` adds only +one branch-restricted read-only characterization workflow to the source cut. +That never-merge driver is NOT part of this proposal. Actual GitHub Actions run +**37465152509/1**, job112274212867, completed successfully at Python3.11.16. +It authenticated the six packet files and exact membership, then ran the original +baseline, M1-M5 and M9 in normal and optimized modes: **14 real subprocesses**. +All seven pairs of stdout AND stderr are byte-identical. Baseline matches the +original RESULTS. The original checker is self-contained; this characterization +did not revalidate all consumed upstream mathematical files or run exploration. + +Artifact11413459080:58209B, SHA256 +`393e02ad9defd6ac8167e154835be035ec0d802d6ed161b613b14af22ec20aff`. +Its30members are28binary streams, a run-bound record and an exact tracked-source +tar. All members/CRCs/stream identities and eight archived original source files +were independently verified. A second local CPython3.13.5 run of all14 original +invocations matched every captured stream and exit. + +| Mutant | Sole false check | Exact `info` | +|---|---|---| +|M1|C2_elder_window_exact_maximin|elder window fails at phi = -29/60 (death level None)| +|M2|C6_cusp_integrals|elder cusp integral| +|M3|C4_cusp_limit_field_pinned_polynomials|cusp limit field mismatch (m = 1): -181/240 vs -67/80| +|M4|C3_fiber_reduction|fiber identity fails (m = 1)| +|M5|C8_gap_affine_structure_and_cand_constants|d det K_M / dk != -6 det A_M (m = 1)| + +Each complete mutant JSON retains every other baseline check, its own label, +overall Boolean false, object and scientific-effect fields. It exits1 with empty +stderr. M9 exits2, stdout empty, stderr exactly `unknown mutant label\n`. +The checker source explicitly selects the corresponding C2/C6/C4/C3/C8 mutation; +these are not inferred solely from a generic process failure. + +## Repair and deliberately unchanged behavior + +The existing workflow now requires the exact M1-M5 ordered inventory before any +checker child. The five fixed stdout sizes/SHA256s bind their ENTIRE original +reports, together with exit1 and empty stderr. Baseline retains exact RESULTS +comparison and additionally requires empty stderr. M9 requires its complete +exit2/stdout/stderr triple. References are not learned from the current mutant +run; a changed report requires deliberate source-bound review and rebinding. + +Exact bytes intentionally reject even JSON whitespace/ordering changes. This is +a deterministic internal command contract, not a general JSON-format API. +It avoids a new helper import, parser framework or dynamic auxiliary dependency. + +The complete source preflight and final clean-Git block are unchanged. This +includes nonempty consumed/cited/tree-or-API dependency identity checks and the +existing fail-closed behavior on source drift. No source hash was widened or +rebased to hide a historical dependency mismatch. Original packet, proof, +checker, RESULTS, SOURCES, exploration, formal, rules and scientific flags are +untouched. Existing action/Python pins, read-only permissions,10-minute budget +and600-second child timeouts remain. Only the new test trigger and two-mode test +step are added outside the changed result-validation block. + +## Test-first evidence and limits + +The new8-method test file executes the complete actual verification shell inside +real disposable committed Git repositories. A synthetic child emits the known +original report bodies; no subprocess is mocked and no numerical calculation is +claimed by these fixtures. Packet and all three upstream source categories have +nonempty identity-bound data. All source preflight and final dirty-tree checks +remain active. A separate child trace checks the complete sequence or exact +stopping prefix; named source failures also require their own diagnostics. +Signing and automatic maintenance are disabled only on the disposable Git commit. + +Each complete run covers **85 scenarios**. The final unchanged test file against +the original workflow yields **60 intended assertion failures,0errors** in EACH +normal/optimized mode:59 unsafe admissions plus missing regression wiring. +The other26 scenarios behave correctly. The amended workflow yields **8/8 methods +PASS,85/85 scenarios** in EACH mode. Cases cover every mutant's crash/silence/ +stderr/wrong exit; wrong check/reason/label structure, Boolean coercion, duplicate +keys, nonfinite/trailing/extra/formatted JSON; baseline and M9 streams/exits; +source tamper/membership/symlink, missing/reordered/duplicated control lists and +the final tracked-tree mutation. The whole-shell child itself runs both modes. + +Local runtime: CPython3.13.5/Git2.47.3/Linux. YAML and all run-block Bash syntax +checks passed; unchanged preflight/postflight were compared explicitly. Local +work is an authenticated source subset, not a complete repository test/Lean +execution. Direct GitHub DNS was unavailable. An unavailable streaming-execution +attempt launched no test; completed foreground runs and original failed results +are retained. No timeout or failure was converted to a pass. + +Reproduction at the repository root: + +```sh +python -B -S -m unittest discover -s tests -p test_cusp_workflow_protocol.py -v +python -B -O -S -m unittest discover -s tests -p test_cusp_workflow_protocol.py -v +``` + +For RED, use an isolated copy of the original tree with the new test retained. +The original seven packet files must stay present. `CUSP_WORKFLOW_ROOT` selects +an isolated workflow/reference tree for local counterfactual testing only. + +This is not a hostile-executable sandbox, proof of an arbitrary child's internal +failure location, exhaustive API/loader/fs coverage or durable timeout-evidence +system. A malicious program could print a known report; source trust and proof +validity remain separate. The earlier never-merge characterization is NOT a +production candidate's required-check receipt. Actual full current-candidate +cusp/source replay, all applicable hosted checks and one nonauthor engineering +review remain required before separate eligible guarded integration. #322 stays +open for its broader class; other active authors and integration slots are intact. diff --git a/tests/test_cusp_workflow_protocol.py b/tests/test_cusp_workflow_protocol.py new file mode 100644 index 000000000..e8a32cab3 --- /dev/null +++ b/tests/test_cusp_workflow_protocol.py @@ -0,0 +1,229 @@ +"""Complete-shell protocol tests; synthetic checkers, real Git and subprocesses. + +The expected report bodies below are characterized from the unchanged checker +at 11f9127f. No mathematical result or subprocess outcome is mocked. A child +emits recorded carriers instead of redoing the numerical calculation. Source +preflight and final clean-tree checks run unchanged around those real children. +""" +import hashlib +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import textwrap +import unittest +from concurrent.futures import ThreadPoolExecutor + +ROOT = Path(os.environ.get('CUSP_WORKFLOW_ROOT', Path(__file__).resolve().parents[1])) +WORKFLOW = ROOT / '.github/workflows/cusp-second-order.yml' +PACKET = Path('frontiers/cusp_second_order_20261001') +LABELS = ('M1', 'M2', 'M3', 'M4', 'M5') +REASONS = { + 'M1': ('C2_elder_window_exact_maximin', 'elder window fails at phi = -29/60 (death level None)'), + 'M2': ('C6_cusp_integrals', 'elder cusp integral'), + 'M3': ('C4_cusp_limit_field_pinned_polynomials', 'cusp limit field mismatch (m = 1): -181/240 vs -67/80'), + 'M4': ('C3_fiber_reduction', 'fiber identity fails (m = 1)'), + 'M5': ('C8_gap_affine_structure_and_cand_constants', 'd det K_M / dk != -6 det A_M (m = 1)'), +} + +def digest(raw): + return hashlib.sha256(raw).hexdigest() + + +def carriers(): + baseline = (ROOT / PACKET / 'RESULTS.json').read_bytes() + if digest(baseline) != '038e0d0554a7575679d24492d110e1a823136dfb88dc556e45c58cc386c86339': + raise ValueError('test reference must be deliberately rebound to a changed original report') + outputs = {'baseline': baseline} + for label, (check, reason) in REASONS.items(): + doc = json.loads(baseline) + doc['mutant'] = label + doc['passed'] = False + doc['checks'][check] = {'passed': False, 'info': reason} + outputs[label] = (json.dumps(doc, indent=1, sort_keys=True) + '\n').encode() + return outputs + + +def shell(): + text = WORKFLOW.read_text() + heading = ' - name: Verify sources, replay both modes, reject mutants\n' + begin = text.index(' set -euo pipefail\n', text.index(heading)) + final = ' git diff --exit-code\n' + end = text.index(final, begin) + len(final) + return textwrap.dedent(text[begin:end]) + + +def child_source(behavior, target, mode): + return ('''import json, os, sys +from pathlib import Path +OUTPUTS = ''' + repr(carriers()) + ''' +BEHAVIOR = ''' + repr(behavior) + ''' +TARGET = ''' + repr(target) + ''' +MODE = ''' + repr(mode) + ''' +label = sys.argv[-1] if len(sys.argv) > 1 else 'baseline' +with open(os.environ['CUSP_TRACE'], 'a') as f: + f.write(json.dumps([sys.flags.optimize, label])+'\\n') +active = label == TARGET and sys.flags.optimize == MODE +raw = OUTPUTS.get(label, b'') +rc = 0 if label == 'baseline' else 2 if label == 'M9' else 1 +err = b'unknown mutant label\\n' if label == 'M9' else b'' +if active: + if BEHAVIOR == 'crash': raise RuntimeError('unrelated synthetic crash') + if BEHAVIOR == 'silent': raw, err = b'', b'' + if BEHAVIOR == 'stderr': err += b'unrelated stderr\\n' + if BEHAVIOR == 'wrong_exit': rc = 0 if label != 'baseline' else 1 + if BEHAVIOR == 'wrong_reason': + if label == 'M9': err = b'wrong reason\\n' + else: + doc = json.loads(raw) + doc['checks'][next(k for k,v in doc['checks'].items() if not v['passed'])]['info'] = 'wrong reason' + raw = (json.dumps(doc, indent=1, sort_keys=True)+'\\n').encode() + if BEHAVIOR == 'wrong_check': + doc = json.loads(raw) + for v in doc['checks'].values(): v['passed'] = True + doc['checks']['C1_quartic_ridge_identities']['passed'] = False + raw = (json.dumps(doc, indent=1, sort_keys=True)+'\\n').encode() + if BEHAVIOR == 'boolean_int': raw = raw.replace(b'"passed": false', b'"passed": 0') + if BEHAVIOR == 'duplicate': raw = raw.replace(b'"mutant":', b'"mutant": "duplicate", "mutant":', 1) + if BEHAVIOR == 'nonfinite': raw = raw.replace(b'"passed": false', b'"passed": NaN') + if BEHAVIOR == 'trailing': raw += b'garbage\\n' + if BEHAVIOR == 'extra_field': + doc=json.loads(raw); doc['unexpected']=True + raw=(json.dumps(doc, indent=1, sort_keys=True)+'\\n').encode() + if BEHAVIOR == 'formatting': raw=json.dumps(json.loads(raw)).encode() + if BEHAVIOR == 'stdout': raw=b'unexpected unknown-label output\\n' + if BEHAVIOR == 'dirty': Path('tracked.txt').write_text('changed') +sys.stdout.buffer.write(raw) +sys.stderr.buffer.write(err) +raise SystemExit(rc) +''').encode() + + +def identity(path, raw): + return {'path': path, 'bytes': len(raw), 'sha256': digest(raw), + 'git_blob': hashlib.sha1(b'blob ' + str(len(raw)).encode() + b'\0' + raw).hexdigest()} + + +def run_case(case): + behavior, target, mode, success, prefix = case + with tempfile.TemporaryDirectory(prefix='cusp-protocol-') as td: + outer = Path(td) + root = outer / 'repo'; root.mkdir() + packet = root / PACKET; packet.mkdir(parents=True) + payloads = {'cusp_check.py': child_source(behavior, target, mode), + 'RESULTS.json': carriers()['baseline'], 'NOTE.md': b'synthetic protocol fixture\n'} + for name, data in payloads.items(): (packet / name).write_bytes(data) + manifest = {'files': [identity(n,b) for n,b in payloads.items()], 'mutants': list(LABELS)} + for kind in ('consumed', 'cited_only', 'consumed_unmerged'): + path = kind + '.md'; data = ('nonempty ' + kind).encode() + (root / path).write_bytes(data) + manifest[kind] = [identity(path, data)] + if behavior == 'drop_labels': manifest['mutants'] = list(LABELS[:-1]) + if behavior == 'reverse_labels': manifest['mutants'].reverse() + if behavior == 'duplicate_label': manifest['mutants'].append('M1') + (packet / 'SOURCES.json').write_text(json.dumps(manifest)) + (root / 'tracked.txt').write_text('original') + if behavior == 'packet_tamper': (packet / 'NOTE.md').write_bytes(b'wrong') + if behavior == 'extra_member': (packet / 'extra').write_text('extra') + if behavior == 'symlink': + (packet / 'NOTE.md').unlink(); (packet / 'NOTE.md').symlink_to(packet / 'RESULTS.json') + if behavior in ('consumed_tamper','cited_only_tamper','consumed_unmerged_tamper'): + (root / (behavior[:-7] + '.md')).write_text('wrong') + env = {k:v for k,v in os.environ.items() if not k.startswith('GIT_')} + env.pop('PYTHONOPTIMIZE', None) + env.update(PYTHONDONTWRITEBYTECODE='1', PYTHONPATH='', GH_TOKEN='synthetic-only', + GH_REPOSITORY='synthetic/protocol', CUSP_TRACE=str(outer / 'children.jsonl')) + for command in (['git','init','-q'], ['git','add','.'], + ['git','-c','user.name=Protocol Fixture','-c','user.email=fixture@example.invalid', + '-c','commit.gpgsign=false','-c','maintenance.auto=false','commit','-qm','synthetic sources']): + subprocess.run(command, cwd=root, env=env, check=True, capture_output=True, timeout=15) + result = subprocess.run(['bash','-c',shell()], cwd=root, env=env, capture_output=True, timeout=45) + trace = outer / 'children.jsonl' + children = [json.loads(s) for s in trace.read_text().splitlines()] if trace.exists() else [] + evidence = os.environ.get('CUSP_CASE_EVIDENCE') + if evidence: + folder = Path(evidence) / ('%s-%s-%d' % (behavior,target,mode)); folder.mkdir(parents=True,exist_ok=False) + (folder/'stdout').write_bytes(result.stdout); (folder/'stderr').write_bytes(result.stderr) + (folder/'result.json').write_text(json.dumps({'case':case,'returncode':result.returncode,'children':children})+'\n') + return case, result, children + + +def expected_prefix(label, mode): + all_children = [(m,l) for m in (0,1) for l in ('baseline',*LABELS,'M9')] + return all_children[:all_children.index((mode,label))+1] + + +class CuspWorkflowProtocolTests(unittest.TestCase): + def check_cases(self, cases): + with ThreadPoolExecutor(max_workers=4) as pool: + results = list(pool.map(run_case, cases)) + for case, cp, children in results: + behavior, target, mode, success, prefix = case + with self.subTest(behavior=behavior,target=target,mode=mode): + self.assertEqual(cp.returncode == 0, success, cp.stdout.decode(errors='replace')+cp.stderr.decode(errors='replace')) + self.assertEqual([tuple(v) for v in children], prefix) + if success: + self.assertEqual(cp.stderr, b'') + else: + errors = { + 'packet_tamper': 'packet file identity mismatch: NOTE.md', + 'extra_member': 'packet tree differs from manifest', + 'symlink': 'symlink: NOTE.md', + 'consumed_tamper': 'merged source drifted: consumed.md', + 'cited_only_tamper': 'merged source drifted: cited_only.md', + 'consumed_unmerged_tamper': 'consumed unmerged dependency differs from the pinned blob', + 'drop_labels': 'cusp mutant inventory differs', + 'reverse_labels': 'cusp mutant inventory differs', + 'duplicate_label': 'cusp mutant inventory differs', + } + if behavior in errors: + self.assertIn(errors[behavior].encode(), cp.stderr) + elif behavior == 'dirty': + self.assertIn(b'diff --git', cp.stdout) + elif target in LABELS and behavior != 'wrong_exit': + self.assertIn(('mutant '+target+' did not emit its exact ').encode(), cp.stderr) + elif target == 'M9' and behavior != 'wrong_exit': + self.assertIn(b'complete expected exit-2 diagnostic', cp.stderr) + + def test_valid_original_carriers(self): + self.check_cases([('valid','baseline',0,True,[(m,l) for m in (0,1) for l in ('baseline',*LABELS,'M9')])]) + + def test_every_mutant_rejects_unrelated_process_failure(self): + self.check_cases([(b,l,m,False,expected_prefix(l,m)) for m in (0,1) for l in LABELS + for b in ('crash','silent','stderr','wrong_exit')]) + + def test_complete_report_identity(self): + self.check_cases([(b,'M1',m,False,expected_prefix('M1',m)) for m in (0,1) + for b in ('wrong_reason','wrong_check','boolean_int','duplicate','nonfinite','trailing','extra_field','formatting')]) + + def test_baseline_and_unknown_label_contracts(self): + cases=[(b,l,m,False,expected_prefix(l,m)) for m in (0,1) + for l,behaviors in [('baseline',('silent','stderr','wrong_exit','trailing')), + ('M9',('silent','stderr','wrong_exit','wrong_reason','stdout'))] for b in behaviors] + self.check_cases(cases) + + def test_source_guards_precede_all_children(self): + self.check_cases([(b,'baseline',0,False,[]) for b in + ('packet_tamper','extra_member','symlink','consumed_tamper','cited_only_tamper','consumed_unmerged_tamper')]) + + def test_exact_mutant_inventory_before_children(self): + self.check_cases([(b,'baseline',0,False,[]) for b in ('drop_labels','reverse_labels','duplicate_label')]) + + def test_final_clean_tree_guard(self): + self.check_cases([('dirty','M9',1,False,[(m,l) for m in (0,1) for l in ('baseline',*LABELS,'M9')])]) + + def test_regression_is_wired_without_relaxed_settings(self): + text = WORKFLOW.read_text() + self.assertIn("- 'tests/test_cusp_workflow_protocol.py'",text) + for flags in ('-B -S','-B -O -S'): + self.assertIn('python '+flags+' -m unittest discover -s tests -p test_cusp_workflow_protocol.py -v',text) + self.assertIn("python-version: '3.11.16'",text) + self.assertIn('timeout-minutes: 10',text) + self.assertIn('contents: read',text) + self.assertNotIn('continue-on-error',text) + + +if __name__ == '__main__': + unittest.main()