Skip to content

Commit 3d9e943

Browse files
committed
refactor(checkers): keep direct runtime migration compatibility
Retain the existing direct provider_runtime constructor as a temporary compatibility seam while making provider_runtime_factory lazy and cached. Reject dual ownership and preserve pre-authorization invariants for both forms.
1 parent 3004c7f commit 3d9e943

2 files changed

Lines changed: 74 additions & 49 deletions

File tree

src/jacobian/checker_operations.py

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,13 @@ class ExactReplayCheckerDeclaration:
9494
are strictly constructed from the producer capability ID so that no
9595
verifier metadata is absent at installation.
9696
97-
A declaration may own a passive factory for its complete clean-process
98-
provider runtime. The factory is evaluated and cached only when installation
99-
asks for ``provider_runtime``; importing a domain declaration therefore does
100-
not identify or hash checker source. Existing built-in families may continue
101-
to use the legacy central runtime registry while they are migrated. A
102-
realized declaration runtime must not carry checker IDs before operator
103-
authorization.
97+
New declarations should own a passive ``provider_runtime_factory``. The
98+
factory is evaluated and cached only when installation reads
99+
``provider_runtime``; importing a domain declaration therefore does not
100+
identify or hash checker source. ``provider_runtime`` remains accepted as a
101+
compatibility seam for already-built declarations while those families are
102+
migrated. A realized declaration runtime must not carry checker IDs before
103+
operator authorization.
104104
"""
105105

106106
capability_id: str
@@ -113,17 +113,16 @@ class ExactReplayCheckerDeclaration:
113113
"operator-authorized Python-FLINT exact replay independent of the "
114114
"SymPy producer"
115115
)
116-
provider_runtime_factory: ProviderRuntimeFactory | None = None
117-
verification_capability_id: str | None = None
118-
verification_title: str | None = None
119-
verification_description: str | None = None
120-
verification_tags: tuple[str, ...] = ()
121-
_provider_runtime: CapabilityProviderRuntime | None = field(
116+
provider_runtime: CapabilityProviderRuntime | None = None
117+
provider_runtime_factory: ProviderRuntimeFactory | None = field(
122118
default=None,
123-
init=False,
124119
repr=False,
125120
compare=False,
126121
)
122+
verification_capability_id: str | None = None
123+
verification_title: str | None = None
124+
verification_description: str | None = None
125+
verification_tags: tuple[str, ...] = ()
127126

128127
def __post_init__(self) -> None:
129128
for field_name, value in {
@@ -138,6 +137,17 @@ def __post_init__(self) -> None:
138137
raise ValueError(
139138
f"exact replay checker declaration {field_name} must not be empty"
140139
)
140+
runtime = object.__getattribute__(self, "provider_runtime")
141+
factory = object.__getattribute__(self, "provider_runtime_factory")
142+
if runtime is not None and factory is not None:
143+
raise ValueError(
144+
"declaration must provide either provider_runtime or "
145+
"provider_runtime_factory, not both"
146+
)
147+
if runtime is not None and runtime.checker_ids:
148+
raise ValueError(
149+
"declaration-owned provider runtime must not pre-authorize checker IDs"
150+
)
141151
derived_id = derive_verification_capability_id(self.capability_id)
142152
explicit_text = (
143153
self.verification_title,
@@ -184,28 +194,27 @@ def __post_init__(self) -> None:
184194
derive_verification_tags(self.capability_id),
185195
)
186196

187-
@property
188-
def provider_runtime(self) -> CapabilityProviderRuntime | None:
189-
"""Realize this declaration's provider identity at installation time."""
190-
191-
factory = self.provider_runtime_factory
197+
def __getattribute__(self, name: str):
198+
if name != "provider_runtime":
199+
return object.__getattribute__(self, name)
200+
runtime = object.__getattribute__(self, "provider_runtime")
201+
if runtime is not None:
202+
return runtime
203+
factory = object.__getattribute__(self, "provider_runtime_factory")
192204
if factory is None:
193205
return None
194-
cached = self._provider_runtime
195-
if cached is not None:
196-
return cached
197-
runtime = factory()
198-
if not isinstance(runtime, CapabilityProviderRuntime):
206+
realized = factory()
207+
if not isinstance(realized, CapabilityProviderRuntime):
199208
raise TypeError(
200209
"declaration-owned provider runtime factory must return "
201210
"CapabilityProviderRuntime"
202211
)
203-
if runtime.checker_ids:
212+
if realized.checker_ids:
204213
raise ValueError(
205214
"declaration-owned provider runtime must not pre-authorize checker IDs"
206215
)
207-
object.__setattr__(self, "_provider_runtime", runtime)
208-
return runtime
216+
object.__setattr__(self, "provider_runtime", realized)
217+
return realized
209218

210219

211220
@dataclass(frozen=True, slots=True)

tests/unit/test_checker_declaration_runtime.py

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ def _runtime():
1919
)
2020

2121

22+
def _declaration(**kwargs):
23+
return ExactReplayCheckerDeclaration(
24+
"test.compute.value",
25+
CanonicalRational,
26+
"check_rational_solution",
27+
"test.value.replay",
28+
entrypoint_module="jacobian_checkers.linear",
29+
**kwargs,
30+
)
31+
32+
2233
def test_exact_replay_declaration_defers_and_caches_provider_runtime() -> None:
2334
calls = 0
2435

@@ -27,14 +38,7 @@ def factory():
2738
calls += 1
2839
return _runtime()
2940

30-
declaration = ExactReplayCheckerDeclaration(
31-
"test.compute.value",
32-
CanonicalRational,
33-
"check_rational_solution",
34-
"test.value.replay",
35-
entrypoint_module="jacobian_checkers.linear",
36-
provider_runtime_factory=factory,
37-
)
41+
declaration = _declaration(provider_runtime_factory=factory)
3842

3943
assert calls == 0
4044
runtime = declaration.provider_runtime
@@ -45,28 +49,40 @@ def factory():
4549
assert calls == 1
4650

4751

52+
def test_exact_replay_declaration_keeps_direct_runtime_compatibility() -> None:
53+
runtime = _runtime()
54+
declaration = _declaration(provider_runtime=runtime)
55+
56+
assert declaration.provider_runtime is runtime
57+
58+
59+
def test_exact_replay_declaration_rejects_two_runtime_owners() -> None:
60+
runtime = _runtime()
61+
62+
with pytest.raises(ValueError, match="either provider_runtime"):
63+
_declaration(
64+
provider_runtime=runtime,
65+
provider_runtime_factory=lambda: runtime,
66+
)
67+
68+
69+
def test_exact_replay_declaration_rejects_preauthorized_direct_runtime() -> None:
70+
runtime = _runtime().model_copy(update={"checker_ids": ("checker:test",)})
71+
72+
with pytest.raises(ValueError, match="must not pre-authorize checker IDs"):
73+
_declaration(provider_runtime=runtime)
74+
75+
4876
def test_exact_replay_declaration_rejects_preauthorized_realized_runtime() -> None:
4977
runtime = _runtime().model_copy(update={"checker_ids": ("checker:test",)})
50-
declaration = ExactReplayCheckerDeclaration(
51-
"test.compute.value",
52-
CanonicalRational,
53-
"check_rational_solution",
54-
"test.value.replay",
55-
entrypoint_module="jacobian_checkers.linear",
56-
provider_runtime_factory=lambda: runtime,
57-
)
78+
declaration = _declaration(provider_runtime_factory=lambda: runtime)
5879

5980
with pytest.raises(ValueError, match="must not pre-authorize checker IDs"):
6081
_ = declaration.provider_runtime
6182

6283

6384
def test_exact_replay_declaration_rejects_wrong_runtime_factory_result() -> None:
64-
declaration = ExactReplayCheckerDeclaration(
65-
"test.compute.value",
66-
CanonicalRational,
67-
"check_rational_solution",
68-
"test.value.replay",
69-
entrypoint_module="jacobian_checkers.linear",
85+
declaration = _declaration(
7086
provider_runtime_factory=lambda: object(), # type: ignore[return-value]
7187
)
7288

0 commit comments

Comments
 (0)