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.
- millforge/__init__.py +1174 -0
- millforge/_forge/LICENSE +21 -0
- millforge/_forge/PROVENANCE.json +295 -0
- millforge/_forge/UPDATE_POLICY.md +24 -0
- millforge/_forge/__init__.py +14 -0
- millforge/_forge/adapter.py +2232 -0
- millforge/_forge/base_runner.py +121 -0
- millforge/_forge/clients/__init__.py +10 -0
- millforge/_forge/clients/base.py +200 -0
- millforge/_forge/context/__init__.py +23 -0
- millforge/_forge/context/manager.py +178 -0
- millforge/_forge/context/strategies.py +335 -0
- millforge/_forge/core/__init__.py +16 -0
- millforge/_forge/core/inference.py +433 -0
- millforge/_forge/core/messages.py +119 -0
- millforge/_forge/core/runner.py +479 -0
- millforge/_forge/core/steps.py +108 -0
- millforge/_forge/core/workflow.py +400 -0
- millforge/_forge/errors.py +222 -0
- millforge/_forge/guardrails/__init__.py +21 -0
- millforge/_forge/guardrails/error_tracker.py +71 -0
- millforge/_forge/guardrails/guardrails.py +194 -0
- millforge/_forge/guardrails/nudge.py +47 -0
- millforge/_forge/guardrails/response_validator.py +119 -0
- millforge/_forge/guardrails/step_enforcer.py +183 -0
- millforge/_forge/prompts/__init__.py +16 -0
- millforge/_forge/prompts/nudges.py +95 -0
- millforge/_forge/prompts/templates.py +285 -0
- millforge/_version.py +3 -0
- millforge/artifacts.py +570 -0
- millforge/base/__init__.py +97 -0
- millforge/base/composition.py +402 -0
- millforge/base/context.py +285 -0
- millforge/base/harness.py +138 -0
- millforge/base/identity.py +465 -0
- millforge/base/options.py +34 -0
- millforge/base/platform.py +17 -0
- millforge/base/prompt.py +317 -0
- millforge/base/runner.py +546 -0
- millforge/compiled_plan.py +970 -0
- millforge/compiler/__init__.py +231 -0
- millforge/compiler/artifact_validation.py +257 -0
- millforge/compiler/canonicalization.py +169 -0
- millforge/compiler/capabilities.py +66 -0
- millforge/compiler/catalogs.py +500 -0
- millforge/compiler/diagnostics.py +491 -0
- millforge/compiler/graph.py +678 -0
- millforge/compiler/lowering.py +198 -0
- millforge/compiler/output.py +692 -0
- millforge/compiler/parsing.py +1424 -0
- millforge/compiler/requests.py +1180 -0
- millforge/compiler/schema_validation.py +272 -0
- millforge/compiler/semantic.py +490 -0
- millforge/compiler/service.py +448 -0
- millforge/compiler/source.py +375 -0
- millforge/compiler/validators.py +184 -0
- millforge/connectors/__init__.py +95 -0
- millforge/connectors/admission.py +801 -0
- millforge/connectors/broker.py +202 -0
- millforge/connectors/contracts.py +1159 -0
- millforge/connectors/diagnostics.py +189 -0
- millforge/connectors/fake.py +66 -0
- millforge/connectors/runtime.py +236 -0
- millforge/contracts.py +2860 -0
- millforge/custom_tools/__init__.py +67 -0
- millforge/custom_tools/compiler.py +724 -0
- millforge/custom_tools/contracts.py +1093 -0
- millforge/custom_tools/diagnostics.py +205 -0
- millforge/eval_artifacts.py +952 -0
- millforge/eval_boundary.py +2435 -0
- millforge/eval_fixtures/__init__.py +1 -0
- millforge/eval_fixtures/default_pack/__init__.py +1 -0
- millforge/eval_fixtures/default_pack/fixtures/fixture.08a.bug_diagnosis.traceback.v1.json +52 -0
- millforge/eval_fixtures/default_pack/fixtures/fixture.08a.direct_edit.import_sort.v1.json +52 -0
- millforge/eval_fixtures/default_pack/fixtures/fixture.08a.evidence_discipline.no_source_change.v1.json +51 -0
- millforge/eval_fixtures/default_pack/fixtures/fixture.08a.false_closure.visible_green.v1.json +52 -0
- millforge/eval_fixtures/default_pack/fixtures/fixture.08a.multi_file.api_contract.v1.json +54 -0
- millforge/eval_fixtures/default_pack/fixtures/fixture.08a.recovery.malformed_artifact.v1.json +54 -0
- millforge/eval_fixtures/default_pack/manifest.json +12 -0
- millforge/eval_modes.py +1282 -0
- millforge/eval_presets.py +1398 -0
- millforge/eval_reports.py +2517 -0
- millforge/eval_suite.py +2429 -0
- millforge/eval_trials.py +2632 -0
- millforge/eval_workflow.py +794 -0
- millforge/exceptions.py +122 -0
- millforge/model_backend.py +2098 -0
- millforge/protocols.py +340 -0
- millforge/py.typed +0 -0
- millforge/runtime.py +1791 -0
- millforge/testing/__init__.py +1089 -0
- millforge/tools/__init__.py +83 -0
- millforge/tools/builtin_runtime.py +1339 -0
- millforge/tools/builtins.py +773 -0
- millforge/tools/execution.py +1545 -0
- millforge/tools/path_policy.py +155 -0
- millforge/tools/pi_compat/PI_LICENSE +21 -0
- millforge/tools/pi_compat/PROVENANCE.json +55 -0
- millforge/tools/pi_compat/UPDATE_POLICY.md +36 -0
- millforge/tools/pi_compat/__init__.py +34 -0
- millforge/tools/pi_compat/contracts.py +49 -0
- millforge/tools/pi_compat/editing.py +390 -0
- millforge/tools/pi_compat/mutations.py +57 -0
- millforge/tools/pi_compat/operations.py +401 -0
- millforge/tools/pi_compat/paths.py +155 -0
- millforge/tools/pi_compat/process.py +1375 -0
- millforge/tools/pi_compat/search.py +738 -0
- millforge/tools/pi_compat/truncation.py +267 -0
- millforge/tools/pi_compat_catalog.py +396 -0
- millforge/tools/pi_compat_runtime.py +460 -0
- millforge/tools/registry.py +553 -0
- millforge/tools/results.py +533 -0
- millforge-0.1.0.dist-info/METADATA +844 -0
- millforge-0.1.0.dist-info/RECORD +116 -0
- millforge-0.1.0.dist-info/WHEEL +4 -0
- millforge-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"""Immutable source-language contracts for Millforge harness files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
from pydantic import (
|
|
10
|
+
BaseModel,
|
|
11
|
+
ConfigDict,
|
|
12
|
+
Field,
|
|
13
|
+
StrictBool,
|
|
14
|
+
StrictInt,
|
|
15
|
+
StrictStr,
|
|
16
|
+
field_validator,
|
|
17
|
+
model_validator,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from millforge.compiler.validators import (
|
|
21
|
+
validate_argument_name,
|
|
22
|
+
validate_artifact_id,
|
|
23
|
+
validate_harness_id,
|
|
24
|
+
validate_harness_version,
|
|
25
|
+
validate_policy_id,
|
|
26
|
+
validate_profile_id,
|
|
27
|
+
validate_stage_kind_id,
|
|
28
|
+
validate_terminal_result,
|
|
29
|
+
validate_threshold,
|
|
30
|
+
validate_tool_reference,
|
|
31
|
+
validate_unique,
|
|
32
|
+
validate_utf8_size,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
MAX_NODES = 512
|
|
36
|
+
MAX_PREREQUISITES_PER_NODE = 64
|
|
37
|
+
MAX_ARGUMENT_MATCHES_PER_PREREQUISITE = 32
|
|
38
|
+
MAX_TERMINAL_NODES = 64
|
|
39
|
+
MAX_DECLARED_ARTIFACTS = 512
|
|
40
|
+
MAX_TERMINAL_ARTIFACT_POLICIES = 64
|
|
41
|
+
MAX_ARTIFACTS_PER_TERMINAL = 512
|
|
42
|
+
MAX_STAGE_KIND_IDS = 64
|
|
43
|
+
MAX_SYSTEM_INSTRUCTIONS_UTF8 = 65_536
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ArgumentMatchSource(BaseModel):
|
|
47
|
+
"""Immutable prerequisite argument equality mapping."""
|
|
48
|
+
|
|
49
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
50
|
+
|
|
51
|
+
prior_argument: StrictStr
|
|
52
|
+
current_argument: StrictStr
|
|
53
|
+
|
|
54
|
+
@field_validator("prior_argument", "current_argument")
|
|
55
|
+
@classmethod
|
|
56
|
+
def _arguments_valid(cls, value: str) -> str:
|
|
57
|
+
return validate_argument_name(value)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class PrerequisiteSource(BaseModel):
|
|
61
|
+
"""Immutable source prerequisite declaration."""
|
|
62
|
+
|
|
63
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
64
|
+
|
|
65
|
+
node_id: StrictStr
|
|
66
|
+
argument_matches: tuple[ArgumentMatchSource, ...] = Field(default_factory=tuple)
|
|
67
|
+
|
|
68
|
+
@model_validator(mode="before")
|
|
69
|
+
@classmethod
|
|
70
|
+
def _convert_argument_mapping(cls, data: Any) -> Any:
|
|
71
|
+
if not isinstance(data, Mapping):
|
|
72
|
+
return data
|
|
73
|
+
values = dict(data)
|
|
74
|
+
raw_matches = values.get("argument_matches")
|
|
75
|
+
if isinstance(raw_matches, Mapping):
|
|
76
|
+
values["argument_matches"] = tuple(
|
|
77
|
+
{"prior_argument": key, "current_argument": value}
|
|
78
|
+
for key, value in raw_matches.items()
|
|
79
|
+
)
|
|
80
|
+
return values
|
|
81
|
+
|
|
82
|
+
@field_validator("node_id")
|
|
83
|
+
@classmethod
|
|
84
|
+
def _node_id_valid(cls, value: str) -> str:
|
|
85
|
+
from millforge.compiler.validators import validate_node_id
|
|
86
|
+
|
|
87
|
+
return validate_node_id(value)
|
|
88
|
+
|
|
89
|
+
@field_validator("argument_matches")
|
|
90
|
+
@classmethod
|
|
91
|
+
def _argument_matches_limited(
|
|
92
|
+
cls, value: tuple[ArgumentMatchSource, ...]
|
|
93
|
+
) -> tuple[ArgumentMatchSource, ...]:
|
|
94
|
+
if len(value) > MAX_ARGUMENT_MATCHES_PER_PREREQUISITE:
|
|
95
|
+
raise ValueError("argument_matches may contain at most 32 entries")
|
|
96
|
+
validate_unique(tuple(item.prior_argument for item in value), "prior_argument")
|
|
97
|
+
validate_unique(
|
|
98
|
+
tuple(item.current_argument for item in value), "current_argument"
|
|
99
|
+
)
|
|
100
|
+
return value
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class HarnessNodeSource(BaseModel):
|
|
104
|
+
"""Immutable source graph node declaration."""
|
|
105
|
+
|
|
106
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
107
|
+
|
|
108
|
+
node_id: StrictStr
|
|
109
|
+
tool_ref: StrictStr
|
|
110
|
+
required: StrictBool = False
|
|
111
|
+
prerequisites: tuple[PrerequisiteSource, ...] = Field(default_factory=tuple)
|
|
112
|
+
terminal_result: StrictStr | None = None
|
|
113
|
+
produces: tuple[StrictStr, ...] = Field(default_factory=tuple)
|
|
114
|
+
|
|
115
|
+
@field_validator("node_id")
|
|
116
|
+
@classmethod
|
|
117
|
+
def _node_id_valid(cls, value: str) -> str:
|
|
118
|
+
from millforge.compiler.validators import validate_node_id
|
|
119
|
+
|
|
120
|
+
return validate_node_id(value)
|
|
121
|
+
|
|
122
|
+
@field_validator("tool_ref")
|
|
123
|
+
@classmethod
|
|
124
|
+
def _tool_ref_valid(cls, value: str) -> str:
|
|
125
|
+
return validate_tool_reference(value)
|
|
126
|
+
|
|
127
|
+
@field_validator("prerequisites")
|
|
128
|
+
@classmethod
|
|
129
|
+
def _prerequisites_limited(
|
|
130
|
+
cls, value: tuple[PrerequisiteSource, ...]
|
|
131
|
+
) -> tuple[PrerequisiteSource, ...]:
|
|
132
|
+
if len(value) > MAX_PREREQUISITES_PER_NODE:
|
|
133
|
+
raise ValueError("prerequisites may contain at most 64 entries")
|
|
134
|
+
return value
|
|
135
|
+
|
|
136
|
+
@field_validator("terminal_result")
|
|
137
|
+
@classmethod
|
|
138
|
+
def _terminal_result_valid(cls, value: str | None) -> str | None:
|
|
139
|
+
return None if value is None else validate_terminal_result(value)
|
|
140
|
+
|
|
141
|
+
@field_validator("produces")
|
|
142
|
+
@classmethod
|
|
143
|
+
def _produces_valid(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
|
144
|
+
for item in value:
|
|
145
|
+
validate_artifact_id(item)
|
|
146
|
+
return validate_unique(value, "produces")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class HarnessGraphSource(BaseModel):
|
|
150
|
+
"""Immutable source graph carrying node IDs from the authoring mapping."""
|
|
151
|
+
|
|
152
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
153
|
+
|
|
154
|
+
nodes: tuple[HarnessNodeSource, ...] = Field(min_length=1)
|
|
155
|
+
|
|
156
|
+
@model_validator(mode="before")
|
|
157
|
+
@classmethod
|
|
158
|
+
def _convert_node_mapping(cls, data: Any) -> Any:
|
|
159
|
+
if not isinstance(data, Mapping):
|
|
160
|
+
return data
|
|
161
|
+
values = dict(data)
|
|
162
|
+
raw_nodes = values.get("nodes")
|
|
163
|
+
if isinstance(raw_nodes, Mapping):
|
|
164
|
+
converted = []
|
|
165
|
+
for node_id, raw_node in raw_nodes.items():
|
|
166
|
+
if not isinstance(raw_node, Mapping):
|
|
167
|
+
converted.append({"node_id": node_id})
|
|
168
|
+
continue
|
|
169
|
+
if "node_id" in raw_node:
|
|
170
|
+
raise ValueError(
|
|
171
|
+
"graph node bodies must not define node_id; the mapping key is authoritative"
|
|
172
|
+
)
|
|
173
|
+
converted.append({**dict(raw_node), "node_id": node_id})
|
|
174
|
+
values["nodes"] = tuple(converted)
|
|
175
|
+
return values
|
|
176
|
+
|
|
177
|
+
@field_validator("nodes")
|
|
178
|
+
@classmethod
|
|
179
|
+
def _nodes_valid(
|
|
180
|
+
cls, value: tuple[HarnessNodeSource, ...]
|
|
181
|
+
) -> tuple[HarnessNodeSource, ...]:
|
|
182
|
+
if len(value) > MAX_NODES:
|
|
183
|
+
raise ValueError("nodes may contain at most 512 entries")
|
|
184
|
+
validate_unique(tuple(node.node_id for node in value), "node_id")
|
|
185
|
+
terminal_count = sum(node.terminal_result is not None for node in value)
|
|
186
|
+
if terminal_count > MAX_TERMINAL_NODES:
|
|
187
|
+
raise ValueError("nodes may contain at most 64 terminal nodes")
|
|
188
|
+
return value
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class StageScopeSource(BaseModel):
|
|
192
|
+
"""Immutable source stage-scope declaration."""
|
|
193
|
+
|
|
194
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
195
|
+
|
|
196
|
+
stage_kind_ids: tuple[StrictStr, ...] = Field(min_length=1)
|
|
197
|
+
|
|
198
|
+
@field_validator("stage_kind_ids")
|
|
199
|
+
@classmethod
|
|
200
|
+
def _stage_kind_ids_valid(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
|
201
|
+
if len(value) > MAX_STAGE_KIND_IDS:
|
|
202
|
+
raise ValueError("stage_kind_ids may contain at most 64 entries")
|
|
203
|
+
for item in value:
|
|
204
|
+
validate_stage_kind_id(item)
|
|
205
|
+
return validate_unique(value, "stage_kind_ids")
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class PromptSource(BaseModel):
|
|
209
|
+
"""Immutable source prompt-policy declaration."""
|
|
210
|
+
|
|
211
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
212
|
+
|
|
213
|
+
policy_id: StrictStr
|
|
214
|
+
system_instructions: StrictStr
|
|
215
|
+
include_request_context: StrictBool
|
|
216
|
+
|
|
217
|
+
@field_validator("policy_id")
|
|
218
|
+
@classmethod
|
|
219
|
+
def _policy_id_valid(cls, value: str) -> str:
|
|
220
|
+
return validate_policy_id(value)
|
|
221
|
+
|
|
222
|
+
@field_validator("system_instructions")
|
|
223
|
+
@classmethod
|
|
224
|
+
def _system_instructions_valid(cls, value: str) -> str:
|
|
225
|
+
if not value.strip():
|
|
226
|
+
raise ValueError("system_instructions must be nonblank")
|
|
227
|
+
return validate_utf8_size(
|
|
228
|
+
value, "system_instructions", MAX_SYSTEM_INSTRUCTIONS_UTF8
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class BudgetSource(BaseModel):
|
|
233
|
+
"""Immutable source budget declaration with no implicit defaults."""
|
|
234
|
+
|
|
235
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
236
|
+
|
|
237
|
+
max_iterations: StrictInt = Field(ge=1, le=256)
|
|
238
|
+
max_validation_retries: StrictInt = Field(ge=1, le=64)
|
|
239
|
+
max_tool_errors: StrictInt = Field(ge=1, le=64)
|
|
240
|
+
max_prerequisite_violations: StrictInt = Field(ge=1, le=64)
|
|
241
|
+
max_premature_terminal_attempts: StrictInt = Field(ge=1, le=64)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class ContextPolicySource(BaseModel):
|
|
245
|
+
"""Immutable source context-policy declaration."""
|
|
246
|
+
|
|
247
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
248
|
+
|
|
249
|
+
strategy_id: Literal["forge.tiered.v1"]
|
|
250
|
+
budget_tokens: StrictInt = Field(ge=256, le=1_000_000)
|
|
251
|
+
keep_recent_iterations: StrictInt = Field(ge=0, le=64)
|
|
252
|
+
phase_thresholds: tuple[float, float, float]
|
|
253
|
+
|
|
254
|
+
@field_validator("phase_thresholds", mode="before")
|
|
255
|
+
@classmethod
|
|
256
|
+
def _thresholds_scalar_valid(cls, value: Any) -> tuple[float, float, float]:
|
|
257
|
+
if not isinstance(value, (tuple, list)) or len(value) != 3:
|
|
258
|
+
raise ValueError("phase_thresholds must contain exactly three values")
|
|
259
|
+
thresholds = tuple(
|
|
260
|
+
validate_threshold(item, "phase_thresholds") for item in value
|
|
261
|
+
)
|
|
262
|
+
return (thresholds[0], thresholds[1], thresholds[2])
|
|
263
|
+
|
|
264
|
+
@field_validator("phase_thresholds")
|
|
265
|
+
@classmethod
|
|
266
|
+
def _thresholds_ordered(
|
|
267
|
+
cls, value: tuple[float, float, float]
|
|
268
|
+
) -> tuple[float, float, float]:
|
|
269
|
+
if any(not math.isfinite(item) for item in value):
|
|
270
|
+
raise ValueError("phase_thresholds must be finite")
|
|
271
|
+
if tuple(sorted(value)) != value:
|
|
272
|
+
raise ValueError("phase_thresholds must be non-decreasing")
|
|
273
|
+
return value
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class TerminalArtifactPolicySource(BaseModel):
|
|
277
|
+
"""Immutable terminal-to-required-artifacts policy entry."""
|
|
278
|
+
|
|
279
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
280
|
+
|
|
281
|
+
terminal_result: StrictStr
|
|
282
|
+
artifact_ids: tuple[StrictStr, ...] = Field(min_length=1)
|
|
283
|
+
|
|
284
|
+
@field_validator("terminal_result")
|
|
285
|
+
@classmethod
|
|
286
|
+
def _terminal_result_valid(cls, value: str) -> str:
|
|
287
|
+
return validate_terminal_result(value)
|
|
288
|
+
|
|
289
|
+
@field_validator("artifact_ids")
|
|
290
|
+
@classmethod
|
|
291
|
+
def _artifact_ids_valid(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
|
292
|
+
if len(value) > MAX_ARTIFACTS_PER_TERMINAL:
|
|
293
|
+
raise ValueError("artifact_ids may contain at most 512 entries")
|
|
294
|
+
for item in value:
|
|
295
|
+
validate_artifact_id(item)
|
|
296
|
+
return validate_unique(value, "artifact_ids")
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class ArtifactPolicySource(BaseModel):
|
|
300
|
+
"""Immutable source artifact policy declaration."""
|
|
301
|
+
|
|
302
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
303
|
+
|
|
304
|
+
declared_artifact_ids: tuple[StrictStr, ...] = Field(default_factory=tuple)
|
|
305
|
+
required_by_terminal: tuple[TerminalArtifactPolicySource, ...] = Field(
|
|
306
|
+
default_factory=tuple
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
@model_validator(mode="before")
|
|
310
|
+
@classmethod
|
|
311
|
+
def _convert_terminal_mapping(cls, data: Any) -> Any:
|
|
312
|
+
if not isinstance(data, Mapping):
|
|
313
|
+
return data
|
|
314
|
+
values = dict(data)
|
|
315
|
+
raw_required = values.get("required_by_terminal")
|
|
316
|
+
if isinstance(raw_required, Mapping):
|
|
317
|
+
values["required_by_terminal"] = tuple(
|
|
318
|
+
{"terminal_result": terminal_result, "artifact_ids": artifact_ids}
|
|
319
|
+
for terminal_result, artifact_ids in raw_required.items()
|
|
320
|
+
)
|
|
321
|
+
return values
|
|
322
|
+
|
|
323
|
+
@field_validator("declared_artifact_ids")
|
|
324
|
+
@classmethod
|
|
325
|
+
def _declared_artifact_ids_valid(cls, value: tuple[str, ...]) -> tuple[str, ...]:
|
|
326
|
+
if len(value) > MAX_DECLARED_ARTIFACTS:
|
|
327
|
+
raise ValueError("declared_artifact_ids may contain at most 512 entries")
|
|
328
|
+
for item in value:
|
|
329
|
+
validate_artifact_id(item)
|
|
330
|
+
return validate_unique(value, "declared_artifact_ids")
|
|
331
|
+
|
|
332
|
+
@field_validator("required_by_terminal")
|
|
333
|
+
@classmethod
|
|
334
|
+
def _required_by_terminal_limited(
|
|
335
|
+
cls, value: tuple[TerminalArtifactPolicySource, ...]
|
|
336
|
+
) -> tuple[TerminalArtifactPolicySource, ...]:
|
|
337
|
+
if len(value) > MAX_TERMINAL_ARTIFACT_POLICIES:
|
|
338
|
+
raise ValueError("required_by_terminal may contain at most 64 entries")
|
|
339
|
+
validate_unique(
|
|
340
|
+
tuple(item.terminal_result for item in value), "required_by_terminal"
|
|
341
|
+
)
|
|
342
|
+
return value
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class HarnessSource(BaseModel):
|
|
346
|
+
"""Immutable validated Millforge harness source contract."""
|
|
347
|
+
|
|
348
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
349
|
+
|
|
350
|
+
schema_version: Literal["1.0"]
|
|
351
|
+
kind: Literal["millforge_harness"]
|
|
352
|
+
harness_id: StrictStr
|
|
353
|
+
harness_version: StrictInt
|
|
354
|
+
stage_scope: StageScopeSource
|
|
355
|
+
model_profile_id: StrictStr
|
|
356
|
+
prompt: PromptSource
|
|
357
|
+
budgets: BudgetSource
|
|
358
|
+
context: ContextPolicySource
|
|
359
|
+
graph: HarnessGraphSource
|
|
360
|
+
artifacts: ArtifactPolicySource
|
|
361
|
+
|
|
362
|
+
@field_validator("harness_id")
|
|
363
|
+
@classmethod
|
|
364
|
+
def _harness_id_valid(cls, value: str) -> str:
|
|
365
|
+
return validate_harness_id(value)
|
|
366
|
+
|
|
367
|
+
@field_validator("harness_version")
|
|
368
|
+
@classmethod
|
|
369
|
+
def _harness_version_valid(cls, value: int) -> int:
|
|
370
|
+
return validate_harness_version(value)
|
|
371
|
+
|
|
372
|
+
@field_validator("model_profile_id")
|
|
373
|
+
@classmethod
|
|
374
|
+
def _model_profile_id_valid(cls, value: str) -> str:
|
|
375
|
+
return validate_profile_id(value)
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Shared validators for Millforge harness source contracts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import re
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
HARNESS_ID_MAX_LENGTH = 160
|
|
10
|
+
POLICY_ID_MAX_LENGTH = 160
|
|
11
|
+
PROFILE_ID_MAX_LENGTH = 160
|
|
12
|
+
CANONICAL_TOOL_ID_MAX_LENGTH = 160
|
|
13
|
+
CAPABILITY_ID_MAX_LENGTH = CANONICAL_TOOL_ID_MAX_LENGTH
|
|
14
|
+
NODE_ID_MAX_LENGTH = 64
|
|
15
|
+
ARTIFACT_ID_MAX_LENGTH = 128
|
|
16
|
+
TERMINAL_RESULT_MAX_LENGTH = 128
|
|
17
|
+
ARGUMENT_NAME_MAX_LENGTH = 128
|
|
18
|
+
REQUEST_ID_MAX_LENGTH = 160
|
|
19
|
+
TOOL_VERSION_MAX = 2_147_483_647
|
|
20
|
+
HARNESS_VERSION_MAX = 2_147_483_647
|
|
21
|
+
|
|
22
|
+
_DOTTED_ID_RE = re.compile(r"^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$")
|
|
23
|
+
_NODE_ID_RE = re.compile(r"^[a-z][a-z0-9_]{0,63}$")
|
|
24
|
+
_ARTIFACT_ID_RE = re.compile(r"^[a-z][a-z0-9._-]{0,127}$")
|
|
25
|
+
_TERMINAL_RESULT_RE = re.compile(r"^[A-Z][A-Z0-9_]{0,127}$")
|
|
26
|
+
_ARGUMENT_NAME_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]{0,127}$")
|
|
27
|
+
_TOOL_REF_RE = re.compile(
|
|
28
|
+
r"^(?P<tool_id>[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*)@(?P<version>[0-9]+)$"
|
|
29
|
+
)
|
|
30
|
+
_LOWER_FIELD_KEY_RE = re.compile(r"^[a-z][a-z0-9_]{0,63}$")
|
|
31
|
+
_SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class ToolReference:
|
|
36
|
+
"""Parsed exact-version tool reference."""
|
|
37
|
+
|
|
38
|
+
tool_id: str
|
|
39
|
+
version: int
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def validate_nonblank(value: str, field_name: str) -> str:
|
|
43
|
+
"""Validate a strict string is nonblank without normalizing it."""
|
|
44
|
+
if not value.strip():
|
|
45
|
+
raise ValueError(f"{field_name} must be a non-empty string")
|
|
46
|
+
return value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def validate_utf8_size(value: str, field_name: str, maximum: int) -> str:
|
|
50
|
+
"""Validate a string's UTF-8 encoded size."""
|
|
51
|
+
if len(value.encode("utf-8")) > maximum:
|
|
52
|
+
raise ValueError(f"{field_name} must be at most {maximum} UTF-8 bytes")
|
|
53
|
+
return value
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def validate_unique(values: tuple[str, ...], field_name: str) -> tuple[str, ...]:
|
|
57
|
+
"""Validate a tuple has no duplicate values."""
|
|
58
|
+
if len(set(values)) != len(values):
|
|
59
|
+
raise ValueError(f"{field_name} values must be unique")
|
|
60
|
+
return values
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def validate_harness_id(value: str) -> str:
|
|
64
|
+
return _validate_pattern(value, "harness_id", _DOTTED_ID_RE, HARNESS_ID_MAX_LENGTH)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def validate_policy_id(value: str) -> str:
|
|
68
|
+
return _validate_pattern(value, "policy_id", _DOTTED_ID_RE, POLICY_ID_MAX_LENGTH)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def validate_profile_id(value: str) -> str:
|
|
72
|
+
return _validate_pattern(value, "profile_id", _DOTTED_ID_RE, PROFILE_ID_MAX_LENGTH)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def validate_canonical_tool_id(value: str) -> str:
|
|
76
|
+
return _validate_pattern(
|
|
77
|
+
value, "tool_id", _DOTTED_ID_RE, CANONICAL_TOOL_ID_MAX_LENGTH
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def validate_capability_id(value: str) -> str:
|
|
82
|
+
return _validate_pattern(
|
|
83
|
+
value, "capability_id", _DOTTED_ID_RE, CAPABILITY_ID_MAX_LENGTH
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def validate_stage_kind_id(value: str) -> str:
|
|
88
|
+
return _validate_pattern(
|
|
89
|
+
value, "stage_kind_id", _DOTTED_ID_RE, POLICY_ID_MAX_LENGTH
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def validate_request_id(value: str) -> str:
|
|
94
|
+
return _validate_pattern(value, "request_id", _DOTTED_ID_RE, REQUEST_ID_MAX_LENGTH)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def validate_node_id(value: str) -> str:
|
|
98
|
+
return _validate_pattern(value, "node_id", _NODE_ID_RE, NODE_ID_MAX_LENGTH)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def validate_artifact_id(value: str) -> str:
|
|
102
|
+
return _validate_pattern(
|
|
103
|
+
value, "artifact_id", _ARTIFACT_ID_RE, ARTIFACT_ID_MAX_LENGTH
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def validate_terminal_result(value: str) -> str:
|
|
108
|
+
return _validate_pattern(
|
|
109
|
+
value, "terminal_result", _TERMINAL_RESULT_RE, TERMINAL_RESULT_MAX_LENGTH
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def validate_argument_name(value: str) -> str:
|
|
114
|
+
return _validate_pattern(
|
|
115
|
+
value, "argument_name", _ARGUMENT_NAME_RE, ARGUMENT_NAME_MAX_LENGTH
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def validate_lower_field_key(value: str) -> str:
|
|
120
|
+
return _validate_pattern(value, "key", _LOWER_FIELD_KEY_RE, 64)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def validate_sha256(value: str, field_name: str) -> str:
|
|
124
|
+
if not _SHA256_RE.fullmatch(value):
|
|
125
|
+
raise ValueError(f"{field_name} must be exactly 64 lowercase hex characters")
|
|
126
|
+
return value
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def validate_harness_version(value: int) -> int:
|
|
130
|
+
if value < 1 or value > HARNESS_VERSION_MAX:
|
|
131
|
+
raise ValueError("harness_version must be in range 1..2147483647")
|
|
132
|
+
return value
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def validate_tool_version(value: int) -> int:
|
|
136
|
+
if value < 1 or value > TOOL_VERSION_MAX:
|
|
137
|
+
raise ValueError("tool_version must be in range 1..2147483647")
|
|
138
|
+
return value
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def validate_tool_reference(value: str) -> str:
|
|
142
|
+
parse_tool_reference(value)
|
|
143
|
+
return value
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def parse_tool_reference(value: str) -> ToolReference:
|
|
147
|
+
"""Parse and validate ``<canonical tool ID>@<positive integer version>``."""
|
|
148
|
+
match = _TOOL_REF_RE.fullmatch(value)
|
|
149
|
+
if match is None:
|
|
150
|
+
raise ValueError("tool_ref must be an exact-version tool reference")
|
|
151
|
+
tool_id = validate_canonical_tool_id(match.group("tool_id"))
|
|
152
|
+
version_text = match.group("version")
|
|
153
|
+
if len(version_text) > 1 and version_text.startswith("0"):
|
|
154
|
+
raise ValueError("tool_ref version must not contain leading zeroes")
|
|
155
|
+
version = int(version_text)
|
|
156
|
+
if version < 1 or version > TOOL_VERSION_MAX:
|
|
157
|
+
raise ValueError("tool_ref version must be in range 1..2147483647")
|
|
158
|
+
return ToolReference(tool_id=tool_id, version=version)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def validate_threshold(value: object, field_name: str) -> float:
|
|
162
|
+
"""Validate a context threshold scalar.
|
|
163
|
+
|
|
164
|
+
Integer values are accepted for explicitly float-valued thresholds, but
|
|
165
|
+
strings and booleans are not coerced.
|
|
166
|
+
"""
|
|
167
|
+
if isinstance(value, bool) or not isinstance(value, (float, int)):
|
|
168
|
+
raise ValueError(f"{field_name} must be a finite number")
|
|
169
|
+
threshold = float(value)
|
|
170
|
+
if not math.isfinite(threshold):
|
|
171
|
+
raise ValueError(f"{field_name} must be finite")
|
|
172
|
+
if threshold <= 0 or threshold > 1:
|
|
173
|
+
raise ValueError(f"{field_name} must be in (0, 1]")
|
|
174
|
+
return threshold
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _validate_pattern(
|
|
178
|
+
value: str, field_name: str, pattern: re.Pattern[str], maximum: int
|
|
179
|
+
) -> str:
|
|
180
|
+
if len(value) > maximum:
|
|
181
|
+
raise ValueError(f"{field_name} must be at most {maximum} characters")
|
|
182
|
+
if pattern.fullmatch(value) is None:
|
|
183
|
+
raise ValueError(f"{field_name} has invalid format")
|
|
184
|
+
return value
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Connector admission contracts for deterministic offline admission."""
|
|
2
|
+
|
|
3
|
+
from millforge.connectors.contracts import (
|
|
4
|
+
CONNECTOR_ADMISSION_MANIFEST_KIND,
|
|
5
|
+
CONNECTOR_ADMISSION_MANIFEST_SCHEMA,
|
|
6
|
+
CONNECTOR_ADMISSION_MANIFEST_VERSION,
|
|
7
|
+
CONNECTOR_ADMISSION_RECORD_HASH_KIND,
|
|
8
|
+
CONNECTOR_DISCOVERY_SNAPSHOT_HASH_KIND,
|
|
9
|
+
CONNECTOR_DISCOVERY_TOOL_HASH_KIND,
|
|
10
|
+
CONNECTOR_IDENTITY_HASH_KIND,
|
|
11
|
+
ConnectorAdmissionManifest,
|
|
12
|
+
ConnectorAdmissionPolicy,
|
|
13
|
+
ConnectorAdmissionRecord,
|
|
14
|
+
ConnectorAdmissionResult,
|
|
15
|
+
ConnectorApprovalPolicy,
|
|
16
|
+
ConnectorContractValidation,
|
|
17
|
+
ConnectorDiscoverySnapshot,
|
|
18
|
+
ConnectorIdentity,
|
|
19
|
+
ConnectorProtocol,
|
|
20
|
+
ConnectorToolSelection,
|
|
21
|
+
ConnectorTransportKind,
|
|
22
|
+
DeniedConnectorTool,
|
|
23
|
+
DescriptionPolicy,
|
|
24
|
+
DiscoveredProviderTool,
|
|
25
|
+
ExpectedConnectorIdentity,
|
|
26
|
+
InputSchemaPolicy,
|
|
27
|
+
OutputSchemaPolicy,
|
|
28
|
+
)
|
|
29
|
+
from millforge.connectors.diagnostics import (
|
|
30
|
+
ConnectorDiagnostic,
|
|
31
|
+
ConnectorDiagnosticCode,
|
|
32
|
+
ConnectorDiagnosticEvidence,
|
|
33
|
+
ConnectorDiagnosticPhase,
|
|
34
|
+
ConnectorDiagnosticSeverity,
|
|
35
|
+
connector_diagnostic,
|
|
36
|
+
malformed_input_diagnostic,
|
|
37
|
+
)
|
|
38
|
+
from millforge.connectors.admission import admit_connector_tools
|
|
39
|
+
from millforge.connectors.broker import (
|
|
40
|
+
ConnectorBroker,
|
|
41
|
+
ConnectorBrokerOutcome,
|
|
42
|
+
ConnectorInvocationRequest,
|
|
43
|
+
ConnectorProviderToolEvidence,
|
|
44
|
+
connector_idempotency_key,
|
|
45
|
+
)
|
|
46
|
+
from millforge.connectors.fake import DeterministicFakeConnectorBroker
|
|
47
|
+
from millforge.connectors.runtime import (
|
|
48
|
+
ConnectorAdmissionBinding,
|
|
49
|
+
ConnectorAdmissionSnapshot,
|
|
50
|
+
ConnectorAdmissionSnapshotError,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"CONNECTOR_ADMISSION_MANIFEST_KIND",
|
|
55
|
+
"CONNECTOR_ADMISSION_MANIFEST_SCHEMA",
|
|
56
|
+
"CONNECTOR_ADMISSION_MANIFEST_VERSION",
|
|
57
|
+
"CONNECTOR_ADMISSION_RECORD_HASH_KIND",
|
|
58
|
+
"CONNECTOR_DISCOVERY_SNAPSHOT_HASH_KIND",
|
|
59
|
+
"CONNECTOR_DISCOVERY_TOOL_HASH_KIND",
|
|
60
|
+
"CONNECTOR_IDENTITY_HASH_KIND",
|
|
61
|
+
"ConnectorAdmissionManifest",
|
|
62
|
+
"ConnectorAdmissionPolicy",
|
|
63
|
+
"ConnectorAdmissionRecord",
|
|
64
|
+
"ConnectorAdmissionResult",
|
|
65
|
+
"ConnectorAdmissionBinding",
|
|
66
|
+
"ConnectorAdmissionSnapshot",
|
|
67
|
+
"ConnectorAdmissionSnapshotError",
|
|
68
|
+
"ConnectorBroker",
|
|
69
|
+
"ConnectorBrokerOutcome",
|
|
70
|
+
"ConnectorInvocationRequest",
|
|
71
|
+
"ConnectorProviderToolEvidence",
|
|
72
|
+
"ConnectorApprovalPolicy",
|
|
73
|
+
"ConnectorContractValidation",
|
|
74
|
+
"ConnectorDiagnostic",
|
|
75
|
+
"ConnectorDiagnosticCode",
|
|
76
|
+
"ConnectorDiagnosticEvidence",
|
|
77
|
+
"ConnectorDiagnosticPhase",
|
|
78
|
+
"ConnectorDiagnosticSeverity",
|
|
79
|
+
"ConnectorDiscoverySnapshot",
|
|
80
|
+
"ConnectorIdentity",
|
|
81
|
+
"ConnectorProtocol",
|
|
82
|
+
"ConnectorToolSelection",
|
|
83
|
+
"ConnectorTransportKind",
|
|
84
|
+
"DeniedConnectorTool",
|
|
85
|
+
"DeterministicFakeConnectorBroker",
|
|
86
|
+
"DescriptionPolicy",
|
|
87
|
+
"DiscoveredProviderTool",
|
|
88
|
+
"ExpectedConnectorIdentity",
|
|
89
|
+
"InputSchemaPolicy",
|
|
90
|
+
"OutputSchemaPolicy",
|
|
91
|
+
"admit_connector_tools",
|
|
92
|
+
"connector_idempotency_key",
|
|
93
|
+
"connector_diagnostic",
|
|
94
|
+
"malformed_input_diagnostic",
|
|
95
|
+
]
|