Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/monthly_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_OUTPUT: ${{ github.output }}
run: |
set -euo pipefail
python - <<'PY'
Expand Down
5 changes: 5 additions & 0 deletions src/release_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,11 +436,16 @@ def validate_release_outputs(
errors,
)
)
selected_row_count = int(selected_mask.sum())
selected_symbols_by_rank = _selected_symbols_ordered_by_rank(latest_ranking, selected_mask, errors)
if live_pool_symbols and not set(live_pool_symbols).issubset(set(ranking_symbols)):
errors.append("live_pool.json symbols must all be present in latest_ranking.csv")
if live_pool_symbols and not set(live_pool_symbols).issubset(selected_symbols):
errors.append("live_pool.json symbols must all be selected in latest_ranking.csv")
if live_pool_symbols and selected_row_count != len(live_pool_symbols):
errors.append(
"latest_ranking.csv selected_flag row count must match live_pool.json symbols length"
)
if live_pool_symbols and selected_symbols_by_rank:
expected_live_pool_symbols = selected_symbols_by_rank[: len(live_pool_symbols)]
if live_pool_symbols != expected_live_pool_symbols:
Expand Down
1 change: 1 addition & 0 deletions tests/test_monthly_publish_workflow_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def test_monthly_review_issue_creation_does_not_require_gh_cli(self) -> None:
self.assertIn("--shadow-universe-mode", workflow)
self.assertIn("https://api.github.com/repos/{repository}", workflow)
self.assertIn('GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}', workflow)
self.assertNotIn("GITHUB_OUTPUT: ${{ github.output }}", workflow)
self.assertIn("issue_number=", workflow)
self.assertIn("SELFHOSTED_CODEX_REVIEW_REPOSITORY", workflow)
self.assertIn("QuantStrategyLab/CryptoCodexAuditBridge", workflow)
Expand Down
35 changes: 35 additions & 0 deletions tests/test_release_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,41 @@ def test_validate_release_outputs_rejects_live_pool_order_mismatch(self) -> None
validation["errors"],
)

def test_validate_release_outputs_rejects_extra_selected_ranking_rows(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir)
self.build_outputs(root)
output_dir = root / "data" / "output"
ranking_path = output_dir / "latest_ranking.csv"
artifact_manifest_path = output_dir / "artifact_manifest.json"

ranking = pd.read_csv(ranking_path)
ranking.loc[len(ranking)] = {
"as_of_date": "2026-03-13",
"symbol": "XRPUSDT",
"rule_score": 0.4,
"linear_score": 0.3,
"ml_score": 0.2,
"final_score": 0.4,
"regime": "risk_off",
"confidence": 0.5,
"selected_flag": True,
"current_rank": 6,
}
ranking.to_csv(ranking_path, index=False)

artifact_manifest = json.loads(artifact_manifest_path.read_text(encoding="utf-8"))
artifact_manifest["artifacts"]["latest_ranking"]["sha256"] = sha256_file(ranking_path)
write_json(artifact_manifest_path, artifact_manifest)

validation = validate_release_outputs(root / "data" / "output", require_artifact_manifest=True)

self.assertFalse(validation["ok"])
self.assertIn(
"latest_ranking.csv selected_flag row count must match live_pool.json symbols length",
validation["errors"],
)

def test_validate_release_outputs_rejects_stale_outputs_when_required(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir)
Expand Down