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.
Files changed (112) hide show
  1. graphite/__init__.py +41 -0
  2. graphite/__main__.py +7 -0
  3. graphite/_cleanup_worker.py +525 -0
  4. graphite/activation.py +164 -0
  5. graphite/agent_hooks.py +577 -0
  6. graphite/agent_settings.py +226 -0
  7. graphite/analyze.py +146 -0
  8. graphite/answer_contract.py +420 -0
  9. graphite/bootstrap.py +210 -0
  10. graphite/buildlock.py +99 -0
  11. graphite/cache.py +131 -0
  12. graphite/channel.py +1325 -0
  13. graphite/cli.py +3053 -0
  14. graphite/cluster.py +111 -0
  15. graphite/config.py +209 -0
  16. graphite/context.py +355 -0
  17. graphite/daemon.py +745 -0
  18. graphite/daemon_health.py +733 -0
  19. graphite/debt.py +118 -0
  20. graphite/dependency_install.py +1597 -0
  21. graphite/detach.py +33 -0
  22. graphite/doctor.py +678 -0
  23. graphite/doctor_probes.py +2100 -0
  24. graphite/engine_identity.py +238 -0
  25. graphite/export/__init__.py +6 -0
  26. graphite/export/html.py +244 -0
  27. graphite/export/json.py +39 -0
  28. graphite/export/md.py +68 -0
  29. graphite/extract/__init__.py +4 -0
  30. graphite/extract/ast.py +1964 -0
  31. graphite/freshness.py +127 -0
  32. graphite/git.py +406 -0
  33. graphite/graph.py +117 -0
  34. graphite/graph_io.py +188 -0
  35. graphite/health.py +147 -0
  36. graphite/hook_entry.py +68 -0
  37. graphite/hookinstall.py +224 -0
  38. graphite/hookshim.py +86 -0
  39. graphite/incident_ledger.py +247 -0
  40. graphite/ingest.py +279 -0
  41. graphite/init.py +791 -0
  42. graphite/io.py +32 -0
  43. graphite/listing.py +51 -0
  44. graphite/llm.py +518 -0
  45. graphite/llm_probe.py +157 -0
  46. graphite/mcp.py +7 -0
  47. graphite/mcp_server.py +450 -0
  48. graphite/natural_query.py +252 -0
  49. graphite/overlays.py +713 -0
  50. graphite/probe_process.py +879 -0
  51. graphite/probe_workspace.py +728 -0
  52. graphite/process_contracts.py +22 -0
  53. graphite/provider_observer.py +397 -0
  54. graphite/query.py +646 -0
  55. graphite/query_plan.py +97 -0
  56. graphite/replacement_audit.py +291 -0
  57. graphite/resolve.py +660 -0
  58. graphite/review.py +782 -0
  59. graphite/routing/__init__.py +5 -0
  60. graphite/routing/approval.py +362 -0
  61. graphite/routing/classifier.py +169 -0
  62. graphite/routing/claude_executor.py +419 -0
  63. graphite/routing/claude_probe.py +102 -0
  64. graphite/routing/cli_identity.py +84 -0
  65. graphite/routing/codex_executor.py +383 -0
  66. graphite/routing/codex_probe.py +93 -0
  67. graphite/routing/context_builder.py +327 -0
  68. graphite/routing/contracts.py +802 -0
  69. graphite/routing/diff_policy.py +468 -0
  70. graphite/routing/edit_apply.py +166 -0
  71. graphite/routing/effort.py +43 -0
  72. graphite/routing/lifecycle.py +771 -0
  73. graphite/routing/lifecycle_operator.py +227 -0
  74. graphite/routing/lifecycle_service.py +555 -0
  75. graphite/routing/lifecycle_storage.py +977 -0
  76. graphite/routing/ollama_executor.py +341 -0
  77. graphite/routing/ollama_probe.py +72 -0
  78. graphite/routing/openrouter_executor.py +338 -0
  79. graphite/routing/openrouter_probe.py +188 -0
  80. graphite/routing/policy.py +815 -0
  81. graphite/routing/probe_runner.py +543 -0
  82. graphite/routing/process_runner.py +523 -0
  83. graphite/routing/profiles.py +554 -0
  84. graphite/routing/prompt.py +58 -0
  85. graphite/routing/registry.py +444 -0
  86. graphite/routing/route_pool.py +629 -0
  87. graphite/routing/route_pool_execution.py +275 -0
  88. graphite/routing/schema_validation.py +169 -0
  89. graphite/routing/service.py +1263 -0
  90. graphite/routing/settings.py +99 -0
  91. graphite/routing/shadow.py +201 -0
  92. graphite/routing/storage.py +4001 -0
  93. graphite/routing/telemetry.py +346 -0
  94. graphite/routing/worktree.py +259 -0
  95. graphite/routing/zai_edit.py +113 -0
  96. graphite/routing/zai_executor.py +191 -0
  97. graphite/routing/zai_probe.py +126 -0
  98. graphite/savings.py +84 -0
  99. graphite/ts_bridge.py +142 -0
  100. graphite/ts_resolver.mjs +314 -0
  101. graphite/typescript_activation.py +1586 -0
  102. graphite/usage_ledger.py +156 -0
  103. graphite/validation.py +148 -0
  104. graphite/watch.py +167 -0
  105. graphite/windows_job.py +368 -0
  106. graphite/windows_startup.py +144 -0
  107. graphite/windows_task.py +212 -0
  108. graphite_code-0.3.0.dist-info/METADATA +743 -0
  109. graphite_code-0.3.0.dist-info/RECORD +112 -0
  110. graphite_code-0.3.0.dist-info/WHEEL +4 -0
  111. graphite_code-0.3.0.dist-info/entry_points.txt +3 -0
  112. graphite_code-0.3.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,802 @@
1
+ """Immutable public contracts for development routing."""
2
+ from __future__ import annotations
3
+
4
+ import hashlib
5
+ import json
6
+ import math
7
+ import re
8
+ from dataclasses import dataclass
9
+ from enum import StrEnum
10
+ from pathlib import Path, PurePosixPath
11
+ from typing import Any, ClassVar
12
+
13
+ MAX_OBJECTIVE_LENGTH = 4_096
14
+ MAX_TARGETS = 64
15
+ MAX_REASON_COUNT = 32
16
+ MAX_ALTERNATIVES = 8
17
+ MAX_OUTBOUND_ITEMS = 64
18
+ MAX_STRING_LENGTH = 256
19
+
20
+ _WINDOWS_ABSOLUTE = re.compile(r"^[A-Za-z]:[\\/]")
21
+ _HEX_64 = re.compile(r"^[0-9a-f]{64}$")
22
+ _GIT_OBJECT = re.compile(r"^(?:[0-9a-f]{40}|[0-9a-f]{64})$")
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
+ _DATA_POLICIES = frozenset({"metadata_only", "source_allowed"})
28
+ _VALIDATION_OUTCOMES = frozenset({"passed", "failed", "blocked", "not_run"})
29
+
30
+
31
+ class TaskCategory(StrEnum):
32
+ DOCUMENTATION = "documentation"
33
+ ISOLATED_CODE = "isolated_code"
34
+ FEATURE = "feature"
35
+ REFACTOR = "refactor"
36
+ ARCHITECTURE = "architecture"
37
+ AUTHENTICATION = "authentication"
38
+ AUTHORIZATION = "authorization"
39
+ TENANT_ISOLATION = "tenant_isolation"
40
+ MIGRATION = "migration"
41
+ DEPLOYMENT = "deployment"
42
+ INFRASTRUCTURE = "infrastructure"
43
+ CONCURRENCY = "concurrency"
44
+ FINANCIAL = "financial"
45
+ LEGAL = "legal"
46
+ UNKNOWN = "unknown"
47
+
48
+
49
+ class RiskTier(StrEnum):
50
+ LOW = "low"
51
+ MEDIUM = "medium"
52
+ HIGH = "high"
53
+
54
+
55
+ class Effort(StrEnum):
56
+ DEFAULT = "default"
57
+ LOW = "low"
58
+ MEDIUM = "medium"
59
+ HIGH = "high"
60
+ XHIGH = "xhigh"
61
+ MAX = "max"
62
+
63
+
64
+ class ProviderId(StrEnum):
65
+ CLAUDE_CODE = "claude-code"
66
+ CODEX = "codex"
67
+ OPENROUTER = "openrouter"
68
+ ZAI = "zai"
69
+
70
+
71
+ class PermissionMode(StrEnum):
72
+ READ_ONLY = "read-only"
73
+ WORKSPACE_WRITE = "workspace-write"
74
+
75
+
76
+ class EvidenceProvenance(StrEnum):
77
+ MACHINE_VERIFIED = "machine_verified"
78
+ CI_IMPORTED = "ci_imported"
79
+ HUMAN = "human"
80
+ PAIRWISE = "pairwise"
81
+ REVERSION = "reversion"
82
+ AMBIGUOUS = "ambiguous"
83
+
84
+
85
+ class ExecutionOutcome(StrEnum):
86
+ SUCCEEDED = "succeeded"
87
+ DECLINED = "declined"
88
+ BLOCKED = "blocked"
89
+ PROVIDER_FAILED = "provider_failed"
90
+ TIMED_OUT = "timed_out"
91
+ CANCELLED = "cancelled"
92
+ UNKNOWN = "unknown"
93
+
94
+
95
+ class FailureReason(StrEnum):
96
+ GRAPH_MISSING = "graph_missing"
97
+ GRAPH_STALE = "graph_stale"
98
+ GRAPH_INVALID = "graph_invalid"
99
+ STORAGE_UNAVAILABLE = "storage_unavailable"
100
+ REGISTRY_MISSING = "registry_missing"
101
+ REGISTRY_STALE = "registry_stale"
102
+ MODEL_UNAVAILABLE = "model_unavailable"
103
+ MODEL_CHANGED = "model_changed"
104
+ EFFORT_UNSUPPORTED = "effort_unsupported"
105
+ CONTEXT_REJECTED = "context_rejected"
106
+ BUDGET_EXHAUSTED = "budget_exhausted"
107
+ APPROVAL_DECLINED = "approval_declined"
108
+ APPROVAL_EXPIRED = "approval_expired"
109
+ APPROVAL_INVALID = "approval_invalid"
110
+ PROVIDER_UNAVAILABLE = "provider_unavailable"
111
+ PROVIDER_PROTOCOL = "provider_protocol"
112
+ RESPONSE_LIMIT = "response_limit"
113
+ TIMEOUT = "timeout"
114
+ CANCELLED = "cancelled"
115
+ INTERNAL = "internal"
116
+
117
+
118
+ def _bounded_text(value: object, *, code: str, maximum: int = MAX_STRING_LENGTH) -> str:
119
+ if not isinstance(value, str) or not value or "\x00" in value or len(value) > maximum:
120
+ raise ValueError(code)
121
+ return value
122
+
123
+
124
+ def _bounded_identifier(value: object, *, code: str) -> str:
125
+ text = _bounded_text(value, code=code)
126
+ if any(character.isspace() for character in text):
127
+ raise ValueError(code)
128
+ return text
129
+
130
+
131
+ def _bounded_integer(value: object, *, code: str, minimum: int = 0, maximum: int = 10**12) -> int:
132
+ if isinstance(value, bool) or not isinstance(value, int) or value < minimum or value > maximum:
133
+ raise ValueError(code)
134
+ return value
135
+
136
+
137
+ def _relative_path(value: object) -> str:
138
+ if not isinstance(value, str) or not value or "\x00" in value or len(value) > 1_024:
139
+ raise ValueError("target_invalid")
140
+ normalized = value.replace("\\", "/")
141
+ parts = PurePosixPath(normalized).parts
142
+ if (
143
+ _WINDOWS_ABSOLUTE.match(value)
144
+ or normalized.startswith("/")
145
+ or any(part in {"", ".", ".."} for part in parts)
146
+ ):
147
+ raise ValueError("target_invalid")
148
+ return PurePosixPath(*parts).as_posix()
149
+
150
+
151
+ def _enum(value: object, enum_type: type[StrEnum], *, code: str) -> StrEnum:
152
+ try:
153
+ return enum_type(value)
154
+ except (TypeError, ValueError) as exc:
155
+ raise ValueError(code) from exc
156
+
157
+
158
+ def _strings(values: object, *, code: str, maximum: int) -> tuple[str, ...]:
159
+ if not isinstance(values, (tuple, list)) or len(values) > maximum:
160
+ raise ValueError(code)
161
+ return tuple(_bounded_text(value, code=code) for value in values)
162
+
163
+
164
+ def _hash(value: object, *, code: str) -> str:
165
+ if not isinstance(value, str) or not _HEX_64.fullmatch(value):
166
+ raise ValueError(code)
167
+ return value
168
+
169
+
170
+ def _semantic_version(value: object, *, code: str) -> str:
171
+ text = _bounded_identifier(value, code=code)
172
+ if not _SEMANTIC_VERSION.fullmatch(text):
173
+ raise ValueError(code)
174
+ return text
175
+
176
+
177
+ def _git_object(value: object, *, code: str) -> str:
178
+ if not isinstance(value, str) or not _GIT_OBJECT.fullmatch(value):
179
+ raise ValueError(code)
180
+ return value
181
+
182
+
183
+ class PublicRecord:
184
+ _public_fields: ClassVar[tuple[str, ...]] = ()
185
+
186
+ def to_dict(self) -> dict[str, Any]:
187
+ result: dict[str, Any] = {}
188
+ for field_name in self._public_fields:
189
+ value = getattr(self, field_name)
190
+ if isinstance(value, StrEnum):
191
+ result[field_name] = value.value
192
+ elif isinstance(value, tuple):
193
+ result[field_name] = [item.value if isinstance(item, StrEnum) else item for item in value]
194
+ else:
195
+ result[field_name] = value
196
+ return result
197
+
198
+
199
+ @dataclass(frozen=True)
200
+ class TaskRequest(PublicRecord):
201
+ objective: str
202
+ repository_root: Path
203
+ targets: tuple[str, ...]
204
+ max_input_tokens: int
205
+ max_output_tokens: int
206
+ data_policy: str
207
+ category_hint: TaskCategory | None = None
208
+
209
+ _public_fields = (
210
+ "objective",
211
+ "repository",
212
+ "targets",
213
+ "max_input_tokens",
214
+ "max_output_tokens",
215
+ "data_policy",
216
+ "category_hint",
217
+ )
218
+
219
+ def __post_init__(self) -> None:
220
+ if not isinstance(self.objective, str) or not self.objective or "\x00" in self.objective:
221
+ raise ValueError("objective_invalid")
222
+ if len(self.objective) > MAX_OBJECTIVE_LENGTH:
223
+ raise ValueError("objective_too_long")
224
+ objective = self.objective
225
+ if not isinstance(self.repository_root, Path) or not self.repository_root.is_absolute():
226
+ raise ValueError("repository_root_invalid")
227
+ if not isinstance(self.targets, (tuple, list)):
228
+ raise ValueError("targets_invalid")
229
+ if len(self.targets) > MAX_TARGETS:
230
+ raise ValueError("targets_too_many")
231
+ targets = tuple(_relative_path(target) for target in self.targets)
232
+ input_tokens = _bounded_integer(
233
+ self.max_input_tokens,
234
+ code="max_input_tokens_invalid",
235
+ minimum=1,
236
+ maximum=262_144,
237
+ )
238
+ output_tokens = _bounded_integer(
239
+ self.max_output_tokens,
240
+ code="max_output_tokens_invalid",
241
+ minimum=1,
242
+ maximum=32_768,
243
+ )
244
+ if self.data_policy not in _DATA_POLICIES:
245
+ raise ValueError("data_policy_invalid")
246
+ category = None
247
+ if self.category_hint is not None:
248
+ category = _enum(self.category_hint, TaskCategory, code="category_hint_invalid")
249
+ object.__setattr__(self, "objective", objective)
250
+ object.__setattr__(self, "repository_root", self.repository_root.resolve())
251
+ object.__setattr__(self, "targets", targets)
252
+ object.__setattr__(self, "max_input_tokens", input_tokens)
253
+ object.__setattr__(self, "max_output_tokens", output_tokens)
254
+ object.__setattr__(self, "category_hint", category)
255
+
256
+ @property
257
+ def repository(self) -> str:
258
+ return self.repository_root.name
259
+
260
+
261
+ @dataclass(frozen=True)
262
+ class TaskProfile(PublicRecord):
263
+ task_id: str
264
+ category: TaskCategory
265
+ risk: RiskTier
266
+ complexity: int
267
+ impact_radius: int
268
+ risk_flags: tuple[str, ...]
269
+ context_requirements: tuple[str, ...]
270
+ verification_requirements: tuple[str, ...]
271
+
272
+ _public_fields = (
273
+ "task_id",
274
+ "category",
275
+ "risk",
276
+ "complexity",
277
+ "impact_radius",
278
+ "risk_flags",
279
+ "context_requirements",
280
+ "verification_requirements",
281
+ )
282
+
283
+ def __post_init__(self) -> None:
284
+ object.__setattr__(self, "task_id", _bounded_identifier(self.task_id, code="task_id_invalid"))
285
+ object.__setattr__(self, "category", _enum(self.category, TaskCategory, code="category_invalid"))
286
+ object.__setattr__(self, "risk", _enum(self.risk, RiskTier, code="risk_invalid"))
287
+ object.__setattr__(self, "complexity", _bounded_integer(self.complexity, code="complexity_invalid", maximum=10))
288
+ object.__setattr__(self, "impact_radius", _bounded_integer(self.impact_radius, code="impact_radius_invalid", maximum=100_000))
289
+ object.__setattr__(self, "risk_flags", _strings(self.risk_flags, code="risk_flags_invalid", maximum=MAX_REASON_COUNT))
290
+ object.__setattr__(self, "context_requirements", _strings(self.context_requirements, code="context_requirements_invalid", maximum=MAX_REASON_COUNT))
291
+ object.__setattr__(self, "verification_requirements", _strings(self.verification_requirements, code="verification_requirements_invalid", maximum=MAX_REASON_COUNT))
292
+
293
+
294
+ @dataclass(frozen=True)
295
+ class ModelProfile(PublicRecord):
296
+ model_id: str
297
+ profile_version: str
298
+ capabilities: tuple[str, ...]
299
+ context_window_tokens: int
300
+ supported_efforts: tuple[Effort, ...]
301
+ provisional: bool
302
+
303
+ _public_fields = (
304
+ "model_id",
305
+ "profile_version",
306
+ "capabilities",
307
+ "context_window_tokens",
308
+ "supported_efforts",
309
+ "provisional",
310
+ )
311
+
312
+ def __post_init__(self) -> None:
313
+ object.__setattr__(self, "model_id", _bounded_identifier(self.model_id, code="model_id_invalid"))
314
+ object.__setattr__(self, "profile_version", _bounded_identifier(self.profile_version, code="profile_version_invalid"))
315
+ object.__setattr__(self, "capabilities", _strings(self.capabilities, code="capabilities_invalid", maximum=32))
316
+ object.__setattr__(self, "context_window_tokens", _bounded_integer(self.context_window_tokens, code="context_window_tokens_invalid", minimum=1, maximum=10_000_000))
317
+ if not isinstance(self.supported_efforts, (tuple, list)) or not self.supported_efforts:
318
+ raise ValueError("effort_invalid")
319
+ efforts = tuple(_enum(item, Effort, code="effort_invalid") for item in self.supported_efforts)
320
+ if len(set(efforts)) != len(efforts):
321
+ raise ValueError("effort_invalid")
322
+ if not isinstance(self.provisional, bool):
323
+ raise ValueError("provisional_invalid")
324
+ object.__setattr__(self, "supported_efforts", efforts)
325
+
326
+
327
+ @dataclass(frozen=True)
328
+ class CliIdentity(PublicRecord):
329
+ provider: ProviderId
330
+ executable_sha256: str
331
+ cli_version: str
332
+ adapter_protocol_version: str
333
+
334
+ _public_fields = (
335
+ "provider",
336
+ "executable_sha256",
337
+ "cli_version",
338
+ "adapter_protocol_version",
339
+ )
340
+
341
+ def __post_init__(self) -> None:
342
+ object.__setattr__(self, "provider", _enum(self.provider, ProviderId, code="provider_invalid"))
343
+ object.__setattr__(
344
+ self,
345
+ "executable_sha256",
346
+ _hash(self.executable_sha256, code="executable_sha256_invalid"),
347
+ )
348
+ object.__setattr__(
349
+ self, "cli_version", _semantic_version(self.cli_version, code="cli_version_invalid")
350
+ )
351
+ object.__setattr__(
352
+ self,
353
+ "adapter_protocol_version",
354
+ _semantic_version(
355
+ self.adapter_protocol_version, code="adapter_protocol_version_invalid"
356
+ ),
357
+ )
358
+
359
+
360
+ @dataclass(frozen=True)
361
+ class CapabilityProfile(PublicRecord):
362
+ provider: ProviderId
363
+ requested_model: str
364
+ effective_model: str
365
+ profile_version: str
366
+ capabilities: tuple[str, ...]
367
+ context_window_tokens: int
368
+ supported_efforts: tuple[Effort, ...]
369
+ risk_ceiling: RiskTier
370
+ permission_mode: PermissionMode
371
+
372
+ _public_fields = (
373
+ "provider",
374
+ "requested_model",
375
+ "effective_model",
376
+ "profile_version",
377
+ "capabilities",
378
+ "context_window_tokens",
379
+ "supported_efforts",
380
+ "risk_ceiling",
381
+ "permission_mode",
382
+ )
383
+
384
+ def __post_init__(self) -> None:
385
+ object.__setattr__(self, "provider", _enum(self.provider, ProviderId, code="provider_invalid"))
386
+ object.__setattr__(
387
+ self,
388
+ "requested_model",
389
+ _bounded_identifier(self.requested_model, code="requested_model_invalid"),
390
+ )
391
+ object.__setattr__(
392
+ self,
393
+ "effective_model",
394
+ _bounded_identifier(self.effective_model, code="effective_model_invalid"),
395
+ )
396
+ object.__setattr__(
397
+ self,
398
+ "profile_version",
399
+ _bounded_identifier(self.profile_version, code="profile_version_invalid"),
400
+ )
401
+ capabilities = _strings(self.capabilities, code="capabilities_invalid", maximum=32)
402
+ if not capabilities or len(set(capabilities)) != len(capabilities):
403
+ raise ValueError("capabilities_invalid")
404
+ object.__setattr__(self, "capabilities", tuple(sorted(capabilities)))
405
+ object.__setattr__(
406
+ self,
407
+ "context_window_tokens",
408
+ _bounded_integer(
409
+ self.context_window_tokens,
410
+ code="context_window_tokens_invalid",
411
+ minimum=1,
412
+ maximum=10_000_000,
413
+ ),
414
+ )
415
+ if not isinstance(self.supported_efforts, (tuple, list)) or not self.supported_efforts:
416
+ raise ValueError("effort_invalid")
417
+ efforts = tuple(_enum(item, Effort, code="effort_invalid") for item in self.supported_efforts)
418
+ if len(set(efforts)) != len(efforts):
419
+ raise ValueError("effort_invalid")
420
+ object.__setattr__(self, "supported_efforts", tuple(sorted(efforts, key=lambda item: item.value)))
421
+ object.__setattr__(
422
+ self, "risk_ceiling", _enum(self.risk_ceiling, RiskTier, code="risk_ceiling_invalid")
423
+ )
424
+ object.__setattr__(
425
+ self,
426
+ "permission_mode",
427
+ _enum(self.permission_mode, PermissionMode, code="permission_mode_invalid"),
428
+ )
429
+
430
+
431
+ @dataclass(frozen=True)
432
+ class CapabilitySnapshot(PublicRecord):
433
+ schema_version: int
434
+ verified_at: int
435
+ expires_at: int
436
+ identity: CliIdentity
437
+ profile: CapabilityProfile
438
+
439
+ _public_fields = ("schema_version", "verified_at", "expires_at", "identity", "profile")
440
+
441
+ def __post_init__(self) -> None:
442
+ object.__setattr__(
443
+ self,
444
+ "schema_version",
445
+ _bounded_integer(self.schema_version, code="snapshot_schema_invalid", minimum=1, maximum=10),
446
+ )
447
+ verified = _bounded_integer(self.verified_at, code="snapshot_time_invalid")
448
+ expires = _bounded_integer(self.expires_at, code="snapshot_time_invalid")
449
+ if expires <= verified:
450
+ raise ValueError("snapshot_time_invalid")
451
+ if not isinstance(self.identity, CliIdentity) or not isinstance(self.profile, CapabilityProfile):
452
+ raise ValueError("snapshot_identity_invalid")
453
+ if self.identity.provider is not self.profile.provider:
454
+ raise ValueError("snapshot_identity_invalid")
455
+
456
+ def to_dict(self) -> dict[str, Any]:
457
+ return {
458
+ "schema_version": self.schema_version,
459
+ "verified_at": self.verified_at,
460
+ "expires_at": self.expires_at,
461
+ "identity": self.identity.to_dict(),
462
+ "profile": self.profile.to_dict(),
463
+ }
464
+
465
+ @property
466
+ def digest(self) -> str:
467
+ payload = json.dumps(
468
+ self.to_dict(), sort_keys=True, separators=(",", ":"), ensure_ascii=True
469
+ ).encode("utf-8")
470
+ return hashlib.sha256(payload).hexdigest()
471
+
472
+
473
+ @dataclass(frozen=True)
474
+ class RoutingDecision(PublicRecord):
475
+ decision_id: str
476
+ task_id: str
477
+ selected_model: str | None
478
+ effort: Effort | None
479
+ score: int | float
480
+ confidence: float
481
+ alternatives: tuple[str, ...]
482
+ reasons: tuple[str, ...]
483
+ policy_version: str
484
+ evidence_version: str
485
+
486
+ _public_fields = (
487
+ "decision_id",
488
+ "task_id",
489
+ "selected_model",
490
+ "effort",
491
+ "score",
492
+ "confidence",
493
+ "alternatives",
494
+ "reasons",
495
+ "policy_version",
496
+ "evidence_version",
497
+ )
498
+
499
+ def __post_init__(self) -> None:
500
+ object.__setattr__(self, "decision_id", _bounded_identifier(self.decision_id, code="decision_id_invalid"))
501
+ object.__setattr__(self, "task_id", _bounded_identifier(self.task_id, code="task_id_invalid"))
502
+ if self.selected_model is not None:
503
+ object.__setattr__(self, "selected_model", _bounded_identifier(self.selected_model, code="model_id_invalid"))
504
+ effort = None if self.effort is None else _enum(self.effort, Effort, code="effort_invalid")
505
+ if isinstance(self.score, bool) or not isinstance(self.score, (int, float)) or not math.isfinite(self.score) or not 0 <= self.score <= 1_000:
506
+ raise ValueError("score_invalid")
507
+ if isinstance(self.confidence, bool) or not isinstance(self.confidence, (int, float)) or not math.isfinite(self.confidence) or not 0 <= self.confidence <= 1:
508
+ raise ValueError("confidence_invalid")
509
+ object.__setattr__(self, "effort", effort)
510
+ object.__setattr__(self, "alternatives", _strings(self.alternatives, code="alternatives_invalid", maximum=MAX_ALTERNATIVES))
511
+ object.__setattr__(self, "reasons", _strings(self.reasons, code="reasons_invalid", maximum=MAX_REASON_COUNT))
512
+ object.__setattr__(self, "policy_version", _bounded_identifier(self.policy_version, code="policy_version_invalid"))
513
+ object.__setattr__(self, "evidence_version", _bounded_identifier(self.evidence_version, code="evidence_version_invalid"))
514
+
515
+
516
+ @dataclass(frozen=True)
517
+ class ApprovalManifest(PublicRecord):
518
+ approval_id: str
519
+ task_id: str
520
+ decision_id: str
521
+ graph_fingerprint: str
522
+ context_manifest_hash: str
523
+ inventory_digest: str
524
+ model_id: str
525
+ effort: Effort
526
+ max_input_tokens: int
527
+ max_output_tokens: int
528
+ policy_version: str
529
+ issued_at: int
530
+ expires_at: int
531
+ nonce: str
532
+
533
+ _public_fields = (
534
+ "approval_id",
535
+ "task_id",
536
+ "decision_id",
537
+ "graph_fingerprint",
538
+ "context_manifest_hash",
539
+ "inventory_digest",
540
+ "model_id",
541
+ "effort",
542
+ "max_input_tokens",
543
+ "max_output_tokens",
544
+ "policy_version",
545
+ "issued_at",
546
+ "expires_at",
547
+ "nonce",
548
+ )
549
+
550
+ def __post_init__(self) -> None:
551
+ for name in ("approval_id", "task_id", "decision_id", "model_id", "policy_version", "nonce"):
552
+ object.__setattr__(self, name, _bounded_identifier(getattr(self, name), code=f"{name}_invalid"))
553
+ object.__setattr__(self, "graph_fingerprint", _hash(self.graph_fingerprint, code="graph_fingerprint_invalid"))
554
+ object.__setattr__(self, "context_manifest_hash", _hash(self.context_manifest_hash, code="context_manifest_hash_invalid"))
555
+ object.__setattr__(self, "inventory_digest", _hash(self.inventory_digest, code="inventory_digest_invalid"))
556
+ object.__setattr__(self, "effort", _enum(self.effort, Effort, code="effort_invalid"))
557
+ object.__setattr__(self, "max_input_tokens", _bounded_integer(self.max_input_tokens, code="max_input_tokens_invalid", minimum=1, maximum=262_144))
558
+ object.__setattr__(self, "max_output_tokens", _bounded_integer(self.max_output_tokens, code="max_output_tokens_invalid", minimum=1, maximum=32_768))
559
+ issued = _bounded_integer(self.issued_at, code="issued_at_invalid")
560
+ expires = _bounded_integer(self.expires_at, code="expires_at_invalid")
561
+ if expires <= issued:
562
+ raise ValueError("expires_at_invalid")
563
+
564
+
565
+ @dataclass(frozen=True)
566
+ class CliApprovalManifest(PublicRecord):
567
+ approval_id: str
568
+ task_id: str
569
+ decision_id: str
570
+ provider: ProviderId
571
+ requested_model: str
572
+ effective_model: str
573
+ effort: Effort
574
+ cli_executable_sha256: str
575
+ cli_version: str
576
+ adapter_protocol_version: str
577
+ capability_snapshot_digest: str
578
+ graph_fingerprint: str
579
+ context_manifest_hash: str
580
+ repository_commit: str
581
+ worktree_id: str
582
+ permission_mode: PermissionMode
583
+ max_input_tokens: int
584
+ max_output_tokens: int
585
+ policy_version: str
586
+ issued_at: int
587
+ expires_at: int
588
+ nonce: str
589
+
590
+ _public_fields = (
591
+ "approval_id",
592
+ "task_id",
593
+ "decision_id",
594
+ "provider",
595
+ "requested_model",
596
+ "effective_model",
597
+ "effort",
598
+ "cli_executable_sha256",
599
+ "cli_version",
600
+ "adapter_protocol_version",
601
+ "capability_snapshot_digest",
602
+ "graph_fingerprint",
603
+ "context_manifest_hash",
604
+ "repository_commit",
605
+ "worktree_id",
606
+ "permission_mode",
607
+ "max_input_tokens",
608
+ "max_output_tokens",
609
+ "policy_version",
610
+ "issued_at",
611
+ "expires_at",
612
+ "nonce",
613
+ )
614
+
615
+ def __post_init__(self) -> None:
616
+ for name in ("approval_id", "task_id", "decision_id", "policy_version", "nonce"):
617
+ object.__setattr__(
618
+ self, name, _bounded_identifier(getattr(self, name), code=f"{name}_invalid")
619
+ )
620
+ object.__setattr__(self, "provider", _enum(self.provider, ProviderId, code="provider_invalid"))
621
+ for name in ("requested_model", "effective_model", "worktree_id"):
622
+ object.__setattr__(
623
+ self, name, _bounded_identifier(getattr(self, name), code=f"{name}_invalid")
624
+ )
625
+ object.__setattr__(self, "effort", _enum(self.effort, Effort, code="effort_invalid"))
626
+ object.__setattr__(
627
+ self,
628
+ "cli_executable_sha256",
629
+ _hash(self.cli_executable_sha256, code="cli_executable_sha256_invalid"),
630
+ )
631
+ object.__setattr__(
632
+ self, "cli_version", _semantic_version(self.cli_version, code="cli_version_invalid")
633
+ )
634
+ object.__setattr__(
635
+ self,
636
+ "adapter_protocol_version",
637
+ _semantic_version(
638
+ self.adapter_protocol_version, code="adapter_protocol_version_invalid"
639
+ ),
640
+ )
641
+ for name in (
642
+ "capability_snapshot_digest",
643
+ "graph_fingerprint",
644
+ "context_manifest_hash",
645
+ ):
646
+ object.__setattr__(self, name, _hash(getattr(self, name), code=f"{name}_invalid"))
647
+ object.__setattr__(
648
+ self,
649
+ "repository_commit",
650
+ _git_object(self.repository_commit, code="repository_commit_invalid"),
651
+ )
652
+ object.__setattr__(
653
+ self,
654
+ "permission_mode",
655
+ _enum(self.permission_mode, PermissionMode, code="permission_mode_invalid"),
656
+ )
657
+ object.__setattr__(
658
+ self,
659
+ "max_input_tokens",
660
+ _bounded_integer(
661
+ self.max_input_tokens,
662
+ code="max_input_tokens_invalid",
663
+ minimum=1,
664
+ maximum=262_144,
665
+ ),
666
+ )
667
+ object.__setattr__(
668
+ self,
669
+ "max_output_tokens",
670
+ _bounded_integer(
671
+ self.max_output_tokens,
672
+ code="max_output_tokens_invalid",
673
+ minimum=1,
674
+ maximum=32_768,
675
+ ),
676
+ )
677
+ issued = _bounded_integer(self.issued_at, code="issued_at_invalid")
678
+ expires = _bounded_integer(self.expires_at, code="expires_at_invalid")
679
+ if expires <= issued:
680
+ raise ValueError("expires_at_invalid")
681
+
682
+
683
+ @dataclass(frozen=True)
684
+ class ExecutionReceipt(PublicRecord):
685
+ execution_id: str
686
+ approval_id: str
687
+ model_id: str
688
+ effort: Effort
689
+ outcome: ExecutionOutcome
690
+ input_tokens: int | None
691
+ output_tokens: int | None
692
+ latency_ms: int
693
+ prompt_hash: str
694
+ response_hash: str | None
695
+ failure_reason: FailureReason | None
696
+ provider: ProviderId | None = None
697
+ effective_model: str | None = None
698
+ cli_version: str | None = None
699
+ changed_file_count: int | None = None
700
+ changed_byte_count: int | None = None
701
+ validation_outcome: str | None = None
702
+
703
+ _public_fields = (
704
+ "execution_id",
705
+ "approval_id",
706
+ "model_id",
707
+ "effort",
708
+ "outcome",
709
+ "input_tokens",
710
+ "output_tokens",
711
+ "latency_ms",
712
+ "prompt_hash",
713
+ "response_hash",
714
+ "failure_reason",
715
+ "provider",
716
+ "effective_model",
717
+ "cli_version",
718
+ "changed_file_count",
719
+ "changed_byte_count",
720
+ "validation_outcome",
721
+ )
722
+
723
+ def __post_init__(self) -> None:
724
+ for name in ("execution_id", "approval_id", "model_id"):
725
+ object.__setattr__(self, name, _bounded_identifier(getattr(self, name), code=f"{name}_invalid"))
726
+ object.__setattr__(self, "effort", _enum(self.effort, Effort, code="effort_invalid"))
727
+ object.__setattr__(self, "outcome", _enum(self.outcome, ExecutionOutcome, code="outcome_invalid"))
728
+ for name in ("input_tokens", "output_tokens"):
729
+ value = getattr(self, name)
730
+ if value is not None:
731
+ object.__setattr__(self, name, _bounded_integer(value, code=f"{name}_invalid", maximum=10_000_000))
732
+ object.__setattr__(self, "latency_ms", _bounded_integer(self.latency_ms, code="latency_ms_invalid", maximum=86_400_000))
733
+ object.__setattr__(self, "prompt_hash", _hash(self.prompt_hash, code="prompt_hash_invalid"))
734
+ if self.response_hash is not None:
735
+ object.__setattr__(self, "response_hash", _hash(self.response_hash, code="response_hash_invalid"))
736
+ if self.failure_reason is not None:
737
+ object.__setattr__(self, "failure_reason", _enum(self.failure_reason, FailureReason, code="failure_reason_invalid"))
738
+ if self.provider is not None:
739
+ object.__setattr__(self, "provider", _enum(self.provider, ProviderId, code="provider_invalid"))
740
+ if self.effective_model is not None:
741
+ object.__setattr__(
742
+ self,
743
+ "effective_model",
744
+ _bounded_identifier(self.effective_model, code="effective_model_invalid"),
745
+ )
746
+ if self.cli_version is not None:
747
+ object.__setattr__(
748
+ self, "cli_version", _semantic_version(self.cli_version, code="cli_version_invalid")
749
+ )
750
+ for name in ("changed_file_count", "changed_byte_count"):
751
+ value = getattr(self, name)
752
+ if value is not None:
753
+ object.__setattr__(
754
+ self, name, _bounded_integer(value, code=f"{name}_invalid", maximum=10_000_000)
755
+ )
756
+ if self.validation_outcome is not None and self.validation_outcome not in _VALIDATION_OUTCOMES:
757
+ raise ValueError("validation_outcome_invalid")
758
+
759
+
760
+ @dataclass(frozen=True)
761
+ class VerifiedOutcome(PublicRecord):
762
+ outcome_id: str
763
+ execution_id: str
764
+ provenance: EvidenceProvenance
765
+ build_passed: bool | None
766
+ tests_passed: bool | None
767
+ security_passed: bool | None
768
+ human_accepted: bool | None
769
+ repair_count: int
770
+ escalated: bool
771
+ reverted: bool
772
+ severe_failure: bool
773
+ recorded_at: int
774
+
775
+ _public_fields = (
776
+ "outcome_id",
777
+ "execution_id",
778
+ "provenance",
779
+ "build_passed",
780
+ "tests_passed",
781
+ "security_passed",
782
+ "human_accepted",
783
+ "repair_count",
784
+ "escalated",
785
+ "reverted",
786
+ "severe_failure",
787
+ "recorded_at",
788
+ )
789
+
790
+ def __post_init__(self) -> None:
791
+ object.__setattr__(self, "outcome_id", _bounded_identifier(self.outcome_id, code="outcome_id_invalid"))
792
+ object.__setattr__(self, "execution_id", _bounded_identifier(self.execution_id, code="execution_id_invalid"))
793
+ object.__setattr__(self, "provenance", _enum(self.provenance, EvidenceProvenance, code="provenance_invalid"))
794
+ for name in ("build_passed", "tests_passed", "security_passed", "human_accepted"):
795
+ value = getattr(self, name)
796
+ if value is not None and not isinstance(value, bool):
797
+ raise ValueError(f"{name}_invalid")
798
+ object.__setattr__(self, "repair_count", _bounded_integer(self.repair_count, code="repair_count_invalid", maximum=1_000))
799
+ for name in ("escalated", "reverted", "severe_failure"):
800
+ if not isinstance(getattr(self, name), bool):
801
+ raise ValueError(f"{name}_invalid")
802
+ object.__setattr__(self, "recorded_at", _bounded_integer(self.recorded_at, code="recorded_at_invalid"))