Skip to content

Commit 7135cf5

Browse files
Pigbibicodex
andcommitted
fix: run crypto drift preflight on trusted inputs
Co-Authored-By: Codex <noreply@openai.com>
1 parent cddce26 commit 7135cf5

8 files changed

Lines changed: 392 additions & 94 deletions

File tree

.github/workflows/drift-check.yml

Lines changed: 100 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,18 @@ permissions:
1313

1414
jobs:
1515
preflight_backtests:
16-
if: >-
17-
github.event_name != 'pull_request' ||
18-
github.event.pull_request.head.repo.full_name == github.repository
16+
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
1917
runs-on: ubuntu-latest
20-
timeout-minutes: 15
21-
permissions:
22-
contents: read
23-
id-token: write
24-
env:
25-
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
26-
LIFECYCLE_PERFORMANCE_BUCKET: ${{ vars.LIFECYCLE_PERFORMANCE_BUCKET || '' }}
18+
timeout-minutes: 30
2719
steps:
2820
- name: Checkout
2921
uses: actions/checkout@v6
3022

31-
- name: Require lifecycle bucket
32-
run: |
33-
set -euo pipefail
34-
if [ -z "${LIFECYCLE_PERFORMANCE_BUCKET:-}" ]; then
35-
echo "::error::LIFECYCLE_PERFORMANCE_BUCKET must be configured for drift preflight"
36-
exit 1
37-
fi
38-
3923
- name: Checkout QuantPlatformKit
4024
uses: actions/checkout@v6
4125
with:
4226
repository: QuantStrategyLab/QuantPlatformKit
43-
ref: 335c7a22bc3f570bd5705427ccc40172eda6b289
27+
ref: 9bb8f31e898ea238a6446472f9f5e58133128d0c
4428
path: external/QuantPlatformKit
4529

4630
- name: Set up Python
@@ -55,10 +39,76 @@ jobs:
5539
python -m pip install -e . pandas
5640
python -m pip install --no-deps -e external/QuantPlatformKit
5741
58-
- name: Build lifecycle backtests in staging
42+
- name: Download latest trusted lifecycle inputs
43+
env:
44+
GH_TOKEN: ${{ secrets.SNAPSHOT_REPOSITORY_TOKEN }}
45+
INPUT_ROOT: ${{ runner.temp }}/crypto-lifecycle-inputs
46+
run: |
47+
set -euo pipefail
48+
if [ -z "${GH_TOKEN:-}" ]; then
49+
echo "::error::SNAPSHOT_REPOSITORY_TOKEN is required for lifecycle input artifact access"
50+
exit 1
51+
fi
52+
gh api --paginate --slurp "/repos/QuantStrategyLab/CryptoLivePoolPipelines/actions/artifacts?per_page=100" > "${RUNNER_TEMP}/snapshot-artifacts.json"
53+
python - <<'PY' > "${RUNNER_TEMP}/snapshot-artifact-selection.txt"
54+
import json
55+
import os
56+
from pathlib import Path
57+
pages = json.loads((Path(os.environ["RUNNER_TEMP"]) / "snapshot-artifacts.json").read_text())
58+
artifacts = [item for page in pages for item in page.get("artifacts", [])]
59+
candidates = [
60+
item for item in artifacts
61+
if not item.get("expired")
62+
and str(item.get("name", "")).startswith("crypto-lifecycle-inputs-")
63+
and item.get("workflow_run", {}).get("head_branch") == "main"
64+
]
65+
if not candidates:
66+
raise SystemExit("no trusted crypto lifecycle input artifact is available")
67+
selected = max(candidates, key=lambda item: item["created_at"])
68+
print(selected["id"], selected["workflow_run"]["id"])
69+
PY
70+
read -r artifact_id workflow_run_id < "${RUNNER_TEMP}/snapshot-artifact-selection.txt"
71+
gh api "/repos/QuantStrategyLab/CryptoLivePoolPipelines/actions/runs/${workflow_run_id}" > "${RUNNER_TEMP}/snapshot-workflow-run.json"
72+
python - <<'PY'
73+
import json
74+
import os
75+
from pathlib import Path
76+
run = json.loads((Path(os.environ["RUNNER_TEMP"]) / "snapshot-workflow-run.json").read_text())
77+
expected = {
78+
"conclusion": "success",
79+
"head_branch": "main",
80+
"path": ".github/workflows/publish-lifecycle-inputs.yml",
81+
}
82+
mismatches = {key: run.get(key) for key, value in expected.items() if run.get(key) != value}
83+
if run.get("head_repository", {}).get("full_name") != "QuantStrategyLab/CryptoLivePoolPipelines":
84+
mismatches["head_repository"] = run.get("head_repository", {}).get("full_name")
85+
if mismatches:
86+
raise SystemExit(f"lifecycle input provenance check failed: {mismatches}")
87+
PY
88+
gh api "/repos/QuantStrategyLab/CryptoLivePoolPipelines/actions/artifacts/${artifact_id}/zip" > "${RUNNER_TEMP}/snapshot-artifact.zip"
89+
python - <<'PY'
90+
import os
91+
import zipfile
92+
from pathlib import Path
93+
archive = Path(os.environ["RUNNER_TEMP"]) / "snapshot-artifact.zip"
94+
target_root = Path(os.environ["INPUT_ROOT"])
95+
required = {"research_panel.csv.gz", "market_history.csv.gz", "manifest.json"}
96+
with zipfile.ZipFile(archive) as bundle:
97+
by_name = {Path(name).name: name for name in bundle.namelist() if Path(name).name in required}
98+
missing = sorted(required - set(by_name))
99+
if missing:
100+
raise SystemExit(f"crypto lifecycle artifact is missing: {', '.join(missing)}")
101+
target_root.mkdir(parents=True, exist_ok=True)
102+
for name, member in by_name.items():
103+
(target_root / name).write_bytes(bundle.read(member))
104+
PY
105+
106+
- name: Build lifecycle preflight bundle
59107
env:
60-
LIFECYCLE_PREFLIGHT_STAGING_ROOT: ${{ runner.temp }}/lifecycle_preflight
108+
INPUT_ROOT: ${{ runner.temp }}/crypto-lifecycle-inputs
109+
LIFECYCLE_PREFLIGHT_BUNDLE_ROOT: ${{ runner.temp }}/lifecycle-preflight-bundle
61110
run: |
111+
set -euo pipefail
62112
python - <<'PY'
63113
import json
64114
import os
@@ -71,65 +121,61 @@ jobs:
71121
text=True,
72122
)
73123
)["profiles"]
74-
staging_root = Path(os.environ["LIFECYCLE_PREFLIGHT_STAGING_ROOT"])
124+
input_root = Path(os.environ["INPUT_ROOT"])
125+
bundle_root = Path(os.environ["LIFECYCLE_PREFLIGHT_BUNDLE_ROOT"])
126+
store_root = bundle_root / "data" / "lifecycle_store"
75127
for profile in profiles:
128+
returns_output = (
129+
bundle_root
130+
/ "external"
131+
/ "CryptoLivePoolPipelines"
132+
/ "data"
133+
/ "output"
134+
/ profile
135+
/ "portfolio_and_tracker_returns.csv"
136+
)
76137
subprocess.check_call(
77138
[
78139
"python",
79140
"scripts/run_walk_forward_backtest.py",
80141
"--profile",
81142
profile,
143+
"--panel",
144+
str(input_root / "research_panel.csv.gz"),
145+
"--market-history",
146+
str(input_root / "market_history.csv.gz"),
82147
"--store-root",
83-
str(staging_root),
148+
str(store_root),
149+
"--returns-output",
150+
str(returns_output),
84151
]
85152
)
86153
PY
87154
88-
- name: Promote staged lifecycle backtests
89-
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
90-
env:
91-
LIFECYCLE_PREFLIGHT_STAGING_ROOT: ${{ runner.temp }}/lifecycle_preflight
92-
run: |
93-
python - <<'PY'
94-
import json
95-
import os
96-
import subprocess
97-
from pathlib import Path
98-
99-
from quant_platform_kit.strategy_lifecycle.performance_store import PerformanceStore
100-
101-
domain = "crypto"
102-
staging_root = Path(os.environ["LIFECYCLE_PREFLIGHT_STAGING_ROOT"])
103-
source = PerformanceStore(local_root=staging_root)
104-
target = PerformanceStore.from_env()
105-
profiles = json.loads(
106-
subprocess.check_output(
107-
["python", "scripts/run_walk_forward_backtest.py", "--list-profiles"],
108-
text=True,
109-
)
110-
)["profiles"]
111-
results = [source.load_latest_backtest(domain, profile) for profile in profiles]
112-
if any(result is None for result in results):
113-
raise RuntimeError("staged lifecycle backtests are incomplete")
114-
for result in results:
115-
target.save_backtest_result(result)
116-
PY
155+
- name: Upload lifecycle preflight artifact
156+
uses: actions/upload-artifact@v4
157+
with:
158+
name: lifecycle-preflight-${{ github.run_id }}-${{ github.run_attempt }}
159+
path: ${{ runner.temp }}/lifecycle-preflight-bundle
160+
if-no-files-found: error
117161

118162
drift:
163+
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
119164
needs: preflight_backtests
120165
permissions:
121166
contents: read
122167
issues: write
123168
id-token: write
124-
uses: QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@17278db4e7aef0007346d853eb308b6c1bd8c859
169+
uses: QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@9bb8f31e898ea238a6446472f9f5e58133128d0c
125170
with:
126171
strategy_domain: crypto
127172
caller_event_name: ${{ github.event_name }}
128173
caller_pr_head_repository: ${{ github.event.pull_request.head.repo.full_name || '' }}
129174
snapshot_repository: QuantStrategyLab/CryptoLivePoolPipelines
130175
snapshot_checkout_path: external/CryptoLivePoolPipelines
131176
ai_gateway_service_url: ${{ vars.AI_GATEWAY_SERVICE_URL }}
132-
lifecycle_performance_bucket: ${{ vars.LIFECYCLE_PERFORMANCE_BUCKET }}
177+
quant_platform_kit_ref: 9bb8f31e898ea238a6446472f9f5e58133128d0c
178+
lifecycle_preflight_artifact: lifecycle-preflight-${{ github.run_id }}-${{ github.run_attempt }}
133179
secrets:
134180
codex_audit_service_url: ${{ secrets.CODEX_AUDIT_SERVICE_URL }}
135181
snapshot_repository_token: ${{ secrets.SNAPSHOT_REPOSITORY_TOKEN }}

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ description = "Shared crypto strategy catalog and implementations"
99
readme = "README.md"
1010
requires-python = ">=3.11"
1111
dependencies = [
12-
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@335c7a22bc3f570bd5705427ccc40172eda6b289",
12+
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@9bb8f31e898ea238a6446472f9f5e58133128d0c",
1313
]
1414

1515
[tool.setuptools]

0 commit comments

Comments
 (0)