diff --git a/.github/workflows/private-comparisons.yml b/.github/workflows/private-comparisons.yml index 544f14b9..6d521843 100644 --- a/.github/workflows/private-comparisons.yml +++ b/.github/workflows/private-comparisons.yml @@ -3,7 +3,7 @@ name: Private Comparison Evidence on: workflow_call: secrets: - VECTORBT_PRO_DEPLOY_KEY: + VECTORBT_PRO_GH_TOKEN: required: true workflow_dispatch: @@ -30,14 +30,12 @@ jobs: - name: Configure licensed source access env: - VECTORBT_PRO_DEPLOY_KEY: ${{ secrets.VECTORBT_PRO_DEPLOY_KEY }} - run: | - install -m 700 -d "$HOME/.ssh" - printf '%s\n' "$VECTORBT_PRO_DEPLOY_KEY" > "$HOME/.ssh/id_ed25519" - chmod 600 "$HOME/.ssh/id_ed25519" - ssh-keyscan github.com >> "$HOME/.ssh/known_hosts" + GH_TOKEN: ${{ secrets.VECTORBT_PRO_GH_TOKEN }} + run: gh auth setup-git --hostname github.com - name: Build the locked VectorBT Pro environment + env: + GH_TOKEN: ${{ secrets.VECTORBT_PRO_GH_TOKEN }} run: >- python validation/build_framework_env.py --framework vectorbt_pro @@ -102,6 +100,7 @@ jobs: validation/run_lean_case_studies.py --lean-command .venv-lean/bin/lean --output lean-case-studies.json + --promote - name: Retain LEAN provenance uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/tests/contracts/test_framework_environment_builder.py b/tests/contracts/test_framework_environment_builder.py index 0063378b..cda61692 100644 --- a/tests/contracts/test_framework_environment_builder.py +++ b/tests/contracts/test_framework_environment_builder.py @@ -86,7 +86,7 @@ def test_definition_rejects_wrong_private_commit( project.mkdir() (project / "uv.lock").write_text("wrong-commit", encoding="utf-8") (project / "pyproject.toml").write_text( - '[project]\ndependencies = ["vectorbtpro @ git+ssh://example.invalid/repo@wrong"]\n', + '[project]\ndependencies = ["vectorbtpro @ git+https://example.invalid/repo@wrong"]\n', encoding="utf-8", ) monkeypatch.setattr(build_framework_env, "ENVIRONMENTS_DIR", tmp_path) @@ -97,6 +97,28 @@ def test_definition_rejects_wrong_private_commit( assert any("artifact identity" in failure for failure in failures) +def test_definition_rejects_private_ssh_source( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, + targets: dict[str, FrameworkTarget], +) -> None: + target = targets["vectorbt_pro"] + project = tmp_path / "vectorbt_pro" + project.mkdir() + (project / "uv.lock").write_text(target.immutable_id.removeprefix("git:"), encoding="utf-8") + (project / "pyproject.toml").write_text( + "[project]\ndependencies = [" + f'"vectorbtpro @ git+ssh://git@github.com/polakowo/vectorbt.pro.git@' + f'{target.source_commit}"\n]\n', + encoding="utf-8", + ) + monkeypatch.setattr(build_framework_env, "ENVIRONMENTS_DIR", tmp_path) + + failures = build_framework_env.definition_failures("vectorbt_pro", target) + + assert "VectorBT Pro environment must use GitHub CLI authenticated HTTPS" in failures + + def test_private_build_reports_missing_licensed_access( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, @@ -109,7 +131,7 @@ def test_private_build_reports_missing_licensed_access( lambda *args, **_kwargs: (_ for _ in ()).throw(subprocess.CalledProcessError(1, args[0])), ) - with pytest.raises(RuntimeError, match="Authorized SSH access"): + with pytest.raises(RuntimeError, match="GitHub CLI access"): build_framework_env.build_environment( "vectorbt_pro", targets["vectorbt_pro"], root=tmp_path ) @@ -149,6 +171,32 @@ def run(command: list[str], **_kwargs): assert commands == [["docker", "buildx", "imagetools", "inspect", target.artifact]] +def test_lean_build_pulls_the_verified_engine_image( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, + targets: dict[str, FrameworkTarget], +) -> None: + target = targets["lean"] + commands: list[list[str]] = [] + + def run(command: list[str], **_kwargs): + commands.append(command) + return subprocess.CompletedProcess(command, 0) + + monkeypatch.setattr(build_framework_env.subprocess, "run", run) + monkeypatch.setattr(build_framework_env, "definition_failures", lambda *_: []) + monkeypatch.setattr( + build_framework_env, + "verify_environment", + lambda *_args, **_kwargs: {"framework": "lean"}, + ) + + evidence = build_framework_env.build_environment("lean", target, root=tmp_path) + + assert evidence == {"framework": "lean"} + assert commands[-1] == ["docker", "pull", target.artifact] + + def test_private_environment_rejects_wrong_installed_commit( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, diff --git a/tests/contracts/test_release_workflow.py b/tests/contracts/test_release_workflow.py index f1316344..527d70e8 100644 --- a/tests/contracts/test_release_workflow.py +++ b/tests/contracts/test_release_workflow.py @@ -198,8 +198,16 @@ def test_private_comparison_workflow_pins_and_retains_evidence() -> None: workflow = _workflow("private-comparisons.yml") jobs = workflow["jobs"] - assert workflow["on"]["workflow_call"]["secrets"]["VECTORBT_PRO_DEPLOY_KEY"]["required"] + assert workflow["on"]["workflow_call"]["secrets"]["VECTORBT_PRO_GH_TOKEN"]["required"] pro_commands = "\n".join(step.get("run", "") for step in jobs["vectorbt-pro"]["steps"]) + assert "gh auth setup-git" in pro_commands + assert "id_ed25519" not in pro_commands + authenticated_steps = [ + step + for step in jobs["vectorbt-pro"]["steps"] + if step.get("env", {}).get("GH_TOKEN") == "${{ secrets.VECTORBT_PRO_GH_TOKEN }}" + ] + assert len(authenticated_steps) == 2 assert "validation/build_framework_env.py" in pro_commands assert "validation/native/vectorbt_behavior.py" in pro_commands assert "validation/run_all_correctness.py" in pro_commands diff --git a/validation/README.md b/validation/README.md index b943ee9e..6eac8d82 100644 --- a/validation/README.md +++ b/validation/README.md @@ -187,7 +187,7 @@ not used for the published real-strategy ratios above. # Build and verify all public targets from their committed uv locks python validation/build_framework_env.py --all-public -# Licensed VectorBT Pro requires authorized SSH access to the frozen source commit +# Licensed VectorBT Pro requires GitHub CLI access to the frozen source commit python validation/build_framework_env.py --framework vectorbt_pro # Verify the locked LEAN CLI and immutable engine image diff --git a/validation/build_framework_env.py b/validation/build_framework_env.py index 3d70dc1b..0d36f910 100644 --- a/validation/build_framework_env.py +++ b/validation/build_framework_env.py @@ -65,6 +65,10 @@ def definition_failures(framework: str, target: FrameworkTarget) -> list[str]: if len(matching) != 1: failures.append(f"Environment must define one {target.package} dependency: {framework}") elif framework == "vectorbt_pro": + if not matching[0].startswith( + "vectorbtpro @ git+https://github.com/polakowo/vectorbt.pro.git@" + ): + failures.append("VectorBT Pro environment must use GitHub CLI authenticated HTTPS") if target.source_commit is None or f"@{target.source_commit}" not in matching[0]: failures.append("VectorBT Pro environment does not pin the manifest commit") else: @@ -198,11 +202,16 @@ def build_environment( except subprocess.CalledProcessError as error: if framework == "vectorbt_pro": raise RuntimeError( - "VectorBT Pro is unavailable. Authorized SSH access to the licensed source is " - "required; no credentials are stored in this repository." + "VectorBT Pro is unavailable. Authorized GitHub CLI access to the licensed " + "source is required; no credentials are stored in this repository." ) from error raise - return verify_environment(framework, target, root=root) + evidence = verify_environment(framework, target, root=root) + if framework == "lean": + if target.artifact is None: + raise ValueError("LEAN target lacks an immutable engine image") + subprocess.run(["docker", "pull", target.artifact], check=True) + return evidence def main() -> int: diff --git a/validation/environments/vectorbt_pro/pyproject.toml b/validation/environments/vectorbt_pro/pyproject.toml index a546f1de..a04e10eb 100644 --- a/validation/environments/vectorbt_pro/pyproject.toml +++ b/validation/environments/vectorbt_pro/pyproject.toml @@ -4,7 +4,7 @@ version = "0.0.0" requires-python = ">=3.12,<3.13" dependencies = [ "ml4t-backtest", - "vectorbtpro @ git+ssh://git@github.com/polakowo/vectorbt.pro.git@6e18cf0aa37849cfc20848f40f1d26ecfdc771b4", + "vectorbtpro @ git+https://github.com/polakowo/vectorbt.pro.git@6e18cf0aa37849cfc20848f40f1d26ecfdc771b4", ] [tool.uv] diff --git a/validation/environments/vectorbt_pro/uv.lock b/validation/environments/vectorbt_pro/uv.lock index d13e95be..3b05b2df 100644 --- a/validation/environments/vectorbt_pro/uv.lock +++ b/validation/environments/vectorbt_pro/uv.lock @@ -299,7 +299,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "ml4t-backtest", editable = "../../../" }, - { name = "vectorbtpro", git = "ssh://git@github.com/polakowo/vectorbt.pro.git?rev=6e18cf0aa37849cfc20848f40f1d26ecfdc771b4" }, + { name = "vectorbtpro", git = "https://github.com/polakowo/vectorbt.pro.git?rev=6e18cf0aa37849cfc20848f40f1d26ecfdc771b4" }, ] [[package]] @@ -712,7 +712,7 @@ wheels = [ [[package]] name = "vectorbtpro" version = "2026.6.27" -source = { git = "ssh://git@github.com/polakowo/vectorbt.pro.git?rev=6e18cf0aa37849cfc20848f40f1d26ecfdc771b4#6e18cf0aa37849cfc20848f40f1d26ecfdc771b4" } +source = { git = "https://github.com/polakowo/vectorbt.pro.git?rev=6e18cf0aa37849cfc20848f40f1d26ecfdc771b4#6e18cf0aa37849cfc20848f40f1d26ecfdc771b4" } dependencies = [ { name = "attrs" }, { name = "dateparser" }, diff --git a/validation/lean/case_study_evidence.json b/validation/lean/case_study_evidence.json index 558b3183..da9871f9 100644 --- a/validation/lean/case_study_evidence.json +++ b/validation/lean/case_study_evidence.json @@ -1,6 +1,6 @@ { "case_study_support_files": { - "project-config.json": "6f53654afabb96e23944db61235b83848ab24fcac2ecaf9597382e0671fef01f" + "project-config.json": "2c71309922dc0305269869776ba945432379a2c45d5327550708953e6901b0c6" }, "cases": [ { @@ -57,8 +57,8 @@ "validation/lean/workspace/chapter16_etfs/ml4t_daily_equity.csv": "20bc4a28ad04fdc458bd8f5031fbf976623caddd819da70f2e51ddb45dea6609", "validation/lean/workspace/chapter16_etfs/ml4t_order_events.csv.xz": "63535bec501d36e4f1eae501917e0000e636c03a19d637b05cdda1428bc80421" }, - "runtime_seconds": 12.663519160356373, - "summary_sha256": "8a50b99a15d26baa87845c8ed2a517f40747b5e352cc6e739d208cba4323b777" + "runtime_seconds": 21.884960932000013, + "summary_sha256": "677166e5de08ea5de1795d1b4d4ac440b5e2cf1e5c4f26f5139bbd0ee2682240" }, { "case": "chapter16_sp500_equity_option_analytics", @@ -115,8 +115,8 @@ "validation/lean/workspace/chapter16_sp500_equity_option_analytics/ml4t_daily_equity.csv": "83a98f50053b12dd4baa37484b0b268cb8ec83ebdf633ee2e6ee790db0bb73ad", "validation/lean/workspace/chapter16_sp500_equity_option_analytics/ml4t_order_events.csv": "334cd463affb3d23f074882fe1d8131861ec67ca5203fbdfb86cae4d8da64917" }, - "runtime_seconds": 8.238332036882639, - "summary_sha256": "df58a18960ff30d5cc5cf51ee7072dd2b557f46ad7e0ced3d74aaab061eb3e13" + "runtime_seconds": 14.92322724799999, + "summary_sha256": "035984926246be0c41275ba43df96e2e04d06ef105bfb6055d35909f525e43dd" }, { "case": "chapter16_us_equities_panel", @@ -201,8 +201,8 @@ "validation/lean/workspace/chapter16_us_equities_panel/ml4t_order_events.csv.part01.xz": "9aea6ff30791dc06cd9b917e6f0c639d1b6f17883012b9c5190d7be6a899d980", "validation/lean/workspace/chapter16_us_equities_panel/ml4t_order_events.csv.part02.xz": "915bef2036b7d49535eb81a458a1af891f8f15c249bb413831771ea52e982a26" }, - "runtime_seconds": 23.13902745116502, - "summary_sha256": "4d10636f183790250e6feb7018554c09140c1603dd8c09c7e93029679543397b" + "runtime_seconds": 42.17692052699999, + "summary_sha256": "bd8582a9316684d73a0ed55d4ed16ed2e7a05be5b94881607f022a5867f3ecbd" } ], "cli_observed": "lean 1.0.228", diff --git a/validation/lean/case_study_support/project-config.json b/validation/lean/case_study_support/project-config.json index 1e20f0db..71a24ba8 100644 --- a/validation/lean/case_study_support/project-config.json +++ b/validation/lean/case_study_support/project-config.json @@ -1,6 +1,11 @@ { "algorithm-language": "Python", - "parameters": {}, "description": "ml4t case-study lean parity", - "local-id": 0 + "docker": { + "environment": { + "PYTHONPYCACHEPREFIX": "/tmp/lean-pycache" + } + }, + "local-id": 0, + "parameters": {} } diff --git a/validation/native/evidence/lean-18001.json b/validation/native/evidence/lean-18001.json index ee4640e7..da6f5a78 100644 --- a/validation/native/evidence/lean-18001.json +++ b/validation/native/evidence/lean-18001.json @@ -2254,7 +2254,7 @@ "security": "Equity USA, daily, adjusted normalization, leverage 2" }, "oracle_files": { - "config.json": "5b7ec7bf073127d026e6086ec18771dcb700375570ba51def97739d85009351c", + "config.json": "272e56b7a924ac9fd1dfe01e70be10e2a7c58b1b9328576e7a9c0b6e3144dbb3", "main.py": "04aab940df029bf37e7aa1a9d3d8ab0996fce0bb3a10fa4dd4e15ca685e83e19" }, "oracle_sha256": "1675f062ce54f29727d2bf7d17afc5c9db4e439e9f921e270feec49cde065d66", diff --git a/validation/native/lean_project/config.json b/validation/native/lean_project/config.json index 82dc4517..3ea1445b 100644 --- a/validation/native/lean_project/config.json +++ b/validation/native/lean_project/config.json @@ -1,6 +1,11 @@ { "algorithm-language": "Python", "description": "ML4T native LEAN behavior oracle", + "docker": { + "environment": { + "PYTHONPYCACHEPREFIX": "/tmp/lean-pycache" + } + }, "local-id": 771804201, "parameters": {} }