diff --git a/harness/tests/test_codex_pm_router.py b/harness/tests/test_codex_pm_router.py index 240c36694..964838d31 100644 --- a/harness/tests/test_codex_pm_router.py +++ b/harness/tests/test_codex_pm_router.py @@ -251,6 +251,50 @@ def test_validate_compiled_package_rejects_raw_metadata_pollution(): assert "raw_metadata_pollution_detected" in result["errors"] +def test_validate_compiled_package_rejects_nonlinear_graph_without_ready_width(): + router = _load_router() + payload = { + "dag_variant": "parallel_delivery", + "requirement_ir": { + "schema_version": "solar.requirement_ir.v1", + "normalized_goal": "Improve DAG parallelism.", + "problem_statement": "Single chain underuses workers.", + "requirements": [{"id": "REQ-1"}], + "contracts": {"product": {"acceptance": ["Parallelism gate rejects narrow DAGs."]}}, + }, + "compiled_artifacts": { + "product_brief": { + "problem": "Single chain underuses workers.", + "acceptance": ["Parallelism gate rejects narrow DAGs."], + }, + "task_dag": { + "dag_variant": "parallel_delivery", + "quality_gates": {"parallelism": {"min_ready_width": 2}}, + "nodes": [ + {"id": "N1", "depends_on": [], "acceptance": ["ok"], "requirement_ids": ["REQ-1"]}, + {"id": "N2", "depends_on": ["N1"], "acceptance": ["ok"], "requirement_ids": ["REQ-1"]}, + {"id": "N3", "depends_on": ["N2"], "acceptance": ["ok"], "requirement_ids": ["REQ-1"]}, + {"id": "N4", "depends_on": ["N3"], "acceptance": ["ok"], "requirement_ids": ["REQ-1"]}, + ], + }, + "requirement_trace": { + "items": [ + { + "requirement_id": "REQ-1", + "mapped_nodes": ["N1", "N2", "N3", "N4"], + "expected_artifacts": ["task_graph.json"], + } + ] + }, + }, + } + + result = router.validate_compiled_package(payload) + + assert result["ok"] is False + assert any(error.startswith("task_graph_ready_width_below_min:") for error in result["errors"]) + + def test_codex_pm_router_cli_defaults_to_rawintent(tmp_path): env = dict(os.environ) env["SOLAR_HARNESS_DIR"] = str(ROOT) diff --git a/harness/tools/codex_pm_router.py b/harness/tools/codex_pm_router.py index 9f7f9467d..fb0471b8e 100755 --- a/harness/tools/codex_pm_router.py +++ b/harness/tools/codex_pm_router.py @@ -1725,6 +1725,30 @@ def _walk(node_id: str) -> bool: if any(_walk(node_id) for node_id in node_ids): errors.append("task_graph_cycle_detected") + graph_variant = str(graph.get("dag_variant") or payload.get("dag_variant") or "").strip().lower() + nonlinear = graph_variant not in {"", "linear", "serial", "sequential", "single", "single_node"} + quality = graph.get("quality_gates") if isinstance(graph.get("quality_gates"), dict) else {} + parallelism = quality.get("parallelism") if isinstance(quality.get("parallelism"), dict) else {} + explicit_min = parallelism.get("min_ready_width") or quality.get("min_ready_width") or graph.get("min_ready_width") + try: + min_ready_width = int(explicit_min or 0) + except Exception: + min_ready_width = 0 + if nonlinear and len(nodes) >= 4: + min_ready_width = min_ready_width or 2 + source_nodes = [ + str(node.get("id") or "") + for node in nodes + if not [dep for dep in (node.get("depends_on") or []) if str(dep) in node_id_set] + ] + if len(source_nodes) < min_ready_width: + errors.append( + "task_graph_ready_width_below_min:" + f"source_width={len(source_nodes)}" + f"