lean-runtime 4.22.0__tar.gz → 4.24.0__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-4.22.0 → lean_runtime-4.24.0}/CHANGELOG.md +15 -0
- {lean_runtime-4.22.0/lean_runtime.egg-info → lean_runtime-4.24.0}/PKG-INFO +1 -1
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/cli.py +108 -15
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/engine.py +109 -2
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/environments.py +17 -4
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/health.py +15 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/lake_cache.py +2 -2
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/oci.py +29 -1
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/project_execution.py +73 -3
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/project_sharing.py +36 -1
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/projects.py +1 -1
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/references.py +1 -1
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/resolver.py +1 -1
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/run_cli.py +12 -6
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/runtime.py +38 -21
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/shared_projects.py +125 -13
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/store.py +58 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/toolchain_oci.py +11 -1
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/toolchains.py +68 -17
- {lean_runtime-4.22.0 → lean_runtime-4.24.0/lean_runtime.egg-info}/PKG-INFO +1 -1
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime.egg-info/SOURCES.txt +1 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/pyproject.toml +1 -1
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/schemas/cleanup-v1.schema.json +2 -1
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/discovery/test_engine.py +106 -1
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_bundles.py +48 -2
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_cli.py +226 -1
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_events_health.py +1 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_header_cache.py +4 -1
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_interactive.py +3 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_lake_cache.py +9 -0
- lean_runtime-4.24.0/tests/test_project_bootstrap.py +106 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_projects.py +124 -14
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_references.py +3 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_run_cli.py +28 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_runtime.py +3 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_store.py +25 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_toolchain_oci.py +3 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_toolchain_slim.py +2 -9
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_toolchains.py +74 -1
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/LICENSE +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/MANIFEST.in +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/README.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/catalog/environments.toml +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/catalog/locks/core-v4.32.2.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/catalog/locks/leancert-v4.30.0.5.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/catalog/locks/leancert-v4.31.0.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/catalog/locks/leancert-v4.32.2.4.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/catalog/locks/leancert-v4.33.0.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/catalog/locks/mathlib-v4.30.0.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/catalog/locks/mathlib-v4.31.0.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/catalog/locks/mathlib-v4.32.2.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/catalog/locks/mathlib-v4.33.0.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/catalog/locks/mathlib-v4.33.1.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/compatibility/README.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/compatibility/lake-artifact-cache-4.33.0.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/compatibility/mathlib-4.32.2.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/compatibility/mathlib-4.32.2.toml +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/compatibility/mathlib-4.33.0.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/compatibility/mathlib-4.33.0.toml +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/docs/concepts/context-selection.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/docs/index.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/docs/reference/commands.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/docs/reference/python-advanced.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/docs/reference/python-api.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/docs/reference/storage.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/docs/trust.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/docs/tutorial.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/docs/workflows/check-files.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/docs/workflows/lake-projects.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/examples/mathlib.toml +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/__init__.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/__main__.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/_git.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/_paths.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/backends.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/bundles.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/capsules.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/comparison.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/console.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/context_resolution.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/decisions.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/declaration_index.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/declaration_index_build.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/declaration_index_oci.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/diagnostics.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/__init__.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/analyzer.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/api.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/candidate.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/catalog.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/catalog_build.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/catalog_cli.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/catalog_manifest.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/data/catalog.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/defaults.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/errors.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/history.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/memo.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/module_inventory.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/planner.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/policy.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/probe.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/py.typed +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/result.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/schema_resources.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/schemas/catalog-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/schemas/plan-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/discovery/schemas/result-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/errors.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/events.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/facade.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/frontmatter.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/header_cache.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/identifier_resolver.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/import_syntax.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/lake.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/lockfiles.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/locking.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/matrix.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/models.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/oci_protocol.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/packs.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/policies.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/profiling.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/programs.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/publisher_verification.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/py.typed +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/schema_resources.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/serialization.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/specs.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/timings.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/toolchain_slim.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/verification.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime/wire.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime.egg-info/dependency_links.txt +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime.egg-info/entry_points.txt +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime.egg-info/requires.txt +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/lean_runtime.egg-info/top_level.txt +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/mkdocs.yml +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/schemas/attestation-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/schemas/check-batch-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/schemas/comparison-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/schemas/execution-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/schemas/inspect-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/schemas/matrix-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/schemas/plan-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/schemas/profile-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/schemas/publication-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/schemas/verify-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/scripts/announcement_gate.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/scripts/catalog_update.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/scripts/lake_cache_gate.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/scripts/prepare_declaration_index.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/scripts/publish_declaration_index.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/scripts/registry_preflight.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/scripts/run_compatibility.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/scripts/run_v1_case_study.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/scripts/smoke_wheel.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/setup.cfg +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/conftest.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/discovery/e2e/test_real_discovery.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/discovery/test_analyzer.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/discovery/test_catalog.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/discovery/test_catalog_build.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/discovery/test_discovery_schema_resources.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/discovery/test_memo.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/discovery/test_planner.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/discovery/test_policy.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_announcement_gate.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_backend.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_capsules.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_console.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_context_resolution.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_declaration_index.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_diagnostics.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_environment_integration.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_facade.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_frontmatter.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_git.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_identifier_resolver.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_lockfiles.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_locking.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_models.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_multifile.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_oci_protocol.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_packs.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_paths.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_prebuilt_policy.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_programs.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_project_publish.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_schema_resources.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_schemas.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_signatures.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_specs.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_v1_precision.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_verification_inventory.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.24.0}/tests/test_verification_probe.py +0 -0
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 4.24.0 - 2026-08-26
|
|
6
|
+
|
|
7
|
+
- Key shared Lake package artifacts by the exact installed Lean and Lake
|
|
8
|
+
executable digests. Existing `/1` package artifacts are source-only donors;
|
|
9
|
+
the first build of each project re-shares artifacts under the `/2` key.
|
|
10
|
+
- Let an exact registered dependency graph donate source across toolchain
|
|
11
|
+
versions, while stripping its build tree, and refresh existing project
|
|
12
|
+
attachments when their computed workspace identity changes.
|
|
13
|
+
- Report obsolete `/1` project artifacts in `doctor`, reclaim them through
|
|
14
|
+
`clean`, and refuse publishing moving toolchain spellings such as `master`
|
|
15
|
+
or undated `nightly`. Publication also fails early when its input is an
|
|
16
|
+
acquired sparse capsule rather than a source build.
|
|
17
|
+
|
|
18
|
+
## 4.23.0 - 2026-08-24
|
|
19
|
+
|
|
5
20
|
## 4.22.0 - 2026-08-23
|
|
6
21
|
|
|
7
22
|
## 4.21.0 - 2026-08-23
|
|
@@ -119,18 +119,20 @@ def _render_storage(status: StoreStatus) -> None:
|
|
|
119
119
|
("Executions", status.executions, status.executions_bytes),
|
|
120
120
|
("Scratch", status.scratch_workspaces, status.scratch_bytes),
|
|
121
121
|
)
|
|
122
|
+
label_width = max(len(label) for label, _count, _bytes in rows)
|
|
123
|
+
count_width = max(8, *(len(str(count)) for _label, count, _bytes in rows if count is not None))
|
|
122
124
|
for label, count, bytes_used in rows:
|
|
123
|
-
counted = f"{count:>
|
|
125
|
+
counted = f"{count:>{count_width}}" if count is not None else " " * count_width
|
|
124
126
|
size_column = style.cyan(f"{format_byte_size(bytes_used):>10}")
|
|
125
|
-
print(f" {label:<
|
|
126
|
-
total_label = style.bold(f"{'Materialized':<
|
|
127
|
+
print(f" {label:<{label_width}} {counted} {size_column}")
|
|
128
|
+
total_label = style.bold(f"{'Materialized':<{label_width}}")
|
|
127
129
|
total_size = style.bold(f"{format_byte_size(status.bytes_used):>10}")
|
|
128
130
|
free_note = style.dim(f"({format_byte_size(status.bytes_free)} free on disk)")
|
|
129
|
-
print(f" {total_label}
|
|
131
|
+
print(f" {total_label} {' ' * count_width} {total_size} {free_note}")
|
|
130
132
|
if status.allocated_bytes:
|
|
131
|
-
allocated_label = style.bold(f"{'Allocated*':<
|
|
133
|
+
allocated_label = style.bold(f"{'Allocated*':<{label_width}}")
|
|
132
134
|
allocated_size = style.bold(f"{format_byte_size(status.allocated_bytes):>10}")
|
|
133
|
-
print(f" {allocated_label}
|
|
135
|
+
print(f" {allocated_label} {' ' * count_width} {allocated_size}")
|
|
134
136
|
print(style.dim(" * hard links counted once; copy-on-write blocks may still be shared"))
|
|
135
137
|
if status.environment_usage:
|
|
136
138
|
print()
|
|
@@ -177,6 +179,7 @@ def _render_cleanup(
|
|
|
177
179
|
environments: CleanupReport,
|
|
178
180
|
downloads: DownloadCleanupReport | None,
|
|
179
181
|
scratch: CleanupReport | None = None,
|
|
182
|
+
project_artifacts: CleanupReport | None = None,
|
|
180
183
|
) -> None:
|
|
181
184
|
style = styler_for(sys.stdout)
|
|
182
185
|
retained_note = style.dim(
|
|
@@ -218,6 +221,17 @@ def _render_cleanup(
|
|
|
218
221
|
)
|
|
219
222
|
else:
|
|
220
223
|
print("No abandoned workspaces to remove.")
|
|
224
|
+
if project_artifacts is not None:
|
|
225
|
+
if project_artifacts.candidates:
|
|
226
|
+
anything = True
|
|
227
|
+
print(
|
|
228
|
+
style.bold(
|
|
229
|
+
f"Would remove {len(project_artifacts.candidates)} obsolete project "
|
|
230
|
+
f"artifact tree(s) · {format_byte_size(project_artifacts.candidate_bytes)}"
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
else:
|
|
234
|
+
print("No obsolete /1 project artifacts to remove.")
|
|
221
235
|
print(retained_note)
|
|
222
236
|
if anything:
|
|
223
237
|
print()
|
|
@@ -226,10 +240,13 @@ def _render_cleanup(
|
|
|
226
240
|
reclaimed = environments.reclaimed_bytes + (downloads.reclaimed_bytes if downloads else 0)
|
|
227
241
|
if scratch is not None:
|
|
228
242
|
reclaimed += scratch.reclaimed_bytes
|
|
243
|
+
if project_artifacts is not None:
|
|
244
|
+
reclaimed += project_artifacts.reclaimed_bytes
|
|
229
245
|
removed = (
|
|
230
246
|
len(environments.removed)
|
|
231
247
|
+ (len(downloads.removed) if downloads else 0)
|
|
232
248
|
+ (len(scratch.removed) if scratch else 0)
|
|
249
|
+
+ (len(project_artifacts.removed) if project_artifacts else 0)
|
|
233
250
|
)
|
|
234
251
|
if removed:
|
|
235
252
|
parts = [f"{len(environments.removed)} environment(s)"]
|
|
@@ -237,6 +254,8 @@ def _render_cleanup(
|
|
|
237
254
|
parts.append(f"{len(downloads.removed)} cached download(s)")
|
|
238
255
|
if scratch is not None:
|
|
239
256
|
parts.append(f"{len(scratch.removed)} abandoned workspace(s)")
|
|
257
|
+
if project_artifacts is not None:
|
|
258
|
+
parts.append(f"{len(project_artifacts.removed)} obsolete project artifact tree(s)")
|
|
240
259
|
print(
|
|
241
260
|
style.green(f"Removed {' and '.join(parts)} · reclaimed {format_byte_size(reclaimed)}")
|
|
242
261
|
)
|
|
@@ -886,6 +905,13 @@ Advanced namespaces:
|
|
|
886
905
|
adopt.add_argument(
|
|
887
906
|
"--dry-run", action="store_true", help="show the adoption plan without changing anything"
|
|
888
907
|
)
|
|
908
|
+
adopt.add_argument(
|
|
909
|
+
"--no-agents",
|
|
910
|
+
dest="agents",
|
|
911
|
+
action="store_false",
|
|
912
|
+
default=True,
|
|
913
|
+
help="skip generating AGENTS.md",
|
|
914
|
+
)
|
|
889
915
|
_add_common_output(adopt)
|
|
890
916
|
adopt.set_defaults(command="attach", recursive=None, execute=False)
|
|
891
917
|
|
|
@@ -1150,6 +1176,13 @@ Advanced namespaces:
|
|
|
1150
1176
|
pshare.add_argument(
|
|
1151
1177
|
"--dry-run", action="store_true", help="show the sharing plan without changing anything"
|
|
1152
1178
|
)
|
|
1179
|
+
pshare.add_argument(
|
|
1180
|
+
"--no-agents",
|
|
1181
|
+
dest="agents",
|
|
1182
|
+
action="store_false",
|
|
1183
|
+
default=True,
|
|
1184
|
+
help="skip generating AGENTS.md",
|
|
1185
|
+
)
|
|
1153
1186
|
_add_common_output(pshare)
|
|
1154
1187
|
pshare.set_defaults(command="attach", recursive=None, execute=False)
|
|
1155
1188
|
punshare = pc.add_parser("unshare", help="materialize independent dependency copies")
|
|
@@ -1679,9 +1712,19 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1679
1712
|
if args.agents:
|
|
1680
1713
|
print(f"Agent guide: {init_result.root / 'AGENTS.md'}")
|
|
1681
1714
|
project_name = init_plan.project_name or init_result.root.name
|
|
1682
|
-
|
|
1683
|
-
|
|
1715
|
+
# Lake capitalizes the module directory during `lake init`, so derive
|
|
1716
|
+
# the hint from the file it actually created rather than the raw name.
|
|
1717
|
+
created = sorted(
|
|
1718
|
+
path
|
|
1719
|
+
for path in init_result.root.glob("*/Basic.lean")
|
|
1720
|
+
if not path.parent.name.startswith(".")
|
|
1721
|
+
)
|
|
1722
|
+
module_file = (
|
|
1723
|
+
created[0].relative_to(init_result.root)
|
|
1724
|
+
if created
|
|
1725
|
+
else Path(project_name) / "Basic.lean"
|
|
1684
1726
|
)
|
|
1727
|
+
print(f"Next: cd {init_result.root} && lean-runtime check {module_file}")
|
|
1685
1728
|
return 0
|
|
1686
1729
|
if args.command == "scan":
|
|
1687
1730
|
scan_result = runtime.scan_projects(args.path, recursive=args.recursive)
|
|
@@ -1793,8 +1836,24 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1793
1836
|
recursive=args.recursive,
|
|
1794
1837
|
plan=adoption_plan,
|
|
1795
1838
|
)
|
|
1839
|
+
# Adoption is the canonical moment a repository becomes agent-ready,
|
|
1840
|
+
# including re-runs against already-attached projects.
|
|
1841
|
+
agent_guides = (
|
|
1842
|
+
[
|
|
1843
|
+
guide
|
|
1844
|
+
for attached in adoption_result.results
|
|
1845
|
+
if (guide := runtime.write_agents_guide(attached.root)) is not None
|
|
1846
|
+
]
|
|
1847
|
+
if args.agents
|
|
1848
|
+
else []
|
|
1849
|
+
)
|
|
1796
1850
|
if args.json:
|
|
1797
|
-
_json(
|
|
1851
|
+
_json(
|
|
1852
|
+
{
|
|
1853
|
+
**adoption_result.to_dict(),
|
|
1854
|
+
"agent_guides": [str(guide) for guide in agent_guides],
|
|
1855
|
+
}
|
|
1856
|
+
)
|
|
1798
1857
|
else:
|
|
1799
1858
|
_render_adoption_plan(adoption_result.plan)
|
|
1800
1859
|
for attached in adoption_result.results:
|
|
@@ -1802,6 +1861,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1802
1861
|
f"{attached.action}: {attached.root} · "
|
|
1803
1862
|
f"{format_byte_size(attached.reclaimed_bytes)} replaced"
|
|
1804
1863
|
)
|
|
1864
|
+
for guide in agent_guides:
|
|
1865
|
+
print(f"Agent guide: {guide}")
|
|
1805
1866
|
for root, message in adoption_result.failures:
|
|
1806
1867
|
print(f"failed: {root}: {message}", file=sys.stderr)
|
|
1807
1868
|
return 0 if adoption_result.ok else 1
|
|
@@ -1958,6 +2019,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1958
2019
|
name=args.name,
|
|
1959
2020
|
build_timeout=args.timeout,
|
|
1960
2021
|
accelerate=args.accelerate,
|
|
2022
|
+
# Acquire means the whole environment: demanding the root
|
|
2023
|
+
# module keeps a sparse capsule from satisfying it with an
|
|
2024
|
+
# empty closure that only looks ready.
|
|
2025
|
+
import_roots=(ROOT_MODULE,),
|
|
1961
2026
|
)
|
|
1962
2027
|
_json(environment.inspect().to_dict())
|
|
1963
2028
|
if args.timings:
|
|
@@ -2252,12 +2317,22 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2252
2317
|
"toolchain": project_status.toolchain,
|
|
2253
2318
|
"manifest": str(project_status.manifest) if project_status.manifest else None,
|
|
2254
2319
|
"attached": (project_status.root / "lean-runtime.toml").is_file(),
|
|
2320
|
+
"agents_guide": (
|
|
2321
|
+
str(project_status.root / "AGENTS.md")
|
|
2322
|
+
if (project_status.root / "AGENTS.md").is_file()
|
|
2323
|
+
else None
|
|
2324
|
+
),
|
|
2255
2325
|
}
|
|
2256
2326
|
if args.json:
|
|
2257
2327
|
_json(status_payload)
|
|
2258
2328
|
else:
|
|
2259
|
-
|
|
2260
|
-
|
|
2329
|
+
display_payload = dict(status_payload)
|
|
2330
|
+
if display_payload.get("kind") == "project" and (
|
|
2331
|
+
display_payload.get("agents_guide") is None
|
|
2332
|
+
):
|
|
2333
|
+
display_payload["agents_guide"] = "missing (`lean-runtime adopt` writes one)"
|
|
2334
|
+
print(f"{display_payload['kind'].title()}")
|
|
2335
|
+
for key, value in display_payload.items():
|
|
2261
2336
|
if key == "kind":
|
|
2262
2337
|
continue
|
|
2263
2338
|
label = key.replace("_", " ").title()
|
|
@@ -2402,12 +2477,19 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2402
2477
|
dry_run=True,
|
|
2403
2478
|
minimum_age_seconds=min(args.minimum_age_hours * 3600, 3600),
|
|
2404
2479
|
)
|
|
2480
|
+
preview_project_artifacts = runtime.clean_legacy_project_artifacts(dry_run=True)
|
|
2405
2481
|
if not args.json:
|
|
2406
|
-
_render_cleanup(
|
|
2482
|
+
_render_cleanup(
|
|
2483
|
+
cleanup_preview,
|
|
2484
|
+
preview_downloads,
|
|
2485
|
+
preview_scratch,
|
|
2486
|
+
preview_project_artifacts,
|
|
2487
|
+
)
|
|
2407
2488
|
has_candidates = bool(
|
|
2408
2489
|
cleanup_preview.candidates
|
|
2409
2490
|
or (preview_downloads and preview_downloads.candidates)
|
|
2410
2491
|
or preview_scratch.candidates
|
|
2492
|
+
or preview_project_artifacts.candidates
|
|
2411
2493
|
)
|
|
2412
2494
|
if has_candidates and not args.dry_run:
|
|
2413
2495
|
execute_cleanup = _confirm("Remove these files?", yes=args.yes, json_mode=args.json)
|
|
@@ -2428,11 +2510,15 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2428
2510
|
dry_run=not execute_cleanup,
|
|
2429
2511
|
minimum_age_seconds=min(args.minimum_age_hours * 3600, 3600),
|
|
2430
2512
|
)
|
|
2513
|
+
gc_project_artifacts = runtime.clean_legacy_project_artifacts(
|
|
2514
|
+
dry_run=not execute_cleanup
|
|
2515
|
+
)
|
|
2431
2516
|
if args.json:
|
|
2432
2517
|
gc_payload: dict[str, Any] = {
|
|
2433
2518
|
"environments": gc_report.to_dict(),
|
|
2434
2519
|
"downloaded_files": gc_downloads.to_dict() if gc_downloads else None,
|
|
2435
2520
|
"scratch": gc_scratch.to_dict(),
|
|
2521
|
+
"project_artifacts": gc_project_artifacts.to_dict(),
|
|
2436
2522
|
}
|
|
2437
2523
|
_json(
|
|
2438
2524
|
envelope(
|
|
@@ -2442,7 +2528,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2442
2528
|
)
|
|
2443
2529
|
)
|
|
2444
2530
|
elif execute_cleanup:
|
|
2445
|
-
_render_cleanup(gc_report, gc_downloads, gc_scratch)
|
|
2531
|
+
_render_cleanup(gc_report, gc_downloads, gc_scratch, gc_project_artifacts)
|
|
2446
2532
|
return 0
|
|
2447
2533
|
if args.command == "check":
|
|
2448
2534
|
if args.across is not None:
|
|
@@ -2547,7 +2633,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2547
2633
|
watched = Path(args.inputs[0]).expanduser().resolve()
|
|
2548
2634
|
if not watched.is_file():
|
|
2549
2635
|
raise ValueError(f"watched Lean file does not exist: {watched}")
|
|
2550
|
-
print(f"Watching {watched} · Ctrl-C to stop")
|
|
2636
|
+
print(f"Watching {watched} · Ctrl-C to stop", flush=True)
|
|
2551
2637
|
if os.environ.get(_HEADER_SNAPSHOTS_VARIABLE) is None:
|
|
2552
2638
|
runtime.header_cache.enabled = True
|
|
2553
2639
|
previous: tuple[int, int] | None = None
|
|
@@ -2563,6 +2649,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2563
2649
|
policy=_policy(args),
|
|
2564
2650
|
)
|
|
2565
2651
|
_emit_result(watched_result, False)
|
|
2652
|
+
sys.stdout.flush()
|
|
2566
2653
|
time.sleep(args.watch_interval)
|
|
2567
2654
|
check_subject = (
|
|
2568
2655
|
Path(args.inputs[-1]).name
|
|
@@ -2614,10 +2701,16 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2614
2701
|
if args.include:
|
|
2615
2702
|
raise ValueError("local project checks do not accept --include")
|
|
2616
2703
|
display_path = "<stdin>"
|
|
2704
|
+
stdin_project = args.project
|
|
2705
|
+
if stdin_project is None and args.toolchain is None:
|
|
2706
|
+
try:
|
|
2707
|
+
stdin_project = discover_project(Path.cwd()).root
|
|
2708
|
+
except ProjectNotFoundError:
|
|
2709
|
+
stdin_project = None
|
|
2617
2710
|
result = runtime.check(
|
|
2618
2711
|
sys.stdin.read(),
|
|
2619
2712
|
toolchain=args.toolchain,
|
|
2620
|
-
project=
|
|
2713
|
+
project=stdin_project,
|
|
2621
2714
|
policy=_policy(args),
|
|
2622
2715
|
)
|
|
2623
2716
|
source_file = None
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import re
|
|
5
6
|
import threading
|
|
6
7
|
import time
|
|
7
8
|
|
|
8
9
|
from ..errors import LeanRuntimeError
|
|
9
|
-
from
|
|
10
|
+
from ..models import ExecutionResult
|
|
11
|
+
from .candidate import Candidate, DiscoveryPlan
|
|
12
|
+
from .catalog import CatalogEntry
|
|
10
13
|
from .probe import CandidateProbe, ProbeIntegrityFailure, ProbeUnavailable
|
|
11
14
|
from .result import (
|
|
12
15
|
AttemptStatus,
|
|
@@ -27,6 +30,8 @@ DISCOVERY_EXHAUSTED = "DISCOVERY_EXHAUSTED"
|
|
|
27
30
|
DISCOVERY_EXPLICIT_LOCK = "DISCOVERY_EXPLICIT_LOCK"
|
|
28
31
|
DISCOVERY_CANCELLED = "DISCOVERY_CANCELLED"
|
|
29
32
|
DISCOVERY_RUNTIME_ERROR = "DISCOVERY_RUNTIME_ERROR"
|
|
33
|
+
DISCOVERY_MODULE_UNAVAILABLE = "DISCOVERY_MODULE_UNAVAILABLE"
|
|
34
|
+
DISCOVERY_VERDICT_BOUNDED = "DISCOVERY_VERDICT_BOUNDED"
|
|
30
35
|
CANDIDATE_LEAN_REJECTED = "CANDIDATE_LEAN_REJECTED"
|
|
31
36
|
CANDIDATE_UNAVAILABLE = "CANDIDATE_UNAVAILABLE"
|
|
32
37
|
CANDIDATE_INTEGRITY_FAILURE = "CANDIDATE_INTEGRITY_FAILURE"
|
|
@@ -145,6 +150,63 @@ def _rejection_detail(stderr: str, stdout: str) -> str:
|
|
|
145
150
|
return stderr.strip() or stdout.strip() or "Lean rejected the source"
|
|
146
151
|
|
|
147
152
|
|
|
153
|
+
# Lean reports a missing import as `unknown module prefix 'X'` (directory-shaped
|
|
154
|
+
# roots) or `unknown module 'X'`; both are decidable against candidate
|
|
155
|
+
# inventories without running another compiler.
|
|
156
|
+
_UNKNOWN_MODULE = re.compile(r"unknown module(?: prefix)? '([^']+)'")
|
|
157
|
+
# Identifier-level failures are the rejection class where an older environment
|
|
158
|
+
# legitimately changes the verdict (renamed or deleted lemmas), so they always
|
|
159
|
+
# justify continuing the candidate march.
|
|
160
|
+
_IDENTIFIER_MARKERS = ("unknown identifier", "unknown constant")
|
|
161
|
+
# Positive evidence that the source itself is the problem. Kept deliberately
|
|
162
|
+
# narrow: anything unrecognized preserves the ordinary march.
|
|
163
|
+
_PROOF_LEVEL_MARKERS = ("type mismatch", "unsolved goals", "unexpected token")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _provides_module(entry: CatalogEntry, name: str) -> bool:
|
|
167
|
+
return name in entry.modules or any(module.startswith(name + ".") for module in entry.modules)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _rejection_verdict(
|
|
171
|
+
result: ExecutionResult, candidates: tuple[Candidate, ...]
|
|
172
|
+
) -> tuple[str, tuple[str, ...]]:
|
|
173
|
+
"""Classify whether another candidate environment could change a rejection.
|
|
174
|
+
|
|
175
|
+
Returns one of:
|
|
176
|
+
- ``("module_unavailable", names)``: the source imports module roots that no
|
|
177
|
+
planned candidate provides; every further attempt must fail identically.
|
|
178
|
+
- ``("source_verdict", ())``: a proof-level failure (type mismatch,
|
|
179
|
+
unsolved goals, syntax); a second opinion never justifies a fresh
|
|
180
|
+
download — only local environments or an offline plan's source builds.
|
|
181
|
+
- ``("march", ())``: everything else — identifier-level failures, modules
|
|
182
|
+
another candidate does provide, and unclassifiable output all keep the
|
|
183
|
+
ordinary bounded march.
|
|
184
|
+
"""
|
|
185
|
+
text = "\n".join(
|
|
186
|
+
(
|
|
187
|
+
*(item.message for item in result.diagnostics),
|
|
188
|
+
result.stderr,
|
|
189
|
+
result.stdout,
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
missing = tuple(dict.fromkeys(_UNKNOWN_MODULE.findall(text)))
|
|
193
|
+
if missing:
|
|
194
|
+
unavailable = tuple(
|
|
195
|
+
name
|
|
196
|
+
for name in missing
|
|
197
|
+
if not any(_provides_module(candidate.entry, name) for candidate in candidates)
|
|
198
|
+
)
|
|
199
|
+
if unavailable:
|
|
200
|
+
return "module_unavailable", unavailable
|
|
201
|
+
return "march", ()
|
|
202
|
+
lowered = text.lower()
|
|
203
|
+
if any(marker in lowered for marker in _IDENTIFIER_MARKERS):
|
|
204
|
+
return "march", ()
|
|
205
|
+
if any(marker in lowered for marker in _PROOF_LEVEL_MARKERS):
|
|
206
|
+
return "source_verdict", ()
|
|
207
|
+
return "march", ()
|
|
208
|
+
|
|
209
|
+
|
|
148
210
|
def discover(
|
|
149
211
|
source: str,
|
|
150
212
|
plan: DiscoveryPlan,
|
|
@@ -189,6 +251,11 @@ def discover(
|
|
|
189
251
|
raise ValueError("an authoritative candidate probe is required")
|
|
190
252
|
attempts: list[CandidateAttempt] = []
|
|
191
253
|
remote_acquisitions = 0
|
|
254
|
+
# Set once a proof-level rejection shows the source itself is the problem:
|
|
255
|
+
# remaining candidates are only worth a look when reaching them costs no
|
|
256
|
+
# download (already local, or an offline plan's deliberate source builds).
|
|
257
|
+
local_candidates_only = False
|
|
258
|
+
verdict_bounded_skips = 0
|
|
192
259
|
for candidate in plan.planned_candidates:
|
|
193
260
|
if cancel is not None and cancel.is_set():
|
|
194
261
|
return DiscoveryResult(
|
|
@@ -217,6 +284,12 @@ def discover(
|
|
|
217
284
|
candidate_is_local = any(
|
|
218
285
|
reason.code == "RANK_LOCAL_AVAILABLE" for reason in candidate.reasons
|
|
219
286
|
)
|
|
287
|
+
# A proof-level rejection never justifies a *download*. Offline plans
|
|
288
|
+
# (allow_download=False) acquire candidates by deliberate local source
|
|
289
|
+
# builds, so their march is preserved in full.
|
|
290
|
+
if local_candidates_only and not candidate_is_local and plan.policy.allow_download:
|
|
291
|
+
verdict_bounded_skips += 1
|
|
292
|
+
continue
|
|
220
293
|
if not candidate_is_local and remote_acquisitions >= plan.policy.max_remote_acquisitions:
|
|
221
294
|
return DiscoveryResult(
|
|
222
295
|
status="not_found",
|
|
@@ -427,6 +500,29 @@ def discover(
|
|
|
427
500
|
duration_seconds=time.monotonic() - started,
|
|
428
501
|
diagnostics=attempt.diagnostics,
|
|
429
502
|
)
|
|
503
|
+
if outcome_status == "lean_rejected":
|
|
504
|
+
verdict, unavailable = _rejection_verdict(result, plan.planned_candidates)
|
|
505
|
+
if verdict == "module_unavailable":
|
|
506
|
+
# Statically decidable: no candidate can change this verdict.
|
|
507
|
+
return DiscoveryResult(
|
|
508
|
+
status="not_found",
|
|
509
|
+
confidence="exhausted",
|
|
510
|
+
outcome="source_rejected",
|
|
511
|
+
completion="complete",
|
|
512
|
+
plan=plan,
|
|
513
|
+
attempts=tuple(attempts),
|
|
514
|
+
duration_seconds=time.monotonic() - started,
|
|
515
|
+
diagnostics=(
|
|
516
|
+
ResultDiagnostic(
|
|
517
|
+
DISCOVERY_MODULE_UNAVAILABLE,
|
|
518
|
+
"no cataloged environment provides module(s): "
|
|
519
|
+
+ ", ".join(unavailable),
|
|
520
|
+
),
|
|
521
|
+
*attempt.diagnostics,
|
|
522
|
+
),
|
|
523
|
+
)
|
|
524
|
+
if verdict == "source_verdict":
|
|
525
|
+
local_candidates_only = True
|
|
430
526
|
|
|
431
527
|
if time.monotonic() >= wall_deadline or probe_remaining <= 0:
|
|
432
528
|
reason = ResultDiagnostic(DISCOVERY_TIME_LIMIT, "total wall-clock budget expired")
|
|
@@ -465,6 +561,17 @@ def discover(
|
|
|
465
561
|
if attempt.status == "timeout"
|
|
466
562
|
for diagnostic in attempt.diagnostics
|
|
467
563
|
)
|
|
564
|
+
skip_diagnostics = (
|
|
565
|
+
(
|
|
566
|
+
ResultDiagnostic(
|
|
567
|
+
DISCOVERY_VERDICT_BOUNDED,
|
|
568
|
+
f"skipped {verdict_bounded_skips} non-local candidate(s): a proof-level "
|
|
569
|
+
"rejection does not justify downloading further environments",
|
|
570
|
+
),
|
|
571
|
+
)
|
|
572
|
+
if verdict_bounded_skips
|
|
573
|
+
else ()
|
|
574
|
+
)
|
|
468
575
|
return DiscoveryResult(
|
|
469
576
|
status="not_found",
|
|
470
577
|
confidence=confidence,
|
|
@@ -473,5 +580,5 @@ def discover(
|
|
|
473
580
|
plan=plan,
|
|
474
581
|
attempts=tuple(attempts),
|
|
475
582
|
duration_seconds=time.monotonic() - started,
|
|
476
|
-
diagnostics=(*timeout_diagnostics, reason, *rejection_diagnostics),
|
|
583
|
+
diagnostics=(*timeout_diagnostics, reason, *skip_diagnostics, *rejection_diagnostics),
|
|
477
584
|
)
|
|
@@ -766,7 +766,11 @@ class Environment:
|
|
|
766
766
|
spawn(
|
|
767
767
|
resolved_command,
|
|
768
768
|
cwd=instance,
|
|
769
|
-
environment=
|
|
769
|
+
environment=(
|
|
770
|
+
self.manager.toolchains.environment_for(self.lock.toolchain)
|
|
771
|
+
if requested_command[0] == "lake"
|
|
772
|
+
else self.manager.toolchains.environment
|
|
773
|
+
),
|
|
770
774
|
policy=selected_policy,
|
|
771
775
|
),
|
|
772
776
|
)
|
|
@@ -905,7 +909,12 @@ class Environment:
|
|
|
905
909
|
preliminary: list[BackendResult] = []
|
|
906
910
|
path_map = {str(instance / name): name for name in files}
|
|
907
911
|
staging_started = time.monotonic()
|
|
908
|
-
execution_environment =
|
|
912
|
+
execution_environment = (
|
|
913
|
+
self.manager.toolchains.environment_for(self.lock.toolchain)
|
|
914
|
+
if operation == "build"
|
|
915
|
+
or (requested_command and requested_command[0] == "lake")
|
|
916
|
+
else self.manager.toolchains.environment
|
|
917
|
+
)
|
|
909
918
|
if operation == "check":
|
|
910
919
|
assert entrypoint is not None
|
|
911
920
|
for name, source in files.items():
|
|
@@ -1301,7 +1310,11 @@ class EnvironmentManager:
|
|
|
1301
1310
|
result = self.backend.execute(
|
|
1302
1311
|
command,
|
|
1303
1312
|
cwd=workspace,
|
|
1304
|
-
environment=
|
|
1313
|
+
environment=(
|
|
1314
|
+
self.toolchains.environment_for(lock.toolchain)
|
|
1315
|
+
if requested_command[0] == "lake"
|
|
1316
|
+
else self.toolchains.environment
|
|
1317
|
+
),
|
|
1305
1318
|
policy=build_policy,
|
|
1306
1319
|
cancel=cancel,
|
|
1307
1320
|
)
|
|
@@ -1327,7 +1340,7 @@ class EnvironmentManager:
|
|
|
1327
1340
|
result = self.backend.execute(
|
|
1328
1341
|
command,
|
|
1329
1342
|
cwd=workspace,
|
|
1330
|
-
environment=self.toolchains.
|
|
1343
|
+
environment=self.toolchains.environment_for(lock.toolchain),
|
|
1331
1344
|
policy=build_policy,
|
|
1332
1345
|
cancel=cancel,
|
|
1333
1346
|
)
|
|
@@ -107,6 +107,20 @@ def diagnose(toolchains: ToolchainManager, store: EnvironmentStore) -> DoctorRep
|
|
|
107
107
|
)
|
|
108
108
|
else:
|
|
109
109
|
checks.append(DoctorCheck("scratch", "pass", "No abandoned workspaces"))
|
|
110
|
+
legacy_artifacts = store.clean_legacy_project_artifacts(dry_run=True)
|
|
111
|
+
if legacy_artifacts.candidates:
|
|
112
|
+
checks.append(
|
|
113
|
+
DoctorCheck(
|
|
114
|
+
"project-artifacts-v1",
|
|
115
|
+
"warning",
|
|
116
|
+
f"{len(legacy_artifacts.candidates)} package artifact tree(s) use the obsolete "
|
|
117
|
+
"toolchain-insensitive /1 key; reclaim them with `lean-runtime clean --yes`",
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
else:
|
|
121
|
+
checks.append(
|
|
122
|
+
DoctorCheck("project-artifacts-v1", "pass", "No obsolete /1 project artifacts")
|
|
123
|
+
)
|
|
110
124
|
store_status = store.status()
|
|
111
125
|
cutoff = datetime.now(timezone.utc).timestamp() - STALE_ENVIRONMENT_AGE_HOURS * 3600
|
|
112
126
|
stale = tuple(
|
|
@@ -145,6 +159,7 @@ def repair(toolchains: ToolchainManager, store: EnvironmentStore) -> DoctorRepor
|
|
|
145
159
|
include_legacy=True,
|
|
146
160
|
legacy_minimum_age_seconds=LEGACY_SCRATCH_MINIMUM_AGE_SECONDS,
|
|
147
161
|
)
|
|
162
|
+
store.clean_legacy_project_artifacts(dry_run=False)
|
|
148
163
|
with suppress(ToolchainError):
|
|
149
164
|
toolchains.elan_path(bootstrap=True)
|
|
150
165
|
return diagnose(toolchains, store)
|
|
@@ -160,7 +160,7 @@ class LakeArtifactCache:
|
|
|
160
160
|
try:
|
|
161
161
|
process = subprocess.run(
|
|
162
162
|
self.toolchains.command(toolchain, "lake", *arguments),
|
|
163
|
-
env=self.toolchains.
|
|
163
|
+
env=self.toolchains.environment_for(toolchain),
|
|
164
164
|
text=True,
|
|
165
165
|
stdout=subprocess.PIPE,
|
|
166
166
|
stderr=subprocess.STDOUT,
|
|
@@ -183,7 +183,7 @@ class LakeArtifactCache:
|
|
|
183
183
|
|
|
184
184
|
def environment(self, context: ProjectContext) -> dict[str, str]:
|
|
185
185
|
"""Return root-only cache settings without caching dependency packages."""
|
|
186
|
-
environment = self.toolchains.
|
|
186
|
+
environment = self.toolchains.environment_for(context.toolchain)
|
|
187
187
|
if not self.project_opted_in(context):
|
|
188
188
|
return environment
|
|
189
189
|
self.capabilities(context.toolchain)
|
|
@@ -28,7 +28,7 @@ from .bundles import (
|
|
|
28
28
|
EnvironmentBundles,
|
|
29
29
|
_capsule_config_object,
|
|
30
30
|
)
|
|
31
|
-
from .capsules import CapsuleManifest
|
|
31
|
+
from .capsules import CAPSULE_MANIFEST, CapsuleManifest
|
|
32
32
|
from .errors import (
|
|
33
33
|
CredentialAcquisitionError,
|
|
34
34
|
DownloadLimitExceeded,
|
|
@@ -62,6 +62,7 @@ from .store import (
|
|
|
62
62
|
platform_compatibility,
|
|
63
63
|
platform_record,
|
|
64
64
|
)
|
|
65
|
+
from .toolchains import immutable_toolchain_spelling
|
|
65
66
|
|
|
66
67
|
_REPOSITORY = re.compile(r"[a-z0-9]+(?:[._-][a-z0-9]+)*(?:/[a-z0-9]+(?:[._-][a-z0-9]+)*)+")
|
|
67
68
|
_BEARER_PARAMETER = re.compile(r'([a-zA-Z]+)="([^"]*)"')
|
|
@@ -887,6 +888,17 @@ class OCIEnvironmentCache:
|
|
|
887
888
|
capsule = CapsuleManifest.from_dict(config["capsule"])
|
|
888
889
|
if capsule.lock_id != lock.lock_id or capsule.toolchain != lock.toolchain:
|
|
889
890
|
raise EnvironmentError("downloadable capsule does not match its lock")
|
|
891
|
+
# A capsule that lacks a requested root cannot satisfy this acquisition;
|
|
892
|
+
# silently projecting a partial closure would yield an environment that
|
|
893
|
+
# claims readiness while missing the modules the caller asked for.
|
|
894
|
+
available = {module.name for module in capsule.modules}
|
|
895
|
+
unavailable = tuple(root for root in roots if root not in available)
|
|
896
|
+
if unavailable:
|
|
897
|
+
raise DownloadUnavailable(
|
|
898
|
+
"downloadable capsule does not provide module(s): " + ", ".join(unavailable),
|
|
899
|
+
reason_code="capsule_roots_unavailable",
|
|
900
|
+
retryable=False,
|
|
901
|
+
)
|
|
890
902
|
closure = capsule.closure(roots)
|
|
891
903
|
module_names = tuple(module.name for module in closure)
|
|
892
904
|
descriptors = {
|
|
@@ -1447,6 +1459,22 @@ class OCIEnvironmentPublisher:
|
|
|
1447
1459
|
) -> PublicationInfo:
|
|
1448
1460
|
if profile != "check-capsule":
|
|
1449
1461
|
raise ValueError("publication profile must be 'check-capsule'")
|
|
1462
|
+
root = self.store.environment_path(environment_id)
|
|
1463
|
+
try:
|
|
1464
|
+
metadata = json.loads((root / "metadata.json").read_text(encoding="utf-8"))
|
|
1465
|
+
lock = self.store.load_lock(str(metadata["lock_id"]))
|
|
1466
|
+
except (OSError, KeyError, TypeError, json.JSONDecodeError) as exc:
|
|
1467
|
+
raise EnvironmentError("publication input has invalid environment metadata") from exc
|
|
1468
|
+
if (root / "workspace" / CAPSULE_MANIFEST).is_file():
|
|
1469
|
+
raise EnvironmentError(
|
|
1470
|
+
"publication input must be source-built; sparse acquired capsules cannot be "
|
|
1471
|
+
"republished"
|
|
1472
|
+
)
|
|
1473
|
+
if not immutable_toolchain_spelling(lock.toolchain):
|
|
1474
|
+
raise EnvironmentError(
|
|
1475
|
+
f"refusing to publish mutable toolchain spelling {lock.toolchain!r}; "
|
|
1476
|
+
"use an immutable release or dated nightly"
|
|
1477
|
+
)
|
|
1450
1478
|
self.check_access()
|
|
1451
1479
|
with tempfile.TemporaryDirectory(prefix="lean-runtime-publish-") as temporary:
|
|
1452
1480
|
temporary_root = Path(temporary)
|