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,1339 @@
1
+ """Explicit runtime implementations for accepted built-in tool bindings."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import fnmatch
6
+ import os
7
+ import subprocess
8
+ from collections.abc import Callable
9
+ from pathlib import Path
10
+ from typing import Any, Literal
11
+
12
+ from millforge import (
13
+ ArtifactRef,
14
+ SideEffectCertainty,
15
+ ToolExecutionStatus,
16
+ )
17
+ from millforge.compiled_plan import CompiledHarnessPlan
18
+ from millforge.contracts import (
19
+ SideEffectRecord,
20
+ TerminalIntent,
21
+ ToolExecutionContext,
22
+ ToolExecutionResult,
23
+ ValidatedToolCall,
24
+ )
25
+ from millforge.tools.builtins import (
26
+ create_builtin_tool_snapshot,
27
+ iter_builtin_tool_descriptors,
28
+ )
29
+ from millforge.tools.execution import (
30
+ CompiledToolBindingExecutor,
31
+ RuntimeToolRegistry,
32
+ create_tool_executor,
33
+ )
34
+ from millforge.tools.path_policy import (
35
+ PathPolicyError,
36
+ atomic_write_contained,
37
+ canonical_sha256_bytes,
38
+ logical_from_resolved,
39
+ resolve_existing_contained,
40
+ resolve_write_contained,
41
+ validate_logical_path,
42
+ )
43
+ from millforge.tools.results import (
44
+ ToolExecutionErrorCode,
45
+ canonical_sha256,
46
+ make_denial_result,
47
+ make_tool_result,
48
+ )
49
+
50
+ _DEFAULT_MAX_BYTES = 32_768
51
+ _MAX_BYTES = 1_048_576
52
+ _DEFAULT_MAX_RESULTS = 200
53
+ _MAX_RESULTS = 1_000
54
+ _ARTIFACT_FILENAMES = {
55
+ "plan": "plan.md",
56
+ "patch_summary": "patch_summary.md",
57
+ "test_results": "test_results.md",
58
+ "workspace_diff": "workspace_diff.md",
59
+ "checker_verdict": "checker_verdict.md",
60
+ "arbiter_verdict": "arbiter_verdict.md",
61
+ }
62
+ _ARTIFACT_CONTENT_TYPE = "text/markdown"
63
+ _READ_TOOL_ARTIFACT_ID = {
64
+ "builtin.artifact.read_plan": "plan",
65
+ "builtin.artifact.read_patch_summary": "patch_summary",
66
+ "builtin.artifact.read_test_results": "test_results",
67
+ "builtin.artifact.read_workspace_diff": "workspace_diff",
68
+ "builtin.artifact.read_checker_verdict": "checker_verdict",
69
+ }
70
+ _WRITE_TOOL_CONTENT_FIELD = {
71
+ "builtin.artifact.write_plan": ("plan", "plan"),
72
+ "builtin.artifact.write_patch_summary": ("patch_summary", "summary"),
73
+ "builtin.artifact.write_test_results": ("test_results", "results"),
74
+ "builtin.artifact.write_checker_verdict": ("checker_verdict", "verdict"),
75
+ "builtin.artifact.write_arbiter_verdict": ("arbiter_verdict", "verdict"),
76
+ }
77
+ _SHELL_TEST_PROFILES = {
78
+ "tool-boundary": (
79
+ "python",
80
+ "-m",
81
+ "pytest",
82
+ "tests/test_tool_execution_boundary.py",
83
+ "tests/test_builtin_tool_catalog.py",
84
+ ),
85
+ }
86
+ _SHELL_STATIC_CHECK_PROFILES = {
87
+ "tool-boundary-ruff": (
88
+ "python",
89
+ "-m",
90
+ "ruff",
91
+ "check",
92
+ "src/millforge/tools",
93
+ "src/millforge/contracts.py",
94
+ "src/millforge/compiled_plan.py",
95
+ "tests/test_tool_execution_boundary.py",
96
+ ),
97
+ "tool-boundary-mypy": (
98
+ "python",
99
+ "-m",
100
+ "mypy",
101
+ "src/millforge/tools",
102
+ "src/millforge/contracts.py",
103
+ "src/millforge/compiled_plan.py",
104
+ ),
105
+ }
106
+ _SHELL_ALLOWED_SELECTORS = {None, "", "all"}
107
+ _SHELL_ENV_ALLOWLIST = (
108
+ "PATH",
109
+ "HOME",
110
+ "TMPDIR",
111
+ "TMP",
112
+ "TEMP",
113
+ "SYSTEMROOT",
114
+ "WINDIR",
115
+ )
116
+ _SHELL_DEFAULT_TIMEOUT_SECONDS = 60.0
117
+ _SHELL_MAX_TIMEOUT_SECONDS = 1800.0
118
+
119
+
120
+ BuiltinImplementation = Callable[
121
+ [ValidatedToolCall, ToolExecutionContext], ToolExecutionResult
122
+ ]
123
+
124
+
125
+ def create_builtin_runtime_registry() -> RuntimeToolRegistry:
126
+ """Create the explicit source-owned built-in implementation registry."""
127
+ registry = RuntimeToolRegistry()
128
+ implementations: dict[str, BuiltinImplementation] = {
129
+ "builtin.request.inspect": _request_inspect,
130
+ "builtin.request.read_requirements": _request_requirements,
131
+ "builtin.workspace.list_files": _workspace_list_files,
132
+ "builtin.workspace.read_file": _workspace_read_file,
133
+ "builtin.workspace.search_text": _workspace_search_text,
134
+ "builtin.workspace.write_file": _workspace_write_file,
135
+ "builtin.workspace.apply_patch": _workspace_apply_patch,
136
+ "builtin.workspace.read_diff": _workspace_read_diff,
137
+ "builtin.shell.run_tests": _shell_run_profile,
138
+ "builtin.shell.run_static_check": _shell_run_profile,
139
+ "builtin.artifact.read": _artifact_read,
140
+ "builtin.artifact.read_plan": _artifact_read,
141
+ "builtin.artifact.read_patch_summary": _artifact_read,
142
+ "builtin.artifact.read_test_results": _artifact_read,
143
+ "builtin.artifact.read_workspace_diff": _artifact_read,
144
+ "builtin.artifact.read_checker_verdict": _artifact_read,
145
+ "builtin.artifact.write_plan": _artifact_write,
146
+ "builtin.artifact.write_patch_summary": _artifact_write,
147
+ "builtin.artifact.write_test_results": _artifact_write,
148
+ "builtin.artifact.write_workspace_diff": _artifact_write,
149
+ "builtin.artifact.write_checker_verdict": _artifact_write,
150
+ "builtin.artifact.write_arbiter_verdict": _artifact_write,
151
+ "builtin.artifact.write_verdict": _artifact_write,
152
+ "builtin.terminal.submit": _terminal_intent,
153
+ "builtin.terminal.reject": _terminal_intent,
154
+ "builtin.terminal.escalate": _terminal_intent,
155
+ }
156
+ for descriptor in iter_builtin_tool_descriptors():
157
+ implementation = implementations.get(descriptor.tool_id, _not_implemented)
158
+ registry.register(descriptor.implementation_id, implementation)
159
+ return registry
160
+
161
+
162
+ def create_builtin_tool_executor(
163
+ plan: CompiledHarnessPlan,
164
+ ) -> CompiledToolBindingExecutor:
165
+ """Create a built-in executor admitted by the compiled plan and 04B snapshot."""
166
+ return create_tool_executor(
167
+ plan=plan,
168
+ descriptor_snapshot=create_builtin_tool_snapshot(),
169
+ runtime_registry=create_builtin_runtime_registry(),
170
+ )
171
+
172
+
173
+ def validate_builtin_pre_entry_policy(
174
+ call: ValidatedToolCall,
175
+ context: ToolExecutionContext,
176
+ *,
177
+ input_sha256: str | None = None,
178
+ ) -> ToolExecutionResult | None:
179
+ """Return a denial result when built-in side-effect policy rejects a call.
180
+
181
+ The validator intentionally performs no writes, process launches, terminal
182
+ handoff, queue/status mutation, or network access. Production built-in
183
+ implementations keep their internal checks as a second line of defense.
184
+ """
185
+ input_digest = input_sha256 or canonical_sha256(call.arguments)
186
+ try:
187
+ error = _builtin_policy_error(call, context)
188
+ except (OSError, PathPolicyError, ValueError) as exc:
189
+ error = _policy_error(_policy_error_code(call), str(exc))
190
+ if error is None:
191
+ return None
192
+ descriptor = _descriptor_for_call(call)
193
+ return make_denial_result(
194
+ call_id=call.call_id,
195
+ code=error.code,
196
+ summary=error.summary,
197
+ evidence={
198
+ "tool_id": call.binding.tool_id,
199
+ "node_id": call.node_id,
200
+ "policy": error.policy,
201
+ },
202
+ side_effect_class=descriptor.side_effect_class,
203
+ idempotency=descriptor.idempotency,
204
+ input_sha256=input_digest,
205
+ )
206
+
207
+
208
+ def _request_inspect(
209
+ call: ValidatedToolCall,
210
+ context: ToolExecutionContext,
211
+ ) -> ToolExecutionResult:
212
+ artifact_refs = [ref.artifact_id for ref in context.input_artifacts]
213
+ work_item = context.work_item_id or ""
214
+ objective = work_item[:512]
215
+ output = {
216
+ "status": "success",
217
+ "summary": "request context inspected",
218
+ "request_id": context.request_id,
219
+ "stage_id": context.stage.stage_kind_id,
220
+ "objective": objective,
221
+ "artifact_refs": artifact_refs[:_DEFAULT_MAX_RESULTS],
222
+ "truncated": len(artifact_refs) > _DEFAULT_MAX_RESULTS,
223
+ }
224
+ return _success(call, output)
225
+
226
+
227
+ def _request_requirements(
228
+ call: ValidatedToolCall,
229
+ context: ToolExecutionContext,
230
+ ) -> ToolExecutionResult:
231
+ requirements = [
232
+ f"capability:{grant.capability_id}"
233
+ for grant in context.capability_envelope.grants
234
+ ]
235
+ policy = context.compiled_artifact_policy
236
+ if policy is not None:
237
+ requirements.extend(
238
+ f"artifact:{artifact_id}" for artifact_id in policy.declared_artifact_ids
239
+ )
240
+ for item in policy.required_by_terminal:
241
+ joined = ",".join(item.artifact_ids)
242
+ requirements.append(f"terminal:{item.terminal_result}:artifacts:{joined}")
243
+ artifact_refs = [ref.artifact_id for ref in context.input_artifacts]
244
+ truncated = (
245
+ len(requirements) > _MAX_RESULTS or len(artifact_refs) > _DEFAULT_MAX_RESULTS
246
+ )
247
+ output = {
248
+ "status": "success",
249
+ "summary": "request requirements inspected",
250
+ "requirements": requirements[:_MAX_RESULTS],
251
+ "artifact_refs": artifact_refs[:_DEFAULT_MAX_RESULTS],
252
+ "truncated": truncated,
253
+ }
254
+ return _success(call, output)
255
+
256
+
257
+ def _workspace_list_files(
258
+ call: ValidatedToolCall,
259
+ context: ToolExecutionContext,
260
+ ) -> ToolExecutionResult:
261
+ root = _workspace_root(context)
262
+ if root is None:
263
+ return _denied(
264
+ call, ToolExecutionErrorCode.POLICY_DENIED, "workspace root is unavailable"
265
+ )
266
+ max_results = _bounded_int(
267
+ call.arguments.get("max_results"), _DEFAULT_MAX_RESULTS, _MAX_RESULTS
268
+ )
269
+ try:
270
+ base = resolve_existing_contained(
271
+ root, str(call.arguments["root"]), allow_dot=True
272
+ )
273
+ pattern = str(call.arguments.get("glob") or "*")
274
+ paths: list[str] = []
275
+ truncated = False
276
+ for item in sorted(base.rglob("*")):
277
+ if not item.is_file():
278
+ continue
279
+ try:
280
+ logical = logical_from_resolved(root, item)
281
+ except PathPolicyError:
282
+ continue
283
+ if fnmatch.fnmatch(logical, pattern) or fnmatch.fnmatch(item.name, pattern):
284
+ paths.append(logical)
285
+ if len(paths) >= max_results:
286
+ truncated = True
287
+ break
288
+ except (OSError, PathPolicyError) as exc:
289
+ return _denied(call, ToolExecutionErrorCode.POLICY_DENIED, str(exc))
290
+ return _success(
291
+ call,
292
+ {
293
+ "status": "success",
294
+ "summary": f"listed {len(paths)} workspace file(s)",
295
+ "paths": paths,
296
+ "truncated": truncated,
297
+ },
298
+ )
299
+
300
+
301
+ def _workspace_read_file(
302
+ call: ValidatedToolCall,
303
+ context: ToolExecutionContext,
304
+ ) -> ToolExecutionResult:
305
+ root = _workspace_root(context)
306
+ if root is None:
307
+ return _denied(
308
+ call, ToolExecutionErrorCode.POLICY_DENIED, "workspace root is unavailable"
309
+ )
310
+ max_bytes = _bounded_int(
311
+ call.arguments.get("max_bytes"), _DEFAULT_MAX_BYTES, _MAX_BYTES
312
+ )
313
+ try:
314
+ target = resolve_existing_contained(root, str(call.arguments["path"]))
315
+ if not target.is_file():
316
+ return _denied(
317
+ call, ToolExecutionErrorCode.NOT_FOUND, "workspace file not found"
318
+ )
319
+ data = target.read_bytes()
320
+ except (OSError, PathPolicyError) as exc:
321
+ return _denied(call, ToolExecutionErrorCode.POLICY_DENIED, str(exc))
322
+ content, truncated = _decode_bounded(data, max_bytes)
323
+ return _success(
324
+ call,
325
+ {
326
+ "status": "success",
327
+ "summary": "workspace file read",
328
+ "content": content,
329
+ "truncated": truncated,
330
+ "artifact_refs": [],
331
+ },
332
+ )
333
+
334
+
335
+ def _workspace_search_text(
336
+ call: ValidatedToolCall,
337
+ context: ToolExecutionContext,
338
+ ) -> ToolExecutionResult:
339
+ root = _workspace_root(context)
340
+ if root is None:
341
+ return _denied(
342
+ call, ToolExecutionErrorCode.POLICY_DENIED, "workspace root is unavailable"
343
+ )
344
+ query = str(call.arguments["query"])
345
+ max_results = _bounded_int(
346
+ call.arguments.get("max_results"), _DEFAULT_MAX_RESULTS, _MAX_RESULTS
347
+ )
348
+ try:
349
+ base = resolve_existing_contained(
350
+ root, str(call.arguments.get("root") or "."), allow_dot=True
351
+ )
352
+ pattern = str(call.arguments.get("glob") or "*")
353
+ matches: list[dict[str, Any]] = []
354
+ truncated = False
355
+ for item in sorted(base.rglob("*")):
356
+ if not item.is_file():
357
+ continue
358
+ try:
359
+ logical = logical_from_resolved(root, item)
360
+ except PathPolicyError:
361
+ continue
362
+ if not (
363
+ fnmatch.fnmatch(logical, pattern) or fnmatch.fnmatch(item.name, pattern)
364
+ ):
365
+ continue
366
+ try:
367
+ lines = item.read_text(encoding="utf-8", errors="replace").splitlines()
368
+ except OSError:
369
+ continue
370
+ for line_number, line in enumerate(lines, start=1):
371
+ if query in line:
372
+ matches.append(
373
+ {
374
+ "path": logical,
375
+ "line": line_number,
376
+ "snippet": line[:512],
377
+ }
378
+ )
379
+ if len(matches) >= max_results:
380
+ truncated = True
381
+ raise StopIteration
382
+ except StopIteration:
383
+ pass
384
+ except (OSError, PathPolicyError) as exc:
385
+ return _denied(call, ToolExecutionErrorCode.POLICY_DENIED, str(exc))
386
+ return _success(
387
+ call,
388
+ {
389
+ "status": "success",
390
+ "summary": f"found {len(matches)} match(es)",
391
+ "matches": matches,
392
+ "truncated": truncated,
393
+ },
394
+ )
395
+
396
+
397
+ def _workspace_write_file(
398
+ call: ValidatedToolCall,
399
+ context: ToolExecutionContext,
400
+ ) -> ToolExecutionResult:
401
+ root = _workspace_root(context)
402
+ if root is None:
403
+ return _denied(
404
+ call, ToolExecutionErrorCode.POLICY_DENIED, "workspace root is unavailable"
405
+ )
406
+ path = str(call.arguments["path"])
407
+ content = str(call.arguments["content"]).encode("utf-8")
408
+ try:
409
+ target = resolve_write_contained(root, path)
410
+ expected = call.arguments.get("expected_sha256")
411
+ if expected is not None and target.exists():
412
+ actual = canonical_sha256_bytes(target.read_bytes())
413
+ if actual != expected:
414
+ return _denied(
415
+ call,
416
+ ToolExecutionErrorCode.CONFLICT,
417
+ "expected_sha256 does not match current file",
418
+ )
419
+ digest = atomic_write_contained(root, path, content)
420
+ except (OSError, PathPolicyError) as exc:
421
+ return _denied(call, ToolExecutionErrorCode.POLICY_DENIED, str(exc))
422
+ return _success(
423
+ call,
424
+ {
425
+ "status": "success",
426
+ "summary": "workspace file written atomically",
427
+ "path": validate_logical_path(path).as_posix(),
428
+ "content_sha256": digest,
429
+ },
430
+ )
431
+
432
+
433
+ def _workspace_apply_patch(
434
+ call: ValidatedToolCall,
435
+ context: ToolExecutionContext,
436
+ ) -> ToolExecutionResult:
437
+ root = _workspace_root(context)
438
+ if root is None:
439
+ return _denied(
440
+ call, ToolExecutionErrorCode.POLICY_DENIED, "workspace root is unavailable"
441
+ )
442
+ patch = str(call.arguments["patch"])
443
+ try:
444
+ changed_paths = _changed_paths_from_patch(root, patch)
445
+ before = _git_diff(root, changed_paths, _MAX_BYTES)
446
+ expected = call.arguments.get("expected_base_sha256")
447
+ if expected is not None and canonical_sha256(before) != expected:
448
+ return _denied(
449
+ call,
450
+ ToolExecutionErrorCode.CONFLICT,
451
+ "expected_base_sha256 does not match current diff",
452
+ )
453
+ subprocess.run(
454
+ ["git", "apply", "--whitespace=nowarn", "-"],
455
+ cwd=root,
456
+ input=patch.encode("utf-8"),
457
+ stdout=subprocess.PIPE,
458
+ stderr=subprocess.PIPE,
459
+ check=True,
460
+ )
461
+ diff = _git_diff(root, changed_paths, _MAX_BYTES)
462
+ except subprocess.CalledProcessError as exc:
463
+ detail = exc.stderr.decode("utf-8", errors="replace")[:512]
464
+ return _denied(
465
+ call, ToolExecutionErrorCode.CONFLICT, detail or "patch rejected"
466
+ )
467
+ except (OSError, PathPolicyError, ValueError) as exc:
468
+ return _denied(call, ToolExecutionErrorCode.POLICY_DENIED, str(exc))
469
+ return _success(
470
+ call,
471
+ {
472
+ "status": "success",
473
+ "summary": f"applied patch touching {len(changed_paths)} path(s)",
474
+ "changed_paths": changed_paths,
475
+ "diff_sha256": canonical_sha256(diff),
476
+ },
477
+ )
478
+
479
+
480
+ def _workspace_read_diff(
481
+ call: ValidatedToolCall,
482
+ context: ToolExecutionContext,
483
+ ) -> ToolExecutionResult:
484
+ root = _workspace_root(context)
485
+ if root is None:
486
+ return _denied(
487
+ call, ToolExecutionErrorCode.POLICY_DENIED, "workspace root is unavailable"
488
+ )
489
+ max_bytes = _bounded_int(
490
+ call.arguments.get("max_bytes"), _DEFAULT_MAX_BYTES, _MAX_BYTES
491
+ )
492
+ try:
493
+ paths = [
494
+ validate_logical_path(str(item)).as_posix()
495
+ for item in call.arguments.get("paths", [])
496
+ ]
497
+ diff = _git_diff(root, paths, max_bytes)
498
+ except (OSError, PathPolicyError) as exc:
499
+ return _denied(call, ToolExecutionErrorCode.POLICY_DENIED, str(exc))
500
+ encoded = diff.encode("utf-8")
501
+ truncated = len(encoded) > max_bytes
502
+ if truncated:
503
+ diff = encoded[:max_bytes].decode("utf-8", errors="ignore") + "[truncated]"
504
+ return _success(
505
+ call,
506
+ {
507
+ "status": "success",
508
+ "summary": "workspace diff read",
509
+ "diff": diff,
510
+ "truncated": truncated,
511
+ "diff_sha256": canonical_sha256(diff),
512
+ },
513
+ )
514
+
515
+
516
+ def _artifact_read(
517
+ call: ValidatedToolCall,
518
+ context: ToolExecutionContext,
519
+ ) -> ToolExecutionResult:
520
+ artifact_id = _read_artifact_id(call)
521
+ max_bytes = _bounded_int(
522
+ call.arguments.get("max_bytes"), _DEFAULT_MAX_BYTES, _MAX_BYTES
523
+ )
524
+ error = _artifact_policy_error(context, artifact_id, produced_ids=None)
525
+ if error is not None:
526
+ return _denied(call, ToolExecutionErrorCode.POLICY_DENIED, error)
527
+ ref = _artifact_ref(artifact_id)
528
+ try:
529
+ path = _artifact_logical_path(artifact_id)
530
+ root = _artifact_root(context)
531
+ if root is None:
532
+ return _denied(
533
+ call,
534
+ ToolExecutionErrorCode.POLICY_DENIED,
535
+ "artifact root is unavailable",
536
+ )
537
+ target = resolve_existing_contained(root, path)
538
+ data = target.read_bytes()
539
+ except (OSError, PathPolicyError) as exc:
540
+ return _denied(call, ToolExecutionErrorCode.POLICY_DENIED, str(exc))
541
+ content, truncated = _decode_bounded(data, max_bytes)
542
+ return _success(
543
+ call,
544
+ {
545
+ "status": "success",
546
+ "summary": "artifact read",
547
+ "artifact_id": artifact_id,
548
+ "content": content,
549
+ "content_sha256": canonical_sha256_bytes(data),
550
+ "truncated": truncated,
551
+ },
552
+ artifact_refs=(ref,),
553
+ )
554
+
555
+
556
+ def _artifact_write(
557
+ call: ValidatedToolCall,
558
+ context: ToolExecutionContext,
559
+ ) -> ToolExecutionResult:
560
+ root = _artifact_root(context)
561
+ if root is None:
562
+ return _denied(
563
+ call, ToolExecutionErrorCode.POLICY_DENIED, "artifact root is unavailable"
564
+ )
565
+ if call.binding.tool_id == "builtin.artifact.write_verdict":
566
+ artifact_id = str(call.arguments["artifact_id"])
567
+ content = str(call.arguments["verdict"])
568
+ elif call.binding.tool_id == "builtin.artifact.write_workspace_diff":
569
+ artifact_id = "workspace_diff"
570
+ diff_result = _workspace_read_diff(_workspace_read_diff_call(call), context)
571
+ if diff_result.status is not ToolExecutionStatus.SUCCESS:
572
+ return _denied(
573
+ call,
574
+ ToolExecutionErrorCode(diff_result.error_code)
575
+ if diff_result.error_code is not None
576
+ else ToolExecutionErrorCode.POLICY_DENIED,
577
+ diff_result.summary,
578
+ )
579
+ if not isinstance(diff_result.structured_data, dict):
580
+ return _denied(
581
+ call,
582
+ ToolExecutionErrorCode.IMPLEMENTATION_ERROR,
583
+ "workspace diff runtime returned invalid structured data",
584
+ )
585
+ content = str(diff_result.structured_data["diff"])
586
+ else:
587
+ artifact_id, field = _WRITE_TOOL_CONTENT_FIELD[call.binding.tool_id]
588
+ content = str(call.arguments[field])
589
+ error = _artifact_policy_error(
590
+ context, artifact_id, produced_ids=call_node_produced_ids(call)
591
+ )
592
+ if error is not None:
593
+ return _denied(call, ToolExecutionErrorCode.POLICY_DENIED, error)
594
+ ref = _artifact_ref(artifact_id)
595
+ try:
596
+ digest = atomic_write_contained(
597
+ root, _artifact_logical_path(artifact_id), content.encode("utf-8")
598
+ )
599
+ except (OSError, PathPolicyError) as exc:
600
+ return _denied(call, ToolExecutionErrorCode.POLICY_DENIED, str(exc))
601
+ return _success(
602
+ call,
603
+ {
604
+ "status": "success",
605
+ "summary": "artifact written atomically",
606
+ "artifact_id": artifact_id,
607
+ "content_sha256": digest,
608
+ },
609
+ artifact_refs=(ref,),
610
+ )
611
+
612
+
613
+ def _shell_run_profile(
614
+ call: ValidatedToolCall,
615
+ context: ToolExecutionContext,
616
+ ) -> ToolExecutionResult:
617
+ root = _workspace_root(context)
618
+ if root is None:
619
+ return _denied(
620
+ call, ToolExecutionErrorCode.POLICY_DENIED, "workspace root is unavailable"
621
+ )
622
+ if not root.is_dir():
623
+ return _denied(
624
+ call,
625
+ ToolExecutionErrorCode.POLICY_DENIED,
626
+ "workspace root is not a directory",
627
+ )
628
+ selector = call.arguments.get("selector")
629
+ if selector not in _SHELL_ALLOWED_SELECTORS:
630
+ return _denied(
631
+ call,
632
+ ToolExecutionErrorCode.INVALID_ARGUMENTS,
633
+ "selector is not an approved shell profile selector",
634
+ )
635
+ profile = str(call.arguments["profile"])
636
+ command = _shell_profile_command(call.binding.tool_id, profile)
637
+ if command is None:
638
+ return _denied(
639
+ call,
640
+ ToolExecutionErrorCode.INVALID_ARGUMENTS,
641
+ "profile is not an approved shell profile",
642
+ )
643
+ max_bytes = _bounded_int(
644
+ call.arguments.get("max_output_bytes"), _DEFAULT_MAX_BYTES, _MAX_BYTES
645
+ )
646
+ timeout_seconds = min(
647
+ context.timeout.timeout_seconds,
648
+ _descriptor_for_call(call).timeout_policy.timeout_seconds,
649
+ _SHELL_MAX_TIMEOUT_SECONDS,
650
+ )
651
+ if timeout_seconds <= 0:
652
+ timeout_seconds = _SHELL_DEFAULT_TIMEOUT_SECONDS
653
+ env = _shell_environment()
654
+ try:
655
+ completed = subprocess.run(
656
+ list(command),
657
+ cwd=root,
658
+ env=env,
659
+ stdin=subprocess.DEVNULL,
660
+ stdout=subprocess.PIPE,
661
+ stderr=subprocess.PIPE,
662
+ shell=False,
663
+ check=False,
664
+ timeout=timeout_seconds,
665
+ )
666
+ except subprocess.TimeoutExpired as exc:
667
+ output, truncated = _join_process_output(exc.stdout, exc.stderr, max_bytes)
668
+ return _process_failure(
669
+ call,
670
+ ToolExecutionStatus.TIMED_OUT,
671
+ ToolExecutionErrorCode.TIMEOUT,
672
+ f"shell profile timed out: {output}",
673
+ -1,
674
+ truncated,
675
+ SideEffectCertainty.COMPLETION_UNKNOWN,
676
+ )
677
+ except OSError as exc:
678
+ return _process_failure(
679
+ call,
680
+ ToolExecutionStatus.HARD_FAILURE,
681
+ ToolExecutionErrorCode.IMPLEMENTATION_ERROR,
682
+ f"shell profile failed to start: {type(exc).__name__}",
683
+ -1,
684
+ False,
685
+ SideEffectCertainty.NOT_ATTEMPTED,
686
+ )
687
+ output, truncated = _join_process_output(
688
+ completed.stdout, completed.stderr, max_bytes
689
+ )
690
+ if completed.returncode == 0:
691
+ return _success(
692
+ call,
693
+ {
694
+ "status": "success",
695
+ "summary": f"shell profile completed successfully: {output}",
696
+ "exit_code": completed.returncode,
697
+ "artifact_refs": [],
698
+ "truncated": truncated,
699
+ },
700
+ )
701
+ return _process_failure(
702
+ call,
703
+ ToolExecutionStatus.SOFT_FAILURE,
704
+ ToolExecutionErrorCode.POLICY_DENIED,
705
+ f"shell profile failed with exit code {completed.returncode}: {output}",
706
+ completed.returncode,
707
+ truncated,
708
+ SideEffectCertainty.CONFIRMED_COMPLETE,
709
+ )
710
+
711
+
712
+ def _terminal_intent(
713
+ call: ValidatedToolCall,
714
+ context: ToolExecutionContext,
715
+ ) -> ToolExecutionResult:
716
+ terminal_result = str(call.arguments["terminal_result"])
717
+ try:
718
+ missing = _missing_required_terminal_artifacts(call, context, terminal_result)
719
+ if missing:
720
+ return _denied(
721
+ call,
722
+ ToolExecutionErrorCode.TERMINAL_INTENT_INVALID,
723
+ "terminal intent is missing required terminal artifacts",
724
+ )
725
+ summary = str(call.arguments["summary"])
726
+ artifact_refs = _terminal_artifact_refs(call, context)
727
+ TerminalIntent(
728
+ request_id=context.request_id,
729
+ run_id=context.run_id,
730
+ stage=context.stage,
731
+ terminal_node_id=call.node_id,
732
+ terminal_result=terminal_result,
733
+ disposition=_terminal_disposition(call),
734
+ summary=summary,
735
+ artifact_refs=artifact_refs,
736
+ )
737
+ except (PathPolicyError, ValueError) as exc:
738
+ return _denied(call, ToolExecutionErrorCode.TERMINAL_INTENT_INVALID, str(exc))
739
+ return _success(
740
+ call,
741
+ {
742
+ "status": "success",
743
+ "summary": "terminal intent validated",
744
+ "terminal_result": terminal_result,
745
+ },
746
+ artifact_refs=artifact_refs,
747
+ )
748
+
749
+
750
+ def call_node_produced_ids(call: ValidatedToolCall) -> tuple[str, ...]:
751
+ """Return produced IDs admitted by descriptor binding for artifact write tools."""
752
+ for descriptor in iter_builtin_tool_descriptors():
753
+ if descriptor.tool_id == call.binding.tool_id:
754
+ return descriptor.produced_artifact_ids
755
+ return ()
756
+
757
+
758
+ def _not_implemented(
759
+ call: ValidatedToolCall,
760
+ _context: ToolExecutionContext,
761
+ ) -> ToolExecutionResult:
762
+ return _result(
763
+ call,
764
+ ToolExecutionStatus.SOFT_FAILURE,
765
+ ToolExecutionErrorCode.IMPLEMENTATION_ERROR,
766
+ "built-in runtime implementation is not wired in this process",
767
+ {
768
+ "status": "soft_failure",
769
+ "summary": "built-in runtime implementation is not wired in this process",
770
+ },
771
+ SideEffectCertainty.NOT_ATTEMPTED,
772
+ )
773
+
774
+
775
+ def _success(
776
+ call: ValidatedToolCall,
777
+ output: dict[str, Any],
778
+ *,
779
+ artifact_refs: tuple[ArtifactRef, ...] = (),
780
+ ) -> ToolExecutionResult:
781
+ return _result(
782
+ call,
783
+ ToolExecutionStatus.SUCCESS,
784
+ None,
785
+ str(output["summary"]),
786
+ output,
787
+ SideEffectCertainty.CONFIRMED_COMPLETE,
788
+ artifact_refs=artifact_refs,
789
+ )
790
+
791
+
792
+ def _denied(
793
+ call: ValidatedToolCall,
794
+ code: ToolExecutionErrorCode,
795
+ summary: str,
796
+ ) -> ToolExecutionResult:
797
+ output = _failure_output(call, summary)
798
+ return _result(
799
+ call,
800
+ ToolExecutionStatus.NOT_EXECUTED,
801
+ code,
802
+ summary,
803
+ output,
804
+ SideEffectCertainty.NOT_ATTEMPTED,
805
+ )
806
+
807
+
808
+ def _result(
809
+ call: ValidatedToolCall,
810
+ status: ToolExecutionStatus,
811
+ code: ToolExecutionErrorCode | None,
812
+ summary: str,
813
+ output: dict[str, Any],
814
+ certainty: SideEffectCertainty,
815
+ *,
816
+ artifact_refs: tuple[ArtifactRef, ...] = (),
817
+ side_effect_record: SideEffectRecord | None = None,
818
+ ) -> ToolExecutionResult:
819
+ descriptor = _descriptor_for_call(call)
820
+ return make_tool_result(
821
+ call_id=call.call_id,
822
+ status=status,
823
+ code=code,
824
+ summary=summary,
825
+ structured_data=output,
826
+ side_effect_class=descriptor.side_effect_class,
827
+ idempotency=descriptor.idempotency,
828
+ side_effect_certainty=certainty,
829
+ input_sha256=canonical_sha256(call.arguments),
830
+ artifact_refs=artifact_refs,
831
+ side_effect_record=side_effect_record,
832
+ )
833
+
834
+
835
+ def _descriptor_for_call(call: ValidatedToolCall) -> Any:
836
+ for descriptor in iter_builtin_tool_descriptors():
837
+ if descriptor.implementation_id == call.binding.implementation_id:
838
+ return descriptor
839
+ raise KeyError(call.binding.implementation_id)
840
+
841
+
842
+ def _failure_output(call: ValidatedToolCall, summary: str) -> dict[str, Any]:
843
+ common = {"status": "soft_failure", "summary": summary}
844
+ tool_id = call.binding.tool_id
845
+ if tool_id == "builtin.request.inspect":
846
+ return {
847
+ **common,
848
+ "request_id": "",
849
+ "stage_id": "",
850
+ "objective": "",
851
+ "artifact_refs": [],
852
+ "truncated": False,
853
+ }
854
+ if tool_id == "builtin.request.read_requirements":
855
+ return {**common, "requirements": [], "artifact_refs": [], "truncated": False}
856
+ if tool_id == "builtin.workspace.list_files":
857
+ return {**common, "paths": [], "truncated": False}
858
+ if tool_id == "builtin.workspace.read_file":
859
+ return {**common, "content": "", "truncated": False, "artifact_refs": []}
860
+ if tool_id == "builtin.workspace.search_text":
861
+ return {**common, "matches": [], "truncated": False}
862
+ if tool_id == "builtin.workspace.write_file":
863
+ return {**common, "path": "", "content_sha256": "0" * 64}
864
+ if tool_id == "builtin.workspace.apply_patch":
865
+ return {**common, "changed_paths": [], "diff_sha256": "0" * 64}
866
+ if tool_id == "builtin.workspace.read_diff":
867
+ return {**common, "diff": "", "truncated": False, "diff_sha256": "0" * 64}
868
+ if tool_id.startswith("builtin.shell."):
869
+ return {**common, "exit_code": -1, "artifact_refs": [], "truncated": False}
870
+ if tool_id == "builtin.artifact.read" or tool_id in _READ_TOOL_ARTIFACT_ID:
871
+ return {
872
+ **common,
873
+ "artifact_id": _failure_artifact_id(call),
874
+ "content": "",
875
+ "content_sha256": "0" * 64,
876
+ "truncated": False,
877
+ }
878
+ if tool_id.startswith("builtin.artifact.write_"):
879
+ artifact_id = str(call.arguments.get("artifact_id") or "")
880
+ if not artifact_id:
881
+ artifact_id = _WRITE_TOOL_CONTENT_FIELD.get(tool_id, ("", ""))[0]
882
+ return {**common, "artifact_id": artifact_id, "content_sha256": "0" * 64}
883
+ if tool_id.startswith("builtin.terminal."):
884
+ return {
885
+ **common,
886
+ "terminal_result": str(call.arguments.get("terminal_result", "")),
887
+ }
888
+ return common
889
+
890
+
891
+ class _BuiltinPolicyError:
892
+ def __init__(
893
+ self,
894
+ code: ToolExecutionErrorCode,
895
+ summary: str,
896
+ policy: str,
897
+ ) -> None:
898
+ self.code = code
899
+ self.summary = summary
900
+ self.policy = policy
901
+
902
+
903
+ def _policy_error(
904
+ code: ToolExecutionErrorCode,
905
+ summary: str,
906
+ *,
907
+ policy: str = "built-in side-effect policy",
908
+ ) -> _BuiltinPolicyError:
909
+ return _BuiltinPolicyError(code, summary, policy)
910
+
911
+
912
+ def _policy_error_code(call: ValidatedToolCall) -> ToolExecutionErrorCode:
913
+ return (
914
+ ToolExecutionErrorCode.TERMINAL_INTENT_INVALID
915
+ if call.binding.tool_id.startswith("builtin.terminal.")
916
+ else ToolExecutionErrorCode.POLICY_DENIED
917
+ )
918
+
919
+
920
+ def _builtin_policy_error(
921
+ call: ValidatedToolCall,
922
+ context: ToolExecutionContext,
923
+ ) -> _BuiltinPolicyError | None:
924
+ tool_id = call.binding.tool_id
925
+ if tool_id.startswith("builtin.workspace."):
926
+ return _workspace_policy_error(call, context)
927
+ if tool_id.startswith("builtin.artifact."):
928
+ return _artifact_pre_entry_policy_error(call, context)
929
+ if tool_id.startswith("builtin.shell."):
930
+ return _shell_policy_error(call, context)
931
+ if tool_id.startswith("builtin.terminal."):
932
+ return _terminal_policy_error(call, context)
933
+ return None
934
+
935
+
936
+ def _workspace_policy_error(
937
+ call: ValidatedToolCall,
938
+ context: ToolExecutionContext,
939
+ ) -> _BuiltinPolicyError | None:
940
+ root = _workspace_root(context)
941
+ if root is None:
942
+ return _policy_error(
943
+ ToolExecutionErrorCode.POLICY_DENIED, "workspace root is unavailable"
944
+ )
945
+ tool_id = call.binding.tool_id
946
+ if tool_id == "builtin.workspace.list_files":
947
+ resolve_existing_contained(root, str(call.arguments["root"]), allow_dot=True)
948
+ elif tool_id == "builtin.workspace.read_file":
949
+ target = resolve_existing_contained(root, str(call.arguments["path"]))
950
+ if not target.is_file():
951
+ return _policy_error(
952
+ ToolExecutionErrorCode.NOT_FOUND, "workspace file not found"
953
+ )
954
+ elif tool_id == "builtin.workspace.search_text":
955
+ resolve_existing_contained(
956
+ root, str(call.arguments.get("root") or "."), allow_dot=True
957
+ )
958
+ elif tool_id == "builtin.workspace.write_file":
959
+ target = resolve_write_contained(root, str(call.arguments["path"]))
960
+ expected = call.arguments.get("expected_sha256")
961
+ if expected is not None and target.exists():
962
+ actual = canonical_sha256_bytes(target.read_bytes())
963
+ if actual != expected:
964
+ return _policy_error(
965
+ ToolExecutionErrorCode.CONFLICT,
966
+ "expected_sha256 does not match current file",
967
+ )
968
+ elif tool_id == "builtin.workspace.apply_patch":
969
+ _changed_paths_from_patch(root, str(call.arguments["patch"]))
970
+ elif tool_id == "builtin.workspace.read_diff":
971
+ if not root.is_dir():
972
+ return _policy_error(
973
+ ToolExecutionErrorCode.POLICY_DENIED,
974
+ "workspace root is unavailable",
975
+ )
976
+ for item in call.arguments.get("paths", []):
977
+ validate_logical_path(str(item))
978
+ return None
979
+
980
+
981
+ def _artifact_pre_entry_policy_error(
982
+ call: ValidatedToolCall,
983
+ context: ToolExecutionContext,
984
+ ) -> _BuiltinPolicyError | None:
985
+ root = _artifact_root(context)
986
+ if root is None:
987
+ return _policy_error(
988
+ ToolExecutionErrorCode.POLICY_DENIED, "artifact root is unavailable"
989
+ )
990
+ root.resolve(strict=True)
991
+ if call.binding.tool_id == "builtin.artifact.read" or (
992
+ call.binding.tool_id in _READ_TOOL_ARTIFACT_ID
993
+ ):
994
+ artifact_id = _read_artifact_id(call)
995
+ error = _artifact_policy_error(context, artifact_id, produced_ids=None)
996
+ if error is not None:
997
+ return _policy_error(ToolExecutionErrorCode.POLICY_DENIED, error)
998
+ resolve_existing_contained(root, _artifact_logical_path(artifact_id))
999
+ return None
1000
+ if call.binding.tool_id == "builtin.artifact.write_verdict":
1001
+ artifact_id = str(call.arguments["artifact_id"])
1002
+ elif call.binding.tool_id == "builtin.artifact.write_workspace_diff":
1003
+ artifact_id = "workspace_diff"
1004
+ diff_error = _workspace_policy_error(_workspace_read_diff_call(call), context)
1005
+ if diff_error is not None:
1006
+ return diff_error
1007
+ else:
1008
+ artifact_id = _WRITE_TOOL_CONTENT_FIELD[call.binding.tool_id][0]
1009
+ error = _artifact_policy_error(
1010
+ context, artifact_id, produced_ids=call_node_produced_ids(call)
1011
+ )
1012
+ if error is not None:
1013
+ return _policy_error(ToolExecutionErrorCode.POLICY_DENIED, error)
1014
+ resolve_write_contained(root, _artifact_logical_path(artifact_id))
1015
+ return None
1016
+
1017
+
1018
+ def _shell_policy_error(
1019
+ call: ValidatedToolCall,
1020
+ context: ToolExecutionContext,
1021
+ ) -> _BuiltinPolicyError | None:
1022
+ root = _workspace_root(context)
1023
+ if root is None:
1024
+ return _policy_error(
1025
+ ToolExecutionErrorCode.POLICY_DENIED, "workspace root is unavailable"
1026
+ )
1027
+ if not root.is_dir():
1028
+ return _policy_error(
1029
+ ToolExecutionErrorCode.POLICY_DENIED,
1030
+ "workspace root is not a directory",
1031
+ )
1032
+ selector = call.arguments.get("selector")
1033
+ if selector not in _SHELL_ALLOWED_SELECTORS:
1034
+ return _policy_error(
1035
+ ToolExecutionErrorCode.INVALID_ARGUMENTS,
1036
+ "selector is not an approved shell profile selector",
1037
+ )
1038
+ profile = str(call.arguments["profile"])
1039
+ if _shell_profile_command(call.binding.tool_id, profile) is None:
1040
+ return _policy_error(
1041
+ ToolExecutionErrorCode.INVALID_ARGUMENTS,
1042
+ "profile is not an approved shell profile",
1043
+ )
1044
+ timeout_seconds = min(
1045
+ context.timeout.timeout_seconds,
1046
+ _descriptor_for_call(call).timeout_policy.timeout_seconds,
1047
+ _SHELL_MAX_TIMEOUT_SECONDS,
1048
+ )
1049
+ if timeout_seconds <= 0:
1050
+ return _policy_error(
1051
+ ToolExecutionErrorCode.TIMEOUT, "shell timeout policy is exhausted"
1052
+ )
1053
+ return None
1054
+
1055
+
1056
+ def _terminal_policy_error(
1057
+ call: ValidatedToolCall,
1058
+ context: ToolExecutionContext,
1059
+ ) -> _BuiltinPolicyError | None:
1060
+ terminal_result = str(call.arguments["terminal_result"])
1061
+ missing = _missing_required_terminal_artifacts(call, context, terminal_result)
1062
+ if missing:
1063
+ return _policy_error(
1064
+ ToolExecutionErrorCode.TERMINAL_INTENT_INVALID,
1065
+ "terminal intent is missing required terminal artifacts",
1066
+ policy="terminal artifact policy",
1067
+ )
1068
+ _terminal_artifact_refs(call, context)
1069
+ TerminalIntent(
1070
+ request_id=context.request_id,
1071
+ run_id=context.run_id,
1072
+ stage=context.stage,
1073
+ terminal_node_id=call.node_id,
1074
+ terminal_result=terminal_result,
1075
+ disposition=_terminal_disposition(call),
1076
+ summary=str(call.arguments["summary"]),
1077
+ artifact_refs=_terminal_artifact_refs(call, context),
1078
+ )
1079
+ return None
1080
+
1081
+
1082
+ def _shell_profile_command(tool_id: str, profile: str) -> tuple[str, ...] | None:
1083
+ profiles = (
1084
+ _SHELL_TEST_PROFILES
1085
+ if tool_id == "builtin.shell.run_tests"
1086
+ else _SHELL_STATIC_CHECK_PROFILES
1087
+ )
1088
+ return profiles.get(profile)
1089
+
1090
+
1091
+ def _shell_environment() -> dict[str, str]:
1092
+ env = {
1093
+ key: value
1094
+ for key in _SHELL_ENV_ALLOWLIST
1095
+ if (value := os.environ.get(key)) is not None
1096
+ }
1097
+ env["PYTHONUNBUFFERED"] = "1"
1098
+ env["NO_COLOR"] = "1"
1099
+ env.setdefault("LC_ALL", "C.UTF-8")
1100
+ return env
1101
+
1102
+
1103
+ def _join_process_output(stdout: Any, stderr: Any, max_bytes: int) -> tuple[str, bool]:
1104
+ out = stdout or b""
1105
+ err = stderr or b""
1106
+ if isinstance(out, str):
1107
+ out = out.encode("utf-8", errors="replace")
1108
+ if isinstance(err, str):
1109
+ err = err.encode("utf-8", errors="replace")
1110
+ data = out + (b"\n" if out and err else b"") + err
1111
+ content, truncated = _decode_bounded(data, max_bytes)
1112
+ return content.strip() or "[no output]", truncated
1113
+
1114
+
1115
+ def _process_failure(
1116
+ call: ValidatedToolCall,
1117
+ status: ToolExecutionStatus,
1118
+ code: ToolExecutionErrorCode,
1119
+ summary: str,
1120
+ exit_code: int,
1121
+ truncated: bool,
1122
+ certainty: SideEffectCertainty,
1123
+ ) -> ToolExecutionResult:
1124
+ side_effect_record = None
1125
+ if certainty is SideEffectCertainty.COMPLETION_UNKNOWN:
1126
+ side_effect_record = SideEffectRecord(
1127
+ certainty=certainty,
1128
+ detail_code=ToolExecutionErrorCode.AMBIGUOUS_SIDE_EFFECT.value,
1129
+ summary="process completion could not be proven after timeout",
1130
+ retry_allowed=False,
1131
+ )
1132
+ return _result(
1133
+ call,
1134
+ status,
1135
+ code,
1136
+ summary,
1137
+ {
1138
+ "status": "soft_failure",
1139
+ "summary": summary,
1140
+ "exit_code": exit_code,
1141
+ "artifact_refs": [],
1142
+ "truncated": truncated,
1143
+ },
1144
+ certainty,
1145
+ side_effect_record=side_effect_record,
1146
+ )
1147
+
1148
+
1149
+ def _terminal_disposition(
1150
+ call: ValidatedToolCall,
1151
+ ) -> Literal["success", "blocked", "rejected", "escalated"]:
1152
+ if call.binding.tool_id == "builtin.terminal.reject":
1153
+ return "rejected"
1154
+ if call.binding.tool_id == "builtin.terminal.escalate":
1155
+ return "escalated"
1156
+ terminal_result = str(call.arguments["terminal_result"])
1157
+ return (
1158
+ "blocked"
1159
+ if terminal_result.endswith("_BLOCKED") or terminal_result == "BLOCKED"
1160
+ else "success"
1161
+ )
1162
+
1163
+
1164
+ def _missing_required_terminal_artifacts(
1165
+ call: ValidatedToolCall,
1166
+ context: ToolExecutionContext,
1167
+ terminal_result: str,
1168
+ ) -> list[str]:
1169
+ policy = context.compiled_artifact_policy
1170
+ if policy is None:
1171
+ return []
1172
+ provided = set(_terminal_artifact_ids(call, context))
1173
+ required = {
1174
+ artifact_id
1175
+ for requirement in policy.required_by_terminal
1176
+ if requirement.terminal_result == terminal_result
1177
+ for artifact_id in requirement.artifact_ids
1178
+ }
1179
+ return sorted(required - provided)
1180
+
1181
+
1182
+ def _terminal_artifact_ids(
1183
+ call: ValidatedToolCall,
1184
+ context: ToolExecutionContext,
1185
+ ) -> tuple[str, ...]:
1186
+ explicit = call.arguments.get("artifact_refs")
1187
+ if explicit is None:
1188
+ return tuple(ref.artifact_id for ref in context.input_artifacts)
1189
+ refs_by_id = {ref.artifact_id: ref for ref in context.input_artifacts}
1190
+ ids: list[str] = []
1191
+ for item in explicit:
1192
+ artifact_id = str(item)
1193
+ if artifact_id not in refs_by_id:
1194
+ raise ValueError(
1195
+ f"terminal artifact_ref {artifact_id!r} is not provided by the runtime"
1196
+ )
1197
+ ids.append(artifact_id)
1198
+ return tuple(ids)
1199
+
1200
+
1201
+ def _terminal_artifact_refs(
1202
+ call: ValidatedToolCall,
1203
+ context: ToolExecutionContext,
1204
+ ) -> tuple[ArtifactRef, ...]:
1205
+ ids = _terminal_artifact_ids(call, context)
1206
+ refs_by_id = {ref.artifact_id: ref for ref in context.input_artifacts}
1207
+ return tuple(refs_by_id[artifact_id] for artifact_id in ids)
1208
+
1209
+
1210
+ def _workspace_root(context: ToolExecutionContext) -> Path | None:
1211
+ return None if context.workspace_root is None else Path(context.workspace_root)
1212
+
1213
+
1214
+ def _artifact_root(context: ToolExecutionContext) -> Path | None:
1215
+ return None if context.artifact_root is None else Path(context.artifact_root)
1216
+
1217
+
1218
+ def _artifact_policy_error(
1219
+ context: ToolExecutionContext,
1220
+ artifact_id: str,
1221
+ *,
1222
+ produced_ids: tuple[str, ...] | None,
1223
+ ) -> str | None:
1224
+ policy = context.compiled_artifact_policy
1225
+ if policy is None:
1226
+ return "compiled artifact policy is unavailable"
1227
+ if artifact_id not in policy.declared_artifact_ids:
1228
+ return "artifact_id is not declared by the compiled artifact policy"
1229
+ if produced_ids is not None and artifact_id not in produced_ids:
1230
+ return "artifact_id is not produced by this compiled tool binding"
1231
+ if artifact_id not in _ARTIFACT_FILENAMES:
1232
+ return "artifact_id has no runtime-managed artifact path"
1233
+ return None
1234
+
1235
+
1236
+ def _read_artifact_id(call: ValidatedToolCall) -> str:
1237
+ artifact_id = _READ_TOOL_ARTIFACT_ID.get(call.binding.tool_id)
1238
+ if artifact_id is not None:
1239
+ return artifact_id
1240
+ return str(call.arguments["artifact_id"])
1241
+
1242
+
1243
+ def _failure_artifact_id(call: ValidatedToolCall) -> str:
1244
+ artifact_id = _READ_TOOL_ARTIFACT_ID.get(call.binding.tool_id)
1245
+ if artifact_id is not None:
1246
+ return artifact_id
1247
+ return str(call.arguments.get("artifact_id", ""))
1248
+
1249
+
1250
+ def _workspace_read_diff_call(call: ValidatedToolCall) -> ValidatedToolCall:
1251
+ descriptor = _descriptor_for_tool_id("builtin.workspace.read_diff")
1252
+ binding = call.binding.model_copy(
1253
+ update={
1254
+ "tool_id": descriptor.tool_id,
1255
+ "tool_version": descriptor.tool_version,
1256
+ "descriptor_sha256": descriptor.descriptor_sha256,
1257
+ "implementation_id": descriptor.implementation_id,
1258
+ }
1259
+ )
1260
+ return call.model_copy(update={"binding": binding, "arguments": {}})
1261
+
1262
+
1263
+ def _artifact_logical_path(artifact_id: str) -> str:
1264
+ try:
1265
+ return _ARTIFACT_FILENAMES[artifact_id]
1266
+ except KeyError as exc:
1267
+ raise PathPolicyError(
1268
+ "artifact_id has no runtime-managed artifact path"
1269
+ ) from exc
1270
+
1271
+
1272
+ def _artifact_ref(artifact_id: str) -> ArtifactRef:
1273
+ return ArtifactRef(
1274
+ artifact_id=artifact_id,
1275
+ path=Path("millforge") / _artifact_logical_path(artifact_id),
1276
+ content_type=_ARTIFACT_CONTENT_TYPE,
1277
+ )
1278
+
1279
+
1280
+ def _descriptor_for_tool_id(tool_id: str) -> Any:
1281
+ for descriptor in iter_builtin_tool_descriptors():
1282
+ if descriptor.tool_id == tool_id:
1283
+ return descriptor
1284
+ raise KeyError(tool_id)
1285
+
1286
+
1287
+ def _bounded_int(value: Any, default: int, maximum: int) -> int:
1288
+ if value is None:
1289
+ return default
1290
+ if not isinstance(value, int) or isinstance(value, bool) or value <= 0:
1291
+ return default
1292
+ return min(value, maximum)
1293
+
1294
+
1295
+ def _decode_bounded(data: bytes, max_bytes: int) -> tuple[str, bool]:
1296
+ truncated = len(data) > max_bytes
1297
+ if truncated:
1298
+ data = data[:max_bytes]
1299
+ content = data.decode("utf-8", errors="replace")
1300
+ if truncated:
1301
+ content += "[truncated]"
1302
+ return content, truncated
1303
+
1304
+
1305
+ def _changed_paths_from_patch(root: Path, patch: str) -> list[str]:
1306
+ changed: set[str] = set()
1307
+ for line in patch.splitlines():
1308
+ if not (
1309
+ line.startswith("--- ")
1310
+ or line.startswith("+++ ")
1311
+ or line.startswith("rename from ")
1312
+ or line.startswith("rename to ")
1313
+ ):
1314
+ continue
1315
+ raw = line.split(maxsplit=1)[1].strip()
1316
+ if raw == "/dev/null":
1317
+ continue
1318
+ if raw.startswith(("a/", "b/")):
1319
+ raw = raw[2:]
1320
+ logical = validate_logical_path(raw).as_posix()
1321
+ resolve_write_contained(root, logical)
1322
+ changed.add(logical)
1323
+ if not changed:
1324
+ raise ValueError("patch does not declare any changed paths")
1325
+ return sorted(changed)
1326
+
1327
+
1328
+ def _git_diff(root: Path, paths: list[str], max_bytes: int) -> str:
1329
+ command = ["git", "diff", "--"]
1330
+ command.extend(paths)
1331
+ completed = subprocess.run(
1332
+ command,
1333
+ cwd=root,
1334
+ stdout=subprocess.PIPE,
1335
+ stderr=subprocess.PIPE,
1336
+ check=False,
1337
+ )
1338
+ data = completed.stdout[: max_bytes + 1]
1339
+ return data.decode("utf-8", errors="replace")