lean-runtime 2.8.0__tar.gz → 2.9.1__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.8.0 → lean_runtime-2.9.1}/CHANGELOG.md +21 -0
  2. {lean_runtime-2.8.0/lean_runtime.egg-info → lean_runtime-2.9.1}/PKG-INFO +9 -3
  3. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/README.md +8 -2
  4. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/cli.md +17 -4
  5. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/getting-started.md +5 -0
  6. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/local-projects.md +4 -1
  7. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/portable-copies.md +51 -4
  8. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/project-publishing.md +9 -2
  9. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/__init__.py +6 -1
  10. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/cli.py +87 -12
  11. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/errors.py +70 -0
  12. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/oci.py +346 -27
  13. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/project_execution.py +125 -31
  14. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/projects.py +26 -12
  15. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/runtime.py +48 -28
  16. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/schema_resources.py +1 -0
  17. {lean_runtime-2.8.0 → lean_runtime-2.9.1/lean_runtime.egg-info}/PKG-INFO +9 -3
  18. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime.egg-info/SOURCES.txt +1 -0
  19. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/pyproject.toml +1 -1
  20. lean_runtime-2.9.1/schemas/publication-v1.schema.json +70 -0
  21. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_bundles.py +260 -1
  22. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_cli.py +127 -1
  23. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_project_publish.py +45 -2
  24. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_projects.py +53 -0
  25. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_schema_resources.py +1 -0
  26. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_schemas.py +58 -0
  27. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/LICENSE +0 -0
  28. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/MANIFEST.in +0 -0
  29. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/catalog/environments.toml +0 -0
  30. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/catalog/locks/core-v4.32.2.lock.json +0 -0
  31. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/catalog/locks/leancert-v4.30.0.5.lock.json +0 -0
  32. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/catalog/locks/leancert-v4.31.0.lock.json +0 -0
  33. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/catalog/locks/leancert-v4.32.2.4.lock.json +0 -0
  34. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/catalog/locks/leancert-v4.33.0.lock.json +0 -0
  35. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/catalog/locks/mathlib-v4.30.0.lock.json +0 -0
  36. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/catalog/locks/mathlib-v4.31.0.lock.json +0 -0
  37. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/catalog/locks/mathlib-v4.32.2.lock.json +0 -0
  38. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/catalog/locks/mathlib-v4.33.0.lock.json +0 -0
  39. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/compatibility/README.md +0 -0
  40. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/compatibility/lake-artifact-cache-4.33.0.json +0 -0
  41. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/compatibility/mathlib-4.32.2.json +0 -0
  42. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/compatibility/mathlib-4.32.2.toml +0 -0
  43. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/compatibility/mathlib-4.33.0.json +0 -0
  44. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/compatibility/mathlib-4.33.0.toml +0 -0
  45. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/architecture.md +0 -0
  46. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/captures.md +0 -0
  47. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/case-study-v1.md +0 -0
  48. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/development.md +0 -0
  49. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/environments.md +0 -0
  50. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/index.md +0 -0
  51. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/lake-artifact-cache-design.md +0 -0
  52. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/python-api.md +0 -0
  53. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/ready-programs.md +0 -0
  54. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/standalone-files.md +0 -0
  55. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/trust-and-limitations.md +0 -0
  56. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/docs/v1-precision.md +0 -0
  57. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/examples/mathlib.toml +0 -0
  58. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/__main__.py +0 -0
  59. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/_git.py +0 -0
  60. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/_paths.py +0 -0
  61. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/backends.py +0 -0
  62. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/bundles.py +0 -0
  63. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/capsules.py +0 -0
  64. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/comparison.py +0 -0
  65. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/console.py +0 -0
  66. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/decisions.py +0 -0
  67. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/diagnostics.py +0 -0
  68. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/__init__.py +0 -0
  69. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/analyzer.py +0 -0
  70. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/api.py +0 -0
  71. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/candidate.py +0 -0
  72. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/catalog.py +0 -0
  73. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/catalog_build.py +0 -0
  74. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/catalog_cli.py +0 -0
  75. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/catalog_manifest.py +0 -0
  76. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/data/catalog.json +0 -0
  77. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/defaults.py +0 -0
  78. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/engine.py +0 -0
  79. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/errors.py +0 -0
  80. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/module_inventory.py +0 -0
  81. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/planner.py +0 -0
  82. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/policy.py +0 -0
  83. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/probe.py +0 -0
  84. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/py.typed +0 -0
  85. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/result.py +0 -0
  86. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/schema_resources.py +0 -0
  87. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/schemas/catalog-v1.schema.json +0 -0
  88. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/schemas/plan-v1.schema.json +0 -0
  89. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/discovery/schemas/result-v1.schema.json +0 -0
  90. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/environments.py +0 -0
  91. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/events.py +0 -0
  92. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/facade.py +0 -0
  93. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/frontmatter.py +0 -0
  94. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/header_cache.py +0 -0
  95. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/health.py +0 -0
  96. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/identifier_resolver.py +0 -0
  97. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/lake.py +0 -0
  98. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/lake_cache.py +0 -0
  99. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/lockfiles.py +0 -0
  100. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/locking.py +0 -0
  101. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/matrix.py +0 -0
  102. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/models.py +0 -0
  103. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/oci_protocol.py +0 -0
  104. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/packs.py +0 -0
  105. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/policies.py +0 -0
  106. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/profiling.py +0 -0
  107. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/programs.py +0 -0
  108. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/project_sharing.py +0 -0
  109. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/publisher_verification.py +0 -0
  110. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/py.typed +0 -0
  111. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/references.py +0 -0
  112. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/resolver.py +0 -0
  113. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/run_cli.py +0 -0
  114. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/serialization.py +0 -0
  115. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/shared_projects.py +0 -0
  116. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/specs.py +0 -0
  117. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/store.py +0 -0
  118. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/timings.py +0 -0
  119. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/toolchain_oci.py +0 -0
  120. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/toolchain_slim.py +0 -0
  121. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/toolchains.py +0 -0
  122. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/verification.py +0 -0
  123. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime/wire.py +0 -0
  124. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime.egg-info/dependency_links.txt +0 -0
  125. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime.egg-info/entry_points.txt +0 -0
  126. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime.egg-info/requires.txt +0 -0
  127. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/lean_runtime.egg-info/top_level.txt +0 -0
  128. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/mkdocs.yml +0 -0
  129. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/schemas/cleanup-v1.schema.json +0 -0
  130. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/schemas/comparison-v1.schema.json +0 -0
  131. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/schemas/execution-v1.schema.json +0 -0
  132. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/schemas/inspect-v1.schema.json +0 -0
  133. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/schemas/matrix-v1.schema.json +0 -0
  134. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/schemas/profile-v1.schema.json +0 -0
  135. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/schemas/verify-v1.schema.json +0 -0
  136. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/scripts/announcement_gate.py +0 -0
  137. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/scripts/lake_cache_gate.py +0 -0
  138. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/scripts/registry_preflight.py +0 -0
  139. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/scripts/run_compatibility.py +0 -0
  140. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/scripts/run_v1_case_study.py +0 -0
  141. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/scripts/smoke_wheel.py +0 -0
  142. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/setup.cfg +0 -0
  143. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/conftest.py +0 -0
  144. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/discovery/e2e/test_real_discovery.py +0 -0
  145. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/discovery/test_analyzer.py +0 -0
  146. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/discovery/test_catalog.py +0 -0
  147. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/discovery/test_catalog_build.py +0 -0
  148. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/discovery/test_discovery_schema_resources.py +0 -0
  149. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/discovery/test_engine.py +0 -0
  150. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/discovery/test_planner.py +0 -0
  151. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/discovery/test_policy.py +0 -0
  152. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_backend.py +0 -0
  153. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_capsules.py +0 -0
  154. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_console.py +0 -0
  155. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_diagnostics.py +0 -0
  156. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_environment_integration.py +0 -0
  157. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_events_health.py +0 -0
  158. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_facade.py +0 -0
  159. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_frontmatter.py +0 -0
  160. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_git.py +0 -0
  161. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_header_cache.py +0 -0
  162. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_identifier_resolver.py +0 -0
  163. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_interactive.py +0 -0
  164. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_lake_cache.py +0 -0
  165. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_lockfiles.py +0 -0
  166. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_models.py +0 -0
  167. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_multifile.py +0 -0
  168. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_oci_protocol.py +0 -0
  169. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_packs.py +0 -0
  170. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_paths.py +0 -0
  171. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_prebuilt_policy.py +0 -0
  172. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_programs.py +0 -0
  173. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_references.py +0 -0
  174. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_run_cli.py +0 -0
  175. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_runtime.py +0 -0
  176. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_signatures.py +0 -0
  177. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_specs.py +0 -0
  178. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_store.py +0 -0
  179. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_toolchain_oci.py +0 -0
  180. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_toolchain_slim.py +0 -0
  181. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_toolchains.py +0 -0
  182. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_v1_precision.py +0 -0
  183. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_verification_inventory.py +0 -0
  184. {lean_runtime-2.8.0 → lean_runtime-2.9.1}/tests/test_verification_probe.py +0 -0
@@ -2,6 +2,27 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 2.9.1 - 2026-08-15
6
+
7
+ - Make GHCR credential discovery a single bounded GitHub CLI status read so a
8
+ transient identity lookup cannot silently downgrade an authenticated account
9
+ to anonymous; add a validated `lean-runtime.publication/v1` envelope behind
10
+ `publish environment --json`.
11
+
12
+ ## 2.9.0 - 2026-08-15
13
+
14
+ - Make environment publication fail closed: verify registry push access before
15
+ building, report the selected credential source, remotely verify every
16
+ manifest, emit an explicit terminal failure event, and reserve exit codes 3,
17
+ 4, and 5 for permission, retryable transport, and partial/indeterminate
18
+ publication failures respectively.
19
+ - Make standalone `check FILE` failures point directly to `--toolchain`, and
20
+ have project checks ask Lake to materialize a missing local import before one
21
+ automatic retry.
22
+ - Accept any fetchable Git origin for immutable project locks and local exports,
23
+ including self-hosted and local bare repositories; GitHub origins retain
24
+ their canonical `github:` convenience reference.
25
+
5
26
  ## 2.8.0 - 2026-08-14
6
27
 
7
28
  - Cut repeated project-check latency with capability-probed Lean header snapshots;
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-runtime
3
- Version: 2.8.0
3
+ Version: 2.9.1
4
4
  Summary: Run Lean 4 proofs from Python or standalone files
5
5
  Author: Alejandro Radisic
6
6
  License-Expression: Apache-2.0
@@ -258,11 +258,17 @@ Environment libraries may be public or private. For example:
258
258
 
259
259
  ```bash
260
260
  lean-runtime --library ghcr.io/owner/lean-environments download environment.lock.json
261
- lean-runtime build-and-publish environment.lock.json \
261
+ lean-runtime publish environment environment.lock.json \
262
262
  --publish-to ghcr.io/owner/lean-environments
263
263
  ```
264
264
 
265
- To publish an existing clean, pushed GitHub Lean project, inspect it and
265
+ Publication verifies push access before doing an expensive build and reports
266
+ which credential source it selected. Run `lean-runtime publish environment
267
+ --publish-to ghcr.io/owner/lean-environments --check-access` to test access by
268
+ itself. A registry denial is a nonzero, machine-distinct failure; success is not
269
+ reported until the remote manifest digest is read back and verified.
270
+
271
+ To publish an existing clean Git-backed Lean project, inspect it and
266
272
  generate the maintained multi-platform workflow:
267
273
 
268
274
  ```bash
@@ -222,11 +222,17 @@ Environment libraries may be public or private. For example:
222
222
 
223
223
  ```bash
224
224
  lean-runtime --library ghcr.io/owner/lean-environments download environment.lock.json
225
- lean-runtime build-and-publish environment.lock.json \
225
+ lean-runtime publish environment environment.lock.json \
226
226
  --publish-to ghcr.io/owner/lean-environments
227
227
  ```
228
228
 
229
- To publish an existing clean, pushed GitHub Lean project, inspect it and
229
+ Publication verifies push access before doing an expensive build and reports
230
+ which credential source it selected. Run `lean-runtime publish environment
231
+ --publish-to ghcr.io/owner/lean-environments --check-access` to test access by
232
+ itself. A registry denial is a nonzero, machine-distinct failure; success is not
233
+ reported until the remote manifest digest is read back and verified.
234
+
235
+ To publish an existing clean Git-backed Lean project, inspect it and
230
236
  generate the maintained multi-platform workflow:
231
237
 
232
238
  ```bash
@@ -77,6 +77,8 @@ lean-runtime --library ghcr.io/owner/lean-environments download environment.lock
77
77
  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
+ 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
80
82
  lean-runtime check research-stack Main.lean --json
81
83
  lean-runtime inspect research-stack --packages
82
84
  lean-runtime environments
@@ -225,6 +227,15 @@ controls whether ready-to-use environments are downloaded or built locally.
225
227
  `LEAN_RUNTIME_LIBRARIES` accepts a comma-separated equivalent and
226
228
  `LEAN_RUNTIME_AVAILABILITY` sets the default policy.
227
229
 
230
+ Environment publishing probes repository push access before opening or building
231
+ the lock. `--check-access` runs only that content-free preflight and needs no
232
+ lock. GHCR publishers can use an authenticated `gh` session; explicit
233
+ `LEAN_RUNTIME_REGISTRY_USERNAME` and `LEAN_RUNTIME_REGISTRY_PASSWORD` values take
234
+ precedence. Publication exits `3` for authentication/permission denial, `4` for
235
+ retryable transport or registry failures, and `5` when remote state is partial
236
+ or indeterminate. Success is emitted only after the published manifest is read
237
+ back and its digest is verified. See [Portable copies](portable-copies.md#publishing).
238
+
228
239
  ## Publish an existing project
229
240
 
230
241
  ```bash
@@ -235,10 +246,12 @@ lean-runtime project init-publish . --module MyProject \
235
246
  --library ghcr.io/owner/my-project-environments
236
247
  ```
237
248
 
238
- `inspect` is read-only. The other commands require a clean root GitHub project
239
- whose exact HEAD commit is available from `origin`. `init-publish` creates a
240
- small caller for the maintained multi-platform publication and clean-consumer
241
- workflow. See [Publishing a Lean project](project-publishing.md).
249
+ `inspect` is read-only. The other commands require a clean root Git project
250
+ whose exact HEAD commit is available from its configured origin. GitHub,
251
+ self-hosted HTTPS/SSH, and local bare repositories are supported.
252
+ `init-publish` creates a small caller for the maintained multi-platform
253
+ publication and clean-consumer workflow. See
254
+ [Publishing a Lean project](project-publishing.md).
242
255
 
243
256
  Use global `--publisher_verification required --trusted-publisher ID --trusted-issuer ISSUER`
244
257
  to require a verified publisher. `build-and-publish --sign` records the trusted
@@ -115,6 +115,11 @@ lean-run Main.lean
115
115
  then lets Lean determine which candidate accepts the source. Use
116
116
  `--lock-out environment.lock.json` to pin the successful environment.
117
117
 
118
+ The lower-level `lean-runtime check Main.lean` command does not guess a
119
+ toolchain for a standalone file. Pass one explicitly, for example
120
+ `lean-runtime check Main.lean --toolchain v4.33.0`, or use `lean-run` for
121
+ catalog discovery.
122
+
118
123
  Strict TOML frontmatter remains available when the context is already known:
119
124
 
120
125
  ```lean
@@ -29,7 +29,10 @@ lean-runtime check ./my-project/MyProject/Main.lean
29
29
  ```
30
30
 
31
31
  The actual project-relative file is passed to `lake env lean`, so imports of
32
- local modules retain normal Lake semantics. Checking a source string writes a
32
+ local modules retain normal Lake semantics. A successful file check does not
33
+ itself write that file's `.olean`; if another checked file imports a local
34
+ module whose artifact is missing, Lean Runtime asks Lake to build that module's
35
+ `leanArts` target and retries once. Checking a source string writes a
33
36
  uniquely named disposable file under `.lake/lean-runtime/` and removes it after
34
37
  execution:
35
38
 
@@ -113,25 +113,72 @@ Signature failure is an integrity failure and never triggers source fallback.
113
113
 
114
114
  ## Publishing
115
115
 
116
+ Check push access without building or uploading an environment:
117
+
118
+ ```bash
119
+ lean-runtime publish environment \
120
+ --publish-to ghcr.io/alerad/leancert-runtime \
121
+ --check-access
122
+ ```
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
+
127
+ For GHCR, credentials are selected in this order: the
128
+ `LEAN_RUNTIME_REGISTRY_USERNAME` / `LEAN_RUNTIME_REGISTRY_PASSWORD` pair, an
129
+ authenticated GitHub CLI session, then anonymous access. The access report names
130
+ the selected account and source but never prints the token. Lean Runtime does not
131
+ implicitly read Docker's credential store. To give the GitHub CLI package access,
132
+ run:
133
+
134
+ ```bash
135
+ gh auth refresh -s write:packages,read:packages
136
+ ```
137
+
116
138
  Publish the current platform after ensuring the lock:
117
139
 
118
140
  ```bash
119
141
  export LEAN_RUNTIME_REGISTRY_USERNAME=alerad
120
142
  export LEAN_RUNTIME_REGISTRY_PASSWORD="$GHCR_TOKEN"
121
143
 
122
- lean-runtime build-and-publish environment.lock.json \
144
+ lean-runtime publish environment environment.lock.json \
123
145
  --publish-to ghcr.io/alerad/leancert-runtime \
124
146
  --tag v4.32.2.4 \
125
147
  --sign --attest
126
148
  ```
127
149
 
128
- The publisher checks for existing blobs, uploads only missing content, publishes
129
- the platform manifest by digest, and updates the canonical `lock_<sha>` index
130
- tag last. Human tags are aliases to that same index. This follows the
150
+ The same push-access probe runs automatically before the environment is built,
151
+ so a missing scope fails in seconds instead of after export. The probe starts
152
+ and immediately cancels an empty OCI upload session; it publishes no manifest or
153
+ index. The publisher then checks for existing blobs, uploads only missing
154
+ content, publishes the platform manifest by digest, and updates the canonical
155
+ `lock_<sha>` index tag last. Every manifest is fetched back and its digest is
156
+ verified before success is reported. Human tags are aliases to that same index.
157
+ This follows the
131
158
  [OCI Distribution Specification](https://github.com/opencontainers/distribution-spec/blob/main/spec.md)
132
159
  and uses an [OCI image index](https://github.com/opencontainers/image-spec/blob/main/image-index.md)
133
160
  for platform selection.
134
161
 
162
+ Publication has stable failure codes for CI:
163
+
164
+ | Exit | Meaning | Retry? |
165
+ |---:|---|---|
166
+ | `2` | Invalid invocation or local input | Fix the invocation |
167
+ | `3` | Registry authentication or permission denial | Fix credentials/scopes first |
168
+ | `4` | Retryable network, throttling, or registry 5xx failure | Yes, with backoff |
169
+ | `5` | Partial or indeterminate publication state | Inspect the terminal state, then retry safely |
170
+
171
+ An unpublished failure explicitly states that no manifest or index was
172
+ finalized; already uploaded content-addressed blobs are unreferenced and safe to
173
+ reuse on retry. Once a manifest write has been attempted, any failure that
174
+ cannot prove the resulting remote state is reported as partial/indeterminate;
175
+ it is never reported as success. Registries do not use status codes
176
+ uniformly: in particular, GHCR can return 403 for missing permissions as well as
177
+ an inaccessible or not-yet-created package namespace, so diagnostics describe
178
+ these as likely causes rather than certainties. Automation can consume the same
179
+ state from the terminal `library.publish_failed` or `library.published` runtime
180
+ event.
181
+
135
182
  Repository authors can use the bundled composite action after checking out the
136
183
  repository and generating or retaining a lock:
137
184
 
@@ -1,6 +1,6 @@
1
1
  # Publishing a Lean project
2
2
 
3
- Lean Runtime can freeze a clean, pushed GitHub Lean project into an exact lock,
3
+ Lean Runtime can freeze a clean Git-backed Lean project into an exact lock,
4
4
  build it on each supported computer type, and publish verified ready-to-use
5
5
  environments through an OCI-compatible library. Consumers import the project's
6
6
  public module without cloning or building the project.
@@ -24,7 +24,8 @@ Add `--check-remote` to prove that the exact HEAD commit is available from
24
24
  - a root `lakefile.toml` or `lakefile.lean` with an importable Lean library;
25
25
  - the Lake project at the Git repository root;
26
26
  - a clean checkout;
27
- - a GitHub `origin`; and
27
+ - a fetchable `origin` (GitHub, self-hosted HTTPS/SSH, `file://`, or a local
28
+ bare repository); and
28
29
  - a HEAD commit available from that remote.
29
30
 
30
31
  If `lakefile.lean` must be translated, its pinned toolchain must already be
@@ -40,6 +41,12 @@ The default output is `environment.lock.json` in the project root. The lock
40
41
  contains the exact project commit, complete Lake dependency graph, toolchain,
41
42
  and selected public module.
42
43
 
44
+ GitHub HTTPS and SSH origins are canonicalized to the portable HTTPS form and
45
+ receive a `github:OWNER/REPOSITORY@COMMIT` convenience reference. Other origins
46
+ are preserved as exact Git sources. Relative local paths become absolute
47
+ `file://` URLs, which is useful for offline export but intentionally remains
48
+ machine-local unless the resulting capsule is distributed.
49
+
43
50
  ## Export this computer
44
51
 
45
52
  A portable capsule is useful for transferring the current computer's
@@ -21,6 +21,8 @@ from .errors import (
21
21
  MaterializationError,
22
22
  PolicyError,
23
23
  ProjectError,
24
+ PublicationError,
25
+ RegistryRequestError,
24
26
  ResolutionError,
25
27
  SpecificationError,
26
28
  ToolchainError,
@@ -49,7 +51,7 @@ from .models import (
49
51
  PhaseTiming,
50
52
  ProjectProvenance,
51
53
  )
52
- from .oci import DEFAULT_ENVIRONMENT_LIBRARIES, PublicationInfo
54
+ from .oci import DEFAULT_ENVIRONMENT_LIBRARIES, PublicationAccess, PublicationInfo
53
55
  from .policies import ExecutionPolicy
54
56
  from .profiling import ProfileReport
55
57
  from .programs import ProgramDescription, ProgramInfo, ReadyProgram
@@ -142,6 +144,9 @@ __all__ = [
142
144
  "PreparedEnvironment",
143
145
  "PolicyError",
144
146
  "PublicationInfo",
147
+ "PublicationAccess",
148
+ "PublicationError",
149
+ "RegistryRequestError",
145
150
  "DownloadUnavailable",
146
151
  "ProjectError",
147
152
  "ResolutionError",
@@ -15,7 +15,13 @@ from typing import Any, cast
15
15
 
16
16
  from .console import styler_for
17
17
  from .environments import ExecutionCapture
18
- from .errors import LeanRuntimeError, MaterializationError, ProjectError, ResolutionError
18
+ from .errors import (
19
+ LeanRuntimeError,
20
+ MaterializationError,
21
+ ProjectError,
22
+ PublicationError,
23
+ ResolutionError,
24
+ )
19
25
  from .events import RuntimeEvent
20
26
  from .health import DoctorReport
21
27
  from .lake import ROOT_MODULE
@@ -49,6 +55,7 @@ def _schema_for(command: str) -> str:
49
55
  "matrix": "lean-runtime.matrix/v1",
50
56
  "clean": "lean-runtime.cleanup/v1",
51
57
  "inspect": "lean-runtime.inspect/v1",
58
+ "build-and-publish": "lean-runtime.publication/v1",
52
59
  }.get(command, "lean-runtime.execution/v1")
53
60
 
54
61
 
@@ -276,6 +283,31 @@ def _print_operation_failure(exc: ResolutionError | MaterializationError, *, ver
276
283
  print(exc.output.rstrip(), file=sys.stderr)
277
284
 
278
285
 
286
+ def _print_publication_failure(exc: PublicationError) -> None:
287
+ status = f" (HTTP {exc.status_code})" if exc.status_code is not None else ""
288
+ print(
289
+ f"✗ Publication failed at {exc.registry} during {exc.phase}{status}",
290
+ file=sys.stderr,
291
+ )
292
+ 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
+ )
298
+ if exc.partial:
299
+ print(
300
+ " No verified final release was produced; immutable platform content may exist.",
301
+ file=sys.stderr,
302
+ )
303
+ print(" Safe to retry the publication; verified content will be reused.", file=sys.stderr)
304
+ else:
305
+ print(" Nothing was published: no manifest or index was finalized.", file=sys.stderr)
306
+ print(" Safe to retry; completed blobs will be reused.", file=sys.stderr)
307
+ if exc.hint:
308
+ print(f" Fix: {exc.hint}", file=sys.stderr)
309
+
310
+
279
311
  def _cli_source_name(path: Path) -> str:
280
312
  if path.is_absolute():
281
313
  return path.name
@@ -400,7 +432,7 @@ Run `lean-runtime COMMAND --help` for examples and options.""",
400
432
  publish = commands.add_parser("publish", help="publish an environment, toolchain, or program")
401
433
  publish_kinds = publish.add_subparsers(dest="publish_kind", required=True)
402
434
  publish_environment = publish_kinds.add_parser("environment")
403
- publish_environment.add_argument("lock", type=Path)
435
+ publish_environment.add_argument("lock", nargs="?", type=Path)
404
436
  publish_environment.add_argument("--publish-to", required=True)
405
437
  publish_environment.add_argument("--tag", action="append", default=[])
406
438
  publish_environment.add_argument("--name")
@@ -409,6 +441,14 @@ Run `lean-runtime COMMAND --help` for examples and options.""",
409
441
  publish_environment.add_argument("--accelerate", action="store_true")
410
442
  publish_environment.add_argument("--sign", action="store_true")
411
443
  publish_environment.add_argument("--attest", action="store_true")
444
+ publish_environment.add_argument(
445
+ "--json", action="store_true", help="emit a versioned JSON envelope"
446
+ )
447
+ publish_environment.add_argument(
448
+ "--check-access",
449
+ action="store_true",
450
+ help="verify registry push access without building or publishing",
451
+ )
412
452
  publish_toolchain = publish_kinds.add_parser("toolchain")
413
453
  publish_toolchain.add_argument("toolchain")
414
454
  publish_toolchain.add_argument("--library", required=True)
@@ -464,7 +504,7 @@ Run `lean-runtime COMMAND --help` for examples and options.""",
464
504
  push = commands.add_parser(
465
505
  "build-and-publish", help="build an exact environment and publish it to a library"
466
506
  )
467
- push.add_argument("lock", type=Path)
507
+ push.add_argument("lock", nargs="?", type=Path)
468
508
  push.add_argument("--publish-to", required=True)
469
509
  push.add_argument("--tag", action="append", default=[])
470
510
  push.add_argument("--name")
@@ -490,6 +530,12 @@ Run `lean-runtime COMMAND --help` for examples and options.""",
490
530
  push.add_argument(
491
531
  "--attest", action="store_true", help="publish a signed source/probe attestation"
492
532
  )
533
+ push.add_argument("--json", action="store_true", help="emit a versioned JSON envelope")
534
+ push.add_argument(
535
+ "--check-access",
536
+ action="store_true",
537
+ help="verify registry push access without building or publishing",
538
+ )
493
539
 
494
540
  publish_index = commands.add_parser(
495
541
  "finalize-publication", help="combine computer-specific publication results"
@@ -777,7 +823,7 @@ Run `lean-runtime COMMAND --help` for examples and options.""",
777
823
  install.add_argument("toolchain")
778
824
 
779
825
  project = commands.add_parser(
780
- "project", help="inspect, freeze, or export a pinned GitHub Lean project"
826
+ "project", help="inspect, freeze, or export a pinned Git-backed Lean project"
781
827
  )
782
828
  project_commands = project.add_subparsers(dest="project_command", required=True)
783
829
  project_inspect = project_commands.add_parser(
@@ -1144,21 +1190,37 @@ def main(argv: list[str] | None = None) -> int:
1144
1190
  _json(environment.inspect().to_dict())
1145
1191
  return 0
1146
1192
  if args.command == "build-and-publish":
1193
+ access = runtime.check_publication_access(args.publish_to)
1194
+ if args.check_access:
1195
+ _json(
1196
+ envelope(_schema_for(args.command), ok=True, data=access.to_dict())
1197
+ if args.json
1198
+ else access.to_dict()
1199
+ )
1200
+ return 0
1201
+ if args.lock is None:
1202
+ raise ValueError("publish environment requires LOCK unless --check-access is used")
1147
1203
  environment = runtime.open_exact(
1148
1204
  EnvironmentLock.load(args.lock),
1149
1205
  name=args.name,
1150
1206
  build_timeout=args.timeout,
1151
1207
  accelerate=args.accelerate,
1152
1208
  )
1209
+ publication = runtime.publish_environment(
1210
+ environment.id,
1211
+ args.publish_to,
1212
+ tags=args.tag,
1213
+ finalize=not args.platform_only,
1214
+ sign=args.sign,
1215
+ attest=args.attest,
1216
+ ).to_dict()
1217
+ publication["consumer_command"] = (
1218
+ f"lean-runtime --library {args.publish_to} download {args.lock}"
1219
+ )
1153
1220
  _json(
1154
- runtime.publish_environment(
1155
- environment.id,
1156
- args.publish_to,
1157
- tags=args.tag,
1158
- finalize=not args.platform_only,
1159
- sign=args.sign,
1160
- attest=args.attest,
1161
- ).to_dict()
1221
+ envelope(_schema_for(args.command), ok=True, data=publication)
1222
+ if args.json
1223
+ else publication
1162
1224
  )
1163
1225
  return 0
1164
1226
  if args.command == "finalize-publication":
@@ -1631,6 +1693,19 @@ def main(argv: list[str] | None = None) -> int:
1631
1693
  else:
1632
1694
  _print_operation_failure(exc, verbose=args.verbose)
1633
1695
  return 2
1696
+ except PublicationError as exc:
1697
+ if getattr(args, "json", False):
1698
+ _json(
1699
+ envelope(
1700
+ _schema_for(args.command),
1701
+ ok=False,
1702
+ data=exc.to_dict(),
1703
+ errors=[error("publication_failed", str(exc), details=exc.to_dict())],
1704
+ )
1705
+ )
1706
+ else:
1707
+ _print_publication_failure(exc)
1708
+ return exc.exit_code
1634
1709
  except (LeanRuntimeError, OSError, UnicodeError, ValueError, json.JSONDecodeError) as exc:
1635
1710
  if getattr(args, "json", False):
1636
1711
  _json(
@@ -64,6 +64,76 @@ class DownloadUnavailable(EnvironmentError):
64
64
  """A prebuilt cache had no usable artifact or could not be reached."""
65
65
 
66
66
 
67
+ class RegistryRequestError(DownloadUnavailable):
68
+ """An OCI registry request failed with machine-readable retry semantics."""
69
+
70
+ def __init__(
71
+ self,
72
+ message: str,
73
+ *,
74
+ operation: str,
75
+ status_code: int | None = None,
76
+ retryable: bool = False,
77
+ ) -> None:
78
+ super().__init__(message)
79
+ self.operation = operation
80
+ self.status_code = status_code
81
+ self.retryable = retryable
82
+
83
+
84
+ class PublicationError(EnvironmentError):
85
+ """A required publication did not reach a remotely verified terminal state."""
86
+
87
+ def __init__(
88
+ self,
89
+ message: str,
90
+ *,
91
+ phase: str,
92
+ registry: str,
93
+ status_code: int | None = None,
94
+ retryable: bool = False,
95
+ published: bool = False,
96
+ partial: bool = False,
97
+ credential_source: str = "anonymous",
98
+ username: str | None = None,
99
+ hint: str | None = None,
100
+ ) -> None:
101
+ super().__init__(message)
102
+ self.phase = phase
103
+ self.registry = registry
104
+ self.status_code = status_code
105
+ self.retryable = retryable
106
+ self.published = published
107
+ self.partial = partial
108
+ self.credential_source = credential_source
109
+ self.username = username
110
+ self.hint = hint
111
+
112
+ @property
113
+ def exit_code(self) -> int:
114
+ if self.partial:
115
+ return 5
116
+ if self.status_code in {401, 403}:
117
+ return 3
118
+ if self.retryable:
119
+ return 4
120
+ return 5
121
+
122
+ def to_dict(self) -> dict[str, object]:
123
+ return {
124
+ "phase": self.phase,
125
+ "registry": self.registry,
126
+ "status_code": self.status_code,
127
+ "retryable": self.retryable,
128
+ "published": self.published,
129
+ "partial": self.partial,
130
+ "credential_source": self.credential_source,
131
+ "username": self.username,
132
+ "hint": self.hint,
133
+ "message": str(self),
134
+ }
135
+
136
+
67
137
  class DownloadLimitExceeded(EnvironmentError):
68
138
  """An acquisition needs more bytes than the configured download limit.
69
139