millforge 0.1.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 (116) hide show
  1. millforge/__init__.py +1174 -0
  2. millforge/_forge/LICENSE +21 -0
  3. millforge/_forge/PROVENANCE.json +295 -0
  4. millforge/_forge/UPDATE_POLICY.md +24 -0
  5. millforge/_forge/__init__.py +14 -0
  6. millforge/_forge/adapter.py +2232 -0
  7. millforge/_forge/base_runner.py +121 -0
  8. millforge/_forge/clients/__init__.py +10 -0
  9. millforge/_forge/clients/base.py +200 -0
  10. millforge/_forge/context/__init__.py +23 -0
  11. millforge/_forge/context/manager.py +178 -0
  12. millforge/_forge/context/strategies.py +335 -0
  13. millforge/_forge/core/__init__.py +16 -0
  14. millforge/_forge/core/inference.py +433 -0
  15. millforge/_forge/core/messages.py +119 -0
  16. millforge/_forge/core/runner.py +479 -0
  17. millforge/_forge/core/steps.py +108 -0
  18. millforge/_forge/core/workflow.py +400 -0
  19. millforge/_forge/errors.py +222 -0
  20. millforge/_forge/guardrails/__init__.py +21 -0
  21. millforge/_forge/guardrails/error_tracker.py +71 -0
  22. millforge/_forge/guardrails/guardrails.py +194 -0
  23. millforge/_forge/guardrails/nudge.py +47 -0
  24. millforge/_forge/guardrails/response_validator.py +119 -0
  25. millforge/_forge/guardrails/step_enforcer.py +183 -0
  26. millforge/_forge/prompts/__init__.py +16 -0
  27. millforge/_forge/prompts/nudges.py +95 -0
  28. millforge/_forge/prompts/templates.py +285 -0
  29. millforge/_version.py +3 -0
  30. millforge/artifacts.py +570 -0
  31. millforge/base/__init__.py +97 -0
  32. millforge/base/composition.py +402 -0
  33. millforge/base/context.py +285 -0
  34. millforge/base/harness.py +138 -0
  35. millforge/base/identity.py +465 -0
  36. millforge/base/options.py +34 -0
  37. millforge/base/platform.py +17 -0
  38. millforge/base/prompt.py +317 -0
  39. millforge/base/runner.py +546 -0
  40. millforge/compiled_plan.py +970 -0
  41. millforge/compiler/__init__.py +231 -0
  42. millforge/compiler/artifact_validation.py +257 -0
  43. millforge/compiler/canonicalization.py +169 -0
  44. millforge/compiler/capabilities.py +66 -0
  45. millforge/compiler/catalogs.py +500 -0
  46. millforge/compiler/diagnostics.py +491 -0
  47. millforge/compiler/graph.py +678 -0
  48. millforge/compiler/lowering.py +198 -0
  49. millforge/compiler/output.py +692 -0
  50. millforge/compiler/parsing.py +1424 -0
  51. millforge/compiler/requests.py +1180 -0
  52. millforge/compiler/schema_validation.py +272 -0
  53. millforge/compiler/semantic.py +490 -0
  54. millforge/compiler/service.py +448 -0
  55. millforge/compiler/source.py +375 -0
  56. millforge/compiler/validators.py +184 -0
  57. millforge/connectors/__init__.py +95 -0
  58. millforge/connectors/admission.py +801 -0
  59. millforge/connectors/broker.py +202 -0
  60. millforge/connectors/contracts.py +1159 -0
  61. millforge/connectors/diagnostics.py +189 -0
  62. millforge/connectors/fake.py +66 -0
  63. millforge/connectors/runtime.py +236 -0
  64. millforge/contracts.py +2860 -0
  65. millforge/custom_tools/__init__.py +67 -0
  66. millforge/custom_tools/compiler.py +724 -0
  67. millforge/custom_tools/contracts.py +1093 -0
  68. millforge/custom_tools/diagnostics.py +205 -0
  69. millforge/eval_artifacts.py +952 -0
  70. millforge/eval_boundary.py +2435 -0
  71. millforge/eval_fixtures/__init__.py +1 -0
  72. millforge/eval_fixtures/default_pack/__init__.py +1 -0
  73. millforge/eval_fixtures/default_pack/fixtures/fixture.08a.bug_diagnosis.traceback.v1.json +52 -0
  74. millforge/eval_fixtures/default_pack/fixtures/fixture.08a.direct_edit.import_sort.v1.json +52 -0
  75. millforge/eval_fixtures/default_pack/fixtures/fixture.08a.evidence_discipline.no_source_change.v1.json +51 -0
  76. millforge/eval_fixtures/default_pack/fixtures/fixture.08a.false_closure.visible_green.v1.json +52 -0
  77. millforge/eval_fixtures/default_pack/fixtures/fixture.08a.multi_file.api_contract.v1.json +54 -0
  78. millforge/eval_fixtures/default_pack/fixtures/fixture.08a.recovery.malformed_artifact.v1.json +54 -0
  79. millforge/eval_fixtures/default_pack/manifest.json +12 -0
  80. millforge/eval_modes.py +1282 -0
  81. millforge/eval_presets.py +1398 -0
  82. millforge/eval_reports.py +2517 -0
  83. millforge/eval_suite.py +2429 -0
  84. millforge/eval_trials.py +2632 -0
  85. millforge/eval_workflow.py +794 -0
  86. millforge/exceptions.py +122 -0
  87. millforge/model_backend.py +2098 -0
  88. millforge/protocols.py +340 -0
  89. millforge/py.typed +0 -0
  90. millforge/runtime.py +1791 -0
  91. millforge/testing/__init__.py +1089 -0
  92. millforge/tools/__init__.py +83 -0
  93. millforge/tools/builtin_runtime.py +1339 -0
  94. millforge/tools/builtins.py +773 -0
  95. millforge/tools/execution.py +1545 -0
  96. millforge/tools/path_policy.py +155 -0
  97. millforge/tools/pi_compat/PI_LICENSE +21 -0
  98. millforge/tools/pi_compat/PROVENANCE.json +55 -0
  99. millforge/tools/pi_compat/UPDATE_POLICY.md +36 -0
  100. millforge/tools/pi_compat/__init__.py +34 -0
  101. millforge/tools/pi_compat/contracts.py +49 -0
  102. millforge/tools/pi_compat/editing.py +390 -0
  103. millforge/tools/pi_compat/mutations.py +57 -0
  104. millforge/tools/pi_compat/operations.py +401 -0
  105. millforge/tools/pi_compat/paths.py +155 -0
  106. millforge/tools/pi_compat/process.py +1375 -0
  107. millforge/tools/pi_compat/search.py +738 -0
  108. millforge/tools/pi_compat/truncation.py +267 -0
  109. millforge/tools/pi_compat_catalog.py +396 -0
  110. millforge/tools/pi_compat_runtime.py +460 -0
  111. millforge/tools/registry.py +553 -0
  112. millforge/tools/results.py +533 -0
  113. millforge-0.1.0.dist-info/METADATA +844 -0
  114. millforge-0.1.0.dist-info/RECORD +116 -0
  115. millforge-0.1.0.dist-info/WHEEL +4 -0
  116. millforge-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,970 @@
1
+ """02B compiled harness plan and runtime trace contracts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import math
8
+ import re
9
+ from enum import Enum
10
+ from pathlib import Path
11
+ from typing import Any, Literal
12
+
13
+ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
14
+
15
+ _SHA256_RE = re.compile(r"[0-9a-f]{64}")
16
+ JsonObject = dict[str, Any]
17
+ ConnectorApprovalDecision = Literal[
18
+ "approved",
19
+ "forbidden",
20
+ "pending",
21
+ "missing",
22
+ "wrong_stage",
23
+ "wrong_run",
24
+ "wrong_scope",
25
+ "expired_or_stale",
26
+ ]
27
+ ConnectorApprovalPolicyValue = Literal[
28
+ "none",
29
+ "millrace_explicit",
30
+ "operator_out_of_band",
31
+ "forbidden",
32
+ ]
33
+ ConnectorDriftDecision = Literal["passed", "failed", "not_reached"]
34
+
35
+
36
+ class SideEffectClass(str, Enum):
37
+ """Closed side-effect classification for compiled nodes."""
38
+
39
+ READ_ONLY = "read_only"
40
+ ARTIFACT_WRITE = "artifact_write"
41
+ WORKSPACE_WRITE = "workspace_write"
42
+ PROCESS_EXECUTION = "process_execution"
43
+ NETWORK_READ = "network_read"
44
+ NETWORK_WRITE = "network_write"
45
+ TERMINAL = "terminal"
46
+
47
+
48
+ class IdempotencyClass(str, Enum):
49
+ """Closed idempotency classification for compiled nodes."""
50
+
51
+ IDEMPOTENT = "idempotent"
52
+ IDEMPOTENT_WITH_KEY = "idempotent_with_key"
53
+ NON_IDEMPOTENT = "non_idempotent"
54
+ UNKNOWN = "unknown"
55
+
56
+
57
+ class ToolTraceDecision(str, Enum):
58
+ """Closed prerequisite and capability decision values for tool traces."""
59
+
60
+ ALLOWED = "allowed"
61
+ DENIED = "denied"
62
+
63
+
64
+ class ToolExecutionStatus(str, Enum):
65
+ """Closed tool execution status values used in tool traces."""
66
+
67
+ NOT_EXECUTED = "not_executed"
68
+ SUCCESS = "success"
69
+ SOFT_FAILURE = "soft_failure"
70
+ HARD_FAILURE = "hard_failure"
71
+ CANCELLED = "cancelled"
72
+ TIMED_OUT = "timed_out"
73
+ AMBIGUOUS = "ambiguous"
74
+
75
+
76
+ class ToolTraceSideEffectClass(str, Enum):
77
+ """Closed side-effect classification for persisted tool traces."""
78
+
79
+ READ_ONLY = "read_only"
80
+ ARTIFACT_WRITE = "artifact_write"
81
+ WORKSPACE_WRITE = "workspace_write"
82
+ PROCESS_EXECUTION = "process_execution"
83
+ NETWORK_READ = "network_read"
84
+ NETWORK_WRITE = "network_write"
85
+ TERMINAL = "terminal"
86
+
87
+
88
+ class ToolTraceIdempotency(str, Enum):
89
+ """Closed idempotency classification for persisted tool traces."""
90
+
91
+ IDEMPOTENT = "idempotent"
92
+ IDEMPOTENT_WITH_KEY = "idempotent_with_key"
93
+ NON_IDEMPOTENT = "non_idempotent"
94
+ UNKNOWN = "unknown"
95
+
96
+
97
+ class SideEffectCertainty(str, Enum):
98
+ """Closed side-effect certainty values for persisted tool traces."""
99
+
100
+ NOT_ATTEMPTED = "not_attempted"
101
+ CONFIRMED_ABSENT = "confirmed_absent"
102
+ CONFIRMED_COMPLETE = "confirmed_complete"
103
+ ROLLED_BACK = "rolled_back"
104
+ COMPLETION_UNKNOWN = "completion_unknown"
105
+
106
+
107
+ class SessionEventType(str, Enum):
108
+ """Closed runtime session event values from the 02B contract."""
109
+
110
+ RUNTIME_RECEIVED = "runtime_received"
111
+ RUNTIME_VERIFIED = "runtime_verified"
112
+ BACKEND_CONSTRUCTED = "backend_constructed"
113
+ BINDING_REJECTED = "binding_rejected"
114
+ COMPILED_HARNESS_INVALID = "compiled_harness_invalid"
115
+ BACKEND_FAILED = "backend_failed"
116
+ SESSION_STARTED = "session_started"
117
+ WORKFLOW_CONSTRUCTED = "workflow_constructed"
118
+ MODEL_REQUEST_STARTED = "model_request_started"
119
+ MODEL_REQUEST_COMPLETED = "model_request_completed"
120
+ MODEL_REQUEST_FAILED = "model_request_failed"
121
+ CORRECTION_ISSUED = "correction_issued"
122
+ PREMATURE_TERMINAL_REJECTED = "premature_terminal_rejected"
123
+ PREREQUISITE_REJECTED = "prerequisite_rejected"
124
+ TOOL_STARTED = "tool_started"
125
+ TOOL_COMPLETED = "tool_completed"
126
+ TOOL_FAILED = "tool_failed"
127
+ CONTEXT_COMPACTED = "context_compacted"
128
+ TERMINAL_INTENT_ACCEPTED = "terminal_intent_accepted"
129
+ TERMINAL_INTENT_REJECTED = "terminal_intent_rejected"
130
+ FINALIZATION_STARTED = "finalization_started"
131
+ FINALIZATION_COMPLETED = "finalization_completed"
132
+ FINALIZATION_FAILED = "finalization_failed"
133
+ BUDGET_EXHAUSTED = "budget_exhausted"
134
+ TIMED_OUT = "timed_out"
135
+ CANCELLED = "cancelled"
136
+ INTERNAL_FAILED = "internal_failed"
137
+
138
+
139
+ def _nonblank(value: str, field_name: str) -> str:
140
+ if not value.strip():
141
+ raise ValueError(f"{field_name} must be a non-empty string")
142
+ return value
143
+
144
+
145
+ def _unique(values: tuple[str, ...], field_name: str) -> None:
146
+ if len(set(values)) != len(values):
147
+ raise ValueError(f"{field_name} values must be unique")
148
+
149
+
150
+ def _validate_sha256(value: str, field_name: str) -> str:
151
+ if not _SHA256_RE.fullmatch(value):
152
+ raise ValueError(f"{field_name} must be exactly 64 lowercase hex characters")
153
+ return value
154
+
155
+
156
+ class DiagnosticField(BaseModel):
157
+ """Immutable bounded scalar diagnostic field."""
158
+
159
+ model_config = ConfigDict(extra="forbid", frozen=True)
160
+
161
+ key: str = Field(description="Diagnostic field key")
162
+ value: str | int | float | bool | None = Field(
163
+ description="Bounded JSON scalar diagnostic field value"
164
+ )
165
+
166
+ @field_validator("key")
167
+ @classmethod
168
+ def _key_nonblank(cls, value: str) -> str:
169
+ return _nonblank(value, "key")
170
+
171
+
172
+ class StageIdentity(BaseModel):
173
+ """Immutable provider-local identity of a compiled harness stage.
174
+
175
+ On an execution request this identifies the stage that the selected
176
+ Millforge harness admits. It is not the caller's workflow plane, node,
177
+ route, dispatch identity, or authority.
178
+ """
179
+
180
+ model_config = ConfigDict(extra="forbid", frozen=True)
181
+
182
+ plane: Literal["execution", "planning", "learning"] = Field(
183
+ description="Provider-local plane the compiled harness stage belongs to"
184
+ )
185
+ node_id: str = Field(description="Provider-local compiled stage node identifier")
186
+ stage_kind_id: str = Field(
187
+ description="Provider-local compiled stage kind identifier"
188
+ )
189
+
190
+ @field_validator("node_id", "stage_kind_id")
191
+ @classmethod
192
+ def _strings_nonblank(cls, value: str, info: Any) -> str:
193
+ return _nonblank(value, info.field_name)
194
+
195
+
196
+ class CompilerIdentity(BaseModel):
197
+ """Identity of the compiler that produced the compiled plan."""
198
+
199
+ model_config = ConfigDict(extra="forbid", frozen=True)
200
+
201
+ name: str
202
+ version: str
203
+ build_id: str
204
+
205
+ @field_validator("name", "version", "build_id")
206
+ @classmethod
207
+ def _strings_nonblank(cls, value: str, info: Any) -> str:
208
+ return _nonblank(value, info.field_name)
209
+
210
+
211
+ class CompiledModelProfile(BaseModel):
212
+ """Logical model profile reference only."""
213
+
214
+ model_config = ConfigDict(extra="forbid", frozen=True)
215
+
216
+ profile_id: str
217
+
218
+ @field_validator("profile_id")
219
+ @classmethod
220
+ def _profile_id_nonblank(cls, value: str) -> str:
221
+ return _nonblank(value, "profile_id")
222
+
223
+
224
+ class CompiledPromptPolicy(BaseModel):
225
+ """Deterministic prompt policy included in the compiled plan."""
226
+
227
+ model_config = ConfigDict(extra="forbid", frozen=True)
228
+
229
+ policy_id: str
230
+ system_instructions: str
231
+ include_request_context: bool
232
+
233
+ @field_validator("policy_id", "system_instructions")
234
+ @classmethod
235
+ def _strings_nonblank(cls, value: str, info: Any) -> str:
236
+ return _nonblank(value, info.field_name)
237
+
238
+
239
+ class CompiledBudgetPolicy(BaseModel):
240
+ """Closed correction and enforcement budget policy."""
241
+
242
+ model_config = ConfigDict(extra="forbid", frozen=True)
243
+
244
+ max_iterations: int = Field(gt=0)
245
+ max_validation_retries: int = Field(gt=0)
246
+ max_tool_errors: int = Field(gt=0)
247
+ max_prerequisite_violations: int = Field(gt=0)
248
+ max_premature_terminal_attempts: int = Field(gt=0)
249
+
250
+
251
+ class CompiledContextPolicy(BaseModel):
252
+ """Context policy consumed by the deterministic runtime."""
253
+
254
+ model_config = ConfigDict(extra="forbid", frozen=True)
255
+
256
+ strategy_id: Literal["forge.tiered.v1"]
257
+ budget_tokens: int = Field(gt=0)
258
+ keep_recent_iterations: int = Field(ge=0)
259
+ phase_thresholds: tuple[float, float, float]
260
+
261
+ @field_validator("phase_thresholds")
262
+ @classmethod
263
+ def _thresholds_valid(
264
+ cls, value: tuple[float, float, float]
265
+ ) -> tuple[float, float, float]:
266
+ if any(not math.isfinite(item) for item in value):
267
+ raise ValueError("phase_thresholds must be finite")
268
+ if any(item <= 0 or item > 1 for item in value):
269
+ raise ValueError("phase_thresholds must be in (0, 1]")
270
+ if tuple(sorted(value)) != value:
271
+ raise ValueError("phase_thresholds must be non-decreasing")
272
+ return value
273
+
274
+
275
+ class ToolBindingRef(BaseModel):
276
+ """Resolved tool binding reference."""
277
+
278
+ model_config = ConfigDict(extra="forbid", frozen=True)
279
+
280
+ tool_id: str
281
+ tool_version: int = Field(gt=0)
282
+ descriptor_sha256: str
283
+ implementation_id: str
284
+
285
+ @field_validator("tool_id", "implementation_id")
286
+ @classmethod
287
+ def _strings_nonblank(cls, value: str, info: Any) -> str:
288
+ return _nonblank(value, info.field_name)
289
+
290
+ @field_validator("descriptor_sha256")
291
+ @classmethod
292
+ def _descriptor_sha256_valid(cls, value: str) -> str:
293
+ return _validate_sha256(value, "descriptor_sha256")
294
+
295
+
296
+ class ArgumentMatch(BaseModel):
297
+ """Mapping from prerequisite output argument to current input argument."""
298
+
299
+ model_config = ConfigDict(extra="forbid", frozen=True)
300
+
301
+ prerequisite_argument: str
302
+ current_argument: str
303
+
304
+ @field_validator("prerequisite_argument", "current_argument")
305
+ @classmethod
306
+ def _strings_nonblank(cls, value: str, info: Any) -> str:
307
+ return _nonblank(value, info.field_name)
308
+
309
+
310
+ class CompiledPrerequisite(BaseModel):
311
+ """Prerequisite node dependency for a compiled node."""
312
+
313
+ model_config = ConfigDict(extra="forbid", frozen=True)
314
+
315
+ node_id: str
316
+ argument_matches: tuple[ArgumentMatch, ...] = Field(default_factory=tuple)
317
+
318
+ @field_validator("node_id")
319
+ @classmethod
320
+ def _node_id_nonblank(cls, value: str) -> str:
321
+ return _nonblank(value, "node_id")
322
+
323
+
324
+ class CompiledHarnessNode(BaseModel):
325
+ """A single node in a compiled harness plan."""
326
+
327
+ model_config = ConfigDict(extra="forbid", frozen=True)
328
+
329
+ node_id: str
330
+ model_tool_name: str
331
+ description: str
332
+ input_schema: JsonObject
333
+ binding: ToolBindingRef
334
+ prerequisites: tuple[CompiledPrerequisite, ...] = Field(default_factory=tuple)
335
+ required: bool
336
+ terminal_result: str | None = None
337
+ required_capabilities: tuple[str, ...] = Field(default_factory=tuple)
338
+ produced_artifact_ids: tuple[str, ...] = Field(default_factory=tuple)
339
+ side_effect_class: SideEffectClass
340
+ idempotency: IdempotencyClass
341
+
342
+ @field_validator("node_id", "model_tool_name", "description")
343
+ @classmethod
344
+ def _strings_nonblank(cls, value: str, info: Any) -> str:
345
+ return _nonblank(value, info.field_name)
346
+
347
+ @field_validator("terminal_result")
348
+ @classmethod
349
+ def _terminal_result_nonblank(cls, value: str | None) -> str | None:
350
+ return None if value is None else _nonblank(value, "terminal_result")
351
+
352
+ @field_validator("required_capabilities", "produced_artifact_ids")
353
+ @classmethod
354
+ def _tuple_strings_nonblank(
355
+ cls, value: tuple[str, ...], info: Any
356
+ ) -> tuple[str, ...]:
357
+ for item in value:
358
+ _nonblank(item, info.field_name)
359
+ _unique(value, info.field_name)
360
+ return value
361
+
362
+
363
+ class TerminalArtifactRequirement(BaseModel):
364
+ """Artifacts required for a specific terminal result."""
365
+
366
+ model_config = ConfigDict(extra="forbid", frozen=True)
367
+
368
+ terminal_result: str
369
+ artifact_ids: tuple[str, ...] = Field(min_length=1)
370
+
371
+ @field_validator("terminal_result")
372
+ @classmethod
373
+ def _terminal_result_nonblank(cls, value: str) -> str:
374
+ return _nonblank(value, "terminal_result")
375
+
376
+ @field_validator("artifact_ids")
377
+ @classmethod
378
+ def _artifact_ids_valid(cls, value: tuple[str, ...]) -> tuple[str, ...]:
379
+ for item in value:
380
+ _nonblank(item, "artifact_ids")
381
+ _unique(value, "artifact_ids")
382
+ return value
383
+
384
+
385
+ class CompiledArtifactPolicy(BaseModel):
386
+ """Declared and terminal-required artifacts for a compiled plan."""
387
+
388
+ model_config = ConfigDict(extra="forbid", frozen=True)
389
+
390
+ declared_artifact_ids: tuple[str, ...] = Field(default_factory=tuple)
391
+ required_by_terminal: tuple[TerminalArtifactRequirement, ...] = Field(
392
+ default_factory=tuple
393
+ )
394
+
395
+ @field_validator("declared_artifact_ids")
396
+ @classmethod
397
+ def _declared_artifact_ids_valid(cls, value: tuple[str, ...]) -> tuple[str, ...]:
398
+ for item in value:
399
+ _nonblank(item, "declared_artifact_ids")
400
+ _unique(value, "declared_artifact_ids")
401
+ return value
402
+
403
+
404
+ class CompiledHarnessPlan(BaseModel):
405
+ """Immutable compiled harness plan consumed by the runtime."""
406
+
407
+ model_config = ConfigDict(extra="forbid", frozen=True)
408
+
409
+ schema_version: Literal["1.0"]
410
+ kind: Literal["compiled_millforge_harness"]
411
+ harness_id: str
412
+ harness_version: int = Field(gt=0)
413
+ source_sha256: str
414
+ compiled_sha256: str
415
+ stage_kind_ids: tuple[str, ...]
416
+ model_profile: CompiledModelProfile
417
+ prompt_policy: CompiledPromptPolicy
418
+ budgets: CompiledBudgetPolicy
419
+ context_policy: CompiledContextPolicy
420
+ nodes: tuple[CompiledHarnessNode, ...] = Field(min_length=1)
421
+ required_capabilities: tuple[str, ...] = Field(default_factory=tuple)
422
+ terminal_result_map: dict[str, str]
423
+ artifact_policy: CompiledArtifactPolicy
424
+ compiler_identity: CompilerIdentity
425
+
426
+ @field_validator("harness_id")
427
+ @classmethod
428
+ def _harness_id_nonblank(cls, value: str) -> str:
429
+ return _nonblank(value, "harness_id")
430
+
431
+ @field_validator("source_sha256", "compiled_sha256")
432
+ @classmethod
433
+ def _hashes_valid(cls, value: str, info: Any) -> str:
434
+ return _validate_sha256(value, info.field_name)
435
+
436
+ @field_validator("stage_kind_ids", "required_capabilities")
437
+ @classmethod
438
+ def _tuple_strings_unique(
439
+ cls, value: tuple[str, ...], info: Any
440
+ ) -> tuple[str, ...]:
441
+ for item in value:
442
+ _nonblank(item, info.field_name)
443
+ _unique(value, info.field_name)
444
+ return value
445
+
446
+ @field_validator("terminal_result_map")
447
+ @classmethod
448
+ def _terminal_result_map_nonblank(cls, value: dict[str, str]) -> dict[str, str]:
449
+ for key, item in value.items():
450
+ _nonblank(key, "terminal_result_map key")
451
+ _nonblank(item, "terminal_result_map value")
452
+ if len(set(value.values())) != len(value.values()):
453
+ raise ValueError("terminal_result_map values must be unique")
454
+ return value
455
+
456
+ @model_validator(mode="after")
457
+ def _check_plan_invariants(self) -> CompiledHarnessPlan:
458
+ violations = self.validate_plan_invariants()
459
+ if violations:
460
+ raise ValueError("; ".join(violations))
461
+ return self
462
+
463
+ def validate_plan_invariants(self) -> list[str]:
464
+ """Return cross-field invariant violations."""
465
+ violations: list[str] = []
466
+ node_ids = [node.node_id for node in self.nodes]
467
+ model_tool_names = [node.model_tool_name for node in self.nodes]
468
+ binding_pairs = [
469
+ (node.binding.tool_id, node.binding.tool_version) for node in self.nodes
470
+ ]
471
+ terminal_nodes: dict[str, CompiledHarnessNode] = {
472
+ node.node_id: node for node in self.nodes if node.terminal_result
473
+ }
474
+ terminal_results = {
475
+ node.terminal_result
476
+ for node in self.nodes
477
+ if node.terminal_result is not None
478
+ }
479
+ declared_artifacts = set(self.artifact_policy.declared_artifact_ids)
480
+ produced_artifacts = {
481
+ artifact_id
482
+ for node in self.nodes
483
+ for artifact_id in node.produced_artifact_ids
484
+ }
485
+ required_capabilities = set(self.required_capabilities)
486
+
487
+ if len(set(node_ids)) != len(node_ids):
488
+ violations.append("node_id values must be unique")
489
+ if len(set(model_tool_names)) != len(model_tool_names):
490
+ violations.append("model_tool_name values must be unique")
491
+ if len(set(binding_pairs)) != len(binding_pairs):
492
+ violations.append("binding tool_id/tool_version pairs must be unique")
493
+ if not terminal_nodes:
494
+ violations.append("Plan must contain at least one terminal node")
495
+
496
+ for node in self.nodes:
497
+ if node.terminal_result is not None and node.required:
498
+ violations.append(
499
+ f"Terminal node {node.node_id!r} must not be required"
500
+ )
501
+ for prereq in node.prerequisites:
502
+ if prereq.node_id not in node_ids:
503
+ violations.append(
504
+ f"Node {node.node_id!r} prerequisite references unknown "
505
+ f"node_id {prereq.node_id!r}"
506
+ )
507
+ if prereq.node_id == node.node_id:
508
+ violations.append(f"Node {node.node_id!r} cannot require itself")
509
+ for capability in node.required_capabilities:
510
+ if capability not in required_capabilities:
511
+ violations.append(
512
+ f"Node {node.node_id!r} requires undeclared capability "
513
+ f"{capability!r}"
514
+ )
515
+ for artifact_id in node.produced_artifact_ids:
516
+ if artifact_id not in declared_artifacts:
517
+ violations.append(
518
+ f"Node {node.node_id!r} produces undeclared artifact "
519
+ f"{artifact_id!r}"
520
+ )
521
+
522
+ if set(self.terminal_result_map) != set(terminal_nodes):
523
+ violations.append(
524
+ "terminal_result_map keys must exactly name terminal nodes"
525
+ )
526
+ for node_id, terminal_result in self.terminal_result_map.items():
527
+ terminal_node = terminal_nodes.get(node_id)
528
+ if (
529
+ terminal_node is not None
530
+ and terminal_node.terminal_result != terminal_result
531
+ ):
532
+ violations.append(
533
+ f"terminal_result_map value for {node_id!r} must match node terminal_result"
534
+ )
535
+
536
+ for requirement in self.artifact_policy.required_by_terminal:
537
+ if requirement.terminal_result not in terminal_results:
538
+ violations.append(
539
+ f"Artifact requirement terminal_result "
540
+ f"{requirement.terminal_result!r} is not in terminal_result_map"
541
+ )
542
+ for artifact_id in requirement.artifact_ids:
543
+ if artifact_id not in declared_artifacts:
544
+ violations.append(
545
+ f"Required artifact {artifact_id!r} is not declared"
546
+ )
547
+ if artifact_id not in produced_artifacts:
548
+ violations.append(
549
+ f"Required artifact {artifact_id!r} has no producer"
550
+ )
551
+
552
+ return violations
553
+
554
+
555
+ class SessionEvent(BaseModel):
556
+ """Ordered runtime session event record."""
557
+
558
+ model_config = ConfigDict(extra="forbid", frozen=True)
559
+
560
+ schema_version: Literal["1.0"]
561
+ sequence: int = Field(gt=0)
562
+ occurred_at: str
563
+ monotonic_offset_ms: float = Field(ge=0)
564
+ event_type: SessionEventType
565
+ request_id: str
566
+ run_id: str
567
+ session_id: str
568
+ stage: StageIdentity
569
+ node_id: str | None = None
570
+ model_turn: int | None = Field(default=None, ge=0)
571
+ tool_call_id: str | None = None
572
+ code: str | None = None
573
+ fields: tuple[DiagnosticField, ...] = Field(default_factory=tuple)
574
+
575
+ @field_validator(
576
+ "occurred_at",
577
+ "request_id",
578
+ "run_id",
579
+ "session_id",
580
+ )
581
+ @classmethod
582
+ def _strings_nonblank(cls, value: str, info: Any) -> str:
583
+ return _nonblank(value, info.field_name)
584
+
585
+ @field_validator("node_id", "tool_call_id", "code")
586
+ @classmethod
587
+ def _optional_strings_nonblank(cls, value: str | None, info: Any) -> str | None:
588
+ return None if value is None else _nonblank(value, info.field_name)
589
+
590
+ @field_validator("fields")
591
+ @classmethod
592
+ def _field_keys_unique(
593
+ cls, value: tuple[DiagnosticField, ...]
594
+ ) -> tuple[DiagnosticField, ...]:
595
+ keys = [field.key for field in value]
596
+ if len(set(keys)) != len(keys):
597
+ raise ValueError("diagnostic field keys must be unique")
598
+ return value
599
+
600
+
601
+ class ToolTraceDecisionRecord(BaseModel):
602
+ """Typed keyed support decision recorded with a tool trace."""
603
+
604
+ model_config = ConfigDict(extra="forbid", frozen=True)
605
+
606
+ key: str
607
+ decision: ToolTraceDecision
608
+
609
+ @field_validator("key")
610
+ @classmethod
611
+ def _key_nonblank(cls, value: str) -> str:
612
+ return _nonblank(value, "key")
613
+
614
+
615
+ class ToolTraceRecord(BaseModel):
616
+ """Ordered runtime trace record for a tool invocation."""
617
+
618
+ model_config = ConfigDict(extra="forbid", frozen=True)
619
+
620
+ schema_version: Literal["1.0"]
621
+ sequence: int = Field(gt=0)
622
+ occurred_at: str
623
+ monotonic_offset_ms: float = Field(ge=0)
624
+ request_id: str
625
+ run_id: str
626
+ session_id: str
627
+ stage: StageIdentity
628
+ node_id: str
629
+ model_turn: int = Field(ge=0)
630
+ tool_call_id: str
631
+ model_tool_name: str
632
+ binding: ToolBindingRef
633
+ binding_resolution_status: Literal["resolved", "ambiguous", "uncompiled"] = (
634
+ "resolved"
635
+ )
636
+ connector_id: str | None = None
637
+ provider_tool_name: str | None = None
638
+ connector_tool_id: str | None = None
639
+ connector_tool_version: int | None = Field(default=None, ge=1)
640
+ connector_descriptor_sha256: str | None = None
641
+ connector_identity_sha256: str | None = None
642
+ discovery_snapshot_sha256: str | None = None
643
+ approval_policy: ConnectorApprovalPolicyValue | None = None
644
+ approval_decision: ConnectorApprovalDecision | None = None
645
+ approval_evidence: dict[str, str | int | float | bool | None] = Field(
646
+ default_factory=dict
647
+ )
648
+ broker_attempted: bool | None = None
649
+ request_sha256: str | None = None
650
+ response_sha256: str | None = None
651
+ retry_decision: Literal["retry_allowed", "retry_denied"] | None = None
652
+ drift_decision: ConnectorDriftDecision | None = None
653
+ redacted_evidence: dict[str, Any] = Field(default_factory=dict)
654
+ input_sha256: str
655
+ prerequisite_decisions: tuple[ToolTraceDecisionRecord, ...] = Field(
656
+ default_factory=tuple
657
+ )
658
+ capability_decisions: tuple[ToolTraceDecisionRecord, ...] = Field(
659
+ default_factory=tuple
660
+ )
661
+ execution_status: ToolExecutionStatus
662
+ retryable: bool
663
+ side_effect_class: ToolTraceSideEffectClass
664
+ idempotency: ToolTraceIdempotency
665
+ side_effect_certainty: SideEffectCertainty
666
+ side_effect_detail_code: str | None = None
667
+ side_effect_detail_summary: str | None = None
668
+ side_effect_retry_allowed: bool | None = None
669
+ output_sha256: str | None = None
670
+ duration_ms: float = Field(ge=0)
671
+ summary: str
672
+
673
+ @field_validator(
674
+ "occurred_at",
675
+ "request_id",
676
+ "run_id",
677
+ "session_id",
678
+ "node_id",
679
+ "tool_call_id",
680
+ "model_tool_name",
681
+ "summary",
682
+ )
683
+ @classmethod
684
+ def _strings_nonblank(cls, value: str, info: Any) -> str:
685
+ return _nonblank(value, info.field_name)
686
+
687
+ @field_validator("input_sha256")
688
+ @classmethod
689
+ def _input_sha256_valid(cls, value: str) -> str:
690
+ return _validate_sha256(value, "input_sha256")
691
+
692
+ @field_validator(
693
+ "connector_id",
694
+ "provider_tool_name",
695
+ "connector_tool_id",
696
+ "approval_policy",
697
+ "approval_decision",
698
+ )
699
+ @classmethod
700
+ def _optional_connector_strings_nonblank(
701
+ cls, value: str | None, info: Any
702
+ ) -> str | None:
703
+ return None if value is None else _nonblank(value, info.field_name)
704
+
705
+ @field_validator(
706
+ "connector_descriptor_sha256",
707
+ "connector_identity_sha256",
708
+ "discovery_snapshot_sha256",
709
+ )
710
+ @classmethod
711
+ def _optional_connector_sha256_valid(
712
+ cls, value: str | None, info: Any
713
+ ) -> str | None:
714
+ return None if value is None else _validate_sha256(value, info.field_name)
715
+
716
+ @field_validator("request_sha256", "response_sha256", "output_sha256")
717
+ @classmethod
718
+ def _optional_sha256_valid(cls, value: str | None, info: Any) -> str | None:
719
+ return None if value is None else _validate_sha256(value, info.field_name)
720
+
721
+ @field_validator("side_effect_detail_code", "side_effect_detail_summary")
722
+ @classmethod
723
+ def _optional_detail_nonblank(cls, value: str | None, info: Any) -> str | None:
724
+ return None if value is None else _nonblank(value, info.field_name)
725
+
726
+ @field_validator("prerequisite_decisions", "capability_decisions")
727
+ @classmethod
728
+ def _decision_keys_unique(
729
+ cls, value: tuple[ToolTraceDecisionRecord, ...], info: Any
730
+ ) -> tuple[ToolTraceDecisionRecord, ...]:
731
+ keys = [field.key for field in value]
732
+ if len(set(keys)) != len(keys):
733
+ raise ValueError(f"{info.field_name} keys must be unique")
734
+ return value
735
+
736
+ @model_validator(mode="after")
737
+ def _trace_consistency(self) -> ToolTraceRecord:
738
+ connector_fields = (
739
+ self.connector_id,
740
+ self.provider_tool_name,
741
+ self.connector_tool_id,
742
+ self.connector_tool_version,
743
+ self.connector_descriptor_sha256,
744
+ self.connector_identity_sha256,
745
+ self.discovery_snapshot_sha256,
746
+ self.approval_policy,
747
+ self.approval_decision,
748
+ self.broker_attempted,
749
+ self.request_sha256,
750
+ self.response_sha256,
751
+ self.retry_decision,
752
+ self.drift_decision,
753
+ )
754
+ if any(item is not None for item in connector_fields):
755
+ if not all(item is not None for item in connector_fields):
756
+ raise ValueError(
757
+ "connector approval trace fields must be provided together"
758
+ )
759
+ if not self.approval_evidence:
760
+ raise ValueError("connector approval trace requires approval_evidence")
761
+ if not self.redacted_evidence:
762
+ raise ValueError("connector trace requires redacted_evidence")
763
+ if (
764
+ self.binding_resolution_status != "resolved"
765
+ and self.execution_status == ToolExecutionStatus.SUCCESS
766
+ ):
767
+ raise ValueError(
768
+ "binding-resolution failures cannot be marked as successful"
769
+ )
770
+ denied = any(
771
+ record.decision == ToolTraceDecision.DENIED
772
+ for record in (*self.prerequisite_decisions, *self.capability_decisions)
773
+ )
774
+ if denied or self.execution_status == ToolExecutionStatus.NOT_EXECUTED:
775
+ if self.side_effect_certainty != SideEffectCertainty.NOT_ATTEMPTED:
776
+ raise ValueError(
777
+ "denied or not_executed calls must use "
778
+ "side_effect_certainty=not_attempted"
779
+ )
780
+ if self.output_sha256 is not None:
781
+ raise ValueError(
782
+ "denied or not_executed calls must not have output_sha256"
783
+ )
784
+ detail_fields = (
785
+ self.side_effect_detail_code,
786
+ self.side_effect_detail_summary,
787
+ self.side_effect_retry_allowed,
788
+ )
789
+ if any(item is not None for item in detail_fields) and not all(
790
+ item is not None for item in detail_fields
791
+ ):
792
+ raise ValueError("side-effect detail fields must be provided together")
793
+ if (
794
+ self.side_effect_certainty == SideEffectCertainty.COMPLETION_UNKNOWN
795
+ and self.idempotency
796
+ in {ToolTraceIdempotency.NON_IDEMPOTENT, ToolTraceIdempotency.UNKNOWN}
797
+ and self.retryable
798
+ ):
799
+ raise ValueError(
800
+ "completion_unknown side effects are not retryable for "
801
+ "non-idempotent or unknown-idempotency tool work"
802
+ )
803
+ if self.side_effect_retry_allowed is not None:
804
+ if self.side_effect_retry_allowed != self.retryable:
805
+ raise ValueError("side_effect_retry_allowed must match retryable")
806
+ return self
807
+
808
+
809
+ def canonical_json_serialize(obj: Any, *, ensure_ascii: bool = True) -> str:
810
+ """Serialize JSON-compatible data for deterministic SHA-256 hashing."""
811
+ return (
812
+ json.dumps(
813
+ obj,
814
+ sort_keys=True,
815
+ ensure_ascii=ensure_ascii,
816
+ allow_nan=False,
817
+ separators=(",", ":"),
818
+ ).replace("\r\n", "\n")
819
+ + "\n"
820
+ )
821
+
822
+
823
+ def canonical_compiled_plan_bytes(plan: CompiledHarnessPlan) -> bytes:
824
+ """Serialize a finalized compiled plan as canonical UTF-8 JSON bytes."""
825
+ verified, _computed, warnings, restored = verify_compiled_plan_sha256(
826
+ canonical_json_serialize(plan.model_dump(mode="json")),
827
+ expected_compiled_hash=plan.compiled_sha256,
828
+ expected_harness_id=plan.harness_id,
829
+ expected_harness_version=plan.harness_version,
830
+ )
831
+ if not verified or restored is None:
832
+ joined = (
833
+ "; ".join(warnings) if warnings else "compiled hash verification failed"
834
+ )
835
+ raise ValueError(joined)
836
+ return canonical_json_serialize(restored.model_dump(mode="json")).encode("utf-8")
837
+
838
+
839
+ def _no_duplicate_keys(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
840
+ seen: set[str] = set()
841
+ result: dict[str, Any] = {}
842
+ for key, value in pairs:
843
+ if key in seen:
844
+ raise json.JSONDecodeError(f"Duplicate key {key!r}", "", 0)
845
+ seen.add(key)
846
+ result[key] = value
847
+ return result
848
+
849
+
850
+ def parse_and_strip_compiled_plan(
851
+ raw: str,
852
+ ) -> tuple[CompiledHarnessPlan, dict[str, Any]]:
853
+ """Parse JSON with duplicate-key rejection and strip ``compiled_sha256``."""
854
+ parsed: dict[str, Any] = json.loads(raw, object_pairs_hook=_no_duplicate_keys)
855
+ plan = CompiledHarnessPlan.model_validate(parsed)
856
+ stripped = dict(parsed)
857
+ stripped.pop("compiled_sha256", None)
858
+ return plan, stripped
859
+
860
+
861
+ def calculate_compiled_plan_sha256(plan_payload: dict[str, Any]) -> str:
862
+ """Hash a complete JSON-mode compiled-plan payload without its digest field."""
863
+ if "compiled_sha256" not in plan_payload:
864
+ raise ValueError("compiled plan payload must include compiled_sha256")
865
+ body = dict(plan_payload)
866
+ body.pop("compiled_sha256")
867
+ return hashlib.sha256(canonical_json_serialize(body).encode("utf-8")).hexdigest()
868
+
869
+
870
+ def finalize_compiled_plan_sha256(plan: CompiledHarnessPlan) -> CompiledHarnessPlan:
871
+ """Return a fully validated plan with the canonical compiled SHA-256 set."""
872
+ placeholder_payload = plan.model_dump(mode="json")
873
+ digest = calculate_compiled_plan_sha256(placeholder_payload)
874
+ finalized = CompiledHarnessPlan.model_validate(
875
+ {**placeholder_payload, "compiled_sha256": digest}
876
+ )
877
+ verified, computed, warnings, _restored = verify_compiled_plan_sha256(
878
+ canonical_json_serialize(finalized.model_dump(mode="json")),
879
+ expected_compiled_hash=digest,
880
+ expected_harness_id=finalized.harness_id,
881
+ expected_harness_version=finalized.harness_version,
882
+ )
883
+ if not verified or computed != digest:
884
+ joined = (
885
+ "; ".join(warnings) if warnings else "compiled hash verification failed"
886
+ )
887
+ raise ValueError(joined)
888
+ return finalized
889
+
890
+
891
+ def verify_compiled_plan_sha256(
892
+ raw: str,
893
+ *,
894
+ expected_compiled_hash: str | None = None,
895
+ expected_harness_id: str | None = None,
896
+ expected_harness_version: int | None = None,
897
+ ) -> tuple[bool, str, list[str], CompiledHarnessPlan | None]:
898
+ """Verify a serialized 02B compiled plan canonical hash and identity."""
899
+ warnings: list[str] = []
900
+
901
+ try:
902
+ parsed: dict[str, Any] = json.loads(raw, object_pairs_hook=_no_duplicate_keys)
903
+ except json.JSONDecodeError as exc:
904
+ return False, "", [f"JSON parse error: {exc}"], None
905
+
906
+ try:
907
+ plan = CompiledHarnessPlan.model_validate(parsed)
908
+ except Exception as exc:
909
+ return False, "", [f"Plan validation error: {exc}"], None
910
+
911
+ try:
912
+ computed_hash = calculate_compiled_plan_sha256(parsed)
913
+ except (TypeError, ValueError) as exc:
914
+ return False, "", [f"Canonical serialisation error: {exc}"], None
915
+
916
+ verified = True
917
+
918
+ if computed_hash != plan.compiled_sha256:
919
+ warnings.append(
920
+ f"Computed hash {computed_hash} does not match "
921
+ f"plan.compiled_sha256 {plan.compiled_sha256}"
922
+ )
923
+ verified = False
924
+
925
+ if expected_compiled_hash is not None and computed_hash != expected_compiled_hash:
926
+ warnings.append(
927
+ f"Computed hash {computed_hash} does not match "
928
+ f"expected_compiled_hash {expected_compiled_hash}"
929
+ )
930
+ verified = False
931
+
932
+ if expected_harness_id is not None and plan.harness_id != expected_harness_id:
933
+ warnings.append(
934
+ f"Plan harness_id {plan.harness_id!r} does not match "
935
+ f"expected_harness_id {expected_harness_id!r}"
936
+ )
937
+ verified = False
938
+
939
+ if (
940
+ expected_harness_version is not None
941
+ and plan.harness_version != expected_harness_version
942
+ ):
943
+ warnings.append(
944
+ f"Plan harness_version {plan.harness_version!r} does not match "
945
+ f"expected_harness_version {expected_harness_version!r}"
946
+ )
947
+ verified = False
948
+
949
+ return verified, computed_hash, warnings, plan
950
+
951
+
952
+ class FileCompiledHarnessLoader:
953
+ """Filesystem loader for runtime compiled-plan references."""
954
+
955
+ async def load(self, ref: Any) -> CompiledHarnessPlan:
956
+ """Load and verify a compiled harness plan from ``ref.path``."""
957
+ path = Path(ref.path)
958
+ raw = path.read_text(encoding="utf-8")
959
+ expected_hash = getattr(ref.expected_hash, "digest", None)
960
+ identity = ref.identity
961
+ verified, _computed, warnings, plan = verify_compiled_plan_sha256(
962
+ raw,
963
+ expected_compiled_hash=expected_hash,
964
+ expected_harness_id=identity.harness_id,
965
+ expected_harness_version=identity.harness_version,
966
+ )
967
+ if not verified or plan is None:
968
+ joined = "; ".join(warnings) if warnings else "compiled plan invalid"
969
+ raise ValueError(joined)
970
+ return plan