#!/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 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]]]: if user_validation_deferred(): return [ ("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"]), ("P3-connect-product-workflows", ["tools/prepare_product_business_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 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: 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())