diff --git a/Makefile b/Makefile index 5ecf3f11..ca484928 100644 --- a/Makefile +++ b/Makefile @@ -200,6 +200,9 @@ REPAIRED_CANDIDATE_PROMOTION_HANDOFF_SESSION_ID_ARG := $(if $(strip $(REPAIRED_C REPAIRED_CANDIDATE_PROMOTION_HANDOFF_OPERATOR_REF ?= local_operator:unattributed REPAIRED_CANDIDATE_PROMOTION_HANDOFF_STRICT ?= REPAIRED_CANDIDATE_PROMOTION_HANDOFF_STRICT_ARG := $(if $(filter 1 true yes,$(strip $(REPAIRED_CANDIDATE_PROMOTION_HANDOFF_STRICT))),--strict,) +# A scoped product flow normally overrides producer outputs. A caller can instead +# supply a direct repaired-handoff input; command-line and environment values win. +product_workspace_repaired_handoff_input = $(if $(filter file,$(origin $(1))),$($(2)),$($(1))) IDEA_TO_SPEC_REPAIR_SESSION_ACTIVE_CANDIDATE ?= runs/active_idea_to_spec_candidate.json IDEA_TO_SPEC_REPAIR_SESSION_CLARIFICATION_REQUESTS ?= runs/idea_to_spec_clarification_requests.json IDEA_TO_SPEC_REPAIR_SESSION_CLARIFICATION_ANSWERS ?= runs/idea_to_spec_clarification_answers.json @@ -1170,8 +1173,32 @@ product-workspace-decision-backed-repair-chain: .PHONY: product-workspace-repaired-promotion-handoff product-workspace-repaired-promotion-handoff: - @$(MAKE) repaired-candidate-promotion-handoff REPAIRED_CANDIDATE_PROMOTION_HANDOFF_INTAKE="$(IDEA_EVENT_STORMING_INTAKE_OUTPUT)" REPAIRED_CANDIDATE_PROMOTION_HANDOFF_CLARIFICATION_REQUESTS="$(IDEA_TO_SPEC_CLARIFICATION_OUTPUT)" REPAIRED_CANDIDATE_PROMOTION_HANDOFF_CLARIFICATION_ANSWERS="$(IDEA_TO_SPEC_CLARIFICATION_ANSWERS_OUTPUT)" REPAIRED_CANDIDATE_PROMOTION_HANDOFF_ONTOLOGY_DECISIONS="$(PRODUCT_ONTOLOGY_GAP_REVIEW_DECISIONS_OUTPUT)" REPAIRED_CANDIDATE_PROMOTION_HANDOFF_RERUN_INPUT="$(IDEA_TO_SPEC_ANSWER_RERUN_INPUT_OUTPUT)" REPAIRED_CANDIDATE_PROMOTION_HANDOFF_RERUN_PREVIEW="$(IDEA_TO_SPEC_RERUN_PREVIEW_OUTPUT)" REPAIRED_CANDIDATE_PROMOTION_HANDOFF_RERUN_MATERIALIZATION="$(IDEA_TO_SPEC_RERUN_MATERIALIZATION_OUTPUT)" - @$(MAKE) product-workspace-idea-maturity + @$(MAKE) repaired-candidate-promotion-handoff \ + REPAIRED_CANDIDATE_PROMOTION_HANDOFF_INTAKE="$(call product_workspace_repaired_handoff_input,REPAIRED_CANDIDATE_PROMOTION_HANDOFF_INTAKE,IDEA_EVENT_STORMING_INTAKE_OUTPUT)" \ + REPAIRED_CANDIDATE_PROMOTION_HANDOFF_CLARIFICATION_REQUESTS="$(call product_workspace_repaired_handoff_input,REPAIRED_CANDIDATE_PROMOTION_HANDOFF_CLARIFICATION_REQUESTS,IDEA_TO_SPEC_CLARIFICATION_OUTPUT)" \ + REPAIRED_CANDIDATE_PROMOTION_HANDOFF_CLARIFICATION_ANSWERS="$(call product_workspace_repaired_handoff_input,REPAIRED_CANDIDATE_PROMOTION_HANDOFF_CLARIFICATION_ANSWERS,IDEA_TO_SPEC_CLARIFICATION_ANSWERS_OUTPUT)" \ + REPAIRED_CANDIDATE_PROMOTION_HANDOFF_ONTOLOGY_DECISIONS="$(call product_workspace_repaired_handoff_input,REPAIRED_CANDIDATE_PROMOTION_HANDOFF_ONTOLOGY_DECISIONS,PRODUCT_ONTOLOGY_GAP_REVIEW_DECISIONS_OUTPUT)" \ + REPAIRED_CANDIDATE_PROMOTION_HANDOFF_RERUN_INPUT="$(call product_workspace_repaired_handoff_input,REPAIRED_CANDIDATE_PROMOTION_HANDOFF_RERUN_INPUT,IDEA_TO_SPEC_ANSWER_RERUN_INPUT_OUTPUT)" \ + REPAIRED_CANDIDATE_PROMOTION_HANDOFF_RERUN_PREVIEW="$(call product_workspace_repaired_handoff_input,REPAIRED_CANDIDATE_PROMOTION_HANDOFF_RERUN_PREVIEW,IDEA_TO_SPEC_RERUN_PREVIEW_OUTPUT)" \ + REPAIRED_CANDIDATE_PROMOTION_HANDOFF_RERUN_MATERIALIZATION="$(call product_workspace_repaired_handoff_input,REPAIRED_CANDIDATE_PROMOTION_HANDOFF_RERUN_MATERIALIZATION,IDEA_TO_SPEC_RERUN_MATERIALIZATION_OUTPUT)" + @$(MAKE) product-workspace-idea-maturity \ + IDEA_MATURITY_METRICS_INTAKE="$(call product_workspace_repaired_handoff_input,REPAIRED_CANDIDATE_PROMOTION_HANDOFF_INTAKE,IDEA_EVENT_STORMING_INTAKE_OUTPUT)" \ + IDEA_MATURITY_METRICS_CANDIDATE_GRAPH="$(CANDIDATE_SPEC_GRAPH_OUTPUT)" \ + IDEA_MATURITY_METRICS_PRE_SIB="$(PRE_SIB_COHERENCE_OUTPUT)" \ + IDEA_MATURITY_METRICS_CLARIFICATION_REQUESTS="$(call product_workspace_repaired_handoff_input,REPAIRED_CANDIDATE_PROMOTION_HANDOFF_CLARIFICATION_REQUESTS,IDEA_TO_SPEC_CLARIFICATION_OUTPUT)" \ + IDEA_MATURITY_METRICS_CLARIFICATION_ANSWERS="$(call product_workspace_repaired_handoff_input,REPAIRED_CANDIDATE_PROMOTION_HANDOFF_CLARIFICATION_ANSWERS,IDEA_TO_SPEC_CLARIFICATION_ANSWERS_OUTPUT)" \ + IDEA_MATURITY_METRICS_ONTOLOGY_DECISIONS="$(call product_workspace_repaired_handoff_input,REPAIRED_CANDIDATE_PROMOTION_HANDOFF_ONTOLOGY_DECISIONS,PRODUCT_ONTOLOGY_GAP_REVIEW_DECISIONS_OUTPUT)" \ + IDEA_MATURITY_METRICS_RERUN_INPUT="$(call product_workspace_repaired_handoff_input,REPAIRED_CANDIDATE_PROMOTION_HANDOFF_RERUN_INPUT,IDEA_TO_SPEC_ANSWER_RERUN_INPUT_OUTPUT)" \ + IDEA_MATURITY_METRICS_RERUN_PREVIEW="$(call product_workspace_repaired_handoff_input,REPAIRED_CANDIDATE_PROMOTION_HANDOFF_RERUN_PREVIEW,IDEA_TO_SPEC_RERUN_PREVIEW_OUTPUT)" \ + IDEA_MATURITY_METRICS_RERUN_MATERIALIZATION="$(call product_workspace_repaired_handoff_input,REPAIRED_CANDIDATE_PROMOTION_HANDOFF_RERUN_MATERIALIZATION,IDEA_TO_SPEC_RERUN_MATERIALIZATION_OUTPUT)" \ + IDEA_MATURITY_METRICS_PROMOTION_GATE="$(IDEA_TO_SPEC_PROMOTION_GATE_OUTPUT)" \ + IDEA_MATURITY_METRICS_REPAIR_SESSION="$(IDEA_TO_SPEC_REPAIR_SESSION_OUTPUT)" \ + IDEA_MATURITY_METRICS_REPAIRED_HANDOFF="$(REPAIRED_CANDIDATE_PROMOTION_HANDOFF_OUTPUT)" \ + IDEA_MATURITY_METRICS_REPAIRED_CANDIDATE_GRAPH="$(REPAIRED_CANDIDATE_PROMOTION_HANDOFF_CANDIDATE_GRAPH_OUTPUT)" \ + IDEA_MATURITY_METRICS_REPAIRED_PRE_SIB="$(REPAIRED_CANDIDATE_PROMOTION_HANDOFF_PRE_SIB_OUTPUT)" \ + IDEA_MATURITY_METRICS_REPAIRED_ACTIVE_CANDIDATE="$(REPAIRED_CANDIDATE_PROMOTION_HANDOFF_ACTIVE_CANDIDATE_OUTPUT)" \ + IDEA_MATURITY_METRICS_REPAIRED_PROMOTION_GATE="$(REPAIRED_CANDIDATE_PROMOTION_HANDOFF_PROMOTION_GATE_OUTPUT)" \ + IDEA_MATURITY_METRICS_REPAIRED_REPAIR_SESSION="$(REPAIRED_CANDIDATE_PROMOTION_HANDOFF_REPAIR_SESSION_OUTPUT)" .PHONY: metrics-delivery metrics-delivery: diff --git a/tests/test_idea_maturity_metrics_report.py b/tests/test_idea_maturity_metrics_report.py index cc0a13ab..8599bd39 100644 --- a/tests/test_idea_maturity_metrics_report.py +++ b/tests/test_idea_maturity_metrics_report.py @@ -1139,9 +1139,9 @@ def test_idea_maturity_metrics_validate_make_target_invokes_metrics_cli( assert validation["summary"]["status"] == "ok" -def _make_dry_run_target(target: str) -> str: +def _make_dry_run_target(target: str, *variables: str) -> str: result = subprocess.run( - ["make", "-n", target], + ["make", "-n", target, *variables], cwd=ROOT, check=False, capture_output=True, @@ -1269,6 +1269,58 @@ def test_repaired_promotion_handoff_emits_validated_idea_maturity() -> None: assert handoff_index < metrics_index < validation_index +def test_repaired_promotion_handoff_preserves_explicit_scoped_inputs() -> None: + output = _make_dry_run_target( + "product-workspace-repaired-promotion-handoff", + "REPAIRED_CANDIDATE_PROMOTION_HANDOFF_INTAKE=runs/hosted-operation-canary/idea_event_storming_intake.json", + "REPAIRED_CANDIDATE_PROMOTION_HANDOFF_CLARIFICATION_REQUESTS=runs/hosted-operation-canary/idea_to_spec_clarification_requests.json", + "REPAIRED_CANDIDATE_PROMOTION_HANDOFF_CLARIFICATION_ANSWERS=runs/hosted-operation-canary/idea_to_spec_clarification_answers.json", + "REPAIRED_CANDIDATE_PROMOTION_HANDOFF_ONTOLOGY_DECISIONS=runs/hosted-operation-canary/product_ontology_gap_review_decisions.json", + "REPAIRED_CANDIDATE_PROMOTION_HANDOFF_RERUN_INPUT=runs/hosted-operation-canary/idea_to_spec_answer_rerun_input.json", + "REPAIRED_CANDIDATE_PROMOTION_HANDOFF_RERUN_PREVIEW=runs/hosted-operation-canary/idea_to_spec_rerun_preview.json", + "REPAIRED_CANDIDATE_PROMOTION_HANDOFF_RERUN_MATERIALIZATION=runs/hosted-operation-canary/idea_to_spec_rerun_materialization.json", + "REPAIRED_CANDIDATE_PROMOTION_HANDOFF_OUTPUT=runs/hosted-operation-canary/repaired_candidate_promotion_handoff_report.json", + ) + + assert '--intake "runs/hosted-operation-canary/idea_event_storming_intake.json"' in output + assert ( + "--clarification-requests " + '"runs/hosted-operation-canary/idea_to_spec_clarification_requests.json"' in output + ) + assert ( + "--rerun-materialization " + '"runs/hosted-operation-canary/idea_to_spec_rerun_materialization.json"' in output + ) + assert ( + 'IDEA_MATURITY_METRICS_REPAIRED_HANDOFF="runs/hosted-operation-canary/repaired_candidate_promotion_handoff_report.json"' + in output + ) + + +def test_repaired_promotion_handoff_threads_scoped_producer_outputs() -> None: + output = _make_dry_run_target( + "product-workspace-repaired-promotion-handoff", + "IDEA_EVENT_STORMING_INTAKE_OUTPUT=runs/hosted-operation-canary/idea_event_storming_intake.json", + "IDEA_TO_SPEC_CLARIFICATION_OUTPUT=runs/hosted-operation-canary/idea_to_spec_clarification_requests.json", + "IDEA_TO_SPEC_CLARIFICATION_ANSWERS_OUTPUT=runs/hosted-operation-canary/idea_to_spec_clarification_answers.json", + "PRODUCT_ONTOLOGY_GAP_REVIEW_DECISIONS_OUTPUT=runs/hosted-operation-canary/product_ontology_gap_review_decisions.json", + "IDEA_TO_SPEC_ANSWER_RERUN_INPUT_OUTPUT=runs/hosted-operation-canary/idea_to_spec_answer_rerun_input.json", + "IDEA_TO_SPEC_RERUN_PREVIEW_OUTPUT=runs/hosted-operation-canary/idea_to_spec_rerun_preview.json", + "IDEA_TO_SPEC_RERUN_MATERIALIZATION_OUTPUT=runs/hosted-operation-canary/idea_to_spec_rerun_materialization.json", + ) + + assert '--intake "runs/hosted-operation-canary/idea_event_storming_intake.json"' in output + assert ( + "--rerun-materialization " + '"runs/hosted-operation-canary/idea_to_spec_rerun_materialization.json"' in output + ) + assert ( + 'IDEA_MATURITY_METRICS_RERUN_MATERIALIZATION="runs/hosted-operation-canary/idea_to_spec_rerun_materialization.json"' + in output + ) + assert '--repaired-handoff "runs/repaired_candidate_promotion_handoff_report.json"' in output + + def test_idea_maturity_metrics_report_counts_materialized_request_once( tmp_path: Path, ) -> None: