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,231 @@
|
|
|
1
|
+
"""Compiler front-end contracts for Millforge harness sources."""
|
|
2
|
+
|
|
3
|
+
from millforge.compiler.diagnostics import (
|
|
4
|
+
DIAGNOSTIC_REGISTRY,
|
|
5
|
+
DIAGNOSTIC_TRIGGER_MEANINGS,
|
|
6
|
+
CompilerDiagnostic,
|
|
7
|
+
CompilerPhase,
|
|
8
|
+
DiagnosticField,
|
|
9
|
+
DiagnosticSeverity,
|
|
10
|
+
SourceLocation,
|
|
11
|
+
SourceReference,
|
|
12
|
+
detect_secret_candidate,
|
|
13
|
+
sort_diagnostics,
|
|
14
|
+
)
|
|
15
|
+
from millforge.compiler.catalogs import (
|
|
16
|
+
CatalogLookupClassification,
|
|
17
|
+
CatalogMetadataError,
|
|
18
|
+
CatalogSnapshotMetadata,
|
|
19
|
+
ModelProfileCatalogLookup,
|
|
20
|
+
ModelProfileCatalogSnapshot,
|
|
21
|
+
RawToolDescriptor,
|
|
22
|
+
ToolCatalogEntry,
|
|
23
|
+
ToolCatalogLookup,
|
|
24
|
+
ToolCatalogSnapshot,
|
|
25
|
+
admit_model_profile,
|
|
26
|
+
capture_catalog_snapshot_metadata,
|
|
27
|
+
)
|
|
28
|
+
from millforge.compiler.artifact_validation import (
|
|
29
|
+
ArtifactProducerEvidence,
|
|
30
|
+
ArtifactValidationResult,
|
|
31
|
+
validate_artifacts,
|
|
32
|
+
)
|
|
33
|
+
from millforge.compiler.capabilities import (
|
|
34
|
+
CapabilityValidationResult,
|
|
35
|
+
validate_capability_grants,
|
|
36
|
+
)
|
|
37
|
+
from millforge.compiler.canonicalization import (
|
|
38
|
+
canonical_semantic_bytes,
|
|
39
|
+
canonical_semantic_payload,
|
|
40
|
+
source_sha256,
|
|
41
|
+
)
|
|
42
|
+
from millforge.compiler.graph import (
|
|
43
|
+
GraphValidationResult,
|
|
44
|
+
ResolvedNodeDescriptor,
|
|
45
|
+
validate_harness_graph,
|
|
46
|
+
)
|
|
47
|
+
from millforge.compiler.lowering import (
|
|
48
|
+
COMPILER_BUILD_ID,
|
|
49
|
+
COMPILER_NAME,
|
|
50
|
+
CompiledPlanValidationError,
|
|
51
|
+
LoweringInvariantError,
|
|
52
|
+
SourceSemanticHashError,
|
|
53
|
+
compiler_identity,
|
|
54
|
+
lower_resolved_harness,
|
|
55
|
+
)
|
|
56
|
+
from millforge.compiler.output import (
|
|
57
|
+
compiled_plan_output_path,
|
|
58
|
+
diagnostics_output_path,
|
|
59
|
+
persist_compile_outputs,
|
|
60
|
+
request_identity_sha256,
|
|
61
|
+
)
|
|
62
|
+
from millforge.compiler.parsing import (
|
|
63
|
+
HarnessSourceParser,
|
|
64
|
+
ParsedHarnessSource,
|
|
65
|
+
SourceDocument,
|
|
66
|
+
)
|
|
67
|
+
from millforge.compiler.requests import (
|
|
68
|
+
CompileInvocation,
|
|
69
|
+
CompileStatus,
|
|
70
|
+
DefaultHarnessCompileRequestAdmission,
|
|
71
|
+
DiagnosticReportState,
|
|
72
|
+
HarnessCompileRequest,
|
|
73
|
+
HarnessCompileRequestAdmission,
|
|
74
|
+
HarnessCompileResult,
|
|
75
|
+
HarnessRequestAdmissionResult,
|
|
76
|
+
PlanCommitCertainty,
|
|
77
|
+
)
|
|
78
|
+
from millforge.compiler.service import (
|
|
79
|
+
HarnessCompiler,
|
|
80
|
+
InMemoryHarnessCompileError,
|
|
81
|
+
compile,
|
|
82
|
+
compile_harness_source_in_memory,
|
|
83
|
+
compile_raw,
|
|
84
|
+
)
|
|
85
|
+
from millforge.compiler.semantic import (
|
|
86
|
+
ResolvedHarness,
|
|
87
|
+
ResolvedToolBinding,
|
|
88
|
+
ResolvedToolBindingRef,
|
|
89
|
+
SemanticCompileResult,
|
|
90
|
+
compile_semantic,
|
|
91
|
+
compile_semantic_from_admission,
|
|
92
|
+
)
|
|
93
|
+
from millforge.compiler.schema_validation import (
|
|
94
|
+
SCHEMA_ALLOWED_KEYWORDS,
|
|
95
|
+
SCHEMA_ANNOTATION_KEYWORDS,
|
|
96
|
+
SCHEMA_STRUCTURAL_KEYWORDS,
|
|
97
|
+
SchemaSubsetError,
|
|
98
|
+
normalize_json_schema,
|
|
99
|
+
normalized_schema_bytes,
|
|
100
|
+
property_schema_compatibility_bytes,
|
|
101
|
+
validate_json_schema_subset,
|
|
102
|
+
)
|
|
103
|
+
from millforge.compiler.source import (
|
|
104
|
+
ArgumentMatchSource,
|
|
105
|
+
ArtifactPolicySource,
|
|
106
|
+
BudgetSource,
|
|
107
|
+
ContextPolicySource,
|
|
108
|
+
HarnessGraphSource,
|
|
109
|
+
HarnessNodeSource,
|
|
110
|
+
HarnessSource,
|
|
111
|
+
PrerequisiteSource,
|
|
112
|
+
PromptSource,
|
|
113
|
+
StageScopeSource,
|
|
114
|
+
TerminalArtifactPolicySource,
|
|
115
|
+
)
|
|
116
|
+
from millforge.compiler.validators import (
|
|
117
|
+
ToolReference,
|
|
118
|
+
parse_tool_reference,
|
|
119
|
+
validate_argument_name,
|
|
120
|
+
validate_artifact_id,
|
|
121
|
+
validate_canonical_tool_id,
|
|
122
|
+
validate_harness_id,
|
|
123
|
+
validate_node_id,
|
|
124
|
+
validate_policy_id,
|
|
125
|
+
validate_profile_id,
|
|
126
|
+
validate_request_id,
|
|
127
|
+
validate_stage_kind_id,
|
|
128
|
+
validate_terminal_result,
|
|
129
|
+
validate_tool_reference,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
__all__ = [
|
|
133
|
+
"ArgumentMatchSource",
|
|
134
|
+
"ArtifactProducerEvidence",
|
|
135
|
+
"ArtifactPolicySource",
|
|
136
|
+
"ArtifactValidationResult",
|
|
137
|
+
"BudgetSource",
|
|
138
|
+
"CapabilityValidationResult",
|
|
139
|
+
"CatalogLookupClassification",
|
|
140
|
+
"CatalogMetadataError",
|
|
141
|
+
"CatalogSnapshotMetadata",
|
|
142
|
+
"canonical_semantic_bytes",
|
|
143
|
+
"canonical_semantic_payload",
|
|
144
|
+
"CompileInvocation",
|
|
145
|
+
"CompileStatus",
|
|
146
|
+
"CompilerDiagnostic",
|
|
147
|
+
"CompilerPhase",
|
|
148
|
+
"ContextPolicySource",
|
|
149
|
+
"DIAGNOSTIC_REGISTRY",
|
|
150
|
+
"DIAGNOSTIC_TRIGGER_MEANINGS",
|
|
151
|
+
"DefaultHarnessCompileRequestAdmission",
|
|
152
|
+
"DiagnosticReportState",
|
|
153
|
+
"DiagnosticField",
|
|
154
|
+
"DiagnosticSeverity",
|
|
155
|
+
"HarnessCompileRequest",
|
|
156
|
+
"HarnessCompileRequestAdmission",
|
|
157
|
+
"HarnessCompileResult",
|
|
158
|
+
"HarnessCompiler",
|
|
159
|
+
"InMemoryHarnessCompileError",
|
|
160
|
+
"HarnessGraphSource",
|
|
161
|
+
"HarnessNodeSource",
|
|
162
|
+
"HarnessRequestAdmissionResult",
|
|
163
|
+
"HarnessSource",
|
|
164
|
+
"HarnessSourceParser",
|
|
165
|
+
"GraphValidationResult",
|
|
166
|
+
"LoweringInvariantError",
|
|
167
|
+
"COMPILER_BUILD_ID",
|
|
168
|
+
"COMPILER_NAME",
|
|
169
|
+
"CompiledPlanValidationError",
|
|
170
|
+
"ModelProfileCatalogLookup",
|
|
171
|
+
"ModelProfileCatalogSnapshot",
|
|
172
|
+
"ParsedHarnessSource",
|
|
173
|
+
"PlanCommitCertainty",
|
|
174
|
+
"PrerequisiteSource",
|
|
175
|
+
"PromptSource",
|
|
176
|
+
"SourceDocument",
|
|
177
|
+
"SourceLocation",
|
|
178
|
+
"SourceReference",
|
|
179
|
+
"StageScopeSource",
|
|
180
|
+
"TerminalArtifactPolicySource",
|
|
181
|
+
"RawToolDescriptor",
|
|
182
|
+
"ResolvedHarness",
|
|
183
|
+
"ResolvedNodeDescriptor",
|
|
184
|
+
"ResolvedToolBinding",
|
|
185
|
+
"ResolvedToolBindingRef",
|
|
186
|
+
"SCHEMA_ALLOWED_KEYWORDS",
|
|
187
|
+
"SCHEMA_ANNOTATION_KEYWORDS",
|
|
188
|
+
"SCHEMA_STRUCTURAL_KEYWORDS",
|
|
189
|
+
"SchemaSubsetError",
|
|
190
|
+
"SemanticCompileResult",
|
|
191
|
+
"SourceSemanticHashError",
|
|
192
|
+
"source_sha256",
|
|
193
|
+
"ToolCatalogEntry",
|
|
194
|
+
"ToolCatalogLookup",
|
|
195
|
+
"ToolCatalogSnapshot",
|
|
196
|
+
"ToolReference",
|
|
197
|
+
"admit_model_profile",
|
|
198
|
+
"capture_catalog_snapshot_metadata",
|
|
199
|
+
"compiler_identity",
|
|
200
|
+
"compiled_plan_output_path",
|
|
201
|
+
"compile",
|
|
202
|
+
"compile_harness_source_in_memory",
|
|
203
|
+
"compile_raw",
|
|
204
|
+
"compile_semantic",
|
|
205
|
+
"compile_semantic_from_admission",
|
|
206
|
+
"diagnostics_output_path",
|
|
207
|
+
"detect_secret_candidate",
|
|
208
|
+
"normalize_json_schema",
|
|
209
|
+
"normalized_schema_bytes",
|
|
210
|
+
"lower_resolved_harness",
|
|
211
|
+
"persist_compile_outputs",
|
|
212
|
+
"parse_tool_reference",
|
|
213
|
+
"property_schema_compatibility_bytes",
|
|
214
|
+
"request_identity_sha256",
|
|
215
|
+
"sort_diagnostics",
|
|
216
|
+
"validate_artifacts",
|
|
217
|
+
"validate_argument_name",
|
|
218
|
+
"validate_artifact_id",
|
|
219
|
+
"validate_canonical_tool_id",
|
|
220
|
+
"validate_harness_id",
|
|
221
|
+
"validate_harness_graph",
|
|
222
|
+
"validate_capability_grants",
|
|
223
|
+
"validate_node_id",
|
|
224
|
+
"validate_policy_id",
|
|
225
|
+
"validate_profile_id",
|
|
226
|
+
"validate_request_id",
|
|
227
|
+
"validate_stage_kind_id",
|
|
228
|
+
"validate_terminal_result",
|
|
229
|
+
"validate_tool_reference",
|
|
230
|
+
"validate_json_schema_subset",
|
|
231
|
+
]
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""Artifact satisfiability validation for semantic compiler resolution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field, StrictStr
|
|
9
|
+
|
|
10
|
+
from millforge.compiler.catalogs import ToolCatalogEntry
|
|
11
|
+
from millforge.compiler.diagnostics import (
|
|
12
|
+
CompilerDiagnostic,
|
|
13
|
+
CompilerPhase,
|
|
14
|
+
DiagnosticField,
|
|
15
|
+
DiagnosticSeverity,
|
|
16
|
+
sort_diagnostics,
|
|
17
|
+
)
|
|
18
|
+
from millforge.compiler.graph import GraphValidationResult, ResolvedNodeDescriptor
|
|
19
|
+
from millforge.compiler.source import HarnessNodeSource, HarnessSource
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ArtifactProducerEvidence(BaseModel):
|
|
23
|
+
"""Producer evidence for one artifact ID."""
|
|
24
|
+
|
|
25
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
26
|
+
|
|
27
|
+
artifact_id: StrictStr
|
|
28
|
+
all_producer_node_ids: tuple[StrictStr, ...] = Field(default_factory=tuple)
|
|
29
|
+
terminal_gated_producer_node_ids: tuple[StrictStr, ...] = Field(
|
|
30
|
+
default_factory=tuple
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ArtifactValidationResult(BaseModel):
|
|
35
|
+
"""Immutable artifact validation result."""
|
|
36
|
+
|
|
37
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
38
|
+
|
|
39
|
+
diagnostics: tuple[CompilerDiagnostic, ...] = Field(default_factory=tuple)
|
|
40
|
+
producer_evidence: tuple[ArtifactProducerEvidence, ...] = Field(
|
|
41
|
+
default_factory=tuple
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def ok(self) -> bool:
|
|
46
|
+
return not self.diagnostics
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def validate_artifacts(
|
|
50
|
+
source: HarnessSource,
|
|
51
|
+
resolved_nodes: Mapping[str, ToolCatalogEntry | ResolvedNodeDescriptor],
|
|
52
|
+
graph_result: GraphValidationResult,
|
|
53
|
+
) -> ArtifactValidationResult:
|
|
54
|
+
"""Validate source artifact declarations against resolved producer evidence."""
|
|
55
|
+
nodes_by_id = {node.node_id: node for node in source.graph.nodes}
|
|
56
|
+
descriptors = _resolved_entries(resolved_nodes)
|
|
57
|
+
declared = set(source.artifacts.declared_artifact_ids)
|
|
58
|
+
diagnostics: list[CompilerDiagnostic] = []
|
|
59
|
+
valid_producers: dict[str, list[str]] = defaultdict(list)
|
|
60
|
+
|
|
61
|
+
for artifact_id in _duplicates(source.artifacts.declared_artifact_ids):
|
|
62
|
+
diagnostics.append(
|
|
63
|
+
_diagnostic(
|
|
64
|
+
"MF-A006",
|
|
65
|
+
f"Declared artifact {artifact_id!r} is duplicated.",
|
|
66
|
+
artifact_id=artifact_id,
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
for node in sorted(source.graph.nodes, key=lambda item: item.node_id):
|
|
71
|
+
descriptor = descriptors.get(node.node_id)
|
|
72
|
+
if descriptor is None:
|
|
73
|
+
continue
|
|
74
|
+
descriptor_artifacts = set(descriptor.produced_artifact_ids)
|
|
75
|
+
for artifact_id in _duplicates(node.produces):
|
|
76
|
+
diagnostics.append(
|
|
77
|
+
_diagnostic(
|
|
78
|
+
"MF-A006",
|
|
79
|
+
f"Node {node.node_id!r} duplicates produced artifact {artifact_id!r}.",
|
|
80
|
+
node_id=node.node_id,
|
|
81
|
+
artifact_id=artifact_id,
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
for artifact_id in sorted(set(node.produces)):
|
|
85
|
+
if artifact_id not in declared:
|
|
86
|
+
diagnostics.append(
|
|
87
|
+
_diagnostic(
|
|
88
|
+
"MF-A001",
|
|
89
|
+
f"Node {node.node_id!r} produces undeclared artifact {artifact_id!r}.",
|
|
90
|
+
node_id=node.node_id,
|
|
91
|
+
artifact_id=artifact_id,
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
if artifact_id not in descriptor_artifacts:
|
|
95
|
+
diagnostics.append(
|
|
96
|
+
_diagnostic(
|
|
97
|
+
"MF-A002",
|
|
98
|
+
f"Node {node.node_id!r} cannot produce artifact {artifact_id!r}.",
|
|
99
|
+
node_id=node.node_id,
|
|
100
|
+
artifact_id=artifact_id,
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
if artifact_id in declared and artifact_id in descriptor_artifacts:
|
|
104
|
+
valid_producers[artifact_id].append(node.node_id)
|
|
105
|
+
|
|
106
|
+
terminal_results = set(graph_result.terminal_result_map.values())
|
|
107
|
+
terminal_nodes_by_result = {
|
|
108
|
+
result: node_id for node_id, result in graph_result.terminal_result_map.items()
|
|
109
|
+
}
|
|
110
|
+
for requirement in source.artifacts.required_by_terminal:
|
|
111
|
+
for artifact_id in _duplicates(requirement.artifact_ids):
|
|
112
|
+
diagnostics.append(
|
|
113
|
+
_diagnostic(
|
|
114
|
+
"MF-A006",
|
|
115
|
+
f"Terminal artifact policy {requirement.terminal_result!r} duplicates artifact {artifact_id!r}.",
|
|
116
|
+
artifact_id=artifact_id,
|
|
117
|
+
terminal_result=requirement.terminal_result,
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
if requirement.terminal_result not in terminal_results:
|
|
121
|
+
diagnostics.append(
|
|
122
|
+
_diagnostic(
|
|
123
|
+
"MF-A003",
|
|
124
|
+
f"Artifact policy references unknown terminal result {requirement.terminal_result!r}.",
|
|
125
|
+
terminal_result=requirement.terminal_result,
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
continue
|
|
129
|
+
for artifact_id in sorted(set(requirement.artifact_ids)):
|
|
130
|
+
if artifact_id not in declared:
|
|
131
|
+
diagnostics.append(
|
|
132
|
+
_diagnostic(
|
|
133
|
+
"MF-A007",
|
|
134
|
+
f"Terminal-required artifact {artifact_id!r} is not declared.",
|
|
135
|
+
artifact_id=artifact_id,
|
|
136
|
+
terminal_result=requirement.terminal_result,
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
producer_evidence: list[ArtifactProducerEvidence] = []
|
|
141
|
+
terminal_gated: dict[str, set[str]] = defaultdict(set)
|
|
142
|
+
if not graph_result.diagnostics:
|
|
143
|
+
ancestors_by_terminal = {
|
|
144
|
+
terminal_node_id: _ancestors(terminal_node_id, nodes_by_id)
|
|
145
|
+
for terminal_node_id in graph_result.terminal_node_ids
|
|
146
|
+
}
|
|
147
|
+
globally_required = set(graph_result.required_node_ids)
|
|
148
|
+
for requirement in source.artifacts.required_by_terminal:
|
|
149
|
+
terminal_node_id = terminal_nodes_by_result.get(requirement.terminal_result)
|
|
150
|
+
if terminal_node_id is None:
|
|
151
|
+
continue
|
|
152
|
+
gated_nodes = ancestors_by_terminal[terminal_node_id] | globally_required
|
|
153
|
+
for artifact_id in sorted(set(requirement.artifact_ids)):
|
|
154
|
+
if artifact_id not in declared:
|
|
155
|
+
continue
|
|
156
|
+
producers = set(valid_producers.get(artifact_id, ()))
|
|
157
|
+
gated_producers = producers & gated_nodes
|
|
158
|
+
terminal_gated[artifact_id].update(gated_producers)
|
|
159
|
+
if not producers:
|
|
160
|
+
diagnostics.append(
|
|
161
|
+
_diagnostic(
|
|
162
|
+
"MF-A004",
|
|
163
|
+
f"Terminal-required artifact {artifact_id!r} has no valid producer.",
|
|
164
|
+
artifact_id=artifact_id,
|
|
165
|
+
terminal_result=requirement.terminal_result,
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
elif not gated_producers:
|
|
169
|
+
diagnostics.append(
|
|
170
|
+
_diagnostic(
|
|
171
|
+
"MF-A005",
|
|
172
|
+
f"Terminal-required artifact {artifact_id!r} has no terminal-gated producer.",
|
|
173
|
+
artifact_id=artifact_id,
|
|
174
|
+
terminal_result=requirement.terminal_result,
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
for artifact_id in sorted(declared):
|
|
179
|
+
producer_evidence.append(
|
|
180
|
+
ArtifactProducerEvidence(
|
|
181
|
+
artifact_id=artifact_id,
|
|
182
|
+
all_producer_node_ids=tuple(
|
|
183
|
+
sorted(valid_producers.get(artifact_id, ()))
|
|
184
|
+
),
|
|
185
|
+
terminal_gated_producer_node_ids=tuple(
|
|
186
|
+
sorted(terminal_gated.get(artifact_id, ()))
|
|
187
|
+
),
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return ArtifactValidationResult(
|
|
192
|
+
diagnostics=sort_diagnostics(diagnostics),
|
|
193
|
+
producer_evidence=tuple(producer_evidence),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _resolved_entries(
|
|
198
|
+
resolved_nodes: Mapping[str, ToolCatalogEntry | ResolvedNodeDescriptor],
|
|
199
|
+
) -> dict[str, ToolCatalogEntry]:
|
|
200
|
+
entries: dict[str, ToolCatalogEntry] = {}
|
|
201
|
+
for node_id, value in resolved_nodes.items():
|
|
202
|
+
entries[node_id] = (
|
|
203
|
+
value.descriptor if isinstance(value, ResolvedNodeDescriptor) else value
|
|
204
|
+
)
|
|
205
|
+
return entries
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _ancestors(
|
|
209
|
+
terminal_node_id: str,
|
|
210
|
+
nodes_by_id: Mapping[str, HarnessNodeSource],
|
|
211
|
+
) -> set[str]:
|
|
212
|
+
reverse = {
|
|
213
|
+
node.node_id: tuple(prereq.node_id for prereq in node.prerequisites)
|
|
214
|
+
for node in nodes_by_id.values()
|
|
215
|
+
}
|
|
216
|
+
ancestors: set[str] = set()
|
|
217
|
+
stack = list(reverse[terminal_node_id])
|
|
218
|
+
while stack:
|
|
219
|
+
node_id = stack.pop()
|
|
220
|
+
if node_id in ancestors or node_id not in reverse:
|
|
221
|
+
continue
|
|
222
|
+
ancestors.add(node_id)
|
|
223
|
+
stack.extend(reverse[node_id])
|
|
224
|
+
return ancestors
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _duplicates(values: tuple[str, ...]) -> tuple[str, ...]:
|
|
228
|
+
seen: set[str] = set()
|
|
229
|
+
duplicates: set[str] = set()
|
|
230
|
+
for value in values:
|
|
231
|
+
if value in seen:
|
|
232
|
+
duplicates.add(value)
|
|
233
|
+
seen.add(value)
|
|
234
|
+
return tuple(sorted(duplicates))
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _diagnostic(
|
|
238
|
+
code: str,
|
|
239
|
+
message: str,
|
|
240
|
+
*,
|
|
241
|
+
node_id: str | None = None,
|
|
242
|
+
artifact_id: str | None = None,
|
|
243
|
+
terminal_result: str | None = None,
|
|
244
|
+
) -> CompilerDiagnostic:
|
|
245
|
+
fields = []
|
|
246
|
+
if artifact_id is not None:
|
|
247
|
+
fields.append(DiagnosticField(key="artifact_id", value=artifact_id))
|
|
248
|
+
if terminal_result is not None:
|
|
249
|
+
fields.append(DiagnosticField(key="terminal_result", value=terminal_result))
|
|
250
|
+
return CompilerDiagnostic(
|
|
251
|
+
code=code,
|
|
252
|
+
phase=CompilerPhase.ARTIFACT,
|
|
253
|
+
severity=DiagnosticSeverity.ERROR,
|
|
254
|
+
message=message,
|
|
255
|
+
node_id=node_id,
|
|
256
|
+
fields=tuple(fields),
|
|
257
|
+
)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Canonical semantic payload construction for deterministic compiler hashing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
import hashlib
|
|
7
|
+
from collections.abc import Mapping
|
|
8
|
+
from typing import Any, TypeAlias
|
|
9
|
+
|
|
10
|
+
from millforge import canonical_json_serialize
|
|
11
|
+
from millforge.compiler.semantic import ResolvedHarness
|
|
12
|
+
|
|
13
|
+
JsonScalar: TypeAlias = str | int | float | bool | None
|
|
14
|
+
JsonValue: TypeAlias = JsonScalar | list["JsonValue"] | dict[str, "JsonValue"]
|
|
15
|
+
JsonObject: TypeAlias = dict[str, JsonValue]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def canonical_semantic_payload(resolved: ResolvedHarness) -> JsonObject:
|
|
19
|
+
"""Return the validated source semantics used for ``source_sha256``.
|
|
20
|
+
|
|
21
|
+
The payload is built from fresh JSON-compatible containers and excludes
|
|
22
|
+
request identity, parser metadata, source locations, output paths, catalog
|
|
23
|
+
snapshot identity, and catalog-owned objects.
|
|
24
|
+
"""
|
|
25
|
+
source = resolved.source
|
|
26
|
+
return _fresh_json_object(
|
|
27
|
+
{
|
|
28
|
+
"schema_version": source.schema_version,
|
|
29
|
+
"kind": source.kind,
|
|
30
|
+
"harness_id": source.harness_id,
|
|
31
|
+
"harness_version": source.harness_version,
|
|
32
|
+
"stage_kind_ids": sorted(source.stage_scope.stage_kind_ids),
|
|
33
|
+
"model_profile": _fresh_json_value(
|
|
34
|
+
resolved.model_profile.model_dump(mode="json")
|
|
35
|
+
),
|
|
36
|
+
"prompt": {
|
|
37
|
+
"policy_id": source.prompt.policy_id,
|
|
38
|
+
"system_instructions": source.prompt.system_instructions,
|
|
39
|
+
"include_request_context": source.prompt.include_request_context,
|
|
40
|
+
},
|
|
41
|
+
"budgets": {
|
|
42
|
+
"max_iterations": source.budgets.max_iterations,
|
|
43
|
+
"max_validation_retries": source.budgets.max_validation_retries,
|
|
44
|
+
"max_tool_errors": source.budgets.max_tool_errors,
|
|
45
|
+
"max_prerequisite_violations": (
|
|
46
|
+
source.budgets.max_prerequisite_violations
|
|
47
|
+
),
|
|
48
|
+
"max_premature_terminal_attempts": (
|
|
49
|
+
source.budgets.max_premature_terminal_attempts
|
|
50
|
+
),
|
|
51
|
+
},
|
|
52
|
+
"context": {
|
|
53
|
+
"strategy_id": source.context.strategy_id,
|
|
54
|
+
"budget_tokens": source.context.budget_tokens,
|
|
55
|
+
"keep_recent_iterations": source.context.keep_recent_iterations,
|
|
56
|
+
"phase_thresholds": list(source.context.phase_thresholds),
|
|
57
|
+
},
|
|
58
|
+
"nodes": [_node_payload(node) for node in resolved.resolved_nodes],
|
|
59
|
+
"required_node_ids": sorted(resolved.required_node_ids),
|
|
60
|
+
"required_capability_ids": sorted(resolved.required_capability_ids),
|
|
61
|
+
"terminal_result_map": dict(sorted(resolved.terminal_result_map.items())),
|
|
62
|
+
"artifacts": {
|
|
63
|
+
"declared_artifact_ids": sorted(source.artifacts.declared_artifact_ids),
|
|
64
|
+
"required_by_terminal": [
|
|
65
|
+
{
|
|
66
|
+
"terminal_result": item.terminal_result,
|
|
67
|
+
"artifact_ids": sorted(item.artifact_ids),
|
|
68
|
+
}
|
|
69
|
+
for item in sorted(
|
|
70
|
+
source.artifacts.required_by_terminal,
|
|
71
|
+
key=lambda item: item.terminal_result,
|
|
72
|
+
)
|
|
73
|
+
],
|
|
74
|
+
"producer_evidence": [
|
|
75
|
+
{
|
|
76
|
+
"artifact_id": item.artifact_id,
|
|
77
|
+
"all_producer_node_ids": sorted(item.all_producer_node_ids),
|
|
78
|
+
"terminal_gated_producer_node_ids": sorted(
|
|
79
|
+
item.terminal_gated_producer_node_ids
|
|
80
|
+
),
|
|
81
|
+
}
|
|
82
|
+
for item in sorted(
|
|
83
|
+
resolved.artifact_evidence,
|
|
84
|
+
key=lambda item: item.artifact_id,
|
|
85
|
+
)
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def canonical_semantic_bytes(resolved: ResolvedHarness) -> bytes:
|
|
93
|
+
"""Return UTF-8 canonical JSON bytes for a resolved semantic payload."""
|
|
94
|
+
return canonical_json_serialize(canonical_semantic_payload(resolved)).encode(
|
|
95
|
+
"utf-8"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def source_sha256(resolved: ResolvedHarness) -> str:
|
|
100
|
+
"""Return SHA-256 over the canonical validated semantic payload."""
|
|
101
|
+
return hashlib.sha256(canonical_semantic_bytes(resolved)).hexdigest()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _node_payload(node: Any) -> JsonObject:
|
|
105
|
+
source = node.source
|
|
106
|
+
descriptor = node.descriptor
|
|
107
|
+
return _fresh_json_object(
|
|
108
|
+
{
|
|
109
|
+
"node_id": node.node_id,
|
|
110
|
+
"required": source.required,
|
|
111
|
+
"terminal_result": source.terminal_result,
|
|
112
|
+
"prerequisites": [
|
|
113
|
+
{
|
|
114
|
+
"node_id": prerequisite.node_id,
|
|
115
|
+
"argument_matches": [
|
|
116
|
+
{
|
|
117
|
+
"prior_argument": match.prior_argument,
|
|
118
|
+
"current_argument": match.current_argument,
|
|
119
|
+
}
|
|
120
|
+
for match in sorted(
|
|
121
|
+
prerequisite.argument_matches,
|
|
122
|
+
key=lambda item: (
|
|
123
|
+
item.prior_argument,
|
|
124
|
+
item.current_argument,
|
|
125
|
+
),
|
|
126
|
+
)
|
|
127
|
+
],
|
|
128
|
+
}
|
|
129
|
+
for prerequisite in sorted(
|
|
130
|
+
source.prerequisites, key=lambda item: item.node_id
|
|
131
|
+
)
|
|
132
|
+
],
|
|
133
|
+
"produces": sorted(source.produces),
|
|
134
|
+
"binding": {
|
|
135
|
+
"tool_id": node.binding.tool_id,
|
|
136
|
+
"tool_version": node.binding.tool_version,
|
|
137
|
+
"descriptor_sha256": node.binding.descriptor_sha256,
|
|
138
|
+
"implementation_id": node.binding.implementation_id,
|
|
139
|
+
},
|
|
140
|
+
"descriptor": {
|
|
141
|
+
"model_tool_name": descriptor.model_tool_name,
|
|
142
|
+
"description": descriptor.description,
|
|
143
|
+
"input_schema": _fresh_json_value(descriptor.input_schema),
|
|
144
|
+
"side_effect_class": descriptor.side_effect_class.value,
|
|
145
|
+
"idempotency": descriptor.idempotency.value,
|
|
146
|
+
"required_capabilities": sorted(descriptor.required_capabilities),
|
|
147
|
+
"produced_artifact_ids": sorted(descriptor.produced_artifact_ids),
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _fresh_json_object(value: Mapping[str, Any]) -> JsonObject:
|
|
154
|
+
converted = _fresh_json_value(value)
|
|
155
|
+
if not isinstance(converted, dict):
|
|
156
|
+
raise TypeError("expected JSON object")
|
|
157
|
+
return converted
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _fresh_json_value(value: Any) -> JsonValue:
|
|
161
|
+
if value is None or isinstance(value, str | int | bool):
|
|
162
|
+
return value
|
|
163
|
+
if isinstance(value, float):
|
|
164
|
+
return value
|
|
165
|
+
if isinstance(value, Mapping):
|
|
166
|
+
return {str(key): _fresh_json_value(item) for key, item in value.items()}
|
|
167
|
+
if isinstance(value, tuple | list):
|
|
168
|
+
return [_fresh_json_value(item) for item in value]
|
|
169
|
+
return copy.deepcopy(value)
|