lean-runtime 2.9.0__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.0 → lean_runtime-2.9.2}/CHANGELOG.md +19 -0
  2. {lean_runtime-2.9.0/lean_runtime.egg-info → lean_runtime-2.9.2}/PKG-INFO +1 -1
  3. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/cli.md +4 -0
  4. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/environments.md +6 -0
  5. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/portable-copies.md +9 -0
  6. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/cli.py +105 -18
  7. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/console.py +12 -0
  8. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/environments.py +4 -10
  9. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/errors.py +25 -0
  10. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/header_cache.py +6 -1
  11. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/health.py +13 -0
  12. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/models.py +2 -0
  13. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/oci.py +122 -22
  14. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/programs.py +2 -6
  15. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/project_sharing.py +3 -1
  16. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/resolver.py +5 -2
  17. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/runtime.py +38 -11
  18. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/schema_resources.py +1 -0
  19. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/shared_projects.py +5 -2
  20. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/store.py +122 -0
  21. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/toolchains.py +30 -1
  22. {lean_runtime-2.9.0 → lean_runtime-2.9.2/lean_runtime.egg-info}/PKG-INFO +1 -1
  23. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime.egg-info/SOURCES.txt +1 -0
  24. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/pyproject.toml +1 -1
  25. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/schemas/cleanup-v1.schema.json +7 -3
  26. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/schemas/execution-v1.schema.json +1 -1
  27. lean_runtime-2.9.2/schemas/publication-v1.schema.json +72 -0
  28. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_bundles.py +130 -2
  29. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_cli.py +77 -2
  30. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_console.py +12 -0
  31. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_events_health.py +1 -0
  32. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_projects.py +7 -0
  33. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_schema_resources.py +1 -0
  34. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_schemas.py +58 -0
  35. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_store.py +35 -0
  36. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/LICENSE +0 -0
  37. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/MANIFEST.in +0 -0
  38. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/README.md +0 -0
  39. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/catalog/environments.toml +0 -0
  40. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/catalog/locks/core-v4.32.2.lock.json +0 -0
  41. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/catalog/locks/leancert-v4.30.0.5.lock.json +0 -0
  42. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/catalog/locks/leancert-v4.31.0.lock.json +0 -0
  43. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/catalog/locks/leancert-v4.32.2.4.lock.json +0 -0
  44. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/catalog/locks/leancert-v4.33.0.lock.json +0 -0
  45. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/catalog/locks/mathlib-v4.30.0.lock.json +0 -0
  46. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/catalog/locks/mathlib-v4.31.0.lock.json +0 -0
  47. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/catalog/locks/mathlib-v4.32.2.lock.json +0 -0
  48. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/catalog/locks/mathlib-v4.33.0.lock.json +0 -0
  49. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/compatibility/README.md +0 -0
  50. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/compatibility/lake-artifact-cache-4.33.0.json +0 -0
  51. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/compatibility/mathlib-4.32.2.json +0 -0
  52. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/compatibility/mathlib-4.32.2.toml +0 -0
  53. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/compatibility/mathlib-4.33.0.json +0 -0
  54. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/compatibility/mathlib-4.33.0.toml +0 -0
  55. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/architecture.md +0 -0
  56. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/captures.md +0 -0
  57. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/case-study-v1.md +0 -0
  58. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/development.md +0 -0
  59. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/getting-started.md +0 -0
  60. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/index.md +0 -0
  61. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/lake-artifact-cache-design.md +0 -0
  62. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/local-projects.md +0 -0
  63. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/project-publishing.md +0 -0
  64. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/python-api.md +0 -0
  65. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/ready-programs.md +0 -0
  66. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/standalone-files.md +0 -0
  67. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/trust-and-limitations.md +0 -0
  68. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/docs/v1-precision.md +0 -0
  69. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/examples/mathlib.toml +0 -0
  70. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/__init__.py +0 -0
  71. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/__main__.py +0 -0
  72. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/_git.py +0 -0
  73. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/_paths.py +0 -0
  74. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/backends.py +0 -0
  75. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/bundles.py +0 -0
  76. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/capsules.py +0 -0
  77. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/comparison.py +0 -0
  78. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/decisions.py +0 -0
  79. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/diagnostics.py +0 -0
  80. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/__init__.py +0 -0
  81. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/analyzer.py +0 -0
  82. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/api.py +0 -0
  83. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/candidate.py +0 -0
  84. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/catalog.py +0 -0
  85. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/catalog_build.py +0 -0
  86. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/catalog_cli.py +0 -0
  87. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/catalog_manifest.py +0 -0
  88. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/data/catalog.json +0 -0
  89. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/defaults.py +0 -0
  90. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/engine.py +0 -0
  91. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/errors.py +0 -0
  92. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/module_inventory.py +0 -0
  93. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/planner.py +0 -0
  94. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/policy.py +0 -0
  95. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/probe.py +0 -0
  96. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/py.typed +0 -0
  97. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/result.py +0 -0
  98. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/schema_resources.py +0 -0
  99. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/schemas/catalog-v1.schema.json +0 -0
  100. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/schemas/plan-v1.schema.json +0 -0
  101. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/discovery/schemas/result-v1.schema.json +0 -0
  102. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/events.py +0 -0
  103. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/facade.py +0 -0
  104. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/frontmatter.py +0 -0
  105. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/identifier_resolver.py +0 -0
  106. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/lake.py +0 -0
  107. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/lake_cache.py +0 -0
  108. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/lockfiles.py +0 -0
  109. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/locking.py +0 -0
  110. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/matrix.py +0 -0
  111. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/oci_protocol.py +0 -0
  112. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/packs.py +0 -0
  113. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/policies.py +0 -0
  114. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/profiling.py +0 -0
  115. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/project_execution.py +0 -0
  116. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/projects.py +0 -0
  117. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/publisher_verification.py +0 -0
  118. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/py.typed +0 -0
  119. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/references.py +0 -0
  120. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/run_cli.py +0 -0
  121. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/serialization.py +0 -0
  122. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/specs.py +0 -0
  123. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/timings.py +0 -0
  124. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/toolchain_oci.py +0 -0
  125. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/toolchain_slim.py +0 -0
  126. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/verification.py +0 -0
  127. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime/wire.py +0 -0
  128. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime.egg-info/dependency_links.txt +0 -0
  129. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime.egg-info/entry_points.txt +0 -0
  130. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime.egg-info/requires.txt +0 -0
  131. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/lean_runtime.egg-info/top_level.txt +0 -0
  132. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/mkdocs.yml +0 -0
  133. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/schemas/comparison-v1.schema.json +0 -0
  134. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/schemas/inspect-v1.schema.json +0 -0
  135. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/schemas/matrix-v1.schema.json +0 -0
  136. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/schemas/profile-v1.schema.json +0 -0
  137. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/schemas/verify-v1.schema.json +0 -0
  138. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/scripts/announcement_gate.py +0 -0
  139. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/scripts/lake_cache_gate.py +0 -0
  140. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/scripts/registry_preflight.py +0 -0
  141. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/scripts/run_compatibility.py +0 -0
  142. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/scripts/run_v1_case_study.py +0 -0
  143. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/scripts/smoke_wheel.py +0 -0
  144. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/setup.cfg +0 -0
  145. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/conftest.py +0 -0
  146. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/discovery/e2e/test_real_discovery.py +0 -0
  147. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/discovery/test_analyzer.py +0 -0
  148. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/discovery/test_catalog.py +0 -0
  149. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/discovery/test_catalog_build.py +0 -0
  150. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/discovery/test_discovery_schema_resources.py +0 -0
  151. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/discovery/test_engine.py +0 -0
  152. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/discovery/test_planner.py +0 -0
  153. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/discovery/test_policy.py +0 -0
  154. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_backend.py +0 -0
  155. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_capsules.py +0 -0
  156. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_diagnostics.py +0 -0
  157. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_environment_integration.py +0 -0
  158. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_facade.py +0 -0
  159. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_frontmatter.py +0 -0
  160. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_git.py +0 -0
  161. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_header_cache.py +0 -0
  162. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_identifier_resolver.py +0 -0
  163. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_interactive.py +0 -0
  164. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_lake_cache.py +0 -0
  165. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_lockfiles.py +0 -0
  166. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_models.py +0 -0
  167. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_multifile.py +0 -0
  168. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_oci_protocol.py +0 -0
  169. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_packs.py +0 -0
  170. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_paths.py +0 -0
  171. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_prebuilt_policy.py +0 -0
  172. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_programs.py +0 -0
  173. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_project_publish.py +0 -0
  174. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_references.py +0 -0
  175. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_run_cli.py +0 -0
  176. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_runtime.py +0 -0
  177. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_signatures.py +0 -0
  178. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_specs.py +0 -0
  179. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_toolchain_oci.py +0 -0
  180. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_toolchain_slim.py +0 -0
  181. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_toolchains.py +0 -0
  182. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_v1_precision.py +0 -0
  183. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_verification_inventory.py +0 -0
  184. {lean_runtime-2.9.0 → lean_runtime-2.9.2}/tests/test_verification_probe.py +0 -0
@@ -2,6 +2,25 @@
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
+
17
+ ## 2.9.1 - 2026-08-15
18
+
19
+ - Make GHCR credential discovery a single bounded GitHub CLI status read so a
20
+ transient identity lookup cannot silently downgrade an authenticated account
21
+ to anonymous; add a validated `lean-runtime.publication/v1` envelope behind
22
+ `publish environment --json`.
23
+
5
24
  ## 2.9.0 - 2026-08-15
6
25
 
7
26
  - Make environment publication fail closed: verify registry push access before
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-runtime
3
- Version: 2.9.0
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
@@ -78,6 +78,7 @@ lean-runtime copy save research-stack --output research-stack.lean-environment
78
78
  lean-runtime --home /tmp/fresh copy open research-stack.lean-environment --name research-stack
79
79
  lean-runtime publish environment environment.lock.json --publish-to ghcr.io/owner/lean-environments
80
80
  lean-runtime publish environment --publish-to ghcr.io/owner/lean-environments --check-access
81
+ lean-runtime publish environment --publish-to ghcr.io/owner/lean-environments --check-access --json
81
82
  lean-runtime check research-stack Main.lean --json
82
83
  lean-runtime inspect research-stack --packages
83
84
  lean-runtime environments
@@ -214,6 +215,9 @@ lean-runtime check research-stack Main.lean --include Support/Defs.lean
214
215
  ```
215
216
 
216
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.
217
221
  Pass global `--quiet` before the subcommand to suppress it.
218
222
 
219
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,
@@ -121,6 +121,9 @@ lean-runtime publish environment \
121
121
  --check-access
122
122
  ```
123
123
 
124
+ Add `--json` for the versioned `lean-runtime.publication/v1` success or failure
125
+ envelope. The process exit code retains the classification below.
126
+
124
127
  For GHCR, credentials are selected in this order: the
125
128
  `LEAN_RUNTIME_REGISTRY_USERNAME` / `LEAN_RUNTIME_REGISTRY_PASSWORD` pair, an
126
129
  authenticated GitHub CLI session, then anonymous access. The access report names
@@ -132,6 +135,12 @@ run:
132
135
  gh auth refresh -s write:packages,read:packages
133
136
  ```
134
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
+
135
144
  Publish the current platform after ensuring the lock:
136
145
 
137
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,
@@ -55,6 +56,7 @@ def _schema_for(command: str) -> str:
55
56
  "matrix": "lean-runtime.matrix/v1",
56
57
  "clean": "lean-runtime.cleanup/v1",
57
58
  "inspect": "lean-runtime.inspect/v1",
59
+ "build-and-publish": "lean-runtime.publication/v1",
58
60
  }.get(command, "lean-runtime.execution/v1")
59
61
 
60
62
 
@@ -88,6 +90,7 @@ def _render_storage(status: StoreStatus) -> None:
88
90
  ("Project packages", status.project_packages, status.project_packages_bytes),
89
91
  ("Toolchains", None, status.toolchains_bytes),
90
92
  ("Executions", status.executions, status.executions_bytes),
93
+ ("Scratch", status.scratch_workspaces, status.scratch_bytes),
91
94
  )
92
95
  for label, count, bytes_used in rows:
93
96
  counted = f"{count:>5}" if count is not None else " "
@@ -138,7 +141,11 @@ def _render_doctor(report: DoctorReport) -> None:
138
141
  print(f"{symbols[check.status]} {check.name}: {check.message}")
139
142
 
140
143
 
141
- 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:
142
149
  style = styler_for(sys.stdout)
143
150
  retained_note = style.dim(
144
151
  f"Retained {len(environments.retained)} environment(s) (named, recent, or in use)."
@@ -168,17 +175,36 @@ def _render_cleanup(environments: CleanupReport, downloads: DownloadCleanupRepor
168
175
  )
169
176
  else:
170
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.")
171
189
  print(retained_note)
172
190
  if anything:
173
191
  print()
174
192
  print("This was a preview. Re-run with --execute to delete.")
175
193
  return
176
194
  reclaimed = environments.reclaimed_bytes + (downloads.reclaimed_bytes if downloads else 0)
177
- 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
+ )
178
202
  if removed:
179
203
  parts = [f"{len(environments.removed)} environment(s)"]
180
204
  if downloads is not None:
181
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)")
182
208
  print(
183
209
  style.green(f"Removed {' and '.join(parts)} · reclaimed {format_byte_size(reclaimed)}")
184
210
  )
@@ -289,11 +315,15 @@ def _print_publication_failure(exc: PublicationError) -> None:
289
315
  file=sys.stderr,
290
316
  )
291
317
  print(f" {exc}", file=sys.stderr)
292
- identity = exc.username or "anonymous"
293
- print(
294
- f" Authentication: {identity} (source: {exc.credential_source})",
295
- file=sys.stderr,
296
- )
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
+ )
297
327
  if exc.partial:
298
328
  print(
299
329
  " No verified final release was produced; immutable platform content may exist.",
@@ -435,11 +465,19 @@ Run `lean-runtime COMMAND --help` for examples and options.""",
435
465
  publish_environment.add_argument("--publish-to", required=True)
436
466
  publish_environment.add_argument("--tag", action="append", default=[])
437
467
  publish_environment.add_argument("--name")
438
- 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
+ )
439
474
  publish_environment.add_argument("--platform-only", action="store_true")
440
475
  publish_environment.add_argument("--accelerate", action="store_true")
441
476
  publish_environment.add_argument("--sign", action="store_true")
442
477
  publish_environment.add_argument("--attest", action="store_true")
478
+ publish_environment.add_argument(
479
+ "--json", action="store_true", help="emit a versioned JSON envelope"
480
+ )
443
481
  publish_environment.add_argument(
444
482
  "--check-access",
445
483
  action="store_true",
@@ -507,8 +545,8 @@ Run `lean-runtime COMMAND --help` for examples and options.""",
507
545
  push.add_argument(
508
546
  "--timeout",
509
547
  type=float,
510
- default=1800,
511
- 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",
512
550
  )
513
551
  push.add_argument(
514
552
  "--platform-only",
@@ -526,6 +564,7 @@ Run `lean-runtime COMMAND --help` for examples and options.""",
526
564
  push.add_argument(
527
565
  "--attest", action="store_true", help="publish a signed source/probe attestation"
528
566
  )
567
+ push.add_argument("--json", action="store_true", help="emit a versioned JSON envelope")
529
568
  push.add_argument(
530
569
  "--check-access",
531
570
  action="store_true",
@@ -928,6 +967,10 @@ def main(argv: list[str] | None = None) -> int:
928
967
  args.mathlib = None if args.core else args.mathlib_version
929
968
  operation_started = time.monotonic()
930
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
+ )
931
974
  try:
932
975
  if args.command == "completion":
933
976
  print(_completion_script(args.shell), end="")
@@ -942,7 +985,7 @@ def main(argv: list[str] | None = None) -> int:
942
985
  )
943
986
  runtime = Runtime(
944
987
  home=args.home,
945
- on_event=None if args.quiet else _progress,
988
+ on_event=renderer,
946
989
  availability=selected_availability,
947
990
  libraries=args.libraries,
948
991
  max_download_bytes=selected_download_limit,
@@ -1185,16 +1228,26 @@ def main(argv: list[str] | None = None) -> int:
1185
1228
  _json(environment.inspect().to_dict())
1186
1229
  return 0
1187
1230
  if args.command == "build-and-publish":
1188
- 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()
1189
1238
  if args.check_access:
1190
- _json(access.to_dict())
1239
+ _json(
1240
+ envelope(_schema_for(args.command), ok=True, data=access.to_dict())
1241
+ if args.json
1242
+ else access.to_dict()
1243
+ )
1191
1244
  return 0
1192
1245
  if args.lock is None:
1193
1246
  raise ValueError("publish environment requires LOCK unless --check-access is used")
1194
1247
  environment = runtime.open_exact(
1195
1248
  EnvironmentLock.load(args.lock),
1196
1249
  name=args.name,
1197
- build_timeout=args.timeout,
1250
+ build_timeout=timeout_override if timeout_override is not None else 1800,
1198
1251
  accelerate=args.accelerate,
1199
1252
  )
1200
1253
  publication = runtime.publish_environment(
@@ -1204,11 +1257,16 @@ def main(argv: list[str] | None = None) -> int:
1204
1257
  finalize=not args.platform_only,
1205
1258
  sign=args.sign,
1206
1259
  attest=args.attest,
1260
+ publisher=publisher,
1207
1261
  ).to_dict()
1208
1262
  publication["consumer_command"] = (
1209
1263
  f"lean-runtime --library {args.publish_to} download {args.lock}"
1210
1264
  )
1211
- _json(publication)
1265
+ _json(
1266
+ envelope(_schema_for(args.command), ok=True, data=publication)
1267
+ if args.json
1268
+ else publication
1269
+ )
1212
1270
  return 0
1213
1271
  if args.command == "finalize-publication":
1214
1272
  descriptors = []
@@ -1474,10 +1532,15 @@ def main(argv: list[str] | None = None) -> int:
1474
1532
  if args.include_downloads
1475
1533
  else None
1476
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
+ )
1477
1539
  if args.json:
1478
1540
  gc_payload: dict[str, Any] = {
1479
1541
  "environments": gc_report.to_dict(),
1480
1542
  "downloaded_files": gc_downloads.to_dict() if gc_downloads else None,
1543
+ "scratch": gc_scratch.to_dict(),
1481
1544
  }
1482
1545
  _json(
1483
1546
  envelope(
@@ -1487,7 +1550,7 @@ def main(argv: list[str] | None = None) -> int:
1487
1550
  )
1488
1551
  )
1489
1552
  else:
1490
- _render_cleanup(gc_report, gc_downloads)
1553
+ _render_cleanup(gc_report, gc_downloads, gc_scratch)
1491
1554
  return 0
1492
1555
  if args.command == "check":
1493
1556
  if args.across is not None:
@@ -1577,6 +1640,14 @@ def main(argv: list[str] | None = None) -> int:
1577
1640
  )
1578
1641
  _emit_result(watched_result, False)
1579
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)
1580
1651
  if args.project is not None and args.package_refs:
1581
1652
  raise ValueError("check cannot combine --project with --with")
1582
1653
  if not args.inputs:
@@ -1659,9 +1730,11 @@ def main(argv: list[str] | None = None) -> int:
1659
1730
  shared=args.shared,
1660
1731
  )
1661
1732
  except KeyboardInterrupt:
1733
+ renderer.close()
1662
1734
  print("lean-runtime: interrupted", file=sys.stderr)
1663
1735
  return 130
1664
1736
  except (ResolutionError, MaterializationError) as exc:
1737
+ renderer.close()
1665
1738
  details = {
1666
1739
  "phase": exc.phase,
1667
1740
  "command": list(exc.command),
@@ -1681,12 +1754,13 @@ def main(argv: list[str] | None = None) -> int:
1681
1754
  _print_operation_failure(exc, verbose=args.verbose)
1682
1755
  return 2
1683
1756
  except PublicationError as exc:
1757
+ renderer.close()
1684
1758
  if getattr(args, "json", False):
1685
1759
  _json(
1686
1760
  envelope(
1687
1761
  _schema_for(args.command),
1688
1762
  ok=False,
1689
- data={"publication": exc.to_dict()},
1763
+ data=exc.to_dict(),
1690
1764
  errors=[error("publication_failed", str(exc), details=exc.to_dict())],
1691
1765
  )
1692
1766
  )
@@ -1694,6 +1768,7 @@ def main(argv: list[str] | None = None) -> int:
1694
1768
  _print_publication_failure(exc)
1695
1769
  return exc.exit_code
1696
1770
  except (LeanRuntimeError, OSError, UnicodeError, ValueError, json.JSONDecodeError) as exc:
1771
+ renderer.close()
1697
1772
  if getattr(args, "json", False):
1698
1773
  _json(
1699
1774
  envelope(
@@ -1706,6 +1781,18 @@ def main(argv: list[str] | None = None) -> int:
1706
1781
  else:
1707
1782
  print(f"lean-runtime: {exc}", file=sys.stderr)
1708
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()
1709
1796
  _emit_result(result, args.json, display_path=display_path)
1710
1797
  if args.timings:
1711
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",