lean-runtime 2.0.7__tar.gz → 2.0.8__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.8}/CHANGELOG.md +7 -0
- {lean_runtime-2.0.7/lean_runtime.egg-info → lean_runtime-2.0.8}/PKG-INFO +1 -1
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/docs/ready-programs.md +6 -2
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/cli.py +14 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/programs.py +45 -8
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/runtime.py +2 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8/lean_runtime.egg-info}/PKG-INFO +1 -1
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/pyproject.toml +1 -1
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_cli.py +39 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_programs.py +39 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/LICENSE +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/MANIFEST.in +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/README.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/catalog/environments.toml +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/catalog/locks/core-v4.32.2.lock.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/catalog/locks/mathlib-v4.30.0.lock.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/catalog/locks/mathlib-v4.31.0.lock.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/catalog/locks/mathlib-v4.32.2.lock.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/compatibility/README.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/compatibility/mathlib-4.32.2.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/compatibility/mathlib-4.32.2.toml +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/docs/architecture.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/docs/captures.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/docs/case-study-v1.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/docs/cli.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/docs/development.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/docs/environments.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/docs/getting-started.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/docs/index.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/docs/local-projects.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/docs/portable-copies.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/docs/python-api.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/docs/standalone-files.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/docs/trust-and-limitations.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/docs/v1-precision.md +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/examples/mathlib.toml +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/__init__.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/__main__.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/_git.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/_paths.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/backends.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/bundles.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/comparison.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/decisions.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/diagnostics.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/__init__.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/analyzer.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/api.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/candidate.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/catalog.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/catalog_build.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/catalog_cli.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/catalog_manifest.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/data/catalog.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/defaults.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/engine.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/errors.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/module_inventory.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/planner.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/policy.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/probe.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/py.typed +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/result.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/schema_resources.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/schemas/catalog-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/schemas/plan-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/schemas/result-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/environments.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/errors.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/events.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/facade.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/frontmatter.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/health.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/lake.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/lockfiles.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/locking.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/matrix.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/models.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/oci.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/policies.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/profiling.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/projects.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/publisher_verification.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/py.typed +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/references.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/resolver.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/run_cli.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/schema_resources.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/serialization.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/specs.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/store.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/timings.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/toolchains.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/verification.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/wire.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime.egg-info/SOURCES.txt +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime.egg-info/dependency_links.txt +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime.egg-info/entry_points.txt +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime.egg-info/requires.txt +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime.egg-info/top_level.txt +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/mkdocs.yml +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/schemas/cleanup-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/schemas/comparison-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/schemas/execution-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/schemas/inspect-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/schemas/matrix-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/schemas/profile-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/schemas/verify-v1.schema.json +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/scripts/run_compatibility.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/scripts/run_v1_case_study.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/scripts/smoke_wheel.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/setup.cfg +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/conftest.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/discovery/e2e/test_real_discovery.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/discovery/test_analyzer.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/discovery/test_catalog.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/discovery/test_catalog_build.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/discovery/test_discovery_schema_resources.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/discovery/test_engine.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/discovery/test_planner.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/discovery/test_policy.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_backend.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_bundles.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_diagnostics.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_environment_integration.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_events_health.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_facade.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_frontmatter.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_git.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_interactive.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_lockfiles.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_multifile.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_paths.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_prebuilt_policy.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_projects.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_references.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_run_cli.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_runtime.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_schema_resources.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_schemas.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_signatures.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_specs.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_store.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_toolchains.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_v1_precision.py +0 -0
- {lean_runtime-2.0.7 → lean_runtime-2.0.8}/tests/test_verification_inventory.py +0 -0
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 2.0.8 - 2026-08-10
|
|
6
|
+
|
|
7
|
+
- Add immutable, content-addressed provenance to ready-to-run program
|
|
8
|
+
descriptions and accept it through `program-create --provenance-file`.
|
|
9
|
+
- Preserve the identity and import compatibility of existing version 1 program
|
|
10
|
+
descriptions while emitting version 2 descriptions for newly created programs.
|
|
11
|
+
|
|
5
12
|
## 2.0.7
|
|
6
13
|
|
|
7
14
|
- 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
|
|
|
@@ -181,6 +181,11 @@ def parser() -> argparse.ArgumentParser:
|
|
|
181
181
|
program_create.add_argument("--exact-environment-id")
|
|
182
182
|
program_create.add_argument("--toolchain", default="unknown")
|
|
183
183
|
program_create.add_argument("--capability-id")
|
|
184
|
+
program_create.add_argument(
|
|
185
|
+
"--provenance-file",
|
|
186
|
+
type=Path,
|
|
187
|
+
help="JSON object of content-addressed program provenance",
|
|
188
|
+
)
|
|
184
189
|
|
|
185
190
|
program_export = commands.add_parser(
|
|
186
191
|
"program-save-copy", help="save a portable copy of a ready-to-run program"
|
|
@@ -398,6 +403,14 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
398
403
|
_json(environment.inspect().to_dict())
|
|
399
404
|
return 0
|
|
400
405
|
if args.command == "program-create":
|
|
406
|
+
provenance = None
|
|
407
|
+
if args.provenance_file is not None:
|
|
408
|
+
value = json.loads(args.provenance_file.read_text(encoding="utf-8"))
|
|
409
|
+
if not isinstance(value, dict) or not all(
|
|
410
|
+
isinstance(key, str) and isinstance(item, str) for key, item in value.items()
|
|
411
|
+
):
|
|
412
|
+
raise ValueError("program provenance file must contain a JSON string object")
|
|
413
|
+
provenance = value
|
|
401
414
|
program = runtime.create_program(
|
|
402
415
|
args.payload,
|
|
403
416
|
command=args.program_command,
|
|
@@ -406,6 +419,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
406
419
|
exact_environment_id=args.exact_environment_id,
|
|
407
420
|
toolchain=args.toolchain,
|
|
408
421
|
capability_id=args.capability_id,
|
|
422
|
+
provenance=provenance,
|
|
409
423
|
)
|
|
410
424
|
_json(
|
|
411
425
|
{
|
|
@@ -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(
|
|
@@ -219,6 +219,7 @@ class Runtime:
|
|
|
219
219
|
exact_environment_id: str | None = None,
|
|
220
220
|
toolchain: str = "unknown",
|
|
221
221
|
capability_id: str | None = None,
|
|
222
|
+
provenance: Mapping[str, str] | None = None,
|
|
222
223
|
) -> ReadyProgram:
|
|
223
224
|
"""Create a verified ready-to-run program from a payload directory."""
|
|
224
225
|
return self.programs.create(
|
|
@@ -229,6 +230,7 @@ class Runtime:
|
|
|
229
230
|
exact_environment_id=exact_environment_id,
|
|
230
231
|
toolchain=toolchain,
|
|
231
232
|
capability_id=capability_id,
|
|
233
|
+
provenance=provenance,
|
|
232
234
|
)
|
|
233
235
|
|
|
234
236
|
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
|
|
@@ -172,6 +173,44 @@ def test_save_copy_cli_reports_copy_identity(monkeypatch, tmp_path: Path, capsys
|
|
|
172
173
|
assert json.loads(capsys.readouterr().out)["copy_id"] == info.copy_id
|
|
173
174
|
|
|
174
175
|
|
|
176
|
+
def test_program_create_cli_loads_content_addressed_provenance(
|
|
177
|
+
monkeypatch, tmp_path: Path, capsys
|
|
178
|
+
) -> None:
|
|
179
|
+
payload = tmp_path / "payload"
|
|
180
|
+
payload.mkdir()
|
|
181
|
+
profile = tmp_path / "profile.json"
|
|
182
|
+
profile.write_text(json.dumps({"example.protocol.version": "1.0.0"}))
|
|
183
|
+
observed = {}
|
|
184
|
+
|
|
185
|
+
def create_program(_runtime, selected_payload, **options):
|
|
186
|
+
observed.update(payload=selected_payload, **options)
|
|
187
|
+
return SimpleNamespace(
|
|
188
|
+
id="program_" + "a" * 64,
|
|
189
|
+
description=SimpleNamespace(to_dict=lambda: {"schema": "test"}),
|
|
190
|
+
root=payload,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
monkeypatch.setattr("lean_runtime.cli.Runtime.create_program", create_program)
|
|
194
|
+
assert (
|
|
195
|
+
main(
|
|
196
|
+
[
|
|
197
|
+
"--quiet",
|
|
198
|
+
"program-create",
|
|
199
|
+
str(payload),
|
|
200
|
+
"--command",
|
|
201
|
+
"bin/checker",
|
|
202
|
+
"--source-revision",
|
|
203
|
+
"b" * 40,
|
|
204
|
+
"--provenance-file",
|
|
205
|
+
str(profile),
|
|
206
|
+
]
|
|
207
|
+
)
|
|
208
|
+
== 0
|
|
209
|
+
)
|
|
210
|
+
assert observed["provenance"] == {"example.protocol.version": "1.0.0"}
|
|
211
|
+
assert json.loads(capsys.readouterr().out)["program_id"].startswith("program_")
|
|
212
|
+
|
|
213
|
+
|
|
175
214
|
def test_finalize_publication_reads_computer_records(monkeypatch, tmp_path: Path, capsys) -> None:
|
|
176
215
|
result = tmp_path / "computer.json"
|
|
177
216
|
record = {"digest": "sha256:" + "a" * 64, "size": 42}
|
|
@@ -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.8}/lean_runtime/discovery/schemas/catalog-v1.schema.json
RENAMED
|
File without changes
|
{lean_runtime-2.0.7 → lean_runtime-2.0.8}/lean_runtime/discovery/schemas/plan-v1.schema.json
RENAMED
|
File without changes
|
{lean_runtime-2.0.7 → lean_runtime-2.0.8}/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
|
|
File without changes
|
{lean_runtime-2.0.7 → lean_runtime-2.0.8}/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
|
|
File without changes
|