import contextlib import hashlib import io import json from pathlib import Path import sys import tempfile import unittest from unittest.mock import patch sys.path.insert(0, str(Path(__file__).resolve().parent)) import failure_reconciliation as failures import scheduler_progress as progress import prepare_module_implementation_packet as packets import run_freecad_web_scheduler as scheduler from inspect_web_module_artifacts import inspect import test_failure_reconciliation as fixtures class ReconciliationEfficiencyTests(unittest.TestCase): setUp = fixtures.ReconciliationTests.setUp tearDown = fixtures.ReconciliationTests.tearDown save_source = fixtures.ReconciliationTests.save_source def test_inline_inspection_parse_error_is_not_a_cad_runtime_error(self): entry = {"command": "node --input-type=module -e 'inspect FreeCADWebDraftRuntimeProbe)'", "lastError": "file:///project/[eval1]:1 inspect() ) ^ SyntaxError: Unexpected token ')' at compileSourceTextModule"} self.assertEqual(failures.classify(entry)[0], "diagnostic_tool") entry["lastError"] = "file:///project/[eval1]:1 new Function(code) ^ SyntaxError: Unexpected token at new Function" self.assertEqual(failures.classify(entry)[0], "business_runtime") def test_scope_guard_is_reconciled_without_changing_raw_failures(self): self.entry.update(command="/bin/bash -lc 'git status --short'", lastExitCode=64, firstError="SCHEDULER_GIT_SCOPE_REJECTED: git status requires a pathspec") self.save_source(self.entry) before = failures.SOURCE.read_bytes() result = failures.synchronize() self.assertEqual(result["records"]["one"]["status"], "not_applicable") self.assertEqual(failures.SOURCE.read_bytes(), before) self.assertTrue(failures.summary_current()["complete"]) first_proof = result["records"]["one"]["resolution"]["evidencePath"] self.entry["count"] = 2 self.save_source(self.entry) repeated = failures.synchronize() self.assertNotEqual(repeated["records"]["one"]["resolution"]["evidencePath"], first_proof) self.assertTrue((self.root / first_proof).is_file()) def test_guard_rule_does_not_archive_mixed_commands_or_other_errors(self): entry = {"command": "git status --short && node case.mjs", "lastExitCode": 64, "firstError": "SCHEDULER_GIT_SCOPE_REJECTED"} self.assertIsNone(failures.diagnostic_disposition(entry)) entry.update(command="git status --short", lastExitCode=1) self.assertIsNone(failures.diagnostic_disposition(entry)) entry.update(lastExitCode=64, firstError="repository unavailable") self.assertIsNone(failures.diagnostic_disposition(entry)) def test_readonly_tail_after_rejected_git_is_reconciled_without_replay(self): for command in ["git status --short && ls src/WebAssembly && ls src/WebAssembly/FreeCADCoreProbe", "git diff --stat && git diff -- web/src/cam.worker.ts | sed -n '1,160p'"]: with self.subTest(command=command): self.entry.update(command=command, lastExitCode=64, firstError="SCHEDULER_GIT_SCOPE_REJECTED") self.save_source(self.entry) raw = failures.SOURCE.read_bytes() result = failures.synchronize() self.assertEqual(result["records"]["one"]["status"], "not_applicable") self.assertEqual(failures.SOURCE.read_bytes(), raw) def test_shell_side_effects_and_unknown_commands_are_never_auto_closed(self): for command in ["git status --short; cmake --build tree", "git status --short || node case.mjs", "git status --short && sed -i 's/a/b/' source.cpp", "git status --short && ls $(touch file)", "git status --short && ls `touch file`", "git status --short && ls > output", "git status --short | sed -n '1p' source", "git status --short && git diff --output=out", "git status --short && python3 case.py", "git status --short && ls\nnode case.mjs"]: with self.subTest(command=command): self.assertIsNone(failures.diagnostic_disposition({"command": command, "lastExitCode": 64, "firstError": "SCHEDULER_GIT_SCOPE_REJECTED"})) def test_attached_shell_operators_cannot_bypass_readonly_check(self): self.assertTrue(failures.guarded_inspection_chain("git status --short&&ls x")) for command in ["git status --short && ls x||node case.mjs", "git status --short && ls x;node case.mjs", "git status --short && ls x&node case.mjs", "bash -lc 'git status --short' ; node case.mjs", "git status --short && sed -n 1p -e '1w output' source"]: self.assertFalse(failures.guarded_inspection_chain(command)) def test_warm_reconciliation_keeps_valid_snapshot_but_rechecks_proof_sources(self): self.entry.update(command="sed -n 1p old.json") self.save_source(self.entry) source = self.root / "checker.py" source.write_text("version = 1\n") failures.review(["one"], "superseded", "replacement checked", [source]) before = failures.OUTPUT.read_bytes() failures.synchronize() self.assertEqual(failures.OUTPUT.read_bytes(), before) source.write_text("version = 2\n") self.assertEqual(failures.synchronize()["records"]["one"]["status"], "pending") class ReviewSchedulingTests(unittest.TestCase): def test_unresolved_review_yields_without_cooling_business_module(self): with tempfile.TemporaryDirectory() as directory, patch.object(progress, "PROGRESS_PATH", Path(directory) / "progress.json"): for cycle in range(1, 8): batch = {"batchId": f"review-{cycle}", "selected": {"module": "Draft", "tier": "failure-reconciliation"}, "records": [[f"failure:Draft:key{cycle}"]]} result = progress.record_outcome(batch, {}, {}, cycle=cycle, now=1000 + cycle) self.assertEqual(result["deferredIds"], [f"failure:Draft:key{cycle}"]) self.assertEqual(progress.cooling_modules(now=1010), {}) self.assertNotIn("Draft:new-business", progress.cooling_ids(now=1010, candidates={"Draft:new-business"})) self.assertTrue(progress.read(progress.PROGRESS_PATH)["history"]) def test_packet_excludes_already_closed_failure_records(self): with tempfile.TemporaryDirectory() as directory: root = Path(directory) batch = root / "batch.json" output = root / "packet.json" batch.write_text(json.dumps({"status": "ready", "batchId": "b", "selected": {"module": "Draft", "tier": "failure-reconciliation"}, "records": [["failure:Draft:open"], ["failure:Draft:closed"]], "failureKeys": ["open", "closed"]})) row = {"key": "open", "category": "diagnostic_tool", "modules": ["Draft"], "failureFingerprint": "one", "status": "pending", "sourceSnapshot": {}} ledger = {"records": {"open": row, "closed": {**row, "key": "closed", "status": "resolved"}}} with patch.object(packets, "BATCH_PATH", batch), patch.object(packets, "OUTPUT_PATH", output), \ patch.object(packets, "reconcile_failures", return_value=ledger), contextlib.redirect_stdout(io.StringIO()): self.assertEqual(packets.main(), 0) value = json.loads(output.read_text()) self.assertEqual(value["recordCount"], 1) self.assertEqual(value["failureReconciliation"]["records"][0]["key"], "open") def test_fully_reconciled_batch_needs_no_model_turn(self): batch = {"selected": {"tier": "failure-reconciliation"}, "failureKeys": ["one"]} with patch.object(failures, "pending_keys", return_value=[]): self.assertTrue(scheduler.batch_has_completed_evidence(batch)) with patch.object(failures, "pending_keys", return_value=["one"]): self.assertFalse(scheduler.batch_has_completed_evidence(batch)) class ArtifactInspectionTests(unittest.TestCase): def test_published_files_and_hashes_are_checked_without_loading_wasm(self): with tempfile.TemporaryDirectory() as directory: root = Path(directory) public = root / "web/public" (public / "modules").mkdir(parents=True) (public / "kernel").mkdir() artifact = {"runtimeUrl": "/kernel/probe.js", "wasmUrl": "/kernel/probe.wasm", "pthreadWorkerUrl": "/kernel/probe.worker.js", "hashes": {}} for kind, file in (("runtime", "probe.js"), ("wasm", "probe.wasm"), ("pthreadWorker", "probe.worker.js")): content = f"test {kind}".encode() (public / "kernel" / file).write_bytes(content) artifact["hashes"][kind] = hashlib.sha256(content).hexdigest() (public / "modules/manifest.json").write_text(json.dumps({"modules": {"Draft": {"workers": {"draft.worker.ts": artifact}}}})) self.assertEqual(inspect("Draft", root)["status"], "passed") (public / "kernel/probe.worker.js").unlink() self.assertEqual(inspect("Draft", root)["status"], "failed") (public / "kernel/probe.worker.js").write_text("wrong content") self.assertEqual(inspect("Draft", root)["status"], "failed") if __name__ == "__main__": unittest.main()