Skip to content

Commit 07faf99

Browse files
authored
Tighten monthly release validation (#63)
1 parent abc222c commit 07faf99

4 files changed

Lines changed: 41 additions & 1 deletion

File tree

.github/workflows/monthly_publish.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ jobs:
106106
env:
107107
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
108108
GITHUB_REPOSITORY: ${{ github.repository }}
109-
GITHUB_OUTPUT: ${{ github.output }}
110109
run: |
111110
set -euo pipefail
112111
python - <<'PY'

src/release_contract.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,11 +436,16 @@ def validate_release_outputs(
436436
errors,
437437
)
438438
)
439+
selected_row_count = int(selected_mask.sum())
439440
selected_symbols_by_rank = _selected_symbols_ordered_by_rank(latest_ranking, selected_mask, errors)
440441
if live_pool_symbols and not set(live_pool_symbols).issubset(set(ranking_symbols)):
441442
errors.append("live_pool.json symbols must all be present in latest_ranking.csv")
442443
if live_pool_symbols and not set(live_pool_symbols).issubset(selected_symbols):
443444
errors.append("live_pool.json symbols must all be selected in latest_ranking.csv")
445+
if live_pool_symbols and selected_row_count != len(live_pool_symbols):
446+
errors.append(
447+
"latest_ranking.csv selected_flag row count must match live_pool.json symbols length"
448+
)
444449
if live_pool_symbols and selected_symbols_by_rank:
445450
expected_live_pool_symbols = selected_symbols_by_rank[: len(live_pool_symbols)]
446451
if live_pool_symbols != expected_live_pool_symbols:

tests/test_monthly_publish_workflow_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def test_monthly_review_issue_creation_does_not_require_gh_cli(self) -> None:
3535
self.assertIn("--shadow-universe-mode", workflow)
3636
self.assertIn("https://api.github.com/repos/{repository}", workflow)
3737
self.assertIn('GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}', workflow)
38+
self.assertNotIn("GITHUB_OUTPUT: ${{ github.output }}", workflow)
3839
self.assertIn("issue_number=", workflow)
3940
self.assertIn("SELFHOSTED_CODEX_REVIEW_REPOSITORY", workflow)
4041
self.assertIn("QuantStrategyLab/CryptoCodexAuditBridge", workflow)

tests/test_release_contract.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,41 @@ def test_validate_release_outputs_rejects_live_pool_order_mismatch(self) -> None
248248
validation["errors"],
249249
)
250250

251+
def test_validate_release_outputs_rejects_extra_selected_ranking_rows(self) -> None:
252+
with tempfile.TemporaryDirectory() as tmp_dir:
253+
root = Path(tmp_dir)
254+
self.build_outputs(root)
255+
output_dir = root / "data" / "output"
256+
ranking_path = output_dir / "latest_ranking.csv"
257+
artifact_manifest_path = output_dir / "artifact_manifest.json"
258+
259+
ranking = pd.read_csv(ranking_path)
260+
ranking.loc[len(ranking)] = {
261+
"as_of_date": "2026-03-13",
262+
"symbol": "XRPUSDT",
263+
"rule_score": 0.4,
264+
"linear_score": 0.3,
265+
"ml_score": 0.2,
266+
"final_score": 0.4,
267+
"regime": "risk_off",
268+
"confidence": 0.5,
269+
"selected_flag": True,
270+
"current_rank": 6,
271+
}
272+
ranking.to_csv(ranking_path, index=False)
273+
274+
artifact_manifest = json.loads(artifact_manifest_path.read_text(encoding="utf-8"))
275+
artifact_manifest["artifacts"]["latest_ranking"]["sha256"] = sha256_file(ranking_path)
276+
write_json(artifact_manifest_path, artifact_manifest)
277+
278+
validation = validate_release_outputs(root / "data" / "output", require_artifact_manifest=True)
279+
280+
self.assertFalse(validation["ok"])
281+
self.assertIn(
282+
"latest_ranking.csv selected_flag row count must match live_pool.json symbols length",
283+
validation["errors"],
284+
)
285+
251286
def test_validate_release_outputs_rejects_stale_outputs_when_required(self) -> None:
252287
with tempfile.TemporaryDirectory() as tmp_dir:
253288
root = Path(tmp_dir)

0 commit comments

Comments
 (0)