graphite-code 0.3.0__py3-none-any.whl
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.
- graphite/__init__.py +41 -0
- graphite/__main__.py +7 -0
- graphite/_cleanup_worker.py +525 -0
- graphite/activation.py +164 -0
- graphite/agent_hooks.py +577 -0
- graphite/agent_settings.py +226 -0
- graphite/analyze.py +146 -0
- graphite/answer_contract.py +420 -0
- graphite/bootstrap.py +210 -0
- graphite/buildlock.py +99 -0
- graphite/cache.py +131 -0
- graphite/channel.py +1325 -0
- graphite/cli.py +3053 -0
- graphite/cluster.py +111 -0
- graphite/config.py +209 -0
- graphite/context.py +355 -0
- graphite/daemon.py +745 -0
- graphite/daemon_health.py +733 -0
- graphite/debt.py +118 -0
- graphite/dependency_install.py +1597 -0
- graphite/detach.py +33 -0
- graphite/doctor.py +678 -0
- graphite/doctor_probes.py +2100 -0
- graphite/engine_identity.py +238 -0
- graphite/export/__init__.py +6 -0
- graphite/export/html.py +244 -0
- graphite/export/json.py +39 -0
- graphite/export/md.py +68 -0
- graphite/extract/__init__.py +4 -0
- graphite/extract/ast.py +1964 -0
- graphite/freshness.py +127 -0
- graphite/git.py +406 -0
- graphite/graph.py +117 -0
- graphite/graph_io.py +188 -0
- graphite/health.py +147 -0
- graphite/hook_entry.py +68 -0
- graphite/hookinstall.py +224 -0
- graphite/hookshim.py +86 -0
- graphite/incident_ledger.py +247 -0
- graphite/ingest.py +279 -0
- graphite/init.py +791 -0
- graphite/io.py +32 -0
- graphite/listing.py +51 -0
- graphite/llm.py +518 -0
- graphite/llm_probe.py +157 -0
- graphite/mcp.py +7 -0
- graphite/mcp_server.py +450 -0
- graphite/natural_query.py +252 -0
- graphite/overlays.py +713 -0
- graphite/probe_process.py +879 -0
- graphite/probe_workspace.py +728 -0
- graphite/process_contracts.py +22 -0
- graphite/provider_observer.py +397 -0
- graphite/query.py +646 -0
- graphite/query_plan.py +97 -0
- graphite/replacement_audit.py +291 -0
- graphite/resolve.py +660 -0
- graphite/review.py +782 -0
- graphite/routing/__init__.py +5 -0
- graphite/routing/approval.py +362 -0
- graphite/routing/classifier.py +169 -0
- graphite/routing/claude_executor.py +419 -0
- graphite/routing/claude_probe.py +102 -0
- graphite/routing/cli_identity.py +84 -0
- graphite/routing/codex_executor.py +383 -0
- graphite/routing/codex_probe.py +93 -0
- graphite/routing/context_builder.py +327 -0
- graphite/routing/contracts.py +802 -0
- graphite/routing/diff_policy.py +468 -0
- graphite/routing/edit_apply.py +166 -0
- graphite/routing/effort.py +43 -0
- graphite/routing/lifecycle.py +771 -0
- graphite/routing/lifecycle_operator.py +227 -0
- graphite/routing/lifecycle_service.py +555 -0
- graphite/routing/lifecycle_storage.py +977 -0
- graphite/routing/ollama_executor.py +341 -0
- graphite/routing/ollama_probe.py +72 -0
- graphite/routing/openrouter_executor.py +338 -0
- graphite/routing/openrouter_probe.py +188 -0
- graphite/routing/policy.py +815 -0
- graphite/routing/probe_runner.py +543 -0
- graphite/routing/process_runner.py +523 -0
- graphite/routing/profiles.py +554 -0
- graphite/routing/prompt.py +58 -0
- graphite/routing/registry.py +444 -0
- graphite/routing/route_pool.py +629 -0
- graphite/routing/route_pool_execution.py +275 -0
- graphite/routing/schema_validation.py +169 -0
- graphite/routing/service.py +1263 -0
- graphite/routing/settings.py +99 -0
- graphite/routing/shadow.py +201 -0
- graphite/routing/storage.py +4001 -0
- graphite/routing/telemetry.py +346 -0
- graphite/routing/worktree.py +259 -0
- graphite/routing/zai_edit.py +113 -0
- graphite/routing/zai_executor.py +191 -0
- graphite/routing/zai_probe.py +126 -0
- graphite/savings.py +84 -0
- graphite/ts_bridge.py +142 -0
- graphite/ts_resolver.mjs +314 -0
- graphite/typescript_activation.py +1586 -0
- graphite/usage_ledger.py +156 -0
- graphite/validation.py +148 -0
- graphite/watch.py +167 -0
- graphite/windows_job.py +368 -0
- graphite/windows_startup.py +144 -0
- graphite/windows_task.py +212 -0
- graphite_code-0.3.0.dist-info/METADATA +743 -0
- graphite_code-0.3.0.dist-info/RECORD +112 -0
- graphite_code-0.3.0.dist-info/WHEEL +4 -0
- graphite_code-0.3.0.dist-info/entry_points.txt +3 -0
- graphite_code-0.3.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
"""Provider-neutral runtime lifecycle authority contracts.
|
|
2
|
+
|
|
3
|
+
This module is intentionally pure. It normalizes identity, compatibility, and
|
|
4
|
+
transition decisions without discovering executables, contacting endpoints, or
|
|
5
|
+
granting provider execution authority.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from enum import StrEnum
|
|
14
|
+
from typing import Any, ClassVar
|
|
15
|
+
|
|
16
|
+
from .contracts import PublicRecord
|
|
17
|
+
|
|
18
|
+
MAX_CAPABILITIES = 32
|
|
19
|
+
MAX_IDENTIFIER_LENGTH = 128
|
|
20
|
+
|
|
21
|
+
_HEX_64 = re.compile(r"^[0-9a-f]{64}$")
|
|
22
|
+
_SAFE_IDENTIFIER = re.compile(r"^[a-z0-9][a-z0-9._-]{0,127}$")
|
|
23
|
+
_SEMANTIC_VERSION = re.compile(
|
|
24
|
+
r"^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)"
|
|
25
|
+
r"(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?(?:\+[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?$"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RuntimeKind(StrEnum):
|
|
30
|
+
LOCAL_CLI = "local-cli"
|
|
31
|
+
LOCAL_HTTP = "local-http"
|
|
32
|
+
REMOTE_HTTPS = "remote-https"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class LifecycleProviderId(StrEnum):
|
|
36
|
+
CLAUDE_CODE = "claude-code"
|
|
37
|
+
CODEX = "codex"
|
|
38
|
+
OLLAMA = "ollama"
|
|
39
|
+
OPENROUTER = "openrouter"
|
|
40
|
+
ZAI = "zai"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ProviderLifecycleState(StrEnum):
|
|
44
|
+
DISCOVERED = "discovered"
|
|
45
|
+
COMPATIBLE = "compatible"
|
|
46
|
+
VERIFICATION_REQUIRED = "verification_required"
|
|
47
|
+
ACTIVE = "active"
|
|
48
|
+
INCOMPATIBLE = "incompatible"
|
|
49
|
+
UNAVAILABLE = "unavailable"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class IdentityChange(StrEnum):
|
|
53
|
+
UNCHANGED = "unchanged"
|
|
54
|
+
HASH_ONLY = "hash_only"
|
|
55
|
+
PATCH = "patch"
|
|
56
|
+
MINOR = "minor"
|
|
57
|
+
MAJOR = "major"
|
|
58
|
+
CAPABILITY = "capability"
|
|
59
|
+
ENDPOINT = "endpoint"
|
|
60
|
+
MODEL_DIGEST = "model_digest"
|
|
61
|
+
ROUTING_POLICY = "routing_policy"
|
|
62
|
+
POLICY = "policy"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class CompatibilityProbeLevel(StrEnum):
|
|
66
|
+
NONE = "none"
|
|
67
|
+
STANDARD = "standard"
|
|
68
|
+
EXPANDED = "expanded"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class LifecycleReasonCode(StrEnum):
|
|
72
|
+
DISCOVERED = "discovered"
|
|
73
|
+
IDENTITY_UNCHANGED = "identity_unchanged"
|
|
74
|
+
HASH_CHANGED = "hash_changed"
|
|
75
|
+
PATCH_CHANGED = "patch_changed"
|
|
76
|
+
MINOR_CHANGED = "minor_changed"
|
|
77
|
+
MAJOR_CHANGED = "major_changed"
|
|
78
|
+
CAPABILITY_CHANGED = "capability_changed"
|
|
79
|
+
ENDPOINT_CHANGED = "endpoint_changed"
|
|
80
|
+
MODEL_DIGEST_CHANGED = "model_digest_changed"
|
|
81
|
+
ROUTING_POLICY_CHANGED = "routing_policy_changed"
|
|
82
|
+
POLICY_CHANGED = "policy_changed"
|
|
83
|
+
RUNTIME_MISSING = "runtime_missing"
|
|
84
|
+
CREDENTIAL_UNHEALTHY = "credential_unhealthy"
|
|
85
|
+
REQUIRED_CAPABILITY_MISSING = "required_capability_missing"
|
|
86
|
+
POLICY_RANGE_UNSUPPORTED = "policy_range_unsupported"
|
|
87
|
+
PROBE_FAILED = "probe_failed"
|
|
88
|
+
COMPATIBILITY_CONFIRMED = "compatibility_confirmed"
|
|
89
|
+
VERIFICATION_ACCEPTED = "verification_accepted"
|
|
90
|
+
POLICY_PROMOTED = "policy_promoted"
|
|
91
|
+
PATCH_CARRIED_FORWARD = "patch_carried_forward"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
_PROVIDER_RUNTIME_KINDS = {
|
|
95
|
+
LifecycleProviderId.CLAUDE_CODE: RuntimeKind.LOCAL_CLI,
|
|
96
|
+
LifecycleProviderId.CODEX: RuntimeKind.LOCAL_CLI,
|
|
97
|
+
LifecycleProviderId.OLLAMA: RuntimeKind.LOCAL_HTTP,
|
|
98
|
+
LifecycleProviderId.OPENROUTER: RuntimeKind.REMOTE_HTTPS,
|
|
99
|
+
LifecycleProviderId.ZAI: RuntimeKind.REMOTE_HTTPS,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _enum(value: object, enum_type: type[StrEnum], *, code: str) -> StrEnum:
|
|
104
|
+
try:
|
|
105
|
+
return enum_type(value)
|
|
106
|
+
except (TypeError, ValueError) as exc:
|
|
107
|
+
raise ValueError(code) from exc
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _hash(value: object, *, code: str, optional: bool = False) -> str | None:
|
|
111
|
+
if value is None and optional:
|
|
112
|
+
return None
|
|
113
|
+
if not isinstance(value, str) or _HEX_64.fullmatch(value) is None:
|
|
114
|
+
raise ValueError(code)
|
|
115
|
+
return value
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _semantic_version(value: object, *, code: str) -> str:
|
|
119
|
+
if (
|
|
120
|
+
not isinstance(value, str)
|
|
121
|
+
or len(value) > MAX_IDENTIFIER_LENGTH
|
|
122
|
+
or _SEMANTIC_VERSION.fullmatch(value) is None
|
|
123
|
+
):
|
|
124
|
+
raise ValueError(code)
|
|
125
|
+
return value
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _identifier(value: object, *, code: str) -> str:
|
|
129
|
+
if (
|
|
130
|
+
not isinstance(value, str)
|
|
131
|
+
or len(value) > MAX_IDENTIFIER_LENGTH
|
|
132
|
+
or _SAFE_IDENTIFIER.fullmatch(value) is None
|
|
133
|
+
):
|
|
134
|
+
raise ValueError(code)
|
|
135
|
+
return value
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _timestamp(value: object, *, code: str) -> int:
|
|
139
|
+
if isinstance(value, bool) or not isinstance(value, int) or not 0 <= value <= 10**12:
|
|
140
|
+
raise ValueError(code)
|
|
141
|
+
return value
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _capabilities(value: object) -> tuple[str, ...]:
|
|
145
|
+
if not isinstance(value, (tuple, list)) or not value or len(value) > MAX_CAPABILITIES:
|
|
146
|
+
raise ValueError("capabilities_invalid")
|
|
147
|
+
normalized = tuple(_identifier(item, code="capabilities_invalid") for item in value)
|
|
148
|
+
if len(set(normalized)) != len(normalized):
|
|
149
|
+
raise ValueError("capabilities_invalid")
|
|
150
|
+
return tuple(sorted(normalized))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _canonical_digest(value: dict[str, Any]) -> str:
|
|
154
|
+
payload = json.dumps(
|
|
155
|
+
value, sort_keys=True, separators=(",", ":"), ensure_ascii=True
|
|
156
|
+
).encode("utf-8")
|
|
157
|
+
return hashlib.sha256(payload).hexdigest()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _validate_boundary(
|
|
161
|
+
provider: LifecycleProviderId, runtime_kind: RuntimeKind, *, code: str
|
|
162
|
+
) -> None:
|
|
163
|
+
if _PROVIDER_RUNTIME_KINDS.get(provider) is not runtime_kind:
|
|
164
|
+
raise ValueError(code)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _version_triplet(value: str) -> tuple[int, int, int]:
|
|
168
|
+
core = value.split("-", 1)[0].split("+", 1)[0]
|
|
169
|
+
major, minor, patch = core.split(".")
|
|
170
|
+
return int(major), int(minor), int(patch)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _version_key(
|
|
174
|
+
value: str,
|
|
175
|
+
) -> tuple[int, int, int, int, tuple[tuple[int, int | str], ...]]:
|
|
176
|
+
without_build = value.split("+", 1)[0]
|
|
177
|
+
core, separator, prerelease = without_build.partition("-")
|
|
178
|
+
major, minor, patch = (int(part) for part in core.split("."))
|
|
179
|
+
if not separator:
|
|
180
|
+
return major, minor, patch, 1, ()
|
|
181
|
+
components: list[tuple[int, int | str]] = []
|
|
182
|
+
for component in prerelease.split("."):
|
|
183
|
+
components.append(
|
|
184
|
+
(0, int(component)) if component.isdigit() else (1, component)
|
|
185
|
+
)
|
|
186
|
+
return major, minor, patch, 0, tuple(components)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dataclass(frozen=True)
|
|
190
|
+
class ProviderRuntimeIdentity(PublicRecord):
|
|
191
|
+
provider: LifecycleProviderId
|
|
192
|
+
runtime_kind: RuntimeKind
|
|
193
|
+
version: str
|
|
194
|
+
runtime_digest: str
|
|
195
|
+
model_identity_digest: str | None
|
|
196
|
+
routing_policy_digest: str | None
|
|
197
|
+
capabilities: tuple[str, ...]
|
|
198
|
+
policy_version: str
|
|
199
|
+
observed_at: int
|
|
200
|
+
|
|
201
|
+
_public_fields: ClassVar[tuple[str, ...]] = (
|
|
202
|
+
"provider",
|
|
203
|
+
"runtime_kind",
|
|
204
|
+
"version",
|
|
205
|
+
"runtime_digest",
|
|
206
|
+
"model_identity_digest",
|
|
207
|
+
"routing_policy_digest",
|
|
208
|
+
"capabilities",
|
|
209
|
+
"policy_version",
|
|
210
|
+
"observed_at",
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def __post_init__(self) -> None:
|
|
214
|
+
provider = _enum(self.provider, LifecycleProviderId, code="provider_invalid")
|
|
215
|
+
runtime_kind = _enum(self.runtime_kind, RuntimeKind, code="runtime_kind_invalid")
|
|
216
|
+
assert isinstance(provider, LifecycleProviderId)
|
|
217
|
+
assert isinstance(runtime_kind, RuntimeKind)
|
|
218
|
+
_validate_boundary(provider, runtime_kind, code="provider_runtime_kind_invalid")
|
|
219
|
+
model_digest = _hash(
|
|
220
|
+
self.model_identity_digest, code="model_identity_digest_invalid", optional=True
|
|
221
|
+
)
|
|
222
|
+
routing_digest = _hash(
|
|
223
|
+
self.routing_policy_digest, code="routing_policy_digest_invalid", optional=True
|
|
224
|
+
)
|
|
225
|
+
if provider in {
|
|
226
|
+
LifecycleProviderId.OLLAMA,
|
|
227
|
+
LifecycleProviderId.OPENROUTER,
|
|
228
|
+
LifecycleProviderId.ZAI,
|
|
229
|
+
} and model_digest is None:
|
|
230
|
+
raise ValueError("model_identity_digest_invalid")
|
|
231
|
+
if provider is LifecycleProviderId.OPENROUTER and routing_digest is None:
|
|
232
|
+
raise ValueError("routing_policy_digest_invalid")
|
|
233
|
+
if provider is not LifecycleProviderId.OPENROUTER and routing_digest is not None:
|
|
234
|
+
raise ValueError("routing_policy_digest_invalid")
|
|
235
|
+
if runtime_kind is RuntimeKind.LOCAL_CLI and model_digest is not None:
|
|
236
|
+
raise ValueError("model_identity_digest_invalid")
|
|
237
|
+
object.__setattr__(self, "provider", provider)
|
|
238
|
+
object.__setattr__(self, "runtime_kind", runtime_kind)
|
|
239
|
+
object.__setattr__(
|
|
240
|
+
self, "version", _semantic_version(self.version, code="runtime_version_invalid")
|
|
241
|
+
)
|
|
242
|
+
object.__setattr__(
|
|
243
|
+
self, "runtime_digest", _hash(self.runtime_digest, code="runtime_digest_invalid")
|
|
244
|
+
)
|
|
245
|
+
object.__setattr__(self, "model_identity_digest", model_digest)
|
|
246
|
+
object.__setattr__(self, "routing_policy_digest", routing_digest)
|
|
247
|
+
object.__setattr__(self, "capabilities", _capabilities(self.capabilities))
|
|
248
|
+
object.__setattr__(
|
|
249
|
+
self,
|
|
250
|
+
"policy_version",
|
|
251
|
+
_semantic_version(self.policy_version, code="policy_version_invalid"),
|
|
252
|
+
)
|
|
253
|
+
object.__setattr__(
|
|
254
|
+
self, "observed_at", _timestamp(self.observed_at, code="observed_at_invalid")
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def digest(self) -> str:
|
|
259
|
+
return _canonical_digest(
|
|
260
|
+
{
|
|
261
|
+
"provider": self.provider.value,
|
|
262
|
+
"runtime_kind": self.runtime_kind.value,
|
|
263
|
+
"version": self.version,
|
|
264
|
+
"runtime_digest": self.runtime_digest,
|
|
265
|
+
"model_identity_digest": self.model_identity_digest,
|
|
266
|
+
"routing_policy_digest": self.routing_policy_digest,
|
|
267
|
+
"capabilities": list(self.capabilities),
|
|
268
|
+
"policy_version": self.policy_version,
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@dataclass(frozen=True)
|
|
274
|
+
class ProviderCompatibilityPolicy(PublicRecord):
|
|
275
|
+
provider: LifecycleProviderId
|
|
276
|
+
runtime_kind: RuntimeKind
|
|
277
|
+
policy_version: str
|
|
278
|
+
minimum_version: str
|
|
279
|
+
maximum_version_exclusive: str
|
|
280
|
+
required_capabilities: tuple[str, ...]
|
|
281
|
+
|
|
282
|
+
_public_fields: ClassVar[tuple[str, ...]] = (
|
|
283
|
+
"provider",
|
|
284
|
+
"runtime_kind",
|
|
285
|
+
"policy_version",
|
|
286
|
+
"minimum_version",
|
|
287
|
+
"maximum_version_exclusive",
|
|
288
|
+
"required_capabilities",
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
def __post_init__(self) -> None:
|
|
292
|
+
provider = _enum(self.provider, LifecycleProviderId, code="provider_invalid")
|
|
293
|
+
runtime_kind = _enum(self.runtime_kind, RuntimeKind, code="runtime_kind_invalid")
|
|
294
|
+
assert isinstance(provider, LifecycleProviderId)
|
|
295
|
+
assert isinstance(runtime_kind, RuntimeKind)
|
|
296
|
+
_validate_boundary(provider, runtime_kind, code="compatibility_boundary_invalid")
|
|
297
|
+
minimum = _semantic_version(
|
|
298
|
+
self.minimum_version, code="compatibility_version_range_invalid"
|
|
299
|
+
)
|
|
300
|
+
maximum = _semantic_version(
|
|
301
|
+
self.maximum_version_exclusive, code="compatibility_version_range_invalid"
|
|
302
|
+
)
|
|
303
|
+
if _version_key(minimum) >= _version_key(maximum):
|
|
304
|
+
raise ValueError("compatibility_version_range_invalid")
|
|
305
|
+
object.__setattr__(self, "provider", provider)
|
|
306
|
+
object.__setattr__(self, "runtime_kind", runtime_kind)
|
|
307
|
+
object.__setattr__(
|
|
308
|
+
self,
|
|
309
|
+
"policy_version",
|
|
310
|
+
_semantic_version(self.policy_version, code="policy_version_invalid"),
|
|
311
|
+
)
|
|
312
|
+
object.__setattr__(self, "minimum_version", minimum)
|
|
313
|
+
object.__setattr__(self, "maximum_version_exclusive", maximum)
|
|
314
|
+
object.__setattr__(self, "required_capabilities", _capabilities(self.required_capabilities))
|
|
315
|
+
|
|
316
|
+
@property
|
|
317
|
+
def digest(self) -> str:
|
|
318
|
+
return _canonical_digest(self.to_dict())
|
|
319
|
+
|
|
320
|
+
def supports(self, identity: ProviderRuntimeIdentity) -> bool:
|
|
321
|
+
if not isinstance(identity, ProviderRuntimeIdentity):
|
|
322
|
+
raise ValueError("identity_invalid")
|
|
323
|
+
if identity.provider is not self.provider or identity.runtime_kind is not self.runtime_kind:
|
|
324
|
+
return False
|
|
325
|
+
if identity.policy_version != self.policy_version:
|
|
326
|
+
return False
|
|
327
|
+
version = _version_key(identity.version)
|
|
328
|
+
if not (
|
|
329
|
+
_version_key(self.minimum_version)
|
|
330
|
+
<= version
|
|
331
|
+
< _version_key(self.maximum_version_exclusive)
|
|
332
|
+
):
|
|
333
|
+
return False
|
|
334
|
+
return set(self.required_capabilities).issubset(identity.capabilities)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
@dataclass(frozen=True)
|
|
338
|
+
class ProviderCompatibilityAssessment(PublicRecord):
|
|
339
|
+
change: IdentityChange
|
|
340
|
+
state: ProviderLifecycleState
|
|
341
|
+
reason: LifecycleReasonCode
|
|
342
|
+
probe_level: CompatibilityProbeLevel
|
|
343
|
+
|
|
344
|
+
_public_fields: ClassVar[tuple[str, ...]] = (
|
|
345
|
+
"change",
|
|
346
|
+
"state",
|
|
347
|
+
"reason",
|
|
348
|
+
"probe_level",
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
def __post_init__(self) -> None:
|
|
352
|
+
object.__setattr__(
|
|
353
|
+
self, "change", _enum(self.change, IdentityChange, code="identity_change_invalid")
|
|
354
|
+
)
|
|
355
|
+
object.__setattr__(
|
|
356
|
+
self, "state", _enum(self.state, ProviderLifecycleState, code="lifecycle_state_invalid")
|
|
357
|
+
)
|
|
358
|
+
object.__setattr__(
|
|
359
|
+
self, "reason", _enum(self.reason, LifecycleReasonCode, code="lifecycle_reason_invalid")
|
|
360
|
+
)
|
|
361
|
+
object.__setattr__(
|
|
362
|
+
self,
|
|
363
|
+
"probe_level",
|
|
364
|
+
_enum(self.probe_level, CompatibilityProbeLevel, code="probe_level_invalid"),
|
|
365
|
+
)
|
|
366
|
+
if self.state is ProviderLifecycleState.ACTIVE and not (
|
|
367
|
+
(
|
|
368
|
+
self.change is IdentityChange.UNCHANGED
|
|
369
|
+
and self.reason is LifecycleReasonCode.IDENTITY_UNCHANGED
|
|
370
|
+
and self.probe_level is CompatibilityProbeLevel.NONE
|
|
371
|
+
)
|
|
372
|
+
or (
|
|
373
|
+
self.change is IdentityChange.PATCH
|
|
374
|
+
and self.reason is LifecycleReasonCode.PATCH_CARRIED_FORWARD
|
|
375
|
+
and self.probe_level is CompatibilityProbeLevel.STANDARD
|
|
376
|
+
)
|
|
377
|
+
):
|
|
378
|
+
raise ValueError("assessment_authority_invalid")
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def classify_identity_change(
|
|
382
|
+
previous: ProviderRuntimeIdentity, current: ProviderRuntimeIdentity
|
|
383
|
+
) -> IdentityChange:
|
|
384
|
+
if not isinstance(previous, ProviderRuntimeIdentity) or not isinstance(
|
|
385
|
+
current, ProviderRuntimeIdentity
|
|
386
|
+
):
|
|
387
|
+
raise ValueError("identity_invalid")
|
|
388
|
+
if (
|
|
389
|
+
previous.provider is not current.provider
|
|
390
|
+
or previous.runtime_kind is not current.runtime_kind
|
|
391
|
+
):
|
|
392
|
+
raise ValueError("identity_boundary_invalid")
|
|
393
|
+
if previous.digest == current.digest:
|
|
394
|
+
return IdentityChange.UNCHANGED
|
|
395
|
+
previous_version = _version_triplet(previous.version)
|
|
396
|
+
current_version = _version_triplet(current.version)
|
|
397
|
+
if previous_version[0] != current_version[0]:
|
|
398
|
+
return IdentityChange.MAJOR
|
|
399
|
+
if previous_version[1] != current_version[1]:
|
|
400
|
+
return IdentityChange.MINOR
|
|
401
|
+
if previous.model_identity_digest != current.model_identity_digest:
|
|
402
|
+
return IdentityChange.MODEL_DIGEST
|
|
403
|
+
if previous.routing_policy_digest != current.routing_policy_digest:
|
|
404
|
+
return IdentityChange.ROUTING_POLICY
|
|
405
|
+
if previous.capabilities != current.capabilities:
|
|
406
|
+
return IdentityChange.CAPABILITY
|
|
407
|
+
if previous.policy_version != current.policy_version:
|
|
408
|
+
return IdentityChange.POLICY
|
|
409
|
+
if (
|
|
410
|
+
previous.runtime_digest != current.runtime_digest
|
|
411
|
+
and current.runtime_kind is not RuntimeKind.LOCAL_CLI
|
|
412
|
+
):
|
|
413
|
+
return IdentityChange.ENDPOINT
|
|
414
|
+
if previous.version != current.version or previous_version[2] != current_version[2]:
|
|
415
|
+
return IdentityChange.PATCH
|
|
416
|
+
if previous.runtime_digest != current.runtime_digest:
|
|
417
|
+
return IdentityChange.HASH_ONLY
|
|
418
|
+
raise ValueError("identity_change_unclassified")
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
_CHANGE_REASONS = {
|
|
422
|
+
IdentityChange.UNCHANGED: LifecycleReasonCode.IDENTITY_UNCHANGED,
|
|
423
|
+
IdentityChange.HASH_ONLY: LifecycleReasonCode.HASH_CHANGED,
|
|
424
|
+
IdentityChange.PATCH: LifecycleReasonCode.PATCH_CHANGED,
|
|
425
|
+
IdentityChange.MINOR: LifecycleReasonCode.MINOR_CHANGED,
|
|
426
|
+
IdentityChange.MAJOR: LifecycleReasonCode.MAJOR_CHANGED,
|
|
427
|
+
IdentityChange.CAPABILITY: LifecycleReasonCode.CAPABILITY_CHANGED,
|
|
428
|
+
IdentityChange.ENDPOINT: LifecycleReasonCode.ENDPOINT_CHANGED,
|
|
429
|
+
IdentityChange.MODEL_DIGEST: LifecycleReasonCode.MODEL_DIGEST_CHANGED,
|
|
430
|
+
IdentityChange.ROUTING_POLICY: LifecycleReasonCode.ROUTING_POLICY_CHANGED,
|
|
431
|
+
IdentityChange.POLICY: LifecycleReasonCode.POLICY_CHANGED,
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
_STANDARD_CHANGES = frozenset({IdentityChange.HASH_ONLY, IdentityChange.PATCH})
|
|
435
|
+
_EXPANDED_CHANGES = frozenset(
|
|
436
|
+
{
|
|
437
|
+
IdentityChange.MINOR,
|
|
438
|
+
IdentityChange.CAPABILITY,
|
|
439
|
+
IdentityChange.ENDPOINT,
|
|
440
|
+
IdentityChange.MODEL_DIGEST,
|
|
441
|
+
IdentityChange.ROUTING_POLICY,
|
|
442
|
+
IdentityChange.POLICY,
|
|
443
|
+
}
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def assess_identity_change(
|
|
448
|
+
previous: ProviderRuntimeIdentity | None,
|
|
449
|
+
current: ProviderRuntimeIdentity,
|
|
450
|
+
policy: ProviderCompatibilityPolicy,
|
|
451
|
+
*,
|
|
452
|
+
runtime_available: bool = True,
|
|
453
|
+
credential_healthy: bool = True,
|
|
454
|
+
standard_probe_passed: bool = True,
|
|
455
|
+
expanded_probe_passed: bool = True,
|
|
456
|
+
existing_state: ProviderLifecycleState = ProviderLifecycleState.COMPATIBLE,
|
|
457
|
+
) -> ProviderCompatibilityAssessment:
|
|
458
|
+
if not isinstance(current, ProviderRuntimeIdentity) or not isinstance(
|
|
459
|
+
policy, ProviderCompatibilityPolicy
|
|
460
|
+
):
|
|
461
|
+
raise ValueError("identity_invalid")
|
|
462
|
+
if current.provider is not policy.provider or current.runtime_kind is not policy.runtime_kind:
|
|
463
|
+
raise ValueError("compatibility_boundary_invalid")
|
|
464
|
+
existing = _enum(
|
|
465
|
+
existing_state, ProviderLifecycleState, code="lifecycle_state_invalid"
|
|
466
|
+
)
|
|
467
|
+
assert isinstance(existing, ProviderLifecycleState)
|
|
468
|
+
if not isinstance(runtime_available, bool):
|
|
469
|
+
raise ValueError("runtime_available_invalid")
|
|
470
|
+
if not isinstance(credential_healthy, bool):
|
|
471
|
+
raise ValueError("credential_health_invalid")
|
|
472
|
+
if not isinstance(standard_probe_passed, bool) or not isinstance(
|
|
473
|
+
expanded_probe_passed, bool
|
|
474
|
+
):
|
|
475
|
+
raise ValueError("probe_result_invalid")
|
|
476
|
+
if not runtime_available:
|
|
477
|
+
return ProviderCompatibilityAssessment(
|
|
478
|
+
IdentityChange.UNCHANGED,
|
|
479
|
+
ProviderLifecycleState.UNAVAILABLE,
|
|
480
|
+
LifecycleReasonCode.RUNTIME_MISSING,
|
|
481
|
+
CompatibilityProbeLevel.NONE,
|
|
482
|
+
)
|
|
483
|
+
if not credential_healthy:
|
|
484
|
+
return ProviderCompatibilityAssessment(
|
|
485
|
+
IdentityChange.UNCHANGED,
|
|
486
|
+
ProviderLifecycleState.UNAVAILABLE,
|
|
487
|
+
LifecycleReasonCode.CREDENTIAL_UNHEALTHY,
|
|
488
|
+
CompatibilityProbeLevel.NONE,
|
|
489
|
+
)
|
|
490
|
+
if previous is None:
|
|
491
|
+
if not policy.supports(current):
|
|
492
|
+
reason = (
|
|
493
|
+
LifecycleReasonCode.REQUIRED_CAPABILITY_MISSING
|
|
494
|
+
if not set(policy.required_capabilities).issubset(current.capabilities)
|
|
495
|
+
else LifecycleReasonCode.POLICY_RANGE_UNSUPPORTED
|
|
496
|
+
)
|
|
497
|
+
return ProviderCompatibilityAssessment(
|
|
498
|
+
IdentityChange.UNCHANGED,
|
|
499
|
+
ProviderLifecycleState.INCOMPATIBLE,
|
|
500
|
+
reason,
|
|
501
|
+
CompatibilityProbeLevel.STANDARD,
|
|
502
|
+
)
|
|
503
|
+
return ProviderCompatibilityAssessment(
|
|
504
|
+
IdentityChange.UNCHANGED,
|
|
505
|
+
ProviderLifecycleState.DISCOVERED,
|
|
506
|
+
LifecycleReasonCode.DISCOVERED,
|
|
507
|
+
CompatibilityProbeLevel.STANDARD,
|
|
508
|
+
)
|
|
509
|
+
change = classify_identity_change(previous, current)
|
|
510
|
+
if change is IdentityChange.MAJOR:
|
|
511
|
+
return ProviderCompatibilityAssessment(
|
|
512
|
+
change,
|
|
513
|
+
ProviderLifecycleState.INCOMPATIBLE,
|
|
514
|
+
LifecycleReasonCode.MAJOR_CHANGED,
|
|
515
|
+
CompatibilityProbeLevel.NONE,
|
|
516
|
+
)
|
|
517
|
+
if not set(policy.required_capabilities).issubset(current.capabilities):
|
|
518
|
+
return ProviderCompatibilityAssessment(
|
|
519
|
+
change,
|
|
520
|
+
ProviderLifecycleState.INCOMPATIBLE,
|
|
521
|
+
LifecycleReasonCode.REQUIRED_CAPABILITY_MISSING,
|
|
522
|
+
CompatibilityProbeLevel.EXPANDED,
|
|
523
|
+
)
|
|
524
|
+
if not policy.supports(current):
|
|
525
|
+
return ProviderCompatibilityAssessment(
|
|
526
|
+
change,
|
|
527
|
+
ProviderLifecycleState.INCOMPATIBLE,
|
|
528
|
+
LifecycleReasonCode.POLICY_RANGE_UNSUPPORTED,
|
|
529
|
+
CompatibilityProbeLevel.NONE,
|
|
530
|
+
)
|
|
531
|
+
if change is IdentityChange.UNCHANGED:
|
|
532
|
+
return ProviderCompatibilityAssessment(
|
|
533
|
+
change,
|
|
534
|
+
existing,
|
|
535
|
+
LifecycleReasonCode.IDENTITY_UNCHANGED,
|
|
536
|
+
CompatibilityProbeLevel.NONE,
|
|
537
|
+
)
|
|
538
|
+
probe_level = (
|
|
539
|
+
CompatibilityProbeLevel.STANDARD
|
|
540
|
+
if change in _STANDARD_CHANGES
|
|
541
|
+
else CompatibilityProbeLevel.EXPANDED
|
|
542
|
+
)
|
|
543
|
+
probe_passed = (
|
|
544
|
+
standard_probe_passed
|
|
545
|
+
if probe_level is CompatibilityProbeLevel.STANDARD
|
|
546
|
+
else expanded_probe_passed
|
|
547
|
+
)
|
|
548
|
+
if not probe_passed:
|
|
549
|
+
return ProviderCompatibilityAssessment(
|
|
550
|
+
change,
|
|
551
|
+
ProviderLifecycleState.INCOMPATIBLE,
|
|
552
|
+
LifecycleReasonCode.PROBE_FAILED,
|
|
553
|
+
probe_level,
|
|
554
|
+
)
|
|
555
|
+
if (
|
|
556
|
+
change is IdentityChange.PATCH
|
|
557
|
+
and current.runtime_kind is RuntimeKind.LOCAL_CLI
|
|
558
|
+
and existing is ProviderLifecycleState.ACTIVE
|
|
559
|
+
):
|
|
560
|
+
return ProviderCompatibilityAssessment(
|
|
561
|
+
change,
|
|
562
|
+
ProviderLifecycleState.ACTIVE,
|
|
563
|
+
LifecycleReasonCode.PATCH_CARRIED_FORWARD,
|
|
564
|
+
probe_level,
|
|
565
|
+
)
|
|
566
|
+
return ProviderCompatibilityAssessment(
|
|
567
|
+
change,
|
|
568
|
+
ProviderLifecycleState.VERIFICATION_REQUIRED,
|
|
569
|
+
_CHANGE_REASONS[change],
|
|
570
|
+
probe_level,
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
_VALID_TRANSITIONS = {
|
|
575
|
+
None: frozenset(
|
|
576
|
+
{
|
|
577
|
+
ProviderLifecycleState.DISCOVERED,
|
|
578
|
+
ProviderLifecycleState.INCOMPATIBLE,
|
|
579
|
+
ProviderLifecycleState.UNAVAILABLE,
|
|
580
|
+
}
|
|
581
|
+
),
|
|
582
|
+
ProviderLifecycleState.DISCOVERED: frozenset(
|
|
583
|
+
{
|
|
584
|
+
ProviderLifecycleState.COMPATIBLE,
|
|
585
|
+
ProviderLifecycleState.VERIFICATION_REQUIRED,
|
|
586
|
+
ProviderLifecycleState.INCOMPATIBLE,
|
|
587
|
+
ProviderLifecycleState.UNAVAILABLE,
|
|
588
|
+
}
|
|
589
|
+
),
|
|
590
|
+
ProviderLifecycleState.COMPATIBLE: frozenset(
|
|
591
|
+
{
|
|
592
|
+
ProviderLifecycleState.ACTIVE,
|
|
593
|
+
ProviderLifecycleState.VERIFICATION_REQUIRED,
|
|
594
|
+
ProviderLifecycleState.INCOMPATIBLE,
|
|
595
|
+
ProviderLifecycleState.UNAVAILABLE,
|
|
596
|
+
}
|
|
597
|
+
),
|
|
598
|
+
ProviderLifecycleState.VERIFICATION_REQUIRED: frozenset(
|
|
599
|
+
{
|
|
600
|
+
ProviderLifecycleState.ACTIVE,
|
|
601
|
+
ProviderLifecycleState.INCOMPATIBLE,
|
|
602
|
+
ProviderLifecycleState.UNAVAILABLE,
|
|
603
|
+
}
|
|
604
|
+
),
|
|
605
|
+
ProviderLifecycleState.ACTIVE: frozenset(
|
|
606
|
+
{
|
|
607
|
+
ProviderLifecycleState.VERIFICATION_REQUIRED,
|
|
608
|
+
ProviderLifecycleState.INCOMPATIBLE,
|
|
609
|
+
ProviderLifecycleState.UNAVAILABLE,
|
|
610
|
+
}
|
|
611
|
+
),
|
|
612
|
+
ProviderLifecycleState.INCOMPATIBLE: frozenset(
|
|
613
|
+
{
|
|
614
|
+
ProviderLifecycleState.VERIFICATION_REQUIRED,
|
|
615
|
+
ProviderLifecycleState.UNAVAILABLE,
|
|
616
|
+
}
|
|
617
|
+
),
|
|
618
|
+
ProviderLifecycleState.UNAVAILABLE: frozenset(
|
|
619
|
+
{
|
|
620
|
+
ProviderLifecycleState.DISCOVERED,
|
|
621
|
+
ProviderLifecycleState.INCOMPATIBLE,
|
|
622
|
+
}
|
|
623
|
+
),
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
@dataclass(frozen=True)
|
|
628
|
+
class ProviderLifecycleEvent(PublicRecord):
|
|
629
|
+
event_id: str
|
|
630
|
+
provider: LifecycleProviderId
|
|
631
|
+
runtime_kind: RuntimeKind
|
|
632
|
+
previous_identity_digest: str | None
|
|
633
|
+
current_identity_digest: str | None
|
|
634
|
+
previous_state: ProviderLifecycleState | None
|
|
635
|
+
current_state: ProviderLifecycleState
|
|
636
|
+
reason: LifecycleReasonCode
|
|
637
|
+
policy_version: str
|
|
638
|
+
occurred_at: int
|
|
639
|
+
|
|
640
|
+
_public_fields: ClassVar[tuple[str, ...]] = (
|
|
641
|
+
"event_id",
|
|
642
|
+
"provider",
|
|
643
|
+
"runtime_kind",
|
|
644
|
+
"previous_identity_digest",
|
|
645
|
+
"current_identity_digest",
|
|
646
|
+
"previous_state",
|
|
647
|
+
"current_state",
|
|
648
|
+
"reason",
|
|
649
|
+
"policy_version",
|
|
650
|
+
"occurred_at",
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
def __post_init__(self) -> None:
|
|
654
|
+
provider = _enum(self.provider, LifecycleProviderId, code="provider_invalid")
|
|
655
|
+
runtime_kind = _enum(self.runtime_kind, RuntimeKind, code="runtime_kind_invalid")
|
|
656
|
+
assert isinstance(provider, LifecycleProviderId)
|
|
657
|
+
assert isinstance(runtime_kind, RuntimeKind)
|
|
658
|
+
_validate_boundary(provider, runtime_kind, code="provider_runtime_kind_invalid")
|
|
659
|
+
previous_state = (
|
|
660
|
+
None
|
|
661
|
+
if self.previous_state is None
|
|
662
|
+
else _enum(
|
|
663
|
+
self.previous_state,
|
|
664
|
+
ProviderLifecycleState,
|
|
665
|
+
code="lifecycle_state_invalid",
|
|
666
|
+
)
|
|
667
|
+
)
|
|
668
|
+
current_state = _enum(
|
|
669
|
+
self.current_state, ProviderLifecycleState, code="lifecycle_state_invalid"
|
|
670
|
+
)
|
|
671
|
+
reason = _enum(self.reason, LifecycleReasonCode, code="lifecycle_reason_invalid")
|
|
672
|
+
assert previous_state is None or isinstance(previous_state, ProviderLifecycleState)
|
|
673
|
+
assert isinstance(current_state, ProviderLifecycleState)
|
|
674
|
+
assert isinstance(reason, LifecycleReasonCode)
|
|
675
|
+
previous_digest = _hash(
|
|
676
|
+
self.previous_identity_digest,
|
|
677
|
+
code="previous_identity_digest_invalid",
|
|
678
|
+
optional=True,
|
|
679
|
+
)
|
|
680
|
+
current_digest = _hash(
|
|
681
|
+
self.current_identity_digest,
|
|
682
|
+
code="current_identity_digest_invalid",
|
|
683
|
+
optional=True,
|
|
684
|
+
)
|
|
685
|
+
if previous_state is None and previous_digest is not None:
|
|
686
|
+
raise ValueError("lifecycle_identity_invalid")
|
|
687
|
+
if (
|
|
688
|
+
previous_state is not None
|
|
689
|
+
and previous_state is not ProviderLifecycleState.UNAVAILABLE
|
|
690
|
+
and previous_digest is None
|
|
691
|
+
):
|
|
692
|
+
raise ValueError("lifecycle_identity_invalid")
|
|
693
|
+
if current_state is not ProviderLifecycleState.UNAVAILABLE and current_digest is None:
|
|
694
|
+
raise ValueError("lifecycle_identity_invalid")
|
|
695
|
+
if previous_state is current_state:
|
|
696
|
+
identity_unchanged = previous_digest == current_digest
|
|
697
|
+
if identity_unchanged is not (
|
|
698
|
+
reason is LifecycleReasonCode.IDENTITY_UNCHANGED
|
|
699
|
+
):
|
|
700
|
+
raise ValueError("lifecycle_transition_invalid")
|
|
701
|
+
if not identity_unchanged:
|
|
702
|
+
if current_state is ProviderLifecycleState.ACTIVE:
|
|
703
|
+
if reason is not LifecycleReasonCode.PATCH_CARRIED_FORWARD:
|
|
704
|
+
raise ValueError("lifecycle_transition_invalid")
|
|
705
|
+
elif reason not in set(_CHANGE_REASONS.values()):
|
|
706
|
+
raise ValueError("lifecycle_transition_invalid")
|
|
707
|
+
elif current_state not in _VALID_TRANSITIONS[previous_state]:
|
|
708
|
+
raise ValueError("lifecycle_transition_invalid")
|
|
709
|
+
if reason is LifecycleReasonCode.PATCH_CARRIED_FORWARD and not (
|
|
710
|
+
previous_state is ProviderLifecycleState.ACTIVE
|
|
711
|
+
and current_state is ProviderLifecycleState.ACTIVE
|
|
712
|
+
and previous_digest != current_digest
|
|
713
|
+
):
|
|
714
|
+
raise ValueError("lifecycle_transition_invalid")
|
|
715
|
+
if (
|
|
716
|
+
current_state is ProviderLifecycleState.ACTIVE
|
|
717
|
+
and previous_state is not current_state
|
|
718
|
+
and reason is not LifecycleReasonCode.VERIFICATION_ACCEPTED
|
|
719
|
+
):
|
|
720
|
+
raise ValueError("lifecycle_transition_invalid")
|
|
721
|
+
if previous_state is None:
|
|
722
|
+
initial_reasons = {
|
|
723
|
+
ProviderLifecycleState.DISCOVERED: frozenset({LifecycleReasonCode.DISCOVERED}),
|
|
724
|
+
ProviderLifecycleState.INCOMPATIBLE: frozenset(
|
|
725
|
+
{
|
|
726
|
+
LifecycleReasonCode.REQUIRED_CAPABILITY_MISSING,
|
|
727
|
+
LifecycleReasonCode.POLICY_RANGE_UNSUPPORTED,
|
|
728
|
+
LifecycleReasonCode.PROBE_FAILED,
|
|
729
|
+
}
|
|
730
|
+
),
|
|
731
|
+
ProviderLifecycleState.UNAVAILABLE: frozenset(
|
|
732
|
+
{
|
|
733
|
+
LifecycleReasonCode.RUNTIME_MISSING,
|
|
734
|
+
LifecycleReasonCode.CREDENTIAL_UNHEALTHY,
|
|
735
|
+
}
|
|
736
|
+
),
|
|
737
|
+
}
|
|
738
|
+
if reason not in initial_reasons[current_state]:
|
|
739
|
+
raise ValueError("lifecycle_transition_invalid")
|
|
740
|
+
if (
|
|
741
|
+
current_state is ProviderLifecycleState.UNAVAILABLE
|
|
742
|
+
and previous_state is not current_state
|
|
743
|
+
and reason
|
|
744
|
+
not in {
|
|
745
|
+
LifecycleReasonCode.RUNTIME_MISSING,
|
|
746
|
+
LifecycleReasonCode.CREDENTIAL_UNHEALTHY,
|
|
747
|
+
}
|
|
748
|
+
):
|
|
749
|
+
raise ValueError("lifecycle_transition_invalid")
|
|
750
|
+
if (
|
|
751
|
+
previous_state is ProviderLifecycleState.INCOMPATIBLE
|
|
752
|
+
and current_state is ProviderLifecycleState.VERIFICATION_REQUIRED
|
|
753
|
+
and reason is not LifecycleReasonCode.POLICY_PROMOTED
|
|
754
|
+
):
|
|
755
|
+
raise ValueError("lifecycle_transition_invalid")
|
|
756
|
+
object.__setattr__(self, "event_id", _identifier(self.event_id, code="event_id_invalid"))
|
|
757
|
+
object.__setattr__(self, "provider", provider)
|
|
758
|
+
object.__setattr__(self, "runtime_kind", runtime_kind)
|
|
759
|
+
object.__setattr__(self, "previous_identity_digest", previous_digest)
|
|
760
|
+
object.__setattr__(self, "current_identity_digest", current_digest)
|
|
761
|
+
object.__setattr__(self, "previous_state", previous_state)
|
|
762
|
+
object.__setattr__(self, "current_state", current_state)
|
|
763
|
+
object.__setattr__(self, "reason", reason)
|
|
764
|
+
object.__setattr__(
|
|
765
|
+
self,
|
|
766
|
+
"policy_version",
|
|
767
|
+
_semantic_version(self.policy_version, code="policy_version_invalid"),
|
|
768
|
+
)
|
|
769
|
+
object.__setattr__(
|
|
770
|
+
self, "occurred_at", _timestamp(self.occurred_at, code="occurred_at_invalid")
|
|
771
|
+
)
|