lean-runtime 3.0.0__tar.gz → 3.0.2__tar.gz
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.
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/CHANGELOG.md +28 -0
- {lean_runtime-3.0.0/lean_runtime.egg-info → lean_runtime-3.0.2}/PKG-INFO +1 -1
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/portable-copies.md +10 -10
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/v1-precision.md +25 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/api.py +2 -1
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/engine.py +7 -1
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/probe.py +4 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/oci.py +14 -1
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/run_cli.py +6 -7
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/runtime.py +15 -3
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/toolchain_oci.py +23 -3
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/toolchains.py +15 -8
- {lean_runtime-3.0.0 → lean_runtime-3.0.2/lean_runtime.egg-info}/PKG-INFO +1 -1
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/pyproject.toml +1 -1
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/schemas/inspect-v1.schema.json +28 -5
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/discovery/test_engine.py +19 -1
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_bundles.py +15 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_prebuilt_policy.py +23 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_run_cli.py +25 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_schemas.py +26 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_toolchain_slim.py +100 -1
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/LICENSE +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/MANIFEST.in +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/README.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/catalog/environments.toml +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/catalog/locks/core-v4.32.2.lock.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/catalog/locks/leancert-v4.30.0.5.lock.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/catalog/locks/leancert-v4.31.0.lock.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/catalog/locks/leancert-v4.32.2.4.lock.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/catalog/locks/leancert-v4.33.0.lock.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/catalog/locks/mathlib-v4.30.0.lock.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/catalog/locks/mathlib-v4.31.0.lock.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/catalog/locks/mathlib-v4.32.2.lock.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/catalog/locks/mathlib-v4.33.0.lock.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/compatibility/README.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/compatibility/lake-artifact-cache-4.33.0.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/compatibility/mathlib-4.32.2.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/compatibility/mathlib-4.32.2.toml +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/compatibility/mathlib-4.33.0.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/compatibility/mathlib-4.33.0.toml +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/architecture.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/captures.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/case-study-v1.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/cli.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/development.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/environments.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/getting-started.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/index.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/lake-artifact-cache-design.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/local-projects.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/project-publishing.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/python-api.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/ready-programs.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/standalone-files.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/docs/trust-and-limitations.md +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/examples/mathlib.toml +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/__init__.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/__main__.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/_git.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/_paths.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/backends.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/bundles.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/capsules.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/cli.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/comparison.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/console.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/decisions.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/diagnostics.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/__init__.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/analyzer.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/candidate.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/catalog.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/catalog_build.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/catalog_cli.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/catalog_manifest.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/data/catalog.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/defaults.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/errors.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/module_inventory.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/planner.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/policy.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/py.typed +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/result.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/schema_resources.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/schemas/catalog-v1.schema.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/schemas/plan-v1.schema.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/discovery/schemas/result-v1.schema.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/environments.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/errors.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/events.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/facade.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/frontmatter.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/header_cache.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/health.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/identifier_resolver.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/lake.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/lake_cache.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/lockfiles.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/locking.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/matrix.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/models.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/oci_protocol.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/packs.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/policies.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/profiling.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/programs.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/project_execution.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/project_sharing.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/projects.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/publisher_verification.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/py.typed +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/references.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/resolver.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/schema_resources.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/serialization.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/shared_projects.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/specs.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/store.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/timings.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/toolchain_slim.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/verification.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime/wire.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime.egg-info/SOURCES.txt +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime.egg-info/dependency_links.txt +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime.egg-info/entry_points.txt +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime.egg-info/requires.txt +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/lean_runtime.egg-info/top_level.txt +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/mkdocs.yml +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/schemas/attestation-v1.schema.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/schemas/check-batch-v1.schema.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/schemas/cleanup-v1.schema.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/schemas/comparison-v1.schema.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/schemas/execution-v1.schema.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/schemas/matrix-v1.schema.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/schemas/plan-v1.schema.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/schemas/profile-v1.schema.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/schemas/publication-v1.schema.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/schemas/verify-v1.schema.json +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/scripts/announcement_gate.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/scripts/lake_cache_gate.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/scripts/registry_preflight.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/scripts/run_compatibility.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/scripts/run_v1_case_study.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/scripts/smoke_wheel.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/setup.cfg +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/conftest.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/discovery/e2e/test_real_discovery.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/discovery/test_analyzer.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/discovery/test_catalog.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/discovery/test_catalog_build.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/discovery/test_discovery_schema_resources.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/discovery/test_planner.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/discovery/test_policy.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_backend.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_capsules.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_cli.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_console.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_diagnostics.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_environment_integration.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_events_health.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_facade.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_frontmatter.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_git.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_header_cache.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_identifier_resolver.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_interactive.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_lake_cache.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_lockfiles.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_locking.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_models.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_multifile.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_oci_protocol.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_packs.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_paths.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_programs.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_project_publish.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_projects.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_references.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_runtime.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_schema_resources.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_signatures.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_specs.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_store.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_toolchain_oci.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_toolchains.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_v1_precision.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_verification_inventory.py +0 -0
- {lean_runtime-3.0.0 → lean_runtime-3.0.2}/tests/test_verification_probe.py +0 -0
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 3.0.2 - 2026-08-17
|
|
6
|
+
|
|
7
|
+
- Route the first check directly through a newly downloaded slim Lean runtime.
|
|
8
|
+
Command construction and executable hashing now re-evaluate toolchain state
|
|
9
|
+
after remote acquisition and ignore incomplete Elan toolchain directories,
|
|
10
|
+
preventing cold discovery from starting an unintended full toolchain install.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## 3.0.1 - 2026-08-17
|
|
14
|
+
|
|
15
|
+
- Point the portable-copy consuming examples at
|
|
16
|
+
`ghcr.io/alerad/lean-runtime-cache`, which serves check capsules a 3.0 client
|
|
17
|
+
can read, and make the publishing examples use the `ghcr.io/OWNER/...`
|
|
18
|
+
placeholder style the other pages use. The previous
|
|
19
|
+
`ghcr.io/alerad/leancert-runtime` examples named a library that now holds only
|
|
20
|
+
pre-capsule content.
|
|
21
|
+
- Make standalone discovery acquire the verified slim Lean runtime inside the
|
|
22
|
+
acquisition budget, leaving the candidate timeout exclusively for the Lean
|
|
23
|
+
compiler probe and preserving the specific timeout diagnostic.
|
|
24
|
+
- Make `run --offline` genuinely retained-only: a missing exact environment
|
|
25
|
+
now fails before source materialization, Elan installation, or network
|
|
26
|
+
access.
|
|
27
|
+
- Make every `run --explain --json` routing shape validate against the closed
|
|
28
|
+
`lean-runtime.inspect/v1` schema.
|
|
29
|
+
- Return exit 130 with a concise message when either `lean-run` or
|
|
30
|
+
`lean-runtime run` is interrupted during preparation.
|
|
31
|
+
|
|
32
|
+
|
|
5
33
|
## 3.0.0 - 2026-08-16
|
|
6
34
|
|
|
7
35
|
The first release with one canonical command vocabulary, capsule-only
|
|
@@ -46,7 +46,7 @@ specification or lock:
|
|
|
46
46
|
|
|
47
47
|
```python
|
|
48
48
|
runtime = Runtime(
|
|
49
|
-
libraries=["ghcr.io/alerad/
|
|
49
|
+
libraries=["ghcr.io/alerad/lean-runtime-cache"],
|
|
50
50
|
availability="auto",
|
|
51
51
|
)
|
|
52
52
|
environment = runtime.open_exact(lock)
|
|
@@ -55,7 +55,7 @@ environment = runtime.open_exact(lock)
|
|
|
55
55
|
The equivalent environment variables are:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
export LEAN_RUNTIME_LIBRARIES=ghcr.io/alerad/
|
|
58
|
+
export LEAN_RUNTIME_LIBRARIES=ghcr.io/alerad/lean-runtime-cache
|
|
59
59
|
export LEAN_RUNTIME_AVAILABILITY=auto
|
|
60
60
|
```
|
|
61
61
|
|
|
@@ -68,7 +68,7 @@ Explicit prefetch uses the same verified path:
|
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
70
|
lean-runtime \
|
|
71
|
-
--library ghcr.io/alerad/
|
|
71
|
+
--library ghcr.io/alerad/lean-runtime-cache \
|
|
72
72
|
download environment.lock.json
|
|
73
73
|
```
|
|
74
74
|
|
|
@@ -98,10 +98,10 @@ Actions identity:
|
|
|
98
98
|
|
|
99
99
|
```python
|
|
100
100
|
runtime = Runtime(
|
|
101
|
-
libraries=["ghcr.io/alerad/
|
|
101
|
+
libraries=["ghcr.io/alerad/lean-runtime-cache"],
|
|
102
102
|
publisher_verification="required",
|
|
103
103
|
trusted_publisher=(
|
|
104
|
-
"https://github.com/alerad/
|
|
104
|
+
"https://github.com/alerad/lean-runtime/.github/workflows/public-cache.yml@refs/heads/main"
|
|
105
105
|
),
|
|
106
106
|
trusted_issuer="https://token.actions.githubusercontent.com",
|
|
107
107
|
)
|
|
@@ -121,7 +121,7 @@ Check push access without building or uploading an environment:
|
|
|
121
121
|
|
|
122
122
|
```bash
|
|
123
123
|
lean-runtime publish environment \
|
|
124
|
-
--publish-to ghcr.io/
|
|
124
|
+
--publish-to ghcr.io/OWNER/lean-environments \
|
|
125
125
|
--check-access
|
|
126
126
|
```
|
|
127
127
|
|
|
@@ -156,7 +156,7 @@ export LEAN_RUNTIME_REGISTRY_USERNAME=alerad
|
|
|
156
156
|
export LEAN_RUNTIME_REGISTRY_PASSWORD="$GHCR_TOKEN"
|
|
157
157
|
|
|
158
158
|
lean-runtime publish environment environment.lock.json \
|
|
159
|
-
--publish-to ghcr.io/
|
|
159
|
+
--publish-to ghcr.io/OWNER/lean-environments \
|
|
160
160
|
--tag v4.32.2.4 \
|
|
161
161
|
--sign --attest
|
|
162
162
|
```
|
|
@@ -208,7 +208,7 @@ steps:
|
|
|
208
208
|
- uses: ./.github/actions/publish-environment
|
|
209
209
|
with:
|
|
210
210
|
lock: environment.lock.json
|
|
211
|
-
library: ghcr.io/${{ github.repository_owner }}/
|
|
211
|
+
library: ghcr.io/${{ github.repository_owner }}/lean-environments
|
|
212
212
|
tag: ${{ github.ref_name }}
|
|
213
213
|
username: ${{ github.actor }}
|
|
214
214
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -219,7 +219,7 @@ index and retain its JSON result:
|
|
|
219
219
|
|
|
220
220
|
```bash
|
|
221
221
|
lean-runtime publish environment environment.lock.json \
|
|
222
|
-
--publish-to ghcr.io/
|
|
222
|
+
--publish-to ghcr.io/OWNER/lean-environments \
|
|
223
223
|
--platform-only > platform-result.json
|
|
224
224
|
```
|
|
225
225
|
|
|
@@ -228,7 +228,7 @@ multi-platform index and human aliases:
|
|
|
228
228
|
|
|
229
229
|
```bash
|
|
230
230
|
lean-runtime finalize environment "$LOCK_ID" results/*.json \
|
|
231
|
-
--library ghcr.io/
|
|
231
|
+
--library ghcr.io/OWNER/lean-environments \
|
|
232
232
|
--tag "$GITHUB_REF_NAME"
|
|
233
233
|
```
|
|
234
234
|
|
|
@@ -103,6 +103,31 @@ improve without a schema change. Other `--json` commands (for example `storage`,
|
|
|
103
103
|
contracts. Exit code 0 means success, 1 is a completed negative
|
|
104
104
|
result, and 2 is invalid invocation or an exceptional runtime failure.
|
|
105
105
|
|
|
106
|
+
Schemas reference shared definitions by their canonical absolute IDs. To validate entirely
|
|
107
|
+
from the installed wheel, register all packaged schemas instead of allowing a validator to
|
|
108
|
+
retrieve those IDs over the network:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
import json
|
|
112
|
+
from pathlib import Path
|
|
113
|
+
|
|
114
|
+
import lean_runtime
|
|
115
|
+
from jsonschema import Draft202012Validator
|
|
116
|
+
from referencing import Registry, Resource
|
|
117
|
+
|
|
118
|
+
documents = [
|
|
119
|
+
json.loads(lean_runtime.schema_path(name).read_text()) for name in lean_runtime.SCHEMA_NAMES
|
|
120
|
+
]
|
|
121
|
+
registry = Registry().with_resources(
|
|
122
|
+
(document["$id"], Resource.from_contents(document)) for document in documents
|
|
123
|
+
)
|
|
124
|
+
schema = next(
|
|
125
|
+
document for document in documents if document["$id"].endswith("/execution-v1.schema.json")
|
|
126
|
+
)
|
|
127
|
+
value = json.loads(Path("result.execution.json").read_text())
|
|
128
|
+
Draft202012Validator(schema, registry=registry).validate(value)
|
|
129
|
+
```
|
|
130
|
+
|
|
106
131
|
## Reproducible case study
|
|
107
132
|
|
|
108
133
|
After preparing an exact environment, run:
|
|
@@ -58,8 +58,9 @@ class Discovery:
|
|
|
58
58
|
availability=availability,
|
|
59
59
|
libraries=libraries,
|
|
60
60
|
on_event=events.append,
|
|
61
|
+
allow_source_build=self.policy.allow_source_build,
|
|
61
62
|
)
|
|
62
|
-
if not self.policy.allow_source_build and runtime.
|
|
63
|
+
if not self.policy.allow_source_build and runtime.allow_source_build:
|
|
63
64
|
raise PolicyError(
|
|
64
65
|
"the injected Runtime permits source fallback but allow_source_build is false"
|
|
65
66
|
)
|
|
@@ -361,11 +361,17 @@ def discover(
|
|
|
361
361
|
if attempt.status == "lean_rejected"
|
|
362
362
|
for diagnostic in attempt.diagnostics
|
|
363
363
|
)
|
|
364
|
+
timeout_diagnostics = tuple(
|
|
365
|
+
diagnostic
|
|
366
|
+
for attempt in attempts
|
|
367
|
+
if attempt.status == "timeout"
|
|
368
|
+
for diagnostic in attempt.diagnostics
|
|
369
|
+
)
|
|
364
370
|
return DiscoveryResult(
|
|
365
371
|
status="not_found",
|
|
366
372
|
confidence="exhausted",
|
|
367
373
|
plan=plan,
|
|
368
374
|
attempts=tuple(attempts),
|
|
369
375
|
duration_seconds=time.monotonic() - started,
|
|
370
|
-
diagnostics=(reason, *rejection_diagnostics),
|
|
376
|
+
diagnostics=(*timeout_diagnostics, reason, *rejection_diagnostics),
|
|
371
377
|
)
|
|
@@ -94,6 +94,10 @@ class LeanRuntimeProbe:
|
|
|
94
94
|
import_roots=self.import_roots,
|
|
95
95
|
cancel=cancel,
|
|
96
96
|
)
|
|
97
|
+
# Acquisition is complete only when the exact candidate can invoke
|
|
98
|
+
# Lean without downloading or installing anything during the
|
|
99
|
+
# compiler probe.
|
|
100
|
+
self.runtime.toolchains.ensure(candidate.entry.toolchain, cancel=cancel)
|
|
97
101
|
except MaterializationError as exc:
|
|
98
102
|
if exc.phase in {"lock-validation", "source-validation", "bundle-validation"}:
|
|
99
103
|
raise ProbeIntegrityFailure(str(exc)) from exc
|
|
@@ -10,6 +10,7 @@ import re
|
|
|
10
10
|
import shutil
|
|
11
11
|
import subprocess
|
|
12
12
|
import tempfile
|
|
13
|
+
import threading
|
|
13
14
|
import time
|
|
14
15
|
import urllib.error
|
|
15
16
|
import urllib.parse
|
|
@@ -415,6 +416,8 @@ class OCIRegistryClient:
|
|
|
415
416
|
descriptor: dict[str, Any],
|
|
416
417
|
store: EnvironmentStore,
|
|
417
418
|
events: EventEmitter,
|
|
419
|
+
*,
|
|
420
|
+
cancel: threading.Event | None = None,
|
|
418
421
|
) -> Path:
|
|
419
422
|
digest = descriptor.get("digest")
|
|
420
423
|
size = descriptor.get("size")
|
|
@@ -422,7 +425,13 @@ class OCIRegistryClient:
|
|
|
422
425
|
if match is None or not isinstance(size, int) or size < 0:
|
|
423
426
|
raise EnvironmentError("OCI manifest contains an invalid blob descriptor")
|
|
424
427
|
destination = store.oci_blobs / match.group(1)
|
|
425
|
-
with FileLock(
|
|
428
|
+
with FileLock(
|
|
429
|
+
store.lock_dir / f"oci-{match.group(1)}.lock",
|
|
430
|
+
timeout=1800,
|
|
431
|
+
cancel=cancel,
|
|
432
|
+
):
|
|
433
|
+
if cancel is not None and cancel.is_set():
|
|
434
|
+
raise EnvironmentError("OCI blob download was cancelled")
|
|
426
435
|
if destination.is_file() and destination.stat().st_size == size:
|
|
427
436
|
if _digest_path(destination) == digest:
|
|
428
437
|
events.emit(
|
|
@@ -432,6 +441,8 @@ class OCIRegistryClient:
|
|
|
432
441
|
destination.unlink()
|
|
433
442
|
temporary = destination.with_name(f".{destination.name}.partial")
|
|
434
443
|
for attempt in range(1, _BLOB_INTEGRITY_ATTEMPTS + 1):
|
|
444
|
+
if cancel is not None and cancel.is_set():
|
|
445
|
+
raise EnvironmentError("OCI blob download was cancelled")
|
|
435
446
|
if temporary.exists() and temporary.stat().st_size > size:
|
|
436
447
|
temporary.unlink()
|
|
437
448
|
offset = temporary.stat().st_size if temporary.exists() else 0
|
|
@@ -470,6 +481,8 @@ class OCIRegistryClient:
|
|
|
470
481
|
last_progress = 0.0
|
|
471
482
|
with response, temporary.open(mode) as output:
|
|
472
483
|
while chunk := response.read(1024 * 1024):
|
|
484
|
+
if cancel is not None and cancel.is_set():
|
|
485
|
+
raise EnvironmentError("OCI blob download was cancelled")
|
|
473
486
|
written += len(chunk)
|
|
474
487
|
if written > size:
|
|
475
488
|
raise EnvironmentError("OCI blob exceeds its declared size")
|
|
@@ -397,13 +397,7 @@ def run(arguments: argparse.Namespace, *, command_name: str = "lean-run") -> int
|
|
|
397
397
|
if arguments.json:
|
|
398
398
|
print(
|
|
399
399
|
json.dumps(
|
|
400
|
-
|
|
401
|
-
"schema": "lean-runtime.inspect/v1",
|
|
402
|
-
"ok": True,
|
|
403
|
-
"data": explanation,
|
|
404
|
-
"warnings": [],
|
|
405
|
-
"errors": [],
|
|
406
|
-
},
|
|
400
|
+
envelope("lean-runtime.inspect/v1", ok=True, data=explanation),
|
|
407
401
|
ensure_ascii=False,
|
|
408
402
|
indent=2,
|
|
409
403
|
sort_keys=True,
|
|
@@ -443,6 +437,7 @@ def run(arguments: argparse.Namespace, *, command_name: str = "lean-run") -> int
|
|
|
443
437
|
availability=availability,
|
|
444
438
|
libraries=libraries,
|
|
445
439
|
max_download_bytes=arguments.max_download,
|
|
440
|
+
allow_source_build=not arguments.offline and not arguments.no_source_build,
|
|
446
441
|
)
|
|
447
442
|
policy = ExecutionPolicy(timeout_seconds=arguments.check_timeout)
|
|
448
443
|
if context.lock is not None:
|
|
@@ -528,6 +523,10 @@ def run(arguments: argparse.Namespace, *, command_name: str = "lean-run") -> int
|
|
|
528
523
|
show_timings=arguments.timings,
|
|
529
524
|
)
|
|
530
525
|
return 0 if result.ok else 1
|
|
526
|
+
except KeyboardInterrupt:
|
|
527
|
+
renderer.close()
|
|
528
|
+
print(f"{command_name}: interrupted", file=sys.stderr)
|
|
529
|
+
return 130
|
|
531
530
|
except (DiscoveryError, LeanRuntimeError, OSError, UnicodeError, ValueError) as exc:
|
|
532
531
|
renderer.close()
|
|
533
532
|
if arguments.json:
|
|
@@ -209,6 +209,7 @@ class Runtime:
|
|
|
209
209
|
availability: str | None = None,
|
|
210
210
|
libraries: Sequence[str] | None = None,
|
|
211
211
|
max_download_bytes: int | None = None,
|
|
212
|
+
allow_source_build: bool = True,
|
|
212
213
|
publisher_verification: str = "ignore",
|
|
213
214
|
trusted_publisher: str | None = None,
|
|
214
215
|
trusted_issuer: str | None = None,
|
|
@@ -255,6 +256,7 @@ class Runtime:
|
|
|
255
256
|
else DEFAULT_ENVIRONMENT_LIBRARIES
|
|
256
257
|
)
|
|
257
258
|
self.availability = availability
|
|
259
|
+
self.allow_source_build = allow_source_build
|
|
258
260
|
self.max_download_bytes = max_download_bytes
|
|
259
261
|
self.verification_executable = verification_tool
|
|
260
262
|
self.signature_verifier = (
|
|
@@ -286,7 +288,11 @@ class Runtime:
|
|
|
286
288
|
self.toolchains.remote_ensure = self._acquire_check_toolchain
|
|
287
289
|
self.environments.sparse_acquirer = self._acquire_sparse_modules
|
|
288
290
|
|
|
289
|
-
def _acquire_check_toolchain(
|
|
291
|
+
def _acquire_check_toolchain(
|
|
292
|
+
self,
|
|
293
|
+
toolchain: str,
|
|
294
|
+
cancel: threading.Event | None = None,
|
|
295
|
+
) -> bool:
|
|
290
296
|
if self.availability == "local":
|
|
291
297
|
raise ToolchainError(
|
|
292
298
|
f"toolchain {normalize_toolchain(toolchain)!r} is not available locally; "
|
|
@@ -294,7 +300,7 @@ class Runtime:
|
|
|
294
300
|
)
|
|
295
301
|
for library in self.toolchain_libraries:
|
|
296
302
|
try:
|
|
297
|
-
return library.pull(toolchain)
|
|
303
|
+
return library.pull(toolchain, cancel=cancel)
|
|
298
304
|
except DownloadUnavailable:
|
|
299
305
|
continue
|
|
300
306
|
return False
|
|
@@ -347,6 +353,7 @@ class Runtime:
|
|
|
347
353
|
) -> Environment:
|
|
348
354
|
environment_id = environment_identity(lock, build_profile)
|
|
349
355
|
destination = self.store.environment_path(environment_id)
|
|
356
|
+
imported = False
|
|
350
357
|
if self.max_download_bytes is not None and self.availability != "local":
|
|
351
358
|
preflight = self.plan_exact(
|
|
352
359
|
lock,
|
|
@@ -377,7 +384,6 @@ class Runtime:
|
|
|
377
384
|
"lean-run --plan"
|
|
378
385
|
)
|
|
379
386
|
if not destination.is_dir() and self.availability != "local":
|
|
380
|
-
imported = False
|
|
381
387
|
rejections: list[str] = []
|
|
382
388
|
for library in self.libraries:
|
|
383
389
|
try:
|
|
@@ -412,6 +418,12 @@ class Runtime:
|
|
|
412
418
|
capability="source_build",
|
|
413
419
|
environment_id=environment_id,
|
|
414
420
|
)
|
|
421
|
+
if not destination.is_dir() and not imported and not self.allow_source_build:
|
|
422
|
+
raise EnvironmentError(
|
|
423
|
+
f"environment {environment_id} is not retained locally; offline mode "
|
|
424
|
+
"does not permit downloading, toolchain installation, or source "
|
|
425
|
+
"materialization"
|
|
426
|
+
)
|
|
415
427
|
return self.environments.ensure(
|
|
416
428
|
lock,
|
|
417
429
|
name=name,
|
|
@@ -10,6 +10,7 @@ import shutil
|
|
|
10
10
|
import subprocess
|
|
11
11
|
import tarfile
|
|
12
12
|
import tempfile
|
|
13
|
+
import threading
|
|
13
14
|
from dataclasses import dataclass
|
|
14
15
|
from pathlib import Path, PurePosixPath
|
|
15
16
|
from typing import Any
|
|
@@ -314,16 +315,35 @@ class OCIToolchainLibrary:
|
|
|
314
315
|
dict(config["slim_manifest"]),
|
|
315
316
|
)
|
|
316
317
|
|
|
317
|
-
def pull(
|
|
318
|
+
def pull(
|
|
319
|
+
self,
|
|
320
|
+
toolchain: str,
|
|
321
|
+
*,
|
|
322
|
+
cancel: threading.Event | None = None,
|
|
323
|
+
) -> bool:
|
|
318
324
|
local_check = getattr(self.toolchains, "is_available_locally", None)
|
|
319
325
|
if callable(local_check) and local_check(toolchain):
|
|
320
326
|
return True
|
|
321
327
|
plan = self.plan(toolchain)
|
|
322
328
|
self.client.cache_verified_blob(plan.config_data, plan.config_descriptor, self.store)
|
|
323
|
-
|
|
329
|
+
try:
|
|
330
|
+
layer = self.client.download_blob(
|
|
331
|
+
plan.descriptor,
|
|
332
|
+
self.store,
|
|
333
|
+
self.events,
|
|
334
|
+
cancel=cancel,
|
|
335
|
+
)
|
|
336
|
+
except EnvironmentError as exc:
|
|
337
|
+
if cancel is not None and cancel.is_set():
|
|
338
|
+
raise ToolchainError(
|
|
339
|
+
f"Lean toolchain download was cancelled: {normalize_toolchain(toolchain)!r}"
|
|
340
|
+
) from exc
|
|
341
|
+
raise
|
|
324
342
|
destination = self.toolchains.slim_path(toolchain)
|
|
325
343
|
with FileLock(
|
|
326
|
-
self.store.lock_dir / f"toolchain-{toolchain_reference(toolchain)}.lock",
|
|
344
|
+
self.store.lock_dir / f"toolchain-{toolchain_reference(toolchain)}.lock",
|
|
345
|
+
timeout=1800,
|
|
346
|
+
cancel=cancel,
|
|
327
347
|
):
|
|
328
348
|
if self.toolchains.is_available_locally(toolchain):
|
|
329
349
|
return True
|
|
@@ -81,7 +81,7 @@ class ToolchainManager:
|
|
|
81
81
|
self.home = Path(home).expanduser().resolve() if home else default_runtime_home()
|
|
82
82
|
self.elan_home = self.home / "elan"
|
|
83
83
|
self.events = events or EventEmitter()
|
|
84
|
-
self.remote_ensure: Callable[[str], bool] | None = None
|
|
84
|
+
self.remote_ensure: Callable[[str, threading.Event | None], bool] | None = None
|
|
85
85
|
self._executable_digests: dict[str, tuple[int, int, str]] = {}
|
|
86
86
|
|
|
87
87
|
@property
|
|
@@ -264,7 +264,7 @@ class ToolchainManager:
|
|
|
264
264
|
name = normalize_toolchain(toolchain)
|
|
265
265
|
if self.has_slim(name) or (self._elan_toolchain_dir(name) / "bin" / "lean").is_file():
|
|
266
266
|
return name
|
|
267
|
-
if self.remote_ensure is not None and self.remote_ensure(name):
|
|
267
|
+
if self.remote_ensure is not None and self.remote_ensure(name, cancel):
|
|
268
268
|
if not self.has_slim(name):
|
|
269
269
|
raise ToolchainError("remote toolchain acquisition returned without a slim copy")
|
|
270
270
|
return name
|
|
@@ -325,26 +325,33 @@ class ToolchainManager:
|
|
|
325
325
|
A full Elan installation is preferred; when only a verified slim copy
|
|
326
326
|
remains, its executables are invoked directly.
|
|
327
327
|
"""
|
|
328
|
-
name =
|
|
329
|
-
|
|
328
|
+
name = self.ensure(toolchain)
|
|
329
|
+
full = self._elan_toolchain_dir(name)
|
|
330
|
+
full_lean = full / "bin" / "lean"
|
|
331
|
+
if os.name == "nt":
|
|
332
|
+
full_lean = full_lean.with_suffix(".exe")
|
|
333
|
+
if self.has_slim(name) and not full_lean.is_file():
|
|
330
334
|
binary = self.slim_path(name) / "bin" / executable
|
|
335
|
+
if os.name == "nt":
|
|
336
|
+
binary = binary.with_suffix(".exe")
|
|
331
337
|
if not binary.is_file():
|
|
332
338
|
raise ToolchainError(
|
|
333
339
|
f"slim toolchain {name!r} does not provide {executable!r}; "
|
|
334
340
|
"reinstall the full toolchain for this operation"
|
|
335
341
|
)
|
|
336
342
|
return [str(binary), *args]
|
|
337
|
-
name = self.ensure(name)
|
|
338
343
|
return [str(self.elan_path()), "run", name, executable, *args]
|
|
339
344
|
|
|
340
345
|
def executable_digest(self, toolchain: str, executable: str) -> str:
|
|
341
346
|
"""Hash the exact resolved executable for compatibility-cache identities."""
|
|
342
|
-
name =
|
|
347
|
+
name = self.ensure(toolchain)
|
|
343
348
|
full = self._elan_toolchain_dir(name)
|
|
344
|
-
|
|
349
|
+
full_lean = full / "bin" / "lean"
|
|
350
|
+
if os.name == "nt":
|
|
351
|
+
full_lean = full_lean.with_suffix(".exe")
|
|
352
|
+
if self.has_slim(name) and not full_lean.is_file():
|
|
345
353
|
binary = self.slim_path(name) / "bin" / executable
|
|
346
354
|
else:
|
|
347
|
-
self.ensure(name)
|
|
348
355
|
binary = full / "bin" / executable
|
|
349
356
|
if os.name == "nt" and not binary.is_file():
|
|
350
357
|
binary = binary.with_suffix(".exe")
|
|
@@ -8,8 +8,19 @@
|
|
|
8
8
|
"schema": {"const": "lean-runtime.inspect/v1"},
|
|
9
9
|
"ok": {"type": "boolean"},
|
|
10
10
|
"data": {
|
|
11
|
+
"oneOf": [
|
|
12
|
+
{"$ref": "#/$defs/inspectionData"},
|
|
13
|
+
{"$ref": "#/$defs/routingData"},
|
|
14
|
+
{"type": "object", "maxProperties": 0}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"warnings": {"$ref": "https://lean-runtime.dev/schemas/execution-v1.schema.json#/$defs/issues"},
|
|
18
|
+
"errors": {"$ref": "https://lean-runtime.dev/schemas/execution-v1.schema.json#/$defs/issues"}
|
|
19
|
+
},
|
|
20
|
+
"$defs": {
|
|
21
|
+
"inspectionData": {
|
|
11
22
|
"type": "object",
|
|
12
|
-
"
|
|
23
|
+
"required": ["subject", "subject_kind", "lock_id", "environment", "package_locks", "decisions"],
|
|
13
24
|
"additionalProperties": false,
|
|
14
25
|
"properties": {
|
|
15
26
|
"subject": {"type": "string"},
|
|
@@ -20,10 +31,22 @@
|
|
|
20
31
|
"decisions": {"type": "array", "items": {"$ref": "#/$defs/decision"}}
|
|
21
32
|
}
|
|
22
33
|
},
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
"routingData": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"required": ["decision", "context", "subject"],
|
|
37
|
+
"additionalProperties": false,
|
|
38
|
+
"properties": {
|
|
39
|
+
"decision": {"enum": ["context_selected", "automatic_discovery"]},
|
|
40
|
+
"context": {"type": "string"},
|
|
41
|
+
"subject": {
|
|
42
|
+
"oneOf": [
|
|
43
|
+
{"type": "string"},
|
|
44
|
+
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"plan": {"type": "object"}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
27
50
|
"environment": {
|
|
28
51
|
"type": "object",
|
|
29
52
|
"required": ["environment_id", "lock_id", "toolchain", "packages", "platform", "build_profile", "status", "created_at", "names"],
|
|
@@ -222,6 +222,8 @@ def test_per_candidate_timeout_is_enforced(sample_catalog) -> None: # type: ign
|
|
|
222
222
|
).discover_and_check("import Mathlib\n")
|
|
223
223
|
assert result.status == "not_found"
|
|
224
224
|
assert result.attempts[0].status == "timeout"
|
|
225
|
+
assert result.diagnostics[0].code == "CANDIDATE_TIMEOUT"
|
|
226
|
+
assert "time budget" in result.diagnostics[0].detail
|
|
225
227
|
assert result.duration_seconds < 0.5
|
|
226
228
|
|
|
227
229
|
|
|
@@ -305,6 +307,17 @@ def test_lean_runtime_probe_passes_the_acquisition_budget_to_builds(sample_catal
|
|
|
305
307
|
|
|
306
308
|
captured: dict[str, float] = {}
|
|
307
309
|
|
|
310
|
+
class Toolchains:
|
|
311
|
+
def ensure(
|
|
312
|
+
self,
|
|
313
|
+
toolchain: str,
|
|
314
|
+
*,
|
|
315
|
+
cancel: threading.Event | None = None,
|
|
316
|
+
) -> str:
|
|
317
|
+
assert cancel is not None
|
|
318
|
+
captured["toolchain_ready"] = 1
|
|
319
|
+
return toolchain
|
|
320
|
+
|
|
308
321
|
def open_exact( # type: ignore[no-untyped-def]
|
|
309
322
|
lock, *, build_timeout, import_roots=(), cancel=None
|
|
310
323
|
):
|
|
@@ -312,11 +325,16 @@ def test_lean_runtime_probe_passes_the_acquisition_budget_to_builds(sample_catal
|
|
|
312
325
|
captured["build_timeout"] = build_timeout
|
|
313
326
|
return SimpleNamespace(id="env_fake")
|
|
314
327
|
|
|
315
|
-
runtime = SimpleNamespace(
|
|
328
|
+
runtime = SimpleNamespace(
|
|
329
|
+
open_exact=open_exact,
|
|
330
|
+
availability="auto",
|
|
331
|
+
toolchains=Toolchains(),
|
|
332
|
+
)
|
|
316
333
|
probe = LeanRuntimeProbe(runtime=runtime) # type: ignore[arg-type]
|
|
317
334
|
candidate = Discovery(catalog=sample_catalog).plan("import Mathlib\n").candidates[0]
|
|
318
335
|
acquired = probe.acquire(candidate, timeout_seconds=123.0, cancel=threading.Event())
|
|
319
336
|
assert captured["build_timeout"] == 123.0
|
|
337
|
+
assert captured["toolchain_ready"] == 1
|
|
320
338
|
assert acquired.environment_id == "env_fake"
|
|
321
339
|
|
|
322
340
|
|
|
@@ -657,6 +657,21 @@ def test_oci_blob_integrity_failure_retries_once_from_scratch(tmp_path: Path) ->
|
|
|
657
657
|
thread.join(timeout=5)
|
|
658
658
|
|
|
659
659
|
|
|
660
|
+
def test_oci_blob_download_honors_preexisting_cancellation(tmp_path: Path) -> None:
|
|
661
|
+
cancel = threading.Event()
|
|
662
|
+
cancel.set()
|
|
663
|
+
store = EnvironmentStore(tmp_path / "consumer")
|
|
664
|
+
repository = OCIRepository.parse("oci+http://127.0.0.1:1/owner/cache")
|
|
665
|
+
with pytest.raises(EnvironmentError, match="cancelled"):
|
|
666
|
+
OCIRegistryClient(repository).download_blob(
|
|
667
|
+
{"digest": "sha256:" + "a" * 64, "size": 1},
|
|
668
|
+
store,
|
|
669
|
+
EventEmitter(),
|
|
670
|
+
cancel=cancel,
|
|
671
|
+
)
|
|
672
|
+
assert not list(store.oci_blobs.iterdir())
|
|
673
|
+
|
|
674
|
+
|
|
660
675
|
def _publish_full_layout(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
661
676
|
"""Let publisher transport tests skip capsule construction.
|
|
662
677
|
|
|
@@ -102,6 +102,29 @@ def test_require_rejects_missing_cache_configuration(tmp_path: Path) -> None:
|
|
|
102
102
|
runtime.open_exact(_lock())
|
|
103
103
|
|
|
104
104
|
|
|
105
|
+
def test_retained_only_open_rejects_before_materialization(
|
|
106
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
107
|
+
) -> None:
|
|
108
|
+
runtime = Runtime(
|
|
109
|
+
home=tmp_path,
|
|
110
|
+
availability="local",
|
|
111
|
+
libraries=[],
|
|
112
|
+
allow_source_build=False,
|
|
113
|
+
)
|
|
114
|
+
called = False
|
|
115
|
+
|
|
116
|
+
def materialize(*_args: object, **_kwargs: object) -> object:
|
|
117
|
+
nonlocal called
|
|
118
|
+
called = True
|
|
119
|
+
return object()
|
|
120
|
+
|
|
121
|
+
monkeypatch.setattr(runtime.environments, "ensure", materialize)
|
|
122
|
+
with pytest.raises(EnvironmentError, match="not retained locally"):
|
|
123
|
+
runtime.open_exact(_lock())
|
|
124
|
+
assert not called
|
|
125
|
+
assert not runtime.toolchains.elan_home.exists()
|
|
126
|
+
|
|
127
|
+
|
|
105
128
|
def test_registry_redirect_does_not_forward_authorization_cross_host() -> None:
|
|
106
129
|
request = urllib.request.Request("https://registry.example/v2/owner/cache/blobs/sha256:x")
|
|
107
130
|
request.add_header("Authorization", "Bearer secret")
|
|
@@ -483,6 +483,31 @@ def test_run_subcommand_rejects_contradictory_availability(
|
|
|
483
483
|
assert "--offline cannot be combined" in capsys.readouterr().err
|
|
484
484
|
|
|
485
485
|
|
|
486
|
+
def test_offline_run_disables_source_materialization(monkeypatch, tmp_path: Path) -> None:
|
|
487
|
+
source = tmp_path / "Main.lean"
|
|
488
|
+
source.write_text("example : True := by trivial\n")
|
|
489
|
+
monkeypatch.setattr("lean_runtime.run_cli.Runtime", FakeRuntime)
|
|
490
|
+
assert main([str(source), "--toolchain", "v4.32.2", "--offline", "--quiet"]) == 0
|
|
491
|
+
assert FakeRuntime.instance.kwargs["availability"] == "local"
|
|
492
|
+
assert FakeRuntime.instance.kwargs["libraries"] == ()
|
|
493
|
+
assert FakeRuntime.instance.kwargs["allow_source_build"] is False
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def test_lean_run_interrupt_has_no_traceback(monkeypatch, tmp_path: Path, capsys) -> None:
|
|
497
|
+
source = tmp_path / "Main.lean"
|
|
498
|
+
source.write_text("example : True := by trivial\n")
|
|
499
|
+
|
|
500
|
+
class InterruptedRuntime:
|
|
501
|
+
def __init__(self, **_kwargs) -> None:
|
|
502
|
+
raise KeyboardInterrupt
|
|
503
|
+
|
|
504
|
+
monkeypatch.setattr("lean_runtime.run_cli.Runtime", InterruptedRuntime)
|
|
505
|
+
assert main([str(source), "--toolchain", "v4.32.2"]) == 130
|
|
506
|
+
captured = capsys.readouterr()
|
|
507
|
+
assert captured.out == ""
|
|
508
|
+
assert captured.err == "lean-run: interrupted\n"
|
|
509
|
+
|
|
510
|
+
|
|
486
511
|
def test_run_subcommand_forwards_global_libraries(monkeypatch, tmp_path: Path) -> None:
|
|
487
512
|
from lean_runtime.cli import main as lean_runtime_main
|
|
488
513
|
|