Files
FreeCADWasmDev/tools/prepare_implementation_business_batch.py

241 lines
13 KiB
Python

#!/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())