diff --git a/.github/workflows/monthly_optimization_planner.yml b/.github/workflows/monthly_optimization_planner.yml index 8e93fd9..1dca1a8 100644 --- a/.github/workflows/monthly_optimization_planner.yml +++ b/.github/workflows/monthly_optimization_planner.yml @@ -6,13 +6,6 @@ name: Monthly Optimization Planner upstream_run_id: description: "AI review run id from CryptoSnapshotPipelines" required: true - downstream_run_id: - description: "AI review run id from BinancePlatform" - required: true - downstream_repo: - description: "Downstream execution repo" - required: true - default: "QuantStrategyLab/BinancePlatform" jobs: planner: @@ -50,7 +43,6 @@ jobs: owner: ${{ github.repository_owner }} repositories: | CryptoSnapshotPipelines - BinancePlatform CryptoStrategies permission-actions: write permission-issues: write @@ -81,27 +73,16 @@ jobs: --repo "${GITHUB_REPOSITORY}" \ --dir data/input/upstream - - name: Download downstream AI review artifact - env: - GH_TOKEN: ${{ steps.cross_repo_token.outputs.token }} - run: | - mkdir -p data/input/downstream - gh run download "${{ inputs.downstream_run_id }}" \ - --repo "${{ inputs.downstream_repo }}" \ - --dir data/input/downstream - - name: Resolve downloaded artifact paths id: artifact_paths run: | UPSTREAM_DIR=$(find data/input/upstream -mindepth 1 -maxdepth 1 -type d | head -1) - DOWNSTREAM_DIR=$(find data/input/downstream -mindepth 1 -maxdepth 1 -type d | head -1) - if [ -z "${UPSTREAM_DIR}" ] || [ -z "${DOWNSTREAM_DIR}" ]; then - echo "Failed to resolve downloaded artifact directories" >&2 + if [ -z "${UPSTREAM_DIR}" ]; then + echo "Failed to resolve downloaded upstream artifact directory" >&2 exit 1 fi { echo "upstream_dir=${UPSTREAM_DIR}" - echo "downstream_dir=${DOWNSTREAM_DIR}" } >> "${GITHUB_OUTPUT}" - name: Prepare upstream review payload @@ -114,21 +95,10 @@ jobs: --run-url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${{ inputs.upstream_run_id }}" \ --output-file data/output/prepared/upstream_review_payload.json - - name: Prepare downstream review payload - run: | - python3 scripts/build_ai_review_payload.py \ - --source-repo "${{ inputs.downstream_repo }}" \ - --review-kind execution_runtime \ - --issue-context-file "${{ steps.artifact_paths.outputs.downstream_dir }}/issue_context.json" \ - --secondary-review-file "${{ steps.artifact_paths.outputs.downstream_dir }}/secondary_review.json" \ - --run-url "${GITHUB_SERVER_URL}/${{ inputs.downstream_repo }}/actions/runs/${{ inputs.downstream_run_id }}" \ - --output-file data/output/prepared/downstream_review_payload.json - - name: Build monthly optimization plan run: | python3 scripts/build_monthly_optimization_plan.py \ --upstream-review-file data/output/prepared/upstream_review_payload.json \ - --downstream-review-file data/output/prepared/downstream_review_payload.json \ --output-dir data/output/monthly_optimization - name: Append optimization summary @@ -174,18 +144,6 @@ jobs: env: GITHUB_TOKEN: ${{ steps.cross_repo_token.outputs.token }} - - name: Fan out BinancePlatform task issue - run: | - python3 scripts/fanout_monthly_optimization_tasks.py \ - --plan-file data/output/monthly_optimization/optimization_plan.json \ - --owner-repo BinancePlatform \ - --repo "${{ inputs.downstream_repo }}" \ - --planner-issue-url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/issues/${{ steps.optimization_issue.outputs.issue_number }}" \ - --output-file data/output/monthly_optimization/fanout/binance_platform.json \ - --allow-permission-skip - env: - GITHUB_TOKEN: ${{ steps.cross_repo_token.outputs.token }} - - name: Append fanout summary run: | python3 - <<'PY' >> "$GITHUB_STEP_SUMMARY" @@ -235,63 +193,8 @@ jobs: gh workflow run experiment_validation.yml \ -f issue_number="${{ steps.upstream_experiment_target.outputs.issue_number }}" - - name: Resolve downstream experiment validation target - id: downstream_experiment_target - run: | - python3 - <<'PY' - import json - import os - from pathlib import Path - - fanout = json.loads( - Path("data/output/monthly_optimization/fanout/binance_platform.json").read_text(encoding="utf-8") - ) - plan = json.loads( - Path("data/output/monthly_optimization/optimization_plan.json").read_text(encoding="utf-8") - ) - actions = plan.get("repo_action_summary", {}).get("BinancePlatform", {}).get("actions", []) - should_dispatch = bool(fanout.get("issue_number")) and fanout.get("status") in {"created", "updated"} and any( - action.get("experiment_only") for action in actions - ) - with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: - print(f"should_dispatch={'true' if should_dispatch else 'false'}", file=output) - print(f"issue_number={fanout.get('issue_number') or ''}", file=output) - PY - - - name: Best-effort label BinancePlatform issue for experiment validation - if: steps.downstream_experiment_target.outputs.should_dispatch == 'true' - env: - GH_TOKEN: ${{ steps.cross_repo_token.outputs.token }} - TARGET_REPO: ${{ inputs.downstream_repo }} - ISSUE_NUMBER: ${{ steps.downstream_experiment_target.outputs.issue_number }} - run: | - set +e - gh label create experiment-validation --repo "$TARGET_REPO" --color 1D76DB --description "Trigger experiment validation for monthly optimization tasks" --force - label_status=$? - gh issue edit "$ISSUE_NUMBER" --repo "$TARGET_REPO" --add-label experiment-validation - issue_status=$? - set -e - if [ "$label_status" -ne 0 ] || [ "$issue_status" -ne 0 ]; then - echo "Downstream experiment-validation label update skipped for $TARGET_REPO#$ISSUE_NUMBER." >> "$GITHUB_STEP_SUMMARY" - fi - - - name: Dispatch BinancePlatform experiment validation - if: steps.downstream_experiment_target.outputs.should_dispatch == 'true' - env: - GH_TOKEN: ${{ steps.cross_repo_token.outputs.token }} - run: | - set +e - gh workflow run experiment_validation.yml \ - -R "${{ inputs.downstream_repo }}" \ - -f issue_number="${{ steps.downstream_experiment_target.outputs.issue_number }}" - status=$? - set -e - if [ "$status" -ne 0 ]; then - echo "Downstream experiment validation dispatch skipped: ${{ steps.cross_repo_token.outputs.source }} token likely needs Actions write on ${{ inputs.downstream_repo }}." >> "$GITHUB_STEP_SUMMARY" - fi - - name: Upload planner artifact uses: actions/upload-artifact@v7 with: - name: monthly-optimization-plan-${{ inputs.upstream_run_id }}-${{ inputs.downstream_run_id }} + name: monthly-optimization-plan-${{ inputs.upstream_run_id }} path: data/output/monthly_optimization/ diff --git a/scripts/build_ai_review_payload.py b/scripts/build_ai_review_payload.py index d61e032..eb37aca 100644 --- a/scripts/build_ai_review_payload.py +++ b/scripts/build_ai_review_payload.py @@ -9,7 +9,6 @@ SCHEMA_VERSION = "2026-04-02" REPO_ROLE_BY_KIND = { "upstream_selector": "upstream_selector_review", - "execution_runtime": "execution_runtime_review", } diff --git a/scripts/build_monthly_optimization_plan.py b/scripts/build_monthly_optimization_plan.py index 3dbd330..9397275 100644 --- a/scripts/build_monthly_optimization_plan.py +++ b/scripts/build_monthly_optimization_plan.py @@ -10,7 +10,7 @@ RISK_ORDER = {"low": 0, "medium": 1, "high": 2} SCHEMA_VERSION = "2026-04-02" -REPO_ORDER = ["CryptoSnapshotPipelines", "CryptoStrategies", "BinancePlatform"] +REPO_ORDER = ["CryptoSnapshotPipelines", "CryptoStrategies"] MANUAL_REVIEW_PREFIXES = ( "check ", "review ", @@ -30,26 +30,6 @@ def _combined_action_text(action: dict[str, Any]) -> str: def _resolve_owner_repo(source_review: dict[str, Any], action: dict[str, Any]) -> str: text = _combined_action_text(action) - if any( - marker in text - for marker in ( - "monthly report", - "cash-flow", - "cash flow", - "withdrawal", - "deposit", - "realized pnl", - "unrealized pnl", - "open positions", - "no-trade", - "gating", - "free usdt", - "dca", - "rotation", - ) - ): - return "BinancePlatform" - if any( marker in text for marker in ( @@ -127,15 +107,24 @@ def normalize_action(source_review: dict[str, Any], action: dict[str, Any]) -> d } -def build_plan(upstream_review: dict[str, Any], downstream_review: dict[str, Any]) -> dict[str, Any]: - source_reviews = [upstream_review, downstream_review] +def build_plan(*source_reviews: dict[str, Any]) -> dict[str, Any]: + if not source_reviews: + raise ValueError("at least one source review is required") + source_review_list = list(source_reviews) + normalized_actions = [ normalize_action(review, action) - for review in source_reviews + for review in source_review_list for action in review.get("recommended_actions", []) ] + in_scope_actions = [ + action for action in normalized_actions if action["owner_repo"] in REPO_ORDER + ] + out_of_scope_actions = [ + action for action in normalized_actions if action["owner_repo"] not in REPO_ORDER + ] repo_groups: dict[str, list[dict[str, Any]]] = defaultdict(list) - for action in normalized_actions: + for action in in_scope_actions: repo_groups[action["owner_repo"]].append(action) repo_action_summary = { @@ -148,30 +137,31 @@ def build_plan(upstream_review: dict[str, Any], downstream_review: dict[str, Any if repo_groups.get(repo) } - safe_auto_pr_candidates = [action for action in normalized_actions if action["auto_pr_safe"] and action["risk_level"] == "low"] - experiment_candidates = [action for action in normalized_actions if action["experiment_only"]] + safe_auto_pr_candidates = [action for action in in_scope_actions if action["auto_pr_safe"] and action["risk_level"] == "low"] + experiment_candidates = [action for action in in_scope_actions if action["experiment_only"]] human_review_required = [ - action for action in normalized_actions if (not action["auto_pr_safe"]) or action["risk_level"] != "low" + action for action in in_scope_actions if (not action["auto_pr_safe"]) or action["risk_level"] != "low" ] operator_focus = [ f"{review['source_repo']}: {review['summary']}" - for review in source_reviews + for review in source_review_list ] highest_review_risk = highest_risk([ - {"risk_level": upstream_review["risk_level"]}, - {"risk_level": downstream_review["risk_level"]}, + {"risk_level": review["risk_level"]} + for review in source_review_list ]) return { "schema_version": SCHEMA_VERSION, "generated_at": datetime.now(UTC).isoformat().replace("+00:00", "Z"), - "source_reviews": source_reviews, + "source_reviews": source_review_list, "highest_review_risk": highest_review_risk, "repo_action_summary": repo_action_summary, "safe_auto_pr_candidates": sort_actions(safe_auto_pr_candidates), "experiment_candidates": sort_actions(experiment_candidates), "human_review_required": sort_actions(human_review_required), + "out_of_scope_actions": sort_actions(out_of_scope_actions), "operator_focus": operator_focus, } @@ -184,6 +174,7 @@ def render_summary_markdown(plan: dict[str, Any]) -> str: f"- Safe auto-PR candidates: `{len(plan['safe_auto_pr_candidates'])}`", f"- Experiment candidates: `{len(plan['experiment_candidates'])}`", f"- Human review required: `{len(plan['human_review_required'])}`", + f"- Out-of-scope downstream actions: `{len(plan.get('out_of_scope_actions', []))}`", "", "## Source Reviews", ] @@ -218,24 +209,33 @@ def render_summary_markdown(plan: dict[str, Any]) -> str: lines.extend(["", "## Operator Focus"]) lines.extend(f"- {item}" for item in plan["operator_focus"]) + if plan.get("out_of_scope_actions"): + lines.extend(["", "## Out-of-Scope Actions"]) + for action in plan["out_of_scope_actions"]: + lines.append( + f"- `{action['risk_level']}` {action['owner_repo']}: {action['title']} " + f"(from {action['source_repo']} #{action['source_issue_number']})" + ) + return "\n".join(lines).strip() + "\n" def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( - description="Build the monthly optimization plan by combining upstream and downstream AI review payloads.", + description="Build the monthly optimization plan from upstream selector AI review payloads.", ) parser.add_argument("--upstream-review-file", required=True, type=Path) - parser.add_argument("--downstream-review-file", required=True, type=Path) + parser.add_argument("--downstream-review-file", type=Path) parser.add_argument("--output-dir", required=True, type=Path) return parser.parse_args() def main() -> int: args = parse_args() - upstream_review = json.loads(args.upstream_review_file.read_text(encoding="utf-8")) - downstream_review = json.loads(args.downstream_review_file.read_text(encoding="utf-8")) - plan = build_plan(upstream_review, downstream_review) + source_reviews = [json.loads(args.upstream_review_file.read_text(encoding="utf-8"))] + if args.downstream_review_file: + source_reviews.append(json.loads(args.downstream_review_file.read_text(encoding="utf-8"))) + plan = build_plan(*source_reviews) args.output_dir.mkdir(parents=True, exist_ok=True) (args.output_dir / "optimization_plan.json").write_text( json.dumps(plan, ensure_ascii=False, indent=2) + "\n", diff --git a/scripts/prepare_auto_optimization_pr.py b/scripts/prepare_auto_optimization_pr.py index 970535c..754ce07 100644 --- a/scripts/prepare_auto_optimization_pr.py +++ b/scripts/prepare_auto_optimization_pr.py @@ -48,26 +48,6 @@ "walk-forward", "walk forward", ), - "BinancePlatform": ( - "dca", - "rotation", - "eligibility gate", - "free usdt", - "cash flow", - "withdrawal", - "deposit", - "open position", - "execution", - "live trading", - "threshold", - "circuit breaker", - "capital threshold", - "allocation", - "sizing", - "liquidity", - "spread", - "adv", - ), "CryptoStrategies": ( "strategy", "signal", @@ -85,22 +65,6 @@ r"^src/", r"^config/", ), - "BinancePlatform": ( - r"^application/", - r"^infra/", - r"^strategy/", - r"^entrypoints/", - r"^main\.py$", - r"^runtime_support\.py$", - r"^live_services\.py$", - r"^degraded_mode_support\.py$", - r"^market_snapshot_support\.py$", - r"^trade_state_support\.py$", - r"^trend_pool_support\.py$", - r"^strategy_core\.py$", - r"^strategy_loader\.py$", - r"^strategy_registry\.py$", - ), "CryptoStrategies": ( r"^src/", ), @@ -182,17 +146,6 @@ def _is_completed_low_risk_task(action: dict[str, Any], repo_root: Path) -> bool and "deterministic tie-break" in runbook ) - if repo_name == "BinancePlatform" and ( - "zero-trade diagnostics" in title - or "diagnostic reporting for no-trade months" in title - or "no-trade months" in title - ): - monthly_report = _read_text(repo_root / "scripts" / "run_monthly_report_bundle.py") - return ( - "## Zero-Trade Diagnostics" in monthly_report - and "by_category_and_gate" in monthly_report - ) - return False diff --git a/scripts/run_openai_secondary_review.py b/scripts/run_openai_secondary_review.py index 59443f4..c0284b3 100644 --- a/scripts/run_openai_secondary_review.py +++ b/scripts/run_openai_secondary_review.py @@ -11,7 +11,7 @@ OPENAI_API_URL = "https://api.openai.com/v1/chat/completions" -SUPPORTED_REVIEW_KINDS = {"upstream_selector", "execution_runtime"} +SUPPORTED_REVIEW_KINDS = {"upstream_selector"} SECONDARY_REVIEW_SCHEMA: dict[str, Any] = { "type": "object", @@ -43,7 +43,7 @@ "title": {"type": "string"}, "owner_repo": { "type": "string", - "enum": ["CryptoSnapshotPipelines", "CryptoStrategies", "BinancePlatform"], + "enum": ["CryptoSnapshotPipelines", "CryptoStrategies"], }, "risk_level": {"type": "string", "enum": ["low", "medium", "high"]}, "auto_pr_safe": {"type": "boolean"}, @@ -94,15 +94,6 @@ def build_system_prompt(review_kind: str) -> str: "Use recommended_actions for concrete next steps, and only mark auto_pr_safe=true for low-risk " "changes like workflow, telemetry, report wording, tests, or challenger/shadow configuration." ) - if review_kind == "execution_runtime": - return ( - "You are the independent secondary reviewer for BinancePlatform, a downstream Binance Spot execution " - "engine. Review the issue body and the Claude primary review, then return only valid JSON matching " - "the provided schema. Do not simply echo Claude. Re-check whether execution health, gating/no-trade " - "reasons, degraded mode, circuit breaker behavior, and cash-flow context support the same conclusion. " - "Use recommended_actions for concrete next steps, and only mark auto_pr_safe=true for low-risk " - "changes like workflow, telemetry, report wording, tests, or diagnostics." - ) raise ValueError(f"Unsupported review kind: {review_kind}") diff --git a/tests/test_build_monthly_optimization_plan.py b/tests/test_build_monthly_optimization_plan.py index f062770..40ae4e5 100644 --- a/tests/test_build_monthly_optimization_plan.py +++ b/tests/test_build_monthly_optimization_plan.py @@ -6,7 +6,7 @@ class BuildMonthlyOptimizationPlanTests(unittest.TestCase): - def test_build_plan_groups_actions_by_owner_repo(self) -> None: + def test_build_plan_groups_in_scope_actions_by_owner_repo(self) -> None: upstream_review = { "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", "review_kind": "upstream_selector", @@ -25,34 +25,15 @@ def test_build_plan_groups_actions_by_owner_repo(self) -> None: } ], } - downstream_review = { - "source_repo": "QuantStrategyLab/BinancePlatform", - "review_kind": "execution_runtime", - "source_issue": {"number": 9, "title": "Monthly Execution Review: 2026-03", "url": "https://github.com/a/b/issues/9"}, - "risk_level": "low", - "production_recommendation": "keep_production_as_is", - "summary": "Execution looked healthy.", - "recommended_actions": [ - { - "owner_repo": "BinancePlatform", - "title": "Keep gating summary visible", - "risk_level": "low", - "auto_pr_safe": True, - "experiment_only": False, - "summary": "No change to production behavior.", - } - ], - } - plan = build_plan(upstream_review, downstream_review) + plan = build_plan(upstream_review) self.assertEqual(plan["highest_review_risk"], "medium") self.assertIn("CryptoSnapshotPipelines", plan["repo_action_summary"]) - self.assertIn("BinancePlatform", plan["repo_action_summary"]) - self.assertEqual(len(plan["safe_auto_pr_candidates"]), 2) + self.assertEqual(len(plan["safe_auto_pr_candidates"]), 1) self.assertEqual(len(plan["experiment_candidates"]), 1) - def test_build_plan_reassigns_reporting_tasks_and_downgrades_manual_checks(self) -> None: + def test_build_plan_keeps_downstream_actions_out_of_scope(self) -> None: upstream_review = { "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", "review_kind": "upstream_selector", @@ -60,44 +41,31 @@ def test_build_plan_reassigns_reporting_tasks_and_downgrades_manual_checks(self) "risk_level": "low", "production_recommendation": "keep_production_as_is", "summary": "Upstream is stable.", - "recommended_actions": [], - } - downstream_review = { - "source_repo": "QuantStrategyLab/BinancePlatform", - "review_kind": "execution_runtime", - "source_issue": {"number": 9, "title": "Monthly Execution Review: 2026-03", "url": "https://github.com/a/b/issues/9"}, - "risk_level": "medium", - "production_recommendation": "needs_attention", - "summary": "Execution needs follow-up.", "recommended_actions": [ { "owner_repo": "CryptoStrategies", - "title": "Add monthly report cash-flow attribution", + "title": "Add selector report note", "risk_level": "low", "auto_pr_safe": True, "experiment_only": False, - "summary": "Extend the monthly report to show deposits, withdrawals, realized PnL, and unrealized PnL separately.", + "summary": "Document upstream selector evidence.", }, { "owner_repo": "BinancePlatform", - "title": "Check DCA and rotation eligibility gates against current free USDT", + "title": "Check DCA and rotation eligibility gates", "risk_level": "low", "auto_pr_safe": True, "experiment_only": False, - "summary": "Verify minimum order size, reserve floor, and available balance thresholds.", + "summary": "Downstream runtime follow-up should not be fanout from this planner.", }, ], } - plan = build_plan(upstream_review, downstream_review) + plan = build_plan(upstream_review) - bp_actions = plan["repo_action_summary"]["BinancePlatform"]["actions"] - self.assertEqual([action["title"] for action in bp_actions], [ - "Check DCA and rotation eligibility gates against current free USDT", - "Add monthly report cash-flow attribution", - ]) - self.assertEqual(bp_actions[0]["auto_pr_safe"], False) - self.assertEqual(bp_actions[1]["auto_pr_safe"], True) + self.assertNotIn("BinancePlatform", plan["repo_action_summary"]) + self.assertIn("CryptoStrategies", plan["repo_action_summary"]) + self.assertEqual(plan["out_of_scope_actions"][0]["owner_repo"], "BinancePlatform") self.assertEqual(len(plan["safe_auto_pr_candidates"]), 1) def test_render_summary_markdown_mentions_source_reviews_and_repos(self) -> None: @@ -141,6 +109,40 @@ def test_render_summary_markdown_mentions_source_reviews_and_repos(self) -> None self.assertIn("Add challenger breadth check", markdown) self.assertIn("Operator Focus", markdown) + def test_render_summary_mentions_out_of_scope_actions(self) -> None: + plan = { + "highest_review_risk": "low", + "safe_auto_pr_candidates": [], + "experiment_candidates": [], + "human_review_required": [], + "source_reviews": [ + { + "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", + "risk_level": "low", + "production_recommendation": "keep_production_as_is", + "summary": "Stable.", + "source_issue": {"title": "Monthly Report Review: 2026-04-01", "url": "https://github.com/a/b/issues/11"}, + "run_url": "https://github.com/a/b/actions/runs/1", + } + ], + "repo_action_summary": {}, + "operator_focus": [], + "out_of_scope_actions": [ + { + "risk_level": "low", + "owner_repo": "BinancePlatform", + "title": "Check downstream gates", + "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", + "source_issue_number": 11, + } + ], + } + + markdown = render_summary_markdown(plan) + + self.assertIn("Out-of-Scope Actions", markdown) + self.assertIn("BinancePlatform", markdown) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_fanout_monthly_optimization_tasks.py b/tests/test_fanout_monthly_optimization_tasks.py index c8c716f..37acfdc 100644 --- a/tests/test_fanout_monthly_optimization_tasks.py +++ b/tests/test_fanout_monthly_optimization_tasks.py @@ -19,33 +19,29 @@ def setUp(self) -> None: "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", "source_issue": {"number": 11, "title": "Monthly Report Review: 2026-04-01"}, }, - { - "source_repo": "QuantStrategyLab/BinancePlatform", - "source_issue": {"number": 9, "title": "Monthly Execution Review: 2026-03"}, - }, ], "repo_action_summary": { - "BinancePlatform": { + "CryptoStrategies": { "count": 2, "highest_risk_level": "high", "actions": [ { "risk_level": "high", - "title": "Reconcile March cash flows", - "summary": "Separate withdrawals from mark-to-market moves.", - "source_repo": "QuantStrategyLab/BinancePlatform", - "source_issue_number": 9, - "source_issue_url": "https://github.com/QuantStrategyLab/BinancePlatform/issues/9", + "title": "Review selector allocation contract", + "summary": "Keep shared strategy semantics aligned with the upstream monthly pool.", + "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", + "source_issue_number": 11, + "source_issue_url": "https://github.com/QuantStrategyLab/CryptoSnapshotPipelines/issues/11", "auto_pr_safe": False, "experiment_only": False, }, { "risk_level": "low", - "title": "Add zero-trade diagnostics", - "summary": "Keep gating reasons visible in the report.", - "source_repo": "QuantStrategyLab/BinancePlatform", - "source_issue_number": 9, - "source_issue_url": "https://github.com/QuantStrategyLab/BinancePlatform/issues/9", + "title": "Add strategy contract diagnostics", + "summary": "Keep selector/runtime contract assumptions visible in tests.", + "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", + "source_issue_number": 11, + "source_issue_url": "https://github.com/QuantStrategyLab/CryptoSnapshotPipelines/issues/11", "auto_pr_safe": True, "experiment_only": False, }, @@ -56,28 +52,28 @@ def setUp(self) -> None: def test_build_marker_and_title_include_owner_repo(self) -> None: self.assertEqual( - build_marker(self.plan, "BinancePlatform"), - "", + build_marker(self.plan, "CryptoStrategies"), + "", ) self.assertEqual( - build_issue_title(self.plan, "BinancePlatform"), - "Monthly Optimization Tasks · BinancePlatform: 2026-04-01 / 2026-03", + build_issue_title(self.plan, "CryptoStrategies"), + "Monthly Optimization Tasks · CryptoStrategies: 2026-04-01", ) def test_build_issue_body_lists_repo_specific_actions_and_flags(self) -> None: body = build_issue_body( self.plan, - "BinancePlatform", + "CryptoStrategies", planner_issue_url="https://github.com/QuantStrategyLab/CryptoSnapshotPipelines/issues/20", ) - self.assertIn("# Monthly Optimization Tasks · BinancePlatform", body) + self.assertIn("# Monthly Optimization Tasks · CryptoStrategies", body) self.assertIn("Planner issue: https://github.com/QuantStrategyLab/CryptoSnapshotPipelines/issues/20", body) self.assertIn("Actions in this repo: `2`", body) self.assertIn("Highest repo risk: `high`", body) - self.assertIn("Reconcile March cash flows", body) - self.assertIn("Add zero-trade diagnostics [auto-pr-safe]", body) - self.assertIn("Source: [QuantStrategyLab/BinancePlatform #9]", body) + self.assertIn("Review selector allocation contract", body) + self.assertIn("Add strategy contract diagnostics [auto-pr-safe]", body) + self.assertIn("Source: [QuantStrategyLab/CryptoSnapshotPipelines #11]", body) def test_build_closed_issue_body_marks_repo_as_resolved(self) -> None: body = build_closed_issue_body( diff --git a/tests/test_monthly_optimization_planner_workflow_config.py b/tests/test_monthly_optimization_planner_workflow_config.py index 64c6715..dc0d56f 100644 --- a/tests/test_monthly_optimization_planner_workflow_config.py +++ b/tests/test_monthly_optimization_planner_workflow_config.py @@ -14,8 +14,6 @@ def test_planner_workflow_downloads_artifacts_posts_issue_and_fans_out_tasks(sel self.assertIn("workflow_dispatch:", workflow) self.assertIn("upstream_run_id:", workflow) - self.assertIn("downstream_run_id:", workflow) - self.assertIn("downstream_repo:", workflow) self.assertIn("actions: write", workflow) self.assertIn("CROSS_REPO_GITHUB_TOKEN", workflow) self.assertIn("CROSS_REPO_GITHUB_APP_PRIVATE_KEY", workflow) @@ -29,27 +27,23 @@ def test_planner_workflow_downloads_artifacts_posts_issue_and_fans_out_tasks(sel self.assertIn("gh run download", workflow) self.assertIn("Resolve downloaded artifact paths", workflow) self.assertIn("Prepare upstream review payload", workflow) - self.assertIn("Prepare downstream review payload", workflow) self.assertIn("build_ai_review_payload.py", workflow) self.assertIn("build_monthly_optimization_plan.py", workflow) self.assertIn("post_monthly_optimization_issue.py", workflow) self.assertIn("fanout_monthly_optimization_tasks.py", workflow) self.assertIn("Fan out CryptoSnapshotPipelines task issue", workflow) self.assertIn("Fan out CryptoStrategies task issue", workflow) - self.assertIn("Fan out BinancePlatform task issue", workflow) self.assertIn("Resolve upstream experiment validation target", workflow) self.assertIn("Dispatch CryptoSnapshotPipelines experiment validation", workflow) - self.assertIn("Resolve downstream experiment validation target", workflow) - self.assertIn("Best-effort label BinancePlatform issue for experiment validation", workflow) - self.assertIn("experiment-validation", workflow) - self.assertIn("Dispatch BinancePlatform experiment validation", workflow) self.assertIn("gh workflow run experiment_validation.yml", workflow) self.assertIn("--allow-permission-skip", workflow) self.assertIn("Append fanout summary", workflow) self.assertIn("upstream_review_payload.json", workflow) - self.assertIn("downstream_review_payload.json", workflow) self.assertIn("actions/upload-artifact@v7", workflow) self.assertIn("monthly-optimization-plan-", workflow) + self.assertNotIn("BinancePlatform", workflow) + self.assertNotIn("downstream_run_id", workflow) + self.assertNotIn("downstream_repo", workflow) if __name__ == "__main__": diff --git a/tests/test_post_monthly_optimization_issue.py b/tests/test_post_monthly_optimization_issue.py index 0947f51..04e75c2 100644 --- a/tests/test_post_monthly_optimization_issue.py +++ b/tests/test_post_monthly_optimization_issue.py @@ -10,12 +10,11 @@ def test_build_title_and_marker_use_source_reviews(self) -> None: plan = { "source_reviews": [ {"source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", "source_issue": {"number": 11, "title": "Monthly Report Review: 2026-04-01"}}, - {"source_repo": "QuantStrategyLab/BinancePlatform", "source_issue": {"number": 9, "title": "Monthly Execution Review: 2026-03"}}, ] } - self.assertEqual(build_marker(plan), "") - self.assertEqual(build_issue_title(plan), "Monthly Optimization Plan: 2026-04-01 / 2026-03") + self.assertEqual(build_marker(plan), "") + self.assertEqual(build_issue_title(plan), "Monthly Optimization Plan: 2026-04-01") def test_build_issue_body_prefixes_marker(self) -> None: plan = { diff --git a/tests/test_run_openai_secondary_review.py b/tests/test_run_openai_secondary_review.py index f11dab1..0bdc66f 100644 --- a/tests/test_run_openai_secondary_review.py +++ b/tests/test_run_openai_secondary_review.py @@ -7,6 +7,7 @@ build_request_payload, build_system_prompt, extract_completion_content, + SECONDARY_REVIEW_SCHEMA, ) @@ -32,6 +33,13 @@ def test_build_request_payload_uses_structured_json_schema(self) -> None: self.assertTrue(payload["response_format"]["json_schema"]["strict"]) self.assertIn("messages", payload) + def test_recommended_actions_are_limited_to_strategy_repos(self) -> None: + owner_repo_schema = ( + SECONDARY_REVIEW_SCHEMA["properties"]["recommended_actions"]["items"]["properties"]["owner_repo"] + ) + + self.assertEqual(owner_repo_schema["enum"], ["CryptoSnapshotPipelines", "CryptoStrategies"]) + def test_extract_completion_content_reads_first_choice_message(self) -> None: response_payload = { "choices": [