Repository navigation
fix(deploy): release the Firstrade cached currency field #1313
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| pull_request: | |
| push: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| jobs: | |
| test: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Test Dot review safety contracts offline | |
| run: node --test .github/dot-review/test-*.cjs | |
| - name: Install public configuration scan tools | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if ! command -v rg >/dev/null; then | |
| release="ripgrep-14.1.0-x86_64-unknown-linux-musl" | |
| archive="$RUNNER_TEMP/$release.tar.gz" | |
| curl --fail --silent --show-error --location --retry 2 --max-time 60 \ | |
| "https://github.com/BurntSushi/ripgrep/releases/download/14.1.0/$release.tar.gz" \ | |
| --output "$archive" | |
| printf '%s %s\n' 'f84757b07f425fe5cf11d87df6644691c644a5cd2348a2c670894272999d3ba7' "$archive" | sha256sum --check --status | |
| tar -xzf "$archive" -C "$RUNNER_TEMP" "$release/rg" | |
| export PATH="$RUNNER_TEMP/$release:$PATH" | |
| printf '%s\n' "$RUNNER_TEMP/$release" >> "$GITHUB_PATH" | |
| fi | |
| command -v rg | |
| - name: Verify public configuration scanner contract offline | |
| run: | | |
| set -euo pipefail | |
| python3 -m unittest discover -s tests -p test_public_runtime_configuration_scan.py -v | |
| - name: Reject public runtime configuration bindings | |
| id: public-config-scan | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| command -v rg | |
| if rg -l '\$\{\{[[:space:]]*vars\.(GLOBAL_TELEGRAM_CHAT_ID|CLOUD_RUN_SERVICES|CLOUD_RUN_SERVICE|RUNTIME_HEARTBEAT_REQUIRED_SERVICES|RUNTIME_GUARD_SCHEDULER_JOB_PATTERN)([[:space:]]|\}\}|\|\|)' .github/workflows; then | |
| echo "Sensitive operational runtime configuration must use GitHub Secrets, not GitHub Variables." >&2 | |
| exit 1 | |
| else | |
| status=$? | |
| if [ "$status" -ne 1 ]; then | |
| exit "$status" | |
| fi | |
| fi | |
| printf '%s\n' 'scan_completed=true' >> "$GITHUB_OUTPUT" | |
| echo "Public configuration scan completed without matches." | |
| - name: Assert public configuration scan completed | |
| if: ${{ always() }} | |
| env: | |
| PUBLIC_CONFIG_SCAN_OUTCOME: ${{ steps.public-config-scan.outcome }} | |
| PUBLIC_CONFIG_SCAN_COMPLETED: ${{ steps.public-config-scan.outputs.scan_completed }} | |
| run: | | |
| set -euo pipefail | |
| if [ "${PUBLIC_CONFIG_SCAN_OUTCOME:-}" != "success" ] || | |
| [ "${PUBLIC_CONFIG_SCAN_COMPLETED:-}" != "true" ]; then | |
| echo "Public configuration scan completion was not verified." >&2 | |
| exit 1 | |
| fi | |
| - name: Read locked shared dependency refs | |
| id: locked-shared-refs | |
| run: | | |
| set -euo pipefail | |
| python - <<'PY' >> "$GITHUB_OUTPUT" | |
| import re | |
| import tomllib | |
| from pathlib import Path | |
| required = {"quant-platform-kit", "us-equity-strategies"} | |
| lock = tomllib.loads(Path("uv.lock").read_text(encoding="utf-8")) | |
| refs = {} | |
| for package in lock.get("package", []): | |
| name = package.get("name") | |
| if name not in required: | |
| continue | |
| source = package.get("source") or {} | |
| match = re.search(r"#([0-9a-f]{40})$", str(source.get("git") or "")) | |
| if match: | |
| refs[name] = match.group(1) | |
| if set(refs) != required: | |
| raise SystemExit("uv.lock lacks a full SHA for a shared dependency") | |
| for name in sorted(refs): | |
| print(f"{name.replace('-', '_')}={refs[name]}") | |
| PY | |
| - name: Checkout QuantPlatformKit | |
| uses: actions/checkout@v6 | |
| with: | |
| repository: QuantStrategyLab/QuantPlatformKit | |
| ref: ${{ steps.locked-shared-refs.outputs.quant_platform_kit }} | |
| path: external/QuantPlatformKit | |
| - name: Checkout UsEquityStrategies | |
| uses: actions/checkout@v6 | |
| with: | |
| repository: QuantStrategyLab/UsEquityStrategies | |
| ref: ${{ steps.locked-shared-refs.outputs.us_equity_strategies }} | |
| path: external/UsEquityStrategies | |
| - name: Setup Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Install dependencies | |
| run: | | |
| set -euo pipefail | |
| python -m pip install --upgrade pip uv | |
| uv sync --frozen --extra test | |
| - name: Verify shared repository refs | |
| run: | | |
| set -euo pipefail | |
| test "$(git -C external/QuantPlatformKit rev-parse HEAD)" = "${{ steps.locked-shared-refs.outputs.quant_platform_kit }}" | |
| test "$(git -C external/UsEquityStrategies rev-parse HEAD)" = "${{ steps.locked-shared-refs.outputs.us_equity_strategies }}" | |
| - name: Smoke import pinned shared packages | |
| run: | | |
| set -euo pipefail | |
| uv run --no-sync python - <<'PY' | |
| import importlib.metadata | |
| import json | |
| import tomllib | |
| from pathlib import Path | |
| lock = tomllib.loads(Path("uv.lock").read_text(encoding="utf-8")) | |
| for package in lock["package"]: | |
| if package["name"] not in {"quant-platform-kit", "us-equity-strategies"}: | |
| continue | |
| expected = package["source"]["git"].rsplit("#", 1)[1] | |
| installed = json.loads(importlib.metadata.distribution(package["name"]).read_text("direct_url.json") or "{}") | |
| assert installed.get("vcs_info", {}).get("commit_id") == expected, package["name"] | |
| PY | |
| uv run --no-sync python - <<'PY' | |
| from quant_platform_kit.common.port_adapters import CallableNotificationPort, CallablePortfolioPort | |
| from us_equity_strategies import resolve_canonical_profile | |
| assert CallableNotificationPort | |
| assert CallablePortfolioPort | |
| assert resolve_canonical_profile("russell_top50_leader_rotation") == "russell_top50_leader_rotation" | |
| PY | |
| - name: Verify installed shared test imports | |
| run: | | |
| set -euo pipefail | |
| uv run --no-sync python -m pytest -q -p test_support.pytest_provenance \ | |
| --shared-import-mode installed-lock tests/test_shared_import_contract.py | |
| - name: Verify Python dependencies | |
| run: uv pip check | |
| - name: Run ruff | |
| run: | | |
| set -euo pipefail | |
| uv run --no-sync ruff check --exclude external . | |
| - name: Check QPK pin consistency | |
| run: | | |
| set -euo pipefail | |
| printf '%s\n' "${{ steps.locked-shared-refs.outputs.quant_platform_kit }}" > "$RUNNER_TEMP/firstrade-qpk-pin" | |
| uv run --no-sync python external/QuantPlatformKit/scripts/check_qpk_pin_consistency.py \ | |
| --root . \ | |
| --pin-file "$RUNNER_TEMP/firstrade-qpk-pin" | |
| - name: Ensure uv.lock matches pyproject.toml | |
| run: uv lock --check | |
| - name: Run tests | |
| run: uv run --no-sync python -m pytest -q | |
| - name: Measure coverage against the production source layout | |
| run: | | |
| set -euo pipefail | |
| uv run --no-sync python -m coverage run -m pytest -q | |
| uv run --no-sync python -m coverage report --show-missing | |
| uv run --no-sync python - <<'PY' | |
| from coverage import CoverageData | |
| data = CoverageData(basename=".coverage") | |
| data.read() | |
| files = sorted(data.measured_files()) | |
| if not files: | |
| raise SystemExit("coverage measured no files") | |
| for path in files: | |
| print(path) | |
| PY | |
| - name: Build package | |
| run: uv run --no-sync python -m build |