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,272 @@
1
+ """Compiler-owned JSON Schema subset validation and normalization."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import Mapping
7
+ from types import MappingProxyType
8
+ from typing import Any
9
+
10
+ SCHEMA_ANNOTATION_KEYWORDS = frozenset({"default", "description"})
11
+ SCHEMA_STRUCTURAL_KEYWORDS = frozenset(
12
+ {"additionalProperties", "const", "enum", "items", "properties", "required", "type"}
13
+ )
14
+ SCHEMA_UNSUPPORTED_BOUND_KEYWORDS = frozenset(
15
+ {"maxItems", "maxLength", "maximum", "minimum"}
16
+ )
17
+ SCHEMA_ALLOWED_KEYWORDS = SCHEMA_ANNOTATION_KEYWORDS | SCHEMA_STRUCTURAL_KEYWORDS
18
+ SCHEMA_SCALAR_TYPES = frozenset({"boolean", "integer", "number", "string"})
19
+ SCHEMA_ALLOWED_TYPES = SCHEMA_SCALAR_TYPES | frozenset({"array", "object"})
20
+
21
+
22
+ class SchemaSubsetError(ValueError):
23
+ """Raised when a schema falls outside the compiler-owned 03B subset."""
24
+
25
+
26
+ def validate_json_schema_subset(
27
+ schema: Mapping[str, Any], *, field_name: str = "schema"
28
+ ) -> MappingProxyType[str, Any]:
29
+ """Validate, normalize, and deep-freeze an accepted JSON Schema subset."""
30
+ normalized = normalize_json_schema(schema, field_name=field_name)
31
+ return _freeze_json_object(normalized)
32
+
33
+
34
+ def normalize_json_schema(
35
+ schema: Mapping[str, Any], *, field_name: str = "schema"
36
+ ) -> dict[str, Any]:
37
+ """Return a deterministic JSON-compatible schema with annotations stripped."""
38
+ if not isinstance(schema, Mapping):
39
+ raise SchemaSubsetError(f"{field_name} must be a JSON object")
40
+ return _normalize_schema_node(schema, path=f"/{field_name}")
41
+
42
+
43
+ def normalized_schema_bytes(schema: Mapping[str, Any]) -> bytes:
44
+ """Return stable UTF-8 bytes for semantically normalized accepted schemas."""
45
+ normalized = normalize_json_schema(schema)
46
+ return json.dumps(
47
+ normalized,
48
+ allow_nan=False,
49
+ separators=(",", ":"),
50
+ sort_keys=True,
51
+ ).encode("utf-8")
52
+
53
+
54
+ def property_schema_compatibility_bytes(schema: Mapping[str, Any]) -> bytes:
55
+ """Return stable bytes used for top-level argument property compatibility."""
56
+ normalized = normalize_json_schema(schema, field_name="property_schema")
57
+ return json.dumps(
58
+ normalized,
59
+ allow_nan=False,
60
+ separators=(",", ":"),
61
+ sort_keys=True,
62
+ ).encode("utf-8")
63
+
64
+
65
+ def _normalize_schema_node(schema: Mapping[str, Any], *, path: str) -> dict[str, Any]:
66
+ _validate_object_keys(schema, path=path)
67
+ if "const" in schema:
68
+ return _normalize_const_only_schema(schema, path=path)
69
+ schema_type = schema.get("type")
70
+ if not isinstance(schema_type, str):
71
+ raise SchemaSubsetError(f"{path}/type must be one schema type string")
72
+ if schema_type not in SCHEMA_ALLOWED_TYPES:
73
+ raise SchemaSubsetError(f"{path}/type {schema_type!r} is not supported")
74
+
75
+ normalized: dict[str, Any] = {"type": schema_type}
76
+ if "const" in schema:
77
+ normalized["const"] = _normalize_scalar_literal(
78
+ schema["const"], path=f"{path}/const"
79
+ )
80
+ if "enum" in schema:
81
+ normalized["enum"] = _normalize_enum(schema["enum"], path=f"{path}/enum")
82
+
83
+ if schema_type == "object":
84
+ normalized.update(_normalize_object_schema(schema, path=path))
85
+ elif schema_type == "array":
86
+ normalized.update(_normalize_array_schema(schema, path=path))
87
+ else:
88
+ _reject_keywords(
89
+ schema,
90
+ {"properties", "required", "additionalProperties", "items"},
91
+ path=path,
92
+ )
93
+
94
+ _validate_literal_types(normalized, path=path)
95
+ return normalized
96
+
97
+
98
+ def _normalize_const_only_schema(
99
+ schema: Mapping[str, Any], *, path: str
100
+ ) -> dict[str, Any]:
101
+ structural_keys = set(schema) & SCHEMA_STRUCTURAL_KEYWORDS
102
+ if structural_keys != {"const"}:
103
+ joined = ", ".join(sorted(structural_keys - {"const"}))
104
+ raise SchemaSubsetError(
105
+ f"{path}/const-only schema cannot include non-annotation keys"
106
+ + (f": {joined}" if joined else "")
107
+ )
108
+ return {"const": _normalize_scalar_literal(schema["const"], path=f"{path}/const")}
109
+
110
+
111
+ def _normalize_object_schema(schema: Mapping[str, Any], *, path: str) -> dict[str, Any]:
112
+ _reject_keywords(schema, {"items"}, path=path)
113
+ additional_properties = schema.get("additionalProperties")
114
+ if additional_properties is not False:
115
+ raise SchemaSubsetError(f"{path}/additionalProperties must be false")
116
+ properties = schema.get("properties", {})
117
+ if not isinstance(properties, Mapping):
118
+ raise SchemaSubsetError(f"{path}/properties must be an object")
119
+
120
+ normalized_properties: dict[str, Any] = {}
121
+ for property_name in sorted(properties):
122
+ if not isinstance(property_name, str) or not property_name:
123
+ raise SchemaSubsetError(f"{path}/properties keys must be nonblank strings")
124
+ property_schema = properties[property_name]
125
+ if not isinstance(property_schema, Mapping):
126
+ raise SchemaSubsetError(
127
+ f"{path}/properties/{property_name} must be a schema object"
128
+ )
129
+ normalized_properties[property_name] = _normalize_schema_node(
130
+ property_schema, path=f"{path}/properties/{property_name}"
131
+ )
132
+
133
+ required = schema.get("required", [])
134
+ if not isinstance(required, list | tuple):
135
+ raise SchemaSubsetError(f"{path}/required must be an array")
136
+ normalized_required: list[str] = []
137
+ seen_required: set[str] = set()
138
+ for index, item in enumerate(required):
139
+ if not isinstance(item, str) or not item:
140
+ raise SchemaSubsetError(
141
+ f"{path}/required/{index} must be a nonblank string"
142
+ )
143
+ if item in seen_required:
144
+ raise SchemaSubsetError(
145
+ f"{path}/required contains duplicate field {item!r}"
146
+ )
147
+ if item not in normalized_properties:
148
+ raise SchemaSubsetError(f"{path}/required field {item!r} is not a property")
149
+ seen_required.add(item)
150
+ normalized_required.append(item)
151
+
152
+ return {
153
+ "additionalProperties": False,
154
+ "properties": normalized_properties,
155
+ "required": sorted(normalized_required),
156
+ }
157
+
158
+
159
+ def _normalize_array_schema(schema: Mapping[str, Any], *, path: str) -> dict[str, Any]:
160
+ _reject_keywords(
161
+ schema, {"properties", "required", "additionalProperties"}, path=path
162
+ )
163
+ items = schema.get("items")
164
+ if not isinstance(items, Mapping):
165
+ raise SchemaSubsetError(f"{path}/items must be a schema object")
166
+ return {"items": _normalize_schema_node(items, path=f"{path}/items")}
167
+
168
+
169
+ def _normalize_enum(value: Any, *, path: str) -> list[str | int | float | bool | None]:
170
+ if not isinstance(value, list | tuple) or not value:
171
+ raise SchemaSubsetError(f"{path} must be a non-empty array")
172
+ normalized: list[str | int | float | bool | None] = []
173
+ seen: set[tuple[str, Any]] = set()
174
+ for index, item in enumerate(value):
175
+ literal = _normalize_scalar_literal(item, path=f"{path}/{index}")
176
+ key = _scalar_uniqueness_key(literal)
177
+ if key in seen:
178
+ raise SchemaSubsetError(f"{path} contains duplicate scalar literal")
179
+ seen.add(key)
180
+ normalized.append(literal)
181
+ return normalized
182
+
183
+
184
+ def _normalize_scalar_literal(
185
+ value: Any, *, path: str
186
+ ) -> str | int | float | bool | None:
187
+ if value is None:
188
+ return None
189
+ if isinstance(value, bool | str):
190
+ return value
191
+ if isinstance(value, int) and not isinstance(value, bool):
192
+ return value
193
+ if isinstance(value, float):
194
+ json.dumps(value, allow_nan=False)
195
+ return value
196
+ raise SchemaSubsetError(f"{path} must be a scalar string, number, boolean, or null")
197
+
198
+
199
+ def _validate_literal_types(schema: Mapping[str, Any], *, path: str) -> None:
200
+ schema_type = schema["type"]
201
+ for key in ("const", "enum"):
202
+ if key not in schema:
203
+ continue
204
+ values = schema[key] if key == "enum" else [schema[key]]
205
+ for value in values:
206
+ if not _literal_matches_type(value, schema_type):
207
+ raise SchemaSubsetError(
208
+ f"{path}/{key} value does not match schema type"
209
+ )
210
+
211
+
212
+ def _literal_matches_type(value: Any, schema_type: str) -> bool:
213
+ if schema_type == "boolean":
214
+ return isinstance(value, bool)
215
+ if schema_type == "integer":
216
+ return isinstance(value, int) and not isinstance(value, bool)
217
+ if schema_type == "number":
218
+ return isinstance(value, int | float) and not isinstance(value, bool)
219
+ if schema_type == "string":
220
+ return isinstance(value, str)
221
+ return False
222
+
223
+
224
+ def _validate_object_keys(schema: Mapping[str, Any], *, path: str) -> None:
225
+ for key in schema:
226
+ if not isinstance(key, str):
227
+ raise SchemaSubsetError(f"{path} keywords must be strings")
228
+ if key in SCHEMA_UNSUPPORTED_BOUND_KEYWORDS:
229
+ raise SchemaSubsetError(
230
+ f"{path}/{key} is not accepted because runtime does not enforce "
231
+ "schema bounds"
232
+ )
233
+ if key not in SCHEMA_ALLOWED_KEYWORDS:
234
+ raise SchemaSubsetError(f"{path}/{key} is not in the accepted 03B subset")
235
+
236
+
237
+ def _reject_keywords(
238
+ schema: Mapping[str, Any], keywords: set[str], *, path: str
239
+ ) -> None:
240
+ for keyword in sorted(keywords):
241
+ if keyword in schema:
242
+ raise SchemaSubsetError(
243
+ f"{path}/{keyword} is not valid for type {schema['type']!r}"
244
+ )
245
+
246
+
247
+ def _scalar_uniqueness_key(value: str | int | float | bool | None) -> tuple[str, Any]:
248
+ if value is None:
249
+ return ("null", None)
250
+ if isinstance(value, bool):
251
+ return ("boolean", value)
252
+ if isinstance(value, str):
253
+ return ("string", value)
254
+ return ("number", value)
255
+
256
+
257
+ def _freeze_json_object(value: Mapping[str, Any]) -> MappingProxyType[str, Any]:
258
+ frozen = _freeze_json_value(dict(value))
259
+ if not isinstance(frozen, MappingProxyType):
260
+ raise SchemaSubsetError("schema must be a JSON object")
261
+ return frozen
262
+
263
+
264
+ def _freeze_json_value(value: Any) -> Any:
265
+ if isinstance(value, Mapping):
266
+ return MappingProxyType(
267
+ {str(key): _freeze_json_value(item) for key, item in value.items()}
268
+ )
269
+ if isinstance(value, list | tuple):
270
+ return tuple(_freeze_json_value(item) for item in value)
271
+ json.dumps(value, allow_nan=False)
272
+ return value