#!/usr/bin/env python3 """Select the next implementation-first business batch across phase boundaries.""" from __future__ import annotations import json import subprocess import sys from pathlib import Path from implementation_first import implementation_ready_ids, user_validation_deferred from record_runtime_issue import ACTIVE_STATUSES from failure_reconciliation import write as write_json ROOT = Path(__file__).resolve().parents[1] CAPABILITIES = ROOT / "docs/current/feature-capability-registry.json" ISSUES = ROOT / "docs/current/runtime-issue-ledger.json" BATCH = ROOT / "docs/current/module-batch-manifest.json" POLICY = "delivery-workflow-first-v5" def coverage_development_due(history: list[dict]) -> bool: """Reserve case authoring after three model rounds without new delivery.""" attempts = 0 for row in reversed(history): if row.get("outcome") == "provider_wait": continue if row.get("businessLane") == "development" or row.get("firstBusinessPassIds"): return False # Local evidence refresh and historical reconciliation do not reset # delivery starvation, but neither spends a model opportunity here. if row.get("tier") not in {"feature-workflow", "business-acceptance"}: continue if row.get("outcome") in {"no_progress", "source_edits_pending_outcome"} or row.get("fileChanges"): attempts += 1 if attempts >= 3: return True return False def recovery_budget_spent(history: list[dict]) -> bool: failed_turns = 0 for row in reversed(history): if row.get("outcome") == "provider_wait": continue if row.get("businessLane") == "development" or row.get("tier") != "business-acceptance": return False if row.get("outcome") == "progress": if not row.get("fileChanges"): continue # A local evidence refresh must not reset failed model recovery work. return False if row.get("outcome") in {"no_progress", "source_edits_pending_outcome"}: failed_turns += 1 if failed_turns >= 2: return True return False def continuous_coverage_due(history: list[dict]) -> bool: refreshes = 0 for row in reversed(history): if row.get("outcome") == "provider_wait": continue if row.get("businessLane") == "development" or row.get("firstBusinessPassIds"): return False if row.get("tier") == "business-acceptance": refreshes += 1 if refreshes >= 2: return True return False def load(path: Path) -> dict: try: value = json.loads(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError): return {} return value if isinstance(value, dict) else {} def feature_phase_remaining() -> int: rows = [row for row in load(CAPABILITIES).get("records", []) if isinstance(row, dict) and row.get("id")] ready = implementation_ready_ids() active_issues = { str(row.get("capabilityId")) for row in load(ISSUES).get("issues", []) if isinstance(row, dict) and str(row.get("status", "open")) in ACTIVE_STATUSES } return len(({str(row["id"]) for row in rows} - ready) | active_issues) def phase_order(previous: dict, remaining: int) -> list[tuple[str, list[str]]]: policy = load(ROOT / "docs/current/execution-policy.json") if policy.get("continuousDevelopment") is True and policy.get("completionFocus") == "business-acceptance": history = load(ROOT / "docs/current/scheduler-progress.json").get("history", []) development = ("P3-uncovered-business-development", ["tools/prepare_business_acceptance_batch.py", "--uncovered-only"]) refresh = ("P2-current-executable-acceptance", ["tools/prepare_business_acceptance_batch.py", "--covered-only"]) if policy.get("prioritizeCoverageGaps") is True: return [ ("P0-uncovered-business-development", development[1]), ("P1-first-acceptance-of-completed-gaps", refresh[1]), ("P2-remaining-implementation", ["tools/prepare_feature_capability_batch.py"]), ("P3-product-integration", ["tools/prepare_product_business_batch.py"]), ("P3-format-integration", ["tools/prepare_format_business_batch.py"]), ] return [ ("P0-authorized-priority-acceptance", ["tools/prepare_business_acceptance_batch.py", "--priority-only"]), ("P1-current-business-repair", ["tools/prepare_feature_capability_batch.py", "--runtime-only"]), *([development, refresh] if continuous_coverage_due(history) else [refresh, development]), ("P4-remaining-implementation", ["tools/prepare_feature_capability_batch.py"]), ("P4-product-integration", ["tools/prepare_product_business_batch.py"]), ("P4-format-integration", ["tools/prepare_format_business_batch.py"]), ("P5-failure-reconciliation", ["tools/prepare_failure_reconciliation_batch.py"]), ] if user_validation_deferred(): return [ ("P1-primary-workspace-missing-integration", ["tools/prepare_product_business_batch.py"]), ("P0-reported-functional-repair", ["tools/prepare_feature_capability_batch.py", "--runtime-only"]), ("P1-connect-delivery-workflows", ["tools/prepare_feature_capability_batch.py", "--delivery-only"]), ("P2-connect-all-features", ["tools/prepare_feature_capability_batch.py"]), ("P4-connect-format-workflows", ["tools/prepare_format_business_batch.py"]), ] history = load(ROOT / "docs/current/scheduler-progress.json").get("history", []) last_tier = previous.get("selected", {}).get("tier") if last_tier == "failure-reconciliation": last_tier = next((row.get("tier") for row in reversed(load(ROOT / "docs/current/scheduler-progress.json").get("history", [])) if row.get("tier") and row["tier"] != "failure-reconciliation"), None) implementation = [("P2-delivery-implementation", ["tools/prepare_feature_capability_batch.py", "--delivery-only"])] acceptance = [("P2-delivery-acceptance", ["tools/prepare_business_acceptance_batch.py", "--delivery-only"])] development = [] if previous.get("businessLane") != "development" and (recovery_budget_spent(history) or coverage_development_due(history)): command = ["tools/prepare_business_acceptance_batch.py", "--uncovered-only"] module = previous.get("selected", {}).get("module") if module: command.extend(["--exclude-module", module]) development = [("P1-uncovered-business-development", command)] return [ *development, ("P0-observed-runtime-repair", ["tools/prepare_feature_capability_batch.py", "--runtime-only"]), ("P0-executable-business-acceptance", ["tools/prepare_business_acceptance_batch.py", "--covered-only"]), *(implementation + acceptance if last_tier == "business-acceptance" else acceptance + implementation), ("P3-react-product-integration", ["tools/prepare_product_business_batch.py"]), ("P4-module-import-export-workflows", ["tools/prepare_format_business_batch.py"]), *([("P5-remaining-feature-implementation", ["tools/prepare_feature_capability_batch.py"])] if remaining else []), ("P6-remaining-business-acceptance", ["tools/prepare_business_acceptance_batch.py"]), ("P5-failure-reconciliation", ["tools/prepare_failure_reconciliation_batch.py"]), ] def acceptance_preempts(batch: dict) -> bool: if user_validation_deferred(): return False if batch.get("selected", {}).get("tier") != "feature-workflow": return False policy = load(ROOT / "docs/current/execution-policy.json") if policy.get("continuousDevelopment") is True and policy.get("completionFocus") == "business-acceptance": return batch.get("selected", {}).get("family") != "runtime-issue" from prepare_business_acceptance_batch import candidates, inventory state = inventory() if candidates(state, covered_only=True): return True history = load(ROOT / "docs/current/scheduler-progress.json").get("history", []) if coverage_development_due(history) and candidates(state, uncovered_only=True): return True if candidates(state, delivery_only=True) and batch.get("selectionScope") != "delivery": return True if batch.get("schedulingPolicy") != POLICY: return True history = [row for row in load(ROOT / "docs/current/scheduler-progress.json").get("history", []) if row.get("tier") and row["tier"] != "failure-reconciliation" and row.get("outcome") != "provider_wait"] # Bound a preserved implementation turn even when its selected IDs change. return bool(len(history) >= 2 and all(row["tier"] == "feature-workflow" for row in history[-2:]) and candidates(state)) def main() -> int: policy = load(ROOT / "docs/current/execution-policy.json") if policy.get("workScope") == "gap-repair-only": # The migration is already delivered. Old inventories and missing test # receipts must not manufacture another full-port development queue. repairs = [row for row in policy.get("confirmedGapRepairs", []) if isinstance(row, dict) and row.get("status") == "open" and all(row.get(key) for key in ("id", "module", "source", "problem"))] if not repairs: write_json(BATCH, {"schema": "freecad-web-module-function-batch/v1", "status": "no_ready_batch", "schedulingPolicy": "confirmed-gap-repair-only", "selectionScope": "confirmed-gaps", "records": [], "queue": [], "reason": "No confirmed gap queued. Reuse completed migration; do not restart historical backlog or verification."}) print(json.dumps({"status": "no_ready_batch", "reason": "confirmed gaps only; completed migration preserved"})) return 0 row = repairs[0] import hashlib product = row["module"] == "Web" and row["id"].startswith("product:") tier = "product-integration" if product else "feature-workflow" family = "gap-repair-" + str(row.get("key") or row["id"].replace(":", "-")) strategy = "module-business-first-deferred-validation" if product else "feature-first-deferred-validation" parts = [row["module"], tier, family, row["id"]] if product else [row["module"], family, row["id"]] identity = hashlib.sha256("|".join(parts).encode()).hexdigest()[:24] write_json(BATCH, {"schema": "freecad-web-module-function-batch/v1", "status": "ready", "batchId": identity, "strategy": strategy, "schedulingPolicy": "confirmed-gap-repair-only", "minimumRecords": 1, "singletonFallback": True, "selectionScope": "confirmed-gaps", "taskId": load(ROOT / "docs/current/task-state.json").get("active_task"), "selected": {"module": row["module"], "tier": tier, "family": family, "recordCount": 1}, "records": [[row["id"], row.get("command", row["id"]), "implementation_required", row["source"], row.get("line", 1)]], "confirmedGap": row, "businessWorkflow": "repair-existing-function"}) print(json.dumps({"status": "ready", "selectedGap": row["id"]})) return 0 previous = load(BATCH) remaining = feature_phase_remaining() waits = [] for phase, command in phase_order(previous, remaining): result = subprocess.run([sys.executable, *command], cwd=ROOT, text=True, capture_output=True, check=False) if result.stdout: print(result.stdout, end="") if result.stderr: print(result.stderr, end="", file=sys.stderr) if result.returncode: return result.returncode batch = load(BATCH) if batch.get("status") == "ready": batch["schedulingPolicy"] = POLICY write_json(BATCH, batch) # Runtime issues have their own priority phase. Historical review # must not replace an executable business batch at module handoff. print(json.dumps({"implementationPhase": phase, "featureRemaining": remaining}, ensure_ascii=False)) return 0 if batch.get("status") == "waiting_for_retry": waits.append(batch) elif batch.get("status") != "no_ready_batch": print(json.dumps({"status": "error", "reason": "planner returned an unknown batch state", "phase": phase})) return 2 if waits: batch = min(waits, key=lambda value: float(value["retryAt"])) batch["deferredIds"] = sorted({record_id for value in waits for record_id in value.get("deferredIds", [])}) batch["deferredRecords"] = len(batch["deferredIds"]) BATCH.write_text(json.dumps(batch, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") return 0 if __name__ == "__main__": raise SystemExit(main())