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.
Files changed (71) hide show
  1. mxready/__init__.py +1 -0
  2. mxready/api/__init__.py +4 -0
  3. mxready/api/middleware.py +149 -0
  4. mxready/api/rules.py +10 -0
  5. mxready/api/scans.py +164 -0
  6. mxready/api/workspace.py +156 -0
  7. mxready/app.py +179 -0
  8. mxready/cli/__init__.py +1 -0
  9. mxready/cli/build_verification_bundle.py +45 -0
  10. mxready/cli/generate_public_report.py +140 -0
  11. mxready/cli/scan_batch.py +368 -0
  12. mxready/cli/scan_repository.py +212 -0
  13. mxready/cli/serve.py +55 -0
  14. mxready/config.py +117 -0
  15. mxready/errors.py +11 -0
  16. mxready/logging_setup.py +88 -0
  17. mxready/mcp/__init__.py +5 -0
  18. mxready/mcp/server.py +433 -0
  19. mxready/models.py +382 -0
  20. mxready/persistence/__init__.py +1 -0
  21. mxready/persistence/sqlite.py +581 -0
  22. mxready/py.typed +0 -0
  23. mxready/reporting/__init__.py +4 -0
  24. mxready/reporting/badge.py +40 -0
  25. mxready/reporting/comparison.py +33 -0
  26. mxready/reporting/markdown.py +157 -0
  27. mxready/reporting/sarif.py +94 -0
  28. mxready/reporting/terminal.py +75 -0
  29. mxready/repository/__init__.py +14 -0
  30. mxready/repository/git_client.py +275 -0
  31. mxready/repository/github_archive.py +566 -0
  32. mxready/repository/identity.py +140 -0
  33. mxready/resources/__init__.py +19 -0
  34. mxready/resources/frontend/assets/index-BxJEwNUm.css +1 -0
  35. mxready/resources/frontend/assets/index-lzTCdWAt.js +9 -0
  36. mxready/resources/frontend/assets/mx-chip-C0HgFQyE.webp +0 -0
  37. mxready/resources/frontend/index.html +17 -0
  38. mxready/resources/rules/v1/core.yml +634 -0
  39. mxready/resources/rules/v1/manifest.yml +4 -0
  40. mxready/resources/schemas/verification-result-v1.json +259 -0
  41. mxready/scanning/__init__.py +17 -0
  42. mxready/scanning/analyzer.py +183 -0
  43. mxready/scanning/facts.py +596 -0
  44. mxready/scanning/indexer.py +299 -0
  45. mxready/scanning/path_context.py +174 -0
  46. mxready/scanning/python_source.py +67 -0
  47. mxready/scanning/rule_engine.py +595 -0
  48. mxready/scanning/rule_loader.py +282 -0
  49. mxready/scanning/toolchain.py +100 -0
  50. mxready/services/__init__.py +3 -0
  51. mxready/services/scans.py +239 -0
  52. mxready/services/worker.py +38 -0
  53. mxready/verification/__init__.py +11 -0
  54. mxready/verification/bundle.py +137 -0
  55. mxready/verification/validation.py +101 -0
  56. mxready-1.0.0.dist-info/METADATA +526 -0
  57. mxready-1.0.0.dist-info/RECORD +71 -0
  58. mxready-1.0.0.dist-info/WHEEL +5 -0
  59. mxready-1.0.0.dist-info/entry_points.txt +8 -0
  60. mxready-1.0.0.dist-info/licenses/LICENSE +201 -0
  61. mxready-1.0.0.dist-info/top_level.txt +2 -0
  62. mxready_runner/__init__.py +1 -0
  63. mxready_runner/__main__.py +3 -0
  64. mxready_runner/cli.py +88 -0
  65. mxready_runner/contracts.py +26 -0
  66. mxready_runner/execute.py +185 -0
  67. mxready_runner/inspect.py +79 -0
  68. mxready_runner/process.py +103 -0
  69. mxready_runner/py.typed +0 -0
  70. mxready_runner/redact.py +36 -0
  71. 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
+ )