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,1180 @@
1
+ """Compile request, invocation, and result contracts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import errno
6
+ import hashlib
7
+ import json
8
+ import os
9
+ import stat
10
+ from collections.abc import Mapping
11
+ from enum import Enum
12
+ from pathlib import Path, PurePosixPath
13
+ from types import MappingProxyType
14
+ from typing import Any
15
+ from typing import Literal, Protocol
16
+
17
+ from pydantic import (
18
+ AliasChoices,
19
+ BaseModel,
20
+ ConfigDict,
21
+ Field,
22
+ StrictStr,
23
+ ValidationError,
24
+ field_validator,
25
+ model_validator,
26
+ )
27
+
28
+ from millforge.compiler.diagnostics import (
29
+ bound_diagnostics,
30
+ CompilerDiagnostic,
31
+ CompilerPhase,
32
+ DiagnosticField,
33
+ DiagnosticSeverity,
34
+ SourceReference,
35
+ detect_secret_candidate,
36
+ )
37
+ from millforge.compiler.parsing import (
38
+ MAX_SOURCE_SIZE_BYTES,
39
+ HarnessSourceParser,
40
+ HarnessSourceParserProtocol,
41
+ ParsedHarnessSource,
42
+ SourceDocument,
43
+ )
44
+ from millforge.compiler.source import HarnessSource
45
+ from millforge.compiler.validators import (
46
+ validate_harness_id,
47
+ validate_request_id,
48
+ validate_sha256,
49
+ validate_stage_kind_id,
50
+ validate_terminal_result,
51
+ validate_unique,
52
+ validate_utf8_size,
53
+ )
54
+ from millforge.contracts import CapabilityEnvelope, RedactionPolicy
55
+
56
+ _INVALID_REQUEST_ID = "request.invalid"
57
+
58
+
59
+ def _freeze_json_like(value: Any) -> Any:
60
+ """Recursively freeze JSON-like nested containers."""
61
+ if isinstance(value, Mapping):
62
+ return MappingProxyType(
63
+ {key: _freeze_json_like(item) for key, item in value.items()}
64
+ )
65
+ if isinstance(value, list):
66
+ return tuple(_freeze_json_like(item) for item in value)
67
+ if isinstance(value, tuple):
68
+ return tuple(_freeze_json_like(item) for item in value)
69
+ return value
70
+
71
+
72
+ class CompileStatus(str, Enum):
73
+ """Closed compile-result status values."""
74
+
75
+ FAILED = "failed"
76
+ PREPARED = "prepared"
77
+ COMMITTED = "committed"
78
+
79
+
80
+ class PlanCommitCertainty(str, Enum):
81
+ """Closed plan publication certainty values."""
82
+
83
+ ABSENT = "absent"
84
+ COMMITTED = "committed"
85
+ UNKNOWN = "unknown"
86
+
87
+
88
+ class DiagnosticReportState(str, Enum):
89
+ """Closed diagnostic report persistence states."""
90
+
91
+ ABSENT = "absent"
92
+ PREPARED = "prepared"
93
+ COMMITTED = "committed"
94
+ UNKNOWN = "unknown"
95
+
96
+
97
+ class HarnessCompileRequest(BaseModel):
98
+ """Serializable attribute-frozen public compile request."""
99
+
100
+ model_config = ConfigDict(extra="forbid", frozen=True)
101
+
102
+ request_id: StrictStr
103
+ source_path: StrictStr
104
+ source_root: StrictStr
105
+ source_format: Literal["yaml", "json"]
106
+ output_dir: StrictStr
107
+ output_root: StrictStr
108
+ expected_harness_id: StrictStr
109
+ stage_kind_id: StrictStr
110
+ legal_terminal_results: tuple[StrictStr, ...] = Field(min_length=1, max_length=64)
111
+ capability_envelope: CapabilityEnvelope
112
+
113
+ @field_validator("request_id")
114
+ @classmethod
115
+ def _request_id_valid(cls, value: str) -> str:
116
+ return validate_request_id(value)
117
+
118
+ @field_validator("source_path", "output_dir")
119
+ @classmethod
120
+ def _relative_path_size_valid(cls, value: str) -> str:
121
+ if not value.strip():
122
+ raise ValueError("path must be nonblank")
123
+ return validate_utf8_size(value, "path", 1024)
124
+
125
+ @field_validator("source_root", "output_root")
126
+ @classmethod
127
+ def _absolute_root_size_valid(cls, value: str) -> str:
128
+ if not value.startswith("/"):
129
+ raise ValueError("root must be an absolute path string")
130
+ return validate_utf8_size(value, "root", 4096)
131
+
132
+ @field_validator("expected_harness_id")
133
+ @classmethod
134
+ def _expected_harness_id_valid(cls, value: str) -> str:
135
+ return validate_harness_id(value)
136
+
137
+ @field_validator("stage_kind_id")
138
+ @classmethod
139
+ def _stage_kind_id_valid(cls, value: str) -> str:
140
+ return validate_stage_kind_id(value)
141
+
142
+ @field_validator("legal_terminal_results")
143
+ @classmethod
144
+ def _legal_terminal_results_valid(cls, value: tuple[str, ...]) -> tuple[str, ...]:
145
+ for item in value:
146
+ validate_terminal_result(item)
147
+ return validate_unique(value, "legal_terminal_results")
148
+
149
+
150
+ class CompileInvocation(BaseModel):
151
+ """Private deeply snapshotted compile invocation."""
152
+
153
+ model_config = ConfigDict(extra="forbid", frozen=True)
154
+
155
+ request: HarnessCompileRequest
156
+
157
+ @classmethod
158
+ def from_request(cls, request: HarnessCompileRequest) -> CompileInvocation:
159
+ """Build a private deep snapshot of request-owned data."""
160
+ snapshot = request.model_copy(deep=True)
161
+ for grant in snapshot.capability_envelope.grants:
162
+ if grant.constraints is not None:
163
+ object.__setattr__(
164
+ grant, "constraints", _freeze_json_like(grant.constraints)
165
+ )
166
+ return cls(request=snapshot)
167
+
168
+
169
+ class HarnessCompileResult(BaseModel):
170
+ """Immutable compile result boundary."""
171
+
172
+ model_config = ConfigDict(extra="forbid", frozen=True)
173
+
174
+ request_id: StrictStr
175
+ status: CompileStatus
176
+ plan_commit_certainty: PlanCommitCertainty
177
+ diagnostic_report_state: DiagnosticReportState = DiagnosticReportState.ABSENT
178
+ failure_phase: CompilerPhase | None = None
179
+ source_document_sha256: StrictStr | None = None
180
+ source_sha256: StrictStr | None = Field(
181
+ default=None,
182
+ validation_alias=AliasChoices("source_sha256", "source_semantic_sha256"),
183
+ )
184
+ request_identity_sha256: StrictStr | None = None
185
+ harness_id: StrictStr | None = None
186
+ compiled_plan_path: StrictStr | None = None
187
+ compiled_sha256: StrictStr | None = None
188
+ diagnostics_path: StrictStr | None = None
189
+ diagnostics: tuple[CompilerDiagnostic, ...] = Field(default_factory=tuple)
190
+
191
+ @field_validator("request_id")
192
+ @classmethod
193
+ def _request_id_valid(cls, value: str) -> str:
194
+ return validate_request_id(value)
195
+
196
+ @field_validator(
197
+ "source_document_sha256",
198
+ "source_sha256",
199
+ "request_identity_sha256",
200
+ "compiled_sha256",
201
+ )
202
+ @classmethod
203
+ def _hash_valid(cls, value: str | None, info: object) -> str | None:
204
+ if value is None:
205
+ return None
206
+ field_name = getattr(info, "field_name", "sha256")
207
+ return validate_sha256(value, field_name)
208
+
209
+ @field_validator("harness_id")
210
+ @classmethod
211
+ def _harness_id_valid(cls, value: str | None) -> str | None:
212
+ return None if value is None else validate_harness_id(value)
213
+
214
+ @field_validator("compiled_plan_path", "diagnostics_path")
215
+ @classmethod
216
+ def _relative_output_path_valid(cls, value: str | None) -> str | None:
217
+ if value is None:
218
+ return None
219
+ if not value.strip() or _relative_posix_parts(value) is None:
220
+ raise ValueError("output path must be a relative POSIX path string")
221
+ return validate_utf8_size(value, "output path", 1024)
222
+
223
+ @field_validator("diagnostics")
224
+ @classmethod
225
+ def _diagnostics_sorted(
226
+ cls, value: tuple[CompilerDiagnostic, ...]
227
+ ) -> tuple[CompilerDiagnostic, ...]:
228
+ return bound_diagnostics(value)
229
+
230
+ @model_validator(mode="after")
231
+ def _result_invariants(self) -> HarnessCompileResult:
232
+ if self.source_sha256 is not None and self.source_document_sha256 is None:
233
+ raise ValueError("semantic hashes require a source document hash")
234
+ if self.harness_id is not None and self.source_sha256 is None:
235
+ raise ValueError("harness identity requires a semantic hash")
236
+ if self.diagnostic_report_state == DiagnosticReportState.ABSENT:
237
+ if self.diagnostics_path is not None:
238
+ raise ValueError("absent diagnostics cannot carry a diagnostics path")
239
+ elif self.diagnostic_report_state in {
240
+ DiagnosticReportState.PREPARED,
241
+ DiagnosticReportState.COMMITTED,
242
+ }:
243
+ if self.diagnostics_path is None:
244
+ raise ValueError("persisted diagnostics require a diagnostics path")
245
+ if self.status == CompileStatus.FAILED:
246
+ if self.failure_phase is None:
247
+ raise ValueError("failed results require a failure phase")
248
+ if self.plan_commit_certainty == PlanCommitCertainty.COMMITTED:
249
+ raise ValueError("failed results cannot have committed certainty")
250
+ if self.failure_phase == CompilerPhase.REQUEST:
251
+ if (
252
+ self.source_document_sha256 is not None
253
+ or self.source_sha256 is not None
254
+ or self.harness_id is not None
255
+ ):
256
+ raise ValueError("request failures cannot carry source evidence")
257
+ if self.failure_phase == CompilerPhase.PARSE:
258
+ if self.source_sha256 is not None or self.harness_id is not None:
259
+ raise ValueError("parse failures cannot carry semantic evidence")
260
+ if self.plan_commit_certainty == PlanCommitCertainty.ABSENT:
261
+ if (
262
+ self.compiled_plan_path is not None
263
+ or self.compiled_sha256 is not None
264
+ ):
265
+ raise ValueError(
266
+ "failed/absent carries no plan path or compiled hash"
267
+ )
268
+ if self.plan_commit_certainty == PlanCommitCertainty.UNKNOWN:
269
+ if self.failure_phase != CompilerPhase.OUTPUT:
270
+ raise ValueError(
271
+ "unknown certainty is only valid for output failures"
272
+ )
273
+ if self.compiled_plan_path is None or self.compiled_sha256 is None:
274
+ raise ValueError(
275
+ "unknown certainty requires candidate plan path and hash"
276
+ )
277
+ if self.status == CompileStatus.PREPARED:
278
+ if self.failure_phase is not None:
279
+ raise ValueError("prepared results cannot carry a failure phase")
280
+ if (
281
+ self.source_document_sha256 is None
282
+ or self.source_sha256 is None
283
+ or self.harness_id is None
284
+ ):
285
+ raise ValueError(
286
+ "prepared results require source hashes and harness identity"
287
+ )
288
+ if self.plan_commit_certainty == PlanCommitCertainty.UNKNOWN:
289
+ raise ValueError("prepared results cannot have unknown certainty")
290
+ if self.plan_commit_certainty == PlanCommitCertainty.COMMITTED:
291
+ if self.compiled_plan_path is None or self.compiled_sha256 is None:
292
+ raise ValueError(
293
+ "prepared/committed results require compiled plan path and hash"
294
+ )
295
+ if (
296
+ self.plan_commit_certainty == PlanCommitCertainty.ABSENT
297
+ and self.compiled_plan_path is not None
298
+ ):
299
+ raise ValueError("prepared/absent carries no committed plan path")
300
+ if self.status == CompileStatus.COMMITTED:
301
+ if self.failure_phase is not None:
302
+ raise ValueError("committed results cannot carry a failure phase")
303
+ if self.plan_commit_certainty != PlanCommitCertainty.COMMITTED:
304
+ raise ValueError("committed results require committed certainty")
305
+ required = (
306
+ self.compiled_plan_path,
307
+ self.compiled_sha256,
308
+ self.source_sha256,
309
+ self.source_document_sha256,
310
+ self.harness_id,
311
+ )
312
+ if any(item is None for item in required):
313
+ raise ValueError(
314
+ "committed results require paths, hashes, and harness identity"
315
+ )
316
+ return self
317
+
318
+
319
+ class HarnessRequestAdmissionResult(BaseModel):
320
+ """Raw request admission result."""
321
+
322
+ model_config = ConfigDict(extra="forbid", frozen=True)
323
+
324
+ request: HarnessCompileRequest | None = None
325
+ parsed_source: ParsedHarnessSource | None = None
326
+ result: HarnessCompileResult | None = None
327
+
328
+ @model_validator(mode="after")
329
+ def _exactly_one_outcome(self) -> HarnessRequestAdmissionResult:
330
+ if (self.request is None) == (self.result is None):
331
+ raise ValueError("exactly one of request or result must be provided")
332
+ if self.request is None and self.parsed_source is not None:
333
+ raise ValueError("failed admission cannot carry parsed source")
334
+ if self.request is not None:
335
+ if self.parsed_source is None or self.parsed_source.source is None:
336
+ raise ValueError("successful admission requires parsed source")
337
+ return self
338
+
339
+
340
+ class HarnessCompileRequestAdmission(Protocol):
341
+ """Protocol for raw mapping admission into compile requests."""
342
+
343
+ def admit(
344
+ self,
345
+ raw_request: Mapping[str, object],
346
+ ) -> HarnessRequestAdmissionResult:
347
+ """Admit a raw request or return a failed compile result."""
348
+ ...
349
+
350
+
351
+ class DefaultHarnessCompileRequestAdmission:
352
+ """Filesystem-safe admission boundary for raw compile requests."""
353
+
354
+ def __init__(
355
+ self,
356
+ *,
357
+ parser: HarnessSourceParserProtocol | None = None,
358
+ ) -> None:
359
+ self._parser = parser or HarnessSourceParser()
360
+
361
+ def admit(
362
+ self,
363
+ raw_request: Mapping[str, object],
364
+ ) -> HarnessRequestAdmissionResult:
365
+ """Admit a raw request after path and source checks, or fail deterministically."""
366
+ request_id = _request_id_from_raw(raw_request)
367
+ try:
368
+ request = HarnessCompileRequest.model_validate(raw_request)
369
+ except ValidationError as exc:
370
+ return _failed_admission(
371
+ request_id=request_id,
372
+ code="MF-S018",
373
+ message="Compile request schema validation failed.",
374
+ phase=CompilerPhase.REQUEST,
375
+ fields=(_diagnostic_field("field_path", _validation_pointer(exc)),),
376
+ )
377
+
378
+ root_admission = _admit_roots_and_output(request)
379
+ if root_admission is not None:
380
+ return root_admission
381
+
382
+ source_admission = _read_admitted_source(request)
383
+ if isinstance(source_admission, HarnessRequestAdmissionResult):
384
+ return source_admission
385
+
386
+ parsed = self._parser.parse(
387
+ SourceDocument(
388
+ logical_path=request.source_path,
389
+ format=request.source_format,
390
+ content=source_admission,
391
+ )
392
+ )
393
+ if parsed.diagnostics:
394
+ return HarnessRequestAdmissionResult(
395
+ result=HarnessCompileResult(
396
+ request_id=request.request_id,
397
+ status=CompileStatus.FAILED,
398
+ plan_commit_certainty=PlanCommitCertainty.ABSENT,
399
+ failure_phase=parsed.diagnostics[0].phase,
400
+ source_document_sha256=(
401
+ None
402
+ if parsed.source_document_sha256 == "0" * 64
403
+ else parsed.source_document_sha256
404
+ ),
405
+ diagnostics=parsed.diagnostics,
406
+ )
407
+ )
408
+ source_diagnostics = _post_parse_source_diagnostics(
409
+ request=request,
410
+ parsed=parsed,
411
+ )
412
+ if source_diagnostics:
413
+ return _failed_source_admission(
414
+ request=request,
415
+ parsed=parsed,
416
+ diagnostics=source_diagnostics,
417
+ )
418
+ return HarnessRequestAdmissionResult(request=request, parsed_source=parsed)
419
+
420
+
421
+ def _request_id_from_raw(raw_request: Mapping[str, object]) -> str:
422
+ value = raw_request.get("request_id")
423
+ if isinstance(value, str):
424
+ try:
425
+ return validate_request_id(value)
426
+ except ValueError:
427
+ pass
428
+ return _INVALID_REQUEST_ID
429
+
430
+
431
+ def _admit_roots_and_output(
432
+ request: HarnessCompileRequest,
433
+ ) -> HarnessRequestAdmissionResult | None:
434
+ diagnostics: list[CompilerDiagnostic] = []
435
+
436
+ source_parts = _relative_posix_parts(request.source_path)
437
+ if source_parts is None:
438
+ diagnostics.append(
439
+ _request_diagnostic(
440
+ code="MF-S001",
441
+ message="Source path must stay within source_root.",
442
+ )
443
+ )
444
+
445
+ output_parts = _relative_posix_parts(request.output_dir)
446
+ if output_parts is None:
447
+ diagnostics.append(
448
+ _request_diagnostic(
449
+ code="MF-S002",
450
+ message="Output directory must stay within output_root.",
451
+ )
452
+ )
453
+
454
+ output_root = _resolve_existing_directory(
455
+ request.output_root,
456
+ request_id=request.request_id,
457
+ code="MF-S016",
458
+ label="output_root",
459
+ )
460
+ if isinstance(output_root, HarnessRequestAdmissionResult):
461
+ assert output_root.result is not None
462
+ diagnostics.extend(output_root.result.diagnostics)
463
+ output_root_path: Path | None = None
464
+ else:
465
+ output_root_path = output_root
466
+
467
+ source_root = _resolve_existing_directory(
468
+ request.source_root,
469
+ request_id=request.request_id,
470
+ code="MF-S013",
471
+ label="source_root",
472
+ )
473
+ if isinstance(source_root, HarnessRequestAdmissionResult):
474
+ assert source_root.result is not None
475
+ diagnostics.extend(source_root.result.diagnostics)
476
+ source_root_path: Path | None = None
477
+ else:
478
+ source_root_path = source_root
479
+
480
+ if output_root_path is not None and output_parts is not None:
481
+ output_diagnostic = _admit_output_directory(
482
+ request=request,
483
+ output_root=output_root_path,
484
+ output_parts=output_parts,
485
+ )
486
+ if output_diagnostic is not None:
487
+ diagnostics.append(output_diagnostic)
488
+
489
+ if source_root_path is not None and source_parts is not None:
490
+ source_diagnostic = _admit_source_path(
491
+ source_root=source_root_path,
492
+ source_parts=source_parts,
493
+ )
494
+ if source_diagnostic is not None:
495
+ diagnostics.append(source_diagnostic)
496
+
497
+ if diagnostics:
498
+ return _failed_request_admission_many(
499
+ request_id=request.request_id,
500
+ diagnostics=tuple(diagnostics),
501
+ )
502
+ return None
503
+
504
+
505
+ def _resolve_existing_directory(
506
+ value: str,
507
+ *,
508
+ request_id: str,
509
+ code: str,
510
+ label: str,
511
+ ) -> Path | HarnessRequestAdmissionResult:
512
+ try:
513
+ path = Path(value)
514
+ path_stat = path.lstat()
515
+ except OSError as exc:
516
+ return _failed_admission(
517
+ request_id=request_id,
518
+ code=code,
519
+ message=f"{label} must be an existing directory.",
520
+ phase=CompilerPhase.REQUEST,
521
+ fields=(_diagnostic_field("errno", exc.errno or errno.ENOENT),),
522
+ )
523
+ if not stat.S_ISDIR(path_stat.st_mode):
524
+ return _failed_admission(
525
+ request_id=request_id,
526
+ code=code,
527
+ message=f"{label} must be a directory.",
528
+ phase=CompilerPhase.REQUEST,
529
+ )
530
+ try:
531
+ resolved = path.resolve(strict=True)
532
+ except OSError as exc:
533
+ return _failed_admission(
534
+ request_id=request_id,
535
+ code=code,
536
+ message=f"{label} must be an existing directory.",
537
+ phase=CompilerPhase.REQUEST,
538
+ fields=(_diagnostic_field("errno", exc.errno or errno.ENOENT),),
539
+ )
540
+ return resolved
541
+
542
+
543
+ def _read_admitted_source(
544
+ request: HarnessCompileRequest,
545
+ ) -> bytes | HarnessRequestAdmissionResult:
546
+ root = Path(request.source_root).resolve(strict=True)
547
+ source_parts = _relative_posix_parts(request.source_path)
548
+ if source_parts is None:
549
+ return _failed_admission(
550
+ request_id=request.request_id,
551
+ code="MF-S001",
552
+ message="Source path must stay within source_root.",
553
+ phase=CompilerPhase.REQUEST,
554
+ )
555
+ candidate = root.joinpath(*source_parts)
556
+ try:
557
+ parent_resolved = candidate.parent.resolve(strict=True)
558
+ except OSError as exc:
559
+ return _failed_admission(
560
+ request_id=request.request_id,
561
+ code="MF-S013",
562
+ message="Source file does not exist.",
563
+ phase=CompilerPhase.REQUEST,
564
+ fields=(_diagnostic_field("errno", exc.errno or errno.ENOENT),),
565
+ )
566
+ if not _is_relative_to(parent_resolved, root):
567
+ return _failed_admission(
568
+ request_id=request.request_id,
569
+ code="MF-S001",
570
+ message="Source path must stay within source_root.",
571
+ phase=CompilerPhase.REQUEST,
572
+ )
573
+ resolved = parent_resolved.joinpath(candidate.name)
574
+
575
+ try:
576
+ before = resolved.lstat()
577
+ except FileNotFoundError as exc:
578
+ return _failed_admission(
579
+ request_id=request.request_id,
580
+ code="MF-S013",
581
+ message="Source file does not exist.",
582
+ phase=CompilerPhase.REQUEST,
583
+ fields=(_diagnostic_field("errno", exc.errno or errno.ENOENT),),
584
+ )
585
+ except OSError as exc:
586
+ return _failed_admission(
587
+ request_id=request.request_id,
588
+ code="MF-S013",
589
+ message="Source file could not be read.",
590
+ phase=CompilerPhase.REQUEST,
591
+ fields=(_diagnostic_field("errno", exc.errno or errno.EIO),),
592
+ )
593
+ if not stat.S_ISREG(before.st_mode):
594
+ if stat.S_ISLNK(before.st_mode):
595
+ try:
596
+ symlink_target = resolved.resolve(strict=True)
597
+ except OSError:
598
+ symlink_target = None
599
+ else:
600
+ if symlink_target is not None and not _is_relative_to(
601
+ symlink_target, root
602
+ ):
603
+ return _failed_admission(
604
+ request_id=request.request_id,
605
+ code="MF-S001",
606
+ message="Source path must stay within source_root.",
607
+ phase=CompilerPhase.REQUEST,
608
+ )
609
+ return _failed_admission(
610
+ request_id=request.request_id,
611
+ code="MF-S014",
612
+ message="Source path must be a regular file.",
613
+ phase=CompilerPhase.REQUEST,
614
+ )
615
+
616
+ try:
617
+ with resolved.open("rb") as handle:
618
+ opened = os.fstat(handle.fileno())
619
+ if not _same_file(before, opened) or not stat.S_ISREG(opened.st_mode):
620
+ return _source_replaced(request.request_id)
621
+ content = handle.read(MAX_SOURCE_SIZE_BYTES + 1)
622
+ if len(content) > MAX_SOURCE_SIZE_BYTES:
623
+ return _failed_admission(
624
+ request_id=request.request_id,
625
+ code="MF-S003",
626
+ message="Source file exceeds the maximum size.",
627
+ phase=CompilerPhase.PARSE,
628
+ fields=(_diagnostic_field("source_size", len(content)),),
629
+ )
630
+ after = resolved.stat()
631
+ if not _same_file(opened, after):
632
+ return _source_replaced(request.request_id)
633
+ return content
634
+ except OSError as exc:
635
+ return _failed_admission(
636
+ request_id=request.request_id,
637
+ code="MF-S013",
638
+ message="Source file could not be read.",
639
+ phase=CompilerPhase.REQUEST,
640
+ fields=(_diagnostic_field("errno", exc.errno or errno.EIO),),
641
+ )
642
+
643
+
644
+ def _relative_posix_parts(value: str) -> tuple[str, ...] | None:
645
+ path = PurePosixPath(value)
646
+ if path.is_absolute() or value.startswith("/") or "\\" in value:
647
+ return None
648
+ parts = path.parts
649
+ if not parts or any(_is_forbidden_path_part(part) for part in parts):
650
+ return None
651
+ return parts
652
+
653
+
654
+ def _is_forbidden_path_part(part: str) -> bool:
655
+ reserved_names = {
656
+ "con",
657
+ "prn",
658
+ "aux",
659
+ "nul",
660
+ *(f"com{index}" for index in range(1, 10)),
661
+ *(f"lpt{index}" for index in range(1, 10)),
662
+ }
663
+ stem = part.split(".", 1)[0].casefold()
664
+ return (
665
+ part in {"", ".", ".."}
666
+ or part.endswith((" ", "."))
667
+ or ":" in part
668
+ or "\x00" in part
669
+ or stem in reserved_names
670
+ )
671
+
672
+
673
+ def _is_relative_to(path: Path, root: Path) -> bool:
674
+ try:
675
+ path.relative_to(root)
676
+ return True
677
+ except ValueError:
678
+ return False
679
+
680
+
681
+ def _same_file(left: os.stat_result, right: os.stat_result) -> bool:
682
+ return (
683
+ left.st_dev,
684
+ left.st_ino,
685
+ left.st_size,
686
+ left.st_mtime_ns,
687
+ ) == (
688
+ right.st_dev,
689
+ right.st_ino,
690
+ right.st_size,
691
+ right.st_mtime_ns,
692
+ )
693
+
694
+
695
+ def _source_replaced(request_id: str) -> HarnessRequestAdmissionResult:
696
+ return _failed_admission(
697
+ request_id=request_id,
698
+ code="MF-S015",
699
+ message="Source file changed during admission.",
700
+ phase=CompilerPhase.REQUEST,
701
+ )
702
+
703
+
704
+ def _failed_source_admission(
705
+ *,
706
+ request: HarnessCompileRequest,
707
+ parsed: ParsedHarnessSource,
708
+ diagnostics: tuple[CompilerDiagnostic, ...],
709
+ ) -> HarnessRequestAdmissionResult:
710
+ source = parsed.source
711
+ source_sha256 = None
712
+ harness_id = None
713
+ if source is not None:
714
+ source_sha256 = _source_semantic_sha256(source)
715
+ harness_id = source.harness_id
716
+ return HarnessRequestAdmissionResult(
717
+ result=HarnessCompileResult(
718
+ request_id=request.request_id,
719
+ status=CompileStatus.FAILED,
720
+ plan_commit_certainty=PlanCommitCertainty.ABSENT,
721
+ failure_phase=CompilerPhase.SCHEMA,
722
+ source_document_sha256=parsed.source_document_sha256,
723
+ source_sha256=source_sha256,
724
+ harness_id=harness_id,
725
+ diagnostics=diagnostics,
726
+ )
727
+ )
728
+
729
+
730
+ def _source_semantic_sha256(source: HarnessSource) -> str:
731
+ payload = json.dumps(
732
+ source.model_dump(mode="json"),
733
+ sort_keys=True,
734
+ separators=(",", ":"),
735
+ ).encode("utf-8")
736
+ return hashlib.sha256(payload).hexdigest()
737
+
738
+
739
+ def _post_parse_source_diagnostics(
740
+ *,
741
+ request: HarnessCompileRequest,
742
+ parsed: ParsedHarnessSource,
743
+ ) -> tuple[CompilerDiagnostic, ...]:
744
+ source = parsed.source
745
+ if source is None:
746
+ return ()
747
+ diagnostics = [
748
+ *_cross_field_diagnostics(
749
+ request=request,
750
+ source=source,
751
+ logical_path=request.source_path,
752
+ location_index=parsed.location_index,
753
+ ),
754
+ *_secret_scalar_diagnostics(
755
+ source=source,
756
+ logical_path=request.source_path,
757
+ location_index=parsed.location_index,
758
+ ),
759
+ ]
760
+ return bound_diagnostics(diagnostics)
761
+
762
+
763
+ def _cross_field_diagnostics(
764
+ *,
765
+ request: HarnessCompileRequest,
766
+ source: HarnessSource,
767
+ logical_path: str,
768
+ location_index: tuple[SourceReference, ...],
769
+ ) -> tuple[CompilerDiagnostic, ...]:
770
+ diagnostics: list[CompilerDiagnostic] = []
771
+ if source.harness_id != request.expected_harness_id:
772
+ diagnostics.append(
773
+ _source_schema_diagnostic(
774
+ code="MF-S027",
775
+ message="Source harness_id does not match expected_harness_id.",
776
+ logical_path=logical_path,
777
+ field_path="/harness_id",
778
+ location_index=location_index,
779
+ fields=(_diagnostic_field("field_path", "/harness_id"),),
780
+ )
781
+ )
782
+
783
+ if request.stage_kind_id not in source.stage_scope.stage_kind_ids:
784
+ diagnostics.append(
785
+ _source_schema_diagnostic(
786
+ code="MF-S028",
787
+ message="Requested stage_kind_id is outside source stage_scope.",
788
+ logical_path=logical_path,
789
+ field_path="/stage_scope/stage_kind_ids",
790
+ location_index=location_index,
791
+ fields=(
792
+ _diagnostic_field("field_path", "/stage_scope/stage_kind_ids"),
793
+ ),
794
+ )
795
+ )
796
+
797
+ terminal_diagnostic = _terminal_result_diagnostic(
798
+ request=request,
799
+ source=source,
800
+ logical_path=logical_path,
801
+ location_index=location_index,
802
+ )
803
+ if terminal_diagnostic is not None:
804
+ diagnostics.append(terminal_diagnostic)
805
+ return tuple(diagnostics)
806
+
807
+
808
+ def _terminal_result_diagnostic(
809
+ *,
810
+ request: HarnessCompileRequest,
811
+ source: HarnessSource,
812
+ logical_path: str,
813
+ location_index: tuple[SourceReference, ...],
814
+ ) -> CompilerDiagnostic | None:
815
+ terminal_paths: dict[str, str] = {}
816
+ for index, node in enumerate(source.graph.nodes):
817
+ if node.terminal_result is None:
818
+ continue
819
+ field_path = f"/graph/nodes/{index}/terminal_result"
820
+ if node.terminal_result in terminal_paths:
821
+ return _source_schema_diagnostic(
822
+ code="MF-S029",
823
+ message="Legal terminal results must match source terminal declarations.",
824
+ logical_path=logical_path,
825
+ field_path=field_path,
826
+ location_index=location_index,
827
+ fields=(_diagnostic_field("field_path", field_path),),
828
+ )
829
+ terminal_paths[node.terminal_result] = field_path
830
+ source_terminals = set(terminal_paths)
831
+ requested_terminals = set(request.legal_terminal_results)
832
+
833
+ missing_source_terminal = next(
834
+ (
835
+ terminal
836
+ for terminal in request.legal_terminal_results
837
+ if terminal not in source_terminals
838
+ ),
839
+ None,
840
+ )
841
+ undeclared_source_terminal = next(
842
+ (
843
+ node.terminal_result
844
+ for node in source.graph.nodes
845
+ if node.terminal_result is not None
846
+ and node.terminal_result not in requested_terminals
847
+ ),
848
+ None,
849
+ )
850
+ unused_artifact_mapping = next(
851
+ (
852
+ item.terminal_result
853
+ for item in source.artifacts.required_by_terminal
854
+ if item.terminal_result not in source_terminals
855
+ ),
856
+ None,
857
+ )
858
+ if (
859
+ missing_source_terminal is None
860
+ and undeclared_source_terminal is None
861
+ and unused_artifact_mapping is None
862
+ ):
863
+ return None
864
+
865
+ if undeclared_source_terminal is not None:
866
+ field_path = terminal_paths[undeclared_source_terminal]
867
+ elif unused_artifact_mapping is not None:
868
+ field_path = next(
869
+ (
870
+ f"/artifacts/required_by_terminal/{index}/terminal_result"
871
+ for index, item in enumerate(source.artifacts.required_by_terminal)
872
+ if item.terminal_result == unused_artifact_mapping
873
+ ),
874
+ "/artifacts/required_by_terminal",
875
+ )
876
+ else:
877
+ field_path = "/graph/nodes"
878
+ return _source_schema_diagnostic(
879
+ code="MF-S029",
880
+ message="Legal terminal results must match source terminal declarations.",
881
+ logical_path=logical_path,
882
+ field_path=field_path,
883
+ location_index=location_index,
884
+ fields=(_diagnostic_field("field_path", field_path),),
885
+ )
886
+
887
+
888
+ def _secret_scalar_diagnostics(
889
+ *,
890
+ source: HarnessSource,
891
+ logical_path: str,
892
+ location_index: tuple[SourceReference, ...],
893
+ ) -> tuple[CompilerDiagnostic, ...]:
894
+ policy = RedactionPolicy()
895
+ diagnostics: list[CompilerDiagnostic] = []
896
+ for field_path, field_name, value in _source_scalar_strings(
897
+ source.model_dump(mode="python")
898
+ ):
899
+ if not detect_secret_candidate(
900
+ field_path=field_path,
901
+ field_name=field_name,
902
+ value=value,
903
+ policy=policy,
904
+ ):
905
+ continue
906
+ diagnostics.append(
907
+ _source_schema_diagnostic(
908
+ code="MF-S026",
909
+ message="Source scalar resembles a secret and must be removed.",
910
+ logical_path=logical_path,
911
+ field_path=field_path,
912
+ location_index=location_index,
913
+ fields=(_diagnostic_field("field_path", field_path),),
914
+ )
915
+ )
916
+ return tuple(diagnostics)
917
+
918
+
919
+ def _source_scalar_strings(
920
+ value: object,
921
+ *,
922
+ field_path: str = "/",
923
+ field_name: str = "",
924
+ ) -> tuple[tuple[str, str, str], ...]:
925
+ if isinstance(value, str):
926
+ return ((field_path, field_name, value),)
927
+ if isinstance(value, Mapping):
928
+ result: list[tuple[str, str, str]] = []
929
+ for key, item in value.items():
930
+ if not isinstance(key, str):
931
+ continue
932
+ result.extend(
933
+ _source_scalar_strings(
934
+ item,
935
+ field_path=_join_pointer(field_path, key),
936
+ field_name=key,
937
+ )
938
+ )
939
+ return tuple(result)
940
+ if isinstance(value, tuple | list):
941
+ result = []
942
+ for index, item in enumerate(value):
943
+ result.extend(
944
+ _source_scalar_strings(
945
+ item,
946
+ field_path=_join_pointer(field_path, str(index)),
947
+ field_name=field_name,
948
+ )
949
+ )
950
+ return tuple(result)
951
+ return ()
952
+
953
+
954
+ def _source_schema_diagnostic(
955
+ *,
956
+ code: str,
957
+ message: str,
958
+ logical_path: str,
959
+ field_path: str,
960
+ location_index: tuple[SourceReference, ...],
961
+ fields: tuple[DiagnosticField, ...] = (),
962
+ ) -> CompilerDiagnostic:
963
+ return CompilerDiagnostic(
964
+ code=code,
965
+ severity=DiagnosticSeverity.ERROR,
966
+ phase=CompilerPhase.SCHEMA,
967
+ message=message,
968
+ source_reference=_nearest_source_reference(
969
+ logical_path=logical_path,
970
+ field_path=field_path,
971
+ location_index=location_index,
972
+ ),
973
+ fields=fields,
974
+ )
975
+
976
+
977
+ def _nearest_source_reference(
978
+ *,
979
+ logical_path: str,
980
+ field_path: str,
981
+ location_index: tuple[SourceReference, ...],
982
+ ) -> SourceReference:
983
+ by_path = {reference.field_path: reference for reference in location_index}
984
+ candidate = field_path
985
+ while candidate:
986
+ if candidate in by_path:
987
+ reference = by_path[candidate]
988
+ if reference.field_path == field_path:
989
+ return reference
990
+ return SourceReference(
991
+ logical_path=reference.logical_path,
992
+ field_path=field_path,
993
+ location=reference.location,
994
+ )
995
+ if candidate == "/":
996
+ break
997
+ candidate = candidate.rsplit("/", 1)[0] or "/"
998
+ return SourceReference(logical_path=logical_path, field_path=field_path)
999
+
1000
+
1001
+ def _failed_admission(
1002
+ *,
1003
+ request_id: str,
1004
+ code: str,
1005
+ message: str,
1006
+ phase: CompilerPhase,
1007
+ fields: tuple[DiagnosticField, ...] = (),
1008
+ ) -> HarnessRequestAdmissionResult:
1009
+ return _failed_request_admission_many(
1010
+ request_id=request_id,
1011
+ diagnostics=(
1012
+ CompilerDiagnostic(
1013
+ code=code,
1014
+ severity=DiagnosticSeverity.ERROR,
1015
+ phase=phase,
1016
+ message=message,
1017
+ fields=fields,
1018
+ ),
1019
+ ),
1020
+ )
1021
+
1022
+
1023
+ def _failed_request_admission_many(
1024
+ *,
1025
+ request_id: str,
1026
+ diagnostics: tuple[CompilerDiagnostic, ...],
1027
+ ) -> HarnessRequestAdmissionResult:
1028
+ return HarnessRequestAdmissionResult(
1029
+ result=HarnessCompileResult(
1030
+ request_id=request_id,
1031
+ status=CompileStatus.FAILED,
1032
+ plan_commit_certainty=PlanCommitCertainty.ABSENT,
1033
+ failure_phase=diagnostics[0].phase,
1034
+ diagnostics=diagnostics,
1035
+ )
1036
+ )
1037
+
1038
+
1039
+ def _request_diagnostic(
1040
+ *,
1041
+ code: str,
1042
+ message: str,
1043
+ fields: tuple[DiagnosticField, ...] = (),
1044
+ ) -> CompilerDiagnostic:
1045
+ return CompilerDiagnostic(
1046
+ code=code,
1047
+ severity=DiagnosticSeverity.ERROR,
1048
+ phase=CompilerPhase.REQUEST,
1049
+ message=message,
1050
+ fields=fields,
1051
+ )
1052
+
1053
+
1054
+ def _admit_output_directory(
1055
+ *,
1056
+ request: HarnessCompileRequest,
1057
+ output_root: Path,
1058
+ output_parts: tuple[str, ...],
1059
+ ) -> CompilerDiagnostic | None:
1060
+ candidate = output_root.joinpath(*output_parts)
1061
+ try:
1062
+ parent_resolved = candidate.parent.resolve(strict=True)
1063
+ except OSError as exc:
1064
+ return _request_diagnostic(
1065
+ code="MF-S017",
1066
+ message="Output directory must already exist.",
1067
+ fields=(_diagnostic_field("errno", exc.errno or errno.ENOENT),),
1068
+ )
1069
+ if not _is_relative_to(parent_resolved, output_root):
1070
+ return _request_diagnostic(
1071
+ code="MF-S002",
1072
+ message="Output directory must stay within output_root.",
1073
+ )
1074
+ resolved = parent_resolved.joinpath(candidate.name)
1075
+ try:
1076
+ before = resolved.lstat()
1077
+ except FileNotFoundError as exc:
1078
+ return _request_diagnostic(
1079
+ code="MF-S017",
1080
+ message="Output directory must already exist.",
1081
+ fields=(_diagnostic_field("errno", exc.errno or errno.ENOENT),),
1082
+ )
1083
+ except OSError as exc:
1084
+ return _request_diagnostic(
1085
+ code="MF-S017",
1086
+ message="Output directory could not be admitted.",
1087
+ fields=(_diagnostic_field("errno", exc.errno or errno.EIO),),
1088
+ )
1089
+ if stat.S_ISLNK(before.st_mode):
1090
+ try:
1091
+ symlink_target = resolved.resolve(strict=True)
1092
+ except OSError as exc:
1093
+ return _request_diagnostic(
1094
+ code="MF-S017",
1095
+ message="Output directory could not be admitted.",
1096
+ fields=(_diagnostic_field("errno", exc.errno or errno.EIO),),
1097
+ )
1098
+ if not _is_relative_to(symlink_target, output_root):
1099
+ return _request_diagnostic(
1100
+ code="MF-S002",
1101
+ message="Output directory must stay within output_root.",
1102
+ )
1103
+ return _request_diagnostic(
1104
+ code="MF-S017",
1105
+ message="Output directory must be a directory.",
1106
+ fields=(_diagnostic_field("output_dir", request.output_dir),),
1107
+ )
1108
+ if not stat.S_ISDIR(before.st_mode):
1109
+ return _request_diagnostic(
1110
+ code="MF-S017",
1111
+ message="Output directory must be a directory.",
1112
+ fields=(_diagnostic_field("output_dir", request.output_dir),),
1113
+ )
1114
+ try:
1115
+ resolved = resolved.resolve(strict=True)
1116
+ except OSError as exc:
1117
+ return _request_diagnostic(
1118
+ code="MF-S017",
1119
+ message="Output directory could not be admitted.",
1120
+ fields=(_diagnostic_field("errno", exc.errno or errno.EIO),),
1121
+ )
1122
+ if not _is_relative_to(resolved, output_root):
1123
+ return _request_diagnostic(
1124
+ code="MF-S002",
1125
+ message="Output directory must stay within output_root.",
1126
+ )
1127
+ return None
1128
+
1129
+
1130
+ def _admit_source_path(
1131
+ *,
1132
+ source_root: Path,
1133
+ source_parts: tuple[str, ...],
1134
+ ) -> CompilerDiagnostic | None:
1135
+ candidate = source_root.joinpath(*source_parts)
1136
+ try:
1137
+ parent_resolved = candidate.parent.resolve(strict=True)
1138
+ except OSError:
1139
+ return None
1140
+ if not _is_relative_to(parent_resolved, source_root):
1141
+ return _request_diagnostic(
1142
+ code="MF-S001",
1143
+ message="Source path must stay within source_root.",
1144
+ )
1145
+ resolved = parent_resolved.joinpath(candidate.name)
1146
+ try:
1147
+ before = resolved.lstat()
1148
+ except OSError:
1149
+ return None
1150
+ if stat.S_ISLNK(before.st_mode):
1151
+ try:
1152
+ symlink_target = resolved.resolve(strict=True)
1153
+ except OSError:
1154
+ return None
1155
+ if not _is_relative_to(symlink_target, source_root):
1156
+ return _request_diagnostic(
1157
+ code="MF-S001",
1158
+ message="Source path must stay within source_root.",
1159
+ )
1160
+ return None
1161
+
1162
+
1163
+ def _diagnostic_field(key: str, value: str | int) -> DiagnosticField:
1164
+ return DiagnosticField(key=key, value=value)
1165
+
1166
+
1167
+ def _join_pointer(parent: str, token: str) -> str:
1168
+ escaped = token.replace("~", "~0").replace("/", "~1")
1169
+ return f"/{escaped}" if parent == "/" else f"{parent}/{escaped}"
1170
+
1171
+
1172
+ def _validation_pointer(exc: ValidationError) -> str:
1173
+ errors = exc.errors()
1174
+ if not errors:
1175
+ return "/"
1176
+ loc = errors[0]["loc"]
1177
+ if not isinstance(loc, tuple) or not loc:
1178
+ return "/"
1179
+ parts = [str(part).replace("~", "~0").replace("/", "~1") for part in loc]
1180
+ return "/" + "/".join(parts)