lean-runtime 2.0.7__tar.gz → 2.0.9__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.
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/CHANGELOG.md +12 -0
- {lean_runtime-2.0.7/lean_runtime.egg-info → lean_runtime-2.0.9}/PKG-INFO +1 -1
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/docs/ready-programs.md +6 -2
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/cli.py +25 -1
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/environments.py +14 -2
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/programs.py +45 -8
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/runtime.py +10 -1
- {lean_runtime-2.0.7 → lean_runtime-2.0.9/lean_runtime.egg-info}/PKG-INFO +1 -1
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/pyproject.toml +1 -1
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_cli.py +54 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_prebuilt_policy.py +16 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_programs.py +39 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/LICENSE +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/MANIFEST.in +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/README.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/catalog/environments.toml +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/catalog/locks/core-v4.32.2.lock.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/catalog/locks/mathlib-v4.30.0.lock.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/catalog/locks/mathlib-v4.31.0.lock.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/catalog/locks/mathlib-v4.32.2.lock.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/compatibility/README.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/compatibility/mathlib-4.32.2.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/compatibility/mathlib-4.32.2.toml +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/docs/architecture.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/docs/captures.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/docs/case-study-v1.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/docs/cli.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/docs/development.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/docs/environments.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/docs/getting-started.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/docs/index.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/docs/local-projects.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/docs/portable-copies.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/docs/python-api.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/docs/standalone-files.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/docs/trust-and-limitations.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/docs/v1-precision.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/examples/mathlib.toml +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/__init__.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/__main__.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/_git.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/_paths.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/backends.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/bundles.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/comparison.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/decisions.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/diagnostics.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/__init__.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/analyzer.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/api.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/candidate.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/catalog.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/catalog_build.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/catalog_cli.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/catalog_manifest.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/data/catalog.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/defaults.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/engine.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/errors.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/module_inventory.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/planner.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/policy.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/probe.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/py.typed +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/result.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/schema_resources.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/schemas/catalog-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/schemas/plan-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/schemas/result-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/errors.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/events.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/facade.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/frontmatter.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/health.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/lake.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/lockfiles.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/locking.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/matrix.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/models.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/oci.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/policies.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/profiling.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/projects.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/publisher_verification.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/py.typed +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/references.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/resolver.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/run_cli.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/schema_resources.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/serialization.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/specs.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/store.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/timings.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/toolchains.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/verification.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/wire.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime.egg-info/SOURCES.txt +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime.egg-info/dependency_links.txt +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime.egg-info/entry_points.txt +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime.egg-info/requires.txt +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime.egg-info/top_level.txt +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/mkdocs.yml +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/schemas/cleanup-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/schemas/comparison-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/schemas/execution-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/schemas/inspect-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/schemas/matrix-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/schemas/profile-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/schemas/verify-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/scripts/run_compatibility.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/scripts/run_v1_case_study.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/scripts/smoke_wheel.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/setup.cfg +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/conftest.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/discovery/e2e/test_real_discovery.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/discovery/test_analyzer.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/discovery/test_catalog.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/discovery/test_catalog_build.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/discovery/test_discovery_schema_resources.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/discovery/test_engine.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/discovery/test_planner.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/discovery/test_policy.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_backend.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_bundles.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_diagnostics.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_environment_integration.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_events_health.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_facade.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_frontmatter.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_git.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_interactive.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_lockfiles.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_multifile.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_paths.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_projects.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_references.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_run_cli.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_runtime.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_schema_resources.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_schemas.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_signatures.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_specs.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_store.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_toolchains.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_v1_precision.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/test_verification_inventory.py +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 2.0.9 - 2026-08-10
|
|
6
|
+
|
|
7
|
+
- Allow callers and `build-and-publish --timeout` to raise the per-step limit
|
|
8
|
+
for artifact hydration and environment builds on slower platforms.
|
|
9
|
+
|
|
10
|
+
## 2.0.8 - 2026-08-10
|
|
11
|
+
|
|
12
|
+
- Add immutable, content-addressed provenance to ready-to-run program
|
|
13
|
+
descriptions and accept it through `program-create --provenance-file`.
|
|
14
|
+
- Preserve the identity and import compatibility of existing version 1 program
|
|
15
|
+
descriptions while emitting version 2 descriptions for newly created programs.
|
|
16
|
+
|
|
5
17
|
## 2.0.7
|
|
6
18
|
|
|
7
19
|
- Retry an OCI layer once from a clean partial after size or SHA-256 integrity
|
|
@@ -17,6 +17,7 @@ program = runtime.create_program(
|
|
|
17
17
|
"build/program",
|
|
18
18
|
command=["my-program"],
|
|
19
19
|
source_revision="0123456789abcdef0123456789abcdef01234567",
|
|
20
|
+
provenance={"example.protocol.version": "1.0.0"},
|
|
20
21
|
)
|
|
21
22
|
|
|
22
23
|
with program.spawn_interactive() as session:
|
|
@@ -28,8 +29,11 @@ alive and returns one decoded response per request. This is the fast path for
|
|
|
28
29
|
large batches: prepare and verify the checker once, then stream compact inputs
|
|
29
30
|
instead of elaborating a new Lean source file for every item.
|
|
30
31
|
|
|
31
|
-
`create_program` records the program's files
|
|
32
|
-
|
|
32
|
+
`create_program` records the program's files, computer compatibility, and
|
|
33
|
+
optional flat string provenance. All three contribute to the program ID.
|
|
34
|
+
Opening it later with `runtime.program(program.id)` verifies that none has
|
|
35
|
+
changed. The CLI accepts the same metadata as a JSON object through
|
|
36
|
+
`program-create --provenance-file`.
|
|
33
37
|
|
|
34
38
|
## Move it or share it
|
|
35
39
|
|
|
@@ -142,6 +142,12 @@ def parser() -> argparse.ArgumentParser:
|
|
|
142
142
|
push.add_argument("--publish-to", required=True)
|
|
143
143
|
push.add_argument("--tag", action="append", default=[])
|
|
144
144
|
push.add_argument("--name")
|
|
145
|
+
push.add_argument(
|
|
146
|
+
"--timeout",
|
|
147
|
+
type=float,
|
|
148
|
+
default=1800,
|
|
149
|
+
help="maximum seconds for each artifact hydration and the environment build",
|
|
150
|
+
)
|
|
145
151
|
push.add_argument(
|
|
146
152
|
"--platform-only",
|
|
147
153
|
action="store_true",
|
|
@@ -181,6 +187,11 @@ def parser() -> argparse.ArgumentParser:
|
|
|
181
187
|
program_create.add_argument("--exact-environment-id")
|
|
182
188
|
program_create.add_argument("--toolchain", default="unknown")
|
|
183
189
|
program_create.add_argument("--capability-id")
|
|
190
|
+
program_create.add_argument(
|
|
191
|
+
"--provenance-file",
|
|
192
|
+
type=Path,
|
|
193
|
+
help="JSON object of content-addressed program provenance",
|
|
194
|
+
)
|
|
184
195
|
|
|
185
196
|
program_export = commands.add_parser(
|
|
186
197
|
"program-save-copy", help="save a portable copy of a ready-to-run program"
|
|
@@ -363,7 +374,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
363
374
|
_json(environment.inspect().to_dict())
|
|
364
375
|
return 0
|
|
365
376
|
if args.command == "build-and-publish":
|
|
366
|
-
environment = runtime.open_exact(
|
|
377
|
+
environment = runtime.open_exact(
|
|
378
|
+
EnvironmentLock.load(args.lock),
|
|
379
|
+
name=args.name,
|
|
380
|
+
build_timeout=args.timeout,
|
|
381
|
+
)
|
|
367
382
|
_json(
|
|
368
383
|
runtime.publish_environment(
|
|
369
384
|
environment.id,
|
|
@@ -398,6 +413,14 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
398
413
|
_json(environment.inspect().to_dict())
|
|
399
414
|
return 0
|
|
400
415
|
if args.command == "program-create":
|
|
416
|
+
provenance = None
|
|
417
|
+
if args.provenance_file is not None:
|
|
418
|
+
value = json.loads(args.provenance_file.read_text(encoding="utf-8"))
|
|
419
|
+
if not isinstance(value, dict) or not all(
|
|
420
|
+
isinstance(key, str) and isinstance(item, str) for key, item in value.items()
|
|
421
|
+
):
|
|
422
|
+
raise ValueError("program provenance file must contain a JSON string object")
|
|
423
|
+
provenance = value
|
|
401
424
|
program = runtime.create_program(
|
|
402
425
|
args.payload,
|
|
403
426
|
command=args.program_command,
|
|
@@ -406,6 +429,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
406
429
|
exact_environment_id=args.exact_environment_id,
|
|
407
430
|
toolchain=args.toolchain,
|
|
408
431
|
capability_id=args.capability_id,
|
|
432
|
+
provenance=provenance,
|
|
409
433
|
)
|
|
410
434
|
_json(
|
|
411
435
|
{
|
|
@@ -1005,6 +1005,7 @@ class EnvironmentManager:
|
|
|
1005
1005
|
*,
|
|
1006
1006
|
name: str | None = None,
|
|
1007
1007
|
build_profile: str = "release",
|
|
1008
|
+
build_timeout: float = 1800,
|
|
1008
1009
|
cancel: threading.Event | None = None,
|
|
1009
1010
|
) -> Environment:
|
|
1010
1011
|
self.store.publish_lock(lock)
|
|
@@ -1018,7 +1019,14 @@ class EnvironmentManager:
|
|
|
1018
1019
|
with FileLock(self.store.lock_dir / f"{environment_id}.lock", timeout=1800, cancel=cancel):
|
|
1019
1020
|
if not destination.is_dir():
|
|
1020
1021
|
self._ensure_sources(lock)
|
|
1021
|
-
self._materialize(
|
|
1022
|
+
self._materialize(
|
|
1023
|
+
lock,
|
|
1024
|
+
environment_id,
|
|
1025
|
+
destination,
|
|
1026
|
+
build_profile,
|
|
1027
|
+
build_timeout=build_timeout,
|
|
1028
|
+
cancel=cancel,
|
|
1029
|
+
)
|
|
1022
1030
|
else:
|
|
1023
1031
|
self.events.emit(
|
|
1024
1032
|
"environment.cache_hit",
|
|
@@ -1061,6 +1069,7 @@ class EnvironmentManager:
|
|
|
1061
1069
|
destination: Path,
|
|
1062
1070
|
build_profile: str,
|
|
1063
1071
|
*,
|
|
1072
|
+
build_timeout: float,
|
|
1064
1073
|
cancel: threading.Event | None,
|
|
1065
1074
|
) -> None:
|
|
1066
1075
|
stage = self.store.environments / f".staging-{os.getpid()}-{uuid.uuid4().hex}"
|
|
@@ -1086,7 +1095,10 @@ class EnvironmentManager:
|
|
|
1086
1095
|
phase="acquisition",
|
|
1087
1096
|
)
|
|
1088
1097
|
clone_tree(source, packages_dir / package.name)
|
|
1089
|
-
build_policy = ExecutionPolicy(
|
|
1098
|
+
build_policy = ExecutionPolicy(
|
|
1099
|
+
timeout_seconds=build_timeout,
|
|
1100
|
+
max_output_bytes=10_000_000,
|
|
1101
|
+
)
|
|
1090
1102
|
hydration: list[dict[str, Any]] = []
|
|
1091
1103
|
for package in lock.packages:
|
|
1092
1104
|
if not package.artifact_command:
|
|
@@ -14,9 +14,10 @@ import tempfile
|
|
|
14
14
|
import uuid
|
|
15
15
|
from collections.abc import Mapping, Sequence
|
|
16
16
|
from contextlib import suppress
|
|
17
|
-
from dataclasses import asdict, dataclass
|
|
17
|
+
from dataclasses import asdict, dataclass, field
|
|
18
18
|
from datetime import datetime, timezone
|
|
19
19
|
from pathlib import Path, PurePosixPath
|
|
20
|
+
from types import MappingProxyType
|
|
20
21
|
from typing import Any, cast
|
|
21
22
|
|
|
22
23
|
from ._paths import remove_tree
|
|
@@ -47,8 +48,10 @@ from .policies import ExecutionPolicy
|
|
|
47
48
|
from .serialization import canonical_json_bytes, sha256_id, write_json_atomic
|
|
48
49
|
from .store import EnvironmentStore, clone_tree, platform_compatibility, platform_record
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
LEGACY_PROGRAM_SCHEMA = "lean-runtime-execution-program/1"
|
|
52
|
+
PROGRAM_SCHEMA = "lean-runtime-execution-program/2"
|
|
53
|
+
LEGACY_PROGRAM_CONFIG_MEDIA_TYPE = "application/vnd.lean-runtime.program.config.v1+json"
|
|
54
|
+
PROGRAM_CONFIG_MEDIA_TYPE = "application/vnd.lean-runtime.program.config.v2+json"
|
|
52
55
|
PROGRAM_LAYER_MEDIA_TYPE = "application/vnd.lean-runtime.program.layer.v1.tar+gzip"
|
|
53
56
|
_PROGRAM_ID = re.compile(r"program_[0-9a-f]{64}")
|
|
54
57
|
|
|
@@ -106,11 +109,14 @@ class ProgramDescription:
|
|
|
106
109
|
exact_environment_id: str | None = None
|
|
107
110
|
toolchain: str = "unknown"
|
|
108
111
|
capability_id: str | None = None
|
|
112
|
+
provenance: Mapping[str, str] = field(default_factory=dict)
|
|
109
113
|
schema: str = PROGRAM_SCHEMA
|
|
110
114
|
|
|
111
115
|
def __post_init__(self) -> None:
|
|
112
|
-
if self.schema
|
|
116
|
+
if self.schema not in {LEGACY_PROGRAM_SCHEMA, PROGRAM_SCHEMA}:
|
|
113
117
|
raise EnvironmentError(f"unsupported program schema: {self.schema!r}")
|
|
118
|
+
if self.schema == LEGACY_PROGRAM_SCHEMA and self.provenance:
|
|
119
|
+
raise EnvironmentError("legacy program descriptions cannot contain provenance")
|
|
114
120
|
if not self.command or any(not isinstance(part, str) or not part for part in self.command):
|
|
115
121
|
raise EnvironmentError("program command must contain non-empty strings")
|
|
116
122
|
_relative_executable(self.command[0])
|
|
@@ -118,13 +124,26 @@ class ProgramDescription:
|
|
|
118
124
|
raise EnvironmentError("program command executable is absent from its file inventory")
|
|
119
125
|
if not re.fullmatch(r"[0-9a-f]{40,64}", self.source_revision):
|
|
120
126
|
raise EnvironmentError("program source revision must be an exact Git commit")
|
|
127
|
+
if not all(
|
|
128
|
+
isinstance(key, str)
|
|
129
|
+
and bool(re.fullmatch(r"[a-z0-9][a-z0-9._-]*", key))
|
|
130
|
+
and isinstance(value, str)
|
|
131
|
+
and bool(value)
|
|
132
|
+
for key, value in self.provenance.items()
|
|
133
|
+
):
|
|
134
|
+
raise EnvironmentError(
|
|
135
|
+
"program provenance must contain non-empty string values under canonical keys"
|
|
136
|
+
)
|
|
137
|
+
object.__setattr__(
|
|
138
|
+
self, "provenance", MappingProxyType(dict(sorted(self.provenance.items())))
|
|
139
|
+
)
|
|
121
140
|
|
|
122
141
|
@property
|
|
123
142
|
def program_id(self) -> str:
|
|
124
143
|
return sha256_id("program", self.to_dict(include_id=False))
|
|
125
144
|
|
|
126
145
|
def to_dict(self, *, include_id: bool = True) -> dict[str, Any]:
|
|
127
|
-
value = {
|
|
146
|
+
value: dict[str, Any] = {
|
|
128
147
|
"schema": self.schema,
|
|
129
148
|
"command": list(self.command),
|
|
130
149
|
"files": dict(sorted(self.files.items())),
|
|
@@ -135,6 +154,8 @@ class ProgramDescription:
|
|
|
135
154
|
"toolchain": self.toolchain,
|
|
136
155
|
"capability_id": self.capability_id,
|
|
137
156
|
}
|
|
157
|
+
if self.schema == PROGRAM_SCHEMA:
|
|
158
|
+
value["provenance"] = dict(sorted(self.provenance.items()))
|
|
138
159
|
return {"program_id": self.program_id, **value} if include_id else value
|
|
139
160
|
|
|
140
161
|
@classmethod
|
|
@@ -150,6 +171,10 @@ class ProgramDescription:
|
|
|
150
171
|
raise EnvironmentError("program manifest file inventory is invalid")
|
|
151
172
|
if not isinstance(compatibility, dict):
|
|
152
173
|
raise EnvironmentError("program platform compatibility is invalid")
|
|
174
|
+
schema = str(value.get("schema", ""))
|
|
175
|
+
provenance = value.get("provenance", {})
|
|
176
|
+
if not isinstance(provenance, dict):
|
|
177
|
+
raise EnvironmentError("program provenance is invalid")
|
|
153
178
|
manifest = cls(
|
|
154
179
|
command=tuple(command),
|
|
155
180
|
files={str(name): dict(record) for name, record in files.items()},
|
|
@@ -169,7 +194,8 @@ class ProgramDescription:
|
|
|
169
194
|
capability_id=(
|
|
170
195
|
str(value["capability_id"]) if value.get("capability_id") is not None else None
|
|
171
196
|
),
|
|
172
|
-
|
|
197
|
+
provenance={str(key): item for key, item in provenance.items()},
|
|
198
|
+
schema=schema,
|
|
173
199
|
)
|
|
174
200
|
recorded = value.get("program_id")
|
|
175
201
|
if recorded is not None and recorded != manifest.program_id:
|
|
@@ -323,6 +349,7 @@ class ProgramManager:
|
|
|
323
349
|
exact_environment_id: str | None = None,
|
|
324
350
|
toolchain: str = "unknown",
|
|
325
351
|
capability_id: str | None = None,
|
|
352
|
+
provenance: Mapping[str, str] | None = None,
|
|
326
353
|
) -> ReadyProgram:
|
|
327
354
|
payload = payload.expanduser().resolve()
|
|
328
355
|
if not payload.is_dir():
|
|
@@ -336,6 +363,7 @@ class ProgramManager:
|
|
|
336
363
|
exact_environment_id=exact_environment_id,
|
|
337
364
|
toolchain=toolchain,
|
|
338
365
|
capability_id=capability_id,
|
|
366
|
+
provenance=dict(provenance or {}),
|
|
339
367
|
)
|
|
340
368
|
destination = self.store.programs / manifest.program_id
|
|
341
369
|
with FileLock(self.store.lock_dir / f"{manifest.program_id}.lock", timeout=1800):
|
|
@@ -392,7 +420,12 @@ class ProgramManager:
|
|
|
392
420
|
)
|
|
393
421
|
config_path = staging / "config.json"
|
|
394
422
|
config_path.write_bytes(canonical_json_bytes(program.description.to_dict()))
|
|
395
|
-
|
|
423
|
+
config_media_type = (
|
|
424
|
+
LEGACY_PROGRAM_CONFIG_MEDIA_TYPE
|
|
425
|
+
if program.description.schema == LEGACY_PROGRAM_SCHEMA
|
|
426
|
+
else PROGRAM_CONFIG_MEDIA_TYPE
|
|
427
|
+
)
|
|
428
|
+
config = _blob_descriptor_path(config_path, config_media_type)
|
|
396
429
|
manifest_path = staging / "manifest.json"
|
|
397
430
|
manifest_path.write_bytes(
|
|
398
431
|
canonical_json_bytes(
|
|
@@ -507,7 +540,11 @@ class ProgramManager:
|
|
|
507
540
|
or len(layers) != 1
|
|
508
541
|
):
|
|
509
542
|
raise EnvironmentError("program OCI manifest is incomplete")
|
|
510
|
-
|
|
543
|
+
if config_descriptor.get("mediaType") not in {
|
|
544
|
+
LEGACY_PROGRAM_CONFIG_MEDIA_TYPE,
|
|
545
|
+
PROGRAM_CONFIG_MEDIA_TYPE,
|
|
546
|
+
}:
|
|
547
|
+
raise EnvironmentError("program config has an unsupported media type")
|
|
511
548
|
_require_media_type(layers[0], PROGRAM_LAYER_MEDIA_TYPE, "program layer")
|
|
512
549
|
config_path = _descriptor_blob_path(entries, config_descriptor, "program config")
|
|
513
550
|
manifest = ProgramDescription.from_dict(
|
|
@@ -170,6 +170,7 @@ class Runtime:
|
|
|
170
170
|
*,
|
|
171
171
|
name: str | None = None,
|
|
172
172
|
build_profile: str = "release",
|
|
173
|
+
build_timeout: float = 1800,
|
|
173
174
|
cancel: threading.Event | None = None,
|
|
174
175
|
) -> Environment:
|
|
175
176
|
environment_id = environment_identity(lock, build_profile)
|
|
@@ -203,7 +204,13 @@ class Runtime:
|
|
|
203
204
|
"no compatible downloadable environment was found; availability=required "
|
|
204
205
|
"does not permit a source build." + nearest
|
|
205
206
|
)
|
|
206
|
-
return self.environments.ensure(
|
|
207
|
+
return self.environments.ensure(
|
|
208
|
+
lock,
|
|
209
|
+
name=name,
|
|
210
|
+
build_profile=build_profile,
|
|
211
|
+
build_timeout=build_timeout,
|
|
212
|
+
cancel=cancel,
|
|
213
|
+
)
|
|
207
214
|
|
|
208
215
|
def environment(self, identifier: str) -> Environment:
|
|
209
216
|
"""Open a published environment without resolution or network access."""
|
|
@@ -219,6 +226,7 @@ class Runtime:
|
|
|
219
226
|
exact_environment_id: str | None = None,
|
|
220
227
|
toolchain: str = "unknown",
|
|
221
228
|
capability_id: str | None = None,
|
|
229
|
+
provenance: Mapping[str, str] | None = None,
|
|
222
230
|
) -> ReadyProgram:
|
|
223
231
|
"""Create a verified ready-to-run program from a payload directory."""
|
|
224
232
|
return self.programs.create(
|
|
@@ -229,6 +237,7 @@ class Runtime:
|
|
|
229
237
|
exact_environment_id=exact_environment_id,
|
|
230
238
|
toolchain=toolchain,
|
|
231
239
|
capability_id=capability_id,
|
|
240
|
+
provenance=provenance,
|
|
232
241
|
)
|
|
233
242
|
|
|
234
243
|
def program(self, program_id: str) -> ReadyProgram:
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from types import SimpleNamespace
|
|
5
6
|
|
|
6
7
|
from lean_runtime.bundles import PortableCopyInfo
|
|
7
8
|
from lean_runtime.cli import _print_operation_failure, main, parser
|
|
@@ -35,6 +36,21 @@ def test_removed_v1_commands_are_absent() -> None:
|
|
|
35
36
|
assert commands.isdisjoint(removed)
|
|
36
37
|
|
|
37
38
|
|
|
39
|
+
def test_build_and_publish_accepts_environment_build_timeout() -> None:
|
|
40
|
+
arguments = parser().parse_args(
|
|
41
|
+
[
|
|
42
|
+
"build-and-publish",
|
|
43
|
+
"environment.lock.json",
|
|
44
|
+
"--publish-to",
|
|
45
|
+
"oci://cache",
|
|
46
|
+
"--timeout",
|
|
47
|
+
"3600",
|
|
48
|
+
]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
assert arguments.timeout == 3600
|
|
52
|
+
|
|
53
|
+
|
|
38
54
|
def test_verbose_materialization_failure_includes_tool_output(capsys) -> None:
|
|
39
55
|
failure = MaterializationError(
|
|
40
56
|
"environment build failed",
|
|
@@ -172,6 +188,44 @@ def test_save_copy_cli_reports_copy_identity(monkeypatch, tmp_path: Path, capsys
|
|
|
172
188
|
assert json.loads(capsys.readouterr().out)["copy_id"] == info.copy_id
|
|
173
189
|
|
|
174
190
|
|
|
191
|
+
def test_program_create_cli_loads_content_addressed_provenance(
|
|
192
|
+
monkeypatch, tmp_path: Path, capsys
|
|
193
|
+
) -> None:
|
|
194
|
+
payload = tmp_path / "payload"
|
|
195
|
+
payload.mkdir()
|
|
196
|
+
profile = tmp_path / "profile.json"
|
|
197
|
+
profile.write_text(json.dumps({"example.protocol.version": "1.0.0"}))
|
|
198
|
+
observed = {}
|
|
199
|
+
|
|
200
|
+
def create_program(_runtime, selected_payload, **options):
|
|
201
|
+
observed.update(payload=selected_payload, **options)
|
|
202
|
+
return SimpleNamespace(
|
|
203
|
+
id="program_" + "a" * 64,
|
|
204
|
+
description=SimpleNamespace(to_dict=lambda: {"schema": "test"}),
|
|
205
|
+
root=payload,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
monkeypatch.setattr("lean_runtime.cli.Runtime.create_program", create_program)
|
|
209
|
+
assert (
|
|
210
|
+
main(
|
|
211
|
+
[
|
|
212
|
+
"--quiet",
|
|
213
|
+
"program-create",
|
|
214
|
+
str(payload),
|
|
215
|
+
"--command",
|
|
216
|
+
"bin/checker",
|
|
217
|
+
"--source-revision",
|
|
218
|
+
"b" * 40,
|
|
219
|
+
"--provenance-file",
|
|
220
|
+
str(profile),
|
|
221
|
+
]
|
|
222
|
+
)
|
|
223
|
+
== 0
|
|
224
|
+
)
|
|
225
|
+
assert observed["provenance"] == {"example.protocol.version": "1.0.0"}
|
|
226
|
+
assert json.loads(capsys.readouterr().out)["program_id"].startswith("program_")
|
|
227
|
+
|
|
228
|
+
|
|
175
229
|
def test_finalize_publication_reads_computer_records(monkeypatch, tmp_path: Path, capsys) -> None:
|
|
176
230
|
result = tmp_path / "computer.json"
|
|
177
231
|
record = {"digest": "sha256:" + "a" * 64, "size": 42}
|
|
@@ -62,6 +62,22 @@ def test_auto_falls_back_only_when_prebuilt_is_unavailable(
|
|
|
62
62
|
assert runtime.open_exact(_lock()) is sentinel
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
def test_open_exact_forwards_environment_build_timeout(
|
|
66
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
67
|
+
) -> None:
|
|
68
|
+
runtime = Runtime(home=tmp_path, availability="local", libraries=[])
|
|
69
|
+
captured: dict[str, object] = {}
|
|
70
|
+
|
|
71
|
+
def ensure(*_args: object, **kwargs: object) -> object:
|
|
72
|
+
captured.update(kwargs)
|
|
73
|
+
return object()
|
|
74
|
+
|
|
75
|
+
monkeypatch.setattr(runtime.environments, "ensure", ensure)
|
|
76
|
+
runtime.open_exact(_lock(), build_timeout=3600)
|
|
77
|
+
|
|
78
|
+
assert captured["build_timeout"] == 3600
|
|
79
|
+
|
|
80
|
+
|
|
65
81
|
def test_auto_does_not_hide_prebuilt_integrity_failures(
|
|
66
82
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
67
83
|
) -> None:
|
|
@@ -7,6 +7,7 @@ from pathlib import Path
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
9
|
from lean_runtime import EnvironmentError, ExecutionPolicy, Runtime
|
|
10
|
+
from lean_runtime.programs import LEGACY_PROGRAM_SCHEMA, ProgramDescription
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def _revision() -> str:
|
|
@@ -30,6 +31,10 @@ def test_program_identity_payload_validation_and_interactive_execution(tmp_path:
|
|
|
30
31
|
source_revision=_revision(),
|
|
31
32
|
toolchain="leanprover/lean4:v4.32.2",
|
|
32
33
|
capability_id="sha256:" + hashlib.sha256(b"capabilities").hexdigest(),
|
|
34
|
+
provenance={
|
|
35
|
+
"lean.toolchain": "leanprover/lean4:v4.32.2",
|
|
36
|
+
"leancert.core.revision": "b" * 40,
|
|
37
|
+
},
|
|
33
38
|
)
|
|
34
39
|
|
|
35
40
|
assert program.id.startswith("program_")
|
|
@@ -40,6 +45,40 @@ def test_program_identity_payload_validation_and_interactive_execution(tmp_path:
|
|
|
40
45
|
|
|
41
46
|
reopened = runtime.program(program.id)
|
|
42
47
|
assert reopened.description.source_revision == _revision()
|
|
48
|
+
assert reopened.description.provenance["leancert.core.revision"] == "b" * 40
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_program_provenance_is_content_addressed(tmp_path: Path) -> None:
|
|
52
|
+
runtime = Runtime(home=tmp_path / "runtime")
|
|
53
|
+
payload = _payload(tmp_path)
|
|
54
|
+
first = runtime.create_program(
|
|
55
|
+
payload,
|
|
56
|
+
command=("echo-bridge",),
|
|
57
|
+
source_revision=_revision(),
|
|
58
|
+
provenance={"leancert.core.revision": "b" * 40},
|
|
59
|
+
)
|
|
60
|
+
second = runtime.create_program(
|
|
61
|
+
payload,
|
|
62
|
+
command=("echo-bridge",),
|
|
63
|
+
source_revision=_revision(),
|
|
64
|
+
provenance={"leancert.core.revision": "c" * 40},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
assert first.id != second.id
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_legacy_program_identity_remains_stable() -> None:
|
|
71
|
+
description = ProgramDescription(
|
|
72
|
+
command=("echo-bridge",),
|
|
73
|
+
files={"echo-bridge": {"kind": "file", "sha256": "sha256:" + "0" * 64}},
|
|
74
|
+
computer_compatibility={"schema": "computer/1"},
|
|
75
|
+
source_revision=_revision(),
|
|
76
|
+
schema=LEGACY_PROGRAM_SCHEMA,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
restored = ProgramDescription.from_dict(description.to_dict())
|
|
80
|
+
assert restored.program_id == description.program_id
|
|
81
|
+
assert "provenance" not in restored.to_dict()
|
|
43
82
|
|
|
44
83
|
|
|
45
84
|
def test_program_rejects_payload_tampering(tmp_path: Path) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/schemas/catalog-v1.schema.json
RENAMED
|
File without changes
|
{lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/schemas/plan-v1.schema.json
RENAMED
|
File without changes
|
{lean_runtime-2.0.7 → lean_runtime-2.0.9}/lean_runtime/discovery/schemas/result-v1.schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lean_runtime-2.0.7 → lean_runtime-2.0.9}/tests/discovery/test_discovery_schema_resources.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|