lean-runtime 4.27.0__tar.gz → 4.33.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/CHANGELOG.md +77 -0
  2. {lean_runtime-4.27.0/lean_runtime.egg-info → lean_runtime-4.33.0}/PKG-INFO +1 -1
  3. lean_runtime-4.33.0/lean_runtime/_paths.py +87 -0
  4. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/backends.py +35 -4
  5. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/bundles.py +140 -10
  6. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/capsules.py +50 -9
  7. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/cli.py +4 -3
  8. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/console.py +173 -11
  9. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/declaration_index.py +5 -4
  10. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/declaration_index_build.py +32 -0
  11. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/declaration_index_oci.py +38 -0
  12. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/events.py +22 -0
  13. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/locking.py +33 -11
  14. lean_runtime-4.33.0/lean_runtime/package_ids.py +42 -0
  15. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/packs.py +57 -42
  16. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/programs.py +33 -3
  17. lean_runtime-4.33.0/lean_runtime/progress.py +165 -0
  18. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/project_execution.py +6 -0
  19. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/project_sharing.py +60 -19
  20. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/projects.py +15 -5
  21. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/run_cli.py +6 -1
  22. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/runtime.py +102 -41
  23. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/shared_projects.py +14 -8
  24. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/store.py +153 -35
  25. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/toolchain_oci.py +28 -2
  26. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/toolchain_slim.py +49 -20
  27. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/toolchains.py +18 -9
  28. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/verification.py +67 -37
  29. {lean_runtime-4.27.0 → lean_runtime-4.33.0/lean_runtime.egg-info}/PKG-INFO +1 -1
  30. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime.egg-info/SOURCES.txt +5 -0
  31. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/pyproject.toml +1 -1
  32. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/conftest.py +12 -0
  33. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_backend.py +23 -2
  34. lean_runtime-4.33.0/tests/test_package_ids.py +44 -0
  35. lean_runtime-4.33.0/tests/test_paths.py +103 -0
  36. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_programs.py +1 -0
  37. lean_runtime-4.33.0/tests/test_progress.py +257 -0
  38. lean_runtime-4.33.0/tests/test_progress_counted.py +155 -0
  39. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_projects.py +9 -9
  40. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_runtime.py +22 -1
  41. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_store.py +2 -1
  42. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_toolchain_oci.py +2 -1
  43. lean_runtime-4.27.0/lean_runtime/_paths.py +0 -38
  44. lean_runtime-4.27.0/tests/test_paths.py +0 -25
  45. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/LICENSE +0 -0
  46. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/MANIFEST.in +0 -0
  47. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/README.md +0 -0
  48. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/catalog/environments.toml +0 -0
  49. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/catalog/locks/core-v4.32.2.lock.json +0 -0
  50. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/catalog/locks/leancert-v4.30.0.5.lock.json +0 -0
  51. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/catalog/locks/leancert-v4.31.0.lock.json +0 -0
  52. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/catalog/locks/leancert-v4.32.2.4.lock.json +0 -0
  53. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/catalog/locks/leancert-v4.33.0.lock.json +0 -0
  54. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/catalog/locks/mathlib-v4.30.0.lock.json +0 -0
  55. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/catalog/locks/mathlib-v4.31.0.lock.json +0 -0
  56. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/catalog/locks/mathlib-v4.32.2.lock.json +0 -0
  57. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/catalog/locks/mathlib-v4.33.0.lock.json +0 -0
  58. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/catalog/locks/mathlib-v4.33.1.lock.json +0 -0
  59. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/compatibility/README.md +0 -0
  60. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/compatibility/lake-artifact-cache-4.33.0.json +0 -0
  61. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/compatibility/mathlib-4.32.2.json +0 -0
  62. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/compatibility/mathlib-4.32.2.toml +0 -0
  63. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/compatibility/mathlib-4.33.0.json +0 -0
  64. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/compatibility/mathlib-4.33.0.toml +0 -0
  65. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/docs/concepts/context-selection.md +0 -0
  66. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/docs/index.md +0 -0
  67. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/docs/reference/commands.md +0 -0
  68. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/docs/reference/python-advanced.md +0 -0
  69. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/docs/reference/python-api.md +0 -0
  70. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/docs/reference/storage.md +0 -0
  71. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/docs/trust.md +0 -0
  72. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/docs/tutorial.md +0 -0
  73. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/docs/workflows/check-files.md +0 -0
  74. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/docs/workflows/lake-projects.md +0 -0
  75. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/examples/mathlib.toml +0 -0
  76. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/__init__.py +0 -0
  77. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/__main__.py +0 -0
  78. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/_git.py +0 -0
  79. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/comparison.py +0 -0
  80. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/context_resolution.py +0 -0
  81. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/decisions.py +0 -0
  82. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/diagnostics.py +0 -0
  83. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/__init__.py +0 -0
  84. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/analyzer.py +0 -0
  85. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/api.py +0 -0
  86. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/candidate.py +0 -0
  87. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/catalog.py +0 -0
  88. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/catalog_build.py +0 -0
  89. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/catalog_cli.py +0 -0
  90. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/catalog_manifest.py +0 -0
  91. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/data/catalog.json +0 -0
  92. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/defaults.py +0 -0
  93. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/engine.py +0 -0
  94. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/errors.py +0 -0
  95. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/history.py +0 -0
  96. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/memo.py +0 -0
  97. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/module_inventory.py +0 -0
  98. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/planner.py +0 -0
  99. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/policy.py +0 -0
  100. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/probe.py +0 -0
  101. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/py.typed +0 -0
  102. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/result.py +0 -0
  103. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/schema_resources.py +0 -0
  104. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/schemas/catalog-v1.schema.json +0 -0
  105. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/schemas/plan-v1.schema.json +0 -0
  106. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/discovery/schemas/result-v1.schema.json +0 -0
  107. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/environments.py +0 -0
  108. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/errors.py +0 -0
  109. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/facade.py +0 -0
  110. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/frontmatter.py +0 -0
  111. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/header_cache.py +0 -0
  112. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/health.py +0 -0
  113. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/identifier_resolver.py +0 -0
  114. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/import_syntax.py +0 -0
  115. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/lake.py +0 -0
  116. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/lake_cache.py +0 -0
  117. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/lockfiles.py +0 -0
  118. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/matrix.py +0 -0
  119. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/models.py +0 -0
  120. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/oci.py +0 -0
  121. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/oci_protocol.py +0 -0
  122. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/policies.py +0 -0
  123. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/profiling.py +0 -0
  124. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/publisher_verification.py +0 -0
  125. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/py.typed +0 -0
  126. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/references.py +0 -0
  127. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/resolver.py +0 -0
  128. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/schema_resources.py +0 -0
  129. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/serialization.py +0 -0
  130. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/specs.py +0 -0
  131. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/timings.py +0 -0
  132. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime/wire.py +0 -0
  133. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime.egg-info/dependency_links.txt +0 -0
  134. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime.egg-info/entry_points.txt +0 -0
  135. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime.egg-info/requires.txt +0 -0
  136. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/lean_runtime.egg-info/top_level.txt +0 -0
  137. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/mkdocs.yml +0 -0
  138. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/schemas/attestation-v1.schema.json +0 -0
  139. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/schemas/check-batch-v1.schema.json +0 -0
  140. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/schemas/cleanup-v1.schema.json +0 -0
  141. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/schemas/comparison-v1.schema.json +0 -0
  142. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/schemas/execution-v1.schema.json +0 -0
  143. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/schemas/inspect-v1.schema.json +0 -0
  144. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/schemas/matrix-v1.schema.json +0 -0
  145. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/schemas/plan-v1.schema.json +0 -0
  146. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/schemas/profile-v1.schema.json +0 -0
  147. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/schemas/publication-v1.schema.json +0 -0
  148. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/schemas/status-v1.schema.json +0 -0
  149. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/schemas/verify-v1.schema.json +0 -0
  150. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/scripts/announcement_gate.py +0 -0
  151. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/scripts/catalog_update.py +0 -0
  152. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/scripts/lake_cache_gate.py +0 -0
  153. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/scripts/prepare_declaration_index.py +0 -0
  154. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/scripts/publish_declaration_index.py +0 -0
  155. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/scripts/registry_preflight.py +0 -0
  156. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/scripts/run_compatibility.py +0 -0
  157. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/scripts/run_v1_case_study.py +0 -0
  158. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/scripts/smoke_wheel.py +0 -0
  159. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/setup.cfg +0 -0
  160. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/discovery/e2e/test_real_discovery.py +0 -0
  161. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/discovery/test_analyzer.py +0 -0
  162. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/discovery/test_catalog.py +0 -0
  163. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/discovery/test_catalog_build.py +0 -0
  164. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/discovery/test_discovery_schema_resources.py +0 -0
  165. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/discovery/test_engine.py +0 -0
  166. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/discovery/test_memo.py +0 -0
  167. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/discovery/test_planner.py +0 -0
  168. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/discovery/test_policy.py +0 -0
  169. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_announcement_gate.py +0 -0
  170. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_bundles.py +0 -0
  171. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_capsules.py +0 -0
  172. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_cli.py +0 -0
  173. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_console.py +0 -0
  174. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_context_resolution.py +0 -0
  175. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_declaration_index.py +0 -0
  176. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_diagnostics.py +0 -0
  177. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_environment_integration.py +0 -0
  178. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_events_health.py +0 -0
  179. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_facade.py +0 -0
  180. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_frontmatter.py +0 -0
  181. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_git.py +0 -0
  182. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_header_cache.py +0 -0
  183. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_identifier_resolver.py +0 -0
  184. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_interactive.py +0 -0
  185. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_lake_cache.py +0 -0
  186. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_lockfiles.py +0 -0
  187. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_locking.py +0 -0
  188. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_models.py +0 -0
  189. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_multifile.py +0 -0
  190. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_oci_protocol.py +0 -0
  191. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_packs.py +0 -0
  192. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_prebuilt_policy.py +0 -0
  193. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_project_bootstrap.py +0 -0
  194. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_project_publish.py +0 -0
  195. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_references.py +0 -0
  196. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_run_cli.py +0 -0
  197. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_schema_resources.py +0 -0
  198. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_schemas.py +0 -0
  199. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_signatures.py +0 -0
  200. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_specs.py +0 -0
  201. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_toolchain_slim.py +0 -0
  202. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_toolchains.py +0 -0
  203. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_v1_precision.py +0 -0
  204. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_verification_inventory.py +0 -0
  205. {lean_runtime-4.27.0 → lean_runtime-4.33.0}/tests/test_verification_probe.py +0 -0
@@ -2,6 +2,83 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 4.33.0 - 2026-08-27
6
+
7
+ - Cover the remaining silent phases with progress. Elan bootstrap and
8
+ `elan toolchain install`, and the `lake init`/`lake update` behind `new`, now
9
+ stream their output as `process.progress`/`process.output` like every other
10
+ subprocess. Long pure-Python loops report counted progress that renders as a
11
+ bar: `verification.inventory` (hashing build artifacts in `verify`),
12
+ `bundle.tree_inventory` (`env export`/`import`), `source.snapshot_digest`
13
+ (source fingerprints, including path dependencies during `adopt`), and
14
+ `capsule.import_parse`. The publish preflight fetch
15
+ (`publish.remote_probe_started`) and the Lake configuration read behind
16
+ project-wide `check` (`project.config_translate_started`) announce
17
+ themselves. Any event carrying `current`/`total` and a `label` now renders as
18
+ a counted bar without a dedicated console handler, and deep helpers reach
19
+ the active runtime's emitter through `lean_runtime.events.current()`.
20
+ - Re-running `adopt` over a tree no longer re-probes every already attached
21
+ project (a Lake graph load each, up to two minutes for Mathlib); the plan
22
+ already reports them as attached and `verify` re-checks links. Attachment
23
+ now announces `adopt.attach_started` per project, rendered as
24
+ `Attaching projects [bar] i/n`, and the pre-swap Lake graph probe streams its
25
+ output like every other Lake invocation instead of running silently.
26
+
27
+ ## 4.30.0 - 2026-08-26
28
+
29
+ - Attach shared dependencies on Windows without administrator rights. Creating
30
+ a symbolic link needs `SeCreateSymbolicLinkPrivilege` (administrators or
31
+ Developer Mode), so `adopt` and `project attach` failed with
32
+ `WinError 1314: A required privilege is not held by the client` on a normal
33
+ account, and the pre-attach probe never caught it. When symlink creation is
34
+ denied, the package link is now created as a directory junction, which any
35
+ user may create and which Lake and Git traverse identically. Every place
36
+ that recognizes an attached package as a link (`status`, `verify`,
37
+ `unshare`, adoption planning, rollback, source fingerprinting) now
38
+ recognizes junctions too.
39
+ - Size local dependency checkouts during `adopt` without descending into
40
+ attached links, using one directory scan per level instead of `rglob`.
41
+ - Show progress while subprocesses run. Lake's `[n/m]` build steps now stream
42
+ from `check`, `build`, and artifact hydration as `process.progress` events
43
+ (rate-limited to ten per second), rendered on a terminal as one in-place bar
44
+ (`lake [████░░░░] 3300/8318 · Building Mathlib.Foo`) and in logs as 25%
45
+ checkpoints; every other output line becomes a throttled `process.output`
46
+ heartbeat shown in place on a terminal, so `lake exe cache get` is no longer
47
+ silent. Third-party backends that do not accept the new optional `on_output`
48
+ callback keep working unchanged.
49
+ - Announce counted phases that used to be silent: `adopt.inspect_started` and
50
+ `adopt.identity_started` for each project during adoption planning, and
51
+ `project.detach.package_started` for each package `unshare` materializes; all
52
+ carry `current`/`total` and render as the same counted bar.
53
+ - Keep the terminal alive during any silent stretch: when no event has arrived
54
+ for two seconds the console shows `⋯ no new events for Ns · last: <activity>`
55
+ in place; plain logs print `Still working (…)` after ten seconds and then
56
+ every thirty.
57
+ - Name new shared package directories `pkg_<32 hex>` instead of
58
+ `project_package_<64 hex>`. The 80-character name pushed Mathlib's deepest
59
+ build outputs past Windows' 260-character path limit, so an adopted Mathlib
60
+ project could hydrate its cache but never finish `build`
61
+ (`failed to create file '…\Isometric.olean.server'`). The digest is the same
62
+ identity truncated to 128 bits; existing `project_package_*` directories stay
63
+ valid and reusable. To move already adopted projects onto short paths,
64
+ `lean-runtime project unshare` each of them, delete the legacy
65
+ `project-packages/project_package_*` directories (nothing references them once
66
+ every project is unshared; `clean` does not yet reclaim them), and
67
+ `lean-runtime adopt` again.
68
+ - Windows correctness fixes surfaced by running the suite on Windows:
69
+ compact source snapshots stage under a shorter directory name so Git's
70
+ shallow-clone metadata stays below `MAX_PATH` (`'$GIT_DIR' too big`);
71
+ `lean-runtime run` rewrites a container backend's POSIX staged path back to
72
+ the user's file on Windows hosts; lock waiters learn the holding operation
73
+ through a `.owner` sidecar (a byte-locked file cannot be read on Windows);
74
+ declaration shards close their SQLite connection before publication so the
75
+ rename succeeds; `storage` deduplicates hard links via `os.stat()` because
76
+ `DirEntry.stat()` reports no inode on Windows; `publish` accepts a
77
+ drive-letter origin path (`C:\...`) instead of treating `C` as a URL scheme;
78
+ bundle import accepts CRLF working files whose LF-normalized blob is the
79
+ recorded object (`core.autocrlf`); and storage cleanup clamps negative file
80
+ ages from filesystem timestamp granularity.
81
+
5
82
  ## 4.27.0 - 2026-08-26
6
83
 
7
84
  - Fingerprint a project by what Lake compiles, not by everything beside the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-runtime
3
- Version: 4.27.0
3
+ Version: 4.33.0
4
4
  Summary: Run Lean 4 proofs from Python or standalone files
5
5
  Author: Alejandro Radisic
6
6
  License-Expression: Apache-2.0
@@ -0,0 +1,87 @@
1
+ """Internal cross-platform filesystem operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shutil
7
+ import stat
8
+ import time
9
+ from collections.abc import Callable
10
+ from pathlib import Path
11
+ from types import TracebackType
12
+ from typing import Any, cast
13
+
14
+ _WINDOWS_MOUNT_POINT_REPARSE_TAG = 0xA0000003
15
+
16
+
17
+ def _retry_writable(
18
+ function: Callable[..., Any],
19
+ path: str,
20
+ _error: tuple[type[BaseException], BaseException, TracebackType | None],
21
+ ) -> None:
22
+ """Clear Git-for-Windows read-only attributes and retry one removal."""
23
+
24
+ os.chmod(path, stat.S_IWRITE)
25
+ function(path)
26
+
27
+
28
+ def is_link(path: Path) -> bool:
29
+ """Whether ``path`` is a symbolic link or a Windows directory junction.
30
+
31
+ Shared package directories are attached as links. Python reports a
32
+ junction as a plain directory (``is_symlink()`` is false), so every place
33
+ that asks "is this an attached link?" must go through this helper.
34
+ """
35
+
36
+ if path.is_symlink():
37
+ return True
38
+ if os.name != "nt":
39
+ return False
40
+ try:
41
+ status = os.lstat(path)
42
+ except OSError:
43
+ return False
44
+ attributes = getattr(status, "st_file_attributes", 0)
45
+ reparse_tag = getattr(status, "st_reparse_tag", 0)
46
+ return bool(attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT) and (
47
+ reparse_tag == _WINDOWS_MOUNT_POINT_REPARSE_TAG
48
+ )
49
+
50
+
51
+ def link_directory(target: Path, link: Path) -> None:
52
+ """Attach ``link`` to the directory ``target`` without requiring privileges.
53
+
54
+ A symbolic link is preferred everywhere. Windows only grants symlink
55
+ creation to administrators or Developer Mode users
56
+ (``ERROR_PRIVILEGE_NOT_HELD``, 1314); in that case a directory junction,
57
+ which any user may create and which Lake and Git traverse identically, is
58
+ used instead.
59
+ """
60
+
61
+ try:
62
+ os.symlink(target, link, target_is_directory=True)
63
+ except OSError as exc:
64
+ if os.name != "nt" or getattr(exc, "winerror", None) != 1314:
65
+ raise
66
+ import _winapi
67
+
68
+ # These members exist only in the Windows runtime/typeshed surface.
69
+ # Resolve the API dynamically so strict checking on macOS/Linux still
70
+ # validates this module instead of rejecting the platform-only member.
71
+ create_junction = cast(Callable[[str, str], None], vars(_winapi)["CreateJunction"])
72
+ create_junction(str(target.resolve()), str(link))
73
+
74
+
75
+ def remove_tree(path: Path) -> None:
76
+ """Remove a tree containing Git packs, tolerating transient Windows locks."""
77
+
78
+ for attempt in range(4):
79
+ try:
80
+ shutil.rmtree(path, onerror=_retry_writable)
81
+ return
82
+ except PermissionError:
83
+ if not path.exists():
84
+ return
85
+ if attempt == 3:
86
+ raise
87
+ time.sleep(0.05 * (2**attempt))
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
+ import re
6
7
  import signal
7
8
  import subprocess
8
9
  import threading
@@ -83,14 +84,39 @@ class _OutputBudget:
83
84
  return chunk[:size]
84
85
 
85
86
 
86
- def _drain(stream: BinaryIO, budget: _OutputBudget, chunks: list[bytes]) -> None:
87
+ _LINE_BREAK = re.compile(rb"\r\n|\r|\n")
88
+
89
+
90
+ def _drain(
91
+ stream: BinaryIO,
92
+ budget: _OutputBudget,
93
+ chunks: list[bytes],
94
+ on_output: Callable[[str], None] | None = None,
95
+ ) -> None:
96
+ pending = b""
87
97
  while True:
88
98
  chunk = stream.read(65_536)
89
99
  if not chunk:
90
- return
100
+ break
91
101
  kept = budget.take(chunk)
92
102
  if kept:
93
103
  chunks.append(kept)
104
+ if on_output is None:
105
+ continue
106
+ # Progress bars redraw with bare carriage returns, so treat those as lines too.
107
+ parts = _LINE_BREAK.split(pending + chunk)
108
+ pending = parts.pop()
109
+ for part in parts:
110
+ _observe(on_output, part)
111
+ if on_output is not None and pending:
112
+ _observe(on_output, pending)
113
+
114
+
115
+ def _observe(on_output: Callable[[str], None], line: bytes) -> None:
116
+ try:
117
+ on_output(line.decode("utf-8", errors="replace"))
118
+ except Exception: # noqa: BLE001 - an observer must never break execution
119
+ return
94
120
 
95
121
 
96
122
  class _TranscriptReader:
@@ -294,6 +320,7 @@ class LocalBackend:
294
320
  environment: Mapping[str, str],
295
321
  policy: ExecutionPolicy,
296
322
  cancel: threading.Event | None = None,
323
+ on_output: Callable[[str], None] | None = None,
297
324
  ) -> BackendResult:
298
325
  enforced, preexec, creationflags = self._process_options(policy)
299
326
 
@@ -313,8 +340,12 @@ class LocalBackend:
313
340
  stdout_chunks: list[bytes] = []
314
341
  stderr_chunks: list[bytes] = []
315
342
  readers = [
316
- threading.Thread(target=_drain, args=(process.stdout, budget, stdout_chunks)),
317
- threading.Thread(target=_drain, args=(process.stderr, budget, stderr_chunks)),
343
+ threading.Thread(
344
+ target=_drain, args=(process.stdout, budget, stdout_chunks, on_output)
345
+ ),
346
+ threading.Thread(
347
+ target=_drain, args=(process.stderr, budget, stderr_chunks, on_output)
348
+ ),
318
349
  ]
319
350
  for reader in readers:
320
351
  reader.start()
@@ -13,10 +13,11 @@ import tarfile
13
13
  import tempfile
14
14
  import uuid
15
15
  from collections.abc import Iterable, Mapping, Sequence
16
+ from contextlib import nullcontext
16
17
  from dataclasses import dataclass
17
18
  from datetime import datetime, timezone
18
19
  from pathlib import Path, PurePosixPath
19
- from typing import Any
20
+ from typing import Any, Protocol
20
21
 
21
22
  import zstandard
22
23
 
@@ -33,7 +34,7 @@ from .capsules import (
33
34
  setup_artifact_groups,
34
35
  )
35
36
  from .errors import EnvironmentError
36
- from .events import EventEmitter
37
+ from .events import EventEmitter, current
37
38
  from .lake import ROOT_MODULE
38
39
  from .lockfiles import EnvironmentLock, LockedPackage
39
40
  from .locking import FileLock
@@ -61,6 +62,7 @@ from .packs import (
61
62
  unpack_frame,
62
63
  )
63
64
  from .policies import ExecutionPolicy
65
+ from .progress import CountedProgress
64
66
  from .serialization import canonical_json_bytes, write_json_atomic
65
67
  from .store import (
66
68
  EnvironmentStore,
@@ -81,6 +83,53 @@ SOURCE_TREE_INVENTORY = ".lean-runtime-source-tree.json"
81
83
  MAX_CAPSULE_CONFIG_BYTES = 64 * 1024**2
82
84
 
83
85
 
86
+ class _BinaryReader(Protocol):
87
+ def read(self, size: int = -1) -> bytes: ...
88
+
89
+ def readinto(self, buffer: Any) -> int | None: ...
90
+
91
+ def seek(self, offset: int, whence: int = 0) -> int: ...
92
+
93
+ def tell(self) -> int: ...
94
+
95
+
96
+ class _ProgressReader:
97
+ """Report bytes after a consumer has actually read them."""
98
+
99
+ def __init__(
100
+ self, handle: _BinaryReader, progress: CountedProgress, *, offset: int = 0
101
+ ) -> None:
102
+ self._handle = handle
103
+ self._progress = progress
104
+ self._offset = offset
105
+ self._furthest = offset
106
+
107
+ def _report(self) -> None:
108
+ position = self._offset + self._handle.tell()
109
+ if position > self._furthest:
110
+ self._furthest = position
111
+ self._progress.advance(to=position)
112
+
113
+ def read(self, size: int = -1) -> bytes:
114
+ data = self._handle.read(size)
115
+ self._report()
116
+ return data
117
+
118
+ def readinto(self, buffer: Any) -> int:
119
+ count = self._handle.readinto(buffer)
120
+ self._report()
121
+ return count if count is not None else 0
122
+
123
+ def seek(self, offset: int, whence: int = 0) -> int:
124
+ return self._handle.seek(offset, whence)
125
+
126
+ def tell(self) -> int:
127
+ return self._handle.tell()
128
+
129
+ def __getattr__(self, name: str) -> Any:
130
+ return getattr(self._handle, name)
131
+
132
+
84
133
  @dataclass(frozen=True, slots=True)
85
134
  class PortableCopyInfo:
86
135
  environment_id: str
@@ -130,8 +179,27 @@ def _tree_entries(
130
179
  excluded_names: frozenset[str] = frozenset(),
131
180
  omit_volatile_build_metadata: bool = False,
132
181
  ) -> Iterable[tuple[Path, str]]:
133
- for path in sorted(root.rglob("*"), key=lambda value: value.relative_to(root).as_posix()):
182
+ scan = CountedProgress(
183
+ current().emit,
184
+ "bundle.tree_scan",
185
+ f"Scanning {root.name}",
186
+ 1,
187
+ phase="bundle",
188
+ )
189
+ scan.start()
190
+ paths = sorted(root.rglob("*"), key=lambda value: value.relative_to(root).as_posix())
191
+ scan.advance(f"{len(paths)} entries")
192
+ progress = CountedProgress(
193
+ current().emit,
194
+ "bundle.tree_inventory",
195
+ f"Inventorying {root.name}",
196
+ len(paths),
197
+ phase="bundle",
198
+ )
199
+ progress.start()
200
+ for path in paths:
134
201
  if excluded is not None and (path == excluded or excluded in path.parents):
202
+ progress.advance(path.relative_to(root).as_posix())
135
203
  continue
136
204
  relative = path.relative_to(root)
137
205
  if relative.parts and relative.parts[0] in excluded_names:
@@ -145,8 +213,10 @@ def _tree_entries(
145
213
  or relative.name.endswith(".rsp")
146
214
  )
147
215
  ):
216
+ progress.advance(relative.as_posix())
148
217
  continue
149
218
  yield path, relative.as_posix()
219
+ progress.advance(relative.as_posix())
150
220
 
151
221
 
152
222
  def _write_tar_gzip(
@@ -219,6 +289,18 @@ def _oci_archive(entries: dict[str, bytes]) -> bytes:
219
289
 
220
290
 
221
291
  def _write_oci_archive(entries: dict[str, Path], output: Path) -> None:
292
+ ordered = sorted(entries.items())
293
+ total = sum(path.stat().st_size for _name, path in ordered)
294
+ progress = CountedProgress(
295
+ current().emit,
296
+ "bundle.archive_write",
297
+ "Writing portable archive",
298
+ total,
299
+ phase="bundle",
300
+ unit="bytes",
301
+ )
302
+ progress.start()
303
+ written = 0
222
304
  with (
223
305
  output.open("wb") as raw_output,
224
306
  gzip.GzipFile(
@@ -226,11 +308,13 @@ def _write_oci_archive(entries: dict[str, Path], output: Path) -> None:
226
308
  ) as compressed,
227
309
  tarfile.open(fileobj=compressed, mode="w|", format=tarfile.PAX_FORMAT) as archive,
228
310
  ):
229
- for name, path in sorted(entries.items()):
311
+ for name, path in ordered:
230
312
  info = _normalized_info(name, mode=0o644)
231
313
  info.size = path.stat().st_size
232
314
  with path.open("rb") as handle:
233
- archive.addfile(info, handle)
315
+ archive.addfile(info, _ProgressReader(handle, progress, offset=written))
316
+ written += info.size
317
+ progress.advance(name, to=written)
234
318
 
235
319
 
236
320
  def _safe_name(name: str) -> PurePosixPath:
@@ -315,16 +399,32 @@ def _extract_layer(data: bytes | Path, destination: Path) -> None:
315
399
  if destination.is_symlink():
316
400
  raise EnvironmentError("bundle extraction destination must not be a symlink")
317
401
  destination.mkdir(parents=True, exist_ok=True)
402
+ progress: CountedProgress | None = None
403
+ raw_handle: Any = None
318
404
  try:
319
405
  if isinstance(data, Path):
320
- archive = tarfile.open(data, mode="r:gz") # noqa: SIM115
406
+ raw_handle = data.open("rb")
407
+ progress = CountedProgress(
408
+ current().emit,
409
+ "bundle.layer_extract",
410
+ f"Extracting {destination.name}",
411
+ data.stat().st_size,
412
+ phase="bundle",
413
+ unit="bytes",
414
+ )
415
+ progress.start()
416
+ archive = tarfile.open( # noqa: SIM115
417
+ fileobj=_ProgressReader(raw_handle, progress), mode="r:gz"
418
+ )
321
419
  else:
322
420
  archive = tarfile.open( # noqa: SIM115
323
421
  fileobj=io.BytesIO(data), mode="r:gz"
324
422
  )
325
423
  except (tarfile.TarError, OSError) as exc:
424
+ if raw_handle is not None:
425
+ raw_handle.close()
326
426
  raise EnvironmentError("bundle layer is not a valid gzip tar archive") from exc
327
- with archive:
427
+ with archive, raw_handle if raw_handle is not None else nullcontext():
328
428
  for member in archive:
329
429
  count += 1
330
430
  total += member.size
@@ -353,6 +453,8 @@ def _extract_layer(data: bytes | Path, destination: Path) -> None:
353
453
  target.symlink_to(member.linkname)
354
454
  else:
355
455
  raise EnvironmentError(f"unsupported bundle member: {member.name!r}")
456
+ if progress is not None:
457
+ progress.advance(to=progress.total)
356
458
 
357
459
 
358
460
  def _git_object_id(kind: str, data: bytes) -> str:
@@ -360,6 +462,18 @@ def _git_object_id(kind: str, data: bytes) -> str:
360
462
  return hashlib.sha1(framed, usedforsecurity=False).hexdigest() # noqa: S324
361
463
 
362
464
 
465
+ def _matches_normalized(data: bytes, object_id: str) -> bool:
466
+ """Accept a CRLF working file whose LF-normalized blob is the recorded one.
467
+
468
+ Git with ``core.autocrlf`` (the Git for Windows default) stores text blobs
469
+ with LF endings while checking them out with CRLF, so the on-disk bytes of
470
+ an untouched file legitimately hash differently from its recorded object.
471
+ """
472
+ if b"\r\n" not in data:
473
+ return False
474
+ return _git_object_id("blob", data.replace(b"\r\n", b"\n")) == object_id
475
+
476
+
363
477
  def _source_tree_inventory(root: Path, package: LockedPackage) -> bytes:
364
478
  process = subprocess.run(
365
479
  git_command("-C", str(root), "ls-tree", "-rz", "--full-tree", "HEAD"),
@@ -475,7 +589,7 @@ def _verify_source_tree_inventory(root: Path, package: LockedPackage, path: Path
475
589
  if executable != (mode == "100755"):
476
590
  raise EnvironmentError(f"bundled package source mode mismatch: {package.name}")
477
591
  data = source.read_bytes()
478
- if _git_object_id("blob", data) != object_id:
592
+ if _git_object_id("blob", data) != object_id and not _matches_normalized(data, object_id):
479
593
  raise EnvironmentError(f"bundled package source mismatch: {package.name}")
480
594
  entries.append(entry)
481
595
  if _inventory_tree_id(entries) != package.tree_hash:
@@ -1100,11 +1214,26 @@ class EnvironmentBundles:
1100
1214
  entries: dict[str, Path] = {}
1101
1215
  total = 0
1102
1216
  count = 0
1217
+ byte_progress = CountedProgress(
1218
+ current().emit,
1219
+ "bundle.archive_extract",
1220
+ f"Reading {bundle.name}",
1221
+ bundle.stat().st_size,
1222
+ phase="bundle",
1223
+ unit="bytes",
1224
+ )
1225
+ byte_progress.start()
1226
+ raw_handle: Any = None
1103
1227
  try:
1104
- archive = tarfile.open(bundle, mode="r:gz") # noqa: SIM115
1228
+ raw_handle = bundle.open("rb")
1229
+ archive = tarfile.open( # noqa: SIM115
1230
+ fileobj=_ProgressReader(raw_handle, byte_progress), mode="r:gz"
1231
+ )
1105
1232
  except (tarfile.TarError, OSError) as exc:
1233
+ if raw_handle is not None:
1234
+ raw_handle.close()
1106
1235
  raise EnvironmentError(f"could not read OCI bundle: {bundle}") from exc
1107
- with archive:
1236
+ with archive, raw_handle:
1108
1237
  for member in archive:
1109
1238
  count += 1
1110
1239
  if count > MAX_FILES:
@@ -1124,6 +1253,7 @@ class EnvironmentBundles:
1124
1253
  with path.open("wb") as output:
1125
1254
  shutil.copyfileobj(source, output)
1126
1255
  entries[member.name] = path
1256
+ byte_progress.advance(to=byte_progress.total)
1127
1257
  return entries
1128
1258
 
1129
1259
  def _import_capsule_layout(
@@ -21,8 +21,10 @@ from pathlib import Path, PurePosixPath
21
21
  from typing import Any, Literal
22
22
 
23
23
  from .errors import EnvironmentError
24
+ from .events import current
24
25
  from .import_syntax import IMPORT_STATEMENT
25
26
  from .lockfiles import EnvironmentLock
27
+ from .progress import CountedProgress
26
28
  from .serialization import canonical_json_bytes, write_json_atomic
27
29
 
28
30
  CAPSULE_SCHEMA = "lean-runtime-check-capsule/1"
@@ -253,6 +255,14 @@ def parse_import_headers(
253
255
  with tempfile.TemporaryDirectory(prefix="lean-runtime-imports-") as raw:
254
256
  helper = Path(raw) / "ParseImports.lean"
255
257
  helper.write_text(IMPORT_PARSER_SOURCE, encoding="utf-8")
258
+ progress = CountedProgress(
259
+ current().emit,
260
+ "capsule.import_parse",
261
+ "Parsing imports",
262
+ len(source_paths),
263
+ phase="capsule",
264
+ )
265
+ progress.start()
256
266
  for offset in range(0, len(source_paths), batch_size):
257
267
  batch = list(source_paths[offset : offset + batch_size])
258
268
  process = subprocess.run(
@@ -286,6 +296,7 @@ def parse_import_headers(
286
296
  if isinstance(item, dict) and isinstance(item.get("module"), str)
287
297
  }
288
298
  result[path] = tuple(sorted(names))
299
+ progress.advance(to=min(offset + len(batch), len(source_paths)))
289
300
  return result
290
301
 
291
302
 
@@ -369,16 +380,34 @@ def build_manifest(
369
380
  complete_modules: frozenset[str] | None = None,
370
381
  ) -> CapsuleManifest:
371
382
  """Inventory retained module artifacts beneath package build roots."""
383
+ scan = CountedProgress(
384
+ current().emit,
385
+ "capsule.artifact_scan",
386
+ "Scanning capsule artifacts",
387
+ 1,
388
+ phase="capsule",
389
+ )
390
+ scan.start()
391
+ paths = [
392
+ (package, root, path)
393
+ for package, root in sorted(build_roots.items())
394
+ if root.is_dir()
395
+ for path in sorted(root.rglob("*"))
396
+ if path.is_file()
397
+ ]
398
+ scan.advance(f"{len(paths)} files")
399
+ progress = CountedProgress(
400
+ current().emit,
401
+ "capsule.artifact_inventory",
402
+ "Hashing capsule artifacts",
403
+ len(paths),
404
+ phase="capsule",
405
+ )
406
+ progress.start()
372
407
  modules: dict[str, tuple[str, list[CapsuleArtifact]]] = {}
373
- for package, root in sorted(build_roots.items()):
374
- if not root.is_dir():
375
- continue
376
- for path in sorted(root.rglob("*")):
377
- if not path.is_file():
378
- continue
379
- module = module_from_artifact(path.relative_to(root))
380
- if module is None:
381
- continue
408
+ for package, root, path in paths:
409
+ module = module_from_artifact(path.relative_to(root))
410
+ if module is not None:
382
411
  relative = path.relative_to(workspace).as_posix()
383
412
  capability = artifact_capability(path)
384
413
  artifact = CapsuleArtifact(
@@ -390,6 +419,7 @@ def build_manifest(
390
419
  f"capsule module {module} is provided by both {existing[0]} and {package}"
391
420
  )
392
421
  modules.setdefault(module, (package, []))[1].append(artifact)
422
+ progress.advance(path.relative_to(workspace).as_posix())
393
423
  known_complete = frozenset(imports) if complete_modules is None else complete_modules
394
424
  records = tuple(
395
425
  CapsuleModule(
@@ -436,6 +466,14 @@ def inventory_workspace(
436
466
  ) -> CapsuleManifest:
437
467
  """Build an exact capsule inventory from a verified full workspace."""
438
468
  source_roots, build_roots = _package_directories(workspace, lock)
469
+ discovery = CountedProgress(
470
+ current().emit,
471
+ "capsule.module_discovery",
472
+ "Discovering capsule modules",
473
+ len(build_roots) + len(source_roots),
474
+ phase="capsule",
475
+ )
476
+ discovery.start()
439
477
  modules_by_package: dict[str, set[str]] = {}
440
478
  for package, root in build_roots.items():
441
479
  modules_by_package[package] = {
@@ -443,11 +481,13 @@ def inventory_workspace(
443
481
  for path in root.rglob("*.olean")
444
482
  if (module := module_from_artifact(path.relative_to(root))) is not None
445
483
  }
484
+ discovery.advance(package)
446
485
 
447
486
  source_modules: dict[Path, str] = {}
448
487
  claimed: dict[str, tuple[int, Path]] = {}
449
488
  for package, root in source_roots.items():
450
489
  if not root.is_dir():
490
+ discovery.advance(package)
451
491
  continue
452
492
  available = modules_by_package[package]
453
493
  for path in sorted(root.rglob("*.lean")):
@@ -473,6 +513,7 @@ def inventory_workspace(
473
513
  source_modules.pop(previous[1], None)
474
514
  claimed[module] = (stripped, path)
475
515
  source_modules[path] = module
516
+ discovery.advance(package)
476
517
 
477
518
  parsed = parse_import_headers(lean_command, tuple(source_modules))
478
519
  imports = {source_modules[path]: names for path, names in parsed.items()}
@@ -1924,6 +1924,7 @@ def main(argv: list[str] | None = None) -> int:
1924
1924
  renderer = ConsoleRenderer(
1925
1925
  mode="quiet" if args.quiet or getattr(args, "json", False) else None,
1926
1926
  verbose=args.verbose,
1927
+ heartbeat_seconds=2.0,
1927
1928
  )
1928
1929
  try:
1929
1930
  if args.command == "completion":
@@ -1969,9 +1970,9 @@ def main(argv: list[str] | None = None) -> int:
1969
1970
  )
1970
1971
  if declined is not None:
1971
1972
  return declined
1972
- di_publication = OCIDeclarationIndexPublisher(di_repository).publish(
1973
- tuple(di_item.source for di_item in built.shards), lock_id=lock.lock_id
1974
- )
1973
+ di_publication = OCIDeclarationIndexPublisher(
1974
+ di_repository, events=runtime.events
1975
+ ).publish(tuple(di_item.source for di_item in built.shards), lock_id=lock.lock_id)
1975
1976
  if args.sign:
1976
1977
  CosignVerifier(executable=runtime.verification_executable).sign(
1977
1978
  di_repository, di_publication.manifest_digest