lean-runtime 2.9.1__tar.gz → 2.9.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.
Files changed (184) hide show
  1. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/CHANGELOG.md +12 -0
  2. {lean_runtime-2.9.1/lean_runtime.egg-info → lean_runtime-2.9.2}/PKG-INFO +1 -1
  3. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/cli.md +3 -0
  4. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/environments.md +6 -0
  5. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/portable-copies.md +6 -0
  6. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/cli.py +89 -15
  7. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/console.py +12 -0
  8. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/environments.py +4 -10
  9. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/errors.py +25 -0
  10. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/header_cache.py +6 -1
  11. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/health.py +13 -0
  12. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/models.py +2 -0
  13. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/oci.py +96 -13
  14. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/programs.py +2 -6
  15. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/project_sharing.py +3 -1
  16. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/resolver.py +5 -2
  17. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/runtime.py +38 -11
  18. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/shared_projects.py +5 -2
  19. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/store.py +122 -0
  20. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/toolchains.py +30 -1
  21. {lean_runtime-2.9.1 → lean_runtime-2.9.2/lean_runtime.egg-info}/PKG-INFO +1 -1
  22. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/pyproject.toml +1 -1
  23. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/schemas/cleanup-v1.schema.json +7 -3
  24. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/schemas/execution-v1.schema.json +1 -1
  25. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/schemas/publication-v1.schema.json +2 -0
  26. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_bundles.py +81 -5
  27. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_cli.py +23 -6
  28. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_console.py +12 -0
  29. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_events_health.py +1 -0
  30. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_projects.py +7 -0
  31. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_store.py +35 -0
  32. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/LICENSE +0 -0
  33. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/MANIFEST.in +0 -0
  34. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/README.md +0 -0
  35. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/catalog/environments.toml +0 -0
  36. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/catalog/locks/core-v4.32.2.lock.json +0 -0
  37. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/catalog/locks/leancert-v4.30.0.5.lock.json +0 -0
  38. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/catalog/locks/leancert-v4.31.0.lock.json +0 -0
  39. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/catalog/locks/leancert-v4.32.2.4.lock.json +0 -0
  40. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/catalog/locks/leancert-v4.33.0.lock.json +0 -0
  41. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/catalog/locks/mathlib-v4.30.0.lock.json +0 -0
  42. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/catalog/locks/mathlib-v4.31.0.lock.json +0 -0
  43. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/catalog/locks/mathlib-v4.32.2.lock.json +0 -0
  44. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/catalog/locks/mathlib-v4.33.0.lock.json +0 -0
  45. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/compatibility/README.md +0 -0
  46. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/compatibility/lake-artifact-cache-4.33.0.json +0 -0
  47. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/compatibility/mathlib-4.32.2.json +0 -0
  48. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/compatibility/mathlib-4.32.2.toml +0 -0
  49. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/compatibility/mathlib-4.33.0.json +0 -0
  50. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/compatibility/mathlib-4.33.0.toml +0 -0
  51. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/architecture.md +0 -0
  52. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/captures.md +0 -0
  53. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/case-study-v1.md +0 -0
  54. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/development.md +0 -0
  55. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/getting-started.md +0 -0
  56. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/index.md +0 -0
  57. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/lake-artifact-cache-design.md +0 -0
  58. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/local-projects.md +0 -0
  59. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/project-publishing.md +0 -0
  60. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/python-api.md +0 -0
  61. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/ready-programs.md +0 -0
  62. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/standalone-files.md +0 -0
  63. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/trust-and-limitations.md +0 -0
  64. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/docs/v1-precision.md +0 -0
  65. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/examples/mathlib.toml +0 -0
  66. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/__init__.py +0 -0
  67. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/__main__.py +0 -0
  68. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/_git.py +0 -0
  69. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/_paths.py +0 -0
  70. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/backends.py +0 -0
  71. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/bundles.py +0 -0
  72. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/capsules.py +0 -0
  73. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/comparison.py +0 -0
  74. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/decisions.py +0 -0
  75. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/diagnostics.py +0 -0
  76. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/__init__.py +0 -0
  77. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/analyzer.py +0 -0
  78. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/api.py +0 -0
  79. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/candidate.py +0 -0
  80. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/catalog.py +0 -0
  81. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/catalog_build.py +0 -0
  82. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/catalog_cli.py +0 -0
  83. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/catalog_manifest.py +0 -0
  84. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/data/catalog.json +0 -0
  85. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/defaults.py +0 -0
  86. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/engine.py +0 -0
  87. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/errors.py +0 -0
  88. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/module_inventory.py +0 -0
  89. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/planner.py +0 -0
  90. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/policy.py +0 -0
  91. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/probe.py +0 -0
  92. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/py.typed +0 -0
  93. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/result.py +0 -0
  94. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/schema_resources.py +0 -0
  95. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/schemas/catalog-v1.schema.json +0 -0
  96. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/schemas/plan-v1.schema.json +0 -0
  97. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/discovery/schemas/result-v1.schema.json +0 -0
  98. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/events.py +0 -0
  99. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/facade.py +0 -0
  100. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/frontmatter.py +0 -0
  101. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/identifier_resolver.py +0 -0
  102. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/lake.py +0 -0
  103. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/lake_cache.py +0 -0
  104. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/lockfiles.py +0 -0
  105. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/locking.py +0 -0
  106. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/matrix.py +0 -0
  107. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/oci_protocol.py +0 -0
  108. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/packs.py +0 -0
  109. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/policies.py +0 -0
  110. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/profiling.py +0 -0
  111. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/project_execution.py +0 -0
  112. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/projects.py +0 -0
  113. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/publisher_verification.py +0 -0
  114. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/py.typed +0 -0
  115. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/references.py +0 -0
  116. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/run_cli.py +0 -0
  117. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/schema_resources.py +0 -0
  118. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/serialization.py +0 -0
  119. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/specs.py +0 -0
  120. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/timings.py +0 -0
  121. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/toolchain_oci.py +0 -0
  122. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/toolchain_slim.py +0 -0
  123. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/verification.py +0 -0
  124. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime/wire.py +0 -0
  125. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime.egg-info/SOURCES.txt +0 -0
  126. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime.egg-info/dependency_links.txt +0 -0
  127. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime.egg-info/entry_points.txt +0 -0
  128. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime.egg-info/requires.txt +0 -0
  129. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/lean_runtime.egg-info/top_level.txt +0 -0
  130. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/mkdocs.yml +0 -0
  131. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/schemas/comparison-v1.schema.json +0 -0
  132. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/schemas/inspect-v1.schema.json +0 -0
  133. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/schemas/matrix-v1.schema.json +0 -0
  134. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/schemas/profile-v1.schema.json +0 -0
  135. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/schemas/verify-v1.schema.json +0 -0
  136. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/scripts/announcement_gate.py +0 -0
  137. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/scripts/lake_cache_gate.py +0 -0
  138. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/scripts/registry_preflight.py +0 -0
  139. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/scripts/run_compatibility.py +0 -0
  140. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/scripts/run_v1_case_study.py +0 -0
  141. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/scripts/smoke_wheel.py +0 -0
  142. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/setup.cfg +0 -0
  143. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/conftest.py +0 -0
  144. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/discovery/e2e/test_real_discovery.py +0 -0
  145. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/discovery/test_analyzer.py +0 -0
  146. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/discovery/test_catalog.py +0 -0
  147. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/discovery/test_catalog_build.py +0 -0
  148. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/discovery/test_discovery_schema_resources.py +0 -0
  149. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/discovery/test_engine.py +0 -0
  150. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/discovery/test_planner.py +0 -0
  151. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/discovery/test_policy.py +0 -0
  152. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_backend.py +0 -0
  153. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_capsules.py +0 -0
  154. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_diagnostics.py +0 -0
  155. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_environment_integration.py +0 -0
  156. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_facade.py +0 -0
  157. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_frontmatter.py +0 -0
  158. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_git.py +0 -0
  159. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_header_cache.py +0 -0
  160. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_identifier_resolver.py +0 -0
  161. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_interactive.py +0 -0
  162. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_lake_cache.py +0 -0
  163. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_lockfiles.py +0 -0
  164. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_models.py +0 -0
  165. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_multifile.py +0 -0
  166. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_oci_protocol.py +0 -0
  167. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_packs.py +0 -0
  168. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_paths.py +0 -0
  169. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_prebuilt_policy.py +0 -0
  170. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_programs.py +0 -0
  171. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_project_publish.py +0 -0
  172. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_references.py +0 -0
  173. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_run_cli.py +0 -0
  174. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_runtime.py +0 -0
  175. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_schema_resources.py +0 -0
  176. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_schemas.py +0 -0
  177. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_signatures.py +0 -0
  178. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_specs.py +0 -0
  179. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_toolchain_oci.py +0 -0
  180. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_toolchain_slim.py +0 -0
  181. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_toolchains.py +0 -0
  182. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_v1_precision.py +0 -0
  183. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_verification_inventory.py +0 -0
  184. {lean_runtime-2.9.1 → lean_runtime-2.9.2}/tests/test_verification_probe.py +0 -0
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 2.9.2 - 2026-08-16
6
+
7
+ - Keep one fail-closed publication authentication session from push preflight through
8
+ upload, propagate explicit publication timeouts to credential and registry I/O,
9
+ and report credential-provider failures without labeling them anonymous.
10
+ - Use logical project names during atomic initialization, show compact TTY-only check
11
+ progress, cache Lean executable identities across invocations, and report check
12
+ command-preparation timing.
13
+ - Track disposable execution and resolution workspaces with process-held leases;
14
+ include their footprint in storage reporting and reclaim abandoned workspaces with
15
+ `clean` while protecting active work.
16
+
5
17
  ## 2.9.1 - 2026-08-15
6
18
 
7
19
  - Make GHCR credential discovery a single bounded GitHub CLI status read so a
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-runtime
3
- Version: 2.9.1
3
+ Version: 2.9.2
4
4
  Summary: Run Lean 4 proofs from Python or standalone files
5
5
  Author: Alejandro Radisic
6
6
  License-Expression: Apache-2.0
@@ -215,6 +215,9 @@ lean-runtime check research-stack Main.lean --include Support/Defs.lean
215
215
  ```
216
216
 
217
217
  Resolution and materialization print structured lifecycle progress to stderr.
218
+ One-shot checks show a compact in-place status on interactive terminals while Lean
219
+ is working. Redirected output, `--json`, and `--quiet` remain free of human progress;
220
+ `--timings` separates command preparation from Lean execution.
218
221
  Pass global `--quiet` before the subcommand to suppress it.
219
222
 
220
223
  Pass global `--timings` before the subcommand for stable phase timing output. Machine-readable
@@ -93,6 +93,12 @@ Short-lived execution leases prevent deletion during cloning without
93
93
  serializing concurrent checks. Locks and source snapshots remain retained in
94
94
  the current store schema.
95
95
 
96
+ Disposable execution and dependency-resolution workspaces carry their own
97
+ process-held ownership leases. `storage` includes them under `Scratch`, and
98
+ `clean` previews and reclaims abandoned workspaces while retaining every
99
+ workspace with a live owner. Legacy scratch directories created before ownership
100
+ metadata are removed only by an explicit `clean --execute` after the safety grace.
101
+
96
102
  Sparse downloads are stored once by artifact digest under the shared module
97
103
  CAS and hardlinked into environment projections when the filesystem permits.
98
104
  `lean-runtime storage` reports this category separately. Because it is shared,
@@ -135,6 +135,12 @@ run:
135
135
  gh auth refresh -s write:packages,read:packages
136
136
  ```
137
137
 
138
+ Credential discovery is fail-closed: a configured provider that times out or
139
+ returns an unusable token stops publication before any registry request. The same
140
+ verified credential is retained from preflight through upload; it is not rediscovered
141
+ after a long environment build. An explicitly supplied publication `--timeout`
142
+ also applies to credential discovery and registry network operations.
143
+
138
144
  Publish the current platform after ensuring the lock:
139
145
 
140
146
  ```bash
@@ -8,12 +8,13 @@ import os
8
8
  import re
9
9
  import sys
10
10
  import time
11
+ from dataclasses import replace
11
12
  from datetime import datetime
12
13
  from importlib.metadata import version as distribution_version
13
14
  from pathlib import Path
14
15
  from typing import Any, cast
15
16
 
16
- from .console import styler_for
17
+ from .console import ConsoleRenderer, styler_for
17
18
  from .environments import ExecutionCapture
18
19
  from .errors import (
19
20
  LeanRuntimeError,
@@ -89,6 +90,7 @@ def _render_storage(status: StoreStatus) -> None:
89
90
  ("Project packages", status.project_packages, status.project_packages_bytes),
90
91
  ("Toolchains", None, status.toolchains_bytes),
91
92
  ("Executions", status.executions, status.executions_bytes),
93
+ ("Scratch", status.scratch_workspaces, status.scratch_bytes),
92
94
  )
93
95
  for label, count, bytes_used in rows:
94
96
  counted = f"{count:>5}" if count is not None else " "
@@ -139,7 +141,11 @@ def _render_doctor(report: DoctorReport) -> None:
139
141
  print(f"{symbols[check.status]} {check.name}: {check.message}")
140
142
 
141
143
 
142
- def _render_cleanup(environments: CleanupReport, downloads: DownloadCleanupReport | None) -> None:
144
+ def _render_cleanup(
145
+ environments: CleanupReport,
146
+ downloads: DownloadCleanupReport | None,
147
+ scratch: CleanupReport | None = None,
148
+ ) -> None:
143
149
  style = styler_for(sys.stdout)
144
150
  retained_note = style.dim(
145
151
  f"Retained {len(environments.retained)} environment(s) (named, recent, or in use)."
@@ -169,17 +175,36 @@ def _render_cleanup(environments: CleanupReport, downloads: DownloadCleanupRepor
169
175
  )
170
176
  else:
171
177
  print("No unreferenced cached downloads to remove.")
178
+ if scratch is not None:
179
+ if scratch.candidates:
180
+ anything = True
181
+ print(
182
+ style.bold(
183
+ f"Would remove {len(scratch.candidates)} abandoned workspace(s) · "
184
+ f"{format_byte_size(scratch.candidate_bytes)}"
185
+ )
186
+ )
187
+ else:
188
+ print("No abandoned workspaces to remove.")
172
189
  print(retained_note)
173
190
  if anything:
174
191
  print()
175
192
  print("This was a preview. Re-run with --execute to delete.")
176
193
  return
177
194
  reclaimed = environments.reclaimed_bytes + (downloads.reclaimed_bytes if downloads else 0)
178
- removed = len(environments.removed) + (len(downloads.removed) if downloads else 0)
195
+ if scratch is not None:
196
+ reclaimed += scratch.reclaimed_bytes
197
+ removed = (
198
+ len(environments.removed)
199
+ + (len(downloads.removed) if downloads else 0)
200
+ + (len(scratch.removed) if scratch else 0)
201
+ )
179
202
  if removed:
180
203
  parts = [f"{len(environments.removed)} environment(s)"]
181
204
  if downloads is not None:
182
205
  parts.append(f"{len(downloads.removed)} cached download(s)")
206
+ if scratch is not None:
207
+ parts.append(f"{len(scratch.removed)} abandoned workspace(s)")
183
208
  print(
184
209
  style.green(f"Removed {' and '.join(parts)} · reclaimed {format_byte_size(reclaimed)}")
185
210
  )
@@ -290,11 +315,15 @@ def _print_publication_failure(exc: PublicationError) -> None:
290
315
  file=sys.stderr,
291
316
  )
292
317
  print(f" {exc}", file=sys.stderr)
293
- identity = exc.username or "anonymous"
294
- print(
295
- f" Authentication: {identity} (source: {exc.credential_source})",
296
- file=sys.stderr,
297
- )
318
+ if exc.attempted_provider is not None:
319
+ print(" Authentication: unavailable (source: none)", file=sys.stderr)
320
+ print(f" Attempted provider: {exc.attempted_provider}", file=sys.stderr)
321
+ else:
322
+ identity = exc.username or "anonymous"
323
+ print(
324
+ f" Authentication: {identity} (source: {exc.credential_source})",
325
+ file=sys.stderr,
326
+ )
298
327
  if exc.partial:
299
328
  print(
300
329
  " No verified final release was produced; immutable platform content may exist.",
@@ -436,7 +465,12 @@ Run `lean-runtime COMMAND --help` for examples and options.""",
436
465
  publish_environment.add_argument("--publish-to", required=True)
437
466
  publish_environment.add_argument("--tag", action="append", default=[])
438
467
  publish_environment.add_argument("--name")
439
- publish_environment.add_argument("--timeout", type=float, default=1800)
468
+ publish_environment.add_argument(
469
+ "--timeout",
470
+ type=float,
471
+ default=None,
472
+ help="override timeouts for credential, registry, hydration, and build operations",
473
+ )
440
474
  publish_environment.add_argument("--platform-only", action="store_true")
441
475
  publish_environment.add_argument("--accelerate", action="store_true")
442
476
  publish_environment.add_argument("--sign", action="store_true")
@@ -511,8 +545,8 @@ Run `lean-runtime COMMAND --help` for examples and options.""",
511
545
  push.add_argument(
512
546
  "--timeout",
513
547
  type=float,
514
- default=1800,
515
- help="maximum seconds for each artifact hydration and the environment build",
548
+ default=None,
549
+ help="override timeouts for credential, registry, hydration, and build operations",
516
550
  )
517
551
  push.add_argument(
518
552
  "--platform-only",
@@ -933,6 +967,10 @@ def main(argv: list[str] | None = None) -> int:
933
967
  args.mathlib = None if args.core else args.mathlib_version
934
968
  operation_started = time.monotonic()
935
969
  display_path: str | None = None
970
+ renderer = ConsoleRenderer(
971
+ mode="quiet" if args.quiet or getattr(args, "json", False) else None,
972
+ verbose=args.verbose,
973
+ )
936
974
  try:
937
975
  if args.command == "completion":
938
976
  print(_completion_script(args.shell), end="")
@@ -947,7 +985,7 @@ def main(argv: list[str] | None = None) -> int:
947
985
  )
948
986
  runtime = Runtime(
949
987
  home=args.home,
950
- on_event=None if args.quiet else _progress,
988
+ on_event=renderer,
951
989
  availability=selected_availability,
952
990
  libraries=args.libraries,
953
991
  max_download_bytes=selected_download_limit,
@@ -1190,7 +1228,13 @@ def main(argv: list[str] | None = None) -> int:
1190
1228
  _json(environment.inspect().to_dict())
1191
1229
  return 0
1192
1230
  if args.command == "build-and-publish":
1193
- access = runtime.check_publication_access(args.publish_to)
1231
+ timeout_override = args.timeout
1232
+ publisher = runtime.begin_publication(
1233
+ args.publish_to,
1234
+ auth_timeout=timeout_override if timeout_override is not None else 10,
1235
+ registry_timeout=timeout_override if timeout_override is not None else 30,
1236
+ )
1237
+ access = publisher.check_access()
1194
1238
  if args.check_access:
1195
1239
  _json(
1196
1240
  envelope(_schema_for(args.command), ok=True, data=access.to_dict())
@@ -1203,7 +1247,7 @@ def main(argv: list[str] | None = None) -> int:
1203
1247
  environment = runtime.open_exact(
1204
1248
  EnvironmentLock.load(args.lock),
1205
1249
  name=args.name,
1206
- build_timeout=args.timeout,
1250
+ build_timeout=timeout_override if timeout_override is not None else 1800,
1207
1251
  accelerate=args.accelerate,
1208
1252
  )
1209
1253
  publication = runtime.publish_environment(
@@ -1213,6 +1257,7 @@ def main(argv: list[str] | None = None) -> int:
1213
1257
  finalize=not args.platform_only,
1214
1258
  sign=args.sign,
1215
1259
  attest=args.attest,
1260
+ publisher=publisher,
1216
1261
  ).to_dict()
1217
1262
  publication["consumer_command"] = (
1218
1263
  f"lean-runtime --library {args.publish_to} download {args.lock}"
@@ -1487,10 +1532,15 @@ def main(argv: list[str] | None = None) -> int:
1487
1532
  if args.include_downloads
1488
1533
  else None
1489
1534
  )
1535
+ gc_scratch = runtime.clean_scratch(
1536
+ dry_run=not args.execute,
1537
+ minimum_age_seconds=min(args.minimum_age_hours * 3600, 3600),
1538
+ )
1490
1539
  if args.json:
1491
1540
  gc_payload: dict[str, Any] = {
1492
1541
  "environments": gc_report.to_dict(),
1493
1542
  "downloaded_files": gc_downloads.to_dict() if gc_downloads else None,
1543
+ "scratch": gc_scratch.to_dict(),
1494
1544
  }
1495
1545
  _json(
1496
1546
  envelope(
@@ -1500,7 +1550,7 @@ def main(argv: list[str] | None = None) -> int:
1500
1550
  )
1501
1551
  )
1502
1552
  else:
1503
- _render_cleanup(gc_report, gc_downloads)
1553
+ _render_cleanup(gc_report, gc_downloads, gc_scratch)
1504
1554
  return 0
1505
1555
  if args.command == "check":
1506
1556
  if args.across is not None:
@@ -1590,6 +1640,14 @@ def main(argv: list[str] | None = None) -> int:
1590
1640
  )
1591
1641
  _emit_result(watched_result, False)
1592
1642
  time.sleep(args.watch_interval)
1643
+ check_subject = (
1644
+ Path(args.inputs[-1]).name
1645
+ if args.inputs and args.inputs[-1] != "-"
1646
+ else "stdin"
1647
+ if args.inputs
1648
+ else Path(args.project or ".").resolve().name
1649
+ )
1650
+ runtime.events.emit("check.started", "Checking Lean input", subject=check_subject)
1593
1651
  if args.project is not None and args.package_refs:
1594
1652
  raise ValueError("check cannot combine --project with --with")
1595
1653
  if not args.inputs:
@@ -1672,9 +1730,11 @@ def main(argv: list[str] | None = None) -> int:
1672
1730
  shared=args.shared,
1673
1731
  )
1674
1732
  except KeyboardInterrupt:
1733
+ renderer.close()
1675
1734
  print("lean-runtime: interrupted", file=sys.stderr)
1676
1735
  return 130
1677
1736
  except (ResolutionError, MaterializationError) as exc:
1737
+ renderer.close()
1678
1738
  details = {
1679
1739
  "phase": exc.phase,
1680
1740
  "command": list(exc.command),
@@ -1694,6 +1754,7 @@ def main(argv: list[str] | None = None) -> int:
1694
1754
  _print_operation_failure(exc, verbose=args.verbose)
1695
1755
  return 2
1696
1756
  except PublicationError as exc:
1757
+ renderer.close()
1697
1758
  if getattr(args, "json", False):
1698
1759
  _json(
1699
1760
  envelope(
@@ -1707,6 +1768,7 @@ def main(argv: list[str] | None = None) -> int:
1707
1768
  _print_publication_failure(exc)
1708
1769
  return exc.exit_code
1709
1770
  except (LeanRuntimeError, OSError, UnicodeError, ValueError, json.JSONDecodeError) as exc:
1771
+ renderer.close()
1710
1772
  if getattr(args, "json", False):
1711
1773
  _json(
1712
1774
  envelope(
@@ -1719,6 +1781,18 @@ def main(argv: list[str] | None = None) -> int:
1719
1781
  else:
1720
1782
  print(f"lean-runtime: {exc}", file=sys.stderr)
1721
1783
  return 2
1784
+ if args.command in {"check", "check-file"}:
1785
+ runtime.events.emit("check.completed", "Lean check completed", ok=result.ok)
1786
+ total_ms = round((time.monotonic() - operation_started) * 1000)
1787
+ accounted_ms = sum(timing.duration_ms for timing in result.timings)
1788
+ result = replace(
1789
+ result,
1790
+ timings=(
1791
+ PhaseTiming("command_preparation", max(0, total_ms - accounted_ms)),
1792
+ *result.timings,
1793
+ ),
1794
+ )
1795
+ renderer.close()
1722
1796
  _emit_result(result, args.json, display_path=display_path)
1723
1797
  if args.timings:
1724
1798
  print(render_timings(result.timings), file=sys.stderr)
@@ -215,6 +215,18 @@ class ConsoleRenderer:
215
215
  def _render_capability_required(self, event: RuntimeEvent) -> None:
216
216
  self._print(event.message)
217
217
 
218
+ def _render_check_started(self, event: RuntimeEvent) -> None:
219
+ if self.mode != "tty":
220
+ return
221
+ subject = str(event.data.get("subject") or "Lean input")
222
+ message = f"Checking {subject}…"
223
+ self.stream.write("\r" + self.style.cyan(message))
224
+ self.stream.flush()
225
+ self._line_length = len(message)
226
+
227
+ def _render_check_completed(self, _event: RuntimeEvent) -> None:
228
+ self._end_progress_line()
229
+
218
230
  # -- low-level output -----------------------------------------------
219
231
 
220
232
  def _verbose_line(self, event: RuntimeEvent) -> None:
@@ -707,14 +707,11 @@ class Environment:
707
707
  },
708
708
  )
709
709
  job_parent = self.manager.store.jobs / execution_id
710
- job_parent.mkdir(parents=True, exist_ok=True)
710
+ workspace_lease = self.manager.store.lease_workspace(job_parent, "interactive-environment")
711
711
  instance = job_parent / f"instance-{uuid.uuid4().hex}"
712
712
 
713
713
  def cleanup() -> None:
714
- if instance.exists():
715
- remove_tree(instance)
716
- with suppress(OSError):
717
- job_parent.rmdir()
714
+ workspace_lease.close()
718
715
 
719
716
  try:
720
717
  with self.manager.store.execution_lease(self.id):
@@ -844,7 +841,7 @@ class Environment:
844
841
  },
845
842
  )
846
843
  job_parent = self.manager.store.jobs / execution_id
847
- job_parent.mkdir(parents=True, exist_ok=True)
844
+ workspace_lease = self.manager.store.lease_workspace(job_parent, f"environment-{operation}")
848
845
  instance = job_parent / f"instance-{uuid.uuid4().hex}"
849
846
  try:
850
847
  instance_started = time.monotonic()
@@ -994,10 +991,7 @@ class Environment:
994
991
  )
995
992
  return result
996
993
  finally:
997
- if instance.exists():
998
- remove_tree(instance)
999
- with suppress(OSError):
1000
- job_parent.rmdir()
994
+ workspace_lease.close()
1001
995
 
1002
996
  def _result(
1003
997
  self,
@@ -81,6 +81,23 @@ class RegistryRequestError(DownloadUnavailable):
81
81
  self.retryable = retryable
82
82
 
83
83
 
84
+ class CredentialAcquisitionError(EnvironmentError):
85
+ """A configured credential provider could not produce usable credentials."""
86
+
87
+ def __init__(
88
+ self,
89
+ message: str,
90
+ *,
91
+ provider: str,
92
+ failure_kind: str,
93
+ retryable: bool,
94
+ ) -> None:
95
+ super().__init__(message)
96
+ self.provider = provider
97
+ self.failure_kind = failure_kind
98
+ self.retryable = retryable
99
+
100
+
84
101
  class PublicationError(EnvironmentError):
85
102
  """A required publication did not reach a remotely verified terminal state."""
86
103
 
@@ -97,6 +114,8 @@ class PublicationError(EnvironmentError):
97
114
  credential_source: str = "anonymous",
98
115
  username: str | None = None,
99
116
  hint: str | None = None,
117
+ attempted_provider: str | None = None,
118
+ auth_failure_kind: str | None = None,
100
119
  ) -> None:
101
120
  super().__init__(message)
102
121
  self.phase = phase
@@ -108,11 +127,15 @@ class PublicationError(EnvironmentError):
108
127
  self.credential_source = credential_source
109
128
  self.username = username
110
129
  self.hint = hint
130
+ self.attempted_provider = attempted_provider
131
+ self.auth_failure_kind = auth_failure_kind
111
132
 
112
133
  @property
113
134
  def exit_code(self) -> int:
114
135
  if self.partial:
115
136
  return 5
137
+ if self.phase == "credential_acquisition":
138
+ return 4 if self.retryable else 3
116
139
  if self.status_code in {401, 403}:
117
140
  return 3
118
141
  if self.retryable:
@@ -130,6 +153,8 @@ class PublicationError(EnvironmentError):
130
153
  "credential_source": self.credential_source,
131
154
  "username": self.username,
132
155
  "hint": self.hint,
156
+ "attempted_provider": self.attempted_provider,
157
+ "auth_failure_kind": self.auth_failure_kind,
133
158
  "message": str(self),
134
159
  }
135
160
 
@@ -50,11 +50,14 @@ class LeanHeaderCache:
50
50
  self.home = home / "header-snapshots"
51
51
  self.toolchains = toolchains
52
52
  self._support: dict[str, bool] = {}
53
+ self._toolchain_keys: dict[str, str] = {}
53
54
 
54
55
  def _toolchain_key(self, toolchain: str) -> str:
56
+ if toolchain in self._toolchain_keys:
57
+ return self._toolchain_keys[toolchain]
55
58
  digest = getattr(self.toolchains, "executable_digest", None)
56
59
  executable = str(digest(toolchain, "lean")) if callable(digest) else toolchain
57
- return sha256_id(
60
+ key = sha256_id(
58
61
  "lean-header",
59
62
  {
60
63
  "toolchain": toolchain,
@@ -62,6 +65,8 @@ class LeanHeaderCache:
62
65
  "platform": platform_compatibility(),
63
66
  },
64
67
  ).removeprefix("lean-header-")
68
+ self._toolchain_keys[toolchain] = key
69
+ return key
65
70
 
66
71
  def supported(self, toolchain: str) -> bool:
67
72
  key = self._toolchain_key(toolchain)
@@ -86,6 +86,18 @@ def diagnose(toolchains: ToolchainManager, store: EnvironmentStore) -> DoctorRep
86
86
  status = "warning" if staging else "pass"
87
87
  message = f"{len(staging)} incomplete staging directories" if staging else "No stale builds"
88
88
  checks.append(DoctorCheck("staging", status, message))
89
+ scratch = store.clean_scratch(dry_run=True, minimum_age_seconds=3600)
90
+ if scratch.candidates:
91
+ checks.append(
92
+ DoctorCheck(
93
+ "scratch",
94
+ "warning",
95
+ f"{len(scratch.candidates)} abandoned workspaces use "
96
+ f"{scratch.candidate_bytes // (1024**2)} MiB",
97
+ )
98
+ )
99
+ else:
100
+ checks.append(DoctorCheck("scratch", "pass", "No abandoned workspaces"))
89
101
  store_status = store.status()
90
102
  cutoff = datetime.now(timezone.utc).timestamp() - 7 * 24 * 3600
91
103
  stale = tuple(
@@ -114,6 +126,7 @@ def repair(toolchains: ToolchainManager, store: EnvironmentStore) -> DoctorRepor
114
126
  """Apply the safe remedies represented by doctor checks, then diagnose again."""
115
127
  for staging in store.environments.glob(".staging-*"):
116
128
  remove_tree(staging)
129
+ store.clean_scratch(dry_run=False, minimum_age_seconds=3600, include_legacy=False)
117
130
  with suppress(ToolchainError):
118
131
  toolchains.elan_path(bootstrap=True)
119
132
  return diagnose(toolchains, store)
@@ -19,6 +19,7 @@ TimingPhase = Literal[
19
19
  "environment_open",
20
20
  "instance_creation",
21
21
  "input_staging",
22
+ "command_preparation",
22
23
  "execution",
23
24
  "result_collection",
24
25
  "result_publication",
@@ -38,6 +39,7 @@ TIMING_PHASES = frozenset(
38
39
  "environment_open",
39
40
  "instance_creation",
40
41
  "input_staging",
42
+ "command_preparation",
41
43
  "execution",
42
44
  "result_collection",
43
45
  "result_publication",