graphite-code 0.3.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 (112) hide show
  1. graphite/__init__.py +41 -0
  2. graphite/__main__.py +7 -0
  3. graphite/_cleanup_worker.py +525 -0
  4. graphite/activation.py +164 -0
  5. graphite/agent_hooks.py +577 -0
  6. graphite/agent_settings.py +226 -0
  7. graphite/analyze.py +146 -0
  8. graphite/answer_contract.py +420 -0
  9. graphite/bootstrap.py +210 -0
  10. graphite/buildlock.py +99 -0
  11. graphite/cache.py +131 -0
  12. graphite/channel.py +1325 -0
  13. graphite/cli.py +3053 -0
  14. graphite/cluster.py +111 -0
  15. graphite/config.py +209 -0
  16. graphite/context.py +355 -0
  17. graphite/daemon.py +745 -0
  18. graphite/daemon_health.py +733 -0
  19. graphite/debt.py +118 -0
  20. graphite/dependency_install.py +1597 -0
  21. graphite/detach.py +33 -0
  22. graphite/doctor.py +678 -0
  23. graphite/doctor_probes.py +2100 -0
  24. graphite/engine_identity.py +238 -0
  25. graphite/export/__init__.py +6 -0
  26. graphite/export/html.py +244 -0
  27. graphite/export/json.py +39 -0
  28. graphite/export/md.py +68 -0
  29. graphite/extract/__init__.py +4 -0
  30. graphite/extract/ast.py +1964 -0
  31. graphite/freshness.py +127 -0
  32. graphite/git.py +406 -0
  33. graphite/graph.py +117 -0
  34. graphite/graph_io.py +188 -0
  35. graphite/health.py +147 -0
  36. graphite/hook_entry.py +68 -0
  37. graphite/hookinstall.py +224 -0
  38. graphite/hookshim.py +86 -0
  39. graphite/incident_ledger.py +247 -0
  40. graphite/ingest.py +279 -0
  41. graphite/init.py +791 -0
  42. graphite/io.py +32 -0
  43. graphite/listing.py +51 -0
  44. graphite/llm.py +518 -0
  45. graphite/llm_probe.py +157 -0
  46. graphite/mcp.py +7 -0
  47. graphite/mcp_server.py +450 -0
  48. graphite/natural_query.py +252 -0
  49. graphite/overlays.py +713 -0
  50. graphite/probe_process.py +879 -0
  51. graphite/probe_workspace.py +728 -0
  52. graphite/process_contracts.py +22 -0
  53. graphite/provider_observer.py +397 -0
  54. graphite/query.py +646 -0
  55. graphite/query_plan.py +97 -0
  56. graphite/replacement_audit.py +291 -0
  57. graphite/resolve.py +660 -0
  58. graphite/review.py +782 -0
  59. graphite/routing/__init__.py +5 -0
  60. graphite/routing/approval.py +362 -0
  61. graphite/routing/classifier.py +169 -0
  62. graphite/routing/claude_executor.py +419 -0
  63. graphite/routing/claude_probe.py +102 -0
  64. graphite/routing/cli_identity.py +84 -0
  65. graphite/routing/codex_executor.py +383 -0
  66. graphite/routing/codex_probe.py +93 -0
  67. graphite/routing/context_builder.py +327 -0
  68. graphite/routing/contracts.py +802 -0
  69. graphite/routing/diff_policy.py +468 -0
  70. graphite/routing/edit_apply.py +166 -0
  71. graphite/routing/effort.py +43 -0
  72. graphite/routing/lifecycle.py +771 -0
  73. graphite/routing/lifecycle_operator.py +227 -0
  74. graphite/routing/lifecycle_service.py +555 -0
  75. graphite/routing/lifecycle_storage.py +977 -0
  76. graphite/routing/ollama_executor.py +341 -0
  77. graphite/routing/ollama_probe.py +72 -0
  78. graphite/routing/openrouter_executor.py +338 -0
  79. graphite/routing/openrouter_probe.py +188 -0
  80. graphite/routing/policy.py +815 -0
  81. graphite/routing/probe_runner.py +543 -0
  82. graphite/routing/process_runner.py +523 -0
  83. graphite/routing/profiles.py +554 -0
  84. graphite/routing/prompt.py +58 -0
  85. graphite/routing/registry.py +444 -0
  86. graphite/routing/route_pool.py +629 -0
  87. graphite/routing/route_pool_execution.py +275 -0
  88. graphite/routing/schema_validation.py +169 -0
  89. graphite/routing/service.py +1263 -0
  90. graphite/routing/settings.py +99 -0
  91. graphite/routing/shadow.py +201 -0
  92. graphite/routing/storage.py +4001 -0
  93. graphite/routing/telemetry.py +346 -0
  94. graphite/routing/worktree.py +259 -0
  95. graphite/routing/zai_edit.py +113 -0
  96. graphite/routing/zai_executor.py +191 -0
  97. graphite/routing/zai_probe.py +126 -0
  98. graphite/savings.py +84 -0
  99. graphite/ts_bridge.py +142 -0
  100. graphite/ts_resolver.mjs +314 -0
  101. graphite/typescript_activation.py +1586 -0
  102. graphite/usage_ledger.py +156 -0
  103. graphite/validation.py +148 -0
  104. graphite/watch.py +167 -0
  105. graphite/windows_job.py +368 -0
  106. graphite/windows_startup.py +144 -0
  107. graphite/windows_task.py +212 -0
  108. graphite_code-0.3.0.dist-info/METADATA +743 -0
  109. graphite_code-0.3.0.dist-info/RECORD +112 -0
  110. graphite_code-0.3.0.dist-info/WHEEL +4 -0
  111. graphite_code-0.3.0.dist-info/entry_points.txt +3 -0
  112. graphite_code-0.3.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,2100 @@
1
+ """Deadline-aware orchestration for isolated deep readiness probes."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import math
6
+ import os
7
+ import platform
8
+ import re
9
+ import sys
10
+ import tempfile
11
+ import threading
12
+ import time
13
+ from collections.abc import Callable
14
+ from concurrent.futures import ThreadPoolExecutor
15
+ from functools import lru_cache
16
+ from importlib import machinery, metadata
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ from .config import Config
21
+ from .doctor import DoctorCheck
22
+ from .incident_ledger import record_incident, repo_ledger_dir
23
+ from .llm import canonical_provider_name
24
+ from .llm_probe import SYSTEM_PROMPT as _LLM_SYSTEM_PROMPT
25
+ from .llm_probe import USER_PROMPT as _LLM_USER_PROMPT
26
+ from .llm_probe import WORKER_INPUT_LIMIT_BYTES as _LLM_WORKER_INPUT_LIMIT_BYTES
27
+ from .probe_process import (
28
+ INPUT_LIMIT_BYTES,
29
+ ProbeProcessError,
30
+ ProbeProcessResult,
31
+ run_bounded_process,
32
+ )
33
+ from .probe_workspace import ProbeWorkspaceLease, WorkspaceLeaseError
34
+
35
+ _CLEANUP_RESERVE_SECONDS = 1.0
36
+ _CORE_PROBE_SLOT_LOCK = threading.Lock()
37
+ _CORE_PROBE_SLOT_ACTIVE = False
38
+ _MCP_OUTPUT_LIMIT_BYTES = 1024 * 1024
39
+ _MCP_LINE_LIMIT = 64
40
+ _MCP_RESPONSE_LIMIT = 8
41
+ _MCP_NESTING_LIMIT = 32
42
+ _MCP_METADATA_ROOT_LIMIT = 64
43
+ _MCP_BUILDER_ARGUMENT_LIMIT_BYTES = 16 * 1024
44
+ _MCP_PROTOCOL_VERSION = "2024-11-05"
45
+ _LLM_FAILURE_REMEDIATION = (
46
+ "Verify the provider endpoint.",
47
+ "Use a rotated session credential.",
48
+ "Verify the configured model.",
49
+ "Verify the provider timeout.",
50
+ )
51
+ _LLM_CATEGORIES = frozenset(
52
+ {"configuration", "authentication", "timeout", "connection", "provider_error"}
53
+ )
54
+ _LLM_OUTPUT_LIMIT_BYTES = 4096
55
+ _LLM_TIMEOUT_MAX_SECONDS = 60.0
56
+ _REQUIRED_MCP_TOOLS = frozenset(
57
+ {"graphite_query", "graphite_summary", "graphite_community", "graphite_refresh"}
58
+ )
59
+ _TYPESCRIPT_SCRIPT = (
60
+ "try{require.resolve('typescript')}catch(error){"
61
+ "if(error&&error.code==='MODULE_NOT_FOUND'){"
62
+ "process.stdout.write(JSON.stringify({missing_module:'typescript'}));process.exit(0)}"
63
+ "process.exit(4)}"
64
+ "process.stdout.write(JSON.stringify({detected:true}));"
65
+ )
66
+ _MCP_MANIFEST_BUILDER_BOOTSTRAP = """\
67
+ import os
68
+ import json
69
+ import pathlib
70
+ import sys
71
+ from importlib.machinery import PathFinder
72
+
73
+ sys.excepthook = lambda *_: os._exit(70)
74
+ def validate_binding(raw, require_directory):
75
+ if not isinstance(raw, dict) or set(raw) != {"canonical", "identity", "lexical"}:
76
+ raise SystemExit(70)
77
+ lexical = pathlib.Path(raw["lexical"])
78
+ canonical = pathlib.Path(raw["canonical"])
79
+ identity = raw["identity"]
80
+ if (
81
+ not isinstance(raw["lexical"], str)
82
+ or not isinstance(raw["canonical"], str)
83
+ or not lexical.is_absolute()
84
+ or not canonical.is_absolute()
85
+ or not isinstance(identity, list)
86
+ or len(identity) != 4
87
+ or any(not isinstance(item, int) or isinstance(item, bool) for item in identity)
88
+ ):
89
+ raise SystemExit(70)
90
+ if lexical.resolve(strict=True) != canonical or canonical.resolve(strict=True) != canonical:
91
+ raise SystemExit(70)
92
+ stat = canonical.stat()
93
+ if [stat.st_dev, stat.st_ino, stat.st_size, stat.st_mtime_ns] != identity:
94
+ raise SystemExit(70)
95
+ if canonical.is_dir() != require_directory:
96
+ raise SystemExit(70)
97
+ return lexical, canonical
98
+ def overlaps(left, right):
99
+ try:
100
+ left.relative_to(right)
101
+ return True
102
+ except ValueError:
103
+ pass
104
+ try:
105
+ right.relative_to(left)
106
+ return True
107
+ except ValueError:
108
+ return False
109
+ def bind_path(raw, require_directory):
110
+ try:
111
+ raw = os.fspath(raw)
112
+ except TypeError:
113
+ raise SystemExit(70) from None
114
+ if not isinstance(raw, str):
115
+ raise SystemExit(70)
116
+ lexical = pathlib.Path(os.path.abspath(raw))
117
+ canonical = lexical.resolve(strict=True)
118
+ stat = canonical.stat()
119
+ binding = {
120
+ "lexical": str(lexical),
121
+ "canonical": str(canonical),
122
+ "identity": [stat.st_dev, stat.st_ino, stat.st_size, stat.st_mtime_ns],
123
+ }
124
+ validate_binding(binding, require_directory)
125
+ return binding, lexical, canonical
126
+ trusted_binding, trusted_lexical, trusted = bind_path(sys.argv[1], True)
127
+ selected_binding, _, selected = bind_path(sys.argv[2], True)
128
+ init_binding, init_lexical, expected_init = bind_path(
129
+ trusted_lexical / "graphite" / "__init__.py", False
130
+ )
131
+ doctor_binding, doctor_lexical, expected_doctor = bind_path(
132
+ trusted_lexical / "graphite" / "doctor_probes.py", False
133
+ )
134
+ mcp_binding, mcp_lexical, expected_mcp = bind_path(
135
+ trusted_lexical / "graphite" / "mcp.py", False
136
+ )
137
+ if (
138
+ init_lexical != trusted_lexical / "graphite" / "__init__.py"
139
+ or doctor_lexical != trusted_lexical / "graphite" / "doctor_probes.py"
140
+ or mcp_lexical != trusted_lexical / "graphite" / "mcp.py"
141
+ or expected_init.parent != trusted / "graphite"
142
+ or expected_doctor.parent != trusted / "graphite"
143
+ or expected_mcp.parent != trusted / "graphite"
144
+ ):
145
+ raise SystemExit(70)
146
+ stdlib_path = list(sys.path)
147
+ graphite_spec = PathFinder.find_spec("graphite", [str(trusted)])
148
+ if (
149
+ graphite_spec is None
150
+ or graphite_spec.origin is None
151
+ or pathlib.Path(graphite_spec.origin).resolve(strict=True) != expected_init
152
+ or graphite_spec.submodule_search_locations is None
153
+ or [pathlib.Path(item).resolve(strict=True) for item in graphite_spec.submodule_search_locations]
154
+ != [expected_init.parent]
155
+ ):
156
+ raise SystemExit(70)
157
+ doctor_spec = PathFinder.find_spec(
158
+ "graphite.doctor_probes", list(graphite_spec.submodule_search_locations)
159
+ )
160
+ if (
161
+ doctor_spec is None
162
+ or doctor_spec.origin is None
163
+ or pathlib.Path(doctor_spec.origin).resolve(strict=True) != expected_doctor
164
+ ):
165
+ raise SystemExit(70)
166
+ sys.path[:] = [str(trusted), *stdlib_path]
167
+ from graphite import doctor_probes
168
+ if pathlib.Path(doctor_probes.__file__).resolve(strict=True) != expected_doctor:
169
+ raise SystemExit(70)
170
+ cached_payload = sys.stdin.buffer.read()
171
+ raw_metadata_roots = json.loads(sys.argv[3])
172
+ if not isinstance(raw_metadata_roots, list) or len(raw_metadata_roots) > 64:
173
+ raise SystemExit(70)
174
+ metadata_roots = []
175
+ seen_roots = set()
176
+ for raw in raw_metadata_roots:
177
+ if not isinstance(raw, str) or not os.path.isabs(raw):
178
+ raise SystemExit(70)
179
+ candidate = pathlib.Path(os.path.abspath(raw))
180
+ try:
181
+ canonical_candidate = candidate.resolve(strict=True)
182
+ except OSError:
183
+ continue
184
+ if not canonical_candidate.is_dir():
185
+ continue
186
+ _, lexical, root = bind_path(raw, True)
187
+ if root == trusted:
188
+ continue
189
+ if overlaps(root, selected):
190
+ if any(doctor_probes.metadata.Distribution.discover(path=[str(lexical)])):
191
+ raise SystemExit(70)
192
+ continue
193
+ normalized = os.path.normcase(str(root))
194
+ if normalized in seen_roots:
195
+ raise SystemExit(70)
196
+ seen_roots.add(normalized)
197
+ metadata_roots.append(lexical)
198
+ if cached_payload:
199
+ manifest = json.loads(cached_payload.decode("utf-8"))
200
+ manifest = doctor_probes._validate_mcp_manifest(manifest, selected)
201
+ else:
202
+ manifest = doctor_probes._mcp_import_manifest(selected, tuple(metadata_roots))
203
+ envelope = {
204
+ "bindings": {
205
+ "doctor": doctor_binding,
206
+ "init": init_binding,
207
+ "mcp": mcp_binding,
208
+ "selected": selected_binding,
209
+ "trusted": trusted_binding,
210
+ },
211
+ "manifest": manifest,
212
+ }
213
+ sys.stdout.write(json.dumps(envelope, separators=(",", ":")))
214
+ """
215
+ # Hard ceiling on what the child may write to stderr. Not belt-and-braces: the
216
+ # transcript is rejected when stdout plus stderr passes _MCP_OUTPUT_LIMIT_BYTES,
217
+ # and `run_bounded_process` fails the probe outright on the same bound, so an
218
+ # unbounded debug log would manufacture the failure it was added to explain.
219
+ _MCP_CHILD_LOG_BUDGET = 3000
220
+ # Route the MCP library's own log to stderr before handing off to the server.
221
+ #
222
+ # Issue #29's failure mode is `returncode=0`, empty stderr, and a transcript
223
+ # holding only the `initialize` reply -- the child never says why it stopped, so
224
+ # every mechanism proposed for it so far was inferred from adjacent evidence and
225
+ # four were refuted by measurement. The library already logs the facts that
226
+ # separate the survivors ("Dispatching request of type ...", "Response sent",
227
+ # "Request N cancelled - duplicate response suppressed"); nothing was listening.
228
+ #
229
+ # Kept out of the transport itself deliberately. Wrapping `sys.stdin.buffer` or
230
+ # `sys.stdout.buffer` to count bytes would perturb the very timing under
231
+ # observation; a log handler does not sit in the data path.
232
+ _MCP_CHILD_LOG_SETUP = f"""\
233
+ import logging as _logging
234
+ import sys as _sys
235
+ class _BoundedProbeLog(_logging.Handler):
236
+ def __init__(self, budget):
237
+ _logging.Handler.__init__(self)
238
+ self.budget = budget
239
+ def emit(self, record):
240
+ if self.budget <= 0:
241
+ return
242
+ try:
243
+ text = record.name.rpartition(".")[2] + ":" + record.getMessage()
244
+ line = text[:160].replace("\\r", " ").replace("\\n", " ") + "\\n"
245
+ if len(line) > self.budget:
246
+ line = line[: self.budget]
247
+ self.budget -= len(line)
248
+ _sys.stderr.write(line)
249
+ _sys.stderr.flush()
250
+ except Exception:
251
+ self.budget = 0
252
+ _probe_log = _logging.getLogger("mcp")
253
+ _probe_log.handlers[:] = [_BoundedProbeLog({_MCP_CHILD_LOG_BUDGET})]
254
+ _probe_log.setLevel(_logging.DEBUG)
255
+ _probe_log.propagate = False
256
+ """
257
+ _MCP_BOOTSTRAP = """\
258
+ import json
259
+ import os
260
+ import pathlib
261
+ import runpy
262
+ import sys
263
+ from importlib import metadata
264
+ from importlib.machinery import PathFinder
265
+
266
+ sys.excepthook = lambda *_: os._exit(70)
267
+ def overlaps_selected(path):
268
+ try:
269
+ path.relative_to(selected)
270
+ return True
271
+ except ValueError:
272
+ pass
273
+ try:
274
+ selected.relative_to(path)
275
+ return True
276
+ except (ValueError, OSError):
277
+ return False
278
+ def validate_binding(raw, require_directory, reject_selected=True):
279
+ if not isinstance(raw, dict) or set(raw) != {"canonical", "identity", "lexical"}:
280
+ raise SystemExit(70)
281
+ if not isinstance(raw["lexical"], str) or not isinstance(raw["canonical"], str):
282
+ raise SystemExit(70)
283
+ lexical = pathlib.Path(raw["lexical"])
284
+ canonical = pathlib.Path(raw["canonical"])
285
+ identity = raw["identity"]
286
+ if (
287
+ not lexical.is_absolute()
288
+ or not canonical.is_absolute()
289
+ or not isinstance(identity, list)
290
+ or len(identity) != 4
291
+ or any(not isinstance(item, int) or isinstance(item, bool) for item in identity)
292
+ ):
293
+ raise SystemExit(70)
294
+ resolved = lexical.resolve(strict=True)
295
+ if resolved != canonical or canonical.resolve(strict=True) != canonical:
296
+ raise SystemExit(70)
297
+ stat = canonical.stat()
298
+ if [stat.st_dev, stat.st_ino, stat.st_size, stat.st_mtime_ns] != identity:
299
+ raise SystemExit(70)
300
+ if require_directory != canonical.is_dir() or (reject_selected and overlaps_selected(canonical)):
301
+ raise SystemExit(70)
302
+ return lexical, canonical
303
+ selected_raw = json.loads(sys.argv[4])
304
+ if not isinstance(selected_raw, dict):
305
+ raise SystemExit(70)
306
+ selected = pathlib.Path(selected_raw.get("canonical", "."))
307
+ _, selected = validate_binding(selected_raw, True, False)
308
+ trusted_lexical, trusted = validate_binding(json.loads(sys.argv[1]), True, False)
309
+ init_lexical, expected_graphite_init = validate_binding(json.loads(sys.argv[2]), False, False)
310
+ mcp_lexical, expected_graphite_mcp = validate_binding(json.loads(sys.argv[3]), False, False)
311
+ if (
312
+ init_lexical != trusted_lexical / "graphite" / "__init__.py"
313
+ or mcp_lexical != trusted_lexical / "graphite" / "mcp.py"
314
+ or expected_graphite_init.parent != trusted / "graphite"
315
+ or expected_graphite_mcp.parent != trusted / "graphite"
316
+ ):
317
+ raise SystemExit(70)
318
+ stdin_raw = sys.stdin.buffer.raw
319
+ def read_exact(count):
320
+ parts = []
321
+ while count > 0:
322
+ part = stdin_raw.read(count)
323
+ if not part:
324
+ raise SystemExit(70)
325
+ parts.append(part)
326
+ count -= len(part)
327
+ return b"".join(parts)
328
+ # Read the length header a byte at a time and the manifest by exact size, both
329
+ # straight off the raw stream. A buffered read would pull the protocol input in
330
+ # behind the manifest, where only this object could reach it -- the MCP server
331
+ # opens its own reader on fd 0 and would find EOF.
332
+ header = b""
333
+ while not header.endswith(b"\\n"):
334
+ byte = stdin_raw.read(1)
335
+ if not byte:
336
+ raise SystemExit(70)
337
+ header += byte
338
+ if len(header) > 24:
339
+ raise SystemExit(70)
340
+ declared_length = header[:-1]
341
+ if not declared_length.isdigit():
342
+ raise SystemExit(70)
343
+ manifest = json.loads(read_exact(int(declared_length)))
344
+ if not isinstance(manifest, dict) or set(manifest) != {"distributions", "files", "packages"}:
345
+ raise SystemExit(70)
346
+ raw_files = manifest["files"]
347
+ raw_packages = manifest["packages"]
348
+ raw_distributions = manifest["distributions"]
349
+ if not isinstance(raw_files, list) or not isinstance(raw_packages, dict) or not isinstance(raw_distributions, dict):
350
+ raise SystemExit(70)
351
+ allowed_files = set()
352
+ for raw_group in raw_files:
353
+ if not isinstance(raw_group, dict) or set(raw_group) != {"entries", "root"}:
354
+ raise SystemExit(70)
355
+ root_lexical, root = validate_binding(raw_group["root"], True)
356
+ entries = raw_group["entries"]
357
+ if not isinstance(entries, list):
358
+ raise SystemExit(70)
359
+ for entry in entries:
360
+ if (
361
+ not isinstance(entry, list)
362
+ or len(entry) != 5
363
+ or not isinstance(entry[0], str)
364
+ or any(not isinstance(item, int) or isinstance(item, bool) for item in entry[1:])
365
+ ):
366
+ raise SystemExit(70)
367
+ relative = pathlib.PurePath(entry[0])
368
+ if relative.is_absolute() or not relative.parts or any(part in ("", ".", "..") for part in relative.parts):
369
+ raise SystemExit(70)
370
+ lexical = root_lexical.joinpath(*relative.parts)
371
+ canonical = root.joinpath(*relative.parts)
372
+ if lexical.resolve(strict=True) != canonical or canonical.resolve(strict=True) != canonical:
373
+ raise SystemExit(70)
374
+ stat = canonical.stat()
375
+ if not canonical.is_file() or [stat.st_dev, stat.st_ino, stat.st_size, stat.st_mtime_ns] != entry[1:]:
376
+ raise SystemExit(70)
377
+ allowed_files.add(canonical)
378
+ distributions = {}
379
+ for name, raw in raw_distributions.items():
380
+ if not isinstance(name, str):
381
+ raise SystemExit(70)
382
+ _, path = validate_binding(raw, True)
383
+ if not path.name.endswith(".dist-info"):
384
+ raise SystemExit(70)
385
+ distribution = metadata.PathDistribution(path)
386
+ declared = distribution.metadata.get("Name")
387
+ normalize = lambda value: "-".join(filter(None, __import__("re").split(r"[-_.]+", value.lower())))
388
+ if not isinstance(declared, str) or normalize(declared) != name:
389
+ raise SystemExit(70)
390
+ distributions[name] = distribution
391
+ packages = {}
392
+ for name, raw_entry in raw_packages.items():
393
+ if not isinstance(name, str) or not name.isidentifier():
394
+ raise SystemExit(70)
395
+ if not isinstance(raw_entry, dict) or set(raw_entry) != {"origin", "root", "search"}:
396
+ raise SystemExit(70)
397
+ entry = {}
398
+ _, entry["search"] = validate_binding(raw_entry["search"], True)
399
+ raw_origin = raw_entry["origin"]
400
+ if raw_origin is not None:
401
+ _, entry["origin"] = validate_binding(raw_origin, False)
402
+ else:
403
+ entry["origin"] = None
404
+ raw_root = raw_entry["root"]
405
+ if raw_root is not None:
406
+ _, entry["root"] = validate_binding(raw_root, True)
407
+ else:
408
+ entry["root"] = None
409
+ if (
410
+ (entry["origin"] is not None and entry["origin"] not in allowed_files)
411
+ or not entry["search"].is_dir()
412
+ or overlaps_selected(entry["search"])
413
+ or (entry["root"] is not None and overlaps_selected(entry["root"]))
414
+ ):
415
+ raise SystemExit(70)
416
+ packages[name] = entry
417
+
418
+ class GuardedDistributionFinder:
419
+ @staticmethod
420
+ def find_distributions(context=metadata.DistributionFinder.Context()):
421
+ requested = context.name
422
+ if requested is None:
423
+ return iter(distributions.values())
424
+ normalized = "-".join(filter(None, __import__("re").split(r"[-_.]+", requested.lower())))
425
+ distribution = distributions.get(normalized)
426
+ return iter(()) if distribution is None else iter((distribution,))
427
+
428
+ @staticmethod
429
+ def find_spec(fullname, path=None, target=None):
430
+ del target
431
+ top_level = fullname.partition(".")[0]
432
+ entry = packages.get(top_level)
433
+ if entry is None:
434
+ return None
435
+ search = [str(entry["search"])] if fullname == top_level else path
436
+ if search is None:
437
+ raise ModuleNotFoundError(fullname)
438
+ spec = PathFinder.find_spec(fullname, search)
439
+ if spec is None or spec.origin in ("built-in", "frozen"):
440
+ raise ModuleNotFoundError(fullname)
441
+ locations = spec.submodule_search_locations
442
+ origin = None if spec.origin is None else pathlib.Path(spec.origin).resolve(strict=True)
443
+ if origin is not None and origin not in allowed_files:
444
+ raise ModuleNotFoundError(fullname)
445
+ if fullname == top_level and origin != entry["origin"]:
446
+ raise ModuleNotFoundError(fullname)
447
+ if locations is not None:
448
+ resolved = [pathlib.Path(item).resolve(strict=True) for item in locations]
449
+ if len(resolved) != 1:
450
+ raise ModuleNotFoundError(fullname)
451
+ package_root = entry["root"]
452
+ if package_root is None:
453
+ raise ModuleNotFoundError(fullname)
454
+ try:
455
+ resolved[0].relative_to(package_root)
456
+ except ValueError:
457
+ raise ModuleNotFoundError(fullname) from None
458
+ elif origin is None:
459
+ raise ModuleNotFoundError(fullname)
460
+ return spec
461
+
462
+ stdlib_path = list(sys.path)
463
+ sys.path[:] = [str(trusted), *stdlib_path]
464
+ sys.meta_path.insert(0, GuardedDistributionFinder)
465
+ graphite_spec = PathFinder.find_spec("graphite", [str(trusted)])
466
+ if (
467
+ graphite_spec is None
468
+ or graphite_spec.origin is None
469
+ or pathlib.Path(graphite_spec.origin).resolve(strict=True) != expected_graphite_init
470
+ or graphite_spec.submodule_search_locations is None
471
+ or [pathlib.Path(item).resolve(strict=True) for item in graphite_spec.submodule_search_locations]
472
+ != [expected_graphite_init.parent]
473
+ ):
474
+ raise SystemExit(70)
475
+ mcp_spec = PathFinder.find_spec("graphite.mcp", list(graphite_spec.submodule_search_locations))
476
+ if mcp_spec is None or mcp_spec.origin is None or pathlib.Path(mcp_spec.origin).resolve(strict=True) != expected_graphite_mcp:
477
+ raise SystemExit(70)
478
+ """ + _MCP_CHILD_LOG_SETUP + """\
479
+ runpy.run_module("graphite.mcp", run_name="__main__")
480
+ """
481
+ # Machine-independent on purpose. This shipped naming one developer's
482
+ # `.codex_state` scratch directory, so on every other machine it pointed at a
483
+ # file that did not exist -- and it carried that user's name into the wheel.
484
+ # `require.resolve` asks node the same question the probe asks, needs nothing
485
+ # installed, and works wherever the reader happens to be.
486
+ _TYPESCRIPT_REMEDIATION = (
487
+ "Confirm the package resolves from the target project: "
488
+ "node -e \"require.resolve('typescript')\"",
489
+ "Then add typescript with the target project's existing package manager.",
490
+ )
491
+
492
+
493
+ def _blocked(error_type: str, code: str) -> DoctorCheck:
494
+ return DoctorCheck(
495
+ "deep_core",
496
+ "Deterministic pipeline",
497
+ "blocked",
498
+ "The isolated deterministic pipeline probe failed safely.",
499
+ {"error_type": error_type, "code": code},
500
+ ("Run the Graphite build, validate, and query commands locally and inspect their diagnostics.",),
501
+ )
502
+
503
+
504
+ def _process_error_type(code: str) -> str:
505
+ if code in {"timeout", "output_limit"}:
506
+ return code
507
+ return "process"
508
+
509
+
510
+ def _json_object(payload: bytes) -> dict[str, Any]:
511
+ try:
512
+ value = json.loads(payload.decode("utf-8"))
513
+ except (UnicodeDecodeError, json.JSONDecodeError):
514
+ raise ProbeProcessError("unexpected") from None
515
+ if not isinstance(value, dict):
516
+ raise ProbeProcessError("unexpected")
517
+ return value
518
+
519
+
520
+ def _count(value: object, *, positive: bool = False) -> int | None:
521
+ if not isinstance(value, int) or isinstance(value, bool):
522
+ return None
523
+ if value < (1 if positive else 0):
524
+ return None
525
+ return value
526
+
527
+
528
+ def _check_deadline(limit: float, clock: Callable[[], float]) -> None:
529
+ if clock() >= limit:
530
+ raise ProbeProcessError("timeout")
531
+
532
+
533
+ def _cleanup_workspace_lease(
534
+ lease: Any,
535
+ deadline: float,
536
+ clock: Callable[[], float],
537
+ ) -> str | None:
538
+ """Let a bounded cleanup thread retain sole ownership of the live lease."""
539
+ failure: list[str] = []
540
+
541
+ def perform() -> None:
542
+ try:
543
+ lease.cleanup()
544
+ except WorkspaceLeaseError as exc:
545
+ failure.append("cleanup_blocked" if exc.code == "workspace_cleanup_blocked" else "cleanup_failed")
546
+ except Exception:
547
+ failure.append("cleanup_failed")
548
+ finally:
549
+ _release_core_probe_slot()
550
+
551
+ thread = threading.Thread(target=perform, daemon=True)
552
+ try:
553
+ thread.start()
554
+ except RuntimeError:
555
+ _release_core_probe_slot()
556
+ return "cleanup_failed"
557
+ thread.join(max(0.0, deadline - clock()))
558
+ if thread.is_alive():
559
+ return "cleanup_timeout"
560
+ return failure[0] if failure else None
561
+
562
+
563
+ def _claim_core_probe_slot() -> bool:
564
+ global _CORE_PROBE_SLOT_ACTIVE
565
+ with _CORE_PROBE_SLOT_LOCK:
566
+ if _CORE_PROBE_SLOT_ACTIVE:
567
+ return False
568
+ _CORE_PROBE_SLOT_ACTIVE = True
569
+ return True
570
+
571
+
572
+ def _release_core_probe_slot() -> None:
573
+ global _CORE_PROBE_SLOT_ACTIVE
574
+ with _CORE_PROBE_SLOT_LOCK:
575
+ _CORE_PROBE_SLOT_ACTIVE = False
576
+
577
+
578
+ def _check_workspace(lease: Any, limit: float, clock: Callable[[], float]) -> None:
579
+ _check_deadline(limit, clock)
580
+ lease.validate()
581
+ _check_deadline(limit, clock)
582
+
583
+
584
+ def probe_core_pipeline(
585
+ selected_root: Path,
586
+ python_executable: str = sys.executable,
587
+ timeout_seconds: float = 30,
588
+ *,
589
+ _runner: Callable[..., ProbeProcessResult] = run_bounded_process,
590
+ _workspace_factory: Callable[[], Any] = ProbeWorkspaceLease.acquire,
591
+ _clock: Callable[[], float] = time.monotonic,
592
+ ) -> DoctorCheck:
593
+ """Exercise build/validate/query under one checked end-to-end deadline.
594
+
595
+ Subprocess transport is hard-cancelled. Filesystem calls cannot be safely
596
+ preempted, so their deadline is checked immediately before and after each
597
+ synchronous phase. Verified cleanup runs in a bounded daemon thread.
598
+ """
599
+ started = _clock()
600
+ if not math.isfinite(timeout_seconds) or timeout_seconds <= 0:
601
+ return _blocked("timeout", "invalid_timeout")
602
+ if not _claim_core_probe_slot():
603
+ return _blocked("cleanup", "cleanup_busy")
604
+ deadline = started + timeout_seconds
605
+ cleanup_reserve = min(_CLEANUP_RESERVE_SECONDS, max(0.05, timeout_seconds * 0.2))
606
+ phase_deadline = deadline - cleanup_reserve
607
+ lease: Any | None = None
608
+ candidate: DoctorCheck | None = None
609
+
610
+ try:
611
+ _check_deadline(phase_deadline, _clock)
612
+ selected = selected_root.resolve(strict=True)
613
+ if not selected.is_dir():
614
+ return _blocked("invariant", "invalid_selected_root")
615
+ _check_deadline(phase_deadline, _clock)
616
+
617
+ temp_root = Path(tempfile.gettempdir()).resolve(strict=True)
618
+ _check_deadline(phase_deadline, _clock)
619
+ try:
620
+ temp_root.relative_to(selected)
621
+ except ValueError:
622
+ pass
623
+ else:
624
+ return _blocked("isolation", "selected_contains_temp")
625
+
626
+ _check_deadline(phase_deadline, _clock)
627
+ lease = _workspace_factory()
628
+ creation_exceeded_deadline = _clock() >= phase_deadline
629
+ _check_workspace(lease, phase_deadline, _clock)
630
+ work = Path(lease.path).resolve(strict=True)
631
+ try:
632
+ work.relative_to(temp_root)
633
+ except ValueError:
634
+ # Injected leases may use another explicitly verified canonical temp
635
+ # root; the lease remains the authority for its containment.
636
+ lease_root = Path(getattr(lease, "temp_root", temp_root)).resolve(strict=True)
637
+ try:
638
+ work.relative_to(lease_root)
639
+ except ValueError:
640
+ return _blocked("isolation", "unsafe_temp_path")
641
+ try:
642
+ work.relative_to(selected)
643
+ except ValueError:
644
+ pass
645
+ else:
646
+ return _blocked("isolation", "overlapping_temp_path")
647
+ try:
648
+ selected.relative_to(work)
649
+ except ValueError:
650
+ pass
651
+ else:
652
+ return _blocked("isolation", "overlapping_temp_path")
653
+ if creation_exceeded_deadline:
654
+ raise ProbeProcessError("timeout")
655
+ _check_workspace(lease, phase_deadline, _clock)
656
+
657
+ repo = work / "repo"
658
+ source = repo / "src"
659
+ _check_workspace(lease, phase_deadline, _clock)
660
+ source.mkdir(parents=True)
661
+ _check_workspace(lease, phase_deadline, _clock)
662
+ (source / "lib.py").write_text("def answer():\n return 42\n", encoding="utf-8")
663
+ _check_workspace(lease, phase_deadline, _clock)
664
+ (repo / "app.py").write_text("from src.lib import answer\nVALUE = answer()\n", encoding="utf-8")
665
+ _check_workspace(lease, phase_deadline, _clock)
666
+
667
+ out = work / "out"
668
+ cache = work / "cache"
669
+ graph = out / "graph.json"
670
+ commands = [
671
+ [python_executable, "-B", "-P", "-m", "graphite", "--output-dir", str(out), "--cache-dir", str(cache), "--llm", "none", "build", str(repo)],
672
+ [python_executable, "-B", "-P", "-m", "graphite", "validate", "--graph-json", str(graph), "--json"],
673
+ [python_executable, "-B", "-P", "-m", "graphite", "query", "stats", "--graph-json", str(graph)],
674
+ ]
675
+ outputs: list[ProbeProcessResult] = []
676
+ validation_nodes: int | None = None
677
+ validation_edges: int | None = None
678
+ for index, command in enumerate(commands):
679
+ _check_workspace(lease, phase_deadline, _clock)
680
+ outputs.append(_runner(command, cwd=work, timeout_seconds=phase_deadline - _clock()))
681
+ _check_workspace(lease, phase_deadline, _clock)
682
+ if index == 1:
683
+ _check_workspace(lease, phase_deadline, _clock)
684
+ validation = _json_object(outputs[1].stdout)
685
+ _check_deadline(phase_deadline, _clock)
686
+ validation_nodes = _count(validation.get("node_count"), positive=True)
687
+ validation_edges = _count(validation.get("edge_count"))
688
+ if (
689
+ validation.get("ok") is not True
690
+ or validation.get("valid", True) is not True
691
+ or validation.get("error_count") != 0
692
+ or validation.get("errors") != []
693
+ or validation_nodes is None
694
+ or validation_edges is None
695
+ ):
696
+ candidate = _blocked("validation", "validation_failed")
697
+ break
698
+
699
+ if candidate is None:
700
+ _check_workspace(lease, phase_deadline, _clock)
701
+ stats = _json_object(outputs[2].stdout)
702
+ _check_deadline(phase_deadline, _clock)
703
+ stats_nodes = _count(stats.get("node_count"), positive=True)
704
+ stats_edges = _count(stats.get("edge_count"))
705
+ if stats_nodes is None or stats_edges is None:
706
+ candidate = _blocked("response", "invalid_counts")
707
+ elif stats_nodes != validation_nodes or stats_edges != validation_edges:
708
+ candidate = _blocked("response", "count_mismatch")
709
+ else:
710
+ _check_deadline(phase_deadline, _clock)
711
+ candidate = DoctorCheck(
712
+ "deep_core",
713
+ "Deterministic pipeline",
714
+ "ready",
715
+ "The isolated deterministic build, validation, and query pipeline is ready.",
716
+ {
717
+ "node_count": stats_nodes,
718
+ "edge_count": stats_edges,
719
+ "duration_ms": max(0, round((_clock() - started) * 1000)),
720
+ "commands_completed": 3,
721
+ },
722
+ )
723
+ except ProbeProcessError as exc:
724
+ if exc.code == "unexpected":
725
+ candidate = _blocked("response", "malformed_output")
726
+ else:
727
+ candidate = _blocked(_process_error_type(exc.code), exc.code)
728
+ except WorkspaceLeaseError:
729
+ candidate = _blocked("isolation", "workspace_isolation_changed")
730
+ except (OSError, RuntimeError):
731
+ candidate = _blocked("isolation", "temporary_directory_failed")
732
+ except Exception:
733
+ candidate = _blocked("unexpected", "probe_failed")
734
+ finally:
735
+ if lease is not None:
736
+ cleanup_error = _cleanup_workspace_lease(lease, deadline, _clock)
737
+ if cleanup_error is not None:
738
+ candidate = _blocked("cleanup", cleanup_error)
739
+ else:
740
+ _release_core_probe_slot()
741
+
742
+ if candidate is None:
743
+ return _blocked("unexpected", "probe_failed")
744
+ if _clock() >= deadline and candidate.details.get("error_type") != "cleanup":
745
+ return _blocked("timeout", "timeout")
746
+ return candidate
747
+
748
+
749
+ # Bounded well under the ledger's own 2048-char cap. The probe must not lean on
750
+ # that cap: an unbounded string still gets built, hashed and passed around
751
+ # before the ledger ever sees it, and a server can emit megabytes.
752
+ _PROBE_DIAGNOSTIC_STREAM_CHARS = 600
753
+ # stderr gets its own, larger allowance because it carries the child's log
754
+ # rather than a couple of JSON envelopes. Both together, plus the scalar fields
755
+ # and the repr quoting, stay under MAX_DETAIL_CHARS -- pinned by
756
+ # test_probe_diagnostics_detail_stays_within_the_ledger_cap, because raising
757
+ # either cap in isolation would push the joined detail past the ledger's own
758
+ # truncation and silently cost the tail this exists to keep.
759
+ _PROBE_DIAGNOSTIC_STDERR_CHARS = 700
760
+
761
+
762
+ def _stream_excerpt(raw: object, *, limit: int = _PROBE_DIAGNOSTIC_STREAM_CHARS, tail: bool = False) -> str:
763
+ """Excerpt a captured stream, from the head by default or from the tail.
764
+
765
+ Which end to keep is not cosmetic. stdout holds the response transcript and
766
+ the responses under test are the first ones, so the head is what matters.
767
+ stderr holds the child's own log, where the useful entries are the LAST it
768
+ managed to emit before it stopped -- head-truncating it reports the startup
769
+ chatter and cuts off exactly at the failure.
770
+ """
771
+ if isinstance(raw, bytes):
772
+ text = raw.decode("utf-8", "replace")
773
+ else:
774
+ text = str(raw or "")
775
+ if len(text) <= limit:
776
+ return text
777
+ dropped = len(text) - limit
778
+ if tail:
779
+ return f"[{dropped} more chars]...{text[-limit:]}"
780
+ return f"{text[:limit]}...[{dropped} more chars]"
781
+
782
+
783
+ def _record_probe_diagnostics(root: Path, failure: str, result: object = None) -> None:
784
+ """Record what a failing probe knew, so the next occurrence explains itself.
785
+
786
+ `DoctorCheck.details` deliberately stays `{"code": ...}`: callers assert
787
+ exact equality on it, and a doctor report may be shared, whereas subprocess
788
+ stderr should not travel. The repo incident ledger is local and bounded, so
789
+ that is where the evidence goes.
790
+
791
+ Swallows everything. Diagnostics are a convenience; a probe that failed
792
+ because its own logging failed would be worse than the opacity this fixes.
793
+ """
794
+ try:
795
+ detail = " | ".join(
796
+ (
797
+ f"returncode={getattr(result, 'returncode', '<none>')}",
798
+ # Transport-side evidence, and the only evidence there IS when
799
+ # the transport raised rather than returned (graphite#51).
800
+ # `run_bounded_process` never produces a result on that path, so
801
+ # every field below used to read `<none>` on precisely the
802
+ # failure this diagnostic exists to explain.
803
+ #
804
+ # Read `elapsed_s` against `budget_s`:
805
+ #
806
+ # elapsed <= budget -> the deadline fired on time; the child
807
+ # did not answer inside it. NOTE a normal
808
+ # timeout lands BELOW budget, because the
809
+ # runner reserves up to 40% of it for
810
+ # cleanup and enforces the earlier
811
+ # execution deadline.
812
+ # elapsed > budget -> our own deadline was late, i.e. THIS
813
+ # process was starved of CPU. That is the
814
+ # load hypothesis, and nothing in the log
815
+ # could express it before.
816
+ #
817
+ # `stdout_bytes` splits the first case: 0 means the child never
818
+ # produced a byte, non-zero means it was alive and progressing.
819
+ f"elapsed_s={getattr(result, 'elapsed_seconds', '<none>')}",
820
+ f"budget_s={getattr(result, 'budget_seconds', '<none>')}",
821
+ f"stdout_bytes={getattr(result, 'stdout_bytes', '<none>')}",
822
+ f"stderr_bytes={getattr(result, 'stderr_bytes', '<none>')}",
823
+ # Input-side evidence, and the point of recording it: a short
824
+ # response transcript is ambiguous on its own. `input_complete`
825
+ # False means the child stopped reading and therefore saw an
826
+ # early EOF -- it never got the whole request stream. True means
827
+ # it received everything and still answered less, which is a
828
+ # different bug in a different place. Without this the two are
829
+ # indistinguishable from the outside (graphite issue #29).
830
+ f"input_bytes={getattr(result, 'input_bytes', '<none>')}",
831
+ f"input_complete={getattr(result, 'input_complete', '<none>')}",
832
+ # Seconds the child outlived the close of its stdin. -1 means
833
+ # not measured, not instant.
834
+ #
835
+ # Read it as a liveness fact and nothing more. It was added to
836
+ # test whether the child died *of* the EOF, and it CANNOT answer
837
+ # that: the probe closes stdin the instant the payload is
838
+ # written, so the interval is dominated by binding validation
839
+ # and imports that finish before the server reads a byte, and a
840
+ # teardown race in the final milliseconds is invisible inside
841
+ # it. Measured -- a *passing* probe reports 5.14s against
842
+ # 2.08/2.34/4.69s on the #29 failures, so large-vs-small here
843
+ # discriminates nothing. The child's own log below is what
844
+ # separates those cases.
845
+ f"outlived_close_s={getattr(result, 'stdin_close_to_exit_seconds', '<none>')}",
846
+ f"stdout={_stream_excerpt(getattr(result, 'stdout', b''))!r}",
847
+ # Tail, not head: this is the child's own MCP log, and the
848
+ # entries that discriminate a dropped response from a lost one
849
+ # are the last it emitted.
850
+ f"stderr="
851
+ f"{_stream_excerpt(getattr(result, 'stderr', b''), limit=_PROBE_DIAGNOSTIC_STDERR_CHARS, tail=True)!r}",
852
+ )
853
+ )
854
+ record_incident(
855
+ repo_ledger_dir(root),
856
+ klass="doctor",
857
+ code=f"deep_mcp_{failure}",
858
+ subject="deep_mcp",
859
+ detail=detail,
860
+ )
861
+ # Also to stderr, and this is load-bearing rather than belt-and-braces.
862
+ # The ledger is per-repo on local disk; on a CI runner that disk is
863
+ # thrown away, so a ledger-only diagnostic is invisible in exactly the
864
+ # environment where this failure actually occurs. pytest shows captured
865
+ # stderr for a failing test, so this reaches the CI log.
866
+ print(f"[graphite-probe] deep_mcp {failure}: {detail}", file=sys.stderr)
867
+ except Exception:
868
+ return
869
+
870
+
871
+ def _degraded_probe(code: str, label: str, failure: str) -> DoctorCheck:
872
+ return DoctorCheck(
873
+ code,
874
+ label,
875
+ "degraded",
876
+ f"The {label} deep probe {failure}.",
877
+ {"code": failure},
878
+ )
879
+
880
+
881
+ def _paths_overlap(left: Path, right: Path) -> bool:
882
+ try:
883
+ left.relative_to(right)
884
+ return True
885
+ except ValueError:
886
+ pass
887
+ try:
888
+ right.relative_to(left)
889
+ return True
890
+ except ValueError:
891
+ return False
892
+
893
+
894
+ def _path_is_within(path: Path, root: Path) -> bool:
895
+ try:
896
+ path.relative_to(root)
897
+ return True
898
+ except ValueError:
899
+ return False
900
+
901
+
902
+ def _path_binding(path: Path, *, require_directory: bool) -> dict[str, object]:
903
+ # Best-effort race detection only: same-user replacement after the final
904
+ # check remains an operating-system trust boundary, not a complete TOCTOU fix.
905
+ lexical = Path(os.path.abspath(path))
906
+ canonical = lexical.resolve(strict=True)
907
+ if canonical.is_dir() != require_directory:
908
+ raise ValueError
909
+ stat = canonical.stat()
910
+ return {
911
+ "lexical": str(lexical),
912
+ "canonical": str(canonical),
913
+ "identity": [stat.st_dev, stat.st_ino, stat.st_size, stat.st_mtime_ns],
914
+ }
915
+
916
+
917
+ def _validate_path_binding(
918
+ binding: object,
919
+ *,
920
+ require_directory: bool,
921
+ ) -> tuple[Path, Path]:
922
+ if not isinstance(binding, dict) or set(binding) != {"canonical", "identity", "lexical"}:
923
+ raise ValueError
924
+ raw_lexical = binding["lexical"]
925
+ raw_canonical = binding["canonical"]
926
+ identity = binding["identity"]
927
+ if (
928
+ not isinstance(raw_lexical, str)
929
+ or not isinstance(raw_canonical, str)
930
+ or not isinstance(identity, list)
931
+ or len(identity) != 4
932
+ or any(not isinstance(item, int) or isinstance(item, bool) for item in identity)
933
+ ):
934
+ raise ValueError
935
+ lexical = Path(raw_lexical)
936
+ canonical = Path(raw_canonical)
937
+ if not lexical.is_absolute() or not canonical.is_absolute():
938
+ raise ValueError
939
+ if lexical.resolve(strict=True) != canonical or canonical.resolve(strict=True) != canonical:
940
+ raise ValueError
941
+ stat = canonical.stat()
942
+ if [stat.st_dev, stat.st_ino, stat.st_size, stat.st_mtime_ns] != identity:
943
+ raise ValueError
944
+ if canonical.is_dir() != require_directory:
945
+ raise ValueError
946
+ return lexical, canonical
947
+
948
+
949
+ def _compact_manifest_files(
950
+ distribution_paths: dict[str, dict[str, object]],
951
+ all_files: dict[Path, dict[str, object]],
952
+ ) -> list[dict[str, object]]:
953
+ roots: dict[tuple[str, str], tuple[dict[str, object], Path, Path]] = {}
954
+ for binding in distribution_paths.values():
955
+ lexical, canonical = _validate_path_binding(binding, require_directory=True)
956
+ root_binding = _path_binding(lexical.parent, require_directory=True)
957
+ root_lexical, root_canonical = _validate_path_binding(
958
+ root_binding,
959
+ require_directory=True,
960
+ )
961
+ if canonical.parent != root_canonical:
962
+ raise ValueError
963
+ key = (os.path.normcase(str(root_lexical)), os.path.normcase(str(root_canonical)))
964
+ roots[key] = (root_binding, root_lexical, root_canonical)
965
+
966
+ grouped: dict[tuple[str, str], list[list[object]]] = {key: [] for key in roots}
967
+ file_bindings = list(all_files.values())
968
+ with ThreadPoolExecutor(max_workers=min(32, max(1, len(file_bindings)))) as executor:
969
+ validated_files = executor.map(
970
+ lambda binding: _validate_path_binding(binding, require_directory=False),
971
+ file_bindings,
972
+ )
973
+ bound_files = list(zip(file_bindings, validated_files, strict=True))
974
+ for binding, (lexical, canonical) in bound_files:
975
+ matches: list[tuple[tuple[str, str], Path]] = []
976
+ for key, (_, root_lexical, root_canonical) in roots.items():
977
+ try:
978
+ lexical_relative = lexical.relative_to(root_lexical)
979
+ canonical_relative = canonical.relative_to(root_canonical)
980
+ except ValueError:
981
+ continue
982
+ if lexical_relative == canonical_relative:
983
+ matches.append((key, lexical_relative))
984
+ if len(matches) != 1:
985
+ raise ValueError
986
+ key, relative = matches[0]
987
+ if relative.is_absolute() or not relative.parts or any(part in {"", ".", ".."} for part in relative.parts):
988
+ raise ValueError
989
+ identity = binding["identity"]
990
+ if not isinstance(identity, list) or len(identity) != 4:
991
+ raise ValueError
992
+ grouped[key].append([relative.as_posix(), *identity])
993
+
994
+ compact: list[dict[str, object]] = []
995
+ for key in sorted(grouped):
996
+ entries = sorted(grouped[key], key=lambda entry: os.path.normcase(str(entry[0])))
997
+ if entries:
998
+ compact.append({"root": roots[key][0], "entries": entries})
999
+ return compact
1000
+
1001
+
1002
+ def _validate_manifest_file_groups(
1003
+ raw_groups: object,
1004
+ selected: Path,
1005
+ ) -> set[Path]:
1006
+ if not isinstance(raw_groups, list):
1007
+ raise ValueError
1008
+ allowed_files: set[Path] = set()
1009
+ for raw_group in raw_groups:
1010
+ if not isinstance(raw_group, dict) or set(raw_group) != {"entries", "root"}:
1011
+ raise ValueError
1012
+ root_lexical, root = _validate_path_binding(
1013
+ raw_group["root"],
1014
+ require_directory=True,
1015
+ )
1016
+ if _paths_overlap(root, selected):
1017
+ raise ValueError
1018
+ entries = raw_group["entries"]
1019
+ if not isinstance(entries, list):
1020
+ raise ValueError
1021
+ for entry in entries:
1022
+ if (
1023
+ not isinstance(entry, list)
1024
+ or len(entry) != 5
1025
+ or not isinstance(entry[0], str)
1026
+ or any(not isinstance(item, int) or isinstance(item, bool) for item in entry[1:])
1027
+ ):
1028
+ raise ValueError
1029
+ relative = Path(entry[0])
1030
+ if (
1031
+ relative.is_absolute()
1032
+ or not relative.parts
1033
+ or any(part in {"", ".", ".."} for part in relative.parts)
1034
+ ):
1035
+ raise ValueError
1036
+ lexical = root_lexical.joinpath(*relative.parts)
1037
+ canonical = root.joinpath(*relative.parts)
1038
+ if lexical.resolve(strict=True) != canonical or canonical.resolve(strict=True) != canonical:
1039
+ raise ValueError
1040
+ stat = canonical.stat()
1041
+ if (
1042
+ not canonical.is_file()
1043
+ or [stat.st_dev, stat.st_ino, stat.st_size, stat.st_mtime_ns] != entry[1:]
1044
+ or _path_is_within(canonical, selected)
1045
+ ):
1046
+ raise ValueError
1047
+ allowed_files.add(canonical)
1048
+ return allowed_files
1049
+
1050
+
1051
+ def _normalized_distribution_name(value: str) -> str:
1052
+ return re.sub(r"[-_.]+", "-", value).lower()
1053
+
1054
+
1055
+ def _marker_value(name: str) -> str:
1056
+ values = {
1057
+ "platform_python_implementation": platform.python_implementation(),
1058
+ "platform_system": platform.system(),
1059
+ "python_version": f"{sys.version_info.major}.{sys.version_info.minor}",
1060
+ "python_full_version": platform.python_version(),
1061
+ "implementation_name": sys.implementation.name,
1062
+ "sys_platform": sys.platform,
1063
+ }
1064
+ if name not in values:
1065
+ raise ValueError
1066
+ return values[name]
1067
+
1068
+
1069
+ def _version_tuple(value: str) -> tuple[int, ...]:
1070
+ """Leading numeric release components, e.g. `3.14.0rc1` -> `(3, 14, 0)`.
1071
+
1072
+ Pre-release suffixes are dropped rather than raising: `python_full_version`
1073
+ reports them on release-candidate interpreters, and a ValueError from a
1074
+ non-extra marker aborts the entire distribution walk.
1075
+ """
1076
+ parts: list[int] = []
1077
+ for part in value.split("."):
1078
+ digits = re.match(r"\d+", part)
1079
+ if digits is None:
1080
+ break
1081
+ parts.append(int(digits.group(0)))
1082
+ if digits.end() != len(part):
1083
+ break
1084
+ if not parts:
1085
+ raise ValueError
1086
+ return tuple(parts)
1087
+
1088
+
1089
+ def _marker_comparison_holds(condition: str) -> bool:
1090
+ match = re.fullmatch(
1091
+ r"(python_version|python_full_version|sys_platform|platform_system"
1092
+ r"|platform_python_implementation|implementation_name)"
1093
+ r"\s*(==|!=|<=|>=|<|>)\s*(['\"])([^'\"]+)\3",
1094
+ condition,
1095
+ )
1096
+ if match is None:
1097
+ raise ValueError
1098
+ actual = _marker_value(match.group(1))
1099
+ expected = match.group(4)
1100
+ operator = match.group(2)
1101
+ if match.group(1) in {"python_version", "python_full_version"}:
1102
+ if expected.endswith(".*"):
1103
+ # PEP 440 prefix match; only equality operators are defined for it.
1104
+ if operator not in {"==", "!="}:
1105
+ raise ValueError
1106
+ prefix = _version_tuple(expected[: -len(".*")])
1107
+ matches = _version_tuple(actual)[: len(prefix)] == prefix
1108
+ return matches if operator == "==" else not matches
1109
+ actual_value: object = _version_tuple(actual)
1110
+ expected_value: object = _version_tuple(expected)
1111
+ else:
1112
+ actual_value = actual
1113
+ expected_value = expected
1114
+ comparisons = {
1115
+ "==": actual_value == expected_value,
1116
+ "!=": actual_value != expected_value,
1117
+ "<": actual_value < expected_value,
1118
+ "<=": actual_value <= expected_value,
1119
+ ">": actual_value > expected_value,
1120
+ ">=": actual_value >= expected_value,
1121
+ }
1122
+ return comparisons[operator]
1123
+
1124
+
1125
+ def _requirement_extras(raw_requirement: str) -> frozenset[str]:
1126
+ """Extras a requirement requests of its target: `pyjwt[crypto]` -> {"crypto"}.
1127
+
1128
+ Only the portion before any marker is inspected, so an `extra ==` marker on
1129
+ the requirement itself is never mistaken for a bracket.
1130
+ """
1131
+ head, _, _ = raw_requirement.partition(";")
1132
+ match = re.match(r"[A-Za-z0-9][A-Za-z0-9._-]*\s*\[([^\]]*)\]", head)
1133
+ if match is None:
1134
+ return frozenset()
1135
+ requested = set()
1136
+ for part in match.group(1).split(","):
1137
+ name = part.strip()
1138
+ if not name:
1139
+ continue
1140
+ if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*", name):
1141
+ raise ValueError
1142
+ requested.add(_normalized_distribution_name(name))
1143
+ return frozenset(requested)
1144
+
1145
+
1146
+ def _extra_condition_holds(condition: str, active_extras: frozenset[str]) -> bool | None:
1147
+ """Evaluate an `extra ==`/`!=` condition, or None if this is not one."""
1148
+ match = re.fullmatch(r"extra\s*(==|!=)\s*(['\"])([^'\"]+)\2", condition)
1149
+ if match is None:
1150
+ return None
1151
+ present = _normalized_distribution_name(match.group(3)) in active_extras
1152
+ return present if match.group(1) == "==" else not present
1153
+
1154
+
1155
+ def _condition_holds(condition: str, active_extras: frozenset[str]) -> bool:
1156
+ # A sub-condition can arrive parenthesised, e.g. the `(sys_platform ==
1157
+ # "win32")` half of `(sys_platform == "win32") and extra == "crypto"`.
1158
+ # Unwrap balanced pairs; anything still holding a boolean operator is a
1159
+ # nested expression this parser deliberately does not support.
1160
+ while condition.startswith("(") and condition.endswith(")"):
1161
+ condition = condition[1:-1].strip()
1162
+ if re.search(r"\s+(and|or)\s+", condition):
1163
+ raise ValueError
1164
+ extra_result = _extra_condition_holds(condition, active_extras)
1165
+ if extra_result is not None:
1166
+ return extra_result
1167
+ if re.search(r"\bextra\b", condition):
1168
+ # An `extra` reference we cannot parse. Fail closed rather than guess.
1169
+ raise ValueError
1170
+ return _marker_comparison_holds(condition)
1171
+
1172
+
1173
+ def _requirement_applies(
1174
+ raw_requirement: str, active_extras: frozenset[str] = frozenset()
1175
+ ) -> bool:
1176
+ """Whether a requirement is live given the extras its dependent requested.
1177
+
1178
+ An `extra == "x"` marker holds only when a dependent actually asked for `x`
1179
+ (`pyjwt[crypto]`). With no extras requested this stays exactly as
1180
+ restrictive as the blanket rejection it replaced.
1181
+ """
1182
+ _, separator, raw_marker = raw_requirement.partition(";")
1183
+ if not separator:
1184
+ return True
1185
+ marker = raw_marker.strip()
1186
+ while marker.startswith("(") and marker.endswith(")"):
1187
+ marker = marker[1:-1].strip()
1188
+ # PEP 508 precedence: `and` binds tighter than `or`. Nested parenthesised
1189
+ # sub-expressions stay unsupported and fail closed via ValueError.
1190
+ try:
1191
+ return any(
1192
+ all(
1193
+ _condition_holds(condition.strip(), active_extras)
1194
+ for condition in re.split(r"\s+and\s+", clause)
1195
+ )
1196
+ for clause in re.split(r"\s+or\s+", marker)
1197
+ )
1198
+ except ValueError:
1199
+ if re.search(r"\bextra\b", marker):
1200
+ # These never reached the parser while every `extra` marker was
1201
+ # rejected outright. Keep rejecting the ones we cannot evaluate,
1202
+ # rather than aborting the whole distribution walk.
1203
+ return False
1204
+ raise
1205
+
1206
+
1207
+ def _mcp_distribution_closure(
1208
+ metadata_roots: tuple[Path, ...] | None = None,
1209
+ ) -> dict[str, metadata.Distribution]:
1210
+ discovered: dict[str, list[metadata.Distribution]] | None = None
1211
+ if metadata_roots is not None:
1212
+ discovered = {}
1213
+ for distribution in metadata.Distribution.discover(
1214
+ path=[str(root) for root in metadata_roots]
1215
+ ):
1216
+ declared_name = distribution.metadata.get("Name")
1217
+ if not isinstance(declared_name, str):
1218
+ raise ValueError
1219
+ normalized = _normalized_distribution_name(declared_name)
1220
+ discovered.setdefault(normalized, []).append(distribution)
1221
+
1222
+ # Each entry carries the extras its dependent requested, because a
1223
+ # distribution's requirement set is not fixed -- `pyjwt` alone and
1224
+ # `pyjwt[crypto]` pull in different things.
1225
+ pending: list[tuple[str, frozenset[str]]] = [
1226
+ ("mcp", frozenset()),
1227
+ ("networkx", frozenset()),
1228
+ ]
1229
+ distributions: dict[str, metadata.Distribution] = {}
1230
+ walked_extras: dict[str, frozenset[str]] = {}
1231
+ while pending:
1232
+ requested, requested_extras = pending.pop()
1233
+ normalized = _normalized_distribution_name(requested)
1234
+ already = walked_extras.get(normalized)
1235
+ if already is not None and requested_extras <= already:
1236
+ continue
1237
+ active_extras = requested_extras if already is None else already | requested_extras
1238
+ walked_extras[normalized] = active_extras
1239
+ if normalized in distributions:
1240
+ # Seen before, but with fewer extras: re-walk its requirements
1241
+ # against the wider set without re-resolving the distribution.
1242
+ distribution = distributions[normalized]
1243
+ for requirement in distribution.requires or ():
1244
+ if not _requirement_applies(requirement, active_extras):
1245
+ continue
1246
+ match = re.match(r"[A-Za-z0-9][A-Za-z0-9._-]*", requirement)
1247
+ if match is None:
1248
+ raise ValueError
1249
+ pending.append((match.group(0), _requirement_extras(requirement)))
1250
+ continue
1251
+ if discovered is None:
1252
+ distribution = metadata.distribution(requested)
1253
+ else:
1254
+ candidates = discovered.get(normalized, [])
1255
+ if len(candidates) != 1:
1256
+ raise ValueError
1257
+ distribution = candidates[0]
1258
+ declared_name = distribution.metadata.get("Name")
1259
+ if not isinstance(declared_name, str) or _normalized_distribution_name(declared_name) != normalized:
1260
+ raise ValueError
1261
+ distributions[normalized] = distribution
1262
+ for requirement in distribution.requires or ():
1263
+ if not _requirement_applies(requirement, active_extras):
1264
+ continue
1265
+ match = re.match(r"[A-Za-z0-9][A-Za-z0-9._-]*", requirement)
1266
+ if match is None:
1267
+ raise ValueError
1268
+ pending.append((match.group(0), _requirement_extras(requirement)))
1269
+ return distributions
1270
+
1271
+
1272
+ @lru_cache(maxsize=1)
1273
+ def _mcp_import_inventory(
1274
+ metadata_roots: tuple[Path, ...] | None = None,
1275
+ ) -> tuple[
1276
+ dict[str, dict[str, object]],
1277
+ dict[str, frozenset[Path]],
1278
+ dict[str, dict[str, Path]],
1279
+ dict[str, frozenset[str]],
1280
+ dict[Path, dict[str, object]],
1281
+ ]:
1282
+ """Cache immutable installed-distribution records, never ambient import resolution."""
1283
+ distributions = _mcp_distribution_closure(metadata_roots)
1284
+ distribution_paths: dict[str, dict[str, object]] = {}
1285
+ for name, distribution in distributions.items():
1286
+ raw_path = getattr(distribution, "_path", None)
1287
+ if raw_path is None:
1288
+ raise ValueError
1289
+ binding = _path_binding(Path(raw_path), require_directory=True)
1290
+ path = Path(str(binding["canonical"]))
1291
+ if not path.is_dir() or not path.name.endswith(".dist-info"):
1292
+ raise ValueError
1293
+ distribution_paths[name] = binding
1294
+ suffixes = tuple(machinery.all_suffixes())
1295
+ files_by_distribution: dict[str, set[Path]] = {}
1296
+ top_directories: dict[str, dict[str, Path]] = {}
1297
+ ownership: dict[str, set[str]] = {}
1298
+ all_files: dict[Path, dict[str, object]] = {}
1299
+ for name, distribution in distributions.items():
1300
+ _, metadata_path = _validate_path_binding(
1301
+ distribution_paths[name],
1302
+ require_directory=True,
1303
+ )
1304
+ import_root = metadata_path.parent
1305
+ declared_files: set[Path] = set()
1306
+ declared_directories: dict[str, Path] = {}
1307
+ if distribution.files is None:
1308
+ raise ValueError
1309
+ raw_import_files: list[Path] = []
1310
+ # Every declared file names the same handful of top-level components,
1311
+ # and resolving one is a `_getfinalpathname` syscall -- the single
1312
+ # largest cost in this build. Resolve each distinct component once.
1313
+ examined_tops: set[str] = set()
1314
+ for declared in distribution.files:
1315
+ first = str(declared).replace("\\", "/").partition("/")[0]
1316
+ top_level = first.partition(".")[0] if first.endswith(suffixes) else first
1317
+ if top_level.isidentifier() and first == top_level and first not in examined_tops:
1318
+ examined_tops.add(first)
1319
+ try:
1320
+ top_path = Path(distribution.locate_file(first)).resolve(strict=True)
1321
+ except OSError:
1322
+ pass
1323
+ else:
1324
+ if top_path.is_dir():
1325
+ declared_directories[top_level] = top_path
1326
+ if not str(declared).endswith(suffixes):
1327
+ continue
1328
+ declared_parts = str(declared).replace("\\", "/").split("/")
1329
+ if "__pycache__" in declared_parts:
1330
+ continue
1331
+ raw_import_files.append(Path(distribution.locate_file(declared)))
1332
+ def bind_candidate(path: Path) -> dict[str, object] | None:
1333
+ try:
1334
+ return _path_binding(path, require_directory=False)
1335
+ except (OSError, ValueError):
1336
+ return None
1337
+ with ThreadPoolExecutor(max_workers=min(32, max(1, len(raw_import_files)))) as executor:
1338
+ bound_candidates = executor.map(bind_candidate, raw_import_files)
1339
+ candidate_bindings = list(bound_candidates)
1340
+ for binding in candidate_bindings:
1341
+ if binding is None:
1342
+ continue
1343
+ candidate = Path(str(binding["canonical"]))
1344
+ if not candidate.is_file() or not _path_is_within(candidate, import_root):
1345
+ continue
1346
+ declared_files.add(candidate)
1347
+ all_files[candidate] = binding
1348
+ if not declared_files:
1349
+ raise ValueError
1350
+ files_by_distribution[name] = declared_files
1351
+ top_directories[name] = declared_directories
1352
+ raw_top_level = distribution.read_text("top_level.txt") or ""
1353
+ top_levels: set[str] = set()
1354
+ for raw in raw_top_level.splitlines():
1355
+ normalized = raw.strip().replace("\\", "/")
1356
+ if normalized:
1357
+ top_levels.add(normalized.partition("/")[0])
1358
+ top_levels.add(normalized.rpartition("/")[2])
1359
+ for declared in distribution.files:
1360
+ first = str(declared).replace("\\", "/").partition("/")[0]
1361
+ if first.endswith(suffixes):
1362
+ first = first.partition(".")[0]
1363
+ top_levels.add(first)
1364
+ for top_level in top_levels:
1365
+ if top_level.isidentifier() and top_level != "__pycache__":
1366
+ ownership.setdefault(top_level, set()).add(name)
1367
+
1368
+ return (
1369
+ distribution_paths,
1370
+ {name: frozenset(files) for name, files in files_by_distribution.items()},
1371
+ top_directories,
1372
+ {name: frozenset(owners) for name, owners in ownership.items()},
1373
+ all_files,
1374
+ )
1375
+
1376
+
1377
+ def _mcp_import_manifest(
1378
+ selected_root: Path,
1379
+ metadata_roots: tuple[Path, ...] | None = None,
1380
+ ) -> dict[str, object]:
1381
+ """Describe only import files declared by the verified MCP dependency closure."""
1382
+ selected = selected_root.resolve(strict=True)
1383
+ if not selected.is_dir():
1384
+ raise OSError
1385
+ (
1386
+ distribution_paths,
1387
+ files_by_distribution,
1388
+ top_directories,
1389
+ ownership,
1390
+ all_files,
1391
+ ) = _mcp_import_inventory(metadata_roots)
1392
+ for binding in distribution_paths.values():
1393
+ _, canonical = _validate_path_binding(binding, require_directory=True)
1394
+ if _paths_overlap(canonical, selected):
1395
+ raise ValueError
1396
+
1397
+ packages: dict[str, dict[str, str | None]] = {}
1398
+ for top_level, raw_owners in ownership.items():
1399
+ if len(raw_owners) != 1:
1400
+ raise ValueError
1401
+ owner = next(iter(raw_owners))
1402
+ search_path = sys.path if metadata_roots is None else [str(root) for root in metadata_roots]
1403
+ spec = machinery.PathFinder.find_spec(top_level, search_path)
1404
+ if spec is None or spec.origin in {"built-in", "frozen"}:
1405
+ continue
1406
+ raw_locations = spec.submodule_search_locations
1407
+ root: Path | None = None
1408
+ origin: Path | None = None
1409
+ if raw_locations is not None:
1410
+ locations = [Path(raw).resolve(strict=True) for raw in raw_locations]
1411
+ if len(locations) != 1 or not locations[0].is_dir():
1412
+ raise ValueError
1413
+ root = locations[0]
1414
+ search = root.parent
1415
+ if spec.origin is None:
1416
+ if top_directories[owner].get(top_level) != root:
1417
+ raise ValueError
1418
+ else:
1419
+ if spec.origin is None:
1420
+ raise ValueError
1421
+ origin = Path(spec.origin).resolve(strict=True)
1422
+ if origin not in files_by_distribution[owner]:
1423
+ raise ValueError
1424
+ search = origin.parent
1425
+ if spec.origin is not None:
1426
+ origin = Path(spec.origin).resolve(strict=True)
1427
+ if origin not in files_by_distribution[owner]:
1428
+ raise ValueError
1429
+ if _paths_overlap(search, selected) or (root is not None and _paths_overlap(root, selected)):
1430
+ raise ValueError
1431
+ packages[top_level] = {
1432
+ "origin": _path_binding(Path(spec.origin), require_directory=False)
1433
+ if spec.origin is not None
1434
+ else None,
1435
+ "root": _path_binding(Path(next(iter(raw_locations))), require_directory=True)
1436
+ if raw_locations is not None
1437
+ else None,
1438
+ "search": _path_binding(search, require_directory=True),
1439
+ }
1440
+ for required in ("mcp", "networkx"):
1441
+ if required not in packages:
1442
+ raise ValueError
1443
+ return {
1444
+ "distributions": distribution_paths,
1445
+ "files": _compact_manifest_files(distribution_paths, all_files),
1446
+ "packages": packages,
1447
+ }
1448
+
1449
+
1450
+ def _validate_mcp_manifest(manifest: object, selected_root: Path) -> dict[str, object]:
1451
+ if not isinstance(manifest, dict) or set(manifest) != {"distributions", "files", "packages"}:
1452
+ raise ValueError
1453
+ raw_distributions = manifest["distributions"]
1454
+ raw_files = manifest["files"]
1455
+ raw_packages = manifest["packages"]
1456
+ if not isinstance(raw_distributions, dict) or not isinstance(raw_packages, dict):
1457
+ raise ValueError
1458
+ selected = selected_root.resolve(strict=True)
1459
+ allowed_files = _validate_manifest_file_groups(raw_files, selected)
1460
+ for name, binding in raw_distributions.items():
1461
+ if not isinstance(name, str):
1462
+ raise ValueError
1463
+ _, canonical = _validate_path_binding(binding, require_directory=True)
1464
+ if not canonical.name.endswith(".dist-info") or _paths_overlap(canonical, selected):
1465
+ raise ValueError
1466
+ for name, entry in raw_packages.items():
1467
+ if not isinstance(name, str) or not name.isidentifier() or not isinstance(entry, dict):
1468
+ raise ValueError
1469
+ if set(entry) != {"origin", "root", "search"}:
1470
+ raise ValueError
1471
+ _, search = _validate_path_binding(entry["search"], require_directory=True)
1472
+ if _paths_overlap(search, selected):
1473
+ raise ValueError
1474
+ root: Path | None = None
1475
+ if entry["root"] is not None:
1476
+ _, root = _validate_path_binding(entry["root"], require_directory=True)
1477
+ if _paths_overlap(root, selected) or root.parent != search:
1478
+ raise ValueError
1479
+ if entry["origin"] is not None:
1480
+ _, origin = _validate_path_binding(entry["origin"], require_directory=False)
1481
+ if origin not in allowed_files:
1482
+ raise ValueError
1483
+ if root is not None and not _path_is_within(origin, root):
1484
+ raise ValueError
1485
+ elif root is None:
1486
+ raise ValueError
1487
+ if not {"mcp", "networkx"}.issubset(raw_packages):
1488
+ raise ValueError
1489
+ return manifest
1490
+
1491
+
1492
+ def _raw_metadata_search_roots() -> list[str]:
1493
+ roots: list[str] = []
1494
+ seen: set[str] = set()
1495
+ for raw in sys.path:
1496
+ if not isinstance(raw, str) or not raw or not os.path.isabs(raw):
1497
+ continue
1498
+ candidate = os.path.abspath(raw)
1499
+ normalized = os.path.normcase(candidate)
1500
+ if normalized not in seen:
1501
+ seen.add(normalized)
1502
+ roots.append(candidate)
1503
+ return roots
1504
+
1505
+
1506
+ _MCP_MANIFEST_CACHE_LOCK = threading.Lock()
1507
+ _MCP_MANIFEST_CACHE: tuple[tuple[str, ...], bytes] | None = None
1508
+
1509
+
1510
+ def _validate_binding_shape(binding: object) -> dict[str, object]:
1511
+ if not isinstance(binding, dict) or set(binding) != {"canonical", "identity", "lexical"}:
1512
+ raise ValueError
1513
+ lexical = binding["lexical"]
1514
+ canonical = binding["canonical"]
1515
+ identity = binding["identity"]
1516
+ if (
1517
+ not isinstance(lexical, str)
1518
+ or not isinstance(canonical, str)
1519
+ or not os.path.isabs(lexical)
1520
+ or not os.path.isabs(canonical)
1521
+ or not isinstance(identity, list)
1522
+ or len(identity) != 4
1523
+ or any(not isinstance(item, int) or isinstance(item, bool) for item in identity)
1524
+ ):
1525
+ raise ValueError
1526
+ return binding
1527
+
1528
+
1529
+ def _parse_builder_envelope(
1530
+ payload: bytes,
1531
+ ) -> tuple[dict[str, object], dict[str, dict[str, object]]]:
1532
+ envelope = _json_object(payload)
1533
+ if set(envelope) != {"bindings", "manifest"}:
1534
+ raise ValueError
1535
+ manifest = envelope["manifest"]
1536
+ bindings = envelope["bindings"]
1537
+ if (
1538
+ not isinstance(manifest, dict)
1539
+ or set(manifest) != {"distributions", "files", "packages"}
1540
+ or not isinstance(bindings, dict)
1541
+ or set(bindings) != {"doctor", "init", "mcp", "selected", "trusted"}
1542
+ ):
1543
+ raise ValueError
1544
+ verified_bindings = {
1545
+ name: _validate_binding_shape(binding)
1546
+ for name, binding in bindings.items()
1547
+ }
1548
+ return manifest, verified_bindings
1549
+
1550
+
1551
+ def _build_mcp_manifest_bounded(
1552
+ selected_raw: str,
1553
+ *,
1554
+ trusted_raw: str,
1555
+ metadata_roots: list[str],
1556
+ python_executable: str,
1557
+ timeout_seconds: float,
1558
+ builder_script: str,
1559
+ runner: Callable[..., ProbeProcessResult],
1560
+ ) -> tuple[dict[str, object], dict[str, dict[str, object]]]:
1561
+ global _MCP_MANIFEST_CACHE
1562
+ if len(metadata_roots) > _MCP_METADATA_ROOT_LIMIT:
1563
+ raise ValueError
1564
+ metadata_payload = json.dumps(metadata_roots, separators=(",", ":"))
1565
+ argument_size = len(trusted_raw.encode("utf-8")) + len(selected_raw.encode("utf-8")) + len(
1566
+ metadata_payload.encode("utf-8")
1567
+ )
1568
+ if argument_size > _MCP_BUILDER_ARGUMENT_LIMIT_BYTES:
1569
+ raise ValueError
1570
+ use_cache = builder_script == _MCP_MANIFEST_BUILDER_BOOTSTRAP
1571
+ cache_key = (python_executable, trusted_raw, *metadata_roots)
1572
+ cached_payload = b""
1573
+ if use_cache:
1574
+ with _MCP_MANIFEST_CACHE_LOCK:
1575
+ cached = _MCP_MANIFEST_CACHE
1576
+ if cached is not None and cached[0] == cache_key:
1577
+ cached_payload = cached[1]
1578
+ try:
1579
+ result = runner(
1580
+ [
1581
+ python_executable,
1582
+ "-I",
1583
+ "-S",
1584
+ "-B",
1585
+ "-c",
1586
+ builder_script,
1587
+ trusted_raw,
1588
+ selected_raw,
1589
+ metadata_payload,
1590
+ ],
1591
+ cwd=Path(os.path.abspath(os.path.dirname(sys.executable))),
1592
+ stdin=cached_payload,
1593
+ timeout_seconds=timeout_seconds,
1594
+ max_output_bytes=_MCP_OUTPUT_LIMIT_BYTES,
1595
+ )
1596
+ except ProbeProcessError as exc:
1597
+ if exc.code == "nonzero":
1598
+ if use_cache and cached_payload:
1599
+ with _MCP_MANIFEST_CACHE_LOCK:
1600
+ if _MCP_MANIFEST_CACHE == (cache_key, cached_payload):
1601
+ _MCP_MANIFEST_CACHE = None
1602
+ raise ValueError from None
1603
+ raise
1604
+ if len(result.stdout) + len(result.stderr) > _MCP_OUTPUT_LIMIT_BYTES:
1605
+ raise ProbeProcessError("output_limit")
1606
+ manifest, bindings = _parse_builder_envelope(result.stdout)
1607
+ if use_cache:
1608
+ manifest_payload = json.dumps(manifest, separators=(",", ":")).encode("utf-8")
1609
+ if len(manifest_payload) > _MCP_OUTPUT_LIMIT_BYTES:
1610
+ raise ProbeProcessError("output_limit")
1611
+ with _MCP_MANIFEST_CACHE_LOCK:
1612
+ _MCP_MANIFEST_CACHE = (cache_key, manifest_payload)
1613
+ return manifest, bindings
1614
+
1615
+
1616
+ def _validate_json_nesting(text: str) -> None:
1617
+ """Reject excessive or mismatched JSON nesting without interpreting string data."""
1618
+ stack: list[str] = []
1619
+ in_string = False
1620
+ escaped = False
1621
+ pairs = {"}": "{", "]": "["}
1622
+ for character in text:
1623
+ if in_string:
1624
+ if escaped:
1625
+ escaped = False
1626
+ elif character == "\\":
1627
+ escaped = True
1628
+ elif character == '"':
1629
+ in_string = False
1630
+ continue
1631
+ if character == '"':
1632
+ in_string = True
1633
+ elif character in "[{":
1634
+ stack.append(character)
1635
+ if len(stack) > _MCP_NESTING_LIMIT:
1636
+ raise ValueError
1637
+ elif character in "]}":
1638
+ if not stack or stack.pop() != pairs[character]:
1639
+ raise ValueError
1640
+ if in_string or stack:
1641
+ raise ValueError
1642
+
1643
+
1644
+ def _reject_json_constant(value: str) -> None:
1645
+ del value
1646
+ raise ValueError
1647
+
1648
+
1649
+ def _parse_finite_json_float(value: str) -> float:
1650
+ parsed = float(value)
1651
+ if not math.isfinite(parsed):
1652
+ raise ValueError
1653
+ return parsed
1654
+
1655
+
1656
+ def _json_object_without_duplicate_keys(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
1657
+ value: dict[str, Any] = {}
1658
+ for key, item in pairs:
1659
+ if key in value:
1660
+ raise ValueError
1661
+ value[key] = item
1662
+ return value
1663
+
1664
+
1665
+ def _parse_mcp_responses(output: bytes, stderr: bytes) -> dict[int, dict[str, Any]]:
1666
+ if len(output) + len(stderr) > _MCP_OUTPUT_LIMIT_BYTES:
1667
+ raise ValueError
1668
+ raw_lines = output.splitlines()
1669
+ if not raw_lines or len(raw_lines) > _MCP_LINE_LIMIT:
1670
+ raise ValueError
1671
+ responses: dict[int, dict[str, Any]] = {}
1672
+ response_count = 0
1673
+ for raw_line in raw_lines:
1674
+ text = raw_line.decode("utf-8")
1675
+ _validate_json_nesting(text)
1676
+ envelope = json.loads(
1677
+ text,
1678
+ parse_constant=_reject_json_constant,
1679
+ parse_float=_parse_finite_json_float,
1680
+ object_pairs_hook=_json_object_without_duplicate_keys,
1681
+ )
1682
+ if not isinstance(envelope, dict) or envelope.get("jsonrpc") != "2.0":
1683
+ raise ValueError
1684
+ if "id" not in envelope:
1685
+ if not set(envelope).issubset({"jsonrpc", "method", "params"}):
1686
+ raise ValueError
1687
+ method = envelope.get("method")
1688
+ params = envelope.get("params", {})
1689
+ if not isinstance(method, str) or not method or not isinstance(params, (dict, list)):
1690
+ raise ValueError
1691
+ continue
1692
+ response_count += 1
1693
+ if response_count > _MCP_RESPONSE_LIMIT:
1694
+ raise ValueError
1695
+ if not set(envelope).issubset({"jsonrpc", "id", "result", "error"}):
1696
+ raise ValueError
1697
+ response_id = envelope["id"]
1698
+ if not isinstance(response_id, int) or isinstance(response_id, bool):
1699
+ raise ValueError
1700
+ if response_id not in {1, 2} or response_id in responses:
1701
+ raise ValueError
1702
+ if "result" not in envelope or "error" in envelope:
1703
+ raise ValueError
1704
+ responses[response_id] = envelope
1705
+ if set(responses) != {1, 2} or response_count != 2:
1706
+ raise ValueError
1707
+ return responses
1708
+
1709
+
1710
+ def _mcp_transcript_complete(stdout: bytes) -> bool:
1711
+ """True once BOTH expected replies are on stdout, so stdin may be closed.
1712
+
1713
+ This is what makes the deferred close a fix for graphite#29 rather than a
1714
+ delay of it. `initialize` alone is precisely the transcript every observed
1715
+ failure captured -- the server answers it inline in its receive loop, then
1716
+ hands `tools/list` to a concurrent task that races the same loop tearing the
1717
+ write side down on EOF. Accepting one reply as "done" would close stdin at
1718
+ exactly the moment that race is lost.
1719
+
1720
+ Reuses the real parser rather than scanning for ids, so a half-written line
1721
+ is not mistaken for an answer: it raises, and raising means "not yet".
1722
+ """
1723
+ try:
1724
+ responses = _parse_mcp_responses(stdout, b"")
1725
+ except Exception:
1726
+ return False
1727
+ return 1 in responses and 2 in responses
1728
+
1729
+
1730
+ def probe_mcp(
1731
+ root: Path,
1732
+ *,
1733
+ python_executable: str = sys.executable,
1734
+ timeout_seconds: float = 20.0,
1735
+ manifest_timeout_seconds: float | None = None,
1736
+ _runner: Callable[..., ProbeProcessResult] = run_bounded_process,
1737
+ _builder_script: str = _MCP_MANIFEST_BUILDER_BOOTSTRAP,
1738
+ _builder_runner: Callable[..., ProbeProcessResult] = run_bounded_process,
1739
+ ) -> DoctorCheck:
1740
+ """Initialize the MCP server and inspect its read-only tool inventory.
1741
+
1742
+ The manifest build and the server probe get **separate** allowances. They
1743
+ are different faults -- a slow local build is not an unresponsive child --
1744
+ and sharing one deadline meant a cold build (~8.7s over 1758 files) could
1745
+ spend the whole budget and report `timeout` without starting a child
1746
+ (graphite#39). Worst-case wall time is therefore the sum of the two.
1747
+
1748
+ `manifest_timeout_seconds` defaults to `timeout_seconds` so a caller with a
1749
+ single knob still bounds both phases: passing `timeout_seconds=0.05` must
1750
+ keep the builder on a 0.05s leash, not hand it a separate generous one.
1751
+ """
1752
+ started = time.monotonic()
1753
+ if not math.isfinite(timeout_seconds) or timeout_seconds <= 0:
1754
+ return _degraded_probe("deep_mcp", "MCP", "invalid_timeout")
1755
+ if manifest_timeout_seconds is None:
1756
+ manifest_timeout_seconds = timeout_seconds
1757
+ if not math.isfinite(manifest_timeout_seconds) or manifest_timeout_seconds <= 0:
1758
+ return _degraded_probe("deep_mcp", "MCP", "invalid_timeout")
1759
+ deadline = started + manifest_timeout_seconds
1760
+ requests = (
1761
+ {
1762
+ "jsonrpc": "2.0",
1763
+ "id": 1,
1764
+ "method": "initialize",
1765
+ "params": {
1766
+ "protocolVersion": _MCP_PROTOCOL_VERSION,
1767
+ "capabilities": {},
1768
+ "clientInfo": {"name": "graphite-doctor", "version": "1.0"},
1769
+ },
1770
+ },
1771
+ {"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}},
1772
+ {"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}},
1773
+ )
1774
+ protocol_input = b"".join(
1775
+ json.dumps(request, separators=(",", ":")).encode("utf-8") + b"\n"
1776
+ for request in requests
1777
+ )
1778
+ try:
1779
+ selected_raw = os.path.abspath(os.fspath(root))
1780
+ trusted_raw = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
1781
+ metadata_roots = _raw_metadata_search_roots()
1782
+ remaining = deadline - time.monotonic()
1783
+ if remaining <= 0:
1784
+ raise ProbeProcessError("timeout")
1785
+ manifest, bindings = _build_mcp_manifest_bounded(
1786
+ selected_raw,
1787
+ trusted_raw=trusted_raw,
1788
+ metadata_roots=metadata_roots,
1789
+ python_executable=python_executable,
1790
+ timeout_seconds=remaining,
1791
+ builder_script=_builder_script,
1792
+ runner=_builder_runner,
1793
+ )
1794
+ # Length-prefixed, not newline-terminated. The child has to find the
1795
+ # end of the manifest without consuming what follows it, and a
1796
+ # `readline()` scan cannot: BufferedReader pulls a chunk past the
1797
+ # newline into a buffer only that object can see, stranding the
1798
+ # protocol input where the MCP server's own fd-0 reader never finds it.
1799
+ manifest_bytes = json.dumps(manifest, separators=(",", ":")).encode("utf-8")
1800
+ stdin = b"%d\n" % len(manifest_bytes) + manifest_bytes + protocol_input
1801
+ # Fresh clock: the server gets its full allowance no matter how long
1802
+ # building the manifest took. See the note on separate budgets above.
1803
+ remaining = timeout_seconds
1804
+ selected_binding = bindings["selected"]
1805
+ selected = Path(str(selected_binding["canonical"]))
1806
+ result = _runner(
1807
+ [
1808
+ python_executable,
1809
+ "-I",
1810
+ "-S",
1811
+ "-B",
1812
+ "-c",
1813
+ _MCP_BOOTSTRAP,
1814
+ json.dumps(bindings["trusted"], separators=(",", ":")),
1815
+ json.dumps(bindings["init"], separators=(",", ":")),
1816
+ json.dumps(bindings["mcp"], separators=(",", ":")),
1817
+ json.dumps(selected_binding, separators=(",", ":")),
1818
+ ],
1819
+ cwd=selected,
1820
+ stdin=stdin,
1821
+ timeout_seconds=remaining,
1822
+ max_output_bytes=_MCP_OUTPUT_LIMIT_BYTES,
1823
+ # Hold stdin open until both replies are in. The server treats EOF
1824
+ # as end-of-session, so closing it the moment the payload is
1825
+ # written lets teardown race a reply that is still in flight
1826
+ # (graphite#29).
1827
+ stdin_close_when=_mcp_transcript_complete,
1828
+ )
1829
+ except ProbeProcessError as exc:
1830
+ # The exception IS the result on this path (graphite#51). No
1831
+ # `ProbeProcessResult` exists -- run_bounded_process raises instead of
1832
+ # returning -- so passing nothing made every field render `<none>` on
1833
+ # the one failure the diagnostic exists to explain.
1834
+ #
1835
+ # The streams themselves deliberately stay inside the transport: the
1836
+ # error type is contractually free of process data, pinned by tests that
1837
+ # assert a child's output cannot reach `str(exc)`. What crosses is
1838
+ # COUNTS and TIMINGS -- numbers, safe for the same reason `os_error` is
1839
+ # carried as a number.
1840
+ _record_probe_diagnostics(root, exc.code, exc)
1841
+ return _degraded_probe("deep_mcp", "MCP", exc.code)
1842
+ except Exception:
1843
+ _record_probe_diagnostics(root, "probe_failed")
1844
+ return _degraded_probe("deep_mcp", "MCP", "probe_failed")
1845
+
1846
+ try:
1847
+ responses = _parse_mcp_responses(result.stdout, result.stderr)
1848
+ initialize = responses[1]["result"]
1849
+ tools_result = responses[2]["result"]
1850
+ if not isinstance(initialize, dict) or not isinstance(tools_result, dict):
1851
+ raise ValueError
1852
+ server_info = initialize.get("serverInfo")
1853
+ tools = tools_result.get("tools")
1854
+ if not isinstance(server_info, dict) or server_info.get("name") != "graphite":
1855
+ raise ValueError
1856
+ if not isinstance(tools, list):
1857
+ raise ValueError
1858
+ tool_names = {
1859
+ tool.get("name")
1860
+ for tool in tools
1861
+ if isinstance(tool, dict) and isinstance(tool.get("name"), str)
1862
+ }
1863
+ if not _REQUIRED_MCP_TOOLS.issubset(tool_names):
1864
+ raise ValueError
1865
+ except Exception:
1866
+ _record_probe_diagnostics(root, "invalid_response", result)
1867
+ return _degraded_probe("deep_mcp", "MCP", "invalid_response")
1868
+ return DoctorCheck(
1869
+ "deep_mcp",
1870
+ "MCP",
1871
+ "ready",
1872
+ "The MCP server initializes and exposes the required tools.",
1873
+ {"server_name": "graphite", "tool_count": len(tool_names)},
1874
+ )
1875
+
1876
+
1877
+ def _resolve_external_node(root: Path) -> Path | None:
1878
+ """Resolve executable Node from absolute PATH entries outside the selected project."""
1879
+ name = "node.exe" if os.name == "nt" else "node"
1880
+ try:
1881
+ selected = root.resolve()
1882
+ except OSError:
1883
+ return None
1884
+ for raw_directory in os.environ.get("PATH", "").split(os.pathsep):
1885
+ directory = Path(raw_directory)
1886
+ if not raw_directory or not directory.is_absolute():
1887
+ continue
1888
+ try:
1889
+ candidate = (directory / name).resolve(strict=True)
1890
+ if not candidate.is_file() or (os.name != "nt" and not os.access(candidate, os.X_OK)):
1891
+ continue
1892
+ if not _paths_overlap(candidate.parent, selected):
1893
+ return candidate
1894
+ except OSError:
1895
+ continue
1896
+ return None
1897
+
1898
+
1899
+ def probe_typescript(
1900
+ root: Path,
1901
+ *,
1902
+ timeout_seconds: float = 10.0,
1903
+ _node_resolver: Callable[[Path], Path | None] = _resolve_external_node,
1904
+ _runner: Callable[..., ProbeProcessResult] = run_bounded_process,
1905
+ ) -> DoctorCheck:
1906
+ """Detect TypeScript statically without executing project-controlled JavaScript."""
1907
+ node = _node_resolver(root)
1908
+ if node is None:
1909
+ return DoctorCheck(
1910
+ "deep_typescript",
1911
+ "TypeScript",
1912
+ "optional",
1913
+ "Node.js is unavailable; the TypeScript deep probe is optional.",
1914
+ )
1915
+ try:
1916
+ result = _runner(
1917
+ [str(node), "-e", _TYPESCRIPT_SCRIPT],
1918
+ cwd=root,
1919
+ timeout_seconds=timeout_seconds,
1920
+ check=False,
1921
+ )
1922
+ except ProbeProcessError as exc:
1923
+ return _degraded_probe("deep_typescript", "TypeScript", exc.code)
1924
+ except Exception:
1925
+ return _degraded_probe("deep_typescript", "TypeScript", "probe_failed")
1926
+ if result.returncode != 0:
1927
+ return _degraded_probe("deep_typescript", "TypeScript", "invalid_result")
1928
+ try:
1929
+ payload = _json_object(result.stdout)
1930
+ except Exception:
1931
+ return _degraded_probe("deep_typescript", "TypeScript", "invalid_result")
1932
+ if payload == {"missing_module": "typescript"}:
1933
+ return DoctorCheck(
1934
+ "deep_typescript",
1935
+ "TypeScript",
1936
+ "optional",
1937
+ "The TypeScript compiler module is unavailable.",
1938
+ remediation=_TYPESCRIPT_REMEDIATION,
1939
+ )
1940
+ if set(payload) != {"detected"} or payload["detected"] is not True:
1941
+ return _degraded_probe("deep_typescript", "TypeScript", "invalid_result")
1942
+ return DoctorCheck(
1943
+ "deep_typescript",
1944
+ "TypeScript",
1945
+ "optional",
1946
+ "TypeScript was detected but intentionally not executed because project dependencies "
1947
+ "are outside the doctor trust boundary.",
1948
+ )
1949
+
1950
+
1951
+ def _llm_failure(category: str = "provider_error") -> DoctorCheck:
1952
+ safe_category = category if category in _LLM_CATEGORIES else "provider_error"
1953
+ return DoctorCheck(
1954
+ "deep_llm",
1955
+ "LLM",
1956
+ "degraded",
1957
+ "synthetic connectivity probe failed",
1958
+ {"category": safe_category},
1959
+ _LLM_FAILURE_REMEDIATION,
1960
+ )
1961
+
1962
+
1963
+ def _bounded_llm_text(value: object, *, limit: int) -> str | None:
1964
+ if value is None:
1965
+ return None
1966
+ if not isinstance(value, str) or len(value) > limit:
1967
+ raise ValueError
1968
+ return value
1969
+
1970
+
1971
+ def _llm_worker_input(cfg: Config, timeout_seconds: float) -> bytes:
1972
+ mode = _bounded_llm_text(cfg.llm_mode, limit=16)
1973
+ provider = _bounded_llm_text(cfg.llm_provider, limit=128)
1974
+ if (
1975
+ mode is None
1976
+ or mode.strip().lower() not in {"auto", "local", "cloud"}
1977
+ or provider is None
1978
+ or not isinstance(cfg.seed, int)
1979
+ or isinstance(cfg.seed, bool)
1980
+ ):
1981
+ raise ValueError
1982
+ payload = {
1983
+ "mode": mode,
1984
+ "provider": provider,
1985
+ "model": _bounded_llm_text(cfg.llm_model, limit=512),
1986
+ "base_url": _bounded_llm_text(cfg.llm_base_url, limit=2048),
1987
+ "api_key": _bounded_llm_text(cfg.llm_api_key, limit=4096),
1988
+ "timeout_seconds": timeout_seconds,
1989
+ "seed": cfg.seed,
1990
+ "system": _LLM_SYSTEM_PROMPT,
1991
+ "user": _LLM_USER_PROMPT,
1992
+ }
1993
+ encoded = json.dumps(payload, separators=(",", ":")).encode("utf-8")
1994
+ if len(encoded) > INPUT_LIMIT_BYTES or len(encoded) > _LLM_WORKER_INPUT_LIMIT_BYTES:
1995
+ raise ValueError
1996
+ return encoded
1997
+
1998
+
1999
+ def probe_llm(
2000
+ cfg: Config,
2001
+ *,
2002
+ _runner: Callable[..., ProbeProcessResult] = run_bounded_process,
2003
+ ) -> DoctorCheck:
2004
+ """Run one synthetic request in a native-contained, deadline-bounded worker."""
2005
+ if not isinstance(cfg.llm_mode, str):
2006
+ return _llm_failure("configuration")
2007
+ if cfg.llm_mode.strip().lower() == "none":
2008
+ return DoctorCheck(
2009
+ "deep_llm",
2010
+ "LLM",
2011
+ "optional",
2012
+ "disabled by configuration",
2013
+ )
2014
+
2015
+ try:
2016
+ configured_timeout = float(cfg.llm_timeout_seconds)
2017
+ if not math.isfinite(configured_timeout) or configured_timeout <= 0:
2018
+ return _llm_failure("configuration")
2019
+ timeout_seconds = max(0.1, min(configured_timeout, _LLM_TIMEOUT_MAX_SECONDS))
2020
+ stdin = _llm_worker_input(cfg, timeout_seconds)
2021
+ python = Path(sys.executable).resolve(strict=True)
2022
+ source = Path(__file__).resolve(strict=True)
2023
+ worker = source.with_name("llm_probe.py").resolve(strict=True)
2024
+ if worker.parent != source.parent or not worker.is_file() or not python.is_file():
2025
+ return _llm_failure("provider_error")
2026
+ result = _runner(
2027
+ [str(python), "-I", "-S", "-B", str(worker)],
2028
+ cwd=python.parent,
2029
+ stdin=stdin,
2030
+ timeout_seconds=timeout_seconds,
2031
+ max_output_bytes=_LLM_OUTPUT_LIMIT_BYTES,
2032
+ check=False,
2033
+ )
2034
+ except ProbeProcessError as exc:
2035
+ return _llm_failure("timeout" if exc.code == "timeout" else "provider_error")
2036
+ except (OSError, TypeError, ValueError):
2037
+ return _llm_failure("configuration")
2038
+ except Exception:
2039
+ return _llm_failure("provider_error")
2040
+ if result.returncode != 0:
2041
+ return _llm_failure("provider_error")
2042
+ try:
2043
+ payload = _json_object(result.stdout)
2044
+ except Exception:
2045
+ return _llm_failure("provider_error")
2046
+ if payload == {"status": "ready", "response_present": True}:
2047
+ pass
2048
+ elif (
2049
+ set(payload) == {"status", "category"}
2050
+ and payload.get("status") == "degraded"
2051
+ and payload.get("category") in _LLM_CATEGORIES
2052
+ ):
2053
+ return _llm_failure(str(payload["category"]))
2054
+ else:
2055
+ return _llm_failure("provider_error")
2056
+ return DoctorCheck(
2057
+ "deep_llm",
2058
+ "LLM",
2059
+ "ready",
2060
+ "synthetic connectivity probe succeeded",
2061
+ {"provider": canonical_provider_name(cfg.llm_provider), "response_present": True},
2062
+ )
2063
+
2064
+
2065
+ def run_deep_probes(
2066
+ root: Path,
2067
+ *,
2068
+ cfg: Config,
2069
+ include_llm: bool,
2070
+ ) -> list[DoctorCheck]:
2071
+ """Return the deep capabilities implemented in this release."""
2072
+ probes: tuple[tuple[Callable[[Path], DoctorCheck], Callable[[], DoctorCheck]], ...] = (
2073
+ (probe_core_pipeline, lambda: _blocked("unexpected", "probe_failed")),
2074
+ (probe_mcp, lambda: _degraded_probe("deep_mcp", "MCP", "probe_failed")),
2075
+ (
2076
+ probe_typescript,
2077
+ lambda: _degraded_probe("deep_typescript", "TypeScript", "probe_failed"),
2078
+ ),
2079
+ )
2080
+ checks: list[DoctorCheck] = []
2081
+ for probe, fallback in probes:
2082
+ try:
2083
+ checks.append(probe(root))
2084
+ except Exception:
2085
+ checks.append(fallback())
2086
+ if not include_llm:
2087
+ checks.append(
2088
+ DoctorCheck(
2089
+ "deep_llm",
2090
+ "LLM",
2091
+ "optional",
2092
+ "not requested; use --deep --include-llm",
2093
+ )
2094
+ )
2095
+ return checks
2096
+ try:
2097
+ checks.append(probe_llm(cfg))
2098
+ except Exception:
2099
+ checks.append(_llm_failure())
2100
+ return checks