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
2 changes: 1 addition & 1 deletion .github/workflows/ecosystem.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ jobs:
qualification:
permissions:
contents: read
uses: ml4t/ecosystem/.github/workflows/qualify-library.yml@457678d8860d0c816b1dc376e7e2691daa93af4d # v0.1.0
uses: ml4t/ecosystem/.github/workflows/qualify-library.yml@da2cbb9a9d0b4d2f166ad630470ff7d7a1aa2643 # 2026-09-19 policy snapshot
with:
import-package: ml4t.live
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
needs: preflight
permissions:
contents: read
uses: ml4t/ecosystem/.github/workflows/qualify-library.yml@457678d8860d0c816b1dc376e7e2691daa93af4d # v0.1.0
uses: ml4t/ecosystem/.github/workflows/qualify-library.yml@da2cbb9a9d0b4d2f166ad630470ff7d7a1aa2643 # 2026-09-19 policy snapshot
with:
import-package: ml4t.live

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ dependencies = [
ml4t-live = "ml4t.live.cli.main:app"

[project.urls]
Homepage = "https://www.ml4trading.io/docs/live/"
Homepage = "https://www.ml4trading.io/"
Documentation = "https://www.ml4trading.io/docs/live/"
Repository = "https://github.com/ml4t/live"
Issues = "https://github.com/ml4t/live/issues"
Expand Down
2 changes: 1 addition & 1 deletion scripts/qualification/qualify_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"risk-management",
}
EXPECTED_URLS = {
"Homepage": "https://www.ml4trading.io/docs/live/",
"Homepage": "https://www.ml4trading.io/",
"Documentation": "https://www.ml4trading.io/docs/live/",
"Repository": "https://github.com/ml4t/live",
"Issues": "https://github.com/ml4t/live/issues",
Expand Down
2 changes: 1 addition & 1 deletion scripts/qualification/verify_documentation_identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def deployed_identity_failures(
library: str,
version: str,
commit: str,
attempts: int = 12,
attempts: int = 24,
wait_seconds: float = 10.0,
) -> list[str]:
"""Poll representative deployed pages until every identity matches."""
Expand Down
25 changes: 25 additions & 0 deletions tests/unit/test_artifact_qualification.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,19 @@
run_readme_quick_start,
validate_metadata,
)
from scripts.qualification.qualify_artifacts import (
build_distributions as build_release_distributions,
)
from scripts.qualification.scan_release_secrets import scan_payloads

REPOSITORY_ROOT = Path(__file__).resolve().parents[2]


@pytest.fixture(scope="module")
def built_distributions(tmp_path_factory: pytest.TempPathFactory) -> tuple[Path, Path]:
return build_release_distributions(tmp_path_factory.mktemp("distribution"), "1704067200")


def test_release_manifest_marks_typing_and_excludes_agent_files() -> None:
wheel, sdist = load_expected_manifests()

Expand Down Expand Up @@ -74,6 +82,23 @@ def test_external_artifact_input_requires_one_pair(tmp_path: Path) -> None:
distribution_pair(tmp_path)


def test_built_distributions_preserve_canonical_public_identity(
built_distributions: tuple[Path, Path],
) -> None:
wheel, sdist = built_distributions
messages = (
email.message_from_bytes(qualify_artifacts._wheel_member(wheel, ".dist-info/METADATA")),
email.message_from_bytes(qualify_artifacts._sdist_member(sdist, "/PKG-INFO")),
)

for message in messages:
urls = dict(value.split(", ", maxsplit=1) for value in message.get_all("Project-URL", []))
assert message["Author-email"] == "Stefan Jansen <stefan@applied-ai.com>"
assert message["Maintainer-email"] == "Stefan Jansen <pm@ml4trading.io>"
assert urls["Homepage"] == "https://www.ml4trading.io/"
assert urls["Documentation"] == "https://www.ml4trading.io/docs/live/"


def test_metadata_contract_accepts_declared_stable_candidate() -> None:
with (REPOSITORY_ROOT / "pyproject.toml").open("rb") as stream:
project = tomllib.load(stream)["project"]
Expand Down
34 changes: 34 additions & 0 deletions tests/unit/test_documentation_identity.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from __future__ import annotations

import json
from io import BytesIO
from pathlib import Path

import pytest

from scripts.qualification.verify_documentation_identity import (
deployed_identity_failures,
expected_identity,
html_identity_failures,
site_identity_failures,
Expand Down Expand Up @@ -51,3 +53,35 @@ def test_site_identity_checks_every_page_and_manifest(tmp_path: Path) -> None:
(nested / "index.html").write_text(rendered_page(commit="b" * 40))
failures = site_identity_failures(tmp_path, library="live", version="1.2.3", commit=COMMIT)
assert any("api/index.html: ml4t-commit" in failure for failure in failures)


def test_deployed_identity_default_allows_four_minutes_for_propagation(
monkeypatch: pytest.MonkeyPatch,
) -> None:
requests: list[str] = []
sleeps: list[float] = []

def open_url(url: str, *, timeout: int) -> BytesIO:
requests.append(url)
assert timeout == 20
version = "1.2.3" if len(requests) <= 23 * 3 else "1.2.4"
return BytesIO(rendered_page(version=version).encode())

monkeypatch.setattr(
"scripts.qualification.verify_documentation_identity.urllib.request.urlopen", open_url
)
monkeypatch.setattr(
"scripts.qualification.verify_documentation_identity.time.sleep", sleeps.append
)

assert (
deployed_identity_failures(
"https://example.test/docs/live/",
library="live",
version="1.2.4",
commit=COMMIT,
)
== []
)
assert len(requests) == 24 * 3
assert sleeps == [10.0] * 23
Loading