lean-runtime 2.1.2__tar.gz → 2.2.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.
Files changed (151) hide show
  1. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/CHANGELOG.md +12 -0
  2. {lean_runtime-2.1.2/lean_runtime.egg-info → lean_runtime-2.2.0}/PKG-INFO +1 -1
  3. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/environments.py +107 -110
  4. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/runtime.py +41 -1
  5. {lean_runtime-2.1.2 → lean_runtime-2.2.0/lean_runtime.egg-info}/PKG-INFO +1 -1
  6. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/pyproject.toml +1 -1
  7. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/scripts/announcement_gate.py +5 -3
  8. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_multifile.py +37 -1
  9. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_runtime.py +60 -1
  10. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/LICENSE +0 -0
  11. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/MANIFEST.in +0 -0
  12. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/README.md +0 -0
  13. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/catalog/environments.toml +0 -0
  14. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/catalog/locks/core-v4.32.2.lock.json +0 -0
  15. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/catalog/locks/mathlib-v4.30.0.lock.json +0 -0
  16. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/catalog/locks/mathlib-v4.31.0.lock.json +0 -0
  17. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/catalog/locks/mathlib-v4.32.2.lock.json +0 -0
  18. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/compatibility/README.md +0 -0
  19. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/compatibility/mathlib-4.32.2.json +0 -0
  20. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/compatibility/mathlib-4.32.2.toml +0 -0
  21. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/docs/architecture.md +0 -0
  22. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/docs/captures.md +0 -0
  23. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/docs/case-study-v1.md +0 -0
  24. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/docs/cli.md +0 -0
  25. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/docs/development.md +0 -0
  26. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/docs/environments.md +0 -0
  27. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/docs/getting-started.md +0 -0
  28. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/docs/index.md +0 -0
  29. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/docs/local-projects.md +0 -0
  30. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/docs/portable-copies.md +0 -0
  31. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/docs/python-api.md +0 -0
  32. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/docs/ready-programs.md +0 -0
  33. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/docs/standalone-files.md +0 -0
  34. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/docs/trust-and-limitations.md +0 -0
  35. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/docs/v1-precision.md +0 -0
  36. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/examples/mathlib.toml +0 -0
  37. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/__init__.py +0 -0
  38. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/__main__.py +0 -0
  39. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/_git.py +0 -0
  40. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/_paths.py +0 -0
  41. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/backends.py +0 -0
  42. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/bundles.py +0 -0
  43. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/cli.py +0 -0
  44. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/comparison.py +0 -0
  45. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/decisions.py +0 -0
  46. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/diagnostics.py +0 -0
  47. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/__init__.py +0 -0
  48. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/analyzer.py +0 -0
  49. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/api.py +0 -0
  50. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/candidate.py +0 -0
  51. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/catalog.py +0 -0
  52. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/catalog_build.py +0 -0
  53. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/catalog_cli.py +0 -0
  54. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/catalog_manifest.py +0 -0
  55. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/data/catalog.json +0 -0
  56. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/defaults.py +0 -0
  57. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/engine.py +0 -0
  58. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/errors.py +0 -0
  59. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/module_inventory.py +0 -0
  60. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/planner.py +0 -0
  61. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/policy.py +0 -0
  62. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/probe.py +0 -0
  63. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/py.typed +0 -0
  64. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/result.py +0 -0
  65. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/schema_resources.py +0 -0
  66. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/schemas/catalog-v1.schema.json +0 -0
  67. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/schemas/plan-v1.schema.json +0 -0
  68. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/discovery/schemas/result-v1.schema.json +0 -0
  69. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/errors.py +0 -0
  70. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/events.py +0 -0
  71. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/facade.py +0 -0
  72. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/frontmatter.py +0 -0
  73. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/health.py +0 -0
  74. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/lake.py +0 -0
  75. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/lockfiles.py +0 -0
  76. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/locking.py +0 -0
  77. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/matrix.py +0 -0
  78. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/models.py +0 -0
  79. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/oci.py +0 -0
  80. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/policies.py +0 -0
  81. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/profiling.py +0 -0
  82. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/programs.py +0 -0
  83. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/projects.py +0 -0
  84. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/publisher_verification.py +0 -0
  85. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/py.typed +0 -0
  86. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/references.py +0 -0
  87. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/resolver.py +0 -0
  88. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/run_cli.py +0 -0
  89. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/schema_resources.py +0 -0
  90. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/serialization.py +0 -0
  91. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/specs.py +0 -0
  92. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/store.py +0 -0
  93. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/timings.py +0 -0
  94. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/toolchain_slim.py +0 -0
  95. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/toolchains.py +0 -0
  96. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/verification.py +0 -0
  97. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime/wire.py +0 -0
  98. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime.egg-info/SOURCES.txt +0 -0
  99. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime.egg-info/dependency_links.txt +0 -0
  100. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime.egg-info/entry_points.txt +0 -0
  101. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime.egg-info/requires.txt +0 -0
  102. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/lean_runtime.egg-info/top_level.txt +0 -0
  103. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/mkdocs.yml +0 -0
  104. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/schemas/cleanup-v1.schema.json +0 -0
  105. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/schemas/comparison-v1.schema.json +0 -0
  106. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/schemas/execution-v1.schema.json +0 -0
  107. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/schemas/inspect-v1.schema.json +0 -0
  108. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/schemas/matrix-v1.schema.json +0 -0
  109. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/schemas/profile-v1.schema.json +0 -0
  110. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/schemas/verify-v1.schema.json +0 -0
  111. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/scripts/registry_preflight.py +0 -0
  112. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/scripts/run_compatibility.py +0 -0
  113. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/scripts/run_v1_case_study.py +0 -0
  114. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/scripts/smoke_wheel.py +0 -0
  115. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/setup.cfg +0 -0
  116. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/conftest.py +0 -0
  117. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/discovery/e2e/test_real_discovery.py +0 -0
  118. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/discovery/test_analyzer.py +0 -0
  119. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/discovery/test_catalog.py +0 -0
  120. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/discovery/test_catalog_build.py +0 -0
  121. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/discovery/test_discovery_schema_resources.py +0 -0
  122. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/discovery/test_engine.py +0 -0
  123. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/discovery/test_planner.py +0 -0
  124. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/discovery/test_policy.py +0 -0
  125. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_backend.py +0 -0
  126. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_bundles.py +0 -0
  127. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_cli.py +0 -0
  128. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_diagnostics.py +0 -0
  129. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_environment_integration.py +0 -0
  130. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_events_health.py +0 -0
  131. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_facade.py +0 -0
  132. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_frontmatter.py +0 -0
  133. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_git.py +0 -0
  134. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_interactive.py +0 -0
  135. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_lockfiles.py +0 -0
  136. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_models.py +0 -0
  137. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_paths.py +0 -0
  138. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_prebuilt_policy.py +0 -0
  139. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_programs.py +0 -0
  140. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_projects.py +0 -0
  141. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_references.py +0 -0
  142. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_run_cli.py +0 -0
  143. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_schema_resources.py +0 -0
  144. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_schemas.py +0 -0
  145. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_signatures.py +0 -0
  146. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_specs.py +0 -0
  147. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_store.py +0 -0
  148. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_toolchain_slim.py +0 -0
  149. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_toolchains.py +0 -0
  150. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_v1_precision.py +0 -0
  151. {lean_runtime-2.1.2 → lean_runtime-2.2.0}/tests/test_verification_inventory.py +0 -0
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.2.0 - 2026-08-12
4
+
5
+ - Make published-environment checks invoke Lean directly with the immutable
6
+ compiled module roots instead of cloning the multi-gigabyte workspace and
7
+ asking Lake to rescan its full build graph on every proof. Multi-file checks
8
+ compile support modules into the disposable scratch tree; build and execute
9
+ operations keep their existing isolated workspace clones.
10
+ - Resolve exact bundled references such as `mathlib@v4.32.2` to the catalog's
11
+ downloadable lock before Git/Lake resolution. The Python setup API,
12
+ `lean-run --with`, frontmatter, and `--lock-out` now share the same lock and
13
+ reuse the same environment rather than creating a duplicate cache entry.
14
+
3
15
  ## 2.1.2 - 2026-08-12
4
16
 
5
17
  - Raise `lean-run`'s default `--check-timeout` from 120 to 300 seconds: a
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-runtime
3
- Version: 2.1.2
3
+ Version: 2.2.0
4
4
  Summary: Run Lean 4 proofs from Python or standalone files
5
5
  Author: Alejandro Radisic
6
6
  License-Expression: Apache-2.0
@@ -147,6 +147,32 @@ def _package_import_targets(files: Mapping[str, str], lock: EnvironmentLock) ->
147
147
  return tuple(sorted(targets))
148
148
 
149
149
 
150
+ def _check_lean_paths(workspace: Path, scratch: Path, lock: EnvironmentLock) -> tuple[Path, ...]:
151
+ """Return the compiled module roots for a check without asking Lake to scan them."""
152
+ roots = [scratch / ".lake" / "build" / "lib" / "lean"]
153
+ workspace_root = workspace / ".lake" / "build" / "lib" / "lean"
154
+
155
+ raw_packages_dir = lock.manifest.get("packagesDir", ".lake/packages")
156
+ if not isinstance(raw_packages_dir, str):
157
+ raise EnvironmentError("lock packagesDir must be a relative string")
158
+ packages_dir = PurePosixPath(raw_packages_dir)
159
+ if packages_dir.is_absolute() or ".." in packages_dir.parts:
160
+ raise EnvironmentError("lock packagesDir must be a safe relative path")
161
+ package_root = workspace.joinpath(*packages_dir.parts)
162
+ for package in lock.packages:
163
+ root = package_root / package.name
164
+ if package.subdir:
165
+ root = root.joinpath(*PurePosixPath(package.subdir).parts)
166
+ compiled = root / ".lake" / "build" / "lib" / "lean"
167
+ if compiled.is_dir():
168
+ roots.append(compiled)
169
+ # Lake places dependencies before the root package. Keep that precedence;
170
+ # the scratch root remains first so submitted support modules win.
171
+ if workspace_root.is_dir():
172
+ roots.append(workspace_root)
173
+ return tuple(roots)
174
+
175
+
150
176
  @dataclass(frozen=True, slots=True)
151
177
  class EnvironmentInfo:
152
178
  environment_id: str
@@ -755,125 +781,96 @@ class Environment:
755
781
  try:
756
782
  instance_started = time.monotonic()
757
783
  with self.manager.store.execution_lease(self.id):
758
- clone_tree(self.workspace, instance)
759
- instance_timing = PhaseTiming(
760
- "instance_creation", round((time.monotonic() - instance_started) * 1000)
761
- )
762
- preliminary: list[BackendResult] = []
763
- path_map = {str(instance / name): name for name in files}
764
- staging_started = time.monotonic()
765
- if operation == "check":
766
- assert entrypoint is not None
767
- for name, source in files.items():
768
- source_path = instance / name
769
- source_path.parent.mkdir(parents=True, exist_ok=True)
770
- source_path.write_text(source, encoding="utf-8")
771
- staging_timing = PhaseTiming(
772
- "input_staging", round((time.monotonic() - staging_started) * 1000)
784
+ if operation == "check":
785
+ instance.mkdir()
786
+ else:
787
+ clone_tree(self.workspace, instance)
788
+ instance_timing = PhaseTiming(
789
+ "instance_creation", round((time.monotonic() - instance_started) * 1000)
773
790
  )
774
- import_targets = _package_import_targets(files, self.lock)
775
- if import_targets:
776
- import_command = self.manager.toolchains.command(
777
- self.lock.toolchain, "lake", "build", *import_targets
791
+ preliminary: list[BackendResult] = []
792
+ path_map = {str(instance / name): name for name in files}
793
+ staging_started = time.monotonic()
794
+ execution_environment = self.manager.toolchains.environment
795
+ if operation == "check":
796
+ assert entrypoint is not None
797
+ for name, source in files.items():
798
+ source_path = instance / name
799
+ source_path.parent.mkdir(parents=True, exist_ok=True)
800
+ source_path.write_text(source, encoding="utf-8")
801
+ staging_timing = PhaseTiming(
802
+ "input_staging", round((time.monotonic() - staging_started) * 1000)
778
803
  )
779
- import_result = self.manager.backend.execute(
780
- import_command,
781
- cwd=instance,
782
- environment=self.manager.toolchains.environment,
783
- policy=policy,
784
- cancel=cancel,
804
+ lean_paths = _check_lean_paths(self.workspace, instance, self.lock)
805
+ execution_environment["LEAN_PATH"] = os.pathsep.join(
806
+ str(path) for path in lean_paths
785
807
  )
786
- preliminary.append(import_result)
787
- if import_result.exit_code:
788
- result = self._result(
789
- import_result,
790
- command=import_command,
808
+ for support in _support_order(files, entrypoint):
809
+ support_path = instance / support
810
+ output_path = (
811
+ instance / ".lake" / "build" / "lib" / "lean" / support
812
+ ).with_suffix(".olean")
813
+ output_path.parent.mkdir(parents=True, exist_ok=True)
814
+ support_command = self.manager.toolchains.command(
815
+ self.lock.toolchain,
816
+ "lean",
817
+ "-R",
818
+ str(instance),
819
+ "-o",
820
+ str(output_path),
821
+ str(support_path),
822
+ )
823
+ support_result = self.manager.backend.execute(
824
+ support_command,
791
825
  cwd=instance,
792
- execution_id=execution_id,
793
- request_digest=request_digest,
794
- source_digest=source_digest,
795
- started_at=started_at,
826
+ environment=execution_environment,
796
827
  policy=policy,
797
- timings=(instance_timing, staging_timing),
798
- path_map=path_map,
799
- )
800
- self._record_execution(
801
- result,
802
- operation,
803
- targets,
804
- entrypoint,
805
- tuple(files),
806
- requested_command,
828
+ cancel=cancel,
807
829
  )
808
- return result
809
- for support in _support_order(files, entrypoint):
810
- support_path = instance / support
811
- output_path = (
812
- instance / ".lake" / "build" / "lib" / "lean" / support
813
- ).with_suffix(".olean")
814
- output_path.parent.mkdir(parents=True, exist_ok=True)
815
- support_command = self.manager.toolchains.command(
816
- self.lock.toolchain,
817
- "lake",
818
- "env",
819
- "lean",
820
- "-R",
821
- str(instance),
822
- "-o",
823
- str(output_path),
824
- str(support_path),
830
+ preliminary.append(support_result)
831
+ if support_result.exit_code:
832
+ result = self._result(
833
+ support_result,
834
+ command=support_command,
835
+ cwd=instance,
836
+ execution_id=execution_id,
837
+ request_digest=request_digest,
838
+ source_digest=source_digest,
839
+ started_at=started_at,
840
+ policy=policy,
841
+ timings=(instance_timing, staging_timing),
842
+ path_map=path_map,
843
+ )
844
+ self._record_execution(
845
+ result,
846
+ operation,
847
+ targets,
848
+ entrypoint,
849
+ tuple(files),
850
+ requested_command,
851
+ )
852
+ return result
853
+ source_path = instance / entrypoint
854
+ command = self.manager.toolchains.command(
855
+ self.lock.toolchain, "lean", "-R", str(instance), str(source_path)
825
856
  )
826
- support_result = self.manager.backend.execute(
827
- support_command,
828
- cwd=instance,
829
- environment=self.manager.toolchains.environment,
830
- policy=policy,
831
- cancel=cancel,
857
+ elif operation == "build":
858
+ staging_timing = PhaseTiming("input_staging", 0, performed=False)
859
+ command = self.manager.toolchains.command(
860
+ self.lock.toolchain, "lake", "build", *targets
832
861
  )
833
- preliminary.append(support_result)
834
- if support_result.exit_code:
835
- result = self._result(
836
- support_result,
837
- command=support_command,
838
- cwd=instance,
839
- execution_id=execution_id,
840
- request_digest=request_digest,
841
- source_digest=source_digest,
842
- started_at=started_at,
843
- policy=policy,
844
- timings=(instance_timing, staging_timing),
845
- path_map=path_map,
846
- )
847
- self._record_execution(
848
- result,
849
- operation,
850
- targets,
851
- entrypoint,
852
- tuple(files),
853
- requested_command,
854
- )
855
- return result
856
- source_path = instance / entrypoint
857
- command = self.manager.toolchains.command(
858
- self.lock.toolchain, "lake", "env", "lean", str(source_path)
859
- )
860
- elif operation == "build":
861
- staging_timing = PhaseTiming("input_staging", 0, performed=False)
862
- command = self.manager.toolchains.command(
863
- self.lock.toolchain, "lake", "build", *targets
864
- )
865
- else:
866
- staging_timing = PhaseTiming("input_staging", 0, performed=False)
867
- command = self.manager.toolchains.command(
868
- self.lock.toolchain, requested_command[0], *requested_command[1:]
862
+ else:
863
+ staging_timing = PhaseTiming("input_staging", 0, performed=False)
864
+ command = self.manager.toolchains.command(
865
+ self.lock.toolchain, requested_command[0], *requested_command[1:]
866
+ )
867
+ raw = self.manager.backend.execute(
868
+ command,
869
+ cwd=instance,
870
+ environment=execution_environment,
871
+ policy=policy,
872
+ cancel=cancel,
869
873
  )
870
- raw = self.manager.backend.execute(
871
- command,
872
- cwd=instance,
873
- environment=self.manager.toolchains.environment,
874
- policy=policy,
875
- cancel=cancel,
876
- )
877
874
  if operation == "check" and preliminary:
878
875
  raw = BackendResult(
879
876
  exit_code=raw.exit_code,
@@ -42,7 +42,7 @@ from .profiling import ProfileReport, run_profile
42
42
  from .programs import ProgramInfo, ProgramLibrary, ProgramManager, ReadyProgram
43
43
  from .projects import ProjectContext, ProjectEnvironment, discover_project
44
44
  from .publisher_verification import CosignVerifier
45
- from .references import PackageReference, discover_package, normalize_references
45
+ from .references import PACKAGE_ALIASES, PackageReference, discover_package, normalize_references
46
46
  from .resolver import EnvironmentResolver
47
47
  from .serialization import sha256_id, sha256_text
48
48
  from .specs import EnvironmentSpec, GitPackage
@@ -66,6 +66,38 @@ from .verification import (
66
66
  EnvironmentReference = Environment | EnvironmentSpec | EnvironmentLock | str
67
67
 
68
68
 
69
+ def _bundled_lock_for_references(
70
+ packages: Sequence[str | PackageReference], toolchain: str | None
71
+ ) -> EnvironmentLock | None:
72
+ """Match a single exact alias tag to its bundled, downloadable catalog lock."""
73
+ references = normalize_references(packages)
74
+ if len(references) != 1:
75
+ return None
76
+ reference = references[0]
77
+ if reference.revision_kind != "tag":
78
+ return None
79
+ alias = next(
80
+ (
81
+ name
82
+ for name, (owner, repository, _command) in PACKAGE_ALIASES.items()
83
+ if reference.url == f"https://github.com/{owner}/{repository}.git"
84
+ ),
85
+ None,
86
+ )
87
+ if alias is None:
88
+ return None
89
+ entry_id = f"{alias}-{reference.revision}"
90
+ # Imported lazily: discovery's probe layer also imports Runtime.
91
+ from .discovery.defaults import default_catalog
92
+
93
+ entry = next((item for item in default_catalog().entries if item.id == entry_id), None)
94
+ if entry is None:
95
+ return None
96
+ if toolchain is not None and normalize_toolchain(toolchain) != entry.toolchain:
97
+ return None
98
+ return entry.lock
99
+
100
+
69
101
  def _download_reason(error: Exception) -> str:
70
102
  message = str(error).lower()
71
103
  if "platform" in message or "compatible" in message:
@@ -631,6 +663,14 @@ class Runtime:
631
663
  cancel: threading.Event | None = None,
632
664
  ) -> EnvironmentLock:
633
665
  """Discover package references and resolve their exact Lake graph."""
666
+ catalog_lock = _bundled_lock_for_references(packages, toolchain)
667
+ if catalog_lock is not None:
668
+ self.events.emit(
669
+ "catalog.reference_matched",
670
+ "Using bundled exact environment for package reference",
671
+ lock_id=catalog_lock.lock_id,
672
+ )
673
+ return catalog_lock
634
674
  return self.prepare(
635
675
  self.spec_from_references(packages, toolchain=toolchain),
636
676
  timeout=timeout,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-runtime
3
- Version: 2.1.2
3
+ Version: 2.2.0
4
4
  Summary: Run Lean 4 proofs from Python or standalone files
5
5
  Author: Alejandro Radisic
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "lean-runtime"
7
- version = "2.1.2"
7
+ version = "2.2.0"
8
8
  description = "Run Lean 4 proofs from Python or standalone files"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -35,8 +35,10 @@ from pathlib import Path
35
35
  ACCEPTANCE = Path(__file__).resolve().parent.parent / "acceptance"
36
36
 
37
37
  COLD_TIMEOUT = 2700 # toolchain download + environment download, no builds
38
- WARM_TIMEOUT = 300
39
- WARM_LIMIT = 240 # warm Mathlib checks include a full Lake trace scan today
38
+ WARM_TIMEOUT = 600
39
+ WARM_LIMIT = 400 # hang detector, not a perf bar: GitHub macOS runners take ~4x
40
+ # longer than real hardware for the warm Lake trace scan (238s observed vs 38s
41
+ # on an M-series laptop)
40
42
 
41
43
 
42
44
  def run(
@@ -169,7 +171,7 @@ def main() -> int:
169
171
  ok = ok and step(
170
172
  "python-batch-api",
171
173
  [sys.executable, str(ACCEPTANCE / "python_api.py"), str(lock_path)],
172
- timeout=900,
174
+ timeout=1500,
173
175
  )
174
176
 
175
177
  usage = shutil.disk_usage(home)
@@ -3,7 +3,12 @@ from __future__ import annotations
3
3
  import pytest
4
4
 
5
5
  from lean_runtime import EnvironmentError, EnvironmentLock, LockedPackage
6
- from lean_runtime.environments import _package_import_targets, _source_files, _support_order
6
+ from lean_runtime.environments import (
7
+ _check_lean_paths,
8
+ _package_import_targets,
9
+ _source_files,
10
+ _support_order,
11
+ )
7
12
 
8
13
 
9
14
  def test_nested_lean_sources_are_normalized() -> None:
@@ -55,3 +60,34 @@ def test_package_import_targets_exclude_core_and_submitted_modules() -> None:
55
60
  "Main.lean": "import Init\nimport Local\nimport ProofWidgets.Util",
56
61
  }
57
62
  assert _package_import_targets(files, lock) == ("ProofWidgets",)
63
+
64
+
65
+ def test_check_lean_paths_include_scratch_root_and_compiled_packages(tmp_path) -> None:
66
+ lock = EnvironmentLock(
67
+ toolchain="leanprover/lean4:v4.32.0",
68
+ spec_digest="spec_" + "a" * 64,
69
+ root_lakefile='name = "test"\n',
70
+ root_module="",
71
+ manifest={"packages": [], "packagesDir": "vendor"},
72
+ packages=(
73
+ LockedPackage(
74
+ name="sample",
75
+ url="https://example.test/sample",
76
+ revision="b" * 40,
77
+ source_id="source_" + "c" * 64,
78
+ tree_hash="d" * 40,
79
+ subdir="lean",
80
+ ),
81
+ ),
82
+ )
83
+ workspace = tmp_path / "workspace"
84
+ root_build = workspace / ".lake" / "build" / "lib" / "lean"
85
+ package_build = workspace / "vendor" / "sample" / "lean" / ".lake" / "build" / "lib" / "lean"
86
+ root_build.mkdir(parents=True)
87
+ package_build.mkdir(parents=True)
88
+
89
+ assert _check_lean_paths(workspace, tmp_path / "scratch", lock) == (
90
+ tmp_path / "scratch" / ".lake" / "build" / "lib" / "lean",
91
+ package_build,
92
+ root_build,
93
+ )
@@ -7,7 +7,18 @@ from pathlib import Path
7
7
 
8
8
  import pytest
9
9
 
10
- from lean_runtime import EnvironmentSpec, ExecutionJob, ResolutionError, Runtime, ToolchainError
10
+ from lean_runtime import (
11
+ EnvironmentLock,
12
+ EnvironmentSpec,
13
+ ExecutionJob,
14
+ ResolutionError,
15
+ Runtime,
16
+ ToolchainError,
17
+ )
18
+ from lean_runtime.backends import LocalBackend
19
+ from lean_runtime.environments import Environment, EnvironmentManager
20
+ from lean_runtime.runtime import _bundled_lock_for_references
21
+ from lean_runtime.store import EnvironmentStore
11
22
 
12
23
 
13
24
  class FakeToolchains:
@@ -35,6 +46,54 @@ class FakeToolchains:
35
46
  return [sys.executable, "-c", "raise SystemExit(0)"]
36
47
 
37
48
 
49
+ def test_exact_mathlib_reference_matches_bundled_catalog_lock() -> None:
50
+ lock = _bundled_lock_for_references(["mathlib@v4.32.2"], None)
51
+ assert lock is not None
52
+ assert lock.toolchain == "leanprover/lean4:v4.32.2"
53
+ assert any(package.name == "mathlib" for package in lock.packages)
54
+
55
+
56
+ def test_catalog_reference_match_respects_context_and_toolchain() -> None:
57
+ assert _bundled_lock_for_references(["mathlib@main"], None) is None
58
+ assert _bundled_lock_for_references(["mathlib@v4.32.2", "leancert@main"], None) is None
59
+ assert _bundled_lock_for_references(["mathlib@v4.32.2"], "v4.31.0") is None
60
+
61
+
62
+ def test_environment_check_does_not_clone_published_workspace(
63
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
64
+ ) -> None:
65
+ store = EnvironmentStore(tmp_path / "runtime")
66
+ environment_id = "env_" + "a" * 64
67
+ root = store.environment_path(environment_id)
68
+ (root / "workspace" / ".lake" / "build" / "lib" / "lean").mkdir(parents=True)
69
+ lock = EnvironmentLock(
70
+ toolchain="leanprover/lean4:v4.32.0",
71
+ spec_digest="spec_" + "b" * 64,
72
+ root_lakefile='name = "test"\n',
73
+ root_module="",
74
+ manifest={"packages": []},
75
+ packages=(),
76
+ )
77
+ manager = EnvironmentManager(
78
+ store,
79
+ FakeToolchains(tmp_path / "runtime"), # type: ignore[arg-type]
80
+ LocalBackend(),
81
+ )
82
+ environment = Environment(
83
+ manager,
84
+ environment_id,
85
+ lock,
86
+ root,
87
+ {"platform": {}, "build_profile": "release", "created_at": "2026-08-12T00:00:00Z"},
88
+ )
89
+
90
+ def unexpected_clone(_source: Path, _destination: Path) -> None:
91
+ raise AssertionError("checks must not clone a published workspace")
92
+
93
+ monkeypatch.setattr("lean_runtime.environments.clone_tree", unexpected_clone)
94
+ assert environment.check("example : True := by trivial").ok
95
+
96
+
38
97
  def test_check_accepts_source(tmp_path: Path) -> None:
39
98
  runtime = Runtime(toolchains=FakeToolchains(tmp_path)) # type: ignore[arg-type]
40
99
  result = runtime.check("example : True := by trivial", toolchain="4.32.0")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes