281 lines
19 KiB
Python
281 lines
19 KiB
Python
"""Acceptance must reuse ready work without starving implementation or lying about it."""
|
|
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 business_acceptance as evidence
|
|
import prepare_business_acceptance_batch as acceptance
|
|
import prepare_implementation_business_batch as planner
|
|
import run_freecad_web_scheduler as scheduler
|
|
import run_module_batch_pipeline as pipeline
|
|
import preflight_current_module_batch as preflight
|
|
import continue_web_task_cycle as continuation
|
|
|
|
|
|
class PriorityTests(unittest.TestCase):
|
|
def test_coverage_development_survives_feature_and_reconciliation_handoffs(self):
|
|
feature = {"tier": "feature-workflow", "outcome": "no_progress"}
|
|
refresh = {"tier": "business-acceptance", "outcome": "progress", "fileChanges": 0,
|
|
"firstBusinessPassIds": [], "refreshedBusinessIds": ["Web:old"]}
|
|
history = [feature, {"tier": "failure-reconciliation", "outcome": "progress"},
|
|
{**feature, "outcome": "source_edits_pending_outcome", "fileChanges": 1}, refresh, feature]
|
|
self.assertTrue(planner.coverage_development_due(history))
|
|
previous = {"selected": {"module": "Assembly", "tier": "feature-workflow"}}
|
|
with patch.object(planner, "load", return_value={"history": history}):
|
|
self.assertEqual(planner.phase_order(previous, 600)[0],
|
|
("P1-uncovered-business-development", ["tools/prepare_business_acceptance_batch.py", "--uncovered-only", "--exclude-module", "Assembly"]))
|
|
for reset in ({"businessLane": "development"}, {"firstBusinessPassIds": ["Part:new"]}):
|
|
self.assertFalse(planner.coverage_development_due([*history, reset]))
|
|
self.assertFalse(planner.coverage_development_due([feature, refresh, {"outcome": "provider_wait"}]))
|
|
|
|
def test_preserved_feature_yields_to_due_uncovered_development(self):
|
|
history = [{"tier": "feature-workflow", "outcome": "no_progress"}] * 3
|
|
state = self.state()
|
|
state["covered"]["Draft"] = set()
|
|
batch = {"schedulingPolicy": planner.POLICY, "selectionScope": "delivery", "selected": {"tier": "feature-workflow"}}
|
|
with patch.object(acceptance, "inventory", return_value=state), patch.object(planner, "load", return_value={"history": history}):
|
|
self.assertTrue(planner.acceptance_preempts(batch))
|
|
|
|
def test_missing_acceptance_cases_keep_case_authoring_context_on_resume(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
paths = {key: root / f"{key}.json" for key in ("MODULE_BATCH_PATH", "MODULE_PIPELINE_PATH", "IMPLEMENTATION_PACKET_PATH")}
|
|
paths["MODULE_BATCH_PATH"].write_text(json.dumps({"status": "ready", "batchId": "b", "selected": {"tier": "business-acceptance", "module": "Part"}}))
|
|
paths["MODULE_PIPELINE_PATH"].write_text('{}')
|
|
paths["IMPLEMENTATION_PACKET_PATH"].write_text(json.dumps({"batchId": "b", "acceptanceContract": "contract.json",
|
|
"acceptanceSetup": {"ready": False, "missingIds": ["Part:missing"], "caseScripts": ["cases.mjs"]},
|
|
"editableExcerpts": [{"path": "cases.mjs", "excerpt": "EXISTING_CASE_CODE"}]}))
|
|
with patch.multiple(scheduler, **paths):
|
|
first = scheduler.build_prompt("task", failures=[])
|
|
resumed = scheduler.build_prompt("task", failures=[], resumed=True)
|
|
self.assertIn("EXISTING_CASE_CODE", first)
|
|
self.assertIn("Part:missing", resumed)
|
|
self.assertIn("cases.mjs", resumed)
|
|
self.assertNotIn("status=exposed", resumed)
|
|
self.assertNotIn("EXISTING_CASE_CODE", resumed)
|
|
|
|
def test_uncovered_acceptance_stops_before_build_and_preserves_caches(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
(root / "cases.py").write_text("pass\n")
|
|
contract = root / "contract.json"
|
|
contract.write_text(json.dumps({"command": ["python3", "cases.py"], "coveredIds": ["Part:ready"]}))
|
|
batch = {"batchId": "b", "selected": {"module": "Part", "tier": "business-acceptance"},
|
|
"records": [["Part:ready"], ["Part:missing"]]}
|
|
packet = {"batchId": "b", "outerPipelineAvailable": True, "acceptanceContract": "contract.json",
|
|
"acceptanceSetup": {"ready": True}, "build": {"target": "probe", "tree": "build"}}
|
|
paths = {key: root / (key + ".json") for key in ("BATCH_PATH", "PACKET_PATH", "OUTPUT_PATH", "BUILD_CACHE_PATH", "PENDING_BUILD_PATH")}
|
|
paths["BATCH_PATH"].write_text(json.dumps(batch))
|
|
paths["PACKET_PATH"].write_text(json.dumps(packet))
|
|
for key in ("BUILD_CACHE_PATH", "PENDING_BUILD_PATH"):
|
|
paths[key].write_text('{"existing":"preserve"}')
|
|
with patch.multiple(pipeline, ROOT=root, **paths), patch.object(pipeline, "run_step") as run, \
|
|
patch.object(pipeline.subprocess, "run") as subprocess_run, contextlib.redirect_stdout(io.StringIO()):
|
|
self.assertEqual(pipeline.main(), 78)
|
|
run.assert_not_called()
|
|
subprocess_run.assert_not_called()
|
|
result = json.loads(paths["OUTPUT_PATH"].read_text())
|
|
self.assertEqual(result["failedStage"], "business-acceptance")
|
|
self.assertEqual(json.loads(result["steps"][0]["stdout"])["missingIds"], ["Part:missing"])
|
|
self.assertEqual(scheduler.compile_failure_steps(result, "b"), [])
|
|
for key in ("BUILD_CACHE_PATH", "PENDING_BUILD_PATH"):
|
|
self.assertEqual(paths[key].read_text(), '{"existing":"preserve"}')
|
|
with patch.object(pipeline, "ROOT", root):
|
|
contract.write_text(json.dumps({"command": ["python3", "cases.py"], "coveredIds": ["Part:ready", "Part:missing"]}))
|
|
self.assertIsNone(pipeline.acceptance_contract_gap(batch, packet))
|
|
(root / "cases.py").unlink()
|
|
self.assertIsNotNone(pipeline.acceptance_contract_gap(batch, packet))
|
|
|
|
def state(self):
|
|
rows = [{"id": "Part:save", "module": "Part", "command": "part.save", "category": "persistence"},
|
|
{"id": "Draft:format", "module": "Draft", "command": "draft.format", "category": "format"},
|
|
{"id": "Draft:other", "module": "Draft", "command": "draft.other"}]
|
|
return {"pending": rows, "rows": rows, "accepted": set(), "cooling": set(), "receipts": {},
|
|
"covered": {"Part": set(), "Draft": {"Draft:format"}}}
|
|
|
|
def test_executable_work_precedes_module_order_and_never_mixes_uncovered_ids(self):
|
|
batch = acceptance.plan(self.state())
|
|
self.assertEqual([row[0] for row in batch["records"]], ["Draft:format"])
|
|
self.assertTrue(batch["acceptanceReady"])
|
|
selected = batch["selected"]
|
|
parts = preflight.batch_identity_parts(batch, selected["module"], selected["tier"], selected["family"], [row[0] for row in batch["records"]])
|
|
self.assertEqual(batch["batchId"], hashlib.sha256("|".join(parts).encode()).hexdigest()[:24])
|
|
state = self.state()
|
|
state["cooling"] = {"Draft:format"}
|
|
self.assertEqual(acceptance.plan(state)["selected"]["module"], "Part")
|
|
self.assertEqual(acceptance.candidates(state, covered_only=True), [])
|
|
|
|
def test_workflow_groups_preserve_document_and_desktop_boundaries(self):
|
|
state = self.state()
|
|
state["covered"]["Draft"] = set()
|
|
state["pending"].extend([
|
|
{"id": "Part:import", "module": "Part", "command": "part.import", "category": "format"},
|
|
{"id": "Part:desktop:Box", "module": "Part", "command": "Box", "kind": "desktop-gui"},
|
|
])
|
|
selected = acceptance.plan(state)
|
|
self.assertEqual({row[0] for row in selected["records"]}, {"Part:save"})
|
|
self.assertEqual(selected["businessWorkflow"], "document-persistence")
|
|
self.assertTrue(any(row["family"] == "document-import-export" for row in selected["queue"]))
|
|
|
|
def test_context_queue_is_bounded_without_discarding_pending_totals(self):
|
|
state = self.state()
|
|
for index in range(20):
|
|
module = f"Module{index}"
|
|
state["pending"].append({"id": f"{module}:create", "module": module, "command": "create"})
|
|
state["covered"][module] = set()
|
|
batch = acceptance.plan(state)
|
|
self.assertEqual(len(batch["queue"]), 8)
|
|
self.assertEqual(batch["queueGroupCount"], 23)
|
|
self.assertEqual(batch["pendingBusinessCount"], 23)
|
|
|
|
def test_ready_receipt_with_missing_route_is_not_an_acceptance_candidate(self):
|
|
rows = [{"id": "Part:good", "module": "Part", "command": "good", "implementationStatus": "routed"},
|
|
{"id": "Part:gap", "module": "Part", "command": "gap", "implementationStatus": "implementation_required"}]
|
|
receipts = {row["id"]: {"status": "implementation_ready"} for row in rows}
|
|
with patch.object(acceptance, "requirements", return_value=rows), patch.object(acceptance, "accepted_ids", return_value=set()), \
|
|
patch.object(acceptance, "load_registry", return_value={"records": receipts}), patch.object(acceptance, "cooling_ids", return_value=set()), \
|
|
patch.object(acceptance, "contract_ids", return_value=set()):
|
|
self.assertEqual([row["id"] for row in acceptance.inventory()["pending"]], ["Part:good"])
|
|
|
|
def test_executable_first_and_development_alternates_with_implementation(self):
|
|
for previous, expected in (("feature-workflow", "P2-delivery-acceptance"),
|
|
("business-acceptance", "P2-delivery-implementation")):
|
|
with patch.object(planner, "load", return_value={"history": []}):
|
|
phases = planner.phase_order({"selected": {"tier": previous}}, 900)
|
|
self.assertEqual(phases[0][0], "P0-observed-runtime-repair")
|
|
self.assertEqual(phases[1][0], "P0-executable-business-acceptance")
|
|
self.assertEqual(phases[2][0], expected)
|
|
|
|
def test_two_failed_recovery_turns_reserve_new_business_despite_evidence_refresh(self):
|
|
failed = {"tier": "business-acceptance", "outcome": "source_edits_pending_outcome", "fileChanges": 1}
|
|
history = [failed, {"tier": "business-acceptance", "outcome": "progress", "fileChanges": 0}, failed]
|
|
previous = {"selected": {"module": "TechDraw", "tier": "business-acceptance"}}
|
|
with patch.object(planner, "load", return_value={"history": history}):
|
|
phases = planner.phase_order(previous, 600)
|
|
self.assertEqual(phases[0], ("P1-uncovered-business-development", ["tools/prepare_business_acceptance_batch.py", "--uncovered-only", "--exclude-module", "TechDraw"]))
|
|
self.assertIn(("P0-executable-business-acceptance", ["tools/prepare_business_acceptance_batch.py", "--covered-only"]), phases)
|
|
self.assertTrue(any(command == ["tools/prepare_feature_capability_batch.py"] for _, command in phases))
|
|
history.append({**failed, "businessLane": "development"})
|
|
self.assertFalse(planner.recovery_budget_spent(history))
|
|
|
|
def test_uncovered_lane_preserves_scope_and_selects_only_other_modules(self):
|
|
state = self.state()
|
|
original = list(state["pending"])
|
|
batch = acceptance.plan(state, uncovered_only=True, exclude_module="Part")
|
|
self.assertEqual([row[0] for row in batch["records"]], ["Draft:other"])
|
|
self.assertEqual(batch["businessLane"], "development")
|
|
self.assertFalse(batch["acceptanceReady"])
|
|
self.assertEqual(state["pending"], original)
|
|
state["cooling"] = {"Draft:other"}
|
|
with patch.object(acceptance, "retry_details", return_value={}) as retry:
|
|
batch = acceptance.plan(state, uncovered_only=True, exclude_module="Part")
|
|
self.assertEqual(batch["status"], "no_ready_batch")
|
|
retry.assert_called_once_with({"Draft:other"})
|
|
|
|
def test_provider_wait_and_one_failure_do_not_trigger_development_rotation(self):
|
|
failed = {"tier": "business-acceptance", "outcome": "no_progress"}
|
|
self.assertFalse(planner.recovery_budget_spent([failed, {"outcome": "provider_wait"}]))
|
|
self.assertFalse(planner.recovery_budget_spent([failed, failed, {"tier": "feature-workflow", "outcome": "no_progress"}]))
|
|
|
|
def test_preserved_implementation_yields_after_two_turns(self):
|
|
state = self.state()
|
|
state["covered"]["Draft"] = set()
|
|
batch = {"schedulingPolicy": planner.POLICY, "selectionScope": "delivery", "selected": {"tier": "feature-workflow"}}
|
|
history = [{"tier": "feature-workflow", "outcome": "source_edits_pending_outcome"}]
|
|
with patch.object(acceptance, "inventory", return_value=state), patch.object(planner, "load", return_value={"history": history}):
|
|
self.assertFalse(planner.acceptance_preempts(batch))
|
|
history.append(dict(history[0]))
|
|
self.assertTrue(planner.acceptance_preempts(batch))
|
|
history.append({"tier": "business-acceptance", "outcome": "progress"})
|
|
self.assertFalse(planner.acceptance_preempts(batch))
|
|
|
|
def test_executable_acceptance_can_run_before_model_and_checks_script(self):
|
|
with tempfile.TemporaryDirectory() as directory, patch.object(scheduler, "ROOT", Path(directory)):
|
|
root = Path(directory)
|
|
contract = root / "docs/business-acceptance/Draft.json"
|
|
contract.parent.mkdir(parents=True)
|
|
contract.write_text(json.dumps({"command": ["node", "cases.mjs"], "coveredIds": ["Draft:format"]}))
|
|
batch = {"selected": {"tier": "business-acceptance", "module": "Draft"}, "records": [["Draft:format"]]}
|
|
self.assertFalse(scheduler.runtime_pipeline_ready(batch))
|
|
(root / "cases.mjs").write_text("export {};\n")
|
|
self.assertTrue(scheduler.runtime_pipeline_ready(batch))
|
|
batch["records"].append(["Draft:other"])
|
|
self.assertFalse(scheduler.runtime_pipeline_ready(batch))
|
|
|
|
def test_format_string_routes_are_fingerprinted(self):
|
|
with patch.object(evidence, "module_inputs", return_value={}), patch.object(evidence, "digest", return_value="source"):
|
|
row = {"id": "business-format:Import:import:.step", "module": "Import", "route": "importStep"}
|
|
before = evidence.fingerprint(row)
|
|
row["route"] = "other"
|
|
self.assertNotEqual(before, evidence.fingerprint(row))
|
|
|
|
def test_unattempted_acceptance_survives_continuation_then_yields_after_a_turn(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
current = root / "docs/current"
|
|
current.mkdir(parents=True)
|
|
batch = acceptance.plan(self.state())
|
|
batch["generatedAt"] = "2026-09-08T00:00:00+00:00"
|
|
(current / "module-batch-manifest.json").write_text(json.dumps(batch))
|
|
state, checkpoint = current / "task-state.json", current / "continuation-state.json"
|
|
state.write_text('{"active_task":"task"}')
|
|
checkpoint.write_text('{"activeTask":"task"}')
|
|
with patch.multiple(continuation, ROOT=root, STATE=state, CHECKPOINT=checkpoint), \
|
|
patch.object(continuation, "implementation_first_enabled", return_value=True), \
|
|
patch.object(continuation, "batch_retry_details", return_value={}), \
|
|
patch.object(acceptance, "inventory", return_value=self.state()):
|
|
self.assertFalse(continuation.batch_requires_refresh())
|
|
history = [{"batchId": batch["batchId"], "tier": "business-acceptance", "outcome": "source_edits_pending_outcome", "at": 1788900000}]
|
|
(current / "scheduler-progress.json").write_text(json.dumps({"history": history}))
|
|
self.assertTrue(continuation.batch_requires_refresh())
|
|
|
|
def test_acceptance_failure_without_build_retains_recovery_stage(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
batch, packet = root / "batch.json", root / "packet.json"
|
|
batch.write_text(json.dumps({"batchId": "b", "selected": {"tier": "business-acceptance"}}))
|
|
packet.write_text(json.dumps({"batchId": "b", "outerPipelineAvailable": True}))
|
|
with patch.multiple(pipeline, BATCH_PATH=batch, PACKET_PATH=packet), \
|
|
patch.object(pipeline, "run_step", return_value={"exitCode": 1}) as run, \
|
|
patch.object(pipeline, "save") as save, patch.object(pipeline.subprocess, "run"), contextlib.redirect_stdout(io.StringIO()):
|
|
self.assertEqual(pipeline.main(), 1)
|
|
self.assertEqual(run.call_args.args[0], "business-acceptance")
|
|
self.assertEqual(save.call_args.args[0]["failedStage"], "business-acceptance")
|
|
|
|
def test_native_acceptance_builds_and_syncs_before_cases_without_rewriting_receipts(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
tree = root / "build"
|
|
tree.mkdir()
|
|
(tree / "probe.js").write_text("runtime")
|
|
(tree / "probe.wasm").write_text("wasm")
|
|
(root / "source.cpp").write_text("native")
|
|
paths = {key: root / f"{key}.json" for key in ("BATCH_PATH", "PACKET_PATH", "OUTPUT_PATH", "BUILD_CACHE_PATH", "PENDING_BUILD_PATH")}
|
|
paths["BATCH_PATH"].write_text(json.dumps({"batchId": "b", "selected": {"tier": "business-acceptance", "module": "Part"}, "records": [["Part:create"]]}))
|
|
paths["PACKET_PATH"].write_text(json.dumps({"batchId": "b", "module": "Part", "outerPipelineAvailable": True,
|
|
"build": {"target": "probe", "tree": "build", "runtime": "build/probe.js", "wasm": "build/probe.wasm"},
|
|
"supportFiles": [{"path": "source.cpp", "sha256": "native"}]}))
|
|
calls = []
|
|
def run(name, command, environment, timeout):
|
|
calls.append(name)
|
|
return {"name": name, "command": command, "exitCode": 0, "seconds": 0.01}
|
|
with patch.multiple(pipeline, ROOT=root, **paths), patch.object(pipeline, "run_step", side_effect=run), \
|
|
patch.object(pipeline, "record_implementation_batch", side_effect=AssertionError("reuse prior receipt")), contextlib.redirect_stdout(io.StringIO()):
|
|
self.assertEqual(pipeline.main(), 0)
|
|
self.assertLess(calls.index("build"), calls.index("artifact-sync"))
|
|
self.assertLess(calls.index("artifact-sync"), calls.index("business-acceptance"))
|
|
result = json.loads(paths["OUTPUT_PATH"].read_text())
|
|
self.assertEqual(result["status"], "pass")
|
|
self.assertFalse(result["validationDeferred"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|