mxready 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mxready/__init__.py +1 -0
- mxready/api/__init__.py +4 -0
- mxready/api/middleware.py +149 -0
- mxready/api/rules.py +10 -0
- mxready/api/scans.py +164 -0
- mxready/api/workspace.py +156 -0
- mxready/app.py +179 -0
- mxready/cli/__init__.py +1 -0
- mxready/cli/build_verification_bundle.py +45 -0
- mxready/cli/generate_public_report.py +140 -0
- mxready/cli/scan_batch.py +368 -0
- mxready/cli/scan_repository.py +212 -0
- mxready/cli/serve.py +55 -0
- mxready/config.py +117 -0
- mxready/errors.py +11 -0
- mxready/logging_setup.py +88 -0
- mxready/mcp/__init__.py +5 -0
- mxready/mcp/server.py +433 -0
- mxready/models.py +382 -0
- mxready/persistence/__init__.py +1 -0
- mxready/persistence/sqlite.py +581 -0
- mxready/py.typed +0 -0
- mxready/reporting/__init__.py +4 -0
- mxready/reporting/badge.py +40 -0
- mxready/reporting/comparison.py +33 -0
- mxready/reporting/markdown.py +157 -0
- mxready/reporting/sarif.py +94 -0
- mxready/reporting/terminal.py +75 -0
- mxready/repository/__init__.py +14 -0
- mxready/repository/git_client.py +275 -0
- mxready/repository/github_archive.py +566 -0
- mxready/repository/identity.py +140 -0
- mxready/resources/__init__.py +19 -0
- mxready/resources/frontend/assets/index-BxJEwNUm.css +1 -0
- mxready/resources/frontend/assets/index-lzTCdWAt.js +9 -0
- mxready/resources/frontend/assets/mx-chip-C0HgFQyE.webp +0 -0
- mxready/resources/frontend/index.html +17 -0
- mxready/resources/rules/v1/core.yml +634 -0
- mxready/resources/rules/v1/manifest.yml +4 -0
- mxready/resources/schemas/verification-result-v1.json +259 -0
- mxready/scanning/__init__.py +17 -0
- mxready/scanning/analyzer.py +183 -0
- mxready/scanning/facts.py +596 -0
- mxready/scanning/indexer.py +299 -0
- mxready/scanning/path_context.py +174 -0
- mxready/scanning/python_source.py +67 -0
- mxready/scanning/rule_engine.py +595 -0
- mxready/scanning/rule_loader.py +282 -0
- mxready/scanning/toolchain.py +100 -0
- mxready/services/__init__.py +3 -0
- mxready/services/scans.py +239 -0
- mxready/services/worker.py +38 -0
- mxready/verification/__init__.py +11 -0
- mxready/verification/bundle.py +137 -0
- mxready/verification/validation.py +101 -0
- mxready-1.0.0.dist-info/METADATA +526 -0
- mxready-1.0.0.dist-info/RECORD +71 -0
- mxready-1.0.0.dist-info/WHEEL +5 -0
- mxready-1.0.0.dist-info/entry_points.txt +8 -0
- mxready-1.0.0.dist-info/licenses/LICENSE +201 -0
- mxready-1.0.0.dist-info/top_level.txt +2 -0
- mxready_runner/__init__.py +1 -0
- mxready_runner/__main__.py +3 -0
- mxready_runner/cli.py +88 -0
- mxready_runner/contracts.py +26 -0
- mxready_runner/execute.py +185 -0
- mxready_runner/inspect.py +79 -0
- mxready_runner/process.py +103 -0
- mxready_runner/py.typed +0 -0
- mxready_runner/redact.py +36 -0
- mxready_runner/schema.py +326 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import json
|
|
5
|
+
import zipfile
|
|
6
|
+
from importlib.resources import files as resource_files
|
|
7
|
+
|
|
8
|
+
from mxready import __version__
|
|
9
|
+
from mxready.models import ScanReport
|
|
10
|
+
from mxready.resources import verification_schema_bytes
|
|
11
|
+
from mxready_runner.contracts import DEFAULT_CHECKS
|
|
12
|
+
|
|
13
|
+
_RUNNER_FILES = (
|
|
14
|
+
"__init__.py",
|
|
15
|
+
"__main__.py",
|
|
16
|
+
"cli.py",
|
|
17
|
+
"execute.py",
|
|
18
|
+
"inspect.py",
|
|
19
|
+
"contracts.py",
|
|
20
|
+
"process.py",
|
|
21
|
+
"redact.py",
|
|
22
|
+
"schema.py",
|
|
23
|
+
)
|
|
24
|
+
_SECURITY_NOTE = """# MXReady remote verification safety
|
|
25
|
+
|
|
26
|
+
This archive runs only on a host controlled by you.
|
|
27
|
+
|
|
28
|
+
- Read `mxready.yml` and every project command before running it.
|
|
29
|
+
- Environment inspection uses the fixed commands listed in the manifest.
|
|
30
|
+
- Project commands run only after your explicit approval.
|
|
31
|
+
- Use `run --repo-dir /path/to/clean/checkout`; HEAD must match the manifest.
|
|
32
|
+
- Keep the manifest and result outside the checkout. Tracked and untracked changes
|
|
33
|
+
are rejected before and after project commands; ignored build outputs are permitted.
|
|
34
|
+
- `inspect` only checks the environment and does not verify project source.
|
|
35
|
+
- The runner never uses `sudo`, installs packages, or changes GPU drivers.
|
|
36
|
+
- Commands use argument arrays with `shell=False` and bounded timeouts.
|
|
37
|
+
- Output is redacted and truncated, but review `result.json` before sharing it.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
_PROJECT_COMMANDS_EXAMPLE = (
|
|
41
|
+
json.dumps(
|
|
42
|
+
[
|
|
43
|
+
{
|
|
44
|
+
"id": "example-cuda-smoke",
|
|
45
|
+
"command": [
|
|
46
|
+
"python",
|
|
47
|
+
"-c",
|
|
48
|
+
(
|
|
49
|
+
"import torch, your_extension; "
|
|
50
|
+
"a=torch.randn(1024, device='cuda'); "
|
|
51
|
+
"b=torch.randn(1024, device='cuda'); "
|
|
52
|
+
"actual=your_extension.ops.mymuladd(a, b, 1.0); "
|
|
53
|
+
"torch.testing.assert_close(actual, a * b + 1.0); "
|
|
54
|
+
"print('MXReady example CUDA smoke passed')"
|
|
55
|
+
),
|
|
56
|
+
],
|
|
57
|
+
"timeout_seconds": 120,
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
ensure_ascii=False,
|
|
61
|
+
indent=2,
|
|
62
|
+
)
|
|
63
|
+
+ "\n"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def build_verification_bundle(report: ScanReport) -> bytes:
|
|
68
|
+
"""Build a deterministic, self-contained verification ZIP."""
|
|
69
|
+
files: list[tuple[str, bytes]] = [
|
|
70
|
+
(
|
|
71
|
+
"mxready.yml",
|
|
72
|
+
(
|
|
73
|
+
json.dumps(
|
|
74
|
+
_build_manifest(report),
|
|
75
|
+
ensure_ascii=False,
|
|
76
|
+
indent=2,
|
|
77
|
+
sort_keys=True,
|
|
78
|
+
)
|
|
79
|
+
+ "\n"
|
|
80
|
+
).encode("utf-8"),
|
|
81
|
+
),
|
|
82
|
+
("SECURITY.md", _SECURITY_NOTE.encode("utf-8")),
|
|
83
|
+
(
|
|
84
|
+
"project-commands.example.json",
|
|
85
|
+
_PROJECT_COMMANDS_EXAMPLE.encode("utf-8"),
|
|
86
|
+
),
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
for filename in _RUNNER_FILES:
|
|
90
|
+
files.append(
|
|
91
|
+
(
|
|
92
|
+
f"mxready_runner/{filename}",
|
|
93
|
+
resource_files("mxready_runner").joinpath(filename).read_bytes(),
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
files.append(
|
|
97
|
+
(
|
|
98
|
+
"schemas/verification-result-v1.json",
|
|
99
|
+
verification_schema_bytes(),
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
output = io.BytesIO()
|
|
104
|
+
with zipfile.ZipFile(output, mode="w") as archive:
|
|
105
|
+
for name, content in files:
|
|
106
|
+
_write_deterministic_file(archive, name, content)
|
|
107
|
+
return output.getvalue()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _build_manifest(report: ScanReport) -> dict:
|
|
111
|
+
return {
|
|
112
|
+
"schema_version": "1.0",
|
|
113
|
+
"scan_id": str(report.scan_id),
|
|
114
|
+
"repository_url": report.repository.url,
|
|
115
|
+
"repository_commit": report.repository.commit,
|
|
116
|
+
"runner_version": __version__,
|
|
117
|
+
"checks": [
|
|
118
|
+
{
|
|
119
|
+
"id": check.id,
|
|
120
|
+
"command": check.command,
|
|
121
|
+
"timeout_seconds": check.timeout_seconds,
|
|
122
|
+
}
|
|
123
|
+
for check in DEFAULT_CHECKS
|
|
124
|
+
],
|
|
125
|
+
"project_commands": [],
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _write_deterministic_file(
|
|
130
|
+
archive: zipfile.ZipFile,
|
|
131
|
+
name: str,
|
|
132
|
+
content: bytes,
|
|
133
|
+
) -> None:
|
|
134
|
+
info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0))
|
|
135
|
+
info.compress_type = zipfile.ZIP_DEFLATED
|
|
136
|
+
info.external_attr = 0o644 << 16
|
|
137
|
+
archive.writestr(info, content)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import UTC, datetime, timedelta
|
|
5
|
+
|
|
6
|
+
from pydantic import ValidationError
|
|
7
|
+
|
|
8
|
+
from mxready.errors import MxReadyError
|
|
9
|
+
from mxready.models import ScanReport, VerificationRun, VerificationStatus
|
|
10
|
+
from mxready_runner.contracts import DEFAULT_CHECKS
|
|
11
|
+
|
|
12
|
+
MAX_VERIFICATION_UPLOAD_BYTES = 1_048_576
|
|
13
|
+
MAX_FUTURE_SKEW = timedelta(minutes=10)
|
|
14
|
+
VERIFICATION_MAX_AGE = timedelta(days=30)
|
|
15
|
+
_REQUIRED_HARDWARE_CHECKS = frozenset({"mx-smi", "pytorch-device"})
|
|
16
|
+
_REQUIRED_ENV_CHECK_IDS = frozenset(spec.id for spec in DEFAULT_CHECKS)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class ValidatedVerification:
|
|
21
|
+
run: VerificationRun
|
|
22
|
+
status: VerificationStatus
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def validate_verification_upload(
|
|
26
|
+
report: ScanReport,
|
|
27
|
+
payload: bytes,
|
|
28
|
+
*,
|
|
29
|
+
now: datetime,
|
|
30
|
+
) -> ValidatedVerification:
|
|
31
|
+
if len(payload) > MAX_VERIFICATION_UPLOAD_BYTES:
|
|
32
|
+
raise MxReadyError(
|
|
33
|
+
"UPLOAD_TOO_LARGE",
|
|
34
|
+
"Verification results must not exceed 1 MiB.",
|
|
35
|
+
)
|
|
36
|
+
try:
|
|
37
|
+
run = VerificationRun.model_validate_json(payload)
|
|
38
|
+
except (ValidationError, ValueError, UnicodeError) as error:
|
|
39
|
+
raise _invalid_schema() from error
|
|
40
|
+
|
|
41
|
+
if run.scan_id != report.scan_id:
|
|
42
|
+
raise MxReadyError(
|
|
43
|
+
"VERIFICATION_SCHEMA_INVALID",
|
|
44
|
+
"The verification result belongs to a different scan.",
|
|
45
|
+
)
|
|
46
|
+
if run.repository_commit != report.repository.commit:
|
|
47
|
+
raise MxReadyError(
|
|
48
|
+
"VERIFICATION_COMMIT_MISMATCH",
|
|
49
|
+
"The verification result was produced for a different commit.",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
now_utc = _require_aware(now, "current time").astimezone(UTC)
|
|
53
|
+
started_at = _require_aware(run.started_at, "start time").astimezone(UTC)
|
|
54
|
+
finished_at = _require_aware(run.finished_at, "finish time").astimezone(UTC)
|
|
55
|
+
if finished_at < started_at:
|
|
56
|
+
raise _invalid_schema()
|
|
57
|
+
if finished_at > now_utc + MAX_FUTURE_SKEW:
|
|
58
|
+
raise _invalid_schema()
|
|
59
|
+
|
|
60
|
+
if not _env_checks_complete(run):
|
|
61
|
+
raise _invalid_schema()
|
|
62
|
+
|
|
63
|
+
return ValidatedVerification(run=run, status=verification_status_at(run, now=now_utc))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def verification_status_at(run: VerificationRun, *, now: datetime) -> VerificationStatus:
|
|
67
|
+
"""Derive time-dependent status without mutating the historical result."""
|
|
68
|
+
if now.astimezone(UTC) - run.finished_at.astimezone(UTC) > VERIFICATION_MAX_AGE:
|
|
69
|
+
status = VerificationStatus.STALE
|
|
70
|
+
elif run.overall_status == "passed" and _hardware_checks_passed(run):
|
|
71
|
+
status = VerificationStatus.VERIFIED
|
|
72
|
+
else:
|
|
73
|
+
status = VerificationStatus.FAILED
|
|
74
|
+
return status
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _hardware_checks_passed(run: VerificationRun) -> bool:
|
|
78
|
+
passed = {check.id for check in run.checks if check.status == "passed"}
|
|
79
|
+
return passed >= _REQUIRED_HARDWARE_CHECKS
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _env_checks_complete(run: VerificationRun) -> bool:
|
|
83
|
+
"""Reject selectively-reported results that omit runner environment checks."""
|
|
84
|
+
submitted = {check.id for check in run.checks}
|
|
85
|
+
return submitted >= _REQUIRED_ENV_CHECK_IDS
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _require_aware(value: datetime, label: str) -> datetime:
|
|
89
|
+
if value.tzinfo is None or value.utcoffset() is None:
|
|
90
|
+
raise MxReadyError(
|
|
91
|
+
"VERIFICATION_SCHEMA_INVALID",
|
|
92
|
+
f"The verification {label} must include a UTC offset.",
|
|
93
|
+
)
|
|
94
|
+
return value
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _invalid_schema() -> MxReadyError:
|
|
98
|
+
return MxReadyError(
|
|
99
|
+
"VERIFICATION_SCHEMA_INVALID",
|
|
100
|
+
"The verification result does not match schema version 1.0.",
|
|
101
|
+
)
|