engineering-platform 2.2.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 (130) hide show
  1. engineering_platform/ENGINEERING_PLATFORM_CONFIG.json +32 -0
  2. engineering_platform/ENGINEERING_PLATFORM_VERSION.json +15 -0
  3. engineering_platform/__init__.py +1 -0
  4. engineering_platform/__main__.py +7 -0
  5. engineering_platform/agent_state.py +530 -0
  6. engineering_platform/agent_trust.py +174 -0
  7. engineering_platform/assets/dashboard.css +1317 -0
  8. engineering_platform/assets/dashboard.js +8534 -0
  9. engineering_platform/assets/dashboard_locales.mjs +4049 -0
  10. engineering_platform/assets/dashboard_status_store.mjs +41 -0
  11. engineering_platform/assets/operations-console/apple-touch-icon-dark.png +0 -0
  12. engineering_platform/assets/operations-console/apple-touch-icon-light.png +0 -0
  13. engineering_platform/assets/operations-console/icon-dark.png +0 -0
  14. engineering_platform/assets/operations-console/icon-light.png +0 -0
  15. engineering_platform/assets/operations-console/icon-transparent.png +0 -0
  16. engineering_platform/assets/operations-console/manifest.webmanifest +11 -0
  17. engineering_platform/capability_preflight.py +285 -0
  18. engineering_platform/capability_review.py +261 -0
  19. engineering_platform/central_data_transfer.py +195 -0
  20. engineering_platform/central_database.py +245 -0
  21. engineering_platform/central_store_migration.py +1672 -0
  22. engineering_platform/codex_capacity.py +81 -0
  23. engineering_platform/codex_chat.py +226 -0
  24. engineering_platform/codex_observability.py +153 -0
  25. engineering_platform/component_lock.py +40 -0
  26. engineering_platform/component_logging.py +420 -0
  27. engineering_platform/console_presentation.py +14 -0
  28. engineering_platform/console_route_ownership.py +83 -0
  29. engineering_platform/contracts/__init__.py +38 -0
  30. engineering_platform/contracts/ep_consumer.py +391 -0
  31. engineering_platform/contracts/models.py +105 -0
  32. engineering_platform/contracts/projection.py +401 -0
  33. engineering_platform/dashboard_browser_validation.py +206 -0
  34. engineering_platform/dashboard_state.py +630 -0
  35. engineering_platform/dashboard_supervisor.swift +105 -0
  36. engineering_platform/dashboard_translation.py +129 -0
  37. engineering_platform/dependabot_producer.py +349 -0
  38. engineering_platform/drift_diagnostics.py +144 -0
  39. engineering_platform/emergency_recovery.py +268 -0
  40. engineering_platform/engineering_memory.py +139 -0
  41. engineering_platform/ep_consumer_credentials.py +473 -0
  42. engineering_platform/evidence_projection.py +213 -0
  43. engineering_platform/execution_activity.py +218 -0
  44. engineering_platform/execution_context.py +132 -0
  45. engineering_platform/execution_errors.py +42 -0
  46. engineering_platform/execution_evidence.py +24 -0
  47. engineering_platform/execution_executor.py +730 -0
  48. engineering_platform/execution_finalization.py +44 -0
  49. engineering_platform/execution_host.py +3306 -0
  50. engineering_platform/execution_lease.py +365 -0
  51. engineering_platform/execution_lifecycle.py +447 -0
  52. engineering_platform/execution_models.py +43 -0
  53. engineering_platform/execution_readiness.py +166 -0
  54. engineering_platform/execution_reporting.py +1607 -0
  55. engineering_platform/execution_repository.py +253 -0
  56. engineering_platform/execution_timeout_policy.py +56 -0
  57. engineering_platform/execution_timing.py +440 -0
  58. engineering_platform/execution_transaction.py +28 -0
  59. engineering_platform/external_producer_binding.py +235 -0
  60. engineering_platform/file_inbox.py +249 -0
  61. engineering_platform/forensic_attribution.py +338 -0
  62. engineering_platform/forensic_attribution_v2.py +134 -0
  63. engineering_platform/forensic_delta.py +299 -0
  64. engineering_platform/golden_scenario.py +63 -0
  65. engineering_platform/historical_dashboard_configuration.py +171 -0
  66. engineering_platform/host_admin.py +199 -0
  67. engineering_platform/host_preflight.py +231 -0
  68. engineering_platform/installation_relocation.py +122 -0
  69. engineering_platform/investigation_ledger.py +89 -0
  70. engineering_platform/legacy_inbox_migration.py +79 -0
  71. engineering_platform/lifecycle_worker.py +223 -0
  72. engineering_platform/live_status.py +267 -0
  73. engineering_platform/local_api.py +209 -0
  74. engineering_platform/local_api_keychain.py +51 -0
  75. engineering_platform/local_repository_binding.py +138 -0
  76. engineering_platform/managed_autonomy.py +509 -0
  77. engineering_platform/managed_codex_runtime.py +105 -0
  78. engineering_platform/parity_context.py +203 -0
  79. engineering_platform/parity_lifecycle_dispatcher.py +488 -0
  80. engineering_platform/platform_admin.py +13 -0
  81. engineering_platform/platform_api.py +428 -0
  82. engineering_platform/platform_bootstrap.py +385 -0
  83. engineering_platform/platform_components.py +65 -0
  84. engineering_platform/platform_version.py +171 -0
  85. engineering_platform/pr_check_repair.py +276 -0
  86. engineering_platform/pr_evidence_backfill.py +278 -0
  87. engineering_platform/producer.py +209 -0
  88. engineering_platform/project_agent.py +366 -0
  89. engineering_platform/project_agent_service.py +244 -0
  90. engineering_platform/project_topology.py +126 -0
  91. engineering_platform/prompt_history.py +591 -0
  92. engineering_platform/provider_context.py +136 -0
  93. engineering_platform/provider_context_benchmark.py +41 -0
  94. engineering_platform/provider_context_scope.py +90 -0
  95. engineering_platform/provider_interruption.py +168 -0
  96. engineering_platform/provider_process_identity.py +80 -0
  97. engineering_platform/provider_readiness.py +138 -0
  98. engineering_platform/provider_recovery.py +647 -0
  99. engineering_platform/provider_usage.py +497 -0
  100. engineering_platform/providers.py +471 -0
  101. engineering_platform/qualification.py +220 -0
  102. engineering_platform/recommendation_handoff.py +238 -0
  103. engineering_platform/report_analysis.py +193 -0
  104. engineering_platform/repository_attachment.py +171 -0
  105. engineering_platform/repository_handoff.py +95 -0
  106. engineering_platform/resources.py +38 -0
  107. engineering_platform/reviewer_evidence.py +70 -0
  108. engineering_platform/schemas/repository-attachment.schema.json +61 -0
  109. engineering_platform/server.py +3679 -0
  110. engineering_platform/server_console_services.py +2024 -0
  111. engineering_platform/server_relay.py +172 -0
  112. engineering_platform/server_service.py +122 -0
  113. engineering_platform/status_model.py +135 -0
  114. engineering_platform/status_reconciliation.py +34 -0
  115. engineering_platform/storage.py +2440 -0
  116. engineering_platform/submission_cli.py +77 -0
  117. engineering_platform/submission_intake.py +45 -0
  118. engineering_platform/submission_service.py +317 -0
  119. engineering_platform/telemetry.py +951 -0
  120. engineering_platform/templates/workspace-config.json +25 -0
  121. engineering_platform/validation_identity.py +50 -0
  122. engineering_platform/validation_profile.py +211 -0
  123. engineering_platform/workspace_preflight.py +263 -0
  124. engineering_platform/worktree_provenance.py +147 -0
  125. engineering_platform/worktree_tooling.py +18 -0
  126. engineering_platform-2.2.0.dist-info/METADATA +18 -0
  127. engineering_platform-2.2.0.dist-info/RECORD +130 -0
  128. engineering_platform-2.2.0.dist-info/WHEEL +5 -0
  129. engineering_platform-2.2.0.dist-info/entry_points.txt +6 -0
  130. engineering_platform-2.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3679 @@
1
+ """Standalone Engineering Platform Server foundation.
2
+
3
+ This module intentionally owns no project, Agent transport, credential, or
4
+ execution authority. It is the installation-owned runtime boundary on which
5
+ those later capabilities can be composed.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ from dataclasses import asdict, dataclass
11
+ from datetime import datetime, timezone
12
+ from html import escape
13
+ import http.server
14
+ import json
15
+ import logging
16
+ import os
17
+ from pathlib import Path
18
+ import plistlib
19
+ import re
20
+ import select
21
+ import shlex
22
+ import shutil
23
+ import signal
24
+ import sqlite3
25
+ # The lifecycle starts this module with a fixed argv; no shell is used.
26
+ import subprocess # nosec B404
27
+ import sys
28
+ import time
29
+ from threading import Lock, RLock, Timer
30
+ from typing import Mapping, Protocol
31
+ from urllib.error import URLError
32
+ from urllib.request import urlopen
33
+ from urllib.parse import SplitResult, parse_qs, urlsplit
34
+ from uuid import uuid4
35
+
36
+ from . import agent_trust
37
+ from . import central_database
38
+ from . import central_data_transfer
39
+ from . import console_route_ownership
40
+ from . import console_presentation
41
+ from . import server_console_services
42
+ from . import dashboard_translation
43
+ from . import dependabot_producer
44
+ from . import external_producer_binding
45
+ from . import file_inbox
46
+ from . import host_admin
47
+ from . import installation_relocation
48
+ from . import local_repository_binding
49
+ from . import project_topology
50
+ from . import submission_service
51
+ from . import server_relay
52
+ from . import server_service
53
+ from . import storage
54
+ from . import managed_codex_runtime
55
+ from . import provider_readiness
56
+ from .platform_components import (
57
+ PLATFORM_COMPONENT_BY_ID,
58
+ PLATFORM_COMPONENT_IDS,
59
+ PLATFORM_COMPONENTS,
60
+ PLATFORM_COMPONENT_ROUTE_PATTERN,
61
+ RETIRED_COMPONENT_ALIAS_ROUTE_PATTERN,
62
+ )
63
+ from .component_logging import (
64
+ LOG_LEVELS_AT_OR_ABOVE,
65
+ MAX_COMPONENT_LOG_PAGE_SIZE,
66
+ VALID_LEVELS,
67
+ component_logger,
68
+ log_event,
69
+ )
70
+ from .ep_consumer_credentials import verifier
71
+ from .parity_context import ParityProjectStore, project_context
72
+ from .platform_version import EngineeringPlatformManifest
73
+ from .providers import (
74
+ GitHubProvider,
75
+ CodexCliProvider,
76
+ LaunchdProvider,
77
+ LaunchdRuntimeDetails,
78
+ MANAGED_CODEX_CLI_PREFIX_ENVIRONMENT,
79
+ LocalProcessProvider,
80
+ default_engineering_platform_codex_cli_prefix,
81
+ )
82
+ from .resources import package_path
83
+
84
+
85
+ SERVER_CONFIGURATION_FILENAME = "server.json"
86
+ SERVER_IDENTITY_FILENAME = "runtime-identity.json"
87
+ SERVER_RUNTIME_FILENAME = "runtime.json"
88
+ SERVER_DATABASE_FILENAME = "engineering.db"
89
+ SERVER_CONFIGURATION_VERSION = 2
90
+ # ADR-0026 defines the first standalone store as the canonical schema-40
91
+ # product definitions plus immutable control provenance. This server-owned
92
+ # bootstrap is deliberately separate from the retired predecessor migration
93
+ # machinery: it creates a clean installation only and never accepts a source
94
+ # database path.
95
+ SERVER_STORE_SCHEMA_VERSION = 53
96
+ SERVER_ENVIRONMENT_DATA_ROOT = "EP_SERVER_DATA_ROOT"
97
+ FILE_INBOX_DIRECTORY = "file-inbox"
98
+ HTTP_JSON_OPENAPI_PATH = "/openapi.json"
99
+ _CENTRAL_LOG_SORT_COLUMNS = {
100
+ "line": "id",
101
+ "timestamp": "created_at",
102
+ "level": "json_extract(payload, '$.level')",
103
+ "event": "json_extract(payload, '$.event')",
104
+ "runId": "COALESCE(json_extract(payload, '$.run_id'), '')",
105
+ "details": "COALESCE(json_extract(payload, '$.diagnostic'), '')",
106
+ }
107
+ _CHILDREN: dict[int, subprocess.Popen[object]] = {}
108
+ _SAFE_ATTACHMENT_FILENAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}")
109
+ _SAFE_REPORT_ID = re.compile(r"[a-z0-9][a-z0-9-]{0,63}")
110
+ _PROVIDER_LOGIN_LOCK = Lock()
111
+ _PROVIDER_INSTALL_LOCK = Lock()
112
+ _CODEX_RATE_LIMIT_CACHE: tuple[float, bytes] | None = None
113
+ _CODEX_RATE_LIMIT_CACHE_LOCK = Lock()
114
+ _CODEX_IDENTITY_CACHE: tuple[float, dict[str, str]] | None = None
115
+ _CODEX_IDENTITY_CACHE_LOCK = Lock()
116
+
117
+
118
+ def _http_json_openapi_document() -> dict[str, object]:
119
+ """Return the published contract for the canonical HTTP JSON ingress.
120
+
121
+ The document is intentionally local and versioned with the Server: it is
122
+ an interface description, not a separate dashboard-owned API surface.
123
+ """
124
+ submission_properties: dict[str, object] = {
125
+ "repository_id": {"type": "string"},
126
+ "producer": {
127
+ "type": "object",
128
+ "required": ["id", "type"],
129
+ "properties": {
130
+ "id": {"type": "string"},
131
+ "type": {"type": "string"},
132
+ "version": {"type": "string"},
133
+ },
134
+ "additionalProperties": False,
135
+ },
136
+ "prompt": {"type": "string"},
137
+ "idempotency_key": {"type": "string"},
138
+ "correlation_id": {"type": "string"},
139
+ "mission_id": {"type": "string"},
140
+ "engineering_action_id": {"type": "string"},
141
+ "constraints": {"type": "object", "additionalProperties": True},
142
+ "transport_receipt_id": {"type": "string"},
143
+ "transport_received_at": {"type": "string", "format": "date-time"},
144
+ }
145
+ return {
146
+ "openapi": "3.0.3",
147
+ "info": {
148
+ "title": "Engineering Platform HTTP JSON API",
149
+ "version": "1",
150
+ "description": "Canonical authenticated submission ingress and platform health.",
151
+ },
152
+ "paths": {
153
+ "/health": {
154
+ "get": {
155
+ "summary": "Read aggregated Engineering Platform component health",
156
+ "description": "Returns every canonical Platform Component. The aggregate is healthy only when every critical component is healthy.",
157
+ "responses": {
158
+ "200": {"description": "Critical Platform Components are healthy"},
159
+ "503": {"description": "One or more critical Platform Components are degraded"},
160
+ },
161
+ },
162
+ },
163
+ "/healthz": {
164
+ "get": {
165
+ "summary": "Read server health and readiness",
166
+ "responses": {"200": {"description": "Server health"}},
167
+ },
168
+ },
169
+ "/readyz": {
170
+ "get": {
171
+ "summary": "Read server readiness",
172
+ "responses": {"200": {"description": "Server readiness"}},
173
+ },
174
+ },
175
+ "/v1/projects/{project_id}/submissions": {
176
+ "post": {
177
+ "summary": "Submit a canonical Engineering Platform request",
178
+ "security": [{"consumerBearer": []}],
179
+ "parameters": [
180
+ {
181
+ "name": "project_id", "in": "path", "required": True,
182
+ "schema": {"type": "string"},
183
+ },
184
+ {
185
+ "name": "EP-Submission-Transport", "in": "header", "required": False,
186
+ "schema": {"type": "string", "default": "HTTP"},
187
+ },
188
+ ],
189
+ "requestBody": {
190
+ "required": True,
191
+ "content": {
192
+ "application/json": {
193
+ "schema": {
194
+ "type": "object",
195
+ "required": ["repository_id", "producer", "prompt"],
196
+ "properties": submission_properties,
197
+ "additionalProperties": False,
198
+ },
199
+ },
200
+ },
201
+ },
202
+ "responses": {
203
+ "200": {"description": "Submission accepted or idempotently repeated"},
204
+ "400": {"description": "Malformed or invalid submission"},
205
+ "401": {"description": "Missing or invalid consumer credential"},
206
+ "404": {"description": "Unknown project"},
207
+ "409": {"description": "Project unavailable or idempotency conflict"},
208
+ "413": {"description": "Payload exceeds 128 KiB"},
209
+ "415": {"description": "Content type is not application/json"},
210
+ },
211
+ },
212
+ },
213
+ },
214
+ "components": {
215
+ "securitySchemes": {
216
+ "consumerBearer": {"type": "http", "scheme": "bearer", "bearerFormat": "opaque"},
217
+ },
218
+ },
219
+ }
220
+
221
+
222
+ def _attachment_content_disposition(filename: object) -> str:
223
+ """Build a fail-closed attachment header from a bounded ASCII filename.
224
+
225
+ Route validation is not a response-header security boundary. This helper
226
+ rejects control characters and all non-allowlisted filenames before a
227
+ value reaches ``BaseHTTPRequestHandler.send_header``.
228
+ """
229
+ if not isinstance(filename, str):
230
+ raise ValueError("attachment filename is invalid")
231
+ sanitized = filename.replace("\r", "").replace("\n", "")
232
+ if sanitized != filename or not _SAFE_ATTACHMENT_FILENAME.fullmatch(sanitized):
233
+ raise ValueError("attachment filename is invalid")
234
+ return f'attachment; filename="{sanitized}"'
235
+
236
+
237
+ def _report_content_disposition(report_id: object) -> str:
238
+ """Compose the report filename only after independently validating its id."""
239
+ if not isinstance(report_id, str) or not _SAFE_REPORT_ID.fullmatch(report_id):
240
+ raise ValueError("report identifier is invalid")
241
+ return _attachment_content_disposition(f"engineering-report-{report_id}.md")
242
+
243
+
244
+ def _execution_runtime_status() -> dict[str, str]:
245
+ """Project installed Server Python readiness without Dashboard ownership."""
246
+ # ``sys.executable`` is the installed venv launcher. Do not resolve its
247
+ # symlink to the base interpreter: the Console must report the runtime
248
+ # that actually owns EP and its validation environment.
249
+ executable = Path(sys.executable).expanduser().absolute()
250
+ ready = executable.is_file() and os.access(executable, os.X_OK)
251
+ return {
252
+ "state": "READY" if ready else "UNAVAILABLE",
253
+ "executable": str(executable) if ready else "",
254
+ "version": sys.version.split()[0] if ready else "",
255
+ }
256
+
257
+
258
+ def _remaining_rate_limit_capacity(rate_limits: dict[str, object]) -> float | None:
259
+ """Return the most restrictive remaining safe quota percentage."""
260
+ windows = rate_limits.get("windows")
261
+ if not isinstance(windows, list):
262
+ return None
263
+ remaining: list[float] = []
264
+ for window in windows:
265
+ if not isinstance(window, dict):
266
+ continue
267
+ used = window.get("used_percent")
268
+ if isinstance(used, (int, float)) and not isinstance(used, bool):
269
+ remaining.append(max(0.0, min(100.0, 100.0 - float(used))))
270
+ return min(remaining) if remaining else None
271
+
272
+
273
+ def _github_rate_limit_status() -> dict[str, object]:
274
+ """Read GitHub quota state without changing GitHub or repository state."""
275
+ try:
276
+ payload = json.loads(GitHubProvider().github("api", "rate_limit"))
277
+ except (OSError, RuntimeError, json.JSONDecodeError) as error:
278
+ return {"limited": "rate limit" in str(error).lower()}
279
+ resources = payload.get("resources") if isinstance(payload, dict) else None
280
+ if not isinstance(resources, dict):
281
+ return {"limited": False}
282
+ exhausted: list[tuple[str, int]] = []
283
+ for name in ("core", "graphql", "search"):
284
+ resource = resources.get(name)
285
+ if not isinstance(resource, dict):
286
+ continue
287
+ remaining, reset = resource.get("remaining"), resource.get("reset")
288
+ if isinstance(remaining, int) and remaining <= 0:
289
+ exhausted.append((name, reset if isinstance(reset, int) else 0))
290
+ if not exhausted:
291
+ return {"limited": False}
292
+ reset_at = min((reset for _, reset in exhausted if reset > 0), default=None)
293
+ return {"limited": True, "reset_at": reset_at}
294
+
295
+
296
+ def _codex_rate_limits() -> bytes:
297
+ """Read quota through the Server-owned app-server protocol/cache."""
298
+ global _CODEX_RATE_LIMIT_CACHE
299
+ now = time.monotonic()
300
+ with _CODEX_RATE_LIMIT_CACHE_LOCK:
301
+ if _CODEX_RATE_LIMIT_CACHE and now - _CODEX_RATE_LIMIT_CACHE[0] < 60:
302
+ return _CODEX_RATE_LIMIT_CACHE[1]
303
+ identity = _codex_provider_identity()
304
+ provider = CodexCliProvider(); process = None
305
+ try:
306
+ process = provider.app_server()
307
+ if process.stdin is None or process.stdout is None:
308
+ return json.dumps(identity, separators=(",", ":")).encode()
309
+ process.stdin.write(json.dumps({"method": "initialize", "id": 1, "params": {"clientInfo": {"name": "engineering-platform-server", "title": "EP Operations", "version": _console_platform_version()}}}) + "\n")
310
+ process.stdin.flush(); deadline = time.monotonic() + 5; requested = False
311
+ while time.monotonic() < deadline:
312
+ ready, _, _ = select.select((process.stdout,), (), (), max(0, deadline - time.monotonic()))
313
+ if not ready: break
314
+ line = process.stdout.readline()
315
+ if not line: break
316
+ response = json.loads(line)
317
+ if response.get("id") == 1 and not requested:
318
+ process.stdin.write(json.dumps({"method": "initialized", "params": {}}) + "\n")
319
+ process.stdin.write(json.dumps({"method": "account/rateLimits/read", "id": 2, "params": {}}) + "\n")
320
+ process.stdin.flush(); requested = True
321
+ elif response.get("id") == 2:
322
+ encoded = json.dumps({**identity, **_normalize_rate_limits(response.get("result"))}, separators=(",", ":")).encode()
323
+ with _CODEX_RATE_LIMIT_CACHE_LOCK: _CODEX_RATE_LIMIT_CACHE = (time.monotonic(), encoded)
324
+ return encoded
325
+ except (OSError, ValueError, json.JSONDecodeError):
326
+ pass
327
+ finally:
328
+ if process is not None: provider.close_app_server(process)
329
+ return json.dumps(identity, separators=(",", ":")).encode()
330
+
331
+
332
+ def _normalize_rate_limits(payload: object) -> dict[str, object]:
333
+ """Keep only bounded display values from the read-only quota response."""
334
+ limits = payload.get("rateLimits") if isinstance(payload, dict) else None
335
+ if not isinstance(limits, dict): return {}
336
+ windows: list[dict[str, int | str]] = []
337
+ for key in ("primary", "secondary"):
338
+ item = limits.get(key)
339
+ if not isinstance(item, dict): continue
340
+ used, duration, resets = item.get("usedPercent"), item.get("windowDurationMins"), item.get("resetsAt")
341
+ if not isinstance(used, (int, float)) or isinstance(used, bool) or not isinstance(duration, int) or isinstance(duration, bool) or not isinstance(resets, int) or isinstance(resets, bool): continue
342
+ windows.append({"label": _rate_limit_window_label(duration), "used_percent": max(0, min(100, round(used))), "window_minutes": duration, "resets_at": resets})
343
+ credits = payload.get("rateLimitResetCredits"); available = credits.get("availableCount") if isinstance(credits, dict) else None
344
+ result: dict[str, object] = {"windows": windows}
345
+ if isinstance(available, int) and not isinstance(available, bool) and available >= 0: result["reset_credits"] = available
346
+ return result if windows or "reset_credits" in result else {}
347
+
348
+
349
+ def _rate_limit_window_label(duration: int) -> str:
350
+ if duration == 300: return "5-uursvenster"
351
+ if duration == 10_080: return "Weekvenster"
352
+ if duration % 1_440 == 0: return f"{duration // 1_440}-daags venster"
353
+ if duration % 60 == 0: return f"{duration // 60}-uursvenster"
354
+ return f"{duration}-minutenvenster"
355
+
356
+
357
+ def _codex_provider_identity() -> dict[str, str]:
358
+ """Return the managed Codex CLI identity without selecting PATH authority."""
359
+ global _CODEX_IDENTITY_CACHE
360
+ now = time.monotonic()
361
+ with _CODEX_IDENTITY_CACHE_LOCK:
362
+ if _CODEX_IDENTITY_CACHE and now - _CODEX_IDENTITY_CACHE[0] < 300:
363
+ return dict(_CODEX_IDENTITY_CACHE[1])
364
+ identity = {"provider": "Codex CLI", "provider_version": "versie niet beschikbaar"}
365
+ executable = CodexCliProvider()._executable
366
+ if executable:
367
+ candidate = Path(executable).expanduser()
368
+ if candidate.is_absolute(): identity["provider_path"] = str(candidate.parent.parent)
369
+ try: completed = LocalProcessProvider().execute(default_data_root(), (executable, "--version"))
370
+ except OSError: completed = None
371
+ if completed and completed.returncode == 0:
372
+ match = re.search(r"(?<!\d)(\d+\.\d+\.\d+)(?!\d)", (completed.stdout or completed.stderr).strip())
373
+ if match: identity["provider_version"] = match.group(1)
374
+ with _CODEX_IDENTITY_CACHE_LOCK: _CODEX_IDENTITY_CACHE = (now, identity)
375
+ return dict(identity)
376
+
377
+
378
+ def _start_provider_login(data_root: Path, provider: str) -> None:
379
+ """Dispatch one explicit host-wide provider login from the Server."""
380
+ commands = {
381
+ "CODEX": (CodexCliProvider()._executable, "login", "--device-auth"),
382
+ "GITHUB": ("gh", "auth", "login", "--hostname", "github.com", "--web"),
383
+ }
384
+ command = commands.get(provider)
385
+ if command is None:
386
+ raise ValueError("Unsupported provider login request.")
387
+ if provider == "CODEX" and not CodexCliProvider().status().qualified:
388
+ raise ValueError("Codex CLI is not installed.")
389
+ if provider == "GITHUB" and shutil.which("gh") is None:
390
+ raise ValueError("GitHub CLI is not installed.")
391
+ if sys.platform != "darwin":
392
+ raise ValueError("Interactive provider login is supported from the local macOS Server only.")
393
+ script = "\n".join((
394
+ 'tell application "Terminal"', "activate",
395
+ f"do script {json.dumps('exec ' + ' '.join(shlex.quote(part) for part in command))}",
396
+ "end tell",
397
+ ))
398
+ with _PROVIDER_LOGIN_LOCK:
399
+ completed = LocalProcessProvider().execute(data_root, ("/usr/bin/osascript", "-e", script))
400
+ if completed.returncode:
401
+ raise ValueError("Provider login window could not be opened.")
402
+
403
+
404
+ def _logout_provider(data_root: Path, provider: str) -> None:
405
+ """Remove one locally stored provider session without exposing credentials."""
406
+ if provider == "CODEX":
407
+ completed = CodexCliProvider().command("logout")
408
+ elif provider == "GITHUB":
409
+ process = LocalProcessProvider()
410
+ account = process.execute(data_root, ("gh", "api", "user", "--jq", ".login"))
411
+ username = account.stdout.strip()
412
+ if account.returncode or not username or not re.fullmatch(r"[A-Za-z0-9-]+", username):
413
+ raise ValueError("GitHub session cannot be safely identified for logout.")
414
+ completed = process.execute(data_root, ("gh", "auth", "logout", "--hostname", "github.com", "--user", username))
415
+ else:
416
+ raise ValueError("Unsupported provider logout request.")
417
+ if completed.returncode:
418
+ raise ValueError("Provider logout did not complete.")
419
+
420
+
421
+ def _central_execution_active(data_root: Path) -> bool:
422
+ """Read active lifecycle state from CENTRAL, never a checkout status file."""
423
+ with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection:
424
+ row = connection.execute(
425
+ "SELECT 1 FROM ep_parity_lifecycle_dispatches WHERE "
426
+ "state IN ('CLAIMED','RUNNING') OR (state IN ('BLOCKED','FAILED') "
427
+ "AND operator_resolution='OPEN') LIMIT 1"
428
+ ).fetchone()
429
+ return row is not None
430
+
431
+
432
+ def _choose_local_directory(data_root: Path) -> str | None:
433
+ """Use the host-native folder chooser only after an explicit Console action."""
434
+ if sys.platform != "darwin":
435
+ raise ValueError("LOCAL_DIRECTORY_PICKER_UNAVAILABLE")
436
+ result = LocalProcessProvider().execute(data_root, ("osascript", "-e", "POSIX path of (choose folder)"))
437
+ if result.returncode:
438
+ if "-128" in (result.stderr or ""):
439
+ return None
440
+ raise ValueError("LOCAL_DIRECTORY_PICKER_FAILED")
441
+ location = result.stdout.strip()
442
+ if not location or not Path(location).is_dir():
443
+ raise ValueError("LOCAL_DIRECTORY_PICKER_FAILED")
444
+ return location
445
+
446
+
447
+ def _install_provider(data_root: Path, provider: str) -> None:
448
+ """Install one provider only through the Server installation boundary."""
449
+ if not _PROVIDER_INSTALL_LOCK.acquire(blocking=False):
450
+ raise ValueError("Another provider installation is already in progress.")
451
+ try:
452
+ if _central_execution_active(data_root):
453
+ raise ValueError("Provider installation is unavailable while an execution is active.")
454
+ if provider == "CODEX":
455
+ try:
456
+ managed_codex_runtime.provision(data_root)
457
+ except managed_codex_runtime.ManagedCodexRuntimeError as error:
458
+ raise ValueError(str(error)) from error
459
+ key = "codex"
460
+ elif provider == "GITHUB":
461
+ brew = shutil.which("brew")
462
+ if brew is None:
463
+ raise ValueError("GitHub CLI installation requires Homebrew on this host.")
464
+ completed = LocalProcessProvider().execute(data_root, (brew, "install", "gh"))
465
+ verification = LocalProcessProvider().execute(data_root, ("gh", "--version"))
466
+ if completed.returncode or verification.returncode:
467
+ raise ValueError("Provider installation could not be verified.")
468
+ key = "github"
469
+ else:
470
+ raise ValueError("Unsupported provider installation request.")
471
+ if _central_provider_readiness(data_root).get(key, {}).get("state") == "UNAVAILABLE":
472
+ raise ValueError("Provider installation could not be verified.")
473
+ finally:
474
+ _PROVIDER_INSTALL_LOCK.release()
475
+
476
+
477
+ class ServerConfigurationError(ValueError):
478
+ """Raised when an installation-owned server configuration is invalid."""
479
+
480
+
481
+ SERVER_REQUIRED_TABLES = frozenset(
482
+ {
483
+ "engineering_schema_migrations",
484
+ "engineering_metadata",
485
+ "ep_installations",
486
+ "ep_control_provenance",
487
+ "ep_consumer_credentials",
488
+ "ep_consumer_registrations",
489
+ "ep_project_registrations",
490
+ "ep_execution_runs",
491
+ "ep_execution_leases",
492
+ "prompt_execution_history",
493
+ "ep_agent_registrations",
494
+ "ep_agent_pairing_codes",
495
+ "ep_repository_registrations",
496
+ "ep_agent_repository_attachments",
497
+ "ep_local_repository_bindings",
498
+ "ep_submissions",
499
+ "ep_submission_events",
500
+ "ep_submission_prompt_history",
501
+ "ep_parity_lifecycle_dispatches",
502
+ "ep_receipt_run_provenance",
503
+ "ep_external_producer_bindings",
504
+ "ep_external_producer_binding_audit",
505
+ "engineering_transactions",
506
+ "execution_lifecycle_events",
507
+ }
508
+ )
509
+ SERVER_REQUIRED_INDEXES = frozenset(
510
+ {
511
+ "ep_consumer_credentials_scope_lookup",
512
+ "ep_consumer_registrations_status_lookup",
513
+ "ep_project_registrations_status_lookup",
514
+ "ep_execution_runs_project_lookup",
515
+ "ep_control_provenance_subject_lookup",
516
+ "ep_repository_registrations_project_lookup",
517
+ "ep_agent_repository_attachments_repository_lookup",
518
+ "ep_local_repository_bindings_repository_lookup",
519
+ "ep_submissions_project_lookup",
520
+ "ep_submissions_idempotency_lookup",
521
+ "ep_parity_lifecycle_dispatches_run_lookup",
522
+ "ep_receipt_run_provenance_project_lookup",
523
+ "ep_external_producer_bindings_active_key",
524
+ }
525
+ )
526
+
527
+
528
+ @dataclass(frozen=True)
529
+ class ServerConfiguration:
530
+ version: int
531
+ bind_host: str
532
+ bind_port: int
533
+ managed_codex_cli_prefix: str
534
+
535
+ @classmethod
536
+ def load(cls, data_root: Path) -> "ServerConfiguration":
537
+ path = data_root / SERVER_CONFIGURATION_FILENAME
538
+ try:
539
+ raw = json.loads(path.read_text(encoding="utf-8"))
540
+ except (OSError, json.JSONDecodeError) as error:
541
+ raise ServerConfigurationError("EP Server configuration is unavailable.") from error
542
+ if not isinstance(raw, dict):
543
+ raise ServerConfigurationError("EP Server configuration is invalid.")
544
+ legacy_keys = {"version", "bind_host", "bind_port"}
545
+ current_keys = legacy_keys | {"managed_codex_cli_prefix"}
546
+ if set(raw) == legacy_keys and raw.get("version") == 1:
547
+ prefix = str(default_engineering_platform_codex_cli_prefix())
548
+ elif set(raw) == current_keys and raw.get("version") == SERVER_CONFIGURATION_VERSION:
549
+ prefix = raw.get("managed_codex_cli_prefix")
550
+ else:
551
+ raise ServerConfigurationError("EP Server configuration is invalid.")
552
+ candidate = Path(prefix).expanduser() if isinstance(prefix, str) else None
553
+ if (
554
+ not isinstance(raw["bind_host"], str)
555
+ or raw["bind_host"] != "127.0.0.1"
556
+ or not isinstance(raw["bind_port"], int)
557
+ or not 1 <= raw["bind_port"] <= 65535
558
+ or candidate is None
559
+ or not candidate.is_absolute()
560
+ ):
561
+ raise ServerConfigurationError("EP Server configuration is invalid.")
562
+ return cls(int(raw["version"]), raw["bind_host"], raw["bind_port"], str(candidate.resolve(strict=False)))
563
+
564
+
565
+ @dataclass(frozen=True)
566
+ class RuntimeIdentity:
567
+ instance_id: str
568
+ created_at: str
569
+
570
+
571
+ @dataclass(frozen=True)
572
+ class AgentRegistrationRequest:
573
+ """Transport-neutral future Agent registration input.
574
+
575
+ B3 deliberately does not define authentication, enrollment persistence,
576
+ project attachment, or any network representation for this request.
577
+ """
578
+
579
+ agent_id: str
580
+ agent_kind: str
581
+ capabilities: tuple[str, ...]
582
+
583
+
584
+ class AgentRegistrationIntake(Protocol):
585
+ """Future internal extension point; no transport/auth contract is implied."""
586
+
587
+ def accept(self, request: AgentRegistrationRequest) -> None: ...
588
+
589
+
590
+ def default_data_root() -> Path:
591
+ override = os.environ.get(SERVER_ENVIRONMENT_DATA_ROOT)
592
+ if override:
593
+ return Path(override).expanduser().resolve()
594
+ if sys.platform == "darwin":
595
+ return Path.home() / "Library" / "Application Support" / "Engineering Platform Server"
596
+ if os.name == "nt":
597
+ base = os.environ.get("LOCALAPPDATA") or os.environ.get("APPDATA")
598
+ return (Path(base) if base else Path.home() / "AppData" / "Local") / "Engineering Platform Server"
599
+ return Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share")) / "engineering-platform-server"
600
+
601
+
602
+ def _utcnow() -> str:
603
+ return datetime.now(timezone.utc).isoformat()
604
+
605
+
606
+ def _write_json(path: Path, payload: object) -> None:
607
+ path.write_text(json.dumps(payload, sort_keys=True, indent=2) + "\n", encoding="utf-8")
608
+ path.chmod(0o600)
609
+
610
+
611
+ def _table_names(connection: sqlite3.Connection) -> set[str]:
612
+ return {str(row[0]) for row in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")}
613
+
614
+
615
+ def _index_names(connection: sqlite3.Connection) -> set[str]:
616
+ return {str(row[0]) for row in connection.execute("SELECT name FROM sqlite_master WHERE type='index'")}
617
+
618
+
619
+ def _schema_version(connection: sqlite3.Connection) -> int:
620
+ if "engineering_schema_migrations" not in _table_names(connection):
621
+ return 0
622
+ row = connection.execute("SELECT MAX(version) FROM engineering_schema_migrations").fetchone()
623
+ return int(row[0]) if row and row[0] is not None else 0
624
+
625
+
626
+ def _install_schema_41(connection: sqlite3.Connection, identity: RuntimeIdentity) -> None:
627
+ """Install the clean standalone schema and immutable control provenance."""
628
+ for statement in (
629
+ "CREATE TABLE IF NOT EXISTS engineering_schema_migrations (version INTEGER PRIMARY KEY, applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP)",
630
+ "CREATE TABLE IF NOT EXISTS engineering_metadata (key TEXT PRIMARY KEY, value TEXT NOT NULL)",
631
+ "CREATE TABLE IF NOT EXISTS ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version=41))",
632
+ "CREATE TABLE IF NOT EXISTS ep_control_provenance (event_id INTEGER PRIMARY KEY, event_kind TEXT NOT NULL CHECK(event_kind IN ('INSTALLATION_CREATED','CREDENTIAL_LIFECYCLE','CONSUMER_REGISTRATION','PROJECT_SCOPE_MUTATION')), subject_kind TEXT NOT NULL, subject_id TEXT NOT NULL, payload TEXT NOT NULL, recorded_at TEXT NOT NULL)",
633
+ "CREATE INDEX IF NOT EXISTS ep_control_provenance_subject_lookup ON ep_control_provenance(subject_kind,subject_id,event_id DESC)",
634
+ "CREATE TABLE IF NOT EXISTS ep_consumer_credentials (credential_id TEXT PRIMARY KEY CHECK(length(credential_id) BETWEEN 1 AND 128), consumer_id TEXT NOT NULL CHECK(length(consumer_id) BETWEEN 1 AND 128), project_id TEXT NOT NULL CHECK(length(project_id) BETWEEN 1 AND 128), verifier BLOB NOT NULL UNIQUE CHECK(length(verifier)=32), fingerprint BLOB NOT NULL UNIQUE CHECK(length(fingerprint)=32), issued_at TEXT NOT NULL, expires_at TEXT, revoked_at TEXT, replaced_by_credential_id TEXT REFERENCES ep_consumer_credentials(credential_id))",
635
+ "CREATE INDEX IF NOT EXISTS ep_consumer_credentials_scope_lookup ON ep_consumer_credentials(consumer_id,project_id,revoked_at)",
636
+ "CREATE TABLE IF NOT EXISTS ep_consumer_registrations (consumer_id TEXT NOT NULL CHECK(length(consumer_id) BETWEEN 1 AND 128), project_id TEXT NOT NULL CHECK(length(project_id) BETWEEN 1 AND 128), status TEXT NOT NULL CHECK(status IN ('ACTIVE','DISABLED','REVOKED')), created_at TEXT NOT NULL, updated_at TEXT NOT NULL, disabled_at TEXT, revoked_at TEXT, audit_metadata TEXT NOT NULL DEFAULT '{}', PRIMARY KEY(consumer_id,project_id))",
637
+ "CREATE INDEX IF NOT EXISTS ep_consumer_registrations_status_lookup ON ep_consumer_registrations(consumer_id,project_id,status)",
638
+ "CREATE TABLE IF NOT EXISTS ep_project_registrations (project_id TEXT PRIMARY KEY CHECK(length(project_id) BETWEEN 1 AND 128), attachment_contract TEXT NOT NULL, status TEXT NOT NULL CHECK(status IN ('ACTIVE','DISABLED','REVOKED')), created_at TEXT NOT NULL, updated_at TEXT NOT NULL)",
639
+ "CREATE INDEX IF NOT EXISTS ep_project_registrations_status_lookup ON ep_project_registrations(status,project_id)",
640
+ "CREATE TABLE IF NOT EXISTS ep_execution_runs (run_id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES ep_project_registrations(project_id), state TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)",
641
+ "CREATE INDEX IF NOT EXISTS ep_execution_runs_project_lookup ON ep_execution_runs(project_id,state,created_at DESC)",
642
+ "CREATE TABLE IF NOT EXISTS ep_execution_leases (lease_id TEXT PRIMARY KEY, run_id TEXT NOT NULL REFERENCES ep_execution_runs(run_id), holder_id TEXT NOT NULL, acquired_at TEXT NOT NULL, expires_at TEXT NOT NULL, released_at TEXT)",
643
+ "CREATE TABLE IF NOT EXISTS prompt_execution_history (run_id TEXT PRIMARY KEY REFERENCES ep_execution_runs(run_id), prompt_digest TEXT NOT NULL, recorded_at TEXT NOT NULL)",
644
+ ):
645
+ connection.execute(statement)
646
+ agent_trust.install_schema(connection)
647
+ connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(41)")
648
+ connection.execute("INSERT OR IGNORE INTO engineering_metadata(key,value) VALUES('installation.instance_id',?)", (identity.instance_id,))
649
+ connection.execute("INSERT OR IGNORE INTO engineering_metadata(key,value) VALUES('installation.schema_version','41')")
650
+ connection.execute("INSERT OR IGNORE INTO ep_installations(instance_id,created_at,schema_version) VALUES(?,?,41)", (identity.instance_id, identity.created_at))
651
+ connection.execute("INSERT OR IGNORE INTO ep_control_provenance(event_kind,subject_kind,subject_id,payload,recorded_at) VALUES('INSTALLATION_CREATED','installation',?,?,?)", (identity.instance_id, json.dumps({'schema_version': 41}, sort_keys=True), identity.created_at))
652
+ for table in ("ep_control_provenance",):
653
+ for operation in ("UPDATE", "DELETE"):
654
+ connection.execute(f"CREATE TRIGGER IF NOT EXISTS {table}_immutable_{operation.casefold()} BEFORE {operation} ON {table} BEGIN SELECT RAISE(ABORT, '{table} evidence is immutable.'); END")
655
+
656
+
657
+ def _migrate_schema_42(connection: sqlite3.Connection) -> None:
658
+ """Forward-only topology extension; schema-41 structures remain intact."""
659
+ # Schema 41 deliberately constrained the bootstrap record to 41. Preserve
660
+ # its row while widening that bootstrap-only constraint for official
661
+ # forward migrations; no operational rows are rewritten.
662
+ connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema41")
663
+ connection.execute("CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42)))")
664
+ connection.execute("INSERT INTO ep_installations(instance_id,created_at,schema_version) SELECT instance_id,created_at,42 FROM ep_installations_schema41")
665
+ connection.execute("DROP TABLE ep_installations_schema41")
666
+ project_topology.install_schema(connection)
667
+ connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(42)")
668
+ connection.execute("UPDATE engineering_metadata SET value='42' WHERE key='installation.schema_version'")
669
+ connection.execute("UPDATE ep_installations SET schema_version=42")
670
+
671
+
672
+ def _migrate_schema_43(connection: sqlite3.Connection) -> None:
673
+ """Add CENTRAL-owned canonical submission persistence.
674
+
675
+ This is deliberately a forward migration from schema 42; historical
676
+ schema-40 execution databases are neither inspected nor imported.
677
+ """
678
+ connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema42")
679
+ connection.execute("CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43)))")
680
+ connection.execute("INSERT INTO ep_installations(instance_id,created_at,schema_version) SELECT instance_id,created_at,43 FROM ep_installations_schema42")
681
+ connection.execute("DROP TABLE ep_installations_schema42")
682
+ connection.execute("""CREATE TABLE ep_submissions (
683
+ submission_id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES ep_project_registrations(project_id),
684
+ repository_id TEXT NOT NULL REFERENCES ep_repository_registrations(repository_id),
685
+ producer_id TEXT NOT NULL, producer_type TEXT NOT NULL, producer_version TEXT,
686
+ transport TEXT NOT NULL CHECK(transport IN ('HTTP','CLI','FILE_INBOX','LEGACY_FILE')),
687
+ prompt TEXT NOT NULL, prompt_digest TEXT NOT NULL, constraints TEXT NOT NULL,
688
+ idempotency_key TEXT, correlation_id TEXT, mission_id TEXT, engineering_action_id TEXT,
689
+ state TEXT NOT NULL CHECK(state IN ('QUEUED','REJECTED')), admission TEXT NOT NULL,
690
+ created_at TEXT NOT NULL)""")
691
+ connection.execute("CREATE INDEX ep_submissions_project_lookup ON ep_submissions(project_id,state,created_at DESC)")
692
+ connection.execute("CREATE UNIQUE INDEX ep_submissions_idempotency_lookup ON ep_submissions(project_id,idempotency_key) WHERE idempotency_key IS NOT NULL")
693
+ connection.execute("CREATE TABLE ep_submission_events (event_id INTEGER PRIMARY KEY, submission_id TEXT NOT NULL REFERENCES ep_submissions(submission_id), event_kind TEXT NOT NULL, payload TEXT NOT NULL, recorded_at TEXT NOT NULL)")
694
+ connection.execute("CREATE TABLE ep_submission_prompt_history (submission_id TEXT PRIMARY KEY REFERENCES ep_submissions(submission_id), prompt_digest TEXT NOT NULL, recorded_at TEXT NOT NULL)")
695
+ connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(43)")
696
+ connection.execute("UPDATE engineering_metadata SET value='43' WHERE key='installation.schema_version'")
697
+ connection.execute("UPDATE ep_installations SET schema_version=43")
698
+
699
+
700
+ def _migrate_schema_44(connection: sqlite3.Connection) -> None:
701
+ """Add the private, explicit Phase-P local checkout binding surface."""
702
+ connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema43")
703
+ connection.execute("CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43,44)))")
704
+ connection.execute("INSERT INTO ep_installations(instance_id,created_at,schema_version) SELECT instance_id,created_at,44 FROM ep_installations_schema43")
705
+ connection.execute("DROP TABLE ep_installations_schema43")
706
+ local_repository_binding.install_schema(connection)
707
+ connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(44)")
708
+ connection.execute("UPDATE engineering_metadata SET value='44' WHERE key='installation.schema_version'")
709
+ connection.execute("UPDATE ep_installations SET schema_version=44")
710
+
711
+
712
+ def _migrate_schema_45(connection: sqlite3.Connection) -> None:
713
+ """Add the single-writer CENTRAL-to-historical lifecycle association."""
714
+ connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema44")
715
+ connection.execute("CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43,44,45)))")
716
+ connection.execute("INSERT INTO ep_installations(instance_id,created_at,schema_version) SELECT instance_id,created_at,45 FROM ep_installations_schema44")
717
+ connection.execute("DROP TABLE ep_installations_schema44")
718
+ connection.execute("""CREATE TABLE ep_parity_lifecycle_dispatches (
719
+ submission_id TEXT PRIMARY KEY REFERENCES ep_submissions(submission_id),
720
+ project_id TEXT NOT NULL REFERENCES ep_project_registrations(project_id),
721
+ repository_id TEXT NOT NULL REFERENCES ep_repository_registrations(repository_id),
722
+ run_id TEXT NOT NULL UNIQUE REFERENCES ep_execution_runs(run_id),
723
+ state TEXT NOT NULL CHECK(state IN ('CLAIMED','RUNNING','COMPLETE','BLOCKED','FAILED')),
724
+ prompt_path TEXT NOT NULL,
725
+ claimed_at TEXT NOT NULL,
726
+ updated_at TEXT NOT NULL
727
+ )""")
728
+ connection.execute("CREATE INDEX ep_parity_lifecycle_dispatches_run_lookup ON ep_parity_lifecycle_dispatches(run_id,state)")
729
+ connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(45)")
730
+ connection.execute("UPDATE engineering_metadata SET value='45' WHERE key='installation.schema_version'")
731
+ connection.execute("UPDATE ep_installations SET schema_version=45")
732
+
733
+
734
+ def _migrate_schema_46(connection: sqlite3.Connection) -> None:
735
+ """Persist the admitted execution mode with the CENTRAL run.
736
+
737
+ The mode is decided before a submission is claimed. It is therefore run
738
+ evidence, rather than a presentation value to be rediscovered from a
739
+ mutable prompt or a repository-local telemetry row.
740
+ """
741
+ connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema45")
742
+ connection.execute("CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43,44,45,46)))")
743
+ connection.execute("INSERT INTO ep_installations(instance_id,created_at,schema_version) SELECT instance_id,created_at,46 FROM ep_installations_schema45")
744
+ connection.execute("DROP TABLE ep_installations_schema45")
745
+ # Existing CENTRAL runs predate this evidence field. Keep them NULL so
746
+ # the Console accurately reports that their mode was not recorded, rather
747
+ # than silently inventing MANAGED during migration.
748
+ connection.execute("ALTER TABLE ep_execution_runs ADD COLUMN execution_mode TEXT CHECK(execution_mode IN ('MANAGED','GENESIS'))")
749
+ connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(46)")
750
+ connection.execute("UPDATE engineering_metadata SET value='46' WHERE key='installation.schema_version'")
751
+ connection.execute("UPDATE ep_installations SET schema_version=46")
752
+
753
+
754
+ def _migrate_schema_47(connection: sqlite3.Connection) -> None:
755
+ """Keep failed project runs FIFO-blocking until CENTRAL records a resolution."""
756
+ connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema46")
757
+ connection.execute("CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43,44,45,46,47)))")
758
+ connection.execute("INSERT INTO ep_installations(instance_id,created_at,schema_version) SELECT instance_id,created_at,47 FROM ep_installations_schema46")
759
+ connection.execute("DROP TABLE ep_installations_schema46")
760
+ connection.execute("ALTER TABLE ep_parity_lifecycle_dispatches ADD COLUMN operator_resolution TEXT NOT NULL DEFAULT 'NONE' CHECK(operator_resolution IN ('NONE','OPEN','DISMISSED','RETRIED'))")
761
+ connection.execute("ALTER TABLE ep_parity_lifecycle_dispatches ADD COLUMN resolution_submission_id TEXT REFERENCES ep_submissions(submission_id)")
762
+ connection.execute("UPDATE ep_parity_lifecycle_dispatches SET operator_resolution='OPEN' WHERE state IN ('BLOCKED','FAILED')")
763
+ connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(47)")
764
+ connection.execute("UPDATE engineering_metadata SET value='47' WHERE key='installation.schema_version'")
765
+ connection.execute("UPDATE ep_installations SET schema_version=47")
766
+
767
+
768
+ def _migrate_schema_48(connection: sqlite3.Connection) -> None:
769
+ """Move retained lifecycle persistence into the one CENTRAL database.
770
+
771
+ No repository is opened or scanned. These are empty compatibility tables
772
+ for new standalone runs while the preserved runner is being invoked via
773
+ the explicit CENTRAL operational context.
774
+ """
775
+ connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema47")
776
+ connection.execute(
777
+ "CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, "
778
+ "schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43,44,45,46,47,48)))"
779
+ )
780
+ connection.execute(
781
+ "INSERT INTO ep_installations(instance_id,created_at,schema_version) "
782
+ "SELECT instance_id,created_at,48 FROM ep_installations_schema47"
783
+ )
784
+ connection.execute("DROP TABLE ep_installations_schema47")
785
+ storage.install_central_operational_compatibility_schema(connection)
786
+ connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(48)")
787
+ connection.execute("UPDATE engineering_metadata SET value='48' WHERE key='installation.schema_version'")
788
+ connection.execute("UPDATE ep_installations SET schema_version=48")
789
+
790
+
791
+ def _migrate_schema_49(connection: sqlite3.Connection) -> None:
792
+ """Record bounded ingress receipts in the canonical submission row."""
793
+ connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema48")
794
+ connection.execute(
795
+ "CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, "
796
+ "schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43,44,45,46,47,48,49)))"
797
+ )
798
+ connection.execute(
799
+ "INSERT INTO ep_installations(instance_id,created_at,schema_version) "
800
+ "SELECT instance_id,created_at,49 FROM ep_installations_schema48"
801
+ )
802
+ connection.execute("DROP TABLE ep_installations_schema48")
803
+ # SQLite cannot widen the schema-43 transport CHECK in place. Rebuild the
804
+ # parent table while foreign-key enforcement is temporarily disabled by
805
+ # the caller; SQLite keeps dependent references pointed at its canonical
806
+ # name. No submission facts are rewritten or inferred.
807
+ # Child tables must be rebuilt too: SQLite otherwise retains a foreign-key
808
+ # reference to the renamed historical parent table.
809
+ connection.execute("ALTER TABLE ep_submission_events RENAME TO ep_submission_events_schema48")
810
+ connection.execute("ALTER TABLE ep_submission_prompt_history RENAME TO ep_submission_prompt_history_schema48")
811
+ connection.execute("ALTER TABLE ep_parity_lifecycle_dispatches RENAME TO ep_parity_lifecycle_dispatches_schema48")
812
+ connection.execute("ALTER TABLE ep_submissions RENAME TO ep_submissions_schema48")
813
+ connection.execute("""CREATE TABLE ep_submissions (
814
+ submission_id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES ep_project_registrations(project_id),
815
+ repository_id TEXT NOT NULL REFERENCES ep_repository_registrations(repository_id),
816
+ producer_id TEXT NOT NULL, producer_type TEXT NOT NULL, producer_version TEXT,
817
+ transport TEXT NOT NULL CHECK(transport IN ('HTTP','CLI','FILE_INBOX','LEGACY_FILE')),
818
+ prompt TEXT NOT NULL, prompt_digest TEXT NOT NULL, constraints TEXT NOT NULL,
819
+ idempotency_key TEXT, correlation_id TEXT, mission_id TEXT, engineering_action_id TEXT,
820
+ transport_receipt_id TEXT, transport_received_at TEXT,
821
+ state TEXT NOT NULL CHECK(state IN ('QUEUED','REJECTED')), admission TEXT NOT NULL,
822
+ created_at TEXT NOT NULL)""")
823
+ connection.execute("""INSERT INTO ep_submissions(
824
+ submission_id,project_id,repository_id,producer_id,producer_type,producer_version,transport,prompt,prompt_digest,constraints,idempotency_key,correlation_id,mission_id,engineering_action_id,state,admission,created_at)
825
+ SELECT submission_id,project_id,repository_id,producer_id,producer_type,producer_version,transport,prompt,prompt_digest,constraints,idempotency_key,correlation_id,mission_id,engineering_action_id,state,admission,created_at
826
+ FROM ep_submissions_schema48""")
827
+ connection.execute("""CREATE TABLE ep_submission_events (
828
+ event_id INTEGER PRIMARY KEY, submission_id TEXT NOT NULL REFERENCES ep_submissions(submission_id),
829
+ event_kind TEXT NOT NULL, payload TEXT NOT NULL, recorded_at TEXT NOT NULL)""")
830
+ connection.execute("""INSERT INTO ep_submission_events(event_id,submission_id,event_kind,payload,recorded_at)
831
+ SELECT event_id,submission_id,event_kind,payload,recorded_at FROM ep_submission_events_schema48""")
832
+ connection.execute("""CREATE TABLE ep_submission_prompt_history (
833
+ submission_id TEXT PRIMARY KEY REFERENCES ep_submissions(submission_id), prompt_digest TEXT NOT NULL,
834
+ recorded_at TEXT NOT NULL)""")
835
+ connection.execute("""INSERT INTO ep_submission_prompt_history(submission_id,prompt_digest,recorded_at)
836
+ SELECT submission_id,prompt_digest,recorded_at FROM ep_submission_prompt_history_schema48""")
837
+ connection.execute("""CREATE TABLE ep_parity_lifecycle_dispatches (
838
+ submission_id TEXT PRIMARY KEY REFERENCES ep_submissions(submission_id),
839
+ project_id TEXT NOT NULL REFERENCES ep_project_registrations(project_id),
840
+ repository_id TEXT NOT NULL REFERENCES ep_repository_registrations(repository_id),
841
+ run_id TEXT NOT NULL UNIQUE REFERENCES ep_execution_runs(run_id),
842
+ state TEXT NOT NULL CHECK(state IN ('CLAIMED','RUNNING','COMPLETE','BLOCKED','FAILED')),
843
+ prompt_path TEXT NOT NULL, claimed_at TEXT NOT NULL, updated_at TEXT NOT NULL,
844
+ operator_resolution TEXT NOT NULL DEFAULT 'NONE' CHECK(operator_resolution IN ('NONE','OPEN','DISMISSED','RETRIED')),
845
+ resolution_submission_id TEXT REFERENCES ep_submissions(submission_id))""")
846
+ connection.execute("""INSERT INTO ep_parity_lifecycle_dispatches(
847
+ submission_id,project_id,repository_id,run_id,state,prompt_path,claimed_at,updated_at,operator_resolution,resolution_submission_id)
848
+ SELECT submission_id,project_id,repository_id,run_id,state,prompt_path,claimed_at,updated_at,operator_resolution,resolution_submission_id
849
+ FROM ep_parity_lifecycle_dispatches_schema48""")
850
+ connection.execute("DROP TABLE ep_submission_events_schema48")
851
+ connection.execute("DROP TABLE ep_submission_prompt_history_schema48")
852
+ connection.execute("DROP TABLE ep_parity_lifecycle_dispatches_schema48")
853
+ connection.execute("DROP TABLE ep_submissions_schema48")
854
+ connection.execute("CREATE INDEX ep_submissions_project_lookup ON ep_submissions(project_id,state,created_at DESC)")
855
+ connection.execute("CREATE UNIQUE INDEX ep_submissions_idempotency_lookup ON ep_submissions(project_id,idempotency_key) WHERE idempotency_key IS NOT NULL")
856
+ connection.execute("CREATE INDEX ep_parity_lifecycle_dispatches_run_lookup ON ep_parity_lifecycle_dispatches(run_id,state)")
857
+ connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(49)")
858
+ connection.execute("UPDATE engineering_metadata SET value='49' WHERE key='installation.schema_version'")
859
+ connection.execute("UPDATE ep_installations SET schema_version=49")
860
+
861
+
862
+ def _migrate_schema_50(connection: sqlite3.Connection) -> None:
863
+ """Add CENTRAL-owned external producer bindings and immutable audit evidence."""
864
+ connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema49")
865
+ connection.execute("CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43,44,45,46,47,48,49,50)))")
866
+ connection.execute("INSERT INTO ep_installations(instance_id,created_at,schema_version) SELECT instance_id,created_at,50 FROM ep_installations_schema49")
867
+ connection.execute("DROP TABLE ep_installations_schema49")
868
+ connection.execute("""CREATE TABLE ep_external_producer_bindings (
869
+ binding_id TEXT PRIMARY KEY, producer_type TEXT NOT NULL, external_resource_type TEXT NOT NULL,
870
+ external_resource_identity TEXT NOT NULL, project_id TEXT NOT NULL REFERENCES ep_project_registrations(project_id),
871
+ repository_id TEXT NOT NULL REFERENCES ep_repository_registrations(repository_id), status TEXT NOT NULL,
872
+ version INTEGER NOT NULL, created_at TEXT NOT NULL, created_by TEXT NOT NULL, updated_at TEXT NOT NULL,
873
+ provenance TEXT NOT NULL)""")
874
+ connection.execute("CREATE UNIQUE INDEX ep_external_producer_bindings_active_key ON ep_external_producer_bindings(producer_type,external_resource_type,external_resource_identity) WHERE status='ACTIVE'")
875
+ connection.execute("""CREATE TABLE ep_external_producer_binding_audit (
876
+ audit_id INTEGER PRIMARY KEY, binding_id TEXT NOT NULL, action TEXT NOT NULL, actor TEXT NOT NULL,
877
+ reason TEXT NOT NULL, payload TEXT NOT NULL, recorded_at TEXT NOT NULL)""")
878
+ connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(50)")
879
+ connection.execute("UPDATE engineering_metadata SET value='50' WHERE key='installation.schema_version'")
880
+ connection.execute("UPDATE ep_installations SET schema_version=50")
881
+
882
+
883
+ def _migrate_schema_51(connection: sqlite3.Connection) -> None:
884
+ """Add the explicit Server-owned Dependabot transport value.
885
+
886
+ The producer is a bounded internal adapter, not an HTTP caller and not a
887
+ File Inbox delivery. SQLite requires the durable submission constraint to
888
+ be rebuilt to record that distinction truthfully.
889
+ """
890
+ connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema50")
891
+ connection.execute("CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43,44,45,46,47,48,49,50,51)))")
892
+ connection.execute("INSERT INTO ep_installations(instance_id,created_at,schema_version) SELECT instance_id,created_at,51 FROM ep_installations_schema50")
893
+ connection.execute("DROP TABLE ep_installations_schema50")
894
+ connection.execute("ALTER TABLE ep_submission_events RENAME TO ep_submission_events_schema50")
895
+ connection.execute("ALTER TABLE ep_submission_prompt_history RENAME TO ep_submission_prompt_history_schema50")
896
+ connection.execute("ALTER TABLE ep_parity_lifecycle_dispatches RENAME TO ep_parity_lifecycle_dispatches_schema50")
897
+ connection.execute("ALTER TABLE ep_submissions RENAME TO ep_submissions_schema50")
898
+ connection.execute("""CREATE TABLE ep_submissions (
899
+ submission_id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES ep_project_registrations(project_id),
900
+ repository_id TEXT NOT NULL REFERENCES ep_repository_registrations(repository_id),
901
+ producer_id TEXT NOT NULL, producer_type TEXT NOT NULL, producer_version TEXT,
902
+ transport TEXT NOT NULL CHECK(transport IN ('HTTP','CLI','FILE_INBOX','DEPENDABOT','LEGACY_FILE')),
903
+ prompt TEXT NOT NULL, prompt_digest TEXT NOT NULL, constraints TEXT NOT NULL,
904
+ idempotency_key TEXT, correlation_id TEXT, mission_id TEXT, engineering_action_id TEXT,
905
+ transport_receipt_id TEXT, transport_received_at TEXT,
906
+ state TEXT NOT NULL CHECK(state IN ('QUEUED','REJECTED')), admission TEXT NOT NULL,
907
+ created_at TEXT NOT NULL)""")
908
+ connection.execute("""INSERT INTO ep_submissions(
909
+ submission_id,project_id,repository_id,producer_id,producer_type,producer_version,transport,prompt,prompt_digest,constraints,idempotency_key,correlation_id,mission_id,engineering_action_id,transport_receipt_id,transport_received_at,state,admission,created_at)
910
+ SELECT submission_id,project_id,repository_id,producer_id,producer_type,producer_version,transport,prompt,prompt_digest,constraints,idempotency_key,correlation_id,mission_id,engineering_action_id,transport_receipt_id,transport_received_at,state,admission,created_at
911
+ FROM ep_submissions_schema50""")
912
+ connection.execute("""CREATE TABLE ep_submission_events (
913
+ event_id INTEGER PRIMARY KEY, submission_id TEXT NOT NULL REFERENCES ep_submissions(submission_id),
914
+ event_kind TEXT NOT NULL, payload TEXT NOT NULL, recorded_at TEXT NOT NULL)""")
915
+ connection.execute("""INSERT INTO ep_submission_events(event_id,submission_id,event_kind,payload,recorded_at)
916
+ SELECT event_id,submission_id,event_kind,payload,recorded_at FROM ep_submission_events_schema50""")
917
+ connection.execute("""CREATE TABLE ep_submission_prompt_history (
918
+ submission_id TEXT PRIMARY KEY REFERENCES ep_submissions(submission_id), prompt_digest TEXT NOT NULL,
919
+ recorded_at TEXT NOT NULL)""")
920
+ connection.execute("""INSERT INTO ep_submission_prompt_history(submission_id,prompt_digest,recorded_at)
921
+ SELECT submission_id,prompt_digest,recorded_at FROM ep_submission_prompt_history_schema50""")
922
+ connection.execute("""CREATE TABLE ep_parity_lifecycle_dispatches (
923
+ submission_id TEXT PRIMARY KEY REFERENCES ep_submissions(submission_id),
924
+ project_id TEXT NOT NULL REFERENCES ep_project_registrations(project_id),
925
+ repository_id TEXT NOT NULL REFERENCES ep_repository_registrations(repository_id),
926
+ run_id TEXT NOT NULL UNIQUE REFERENCES ep_execution_runs(run_id),
927
+ state TEXT NOT NULL CHECK(state IN ('CLAIMED','RUNNING','COMPLETE','BLOCKED','FAILED')),
928
+ prompt_path TEXT NOT NULL, claimed_at TEXT NOT NULL, updated_at TEXT NOT NULL,
929
+ operator_resolution TEXT NOT NULL DEFAULT 'NONE' CHECK(operator_resolution IN ('NONE','OPEN','DISMISSED','RETRIED')),
930
+ resolution_submission_id TEXT REFERENCES ep_submissions(submission_id))""")
931
+ connection.execute("""INSERT INTO ep_parity_lifecycle_dispatches(
932
+ submission_id,project_id,repository_id,run_id,state,prompt_path,claimed_at,updated_at,operator_resolution,resolution_submission_id)
933
+ SELECT submission_id,project_id,repository_id,run_id,state,prompt_path,claimed_at,updated_at,operator_resolution,resolution_submission_id
934
+ FROM ep_parity_lifecycle_dispatches_schema50""")
935
+ for table in ("ep_submission_events_schema50", "ep_submission_prompt_history_schema50", "ep_parity_lifecycle_dispatches_schema50", "ep_submissions_schema50"):
936
+ connection.execute(f"DROP TABLE {table}")
937
+ connection.execute("CREATE INDEX ep_submissions_project_lookup ON ep_submissions(project_id,state,created_at DESC)")
938
+ connection.execute("CREATE UNIQUE INDEX ep_submissions_idempotency_lookup ON ep_submissions(project_id,idempotency_key) WHERE idempotency_key IS NOT NULL")
939
+ connection.execute("CREATE INDEX ep_parity_lifecycle_dispatches_run_lookup ON ep_parity_lifecycle_dispatches(run_id,state)")
940
+ connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(51)")
941
+ connection.execute("UPDATE engineering_metadata SET value='51' WHERE key='installation.schema_version'")
942
+ connection.execute("UPDATE ep_installations SET schema_version=51")
943
+
944
+
945
+ def _migrate_schema_52(connection: sqlite3.Connection) -> None:
946
+ """Add the sole immutable CENTRAL receipt-to-run provenance authority."""
947
+ connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema51")
948
+ connection.execute("CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43,44,45,46,47,48,49,50,51,52)))")
949
+ connection.execute("INSERT INTO ep_installations(instance_id,created_at,schema_version) SELECT instance_id,created_at,52 FROM ep_installations_schema51")
950
+ connection.execute("DROP TABLE ep_installations_schema51")
951
+ connection.execute("""CREATE TABLE ep_receipt_run_provenance (
952
+ submission_id TEXT PRIMARY KEY REFERENCES ep_submissions(submission_id),
953
+ run_id TEXT NOT NULL UNIQUE REFERENCES ep_execution_runs(run_id),
954
+ project_id TEXT NOT NULL REFERENCES ep_project_registrations(project_id),
955
+ repository_id TEXT NOT NULL REFERENCES ep_repository_registrations(repository_id),
956
+ installation_id TEXT NOT NULL REFERENCES ep_installations(instance_id),
957
+ created_at TEXT NOT NULL
958
+ )""")
959
+ connection.execute("CREATE INDEX ep_receipt_run_provenance_project_lookup ON ep_receipt_run_provenance(project_id,created_at DESC)")
960
+ connection.execute("""CREATE TRIGGER ep_receipt_run_provenance_scope_insert
961
+ BEFORE INSERT ON ep_receipt_run_provenance BEGIN
962
+ SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM ep_submissions s WHERE s.submission_id=NEW.submission_id AND s.project_id=NEW.project_id AND s.repository_id=NEW.repository_id) THEN RAISE(ABORT,'PROVENANCE_SUBMISSION_SCOPE_MISMATCH') END;
963
+ SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM ep_execution_runs r WHERE r.run_id=NEW.run_id AND r.project_id=NEW.project_id) THEN RAISE(ABORT,'PROVENANCE_RUN_PROJECT_MISMATCH') END;
964
+ SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM ep_parity_lifecycle_dispatches d WHERE d.run_id=NEW.run_id AND d.submission_id=NEW.submission_id AND d.project_id=NEW.project_id AND d.repository_id=NEW.repository_id) THEN RAISE(ABORT,'PROVENANCE_DISPATCH_SCOPE_MISMATCH') END;
965
+ SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM engineering_metadata WHERE key='installation.instance_id' AND value=NEW.installation_id) THEN RAISE(ABORT,'PROVENANCE_INSTALLATION_MISMATCH') END;
966
+ END""")
967
+ for operation in ("UPDATE", "DELETE"):
968
+ connection.execute(f"CREATE TRIGGER ep_receipt_run_provenance_immutable_{operation.casefold()} BEFORE {operation} ON ep_receipt_run_provenance BEGIN SELECT RAISE(ABORT,'PROVENANCE_IMMUTABLE'); END")
969
+ # Backfill only rows whose canonical dispatch already proves every scope.
970
+ connection.execute("""INSERT INTO ep_receipt_run_provenance(submission_id,run_id,project_id,repository_id,installation_id,created_at)
971
+ SELECT d.submission_id,d.run_id,d.project_id,d.repository_id,m.value,d.claimed_at
972
+ FROM ep_parity_lifecycle_dispatches d JOIN ep_submissions s ON s.submission_id=d.submission_id AND s.project_id=d.project_id AND s.repository_id=d.repository_id
973
+ JOIN ep_execution_runs r ON r.run_id=d.run_id AND r.project_id=d.project_id
974
+ JOIN engineering_metadata m ON m.key='installation.instance_id'""")
975
+ missing = connection.execute(
976
+ """SELECT 1 FROM ep_parity_lifecycle_dispatches d
977
+ WHERE NOT EXISTS (
978
+ SELECT 1 FROM ep_receipt_run_provenance p
979
+ WHERE p.submission_id=d.submission_id AND p.run_id=d.run_id
980
+ AND p.project_id=d.project_id AND p.repository_id=d.repository_id
981
+ ) LIMIT 1"""
982
+ ).fetchone()
983
+ if missing is not None:
984
+ raise ServerConfigurationError("CENTRAL receipt-to-run provenance migration is incomplete.")
985
+ connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(52)")
986
+ connection.execute("UPDATE engineering_metadata SET value='52' WHERE key='installation.schema_version'")
987
+ connection.execute("UPDATE ep_installations SET schema_version=52")
988
+
989
+
990
+ _CONSUMER_CREDENTIAL_COLUMNS = (
991
+ "credential_id", "consumer_id", "project_id", "verifier", "fingerprint",
992
+ "issued_at", "expires_at", "revoked_at", "replaced_by_credential_id",
993
+ )
994
+ _CONSUMER_REGISTRATION_COLUMNS = (
995
+ "consumer_id", "project_id", "status", "created_at", "updated_at",
996
+ "disabled_at", "revoked_at", "audit_metadata",
997
+ )
998
+
999
+
1000
+ def _table_columns(connection: sqlite3.Connection, table: str) -> tuple[str, ...]:
1001
+ return tuple(str(row[1]) for row in connection.execute(f"PRAGMA table_info({table})"))
1002
+
1003
+
1004
+ def _require_consumer_table_shape(
1005
+ connection: sqlite3.Connection, *, credentials: str, registrations: str,
1006
+ ) -> None:
1007
+ """Validate only table metadata; never surface credential values."""
1008
+
1009
+ if _table_columns(connection, credentials) != _CONSUMER_CREDENTIAL_COLUMNS:
1010
+ raise ServerConfigurationError("EP consumer credential table shape is invalid.")
1011
+ if _table_columns(connection, registrations) != _CONSUMER_REGISTRATION_COLUMNS:
1012
+ raise ServerConfigurationError("EP consumer registration table shape is invalid.")
1013
+ credential_pk = tuple(
1014
+ str(row[1]) for row in connection.execute(f"PRAGMA table_info({credentials})") if int(row[5]) > 0
1015
+ )
1016
+ registration_pk = tuple(
1017
+ str(row[1]) for row in connection.execute(f"PRAGMA table_info({registrations})") if int(row[5]) > 0
1018
+ )
1019
+ if credential_pk != ("credential_id",) or registration_pk != ("consumer_id", "project_id"):
1020
+ raise ServerConfigurationError("EP consumer credential table identity is invalid.")
1021
+
1022
+
1023
+ def _install_ep_consumer_schema(connection: sqlite3.Connection) -> None:
1024
+ connection.execute(
1025
+ "CREATE TABLE ep_consumer_credentials ("
1026
+ "credential_id TEXT PRIMARY KEY CHECK(length(credential_id) BETWEEN 1 AND 128),"
1027
+ "consumer_id TEXT NOT NULL CHECK(length(consumer_id) BETWEEN 1 AND 128),"
1028
+ "project_id TEXT NOT NULL CHECK(length(project_id) BETWEEN 1 AND 128),"
1029
+ "verifier BLOB NOT NULL UNIQUE CHECK(length(verifier)=32),"
1030
+ "fingerprint BLOB NOT NULL UNIQUE CHECK(length(fingerprint)=32),"
1031
+ "issued_at TEXT NOT NULL,expires_at TEXT,revoked_at TEXT,"
1032
+ "replaced_by_credential_id TEXT REFERENCES ep_consumer_credentials(credential_id))"
1033
+ )
1034
+ connection.execute(
1035
+ "CREATE INDEX ep_consumer_credentials_scope_lookup "
1036
+ "ON ep_consumer_credentials(consumer_id,project_id,revoked_at)"
1037
+ )
1038
+ connection.execute(
1039
+ "CREATE TABLE ep_consumer_registrations ("
1040
+ "consumer_id TEXT NOT NULL CHECK(length(consumer_id) BETWEEN 1 AND 128),"
1041
+ "project_id TEXT NOT NULL CHECK(length(project_id) BETWEEN 1 AND 128),"
1042
+ "status TEXT NOT NULL CHECK(status IN ('ACTIVE','DISABLED','REVOKED')),"
1043
+ "created_at TEXT NOT NULL,updated_at TEXT NOT NULL,disabled_at TEXT,revoked_at TEXT,"
1044
+ "audit_metadata TEXT NOT NULL DEFAULT '{}',PRIMARY KEY(consumer_id,project_id))"
1045
+ )
1046
+ connection.execute(
1047
+ "CREATE INDEX ep_consumer_registrations_status_lookup "
1048
+ "ON ep_consumer_registrations(consumer_id,project_id,status)"
1049
+ )
1050
+
1051
+
1052
+ def _assert_exact_consumer_transfer(
1053
+ connection: sqlite3.Connection, *, source: str, destination: str, columns: tuple[str, ...],
1054
+ ) -> None:
1055
+ """Prove cardinality and values match without exposing credential material."""
1056
+
1057
+ column_list = ",".join(columns)
1058
+ source_count = int(connection.execute(f"SELECT COUNT(*) FROM {source}").fetchone()[0])
1059
+ destination_count = int(connection.execute(f"SELECT COUNT(*) FROM {destination}").fetchone()[0])
1060
+ if source_count != destination_count:
1061
+ raise ServerConfigurationError("EP consumer credential transfer cardinality is invalid.")
1062
+ missing = connection.execute(
1063
+ f"SELECT {column_list} FROM {source} EXCEPT SELECT {column_list} FROM {destination} LIMIT 1"
1064
+ ).fetchone()
1065
+ extra = connection.execute(
1066
+ f"SELECT {column_list} FROM {destination} EXCEPT SELECT {column_list} FROM {source} LIMIT 1"
1067
+ ).fetchone()
1068
+ if missing is not None or extra is not None:
1069
+ raise ServerConfigurationError("EP consumer credential transfer identity is invalid.")
1070
+
1071
+
1072
+ def _migrate_schema_53(connection: sqlite3.Connection) -> None:
1073
+ """Transfer consumer credentials to the neutral Server/CENTRAL namespace.
1074
+
1075
+ The entire migration is called from the Server's enclosing immediate
1076
+ transaction. Legacy tables are retained untouched as migration evidence;
1077
+ runtime code switches exclusively to the new tables only after the exact
1078
+ transfer proof succeeds and schema metadata advances.
1079
+ """
1080
+
1081
+ if _schema_version(connection) != 52:
1082
+ raise ServerConfigurationError("EP consumer credential migration schema version is invalid.")
1083
+ metadata = connection.execute(
1084
+ "SELECT value FROM engineering_metadata WHERE key='installation.schema_version'"
1085
+ ).fetchone()
1086
+ if metadata is None or str(metadata[0]) != "52":
1087
+ raise ServerConfigurationError("EP consumer credential migration metadata is invalid.")
1088
+ legacy_credentials, legacy_registrations = (
1089
+ "local_api_credentials", "local_api_consumer_registrations",
1090
+ )
1091
+ current_credentials, current_registrations = (
1092
+ "ep_consumer_credentials", "ep_consumer_registrations",
1093
+ )
1094
+ tables = _table_names(connection)
1095
+ legacy = {legacy_credentials, legacy_registrations} & tables
1096
+ current = {current_credentials, current_registrations} & tables
1097
+ if legacy and legacy != {legacy_credentials, legacy_registrations}:
1098
+ raise ServerConfigurationError("EP consumer credential migration source is incomplete.")
1099
+ if current and current != {current_credentials, current_registrations}:
1100
+ raise ServerConfigurationError("EP consumer credential migration destination is incomplete.")
1101
+ if legacy and current:
1102
+ raise ServerConfigurationError("EP consumer credential migration has ambiguous parallel authority.")
1103
+ if legacy:
1104
+ _require_consumer_table_shape(
1105
+ connection, credentials=legacy_credentials, registrations=legacy_registrations,
1106
+ )
1107
+ _install_ep_consumer_schema(connection)
1108
+ connection.execute(
1109
+ "INSERT INTO ep_consumer_registrations(consumer_id,project_id,status,created_at,updated_at,disabled_at,revoked_at,audit_metadata) "
1110
+ "SELECT consumer_id,project_id,status,created_at,updated_at,disabled_at,revoked_at,audit_metadata "
1111
+ "FROM local_api_consumer_registrations"
1112
+ )
1113
+ connection.execute(
1114
+ "INSERT INTO ep_consumer_credentials(credential_id,consumer_id,project_id,verifier,fingerprint,issued_at,expires_at,revoked_at,replaced_by_credential_id) "
1115
+ "SELECT credential_id,consumer_id,project_id,verifier,fingerprint,issued_at,expires_at,revoked_at,replaced_by_credential_id "
1116
+ "FROM local_api_credentials"
1117
+ )
1118
+ _assert_exact_consumer_transfer(
1119
+ connection, source=legacy_registrations, destination=current_registrations,
1120
+ columns=_CONSUMER_REGISTRATION_COLUMNS,
1121
+ )
1122
+ _assert_exact_consumer_transfer(
1123
+ connection, source=legacy_credentials, destination=current_credentials,
1124
+ columns=_CONSUMER_CREDENTIAL_COLUMNS,
1125
+ )
1126
+ elif current:
1127
+ _require_consumer_table_shape(
1128
+ connection, credentials=current_credentials, registrations=current_registrations,
1129
+ )
1130
+ else:
1131
+ raise ServerConfigurationError("EP consumer credential migration source is absent.")
1132
+ # This table is referenced by the schema-52 receipt provenance table. Keep
1133
+ # those foreign-key declarations pointed at the canonical name while the
1134
+ # installation CHECK constraint is widened for schema 53. Without this
1135
+ # SQLite rewrites a dependent reference to the temporary table name, which
1136
+ # would leave the completed store structurally invalid after that temporary
1137
+ # table is retired.
1138
+ connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema52")
1139
+ connection.execute(
1140
+ "CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, "
1141
+ "schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43,44,45,46,47,48,49,50,51,52,53)))"
1142
+ )
1143
+ connection.execute(
1144
+ "INSERT INTO ep_installations(instance_id,created_at,schema_version) "
1145
+ "SELECT instance_id,created_at,53 FROM ep_installations_schema52"
1146
+ )
1147
+ connection.execute("DROP TABLE ep_installations_schema52")
1148
+ connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(53)")
1149
+ connection.execute("UPDATE engineering_metadata SET value='53' WHERE key='installation.schema_version'")
1150
+ connection.execute("UPDATE ep_installations SET schema_version=53")
1151
+
1152
+
1153
+ def validate_store(data_root: Path, identity: RuntimeIdentity) -> dict[str, object]:
1154
+ """Return a deterministic fail-closed current-schema structural report."""
1155
+ path = data_root / SERVER_DATABASE_FILENAME
1156
+ try:
1157
+ with sqlite3.connect(f"file:{path}?mode=ro", uri=True) as connection:
1158
+ tables = _table_names(connection)
1159
+ indexes = _index_names(connection)
1160
+ schema = _schema_version(connection)
1161
+ integrity = [str(row[0]) for row in connection.execute("PRAGMA integrity_check")]
1162
+ metadata = dict(connection.execute("SELECT key,value FROM engineering_metadata WHERE key IN ('installation.instance_id','installation.schema_version')"))
1163
+ installation = connection.execute("SELECT instance_id FROM ep_installations WHERE instance_id=?", (identity.instance_id,)).fetchone()
1164
+ except (OSError, sqlite3.DatabaseError) as error:
1165
+ raise ServerConfigurationError("EP Server store is unavailable.") from error
1166
+ valid = schema == SERVER_STORE_SCHEMA_VERSION and SERVER_REQUIRED_TABLES <= tables and SERVER_REQUIRED_INDEXES <= indexes and integrity == ["ok"] and metadata == {"installation.instance_id": identity.instance_id, "installation.schema_version": str(SERVER_STORE_SCHEMA_VERSION)} and installation is not None
1167
+ if not valid:
1168
+ raise ServerConfigurationError(
1169
+ f"EP Server store is not a valid official schema-{SERVER_STORE_SCHEMA_VERSION} installation."
1170
+ )
1171
+ return {"schema_version": schema, "integrity": "PASS", "required_tables": sorted(SERVER_REQUIRED_TABLES), "required_indexes": sorted(SERVER_REQUIRED_INDEXES)}
1172
+
1173
+
1174
+ def initialize(data_root: Path, *, bind_host: str = "127.0.0.1", bind_port: int = 8765) -> RuntimeIdentity:
1175
+ """Create or validate an empty, installation-owned server instance."""
1176
+ data_root = data_root.resolve()
1177
+ data_root.mkdir(mode=0o700, parents=True, exist_ok=True)
1178
+ config_path = data_root / SERVER_CONFIGURATION_FILENAME
1179
+ if not config_path.exists():
1180
+ if bind_host != "127.0.0.1" or not 1 <= bind_port <= 65535:
1181
+ raise ServerConfigurationError("EP Server initial bind configuration is invalid.")
1182
+ _write_json(config_path, asdict(ServerConfiguration(
1183
+ SERVER_CONFIGURATION_VERSION, bind_host, bind_port,
1184
+ str(default_engineering_platform_codex_cli_prefix()),
1185
+ )))
1186
+ configuration = ServerConfiguration.load(data_root)
1187
+ # Version 1 inferred the CLI installation at each process boundary from
1188
+ # HOME. Upgrade it once, under the server's stable account identity, so
1189
+ # child workers and later restarts inherit one installation authority.
1190
+ if configuration.version != SERVER_CONFIGURATION_VERSION:
1191
+ configuration = ServerConfiguration(
1192
+ SERVER_CONFIGURATION_VERSION,
1193
+ configuration.bind_host,
1194
+ configuration.bind_port,
1195
+ configuration.managed_codex_cli_prefix,
1196
+ )
1197
+ _write_json(config_path, asdict(configuration))
1198
+ identity_path = data_root / SERVER_IDENTITY_FILENAME
1199
+ if identity_path.exists():
1200
+ try:
1201
+ raw = json.loads(identity_path.read_text(encoding="utf-8"))
1202
+ identity = RuntimeIdentity(str(raw["instance_id"]), str(raw["created_at"]))
1203
+ if not identity.instance_id or not identity.created_at:
1204
+ raise ValueError("empty identity")
1205
+ except (OSError, ValueError, KeyError, TypeError, json.JSONDecodeError) as error:
1206
+ raise ServerConfigurationError("EP Server runtime identity is invalid.") from error
1207
+ else:
1208
+ identity = RuntimeIdentity(str(uuid4()), _utcnow())
1209
+ _write_json(identity_path, asdict(identity))
1210
+ database_path = data_root / SERVER_DATABASE_FILENAME
1211
+ if database_path.exists():
1212
+ try:
1213
+ with sqlite3.connect(f"file:{database_path}?mode=ro", uri=True) as existing:
1214
+ existing_tables = _table_names(existing)
1215
+ if existing_tables:
1216
+ current_schema = _schema_version(existing)
1217
+ if current_schema not in {41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, SERVER_STORE_SCHEMA_VERSION}:
1218
+ raise ServerConfigurationError(
1219
+ f"EP Server store is not a valid official schema-{SERVER_STORE_SCHEMA_VERSION} installation."
1220
+ )
1221
+ if current_schema == SERVER_STORE_SCHEMA_VERSION:
1222
+ validate_store(data_root, identity)
1223
+ return identity
1224
+ if current_schema in {42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52}:
1225
+ with sqlite3.connect(database_path) as connection:
1226
+ # Schema-49 rebuilds the submission parent table
1227
+ # to widen its immutable transport constraint.
1228
+ connection.execute("PRAGMA foreign_keys=OFF")
1229
+ connection.execute("PRAGMA legacy_alter_table=ON")
1230
+ connection.execute("BEGIN IMMEDIATE")
1231
+ if current_schema == 42:
1232
+ _migrate_schema_43(connection)
1233
+ if current_schema in {42, 43}:
1234
+ _migrate_schema_44(connection)
1235
+ if current_schema in {42, 43, 44}:
1236
+ _migrate_schema_45(connection)
1237
+ if current_schema in {42, 43, 44, 45}:
1238
+ _migrate_schema_46(connection)
1239
+ if current_schema in {42, 43, 44, 45, 46}:
1240
+ _migrate_schema_47(connection)
1241
+ if current_schema in {42, 43, 44, 45, 46, 47}:
1242
+ _migrate_schema_48(connection)
1243
+ if current_schema in {42, 43, 44, 45, 46, 47, 48}:
1244
+ _migrate_schema_49(connection)
1245
+ if current_schema in {42, 43, 44, 45, 46, 47, 48, 49}:
1246
+ _migrate_schema_50(connection)
1247
+ if current_schema != 51:
1248
+ _migrate_schema_51(connection)
1249
+ if current_schema != 52:
1250
+ _migrate_schema_52(connection)
1251
+ _migrate_schema_53(connection)
1252
+ connection.execute("COMMIT")
1253
+ connection.execute("PRAGMA legacy_alter_table=OFF")
1254
+ validate_store(data_root, identity)
1255
+ return identity
1256
+ except sqlite3.DatabaseError as error:
1257
+ raise ServerConfigurationError("EP Server store is unavailable.") from error
1258
+ with sqlite3.connect(database_path) as connection:
1259
+ # Schema-53 widens ep_installations while schema-52 provenance already
1260
+ # references it. SQLite must retain those declarations at the
1261
+ # canonical name during the enclosing rebuild transaction.
1262
+ connection.execute("PRAGMA foreign_keys=OFF")
1263
+ connection.execute("PRAGMA legacy_alter_table=ON")
1264
+ connection.execute("BEGIN IMMEDIATE")
1265
+ _install_schema_41(connection, identity)
1266
+ _migrate_schema_42(connection)
1267
+ _migrate_schema_43(connection)
1268
+ _migrate_schema_44(connection)
1269
+ _migrate_schema_45(connection)
1270
+ _migrate_schema_46(connection)
1271
+ _migrate_schema_47(connection)
1272
+ _migrate_schema_48(connection)
1273
+ _migrate_schema_49(connection)
1274
+ _migrate_schema_50(connection)
1275
+ _migrate_schema_51(connection)
1276
+ _migrate_schema_52(connection)
1277
+ _migrate_schema_53(connection)
1278
+ connection.execute("COMMIT")
1279
+ connection.execute("PRAGMA legacy_alter_table=OFF")
1280
+ connection.execute("PRAGMA foreign_keys=ON")
1281
+ database_path.chmod(0o600)
1282
+ validate_store(data_root, identity)
1283
+ return identity
1284
+
1285
+
1286
+ def _runtime(data_root: Path) -> dict[str, object] | None:
1287
+ try:
1288
+ raw = json.loads((data_root / SERVER_RUNTIME_FILENAME).read_text(encoding="utf-8"))
1289
+ return raw if isinstance(raw, dict) else None
1290
+ except (OSError, json.JSONDecodeError):
1291
+ return None
1292
+
1293
+
1294
+ def _alive(pid: object) -> bool:
1295
+ if not isinstance(pid, int) or pid <= 0:
1296
+ return False
1297
+ try:
1298
+ os.kill(pid, 0)
1299
+ except ProcessLookupError:
1300
+ return False
1301
+ except PermissionError:
1302
+ return True
1303
+ return True
1304
+
1305
+
1306
+ def _transport_components(data_root: Path, *, server_running: bool) -> dict[str, dict[str, object]]:
1307
+ """Return secret-free, platform-scoped ingress observability from CENTRAL.
1308
+
1309
+ Submission timestamps are observation only. They never change queue or
1310
+ execution semantics and intentionally retain no credential or raw prompt.
1311
+ """
1312
+ latest: dict[str, str] = {}
1313
+ with sqlite3.connect(f"file:{data_root / SERVER_DATABASE_FILENAME}?mode=ro", uri=True) as connection:
1314
+ for transport, created_at in connection.execute(
1315
+ "SELECT transport,MAX(created_at) FROM ep_submissions GROUP BY transport"
1316
+ ):
1317
+ latest[str(transport)] = str(created_at)
1318
+ http_status_code = "HTTP_INGRESS_HEALTHY" if server_running else "HTTP_INGRESS_DOWN"
1319
+ cli_status_code = "CLI_INGRESS_AVAILABLE" if server_running else "CLI_INGRESS_DEGRADED"
1320
+ file_last = latest.get("FILE_INBOX")
1321
+ dependabot_last = latest.get("DEPENDABOT")
1322
+ heartbeat = file_inbox.read_heartbeat(data_root / FILE_INBOX_DIRECTORY)
1323
+ heartbeat_at = str(heartbeat.get("updated_at", "")) if heartbeat else ""
1324
+ try:
1325
+ heartbeat_fresh = (datetime.now(timezone.utc) - datetime.fromisoformat(heartbeat_at)).total_seconds() <= 10
1326
+ except ValueError:
1327
+ heartbeat_fresh = False
1328
+ delivery_retry = str(heartbeat.get("delivery_retry", "NONE")) if heartbeat else "NONE"
1329
+ submission_ready = bool(heartbeat and heartbeat.get("state") == "READY" and heartbeat.get("readiness") == "SUBMISSION_CAPABLE")
1330
+ quarantine_count = int(heartbeat.get("quarantine_count", 0)) if heartbeat and isinstance(heartbeat.get("quarantine_count", 0), int) else 0
1331
+ recent_error = heartbeat.get("recent_error") if heartbeat else None
1332
+ # A live watcher with pending delivery, a bounded adapter diagnostic, or
1333
+ # quarantined ingress is operational but needs attention. It is not a
1334
+ # CENTRAL execution/run failure and cannot affect queue authority.
1335
+ file_attention_needed = delivery_retry != "NONE" or bool(recent_error) or quarantine_count > 0
1336
+ file_status_code = (
1337
+ "FILE_INGRESS_STOPPED" if not server_running or not heartbeat_fresh
1338
+ else "FILE_INGRESS_NOT_READY" if not submission_ready
1339
+ else "FILE_INGRESS_DEGRADED" if file_attention_needed else "FILE_INGRESS_RUNNING"
1340
+ )
1341
+ dependabot_heartbeat = dependabot_producer.read_heartbeat(data_root)
1342
+ dependabot_updated = str(dependabot_heartbeat.get("updated_at", "")) if dependabot_heartbeat else ""
1343
+ try:
1344
+ dependabot_fresh = (datetime.now(timezone.utc) - datetime.fromisoformat(dependabot_updated)).total_seconds() <= 310
1345
+ except ValueError:
1346
+ dependabot_fresh = False
1347
+ dependabot_ready = bool(
1348
+ server_running
1349
+ and dependabot_fresh
1350
+ and dependabot_heartbeat
1351
+ and dependabot_heartbeat.get("state") == "READY"
1352
+ and dependabot_heartbeat.get("readiness") == "DISCOVERY_CAPABLE"
1353
+ )
1354
+ return {
1355
+ "http_ingress": {
1356
+ "healthy": server_running, "status_code": http_status_code,
1357
+ "detail_code": "CENTRAL_LISTENER_ENDPOINT" if server_running else "CENTRAL_LISTENER_UNAVAILABLE",
1358
+ "version": "1", # canonical submission protocol version
1359
+ "last_successful_submission": latest.get("HTTP"), "recent_error": None,
1360
+ },
1361
+ "cli_ingress": {
1362
+ "healthy": server_running, "status_code": cli_status_code,
1363
+ "detail_code": "CANONICAL_SUBMISSION_COMPATIBILITY" if server_running else "CENTRAL_ENDPOINT_UNAVAILABLE",
1364
+ "version": "1", "last_successful_submission": latest.get("CLI"), "recent_error": None,
1365
+ },
1366
+ "file_inbox_ingress": {
1367
+ "healthy": file_status_code == "FILE_INGRESS_RUNNING", "status_code": file_status_code,
1368
+ "detail_code": "FILE_INBOX_HEARTBEAT" if server_running and heartbeat_fresh else "FILE_INBOX_HEARTBEAT_MISSING",
1369
+ "watched_location": heartbeat.get("watched_location") if heartbeat else str(data_root / FILE_INBOX_DIRECTORY),
1370
+ "heartbeat": heartbeat_at or None,
1371
+ "last_successful_submission": file_last,
1372
+ "delivery_retry_code": f"FILE_INGRESS_DELIVERY_RETRY_{delivery_retry}",
1373
+ "quarantine_count": quarantine_count,
1374
+ # Never transport a raw exception into a presentation projection.
1375
+ "reason_code": "FILE_INBOX_DIAGNOSTIC" if recent_error else None,
1376
+ },
1377
+ "dependabot_producer": {
1378
+ "healthy": dependabot_ready,
1379
+ "status_code": "DEPENDABOT_READY" if dependabot_ready else "DEPENDABOT_DEGRADED",
1380
+ "detail_code": "DEPENDABOT_HEARTBEAT" if dependabot_fresh else "DEPENDABOT_HEARTBEAT_MISSING",
1381
+ "heartbeat": dependabot_updated or None,
1382
+ "last_successful_submission": dependabot_last,
1383
+ "reason_code": "DEPENDABOT_DIAGNOSTIC" if dependabot_heartbeat and dependabot_heartbeat.get("recent_error") else None,
1384
+ },
1385
+ }
1386
+
1387
+
1388
+ def _dashboard_relay_component(*, server_running: bool) -> dict[str, object]:
1389
+ """Project the Relay only when its real lifecycle owner is live.
1390
+
1391
+ The relay is an optional access adapter, but it is still a logical
1392
+ Platform Component. A running EP Server cannot stand in for a missing or
1393
+ repeatedly exiting LaunchAgent.
1394
+ """
1395
+ definition = PLATFORM_COMPONENT_BY_ID["dashboard_relay"]
1396
+ label = definition.lifecycle_label
1397
+ try:
1398
+ lifecycle = LaunchdProvider()
1399
+ runtime = lifecycle.runtime_status(label) if label else None
1400
+ observed = lifecycle.runtime_details(label) if label else None
1401
+ observed = observed if isinstance(observed, LaunchdRuntimeDetails) else None
1402
+ relay_running = bool(runtime and runtime.qualified)
1403
+ detail = runtime.detail if runtime is not None else "lifecycle owner unavailable"
1404
+ except OSError:
1405
+ observed, relay_running, detail = None, False, "lifecycle owner unavailable"
1406
+ healthy = server_running and relay_running
1407
+ if healthy:
1408
+ detail_code = "DASHBOARD_RELAY_TAILSCALE_AVAILABLE"
1409
+ elif observed is not None and not observed.loaded:
1410
+ detail_code = "DASHBOARD_RELAY_LAUNCH_AGENT_UNLOADED"
1411
+ elif observed is not None and not observed.active:
1412
+ detail_code = "DASHBOARD_RELAY_PROCESS_INACTIVE"
1413
+ else:
1414
+ detail_code = "DASHBOARD_RELAY_LIFECYCLE_UNAVAILABLE"
1415
+ return {
1416
+ "healthy": healthy,
1417
+ "status_code": definition.active_status if healthy else definition.inactive_status,
1418
+ "detail_code": detail_code,
1419
+ "lifecycle_label": label,
1420
+ "lifecycle_state": "RUNNING" if relay_running else "STOPPED",
1421
+ # A Relay has its own LaunchAgent process. Never substitute a
1422
+ # placeholder (notably zero) for the observed process lifetime.
1423
+ "uptime_seconds": observed.uptime_seconds if observed is not None else None,
1424
+ "recent_error": None if healthy else detail,
1425
+ }
1426
+
1427
+
1428
+ def _launch_agent_configuration(plist_path: Path) -> dict[str, object]:
1429
+ """Project the safe start policy from one EP-owned LaunchAgent plist."""
1430
+ try:
1431
+ payload = plistlib.loads(plist_path.read_bytes())
1432
+ except (OSError, plistlib.InvalidFileException):
1433
+ return {}
1434
+ if not isinstance(payload, dict):
1435
+ return {}
1436
+ configuration: dict[str, object] = {}
1437
+ run_at_load = payload.get("RunAtLoad")
1438
+ if isinstance(run_at_load, bool):
1439
+ configuration["run_at_load"] = run_at_load
1440
+ keep_alive = payload.get("KeepAlive")
1441
+ if isinstance(keep_alive, (bool, dict)):
1442
+ configuration["keep_alive"] = bool(keep_alive)
1443
+ return configuration
1444
+
1445
+
1446
+ def _platform_component_detail(data_root: Path, component_id: str) -> dict[str, object] | None:
1447
+ """Expose one secret-free detail view from the same platform projection."""
1448
+ component = status(data_root)["components"].get(component_id) # type: ignore[index]
1449
+ if not isinstance(component, dict):
1450
+ return None
1451
+ definition = PLATFORM_COMPONENT_BY_ID[component_id]
1452
+ service_paths = server_service.default_paths(data_root)
1453
+ runtime_executable = Path(sys.executable).expanduser().absolute()
1454
+ installation: dict[str, str] = {}
1455
+ if component_id == "ep_server":
1456
+ installation = {
1457
+ "runtime_path": str(runtime_executable.parent.parent),
1458
+ "central_data_path": str(data_root.resolve()),
1459
+ "launch_agent_path": str(service_paths.plist_path),
1460
+ "error_log_path": str(service_paths.stderr_log),
1461
+ }
1462
+ elif component_id == "platform_database":
1463
+ database_path = data_root / SERVER_DATABASE_FILENAME
1464
+ installation = {
1465
+ "central_data_path": str(data_root.resolve()),
1466
+ "database_path": str(database_path.resolve()),
1467
+ }
1468
+ elif component_id == "dashboard_relay":
1469
+ installation = {
1470
+ "launch_agent_path": str(server_relay.launch_agent_path()),
1471
+ "relay_binary_path": str(server_relay.relay_binary(data_root)),
1472
+ }
1473
+ lifecycle_label = server_service.LABEL if component_id == "ep_server" else definition.lifecycle_label
1474
+ observed: LaunchdRuntimeDetails | None = None
1475
+ if lifecycle_label:
1476
+ candidate = LaunchdProvider().runtime_details(lifecycle_label)
1477
+ observed = candidate if isinstance(candidate, LaunchdRuntimeDetails) else None
1478
+ detail: dict[str, object] = {
1479
+ "component": component_id,
1480
+ "machine": os.uname().nodename,
1481
+ "restart_supported": definition.restart_supported,
1482
+ "installation": installation,
1483
+ **component,
1484
+ }
1485
+ if component_id == "http_ingress":
1486
+ detail["swagger_endpoint"] = HTTP_JSON_OPENAPI_PATH
1487
+ if observed is not None:
1488
+ detail["launchd"] = {
1489
+ "label": observed.label,
1490
+ "loaded": observed.loaded,
1491
+ "active": observed.active,
1492
+ "pid": observed.pid,
1493
+ "last_exit_code": observed.last_exit_code,
1494
+ **(
1495
+ _launch_agent_configuration(Path(installation["launch_agent_path"]))
1496
+ if isinstance(installation.get("launch_agent_path"), str) else {}
1497
+ ),
1498
+ }
1499
+ detail["process_state"] = "OWNED_PROCESS"
1500
+ detail["processes"] = ([{
1501
+ "pid": observed.pid,
1502
+ "memory_kib": observed.memory_kib,
1503
+ }] if observed.active and observed.pid is not None else [])
1504
+ if observed.uptime_seconds is not None:
1505
+ detail["uptime_seconds"] = observed.uptime_seconds
1506
+ elif component_id == "platform_database":
1507
+ detail["launchd"] = {}
1508
+ detail["process_state"] = "STORAGE"
1509
+ try:
1510
+ detail["database_size_bytes"] = (data_root / SERVER_DATABASE_FILENAME).stat().st_size
1511
+ except OSError:
1512
+ pass
1513
+ else:
1514
+ host = LaunchdProvider().runtime_details(server_service.LABEL)
1515
+ host = host if isinstance(host, LaunchdRuntimeDetails) else None
1516
+ detail["launchd"] = {}
1517
+ detail["process_state"] = "IN_PROCESS"
1518
+ detail["process_host"] = {
1519
+ "component": "ep_server",
1520
+ "pid": host.pid if host is not None and host.active else None,
1521
+ "uptime_seconds": host.uptime_seconds if host is not None and host.active else None,
1522
+ }
1523
+ return detail
1524
+
1525
+
1526
+ def _restart_platform_component(data_root: Path, component_id: str) -> dict[str, object]:
1527
+ """Restart one explicitly restartable Platform component through its owner.
1528
+
1529
+ Component identifiers and lifecycle labels come exclusively from the
1530
+ canonical model. This deliberately does not expose a generic process or
1531
+ LaunchAgent control endpoint.
1532
+ """
1533
+ definition = PLATFORM_COMPONENT_BY_ID.get(component_id)
1534
+ if definition is None or not definition.restart_supported or not definition.lifecycle_label:
1535
+ raise ValueError("COMPONENT_RESTART_NOT_SUPPORTED")
1536
+ logger = component_logger(
1537
+ data_root,
1538
+ definition.id,
1539
+ central_database=data_root / SERVER_DATABASE_FILENAME,
1540
+ )
1541
+ log_event(
1542
+ logger,
1543
+ logging.INFO,
1544
+ "component_restart_requested",
1545
+ context={"target_component": definition.id},
1546
+ )
1547
+ lifecycle = LaunchdProvider()
1548
+ try:
1549
+ lifecycle.restart(definition.lifecycle_label)
1550
+ # ``kickstart`` only acknowledges the request. Give launchd a small,
1551
+ # bounded interval to spawn the owned process before deciding whether
1552
+ # the repair actually reached its observable postcondition.
1553
+ deadline = time.monotonic() + 2
1554
+ postcondition = lifecycle.runtime_status(definition.lifecycle_label)
1555
+ while not postcondition.qualified and time.monotonic() < deadline:
1556
+ time.sleep(.1)
1557
+ postcondition = lifecycle.runtime_status(definition.lifecycle_label)
1558
+ if not postcondition.qualified:
1559
+ raise OSError("COMPONENT_RESTART_POSTCONDITION_FAILED")
1560
+ except OSError as error:
1561
+ log_event(
1562
+ logger,
1563
+ logging.WARNING,
1564
+ "component_restart_failed",
1565
+ diagnostic=str(error),
1566
+ context={"target_component": definition.id},
1567
+ )
1568
+ raise
1569
+ log_event(
1570
+ logger,
1571
+ logging.INFO,
1572
+ "component_restart_completed",
1573
+ context={
1574
+ "target_component": definition.id,
1575
+ "postcondition": "LIFECYCLE_OWNER_RUNNING",
1576
+ },
1577
+ )
1578
+ return {
1579
+ "restarting": definition.id,
1580
+ "scope": "PLATFORM",
1581
+ "postcondition": "LIFECYCLE_OWNER_RUNNING",
1582
+ }
1583
+
1584
+
1585
+ def _audit_configuration_change(
1586
+ data_root: Path,
1587
+ *,
1588
+ scope: str,
1589
+ key: str,
1590
+ previous: object,
1591
+ value: object,
1592
+ ) -> None:
1593
+ """Persist a bounded CENTRAL audit event for a successful setting change."""
1594
+ log_event(
1595
+ component_logger(
1596
+ data_root,
1597
+ "operations_console",
1598
+ central_database=data_root / SERVER_DATABASE_FILENAME,
1599
+ ),
1600
+ logging.INFO,
1601
+ "configuration_changed",
1602
+ context={
1603
+ "configuration_scope": scope,
1604
+ "configuration_key": key,
1605
+ "previous_value": previous,
1606
+ "new_value": value,
1607
+ },
1608
+ )
1609
+
1610
+
1611
+ def status(data_root: Path) -> dict[str, object]:
1612
+ identity = initialize(data_root)
1613
+ config = ServerConfiguration.load(data_root)
1614
+ runtime = _runtime(data_root)
1615
+ running = bool(runtime and _alive(runtime.get("pid")))
1616
+ components = _transport_components(data_root, server_running=running)
1617
+ components["dashboard_relay"] = _dashboard_relay_component(server_running=running)
1618
+ # One Server-native inventory feeds Components, the titlebar popout and
1619
+ # detail modals. It deliberately contains no watcher/check-out model.
1620
+ for definition in PLATFORM_COMPONENTS:
1621
+ if definition.id in components:
1622
+ components[definition.id].update({
1623
+ "kind": definition.kind, "name_key": definition.name_key,
1624
+ "group": definition.group, "critical": definition.critical, "restart_supported": definition.restart_supported,
1625
+ "log_component": definition.id,
1626
+ })
1627
+ continue
1628
+ healthy = True if definition.id == "platform_database" else running
1629
+ components[definition.id] = {
1630
+ "kind": definition.kind, "name_key": definition.name_key,
1631
+ "group": definition.group, "critical": definition.critical, "restart_supported": definition.restart_supported,
1632
+ "log_component": definition.id, "healthy": healthy,
1633
+ "status_code": definition.active_status if healthy else definition.inactive_status,
1634
+ "detail_code": definition.detail_code,
1635
+ **({"version": str(SERVER_STORE_SCHEMA_VERSION)} if definition.id == "platform_database" else {}),
1636
+ }
1637
+ server_component = components["ep_server"]
1638
+ server_component["version"] = _console_platform_version()
1639
+ started_at = runtime.get("started_at") if runtime else None
1640
+ if isinstance(started_at, str):
1641
+ try:
1642
+ server_component["uptime_seconds"] = max(
1643
+ 0, int((datetime.now(timezone.utc) - datetime.fromisoformat(started_at)).total_seconds())
1644
+ )
1645
+ except ValueError:
1646
+ pass
1647
+ # Every canonical component is observed and included in the response.
1648
+ # The aggregate follows the component model's explicit criticality: an
1649
+ # optional access or producer adapter remains diagnosable without making
1650
+ # a core Server probe unavailable on a host that does not install it.
1651
+ unhealthy_components = [
1652
+ item.id for item in PLATFORM_COMPONENTS
1653
+ if not bool(components[item.id].get("healthy"))
1654
+ ]
1655
+ healthy = all(
1656
+ bool(components[item.id].get("healthy"))
1657
+ for item in PLATFORM_COMPONENTS
1658
+ if item.critical
1659
+ )
1660
+ return {
1661
+ "service": "engineering-platform-server",
1662
+ "healthy": healthy,
1663
+ "health": "ok" if healthy else "degraded",
1664
+ "unhealthy_components": unhealthy_components,
1665
+ "instance_id": identity.instance_id,
1666
+ "store": "ready",
1667
+ "schema_version": SERVER_STORE_SCHEMA_VERSION,
1668
+ "operational_state": "empty-valid",
1669
+ "running": running,
1670
+ "managed_codex_runtime": managed_codex_runtime.inspect(data_root),
1671
+ "lifecycle_worker": {
1672
+ # The worker is hosted by the sole installed Server process. A
1673
+ # stopped process is never reported as an active worker.
1674
+ "state": "RUNNING" if running else "STOPPED",
1675
+ },
1676
+ "bind": {"host": config.bind_host, "port": config.bind_port},
1677
+ "components": components,
1678
+ "component_model": [
1679
+ {"id": item.id, "name_key": item.name_key, "kind": item.kind, "group": item.group,
1680
+ "critical": item.critical, "restart_supported": item.restart_supported,
1681
+ "lifecycle_label": item.lifecycle_label, "log_component": item.id}
1682
+ for item in PLATFORM_COMPONENTS
1683
+ ],
1684
+ }
1685
+
1686
+
1687
+ def operations_projection(data_root: Path) -> dict[str, object]:
1688
+ """Return the installed CENTRAL's secret-free Console projection.
1689
+
1690
+ The selected project remains a browser presentation preference. This
1691
+ endpoint intentionally returns topology only; no checkout path, Agent
1692
+ credential, or execution capability is exposed here.
1693
+ """
1694
+ identity = initialize(data_root)
1695
+ with sqlite3.connect(f"file:{data_root / SERVER_DATABASE_FILENAME}?mode=ro", uri=True) as connection:
1696
+ topology = project_topology.topology(connection)
1697
+ return {
1698
+ "installation_id": identity.instance_id,
1699
+ "schema_version": SERVER_STORE_SCHEMA_VERSION,
1700
+ "managed_codex_runtime": managed_codex_runtime.inspect(data_root),
1701
+ "projects": topology["projects"],
1702
+ }
1703
+
1704
+
1705
+ def _console_projects(data_root: Path) -> list[dict[str, str]]:
1706
+ """List CENTRAL project identities without opening their checkouts.
1707
+
1708
+ The selector is a logical CENTRAL projection. A local binding is checked
1709
+ only later, when a user explicitly selects that project for a transitional
1710
+ root-bound route.
1711
+ """
1712
+ with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection:
1713
+ rows = connection.execute("""SELECT p.project_id, r.repository_id
1714
+ FROM ep_project_registrations AS p
1715
+ JOIN ep_repository_registrations AS r
1716
+ ON r.project_id=p.project_id AND r.role='authority'
1717
+ WHERE p.status='ACTIVE'
1718
+ ORDER BY p.project_id""").fetchall()
1719
+ return [{"project_id": str(project_id), "repository_id": str(repository_id)}
1720
+ for project_id, repository_id in rows]
1721
+
1722
+
1723
+ def _console_queue_projection(data_root: Path, project_id: str) -> dict[str, object]:
1724
+ """Read the selected project's single transport-neutral CENTRAL FIFO."""
1725
+ for project in _console_projects(data_root):
1726
+ if project["project_id"] != project_id:
1727
+ continue
1728
+ with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection:
1729
+ context = project_context(
1730
+ connection,
1731
+ data_root=data_root,
1732
+ project_id=project_id,
1733
+ repository_id=project["repository_id"],
1734
+ require_local_root=False,
1735
+ )
1736
+ queue = ParityProjectStore(connection, context).console_queue_projection()
1737
+ rows = connection.execute(
1738
+ """SELECT run_id,operator_resolution FROM ep_parity_lifecycle_dispatches
1739
+ WHERE project_id=? AND operator_resolution IN ('DISMISSED','RETRIED')""",
1740
+ (project_id,),
1741
+ ).fetchall()
1742
+ return {**queue, "operator_handling": {str(run_id): str(resolution) for run_id, resolution in rows}}
1743
+ raise local_repository_binding.LocalRepositoryBindingError("CONSOLE_PROJECT_UNAVAILABLE")
1744
+
1745
+
1746
+ def _console_platform_version() -> str:
1747
+ """Read the installed platform version once for CENTRAL Console snapshots."""
1748
+ return EngineeringPlatformManifest.load(
1749
+ package_path("ENGINEERING_PLATFORM_VERSION.json")
1750
+ ).platform_version
1751
+
1752
+
1753
+ def _central_console_project_snapshot(data_root: Path, project_id: str) -> dict[str, object]:
1754
+ """Return the Slice-B project status/history projection from CENTRAL only."""
1755
+ queue = _console_queue_projection(data_root, project_id)
1756
+ with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection:
1757
+ runs = connection.execute(
1758
+ """SELECT run_id,state,created_at,updated_at,execution_mode
1759
+ FROM ep_execution_runs WHERE project_id=?
1760
+ ORDER BY created_at DESC,run_id DESC LIMIT 1000""",
1761
+ (project_id,),
1762
+ ).fetchall()
1763
+ dispatches = dict(connection.execute(
1764
+ "SELECT run_id,state FROM ep_parity_lifecycle_dispatches WHERE project_id=?",
1765
+ (project_id,),
1766
+ ).fetchall())
1767
+ records = [
1768
+ {
1769
+ "run_id": str(run_id), "status": str(dispatches.get(run_id, state)),
1770
+ "state": str(dispatches.get(run_id, state)), "created_at": str(created_at),
1771
+ "updated_at": str(updated_at), "execution_mode": execution_mode,
1772
+ "project_id": project_id,
1773
+ }
1774
+ for run_id, state, created_at, updated_at, execution_mode in runs
1775
+ ]
1776
+ active = next((record for record in records if record["state"] in {"CLAIMED", "RUNNING"}), None)
1777
+ return {
1778
+ "project_id": project_id,
1779
+ "scope": "PROJECT",
1780
+ "status": {
1781
+ "project_id": project_id,
1782
+ "platform_version": _console_platform_version(),
1783
+ "queue_depth": queue["queue_depth"],
1784
+ "queue_items": queue["queue_items"],
1785
+ "active_run": active["run_id"] if active else None,
1786
+ "last_executed_run": records[0]["run_id"] if records else None,
1787
+ "lifecycle_source": "CENTRAL",
1788
+ },
1789
+ "runs": records,
1790
+ "queue": queue,
1791
+ "telemetry": _central_console_telemetry(data_root, project_id),
1792
+ }
1793
+
1794
+
1795
+ def _no_project_console_snapshot(data_root: Path) -> dict[str, object]:
1796
+ """Give the Console a loadable CENTRAL-only snapshot at ``<geen>``.
1797
+
1798
+ The no-project document deliberately hides project state, but the shared
1799
+ dashboard shell still hydrates through the snapshot endpoint. Returning a
1800
+ minimal platform projection prevents it from remaining behind the loading
1801
+ overlay while preserving the fail-closed boundary for all project routes.
1802
+ """
1803
+ # Aggregate the canonical CENTRAL submission state only. File Inbox files,
1804
+ # watcher backlogs and transport retry diagnostics never affect this count.
1805
+ with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection:
1806
+ queue_depth = int(connection.execute(
1807
+ "SELECT COUNT(*) FROM ep_submissions WHERE state IN ('QUEUED','ADMITTED')"
1808
+ ).fetchone()[0])
1809
+ queue = {"operator_handling": {}, "queue_depth": queue_depth, "queue_items": [], "scope": "ALL_PROJECTS"}
1810
+ return {
1811
+ "scope": "PLATFORM",
1812
+ "queue": queue,
1813
+ "runs": [],
1814
+ "telemetry": [],
1815
+ "status": {
1816
+ "lifecycle_source": "CENTRAL",
1817
+ "platform_version": _console_platform_version(),
1818
+ **queue,
1819
+ },
1820
+ }
1821
+
1822
+
1823
+ def _central_console_run_detail(data_root: Path, project_id: str, run_id: str) -> dict[str, object] | None:
1824
+ """Resolve a run by canonical project/run identity, never by checkout."""
1825
+ snapshot = _central_console_project_snapshot(data_root, project_id)
1826
+ return next((record for record in snapshot["runs"] if record["run_id"] == run_id), None)
1827
+
1828
+
1829
+ def _central_console_telemetry(data_root: Path, project_id: str) -> list[dict[str, object]]:
1830
+ """Read bounded daily telemetry through CENTRAL's run/project lineage."""
1831
+ with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection:
1832
+ rows = connection.execute(
1833
+ """SELECT r.execution_date,COUNT(*),
1834
+ SUM(r.terminal_state='COMPLETE'),SUM(r.terminal_state='BLOCKED'),SUM(r.terminal_state='FAILED'),
1835
+ AVG(r.execution_seconds),AVG(r.total_execution_seconds),AVG(r.queue_wait_seconds),
1836
+ SUM(r.input_tokens),SUM(r.output_tokens),SUM(r.total_tokens)
1837
+ FROM execution_runs AS r
1838
+ JOIN ep_parity_lifecycle_dispatches AS d ON d.run_id=r.run_id
1839
+ WHERE d.project_id=?
1840
+ GROUP BY r.execution_date ORDER BY r.execution_date DESC LIMIT 360""",
1841
+ (project_id,),
1842
+ ).fetchall()
1843
+ keys = (
1844
+ "date", "prompt_count", "complete_count", "blocked_count", "failed_count",
1845
+ "average_execution_seconds", "average_total_execution_seconds", "average_queue_wait_seconds",
1846
+ "input_tokens", "output_tokens", "total_tokens",
1847
+ )
1848
+ return [dict(zip(keys, row, strict=True)) | {
1849
+ "average_provider_execution_seconds": None, "average_validation_seconds": None,
1850
+ } for row in rows]
1851
+
1852
+
1853
+ def _central_console_telemetry_detail(data_root: Path, project_id: str, execution_date: str) -> dict[str, object] | None:
1854
+ """Provide a project-isolated CENTRAL telemetry day without root fallback."""
1855
+ if not re.fullmatch(r"[0-9]{4}-[0-9]{2}-[0-9]{2}", execution_date):
1856
+ return None
1857
+ with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection:
1858
+ rows = connection.execute(
1859
+ """SELECT r.run_id,r.execution_started_at,r.terminal_state,r.total_execution_seconds,
1860
+ r.queue_wait_seconds,r.runtime_provider,r.runtime_model,r.reasoning_profile,
1861
+ r.producer_type,r.repository
1862
+ FROM execution_runs AS r
1863
+ JOIN ep_parity_lifecycle_dispatches AS d ON d.run_id=r.run_id
1864
+ WHERE d.project_id=? AND r.execution_date=?
1865
+ ORDER BY r.execution_started_at DESC LIMIT 250""",
1866
+ (project_id, execution_date),
1867
+ ).fetchall()
1868
+ if not rows:
1869
+ return None
1870
+ run_rows = [{
1871
+ "run_id": str(row[0]), "started_at": row[1], "status": row[2],
1872
+ "total_duration_ms": round(float(row[3]) * 1000) if isinstance(row[3], (float, int)) else None,
1873
+ "queue_wait_ms": round(float(row[4]) * 1000) if isinstance(row[4], (float, int)) else None,
1874
+ "provider_duration_ms": None, "validation_duration_ms": None, "external_wait_ms": None,
1875
+ "largest_phase": None, "producer_type": row[8], "repository": row[9],
1876
+ "provider": row[5], "model": row[6], "reasoning_profile": row[7],
1877
+ "phase_telemetry": "NOT_RECORDED",
1878
+ } for row in rows]
1879
+ durations = [row["total_duration_ms"] for row in run_rows if isinstance(row["total_duration_ms"], int)]
1880
+ waits = [row["queue_wait_ms"] for row in run_rows if isinstance(row["queue_wait_ms"], int)]
1881
+ def aggregate(values: list[int]) -> dict[str, int] | None:
1882
+ return {"average_ms": round(sum(values) / len(values)), "median_ms": sorted(values)[len(values) // 2], "total_ms": sum(values), "runs": len(values)} if values else None
1883
+ return {
1884
+ "date": execution_date, "timezone": "UTC", "runs": run_rows, "phases": [], "phase_telemetry_available": False,
1885
+ "summary": {"executions": len(run_rows), "completed": sum(row["status"] == "COMPLETE" for row in run_rows),
1886
+ "blocked": sum(row["status"] == "BLOCKED" for row in run_rows), "failed": sum(row["status"] == "FAILED" for row in run_rows),
1887
+ "total_wall_time": aggregate(durations), "queue_wait": aggregate(waits),
1888
+ "active_processing_time": None, "provider_execution": None, "validation": None, "external_wait": None, "overhead": None,
1889
+ "report_generation": None, "evidence_persistence": None},
1890
+ "bottlenecks": {"longest_average_phase": None, "largest_accumulated_phase": None, "top_time_consumers": [], "shares": {}},
1891
+ }
1892
+
1893
+
1894
+ def _central_console_report(data_root: Path, project_id: str, run_id: str) -> bytes | None:
1895
+ """Read one CENTRAL-indexed immutable report with project authorization."""
1896
+ with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection:
1897
+ row = connection.execute(
1898
+ """SELECT h.report_path FROM prompt_execution_history AS h
1899
+ JOIN ep_parity_lifecycle_dispatches AS d ON d.run_id=h.run_id
1900
+ WHERE d.project_id=? AND h.run_id=?""",
1901
+ (project_id, run_id),
1902
+ ).fetchone()
1903
+ if row is None or not isinstance(row[0], str) or not row[0].startswith("CENTRAL:"):
1904
+ return None
1905
+ candidate = (data_root / "artifacts" / row[0].removeprefix("CENTRAL:")).resolve()
1906
+ try:
1907
+ candidate.relative_to((data_root / "artifacts").resolve())
1908
+ return candidate.read_bytes() if candidate.is_file() else None
1909
+ except (OSError, ValueError):
1910
+ return None
1911
+
1912
+
1913
+ def _central_console_chat_history(data_root: Path, project_id: str, run_id: str) -> list[dict[str, object]] | None:
1914
+ """Return a project-authorized CENTRAL transcript; no root fallback exists."""
1915
+ with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection:
1916
+ belongs = connection.execute(
1917
+ "SELECT 1 FROM ep_parity_lifecycle_dispatches WHERE project_id=? AND run_id=?",
1918
+ (project_id, run_id),
1919
+ ).fetchone()
1920
+ if belongs is None:
1921
+ return None
1922
+ rows = connection.execute(
1923
+ "SELECT role,content,model,created_at FROM execution_chat_messages WHERE run_id=? ORDER BY id",
1924
+ (run_id,),
1925
+ ).fetchall()
1926
+ return [{"role": str(role), "content": str(content), "model": model, "created_at": str(created_at)}
1927
+ for role, content, model, created_at in rows]
1928
+
1929
+
1930
+ @dataclass(frozen=True)
1931
+ class _CentralLogQuery:
1932
+ page: int
1933
+ page_size: int
1934
+ start_at: str
1935
+ end_at: str
1936
+ inclusive_end: bool
1937
+ search: str
1938
+ level: str
1939
+ events: tuple[str, ...]
1940
+ sort_key: str
1941
+ direction: str
1942
+
1943
+
1944
+ def _parse_central_log_query(values: dict[str, list[str]] | None) -> _CentralLogQuery:
1945
+ """Validate the public query once, before composing any CENTRAL SQL."""
1946
+ query = values or {}
1947
+
1948
+ def first(name: str, default: str = "") -> str:
1949
+ return str((query.get(name) or [default])[0])
1950
+
1951
+ try:
1952
+ page = int(first("page", "1"))
1953
+ page_size = int(first("page_size", "50"))
1954
+ except ValueError as error:
1955
+ raise ValueError("Invalid component-log pagination.") from error
1956
+ if page < 1 or not 1 <= page_size <= MAX_COMPONENT_LOG_PAGE_SIZE:
1957
+ raise ValueError("Invalid component-log pagination.")
1958
+ level, search = first("level").upper().strip(), first("search").strip()
1959
+ if level and level not in VALID_LEVELS:
1960
+ raise ValueError("Invalid component-log level.")
1961
+ if len(search) > 160:
1962
+ raise ValueError("Component-log search is too long.")
1963
+ events = tuple(sorted({value.strip() for value in query.get("event", []) if value.strip()}))
1964
+ if len(events) > 50 or any(len(event) > 160 for event in events):
1965
+ raise ValueError("Invalid component-log event filter.")
1966
+ sort_key, direction = first("sort", "timestamp"), first("direction", "desc").lower()
1967
+ if sort_key not in _CENTRAL_LOG_SORT_COLUMNS or direction not in {"asc", "desc"}:
1968
+ raise ValueError("Invalid component-log sort.")
1969
+ return _CentralLogQuery(
1970
+ page=page,
1971
+ page_size=page_size,
1972
+ start_at=first("start").strip(),
1973
+ end_at=first("end").strip(),
1974
+ inclusive_end=first("inclusive_end") == "1",
1975
+ search=search,
1976
+ level=level,
1977
+ events=events,
1978
+ sort_key=sort_key,
1979
+ direction=direction,
1980
+ )
1981
+
1982
+
1983
+ def _central_log_components(component: str) -> tuple[frozenset[str], tuple[str, ...]] | None:
1984
+ """Resolve only canonical component identities without project context."""
1985
+ if component == "all":
1986
+ selected = PLATFORM_COMPONENT_IDS
1987
+ elif component in PLATFORM_COMPONENT_IDS:
1988
+ selected = frozenset({component})
1989
+ else:
1990
+ return None
1991
+ return selected, tuple(selected)
1992
+
1993
+
1994
+ def _central_console_component_logs(
1995
+ data_root: Path,
1996
+ component: str,
1997
+ query: dict[str, list[str]] | None = None,
1998
+ *,
1999
+ export_all: bool = False,
2000
+ ) -> dict[str, object] | None:
2001
+ """Read one filtered, sorted CENTRAL log page before it reaches the Console."""
2002
+ selection = _central_log_components(component)
2003
+ if selection is None:
2004
+ return None
2005
+ _, stored_components = selection
2006
+ filters = _parse_central_log_query(query)
2007
+ clauses = ["component IN (" + ",".join("?" for _ in stored_components) + ")"]
2008
+ parameters: list[object] = list(stored_components)
2009
+ if filters.start_at:
2010
+ clauses.append("created_at >= ?")
2011
+ parameters.append(filters.start_at)
2012
+ if filters.end_at:
2013
+ clauses.append("created_at <= ?" if filters.inclusive_end else "created_at < ?")
2014
+ parameters.append(filters.end_at)
2015
+ if filters.search:
2016
+ escaped = filters.search.lower().replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
2017
+ clauses.append("LOWER(payload) LIKE ? ESCAPE '\\'")
2018
+ parameters.append(f"%{escaped}%")
2019
+ if filters.level in LOG_LEVELS_AT_OR_ABOVE:
2020
+ levels = LOG_LEVELS_AT_OR_ABOVE[filters.level]
2021
+ clauses.append("json_extract(payload, '$.level') IN (" + ",".join("?" for _ in levels) + ")")
2022
+ parameters.extend(levels)
2023
+ event_option_clauses, event_option_parameters = list(clauses), list(parameters)
2024
+ if filters.events:
2025
+ clauses.append("json_extract(payload, '$.event') IN (" + ",".join("?" for _ in filters.events) + ")")
2026
+ parameters.extend(filters.events)
2027
+ where = " WHERE " + " AND ".join(clauses)
2028
+ with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection:
2029
+ total = int(connection.execute("SELECT COUNT(*) FROM engineering_component_logs" + where, parameters).fetchone()[0])
2030
+ rows = connection.execute(
2031
+ "SELECT id,component,payload,created_at FROM engineering_component_logs" + where
2032
+ + f" ORDER BY {_CENTRAL_LOG_SORT_COLUMNS[filters.sort_key]} {filters.direction.upper()}, id {filters.direction.upper()} LIMIT ? OFFSET ?",
2033
+ [
2034
+ *parameters,
2035
+ 5000 if export_all else filters.page_size,
2036
+ 0 if export_all else (filters.page - 1) * filters.page_size,
2037
+ ],
2038
+ ).fetchall()
2039
+ event_rows = connection.execute(
2040
+ "SELECT DISTINCT json_extract(payload, '$.event') FROM engineering_component_logs WHERE "
2041
+ + " AND ".join(event_option_clauses)
2042
+ + " AND json_extract(payload, '$.event') IS NOT NULL ORDER BY 1 LIMIT 500",
2043
+ event_option_parameters,
2044
+ ).fetchall()
2045
+ entries: list[dict[str, object]] = []
2046
+ for identifier, stored_component, payload, created_at in rows:
2047
+ try:
2048
+ decoded = json.loads(str(payload))
2049
+ except (TypeError, ValueError, json.JSONDecodeError):
2050
+ decoded = {"event": "malformed_central_log"}
2051
+ record = decoded if isinstance(decoded, dict) else {}
2052
+ entries.append({"line": int(identifier), "timestamp": str(created_at), **record, "component": str(stored_component)})
2053
+ return {
2054
+ "scope": "PLATFORM", "component": component, "entries": entries,
2055
+ "page": filters.page, "page_size": filters.page_size, "total": total,
2056
+ "events": [str(row[0]) for row in event_rows if row[0]],
2057
+ }
2058
+
2059
+
2060
+ def _clear_central_console_component_logs(data_root: Path, component: str) -> dict[str, object] | None:
2061
+ """Delete only the explicitly selected CENTRAL component-log projection."""
2062
+ if component == "all":
2063
+ selected = PLATFORM_COMPONENT_IDS
2064
+ elif component in PLATFORM_COMPONENT_IDS:
2065
+ selected = frozenset({component})
2066
+ else:
2067
+ return None
2068
+ stored = tuple(selected)
2069
+ with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection:
2070
+ cursor = connection.execute(
2071
+ "DELETE FROM engineering_component_logs WHERE component IN (" + ",".join("?" for _ in stored) + ")",
2072
+ stored,
2073
+ )
2074
+ return {"scope": "PLATFORM", "component": component, "deleted": int(cursor.rowcount)}
2075
+
2076
+
2077
+ def _central_console_configuration(data_root: Path) -> dict[str, object]:
2078
+ """Expose only configuration already owned by CENTRAL in this phase."""
2079
+ return {
2080
+ "scope": "PLATFORM",
2081
+ **central_database.maintenance_configuration(data_root),
2082
+ **central_database.capacity_configuration(data_root),
2083
+ **central_database.console_interval_configuration(data_root),
2084
+ }
2085
+
2086
+
2087
+ def _central_provider_readiness(data_root: Path) -> dict[str, dict[str, object]]:
2088
+ """Project-independent, token-free authentication readiness for CENTRAL."""
2089
+ statuses = provider_readiness.host_status(data_root)
2090
+ runtime = provider_readiness.runtime_details(data_root)
2091
+ return {
2092
+ provider: {**value, **runtime.get(provider, {}), "scope": "PLATFORM"}
2093
+ for provider, value in statuses.items()
2094
+ }
2095
+
2096
+
2097
+ def _audit_dashboard_provider_action(
2098
+ data_root: Path, provider: str, action: str, outcome: str, *, level: int = logging.INFO,
2099
+ ) -> None:
2100
+ """Persist a secret-free audit fact for one host-wide Console action."""
2101
+ log_event(
2102
+ component_logger(
2103
+ data_root, "operations_console", central_database=data_root / SERVER_DATABASE_FILENAME,
2104
+ ),
2105
+ level,
2106
+ f"provider_action_{outcome.lower()}",
2107
+ context={
2108
+ "provider": provider,
2109
+ "provider_action": action,
2110
+ "provider_action_source": "DASHBOARD",
2111
+ "audit_outcome": outcome,
2112
+ },
2113
+ )
2114
+
2115
+
2116
+ def _central_provider_repair(data_root: Path, payload: object) -> None:
2117
+ """Start one validated host-wide provider action without a checkout."""
2118
+ if not isinstance(payload, dict) or set(payload) != {"provider", "action"}:
2119
+ raise ValueError("Invalid provider repair request.")
2120
+ provider, action = str(payload["provider"]), str(payload["action"])
2121
+ if provider not in {"CODEX", "GITHUB"} or action not in {"login", "install"}:
2122
+ raise ValueError("Invalid provider repair request.")
2123
+ _audit_dashboard_provider_action(data_root, provider, action, "REQUESTED")
2124
+ try:
2125
+ readiness = _central_provider_readiness(data_root)
2126
+ state = str(readiness[provider.lower()]["state"])
2127
+ if (action == "login" and state != "AUTH_REQUIRED") or (action == "install" and state != "UNAVAILABLE"):
2128
+ raise ValueError("Provider is not ready for the requested repair.")
2129
+ if action == "login":
2130
+ _start_provider_login(data_root, provider)
2131
+ _audit_dashboard_provider_action(data_root, provider, action, "STARTED")
2132
+ else:
2133
+ _install_provider(data_root, provider)
2134
+ _audit_dashboard_provider_action(data_root, provider, action, "COMPLETED")
2135
+ except (OSError, RuntimeError, ValueError):
2136
+ _audit_dashboard_provider_action(data_root, provider, action, "FAILED", level=logging.WARNING)
2137
+ raise
2138
+
2139
+
2140
+ def _central_provider_logout(data_root: Path, payload: object) -> None:
2141
+ """Remove one verified host-wide provider session through CENTRAL.
2142
+
2143
+ The Console never owns provider credentials. It can only request this
2144
+ narrowly validated host operation while the provider is known ready, so a
2145
+ stale or fabricated UI request cannot be delegated to a checkout-bound
2146
+ legacy handler.
2147
+ """
2148
+ if not isinstance(payload, dict) or set(payload) != {"provider"}:
2149
+ raise ValueError("Invalid provider logout request.")
2150
+ provider = str(payload["provider"])
2151
+ if provider not in {"CODEX", "GITHUB"}:
2152
+ raise ValueError("Invalid provider logout request.")
2153
+ _audit_dashboard_provider_action(data_root, provider, "logout", "REQUESTED")
2154
+ try:
2155
+ readiness = _central_provider_readiness(data_root)
2156
+ if str(readiness[provider.lower()]["state"]) != "READY":
2157
+ raise ValueError("Provider is not ready for logout.")
2158
+ _logout_provider(data_root, provider)
2159
+ _audit_dashboard_provider_action(data_root, provider, "logout", "COMPLETED")
2160
+ except (OSError, RuntimeError, ValueError):
2161
+ _audit_dashboard_provider_action(data_root, provider, "logout", "FAILED", level=logging.WARNING)
2162
+ raise
2163
+
2164
+
2165
+ def _with_console_queue(payload: bytes, *, queue: dict[str, object], data_root: Path) -> bytes:
2166
+ """Overlay CENTRAL-only queue and provider evidence onto legacy payloads."""
2167
+ try:
2168
+ decoded = json.loads(payload)
2169
+ except (TypeError, ValueError, json.JSONDecodeError):
2170
+ return payload
2171
+ if not isinstance(decoded, dict):
2172
+ return payload
2173
+ handling = queue.get("operator_handling")
2174
+ if isinstance(handling, dict) and isinstance(decoded.get("runs"), list):
2175
+ for run in decoded["runs"]:
2176
+ if isinstance(run, dict) and handling.get(run.get("run_id")) == "DISMISSED":
2177
+ run["dismissed"] = True
2178
+ run["handling_state"] = "DISMISSED"
2179
+ status_payload = decoded.get("status")
2180
+ if isinstance(status_payload, dict):
2181
+ decoded["status"] = {**status_payload, **queue}
2182
+ else:
2183
+ decoded = {**decoded, **queue}
2184
+ rate_limits = decoded.get("rate_limits")
2185
+ if isinstance(rate_limits, dict):
2186
+ provider = rate_limits.get("provider")
2187
+ remaining = _remaining_rate_limit_capacity(rate_limits)
2188
+ if isinstance(provider, str) and remaining is not None:
2189
+ decoded["ai_capacity_history"] = central_database.record_provider_capacity(
2190
+ data_root, provider=provider, remaining_percent=remaining,
2191
+ )
2192
+ decoded["capacity_scope"] = "EP"
2193
+ decoded["capacity_configuration"] = central_database.capacity_configuration(data_root)
2194
+ return json.dumps(decoded, separators=(",", ":")).encode("utf-8")
2195
+
2196
+
2197
+ def _provider_capacity_projection(data_root: Path) -> dict[str, object]:
2198
+ """Read the account-owned quota once and project it from CENTRAL."""
2199
+ try:
2200
+ payload = json.loads(_codex_rate_limits())
2201
+ except (TypeError, ValueError, json.JSONDecodeError):
2202
+ payload = {}
2203
+ if not isinstance(payload, dict):
2204
+ payload = {}
2205
+ provider = payload.get("provider")
2206
+ remaining = _remaining_rate_limit_capacity(payload)
2207
+ history: list[dict[str, object]] = []
2208
+ if isinstance(provider, str) and remaining is not None:
2209
+ history = central_database.record_provider_capacity(
2210
+ data_root, provider=provider, remaining_percent=remaining,
2211
+ )
2212
+ return {
2213
+ "rate_limits": payload,
2214
+ "ai_capacity_history": history,
2215
+ "scope": "EP",
2216
+ "configuration": central_database.capacity_configuration(data_root),
2217
+ }
2218
+
2219
+
2220
+ def _console_project_options(project_id: str | None, projects: list[dict[str, str]]) -> str:
2221
+ """Render the safe empty choice plus registered CENTRAL identities."""
2222
+ empty = '<option value=""' + (" selected" if project_id is None else "") + '>&lt;geen&gt;</option>'
2223
+ return empty + "".join(
2224
+ f'<option value="{escape(item["project_id"], quote=True)}"'
2225
+ f'{" selected" if item["project_id"] == project_id else ""}>'
2226
+ f'{escape(item["project_id"])}</option>'
2227
+ for item in projects
2228
+ )
2229
+
2230
+
2231
+ def _central_database_section(data_root: Path) -> str:
2232
+ """Render the one installation-owned EP database panel for Configuration."""
2233
+ details = central_database.details(data_root)
2234
+ interval = central_database.maintenance_configuration(data_root)["interval_seconds"]
2235
+ size = f"{int(details['size_bytes']) / 1_000_000:.2f}".replace(".", ",") + " MB"
2236
+ options = "".join(
2237
+ f'<option value="{value}"{" selected" if value == interval else ""}>{label}</option>'
2238
+ for value, label in ((60, "1 minuut"), (3600, "1 uur"), (86400, "1 dag"), (604800, "1 week"))
2239
+ )
2240
+ return (
2241
+ '<section class="configuration-central-database" aria-labelledby="centralDatabaseHeading">'
2242
+ '<header class="configuration-central-database__header">'
2243
+ '<div><h2 id="centralDatabaseHeading" data-i18n="configuration.ep_database">EP-database</h2>'
2244
+ '<p data-i18n="configuration.ep_database_description">Platformbrede opslag voor projecten, uitvoeringen en configuratie.</p></div>'
2245
+ '<div class="configuration-central-database__actions"><a class="configuration-central-database__export" href="/api/central-data/export" download '
2246
+ 'data-i18n="configuration.central_data_export" data-i18n-aria-label="configuration.central_data_export" '
2247
+ 'aria-label="Exporteer platformgegevens">Exporteer platformgegevens</a><button class="configuration-central-database__relocate" id="centralDataImport" type="button" data-i18n="configuration.central_data_import">Importeer platformgegevens</button></div></header>'
2248
+ '<dl class="configuration-central-database__facts">'
2249
+ f'<div class="configuration-central-database__location"><dt class="label" data-i18n="configuration.platform_data_location">Platformgegevenslocatie</dt><dd class="configuration-central-database__location-value"><button class="configuration-central-database__location-link local-folder-link" type="button" data-local-path="{escape(str(data_root.resolve()))}">{escape(str(data_root.resolve()))}</button><button class="configuration-central-database__relocate" id="centralDatabaseRelocate" type="button" data-i18n="configuration.relocate_platform_data">Verplaats platformgegevens</button></dd></div>'
2250
+ f'<div><dt class="label" data-i18n="configuration.database_size">Databasegrootte</dt><dd>{size}</dd></div>'
2251
+ f'<div><dt class="label" data-i18n="configuration.schema_version">Schema-versie</dt><dd>{details["schema_version"]}</dd></div>'
2252
+ f'<div><dt class="label" data-i18n="configuration.integrity">Integriteit</dt><dd data-i18n="configuration.database_integrity.{details["integrity"]}">{details["integrity"]}</dd></div>'
2253
+ '</dl>'
2254
+ '<div class="configuration-central-database__maintenance">'
2255
+ '<div><span class="label" id="centralDatabaseMaintenanceLabel" data-i18n="configuration.ep_database_maintenance">Databaseonderhoud</span>'
2256
+ '<p id="centralDatabaseMaintenanceHelp" data-i18n="configuration.ep_database_maintenance_help">Optimaliseert de EP-database wanneer geen uitvoering actief is.</p></div>'
2257
+ f'<select id="centralDatabaseMaintenanceInterval" aria-labelledby="centralDatabaseMaintenanceLabel" aria-describedby="centralDatabaseMaintenanceHelp centralDatabaseMaintenanceStatus" data-saved-value="{interval}">{options}</select>'
2258
+ '</div>'
2259
+ '<p id="centralDatabaseMaintenanceStatus" role="status" aria-live="polite"></p></section>'
2260
+ f'<dialog class="dashboard-modal-shell dashboard-modal-shell--confirmation installation-relocation-modal" id="centralDatabaseRelocateModal"><section class="dashboard-modal-shell__panel"><header class="dashboard-modal-shell__header"><h2 data-modal-glyph="relocate" data-i18n="configuration.relocate_platform_data">Verplaats platformgegevens</h2><button class="dashboard-modal-shell__close" type="button" aria-label="Close" data-close-relocation="centralDatabaseRelocateModal">×</button></header><p data-i18n="configuration.relocate_platform_data_help">Verplaats alle platformgegevens als één geheel. De server stopt veilig en start daarna opnieuw.</p><dl class="installation-relocation-modal__locations"><div><dt data-i18n="configuration.current_folder">Huidige map</dt><dd><button class="local-folder-link" type="button" data-local-path="{escape(str(data_root.resolve()))}">{escape(str(data_root.resolve()))}</button></dd></div><div id="centralDatabaseRelocateDestination" hidden><dt data-i18n="configuration.new_folder">Nieuwe map</dt><dd id="centralDatabaseRelocateDestinationValue"></dd></div></dl><input id="centralDatabaseRelocateDirectory" type="hidden"><div class="dashboard-modal-shell__actions"><button class="dashboard-modal-shell__action" id="centralDatabaseRelocateBrowse" type="button" data-i18n="configuration.choose_folder">Kies map</button><button class="dashboard-modal-shell__action dashboard-modal-shell__action--primary" id="centralDatabaseRelocateSave" type="button" disabled data-i18n="configuration.relocate">Verplaatsen</button></div><p id="centralDatabaseRelocateStatus" role="status"></p></section></dialog>'
2261
+ '<dialog class="dashboard-modal-shell dashboard-modal-shell--confirmation installation-relocation-modal" id="centralDataImportModal"><section class="dashboard-modal-shell__panel"><header class="dashboard-modal-shell__header"><h2 data-modal-glyph="relocate" data-i18n="configuration.central_data_import">Importeer platformgegevens</h2><button class="dashboard-modal-shell__close" type="button" aria-label="Close" data-close-central-import>×</button></header><p data-i18n="configuration.central_data_import_help">Kies een eerder geëxporteerd ZIP-bestand. Alle huidige platformgegevens worden vervangen.</p><input id="centralDataImportFile" type="file" accept="application/zip,.zip"><p class="installation-relocation-modal__warning" data-i18n="configuration.central_data_import_warning">Dit vervangt de volledige huidige platformstatus.</p><div class="dashboard-modal-shell__actions"><button class="dashboard-modal-shell__action dashboard-modal-shell__action--primary" id="centralDataImportConfirm" type="button" disabled data-i18n="configuration.central_data_import_confirm">Importeren en herstarten</button></div><p id="centralDataImportStatus" role="status"></p></section></dialog>'
2262
+ )
2263
+
2264
+
2265
+ def _central_database_script() -> str:
2266
+ """Bind CENTRAL maintenance plus whole-state transfer controls."""
2267
+ return '''for(const id of ['centralDatabaseRelocateModal','centralDataImportModal','fileInboxRelocateModal']){const dialog=document.getElementById(id);if(dialog&&dialog.parentElement!==document.body)document.body.append(dialog)}const maintenance=document.getElementById('centralDatabaseMaintenanceInterval'),maintenanceStatus=document.getElementById('centralDatabaseMaintenanceStatus'),translate=window.__engineeringPlatformDashboardTranslate;if(maintenance)maintenance.addEventListener('change',async()=>{const previous=maintenance.dataset.savedValue||maintenance.value,requested=Number(maintenance.value);maintenance.disabled=true;try{const response=await fetch('/api/central-database/configuration',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({interval_seconds:requested})});const result=response.ok?await response.json():null;if(!result||Number(result.interval_seconds)!==requested)throw Error();maintenance.dataset.savedValue=String(requested);if(maintenanceStatus)maintenanceStatus.textContent=translate('configuration.ep_database_maintenance_saved')}catch{maintenance.value=previous;if(maintenanceStatus)maintenanceStatus.textContent=translate('configuration.ep_database_maintenance_failed')}finally{maintenance.disabled=false}});const modal=document.getElementById('centralDatabaseRelocateModal'),open=document.getElementById('centralDatabaseRelocate'),input=document.getElementById('centralDatabaseRelocateDirectory'),destination=document.getElementById('centralDatabaseRelocateDestination'),destinationValue=document.getElementById('centralDatabaseRelocateDestinationValue'),save=document.getElementById('centralDatabaseRelocateSave'),status=document.getElementById('centralDatabaseRelocateStatus'),showDestination=value=>{input.value=value;destinationValue.replaceChildren(window.__engineeringPlatformLocalFilesystemLink(value));destination.hidden=false;save.disabled=false;};open?.addEventListener('click',()=>modal.showModal());modal?.querySelector('[data-close-relocation]')?.addEventListener('click',()=>modal.close());document.getElementById('centralDatabaseRelocateBrowse')?.addEventListener('click',async()=>{const r=await fetch('/api/central-data/relocate/browse',{method:'POST'}),p=await r.json();if(p.value)showDestination(p.value);});save?.addEventListener('click',async()=>{const r=await fetch('/api/central-data/relocate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({directory:input.value})}),p=await r.json();status.textContent=r.ok?translate('configuration.relocation_restarting'):p.error||translate('configuration.relocation_failed');if(r.ok)setTimeout(()=>location.reload(),2500);});const importModal=document.getElementById('centralDataImportModal'),importOpen=document.getElementById('centralDataImport'),importFile=document.getElementById('centralDataImportFile'),importSave=document.getElementById('centralDataImportConfirm'),importStatus=document.getElementById('centralDataImportStatus');importOpen?.addEventListener('click',()=>importModal.showModal());importModal?.querySelector('[data-close-central-import]')?.addEventListener('click',()=>importModal.close());importFile?.addEventListener('change',()=>{importSave.disabled=!importFile.files?.length;});importSave?.addEventListener('click',async()=>{const file=importFile.files?.[0];if(!file)return;importSave.disabled=true;const r=await fetch('/api/central-data/import',{method:'POST',headers:{'Content-Type':'application/zip','X-EP-Central-Import-Confirmed':'true'},body:file}),p=await r.json();importStatus.textContent=r.ok?translate('configuration.relocation_restarting'):p.error||translate('configuration.relocation_failed');if(r.ok)setTimeout(()=>location.reload(),2500);else importSave.disabled=false;});'''
2268
+
2269
+
2270
+ def _file_inbox_section(data_root: Path) -> str:
2271
+ """Render File Inbox relocation with the same installation-owned affordance."""
2272
+ location = escape(str((data_root / FILE_INBOX_DIRECTORY).resolve()))
2273
+ return f'''<section class="configuration-central-database" aria-labelledby="fileInboxHeading"><header class="configuration-central-database__header"><div><h2 id="fileInboxHeading" data-i18n="configuration.file_inbox">File Inbox</h2><p data-i18n="configuration.file_inbox_relocation_help">Verplaats de File Inbox alleen wanneer deze leeg is.</p></div></header><dl class="configuration-central-database__facts"><div class="configuration-central-database__location"><dt class="label" data-i18n="configuration.file_inbox_location">File Inbox-locatie</dt><dd class="configuration-central-database__location-value"><button class="configuration-central-database__location-link local-folder-link" type="button" data-local-path="{location}">{location}</button><button class="configuration-central-database__relocate" id="fileInboxRelocate" type="button" data-i18n="configuration.relocate_file_inbox">Verplaats File Inbox</button></dd></div></dl></section><dialog class="dashboard-modal-shell dashboard-modal-shell--confirmation installation-relocation-modal" id="fileInboxRelocateModal"><section class="dashboard-modal-shell__panel"><header class="dashboard-modal-shell__header"><h2 data-modal-glyph="relocate" data-i18n="configuration.relocate_file_inbox">Verplaats File Inbox</h2><button class="dashboard-modal-shell__close" type="button" aria-label="Close" data-close-relocation="fileInboxRelocateModal">×</button></header><p data-i18n="configuration.file_inbox_relocation_help">Verplaats de File Inbox alleen wanneer deze leeg is.</p><dl class="installation-relocation-modal__locations"><div><dt data-i18n="configuration.current_folder">Huidige map</dt><dd><button class="local-folder-link" type="button" data-local-path="{location}">{location}</button></dd></div><div id="fileInboxRelocateDestination" hidden><dt data-i18n="configuration.new_folder">Nieuwe map</dt><dd id="fileInboxRelocateDestinationValue"></dd></div></dl><input id="fileInboxRelocateDirectory" type="hidden"><div class="dashboard-modal-shell__actions"><button class="dashboard-modal-shell__action" id="fileInboxRelocateBrowse" type="button" data-i18n="configuration.choose_folder">Kies map</button><button class="dashboard-modal-shell__action dashboard-modal-shell__action--primary" id="fileInboxRelocateSave" type="button" disabled data-i18n="configuration.relocate">Verplaatsen</button></div><p id="fileInboxRelocateStatus" role="status"></p></section></dialog>'''
2274
+
2275
+
2276
+ def _file_inbox_relocation_script() -> str:
2277
+ return '''const inboxModal=document.getElementById('fileInboxRelocateModal'),inboxOpen=document.getElementById('fileInboxRelocate'),inboxInput=document.getElementById('fileInboxRelocateDirectory'),inboxDestination=document.getElementById('fileInboxRelocateDestination'),inboxDestinationValue=document.getElementById('fileInboxRelocateDestinationValue'),inboxSave=document.getElementById('fileInboxRelocateSave'),inboxStatus=document.getElementById('fileInboxRelocateStatus'),showInboxDestination=value=>{inboxInput.value=value;inboxDestinationValue.replaceChildren(window.__engineeringPlatformLocalFilesystemLink(value));inboxDestination.hidden=false;inboxSave.disabled=false;};inboxOpen?.addEventListener('click',()=>inboxModal.showModal());inboxModal?.querySelector('[data-close-relocation]')?.addEventListener('click',()=>inboxModal.close());document.getElementById('fileInboxRelocateBrowse')?.addEventListener('click',async()=>{const r=await fetch('/api/configuration/file-inbox/relocate/browse',{method:'POST'}),p=await r.json();if(p.value)showInboxDestination(p.value);});inboxSave?.addEventListener('click',async()=>{const r=await fetch('/api/configuration/file-inbox/relocate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({directory:inboxInput.value})}),p=await r.json();inboxStatus.textContent=r.ok?window.__engineeringPlatformDashboardTranslate('configuration.relocation_restarting'):p.error||window.__engineeringPlatformDashboardTranslate('configuration.relocation_failed');if(r.ok)setTimeout(()=>location.reload(),2500);});'''
2278
+
2279
+
2280
+ def _console_project_boundary(project_id: str, options: str) -> str:
2281
+ """Bind CENTRAL selector options and request scope to a dashboard document.
2282
+
2283
+ The historical dashboard initially renders its own selector. Replacing
2284
+ those options happens after the generic visual picker is initialized, so
2285
+ the explicit event is the boundary contract that keeps the two controls
2286
+ synchronized without exposing CENTRAL details to dashboard internals.
2287
+ """
2288
+ return '''<script>
2289
+ (() => {
2290
+ const project = $PROJECT;
2291
+ const options = $OPTIONS;
2292
+ const nativeFetch = window.fetch.bind(window);
2293
+ window.fetch = (input, init = {}) => {
2294
+ const headers = new Headers(init.headers || (input instanceof Request ? input.headers : undefined));
2295
+ headers.set('X-Engineering-Platform-Project', project);
2296
+ return nativeFetch(input, { ...init, headers });
2297
+ };
2298
+ const NativeEventSource = window.EventSource;
2299
+ window.EventSource = function(url, config) {
2300
+ const target = new URL(url, window.location.href);
2301
+ target.searchParams.set('project', project);
2302
+ return new NativeEventSource(target, config);
2303
+ };
2304
+ window.EventSource.prototype = NativeEventSource.prototype;
2305
+ window.addEventListener('DOMContentLoaded', () => {
2306
+ const select = document.getElementById('dashboardProject');
2307
+ if (!select) return;
2308
+ select.innerHTML = options;
2309
+ select.value = project;
2310
+ select.dispatchEvent(new Event('dashboard-select-options-changed', { bubbles: true }));
2311
+ select.addEventListener('change', () => {
2312
+ const url = new URL(window.location.href);
2313
+ url.searchParams.set('project', select.value);
2314
+ window.location.assign(url);
2315
+ });
2316
+ $CENTRAL_DATABASE_SCRIPT
2317
+ });
2318
+ })();
2319
+ </script>'''.replace("$PROJECT", json.dumps(project_id)).replace("$OPTIONS", json.dumps(options)).replace(
2320
+ "$CENTRAL_DATABASE_SCRIPT", _central_database_script(),
2321
+ )
2322
+
2323
+
2324
+ def _no_project_console_document(projects: list[dict[str, str]], data_root: Path) -> bytes:
2325
+ """Render global Console controls without selecting project-owned data."""
2326
+ document = server_console_services.render_console_document(
2327
+ "EP Operations",
2328
+ workspace_id="none",
2329
+ project_name="<geen>",
2330
+ workspace_location="",
2331
+ configuration_inbox="",
2332
+ )
2333
+ options = _console_project_options(None, projects)
2334
+ selector = f'''<label class="dashboard-project" for="dashboardProject"><span data-i18n="project.label"></span><select id="dashboardProject" data-i18n-aria-label="project.label">{options}</select></label>'''
2335
+ boundary = '''<script>window.ENGINEERING_PLATFORM_NO_PROJECT=true;(function(){const select=document.getElementById('dashboardProject');if(select)select.addEventListener('change',()=>{const url=new URL(window.location.href);if(select.value)url.searchParams.set('project',select.value);else url.searchParams.delete('project');window.location.assign(url)});$CENTRAL_DATABASE_SCRIPT})();</script>'''.replace("$CENTRAL_DATABASE_SCRIPT", _central_database_script())
2336
+ empty_state = '''<aside class="dashboard-status-banner dashboard-status-banner--no-project" id="noProjectSelected" role="status" aria-live="polite" data-testid="no-project-selected"><strong data-i18n="central.no_project_selected_title"></strong><span data-i18n="central.no_project_selected_body"></span><button class="no-project-selected__dismiss" id="noProjectSelectedDismiss" type="button" data-i18n-aria-label="action.close" data-i18n-title="action.close"><span aria-hidden="true">×</span></button></aside>'''
2337
+ scoped_style = '''<style>
2338
+ body[data-project-id="none"] #queueItems,
2339
+ body[data-project-id="none"] #promptHistory,
2340
+ body[data-project-id="none"] #currentRun,
2341
+ body[data-project-id="none"] #technicalDetails,
2342
+ body[data-project-id="none"] #workspaceCard { display: none !important; }
2343
+ </style>'''
2344
+ document = re.sub(
2345
+ br'<body data-project-id="[^"]*" data-project-name="[^"]*">',
2346
+ b'<body data-project-id="none" data-project-name="&lt;geen&gt;">',
2347
+ document,
2348
+ count=1,
2349
+ )
2350
+ document = document.replace(
2351
+ b'<label class="dashboard-locale"',
2352
+ selector.encode("utf-8") + b'<label class="dashboard-locale"',
2353
+ 1,
2354
+ )
2355
+ document = document.replace(b'<pre></pre>', b'<pre data-i18n="format.not_available"></pre>', 1)
2356
+ # Keep the unscoped explanation in the sticky header. It is operational
2357
+ # context, not a project card that should scroll away with the dashboard.
2358
+ document = document.replace(
2359
+ b'<aside class="dashboard-status-banner dashboard-status-banner--usage-limit"',
2360
+ empty_state.encode("utf-8")
2361
+ + b'<aside class="dashboard-status-banner dashboard-status-banner--usage-limit"',
2362
+ 1,
2363
+ )
2364
+ document = document.replace(
2365
+ b'<main class="dashboard-grid"',
2366
+ boundary.encode("utf-8") + b'<main class="dashboard-grid"',
2367
+ 1,
2368
+ )
2369
+ document = document.replace(
2370
+ b'<p class="category-description" data-i18n="description.configuration"></p>',
2371
+ b'<p class="category-description" data-i18n="description.configuration"></p>' + _central_database_section(data_root).encode("utf-8"),
2372
+ 1,
2373
+ )
2374
+ return document.replace(b"</head>", scoped_style.encode("utf-8") + b"</head>", 1)
2375
+
2376
+
2377
+ def _selected_project_console_document(project_id: str, projects: list[dict[str, str]], data_root: Path) -> bytes:
2378
+ """Render the installed Console shell without loading a project checkout."""
2379
+ document = server_console_services.render_console_document(
2380
+ "EP Operations", workspace_id=project_id, project_name=project_id,
2381
+ workspace_location="",
2382
+ configuration_inbox="",
2383
+ )
2384
+ options = _console_project_options(project_id, projects)
2385
+ selector = f'''<label class="dashboard-project" for="dashboardProject"><span data-i18n="project.label"></span><select id="dashboardProject" data-i18n-aria-label="project.label">{options}</select></label>'''
2386
+ document = document.replace(
2387
+ b'<label class="dashboard-locale"', selector.encode("utf-8") + b'<label class="dashboard-locale"', 1,
2388
+ )
2389
+ document = document.replace(b'<pre></pre>', b'<pre data-i18n="central.project_workspace_not_authority"></pre>', 1)
2390
+ document = document.replace(
2391
+ b'<p class="category-description" data-i18n="description.configuration"></p>',
2392
+ b'<p class="category-description" data-i18n="description.configuration"></p>' + _central_database_section(data_root).encode("utf-8"), 1,
2393
+ )
2394
+ # A project selection establishes CENTRAL scope; it is not a local
2395
+ # workspace binding. The generic historical template still contains a
2396
+ # checkout/branch/worktree card, which has no authoritative Server data
2397
+ # and no supported action route. Execution-bound checkout evidence stays
2398
+ # in ``#executionContext`` and is deliberately not part of this removal.
2399
+ document = re.sub(
2400
+ br'<dialog class="dashboard-modal-shell dashboard-modal-shell--confirmation confirmation-modal" id="workspaceBranchMainResultModal".*?</dialog>\n',
2401
+ b"",
2402
+ document,
2403
+ count=1,
2404
+ flags=re.DOTALL,
2405
+ )
2406
+ document = re.sub(
2407
+ br'<details class="card card--context workspace-card" id="workspaceCard".*?</details>\n',
2408
+ b"",
2409
+ document,
2410
+ count=1,
2411
+ flags=re.DOTALL,
2412
+ )
2413
+ return document.replace(b"</main>", _console_project_boundary(project_id, options).encode("utf-8") + b"</main>", 1)
2414
+
2415
+
2416
+ _CONSOLE_STATIC_ASSETS = {
2417
+ "/assets/dashboard.css": ("dashboard.css", "text/css; charset=utf-8"),
2418
+ "/assets/dashboard.js": ("dashboard.js", "text/javascript; charset=utf-8"),
2419
+ "/assets/dashboard_locales.mjs": ("dashboard_locales.mjs", "text/javascript; charset=utf-8"),
2420
+ "/assets/dashboard_status_store.mjs": ("dashboard_status_store.mjs", "text/javascript; charset=utf-8"),
2421
+ "/assets/operations-console/icon-dark.png": ("operations-console/icon-dark.png", "image/png"),
2422
+ "/assets/operations-console/icon-light.png": ("operations-console/icon-light.png", "image/png"),
2423
+ "/assets/operations-console/icon-transparent.png": ("operations-console/icon-transparent.png", "image/png"),
2424
+ "/assets/operations-console/apple-touch-icon-dark.png": (console_presentation.APP_ICON_DARK, "image/png"),
2425
+ "/assets/operations-console/apple-touch-icon-light.png": (console_presentation.APP_ICON_LIGHT, "image/png"),
2426
+ "/assets/operations-console/manifest.webmanifest": (console_presentation.WEB_MANIFEST, "application/manifest+json; charset=utf-8"),
2427
+ "/favicon.ico": (console_presentation.APP_ICON_DARK, "image/png"),
2428
+ "/apple-touch-icon.png": (console_presentation.APP_ICON_DARK, "image/png"),
2429
+ "/apple-touch-icon-precomposed.png": (console_presentation.APP_ICON_DARK, "image/png"),
2430
+ }
2431
+
2432
+
2433
+ def _no_project_platform_projection(data_root: Path) -> dict[str, object]:
2434
+ """Return a checkout-free platform projection for the ``<geen>`` view.
2435
+
2436
+ This deliberately has no project fallback. It uses only installed Server
2437
+ state and CENTRAL metadata, so rendering a Console before a checkout is
2438
+ bound is a supported operation.
2439
+ """
2440
+ return {
2441
+ "scope": "PLATFORM",
2442
+ "server": status(data_root),
2443
+ "central_database": central_database.details(data_root),
2444
+ "capacity_configuration": central_database.capacity_configuration(data_root),
2445
+ }
2446
+
2447
+
2448
+ def _authenticated_consumer(connection: sqlite3.Connection, token: object, project_id: str) -> str | None:
2449
+ """Authenticate an existing scoped CENTRAL consumer credential."""
2450
+ if not isinstance(token, str) or not token or len(token) > 4096:
2451
+ return None
2452
+ row = connection.execute("""SELECT c.consumer_id FROM ep_consumer_credentials c
2453
+ JOIN ep_consumer_registrations r ON r.consumer_id=c.consumer_id AND r.project_id=c.project_id
2454
+ WHERE c.verifier=? AND c.project_id=? AND c.revoked_at IS NULL
2455
+ AND (c.expires_at IS NULL OR c.expires_at>CURRENT_TIMESTAMP) AND r.status='ACTIVE'""", (verifier(token), project_id)).fetchone()
2456
+ return str(row[0]) if row else None
2457
+
2458
+
2459
+ def _admit_server_owned_file_inbox(
2460
+ data_root: Path, envelope: dict[str, object], receipt_id: str, received_at: str,
2461
+ ) -> dict[str, object]:
2462
+ """Use the canonical application service for the Server's File Inbox child.
2463
+
2464
+ The adapter bypasses only external consumer authentication. Request
2465
+ parsing, project/repository scope, execution-mode validation, idempotency,
2466
+ admission and lifecycle initialization remain owned by ``submission_service``.
2467
+ """
2468
+ project_id = envelope.get("project_id")
2469
+ submission = envelope.get("submission")
2470
+ if not isinstance(project_id, str) or not isinstance(submission, Mapping):
2471
+ raise file_inbox.FileInboxError("MALFORMED_FILE")
2472
+ payload = dict(submission)
2473
+ payload["idempotency_key"] = receipt_id
2474
+ payload["transport_receipt_id"] = receipt_id
2475
+ payload["transport_received_at"] = received_at
2476
+ constraints = payload.get("constraints")
2477
+ if constraints is None:
2478
+ payload["constraints"] = {"transport_principal": "FILE_INBOX"}
2479
+ elif isinstance(constraints, Mapping):
2480
+ payload["constraints"] = {**constraints, "transport_principal": "FILE_INBOX"}
2481
+ try:
2482
+ with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection:
2483
+ request = submission_service.request_from_mapping(project_id, payload, transport="FILE_INBOX")
2484
+ return submission_service.submit(connection, request).to_dict()
2485
+ except submission_service.SubmissionError as error:
2486
+ raise file_inbox.FileInboxError(error.code) from error
2487
+ except (OSError, sqlite3.Error) as error:
2488
+ # A Server/database interruption is delivery availability, never an
2489
+ # execution failure. The physical item remains in ``processing`` for
2490
+ # the bounded File Inbox retry loop.
2491
+ raise URLError("CENTRAL_UNAVAILABLE") from error
2492
+
2493
+
2494
+ class _HealthHandler(http.server.BaseHTTPRequestHandler):
2495
+ def _status(self) -> dict[str, object]:
2496
+ report = status(self.server.data_root) # type: ignore[attr-defined]
2497
+ worker = getattr(self.server, "lifecycle_worker", None)
2498
+ if worker is not None:
2499
+ report["lifecycle_worker"] = worker.diagnostics().to_dict()
2500
+ return report
2501
+
2502
+ def _send(self, status_code: int, payload: dict[str, object], instance_id: str | None = None) -> None:
2503
+ encoded = json.dumps(payload, sort_keys=True).encode("utf-8")
2504
+ self.send_response(status_code)
2505
+ self.send_header("Content-Type", "application/json")
2506
+ self.send_header("Content-Length", str(len(encoded)))
2507
+ if instance_id:
2508
+ self.send_header("EP-Server-Instance", instance_id)
2509
+ route = getattr(self, "_console_route", None)
2510
+ if route is not None:
2511
+ self.send_header("EP-Console-Route-Owner", route.owner)
2512
+ self.end_headers()
2513
+ self.wfile.write(encoded)
2514
+
2515
+ def _send_ndjson(self, entries: list[dict[str, object]]) -> None:
2516
+ encoded = ("\n".join(json.dumps(entry, sort_keys=True) for entry in entries) + ("\n" if entries else "")).encode("utf-8")
2517
+ self.send_response(200)
2518
+ self.send_header("Content-Type", "application/x-ndjson; charset=utf-8")
2519
+ self.send_header("Content-Length", str(len(encoded)))
2520
+ self.send_header("Cache-Control", "no-store")
2521
+ route = getattr(self, "_console_route", None)
2522
+ if route is not None:
2523
+ self.send_header("EP-Console-Route-Owner", route.owner)
2524
+ self.end_headers()
2525
+ self.wfile.write(encoded)
2526
+
2527
+ def _send_console_asset(self, request: SplitResult) -> bool:
2528
+ """Serve installed Console assets without selecting a project/root."""
2529
+ asset = _CONSOLE_STATIC_ASSETS.get(request.path)
2530
+ if asset is None:
2531
+ return False
2532
+ name, content_type = asset
2533
+ try:
2534
+ content = (console_presentation.ASSET_DIRECTORY / name).read_bytes()
2535
+ except OSError:
2536
+ self.send_error(404)
2537
+ return True
2538
+ self.send_response(200)
2539
+ self.send_header("Content-Type", content_type)
2540
+ self.send_header("Content-Length", str(len(content)))
2541
+ self.send_header("Cache-Control", "no-store")
2542
+ self.send_header("X-Content-Type-Options", "nosniff")
2543
+ self.end_headers()
2544
+ self.wfile.write(content)
2545
+ return True
2546
+
2547
+ def _no_project_platform_route(self, method: str, request: SplitResult) -> bool:
2548
+ """Serve only explicit platform data when no project is selected.
2549
+
2550
+ Unsupported historical endpoints fail closed. In particular, this
2551
+ method never resolves a local repository binding merely to satisfy an
2552
+ old Dashboard helper.
2553
+ """
2554
+ if method != "do_GET":
2555
+ return False
2556
+ if request.path == "/api/platform-status":
2557
+ self._send(200, _no_project_platform_projection(self.server.data_root)) # type: ignore[attr-defined]
2558
+ return True
2559
+ if request.path in {"/api/dashboard-snapshot", "/api/status"}:
2560
+ self._send(200, _no_project_console_snapshot(self.server.data_root)) # type: ignore[attr-defined]
2561
+ return True
2562
+ if request.path == "/api/events":
2563
+ self._stream_no_project_console_events()
2564
+ return True
2565
+ if request.path == "/health":
2566
+ report = status(self.server.data_root) # type: ignore[attr-defined]
2567
+ self._send(200 if report["healthy"] else 503, report, str(report["instance_id"]))
2568
+ return True
2569
+ if request.path == "/api/configuration":
2570
+ # CENTRAL-only settings presently supported by this phase. The
2571
+ # root-local dashboard configuration is intentionally unavailable.
2572
+ self._send(200, _central_console_configuration(self.server.data_root)) # type: ignore[attr-defined]
2573
+ return True
2574
+ if request.path == "/api/execution-runtime-status":
2575
+ self._send(200, _execution_runtime_status())
2576
+ return True
2577
+ if request.path == "/api/github-rate-limit":
2578
+ self._send(200, _github_rate_limit_status())
2579
+ return True
2580
+ if request.path == "/api/host-admin/diagnostics":
2581
+ self._send(200, host_admin.diagnostics(self.server.data_root)) # type: ignore[attr-defined]
2582
+ return True
2583
+ if request.path == "/api/provider-login-status":
2584
+ self._send(200, {"providers": _central_provider_readiness(self.server.data_root)}) # type: ignore[attr-defined]
2585
+ return True
2586
+ if request.path in {"/api/process-metrics", "/api/usage"}:
2587
+ self._send(200, {"scope": "PLATFORM", "available": False})
2588
+ return True
2589
+ if re.fullmatch(rf"/api/logs/(?:all|{PLATFORM_COMPONENT_ROUTE_PATTERN})", request.path):
2590
+ component = request.path.rsplit("/", 1)[-1]
2591
+ self._send(200, _central_console_component_logs(self.server.data_root, component) or {"error": "LOG_COMPONENT_UNKNOWN"}) # type: ignore[attr-defined]
2592
+ return True
2593
+ return False
2594
+
2595
+ def _send_central_database_backup(self) -> None:
2596
+ snapshot = central_database.snapshot(self.server.data_root) # type: ignore[attr-defined]
2597
+ if snapshot is None:
2598
+ self._send(503, {"error": "CENTRAL_DATABASE_UNAVAILABLE"})
2599
+ return
2600
+ filename = f"engineering-platform-central-{time.strftime('%Y%m%dT%H%M%SZ', time.gmtime())}.db"
2601
+ self.send_response(200)
2602
+ self.send_header("Content-Type", "application/vnd.sqlite3")
2603
+ self.send_header("Content-Disposition", _attachment_content_disposition(filename))
2604
+ self.send_header("Content-Length", str(len(snapshot)))
2605
+ self.send_header("Cache-Control", "no-store")
2606
+ self.send_header("X-Content-Type-Options", "nosniff")
2607
+ route = getattr(self, "_console_route", None)
2608
+ if route is not None:
2609
+ self.send_header("EP-Console-Route-Owner", route.owner)
2610
+ self.end_headers()
2611
+ self.wfile.write(snapshot)
2612
+
2613
+ def _send_central_data_export(self) -> None:
2614
+ """Export one quiesced, portable snapshot of all durable CENTRAL data."""
2615
+ if _central_execution_active(self.server.data_root): # type: ignore[attr-defined]
2616
+ self._send(409, {"error": "CENTRAL_DATA_TRANSFER_BLOCKED"})
2617
+ return
2618
+ with self.server.central_data_transfer_lock: # type: ignore[attr-defined]
2619
+ self.server.central_data_transfer_active = True # type: ignore[attr-defined]
2620
+ try:
2621
+ self.server.dependabot_service.stop() # type: ignore[attr-defined]
2622
+ self.server.inbox_service.stop() # type: ignore[attr-defined]
2623
+ self.server.lifecycle_worker.stop() # type: ignore[attr-defined]
2624
+ if _central_execution_active(self.server.data_root): # type: ignore[attr-defined]
2625
+ raise central_data_transfer.CentralDataTransferError("CENTRAL_DATA_TRANSFER_BLOCKED")
2626
+ filename, snapshot = central_data_transfer.export_snapshot(self.server.data_root) # type: ignore[attr-defined]
2627
+ except (OSError, sqlite3.DatabaseError, central_data_transfer.CentralDataTransferError) as error:
2628
+ self._send(409, {"error": str(error)})
2629
+ return
2630
+ finally:
2631
+ self.server.lifecycle_worker.start() # type: ignore[attr-defined]
2632
+ self.server.inbox_service.start() # type: ignore[attr-defined]
2633
+ self.server.dependabot_service.start() # type: ignore[attr-defined]
2634
+ self.server.central_data_transfer_active = False # type: ignore[attr-defined]
2635
+ self.send_response(200)
2636
+ self.send_header("Content-Type", "application/zip")
2637
+ self.send_header("Content-Disposition", _attachment_content_disposition(filename))
2638
+ self.send_header("Content-Length", str(len(snapshot)))
2639
+ self.send_header("Cache-Control", "no-store")
2640
+ self.send_header("X-Content-Type-Options", "nosniff")
2641
+ self.end_headers()
2642
+ self.wfile.write(snapshot)
2643
+
2644
+ def _central_database_configuration(self, method: str) -> bool:
2645
+ request = urlsplit(self.path)
2646
+ if request.path == "/api/central-data/export" and method == "do_GET":
2647
+ self._send_central_data_export()
2648
+ return True
2649
+ if request.path == "/api/central-data/relocate/browse" and method == "do_POST":
2650
+ try:
2651
+ self._send(200, {"value": _choose_local_directory(self.server.data_root)}) # type: ignore[attr-defined]
2652
+ except ValueError as error:
2653
+ self._send(400, {"error": str(error)})
2654
+ return True
2655
+ if request.path == "/api/central-data/relocate" and method == "do_POST":
2656
+ try:
2657
+ length = int(self.headers.get("Content-Length", "0"))
2658
+ payload = json.loads(self.rfile.read(length).decode("utf-8")) if 0 < length <= 4096 else None
2659
+ if not isinstance(payload, dict) or set(payload) != {"directory"} or _central_execution_active(self.server.data_root): # type: ignore[attr-defined]
2660
+ raise ValueError("PLATFORM_DATA_RELOCATION_BLOCKED")
2661
+ result = installation_relocation.request(self.server.data_root, "PLATFORM_DATA", payload["directory"]) # type: ignore[attr-defined]
2662
+ except (ValueError, UnicodeDecodeError, json.JSONDecodeError, OSError) as error:
2663
+ self._send(409, {"error": str(error)})
2664
+ return True
2665
+ self._send(202, {**result, "restarting": True})
2666
+ Timer(0.5, lambda: os.kill(os.getpid(), signal.SIGTERM)).start()
2667
+ return True
2668
+ if request.path == "/api/central-data/import" and method == "do_POST":
2669
+ try:
2670
+ length = int(self.headers.get("Content-Length", "0"))
2671
+ if self.headers.get("X-EP-Central-Import-Confirmed") != "true" or not 0 < length <= central_data_transfer.MAX_ARCHIVE_BYTES or _central_execution_active(self.server.data_root): # type: ignore[attr-defined]
2672
+ raise ValueError("CENTRAL_IMPORT_BLOCKED")
2673
+ imports = self.server.data_root / "runtime" / "central-data-imports" # type: ignore[attr-defined]
2674
+ imports.mkdir(mode=0o700, parents=True, exist_ok=True)
2675
+ upload = imports / f"upload-{uuid4().hex}.zip"
2676
+ with upload.open("wb") as output:
2677
+ remaining = length
2678
+ while remaining:
2679
+ chunk = self.rfile.read(min(1024 * 1024, remaining))
2680
+ if not chunk:
2681
+ raise ValueError("CENTRAL_IMPORT_UPLOAD_INCOMPLETE")
2682
+ output.write(chunk); remaining -= len(chunk)
2683
+ result = central_data_transfer.stage_import(self.server.data_root, upload) # type: ignore[attr-defined]
2684
+ upload.unlink(missing_ok=True)
2685
+ except (ValueError, OSError, central_data_transfer.CentralDataTransferError) as error:
2686
+ self._send(409, {"error": str(error)})
2687
+ return True
2688
+ self._send(202, {**result, "restarting": True})
2689
+ Timer(0.5, lambda: os.kill(os.getpid(), signal.SIGTERM)).start()
2690
+ return True
2691
+ if request.path == "/api/central-database/download" and method == "do_GET":
2692
+ self._send(410, {"error": "CENTRAL_DATABASE_DOWNLOAD_RETIRED"})
2693
+ return True
2694
+ if request.path != "/api/central-database/configuration":
2695
+ return False
2696
+ if method == "do_GET":
2697
+ self._send(200, central_database.maintenance_configuration(self.server.data_root)) # type: ignore[attr-defined]
2698
+ return True
2699
+ try:
2700
+ length = int(self.headers.get("Content-Length", "0"))
2701
+ if not 0 < length <= 4096:
2702
+ raise ValueError
2703
+ payload = json.loads(self.rfile.read(length).decode("utf-8"))
2704
+ if not isinstance(payload, dict):
2705
+ raise ValueError
2706
+ result = central_database.update_maintenance_configuration(
2707
+ self.server.data_root, payload.get("interval_seconds"), # type: ignore[attr-defined]
2708
+ )
2709
+ except (ValueError, UnicodeDecodeError, json.JSONDecodeError):
2710
+ self._send(400, {"error": "CENTRAL_DATABASE_MAINTENANCE_INTERVAL_INVALID"})
2711
+ return True
2712
+ _audit_configuration_change(
2713
+ self.server.data_root, # type: ignore[attr-defined]
2714
+ scope="CENTRAL_DATABASE",
2715
+ key="maintenance_interval_seconds",
2716
+ previous=result.get("previous", "UNAVAILABLE"),
2717
+ value=result.get("interval_seconds", "UNAVAILABLE"),
2718
+ )
2719
+ self._send(200, result)
2720
+ return True
2721
+
2722
+ def _stream_console_events(self, root: Path, project_id: str) -> None:
2723
+ """Stream the selected project from CENTRAL only.
2724
+
2725
+ ``root`` is retained temporarily by the route's binding contract but
2726
+ is intentionally not read: a checkout cannot become state authority
2727
+ merely because it is attached to a selected project.
2728
+ """
2729
+ self.send_response(200)
2730
+ self.send_header("Content-Type", "text/event-stream; charset=utf-8")
2731
+ self.send_header("Cache-Control", "no-store")
2732
+ self.end_headers()
2733
+ try:
2734
+ # Event delivery is a platform concern even when its snapshot has
2735
+ # a selected-project projection. It must therefore use the same
2736
+ # CENTRAL-owned interval as the no-project Console, rather than a
2737
+ # repository/root-local Dashboard preference.
2738
+ stream_interval = int(
2739
+ central_database.console_interval_configuration(self.server.data_root)[ # type: ignore[attr-defined]
2740
+ "dashboard_stream_interval_seconds"
2741
+ ]
2742
+ )
2743
+ self.wfile.write(f"retry: {stream_interval * 1000}\n\n".encode())
2744
+ previous: bytes | None = None
2745
+ for iteration in range(300):
2746
+ snapshot = json.dumps(
2747
+ _central_console_project_snapshot(self.server.data_root, project_id),
2748
+ separators=(",", ":"),
2749
+ ).encode("utf-8") # type: ignore[attr-defined]
2750
+ if snapshot != previous:
2751
+ self.wfile.write(b"event: dashboard\ndata: " + snapshot + b"\n\n")
2752
+ self.wfile.flush()
2753
+ previous = snapshot
2754
+ elif iteration and iteration % 15 == 0:
2755
+ self.wfile.write(b": keepalive\n\n")
2756
+ self.wfile.flush()
2757
+ interval = int(
2758
+ central_database.console_interval_configuration(self.server.data_root)[ # type: ignore[attr-defined]
2759
+ "dashboard_stream_interval_seconds"
2760
+ ]
2761
+ )
2762
+ if interval != stream_interval:
2763
+ self.wfile.write(f"retry: {interval * 1000}\n\n".encode())
2764
+ self.wfile.flush()
2765
+ stream_interval = interval
2766
+ time.sleep(stream_interval)
2767
+ except (BrokenPipeError, ConnectionResetError):
2768
+ return
2769
+
2770
+ def _stream_no_project_console_events(self) -> None:
2771
+ """Send a CENTRAL-only event that completes the shared Console shell."""
2772
+ self.send_response(200)
2773
+ self.send_header("Content-Type", "text/event-stream; charset=utf-8")
2774
+ self.send_header("Cache-Control", "no-store")
2775
+ self.send_header("EP-Console-Route-Owner", "PLATFORM")
2776
+ self.end_headers()
2777
+ try:
2778
+ stream_interval = int(
2779
+ central_database.console_interval_configuration(self.server.data_root)[ # type: ignore[attr-defined]
2780
+ "dashboard_stream_interval_seconds"
2781
+ ]
2782
+ )
2783
+ self.wfile.write(f"retry: {stream_interval * 1000}\n\n".encode())
2784
+ previous: bytes | None = None
2785
+ for iteration in range(300):
2786
+ payload = json.dumps(
2787
+ _no_project_console_snapshot(self.server.data_root), separators=(",", ":") # type: ignore[attr-defined]
2788
+ ).encode("utf-8")
2789
+ if payload != previous:
2790
+ self.wfile.write(b"event: dashboard\ndata: " + payload + b"\n\n")
2791
+ self.wfile.flush()
2792
+ previous = payload
2793
+ elif iteration and iteration % 15 == 0:
2794
+ self.wfile.write(b": keepalive\n\n")
2795
+ self.wfile.flush()
2796
+ time.sleep(stream_interval)
2797
+ except (BrokenPipeError, ConnectionResetError):
2798
+ return
2799
+
2800
+ def _stream_project_console_events(self, project_id: str) -> None:
2801
+ """Keep the selected project's CENTRAL-only dashboard stream alive."""
2802
+ self.send_response(200)
2803
+ self.send_header("Content-Type", "text/event-stream; charset=utf-8")
2804
+ self.send_header("Cache-Control", "no-store")
2805
+ self.send_header("EP-Console-Route-Owner", "PLATFORM")
2806
+ self.end_headers()
2807
+ try:
2808
+ stream_interval = int(
2809
+ central_database.console_interval_configuration(self.server.data_root)[ # type: ignore[attr-defined]
2810
+ "dashboard_stream_interval_seconds"
2811
+ ]
2812
+ )
2813
+ self.wfile.write(f"retry: {stream_interval * 1000}\n\n".encode())
2814
+ previous: bytes | None = None
2815
+ for iteration in range(300):
2816
+ payload = json.dumps(
2817
+ _central_console_project_snapshot(self.server.data_root, project_id), # type: ignore[attr-defined]
2818
+ separators=(",", ":"),
2819
+ ).encode("utf-8")
2820
+ if payload != previous:
2821
+ self.wfile.write(b"event: dashboard\ndata: " + payload + b"\n\n")
2822
+ self.wfile.flush()
2823
+ previous = payload
2824
+ elif iteration and iteration % 15 == 0:
2825
+ self.wfile.write(b": keepalive\n\n")
2826
+ self.wfile.flush()
2827
+ time.sleep(stream_interval)
2828
+ except (BrokenPipeError, ConnectionResetError):
2829
+ return
2830
+
2831
+ def _delegate_dashboard(self, method: str) -> None:
2832
+ """Route the transitional Console after CENTRAL validates its scope."""
2833
+ request = urlsplit(self.path)
2834
+ # Resolve ownership once, before any project identity is read. The
2835
+ # header makes the runtime contract observable to browser/integration
2836
+ # coverage without granting a selected project any authority.
2837
+ self._console_route = console_route_ownership.route_owner(method.removeprefix("do_"), request.path)
2838
+ # The export boundary stops all installed writer services. Refuse a
2839
+ # concurrent Console mutation as well, rather than claiming a ZIP is
2840
+ # a point-in-time snapshot while an HTTP request can still alter it.
2841
+ if method == "do_POST" and getattr(self.server, "central_data_transfer_active", False):
2842
+ self._send(423, {"error": "CENTRAL_DATA_TRANSFER_IN_PROGRESS"})
2843
+ return
2844
+ if method == "do_GET" and self._send_console_asset(request):
2845
+ return
2846
+ if request.path.startswith("/api/configuration/file-inbox/relocate"):
2847
+ self._send(410, {"error": "FILE_INBOX_PARTIAL_RELOCATION_RETIRED"})
2848
+ return
2849
+ if method == "do_POST" and re.fullmatch(r"/api/configuration/inbox-location(?:/browse)?", request.path):
2850
+ self._send(410, {"error": "INBOX_WATCHER_CONFIGURATION_RETIRED"})
2851
+ return
2852
+ if re.fullmatch(r"/api/logs/(?:inbox|dashboard)", request.path):
2853
+ # These former dashboard-owned streams are deliberately absent
2854
+ # from the canonical CENTRAL component model. Handle them before
2855
+ # scope resolution so a stale caller cannot turn a retired route
2856
+ # into a project-delegation failure.
2857
+ self._send(410, {"error": "LEGACY_COMPONENT_LOG_ROUTE_RETIRED"})
2858
+ return
2859
+ if re.fullmatch(
2860
+ rf"/api/components/{RETIRED_COMPONENT_ALIAS_ROUTE_PATTERN}/(?:details|restart)",
2861
+ request.path,
2862
+ ):
2863
+ # A retired name is never normalized to a supported component.
2864
+ # Reject it before project resolution so it cannot acquire a
2865
+ # project, lifecycle or repair interpretation as a side effect.
2866
+ self._send(410, {"error": "LEGACY_COMPONENT_AUTHORITY_RETIRED"})
2867
+ return
2868
+ log_match = re.fullmatch(rf"/api/logs/(all|{PLATFORM_COMPONENT_ROUTE_PATTERN})", request.path)
2869
+ if log_match and method == "do_GET":
2870
+ query = parse_qs(request.query)
2871
+ try:
2872
+ payload = _central_console_component_logs(
2873
+ self.server.data_root, log_match.group(1), query,
2874
+ export_all=(query.get("format") or [""])[0] == "ndjson",
2875
+ ) # type: ignore[attr-defined]
2876
+ except ValueError:
2877
+ self._send(400, {"error": "LOG_QUERY_INVALID"})
2878
+ return
2879
+ if payload is None:
2880
+ self._send(404, {"error": "LOG_COMPONENT_UNKNOWN"})
2881
+ elif (query.get("format") or [""])[0] == "ndjson":
2882
+ self._send_ndjson(list(payload["entries"]))
2883
+ else:
2884
+ self._send(200, payload)
2885
+ return
2886
+ if log_match and method == "do_POST":
2887
+ try:
2888
+ if self.headers.get("Origin") not in {None, "", f"http://{self.headers.get('Host', '')}"}:
2889
+ raise ValueError
2890
+ payload = json.loads(self.rfile.read(int(self.headers.get("Content-Length", "0"))).decode("utf-8"))
2891
+ if not isinstance(payload, dict) or set(payload) != {"component"} or not isinstance(payload["component"], str):
2892
+ raise ValueError
2893
+ result = _clear_central_console_component_logs(self.server.data_root, payload["component"]) # type: ignore[attr-defined]
2894
+ if result is None:
2895
+ raise ValueError
2896
+ except (ValueError, UnicodeDecodeError, json.JSONDecodeError):
2897
+ self._send(400, {"error": "LOG_COMPONENT_INVALID"})
2898
+ else:
2899
+ self._send(200, result)
2900
+ return
2901
+ # Platform health is deliberately independent of the browser's
2902
+ # selected project preference, so the same components remain visible
2903
+ # in both Console modes.
2904
+ if method == "do_GET" and request.path == "/health":
2905
+ report = status(self.server.data_root) # type: ignore[attr-defined]
2906
+ self._send(200 if report["healthy"] else 503, report, str(report["instance_id"]))
2907
+ return
2908
+ if method == "do_GET" and request.path == "/api/host-admin/diagnostics":
2909
+ # Host Admin has an installation-only root and is intentionally
2910
+ # resolved before any selected-project header is inspected.
2911
+ self._send(200, host_admin.diagnostics(self.server.data_root)) # type: ignore[attr-defined]
2912
+ return
2913
+ component_match = re.fullmatch(r"/api/components/([a-z_]+)/details", request.path)
2914
+ if component_match and component_match.group(1) not in PLATFORM_COMPONENT_IDS:
2915
+ component_match = None
2916
+ if method == "do_GET" and component_match:
2917
+ detail = _platform_component_detail(self.server.data_root, component_match.group(1)) # type: ignore[attr-defined]
2918
+ self._send(200, detail) if detail is not None else self._send(404, {"error": "COMPONENT_UNKNOWN"})
2919
+ return
2920
+ restart_match = re.fullmatch(r"/api/components/([a-z_]+)/restart", request.path)
2921
+ if method == "do_POST" and restart_match:
2922
+ try:
2923
+ if self.headers.get("Origin") not in {None, "", f"http://{self.headers.get('Host', '')}"}:
2924
+ raise ValueError
2925
+ if self.rfile.read(int(self.headers.get("Content-Length", "0"))) != b"{}":
2926
+ raise ValueError
2927
+ result = _restart_platform_component(self.server.data_root, restart_match.group(1)) # type: ignore[attr-defined]
2928
+ except (ValueError, OSError):
2929
+ self._send(409, {"error": "COMPONENT_RESTART_UNAVAILABLE"})
2930
+ else:
2931
+ self._send(202, result)
2932
+ return
2933
+ if self._central_database_configuration(method):
2934
+ return
2935
+ if request.path == "/api/provider-capacity":
2936
+ if method != "do_GET":
2937
+ self._send(405, {"error": "METHOD_NOT_ALLOWED"})
2938
+ else:
2939
+ self._send(200, _provider_capacity_projection(self.server.data_root)) # type: ignore[attr-defined]
2940
+ return
2941
+ if request.path == "/api/provider-login-status" and method == "do_GET":
2942
+ self._send(200, {"providers": _central_provider_readiness(self.server.data_root)}) # type: ignore[attr-defined]
2943
+ return
2944
+ if request.path == "/api/execution-runtime-status" and method == "do_GET":
2945
+ # Validation is an installation capability, independent of the
2946
+ # selected project. Keep it out of the legacy checkout delegate.
2947
+ self._send(200, _execution_runtime_status())
2948
+ return
2949
+ if request.path == "/api/execution-runtime/repair" and method == "do_POST":
2950
+ try:
2951
+ if self.headers.get("Origin") not in {None, "", f"http://{self.headers.get('Host', '')}"}:
2952
+ raise ValueError
2953
+ if self.rfile.read(int(self.headers.get("Content-Length", "0"))) != b"{}":
2954
+ raise ValueError
2955
+ runtime = _execution_runtime_status()
2956
+ if runtime["state"] != "READY":
2957
+ raise ValueError
2958
+ except (ValueError, OSError):
2959
+ self._send(409, {"error": "EXECUTION_RUNTIME_UNAVAILABLE"})
2960
+ return
2961
+ self._send(200, {"rechecked": True, "runtime": runtime, "scope": "PLATFORM"})
2962
+ return
2963
+ if request.path == "/api/provider-login/repair" and method == "do_POST":
2964
+ # Provider installation and interactive sign-in are host-wide
2965
+ # operations. They must never fall through to the historical
2966
+ # checkout-bound dashboard handler: on the <geen> projection that
2967
+ # handler rejects the request for lack of a selected project and
2968
+ # the subsequent readiness refresh misleadingly becomes a check
2969
+ # failure.
2970
+ try:
2971
+ if self.headers.get("Origin") not in {None, "", f"http://{self.headers.get('Host', '')}"}:
2972
+ raise ValueError
2973
+ length = int(self.headers.get("Content-Length", "0"))
2974
+ if not 0 < length <= 1024:
2975
+ raise ValueError
2976
+ payload = json.loads(self.rfile.read(length).decode("utf-8"))
2977
+ _central_provider_repair(self.server.data_root, payload) # type: ignore[attr-defined]
2978
+ except (OSError, RuntimeError, ValueError, UnicodeDecodeError, json.JSONDecodeError):
2979
+ self._send(409, {"error": "PROVIDER_REPAIR_UNAVAILABLE"})
2980
+ return
2981
+ self._send(202, {"started": True, "scope": "PLATFORM"})
2982
+ return
2983
+ if request.path == "/api/provider-login/logout" and method == "do_POST":
2984
+ # Logout is the companion host-wide action to login. Do not let
2985
+ # the installed no-project Console fall through to the retired
2986
+ # checkout handler, which rejects it before the CLI can run.
2987
+ try:
2988
+ if self.headers.get("Origin") not in {None, "", f"http://{self.headers.get('Host', '')}"}:
2989
+ raise ValueError
2990
+ length = int(self.headers.get("Content-Length", "0"))
2991
+ if not 0 < length <= 1024:
2992
+ raise ValueError
2993
+ payload = json.loads(self.rfile.read(length).decode("utf-8"))
2994
+ _central_provider_logout(self.server.data_root, payload) # type: ignore[attr-defined]
2995
+ except (OSError, RuntimeError, ValueError, UnicodeDecodeError, json.JSONDecodeError):
2996
+ self._send(409, {"error": "PROVIDER_LOGOUT_UNAVAILABLE"})
2997
+ return
2998
+ self._send(200, {"logged_out": True, "scope": "PLATFORM"})
2999
+ return
3000
+ if request.path == "/api/provider-capacity/configuration":
3001
+ if method == "do_GET":
3002
+ self._send(200, central_database.capacity_configuration(self.server.data_root)) # type: ignore[attr-defined]
3003
+ return
3004
+ try:
3005
+ if self.headers.get("Origin") not in {None, "", f"http://{self.headers.get('Host', '')}"}:
3006
+ raise ValueError
3007
+ payload = json.loads(self.rfile.read(int(self.headers.get("Content-Length", "0"))).decode("utf-8"))
3008
+ reserve = payload.get("codex_capacity_reserve_percent") if isinstance(payload, dict) else None
3009
+ live = _provider_capacity_projection(self.server.data_root) # type: ignore[attr-defined]
3010
+ remaining = _remaining_rate_limit_capacity(live["rate_limits"])
3011
+ if not isinstance(reserve, int) or isinstance(reserve, bool) or (reserve and (remaining is None or reserve > remaining)):
3012
+ raise ValueError
3013
+ result = central_database.update_capacity_configuration(self.server.data_root, reserve) # type: ignore[attr-defined]
3014
+ _audit_configuration_change(
3015
+ self.server.data_root, # type: ignore[attr-defined]
3016
+ scope="PROVIDER_CAPACITY",
3017
+ key="codex_capacity_reserve_percent",
3018
+ previous=result.get("previous", "UNAVAILABLE"),
3019
+ value=result.get("codex_capacity_reserve_percent", "UNAVAILABLE"),
3020
+ )
3021
+ self._send(200, result)
3022
+ except (ValueError, UnicodeDecodeError, json.JSONDecodeError):
3023
+ self._send(409, {"error": "CODEX_CAPACITY_RESERVE_INVALID"})
3024
+ return
3025
+ if request.path == "/api/configuration" and method == "do_POST":
3026
+ try:
3027
+ if self.headers.get("Origin") not in {None, "", f"http://{self.headers.get('Host', '')}"}:
3028
+ raise ValueError
3029
+ payload = json.loads(self.rfile.read(int(self.headers.get("Content-Length", "0"))).decode("utf-8"))
3030
+ if not isinstance(payload, dict) or set(payload) != {"key", "value", "previous"}:
3031
+ raise ValueError
3032
+ result = central_database.update_console_interval_configuration(
3033
+ self.server.data_root, payload["key"], payload["value"],
3034
+ ) # type: ignore[attr-defined]
3035
+ _audit_configuration_change(
3036
+ self.server.data_root, # type: ignore[attr-defined]
3037
+ scope="OPERATIONS_CONSOLE",
3038
+ key=str(result["key"]),
3039
+ previous=result.get("previous", "UNAVAILABLE"),
3040
+ value=result.get("value", "UNAVAILABLE"),
3041
+ )
3042
+ self._send(200, result)
3043
+ except (ValueError, UnicodeDecodeError, json.JSONDecodeError):
3044
+ self._send(409, {"error": "CONSOLE_CONFIGURATION_INVALID"})
3045
+ return
3046
+ if method == "do_POST" and request.path == "/api/runtime-directory/open":
3047
+ # This action historically resolved the first bound checkout.
3048
+ # The installed CENTRAL Console deliberately has no root-bound
3049
+ # runtime action; runtime paths remain display-only diagnostics.
3050
+ self._send(410, {"error": "RUNTIME_DIRECTORY_RETIRED"})
3051
+ return
3052
+ selected = self.headers.get("X-Engineering-Platform-Project")
3053
+ if not selected:
3054
+ selected = (parse_qs(request.query).get("project") or [None])[0]
3055
+ # Listing projects is a CENTRAL-only operation. Do not validate or
3056
+ # inspect any checkout until a selected project needs a transitional
3057
+ # project route below.
3058
+ projects = _console_projects(self.server.data_root) # type: ignore[attr-defined]
3059
+ project_ids = {item["project_id"] for item in projects}
3060
+ if method == "do_GET" and isinstance(selected, str) and selected in project_ids:
3061
+ # Slice B: the core project read model is available even when its
3062
+ # checkout has been deleted or rebound. Do this before the
3063
+ # transitional handler can resolve a root.
3064
+ if request.path == "/":
3065
+ document = _selected_project_console_document(selected, projects, self.server.data_root) # type: ignore[attr-defined]
3066
+ self.send_response(200)
3067
+ self.send_header("Content-Type", "text/html; charset=utf-8")
3068
+ self.send_header("Content-Length", str(len(document)))
3069
+ self.send_header("Cache-Control", "no-store")
3070
+ self.end_headers()
3071
+ self.wfile.write(document)
3072
+ return
3073
+ if request.path == "/api/configuration":
3074
+ self._send(200, _central_console_configuration(self.server.data_root)) # type: ignore[attr-defined]
3075
+ return
3076
+ if re.fullmatch(rf"/api/logs/(?:all|{PLATFORM_COMPONENT_ROUTE_PATTERN})", request.path):
3077
+ component = request.path.rsplit("/", 1)[-1]
3078
+ payload = _central_console_component_logs(self.server.data_root, component) # type: ignore[attr-defined]
3079
+ if payload is None:
3080
+ self._send(404, {"error": "LOG_COMPONENT_UNKNOWN"})
3081
+ else:
3082
+ self._send(200, payload)
3083
+ return
3084
+ if request.path in {"/api/dashboard-snapshot", "/api/status"}:
3085
+ self._send(200, _central_console_project_snapshot(self.server.data_root, selected)) # type: ignore[attr-defined]
3086
+ return
3087
+ if request.path == "/api/prompt-history":
3088
+ snapshot = _central_console_project_snapshot(self.server.data_root, selected) # type: ignore[attr-defined]
3089
+ # Keep the established Console list contract while changing
3090
+ # only its authority source.
3091
+ encoded = json.dumps(snapshot["runs"], separators=(",", ":")).encode("utf-8")
3092
+ self.send_response(200)
3093
+ self.send_header("Content-Type", "application/json; charset=utf-8")
3094
+ self.send_header("Content-Length", str(len(encoded)))
3095
+ self.send_header("Cache-Control", "no-store")
3096
+ self.send_header("X-Content-Type-Options", "nosniff")
3097
+ self.end_headers()
3098
+ self.wfile.write(encoded)
3099
+ return
3100
+ report_match = re.fullmatch(r"/api/prompt-history/([a-z0-9][a-z0-9-]{0,63})/report", request.path)
3101
+ if report_match:
3102
+ content = _central_console_report(self.server.data_root, selected, report_match.group(1)) # type: ignore[attr-defined]
3103
+ if content is None:
3104
+ self._send(404, {"error": "REPORT_NOT_FOUND"})
3105
+ return
3106
+ self.send_response(200)
3107
+ self.send_header("Content-Type", "text/markdown; charset=utf-8")
3108
+ self.send_header(
3109
+ "Content-Disposition",
3110
+ _report_content_disposition(report_match.group(1)),
3111
+ )
3112
+ self.send_header("Content-Length", str(len(content)))
3113
+ self.send_header("Cache-Control", "no-store")
3114
+ self.send_header("X-Content-Type-Options", "nosniff")
3115
+ self.end_headers()
3116
+ self.wfile.write(content)
3117
+ return
3118
+ chat_match = re.fullmatch(r"/api/prompt-history/([a-z0-9][a-z0-9-]{0,63})/chat", request.path)
3119
+ if chat_match:
3120
+ messages = _central_console_chat_history(self.server.data_root, selected, chat_match.group(1)) # type: ignore[attr-defined]
3121
+ if messages is None:
3122
+ self._send(404, {"error": "RUN_NOT_FOUND"})
3123
+ else:
3124
+ self._send(200, {"messages": messages, "source": "CENTRAL"})
3125
+ return
3126
+ detail_match = re.fullmatch(r"/api/prompt-history/([a-z0-9][a-z0-9-]{0,63})/details", request.path)
3127
+ if detail_match:
3128
+ detail = _central_console_run_detail(self.server.data_root, selected, detail_match.group(1)) # type: ignore[attr-defined]
3129
+ if detail is None:
3130
+ self._send(404, {"error": "RUN_NOT_FOUND"})
3131
+ else:
3132
+ self._send(200, {"project_id": selected, "run": detail, "source": "CENTRAL"})
3133
+ return
3134
+ telemetry_match = re.fullmatch(r"/api/telemetry/([0-9]{4}-[0-9]{2}-[0-9]{2})", request.path)
3135
+ if telemetry_match:
3136
+ detail = _central_console_telemetry_detail(
3137
+ self.server.data_root, selected, telemetry_match.group(1), # type: ignore[attr-defined]
3138
+ )
3139
+ if detail is None:
3140
+ self._send(404, {"error": "TELEMETRY_NOT_FOUND"})
3141
+ else:
3142
+ self._send(200, detail)
3143
+ return
3144
+ if request.path == "/api/events":
3145
+ self._stream_project_console_events(selected)
3146
+ return
3147
+ if method == "do_POST" and isinstance(selected, str) and selected in project_ids and (
3148
+ request.path == "/api/configuration" or request.path.startswith("/api/logs/")
3149
+ ):
3150
+ # The local dashboard's mutable metadata/log controls have no
3151
+ # CENTRAL contract yet. Fail closed rather than mutating a
3152
+ # checkout-local store for compatibility.
3153
+ self._send(405, {"error": "CONSOLE_CONFIGURATION_MUTATION_UNAVAILABLE"})
3154
+ return
3155
+ if method == "do_POST" and request.path in {"/api/execution-dismiss", "/api/execution-retry"}:
3156
+ if self.headers.get("Origin") not in {None, "", f"http://{self.headers.get('Host', '')}"}:
3157
+ self._send(403, {"error": "INVALID_ORIGIN"})
3158
+ return
3159
+ if not isinstance(selected, str) or selected not in project_ids:
3160
+ self._send(409, {"error": "CONSOLE_PROJECT_UNAVAILABLE"})
3161
+ return
3162
+ # The preserved execution lifecycle is loaded only when its
3163
+ # project-scoped mutation is requested. Importing the canonical
3164
+ # Server must not load retired watcher-era implementation modules.
3165
+ from .parity_lifecycle_dispatcher import (
3166
+ ParityLifecycleDispatchError,
3167
+ dismiss_operator_gate,
3168
+ retry_operator_gate,
3169
+ )
3170
+ try:
3171
+ length = int(self.headers.get("Content-Length", "0"))
3172
+ if not 2 <= length <= 256:
3173
+ raise ValueError
3174
+ payload = json.loads(self.rfile.read(length).decode("utf-8"))
3175
+ run_id = payload.get("run_id") if isinstance(payload, dict) and set(payload) == {"run_id"} else None
3176
+ if not isinstance(run_id, str) or not re.fullmatch(r"[a-z0-9][a-z0-9-]{0,63}", run_id):
3177
+ raise ValueError
3178
+ if request.path == "/api/execution-dismiss":
3179
+ result: dict[str, object] = dismiss_operator_gate(
3180
+ self.server.data_root, project_id=selected, run_id=run_id, # type: ignore[attr-defined]
3181
+ )
3182
+ else:
3183
+ result = retry_operator_gate(
3184
+ self.server.data_root, project_id=selected, run_id=run_id, # type: ignore[attr-defined]
3185
+ ).to_dict()
3186
+ except (ValueError, UnicodeDecodeError, json.JSONDecodeError):
3187
+ self._send(400, {"error": "INVALID_REQUEST"})
3188
+ return
3189
+ except ParityLifecycleDispatchError as error:
3190
+ self._send(409, {"error": str(error)})
3191
+ return
3192
+ self._send(200, result)
3193
+ return
3194
+ if isinstance(selected, str) and selected in project_ids:
3195
+ # No supported CENTRAL Console route may fall through to the
3196
+ # retained dashboard handler. New routes must be added above
3197
+ # with an explicit Server/CENTRAL authority classification.
3198
+ self._send(404 if method == "do_GET" else 405, {"error": "CENTRAL_CONSOLE_ROUTE_UNAVAILABLE"})
3199
+ return
3200
+ if method == "do_POST" and request.path == "/api/dashboard-translate":
3201
+ if self.headers.get("Origin") not in {None, "", f"http://{self.headers.get('Host', '')}"}:
3202
+ self._send(403, {"error": "INVALID_ORIGIN"})
3203
+ return
3204
+ if not isinstance(selected, str) or selected not in project_ids:
3205
+ self._send(409, {"error": "CONSOLE_PROJECT_UNAVAILABLE"})
3206
+ return
3207
+ try:
3208
+ length = int(self.headers.get("Content-Length", "0"))
3209
+ if not 2 <= length <= 4096:
3210
+ raise ValueError
3211
+ payload = json.loads(self.rfile.read(length).decode("utf-8"))
3212
+ if not isinstance(payload, dict) or set(payload) != {"locale", "texts"}:
3213
+ raise ValueError
3214
+ translations = dashboard_translation.translate(payload["locale"], payload["texts"])
3215
+ except dashboard_translation.DashboardTranslationError as error:
3216
+ status_code = 400 if str(error).endswith(("LOCALE_INVALID", "REQUEST_INVALID")) else 503
3217
+ self._send(status_code, {"error": str(error)})
3218
+ return
3219
+ except (ValueError, UnicodeDecodeError, json.JSONDecodeError):
3220
+ self._send(400, {"error": "DASHBOARD_TRANSLATION_REQUEST_INVALID"})
3221
+ return
3222
+ self._send(200, {"translations": translations})
3223
+ return
3224
+ if method == "do_GET" and request.path == "/" and selected in {None, ""}:
3225
+ # No selection is a valid view. It renders only the host-wide
3226
+ # controls and never substitutes the first project for content.
3227
+ document = _no_project_console_document(projects, self.server.data_root) # type: ignore[attr-defined]
3228
+ self.send_response(200)
3229
+ self.send_header("Content-Type", "text/html; charset=utf-8")
3230
+ self.send_header("Content-Length", str(len(document)))
3231
+ self.send_header("Cache-Control", "no-store")
3232
+ self.end_headers()
3233
+ self.wfile.write(document)
3234
+ return
3235
+ if selected in {None, ""}:
3236
+ if self._no_project_platform_route(method, request):
3237
+ return
3238
+ self._send(409, {"error": "CONSOLE_PROJECT_UNAVAILABLE"})
3239
+ return
3240
+ if not isinstance(selected, str) or selected not in project_ids:
3241
+ self._send(409, {"error": "CONSOLE_PROJECT_UNAVAILABLE"})
3242
+ return
3243
+ # Reaching this point would mean a route escaped the explicit Console
3244
+ # projection table above. Never restore the historical root delegate.
3245
+ self._send(404 if method == "do_GET" else 405, {"error": "CENTRAL_CONSOLE_ROUTE_UNAVAILABLE"})
3246
+
3247
+ def do_GET(self) -> None: # noqa: N802
3248
+ request = urlsplit(self.path)
3249
+ if request.path in {HTTP_JSON_OPENAPI_PATH, "/swagger.json"}:
3250
+ self._send(200, _http_json_openapi_document())
3251
+ return
3252
+ if request.path == "/diagnostics/topology":
3253
+ try:
3254
+ self._send(200, operations_projection(self.server.data_root), initialize(self.server.data_root).instance_id) # type: ignore[attr-defined]
3255
+ except ServerConfigurationError:
3256
+ self._send(503, {"error": "TOPOLOGY_DIAGNOSTIC_UNAVAILABLE"})
3257
+ return
3258
+ if self.path == "/v1/operations/projects":
3259
+ try:
3260
+ self._send(200, operations_projection(self.server.data_root), initialize(self.server.data_root).instance_id) # type: ignore[attr-defined]
3261
+ except ServerConfigurationError:
3262
+ self._send(503, {"error": "operations projection unavailable"})
3263
+ return
3264
+ if request.path == "/" or request.path.startswith("/api/") or request.path.startswith("/assets/") or request.path in {"/health", "/favicon.ico", "/apple-touch-icon.png", "/apple-touch-icon-precomposed.png"}:
3265
+ self._delegate_dashboard("do_GET")
3266
+ return
3267
+ if self.path not in {"/healthz", "/readyz"}:
3268
+ self.send_error(404)
3269
+ return
3270
+ try:
3271
+ report = self._status()
3272
+ except ServerConfigurationError:
3273
+ self._send(503, {"healthy": False, "ready": False})
3274
+ return
3275
+ self._send(200, report, str(report["instance_id"]))
3276
+
3277
+ def do_POST(self) -> None: # noqa: N802
3278
+ if urlsplit(self.path).path.startswith("/api/"):
3279
+ self._delegate_dashboard("do_POST")
3280
+ return
3281
+ if self.path.startswith("/v1/projects/") and self.path.endswith("/submissions"):
3282
+ parts = self.path.split("/")
3283
+ if len(parts) != 5 or not parts[3]:
3284
+ self._send(404, {"error": "not found"})
3285
+ return
3286
+ project_id = parts[3]
3287
+ try:
3288
+ if self.headers.get_content_type() != "application/json":
3289
+ raise submission_service.SubmissionError("UNSUPPORTED_MEDIA_TYPE", 415)
3290
+ length = int(self.headers.get("Content-Length", "-1"))
3291
+ if not 0 < length <= 131072:
3292
+ raise submission_service.SubmissionError("PAYLOAD_TOO_LARGE", 413)
3293
+ raw = self.rfile.read(length)
3294
+ if b"\0" in raw:
3295
+ raise submission_service.SubmissionError("MALFORMED_REQUEST")
3296
+ payload = json.loads(raw.decode("utf-8"))
3297
+ authorization = self.headers.get("Authorization", "")
3298
+ token = authorization[7:] if authorization.startswith("Bearer ") else None
3299
+ # CLI uses this same authenticated HTTP boundary, but the
3300
+ # durable receipt must retain the original adapter. It is
3301
+ # observational provenance only: callers cannot select an
3302
+ # execution implementation through this header.
3303
+ transport = self.headers.get("EP-Submission-Transport", "HTTP")
3304
+ with sqlite3.connect(self.server.data_root / SERVER_DATABASE_FILENAME) as connection: # type: ignore[attr-defined]
3305
+ if _authenticated_consumer(connection, token, project_id) is None:
3306
+ raise submission_service.SubmissionError("UNAUTHENTICATED", 401)
3307
+ request = submission_service.request_from_mapping(project_id, payload, transport=transport)
3308
+ result = submission_service.submit(connection, request)
3309
+ self._send(200, result.to_dict(), initialize(self.server.data_root).instance_id) # type: ignore[attr-defined]
3310
+ except UnicodeDecodeError:
3311
+ self._send(400, {"error": "MALFORMED_REQUEST"})
3312
+ except json.JSONDecodeError:
3313
+ self._send(400, {"error": "MALFORMED_REQUEST"})
3314
+ except submission_service.SubmissionError as error:
3315
+ self._send(error.status, {"error": error.code})
3316
+ return
3317
+ routes = {"/v1/agent/pair": agent_trust.pair, "/v1/agent/register": agent_trust.register, "/v1/agent/heartbeat": agent_trust.heartbeat, "/v1/agent/attachment": agent_trust.register_attachment}
3318
+ action = routes.get(self.path)
3319
+ if action is None:
3320
+ self.send_error(404)
3321
+ return
3322
+ try:
3323
+ length = int(self.headers.get("Content-Length", "0"))
3324
+ if not 0 < length <= 262144:
3325
+ raise agent_trust.AgentTrustError("request body is invalid")
3326
+ body = json.loads(self.rfile.read(length).decode("utf-8"))
3327
+ authorization = self.headers.get("Authorization", "")
3328
+ token = authorization.removeprefix("Bearer ") if authorization.startswith("Bearer ") else None
3329
+ with sqlite3.connect(self.server.data_root / SERVER_DATABASE_FILENAME) as connection: # type: ignore[attr-defined]
3330
+ result = action(connection, body) if action is agent_trust.pair else action(connection, body, token)
3331
+ self._send(200, result, initialize(self.server.data_root).instance_id) # type: ignore[attr-defined]
3332
+ except (ValueError, OSError, json.JSONDecodeError, agent_trust.AgentTrustError):
3333
+ self._send(400 if self.path == "/v1/agent/pair" else 401, {"error": "agent request rejected"})
3334
+
3335
+ def log_message(self, _format: str, *_args: object) -> None:
3336
+ return
3337
+
3338
+
3339
+ def serve(data_root: Path) -> int:
3340
+ relocation = installation_relocation.apply_pending(data_root)
3341
+ imported = central_data_transfer.apply_pending_import(data_root)
3342
+ data_root = data_root.resolve()
3343
+ identity = initialize(data_root)
3344
+ if relocation is not None:
3345
+ _audit_configuration_change(
3346
+ data_root,
3347
+ scope="PLATFORM_DATA",
3348
+ key="location",
3349
+ previous=relocation["previous"],
3350
+ value=relocation["value"],
3351
+ )
3352
+ if imported is not None:
3353
+ _audit_configuration_change(
3354
+ data_root, scope="PLATFORM_DATA", key="import", previous="REPLACED",
3355
+ value=f"{imported['entries']}_ENTRIES",
3356
+ )
3357
+ config = ServerConfiguration.load(data_root)
3358
+ os.environ[SERVER_ENVIRONMENT_DATA_ROOT] = str(data_root.resolve())
3359
+ os.environ[MANAGED_CODEX_CLI_PREFIX_ENVIRONMENT] = config.managed_codex_cli_prefix
3360
+ server = http.server.ThreadingHTTPServer((config.bind_host, config.bind_port), _HealthHandler)
3361
+ server.data_root = data_root.resolve() # type: ignore[attr-defined]
3362
+ server.central_data_transfer_lock = RLock() # type: ignore[attr-defined]
3363
+ server.central_data_transfer_active = False # type: ignore[attr-defined]
3364
+ # Lifecycle composition is intentionally lazy: read-only Server import
3365
+ # and Console startup must stay independent of retired watcher modules.
3366
+ from .lifecycle_worker import LifecycleWorker
3367
+
3368
+ worker = LifecycleWorker(data_root)
3369
+ # The File Inbox is an installed Server child, not a Dashboard or
3370
+ # checkout-owned watcher. Its heartbeat is the source for its platform
3371
+ # component health; a prior successful file is never treated as liveness.
3372
+ inbox_service = file_inbox.FileInboxService(
3373
+ data_root / FILE_INBOX_DIRECTORY,
3374
+ admission=lambda envelope, receipt_id, received_at: _admit_server_owned_file_inbox(
3375
+ data_root, envelope, receipt_id, received_at,
3376
+ ),
3377
+ )
3378
+ dependabot_service = dependabot_producer.DependabotService(
3379
+ data_root,
3380
+ event=lambda event, context: log_event(
3381
+ component_logger(
3382
+ data_root,
3383
+ "dependabot_producer",
3384
+ central_database=data_root / SERVER_DATABASE_FILENAME,
3385
+ ),
3386
+ logging.INFO if event == "dependabot_submission_admitted" else logging.WARNING,
3387
+ event,
3388
+ context=context,
3389
+ ),
3390
+ )
3391
+ server.lifecycle_worker = worker # type: ignore[attr-defined]
3392
+ server.inbox_service = inbox_service # type: ignore[attr-defined]
3393
+ server.dependabot_service = dependabot_service # type: ignore[attr-defined]
3394
+ _write_json(data_root / SERVER_RUNTIME_FILENAME, {"pid": os.getpid(), "instance_id": identity.instance_id, "started_at": _utcnow()})
3395
+ def stop(_signum: int, _frame: object) -> None:
3396
+ # ``shutdown`` must run outside the serve_forever thread.
3397
+ import threading
3398
+ threading.Thread(target=server.shutdown, daemon=True).start()
3399
+ signal.signal(signal.SIGTERM, stop)
3400
+ signal.signal(signal.SIGINT, stop)
3401
+ worker.start()
3402
+ if not worker.wait_until_running():
3403
+ worker.stop()
3404
+ server.server_close()
3405
+ raise RuntimeError("Lifecycle Worker did not become ready.")
3406
+ inbox_service.start()
3407
+ dependabot_service.start()
3408
+ # A fresh installation must have operational evidence before its first
3409
+ # submission. These are genuine Server lifecycle events, persisted in
3410
+ # the same CENTRAL store that backs the Console's combined log table.
3411
+ for definition in PLATFORM_COMPONENTS:
3412
+ log_event(
3413
+ component_logger(
3414
+ data_root, definition.id,
3415
+ central_database=data_root / SERVER_DATABASE_FILENAME,
3416
+ ),
3417
+ logging.INFO,
3418
+ definition.startup_event,
3419
+ context={"target_component": definition.id},
3420
+ )
3421
+ try:
3422
+ server.serve_forever()
3423
+ finally:
3424
+ dependabot_service.stop()
3425
+ inbox_service.stop()
3426
+ worker.stop()
3427
+ server.server_close()
3428
+ (data_root / SERVER_RUNTIME_FILENAME).unlink(missing_ok=True)
3429
+ return 0
3430
+
3431
+
3432
+ def start(data_root: Path) -> dict[str, object]:
3433
+ current = status(data_root)
3434
+ if current["running"]:
3435
+ return current
3436
+ # The installed entrypoint supplies the interpreter. Run from the
3437
+ # installation-owned data root and discard Python import overrides so a
3438
+ # caller's checkout can never become the child Server's import authority.
3439
+ runtime_root = data_root.resolve()
3440
+ configuration = ServerConfiguration.load(runtime_root)
3441
+ # npm is the preserved managed-runtime installer. These are fixed host
3442
+ # tool directories, never a provider-executable fallback or caller PATH.
3443
+ environment = {
3444
+ "PATH": "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin",
3445
+ "PYTHONNOUSERSITE": "1",
3446
+ "PYTHONSAFEPATH": "1",
3447
+ MANAGED_CODEX_CLI_PREFIX_ENVIRONMENT: configuration.managed_codex_cli_prefix,
3448
+ SERVER_ENVIRONMENT_DATA_ROOT: str(runtime_root),
3449
+ }
3450
+ if home := os.environ.get("HOME"):
3451
+ environment["HOME"] = home
3452
+ # Unit tests exercise the lifecycle from an unpackaged source tree. This
3453
+ # explicit test-only bridge is never inherited by an installed process.
3454
+ if "unittest" in sys.argv[0]:
3455
+ environment["PYTHONPATH"] = str(Path(__file__).resolve().parents[1])
3456
+ child = subprocess.Popen([sys.executable, "-m", "engineering_platform.server", "serve", "--data-root", str(runtime_root)], cwd=str(runtime_root), env=environment, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # nosec B603
3457
+ _CHILDREN[child.pid] = child
3458
+ for _ in range(40):
3459
+ time.sleep(0.05)
3460
+ current = status(data_root)
3461
+ if current["running"]:
3462
+ return current
3463
+ raise RuntimeError("EP Server did not become ready.")
3464
+
3465
+
3466
+ def stop(data_root: Path) -> dict[str, object]:
3467
+ runtime = _runtime(data_root)
3468
+ if runtime and _alive(runtime.get("pid")):
3469
+ os.kill(int(runtime["pid"]), signal.SIGTERM)
3470
+ child = _CHILDREN.pop(int(runtime["pid"]), None)
3471
+ if child is not None:
3472
+ try:
3473
+ child.wait(timeout=2)
3474
+ except subprocess.TimeoutExpired:
3475
+ pass
3476
+ for _ in range(40):
3477
+ time.sleep(0.05)
3478
+ if not _alive(runtime["pid"]):
3479
+ break
3480
+ (data_root / SERVER_RUNTIME_FILENAME).unlink(missing_ok=True)
3481
+ return status(data_root)
3482
+
3483
+
3484
+ def health(data_root: Path) -> dict[str, object]:
3485
+ result = status(data_root)
3486
+ if not result["running"]:
3487
+ return {**result, "healthy": False, "ready": False}
3488
+ bind = result["bind"]
3489
+ try:
3490
+ # Server configuration permits loopback host only.
3491
+ with urlopen(f"http://{bind['host']}:{bind['port']}/health", timeout=1) as response: # nosec B310
3492
+ response.read()
3493
+ return {**result, "healthy": True, "ready": True}
3494
+ except (URLError, OSError):
3495
+ return {**result, "healthy": False, "ready": False}
3496
+
3497
+
3498
+ def build_parser() -> argparse.ArgumentParser:
3499
+ parser = argparse.ArgumentParser(prog="engineering-platform-server", description="Manage the standalone Engineering Platform Server foundation")
3500
+ parser.add_argument("command", choices=("init", "start", "serve", "stop", "status", "health", "service-install", "service-uninstall", "relay-install", "relay-uninstall", "pairing-create", "agent-status", "agent-revoke", "agent-reset", "topology", "submission-diagnose", "bootstrap-topology", "register-topology", "provision-declaration", "issue-consumer-credential", "bind-repository", "rebind-repository", "unbind-repository", "resolve-repository", "register-producer-binding", "list-producer-bindings", "deactivate-producer-binding"))
3501
+ parser.add_argument("--data-root", type=Path, default=default_data_root())
3502
+ parser.add_argument("--bind-host", default="127.0.0.1")
3503
+ parser.add_argument("--bind-port", type=int, default=8765)
3504
+ parser.add_argument("--agent-id")
3505
+ parser.add_argument("--project-id")
3506
+ parser.add_argument("--repository-id")
3507
+ parser.add_argument("--path", type=Path)
3508
+ parser.add_argument("--declaration", type=Path)
3509
+ parser.add_argument("--consumer-id")
3510
+ parser.add_argument("--submission-id")
3511
+ parser.add_argument("--producer-type")
3512
+ parser.add_argument("--external-resource-type")
3513
+ parser.add_argument("--external-resource-identity")
3514
+ parser.add_argument("--binding-id")
3515
+ parser.add_argument("--reason")
3516
+ return parser
3517
+
3518
+
3519
+ def main(argv: list[str] | None = None) -> int:
3520
+ args = build_parser().parse_args(argv)
3521
+ try:
3522
+ if args.command == "init":
3523
+ result = {"instance_id": initialize(args.data_root, bind_host=args.bind_host, bind_port=args.bind_port).instance_id, "initialized": True}
3524
+ elif args.command == "start":
3525
+ initialize(args.data_root)
3526
+ configuration = ServerConfiguration.load(args.data_root)
3527
+ if (configuration.bind_host, configuration.bind_port) != (args.bind_host, args.bind_port):
3528
+ _write_json(args.data_root / SERVER_CONFIGURATION_FILENAME, {
3529
+ "version": configuration.version, "bind_host": args.bind_host,
3530
+ "bind_port": args.bind_port,
3531
+ "managed_codex_cli_prefix": configuration.managed_codex_cli_prefix,
3532
+ })
3533
+ result = start(args.data_root)
3534
+ elif args.command == "serve": return serve(args.data_root)
3535
+ elif args.command == "stop": result = stop(args.data_root)
3536
+ elif args.command == "status": result = status(args.data_root)
3537
+ elif args.command == "health": result = health(args.data_root)
3538
+ elif args.command == "service-install":
3539
+ initialize(args.data_root)
3540
+ result = {"result": "INSTALLED", **server_service.install(args.data_root)}
3541
+ elif args.command == "service-uninstall": result = {"result": "UNINSTALLED", **server_service.uninstall(args.data_root)}
3542
+ elif args.command == "relay-install":
3543
+ initialize(args.data_root)
3544
+ result = {"result": "INSTALLED", **server_relay.install(args.data_root)}
3545
+ elif args.command == "relay-uninstall":
3546
+ result = {"result": "UNINSTALLED", **server_relay.uninstall()}
3547
+ elif args.command == "topology":
3548
+ initialize(args.data_root)
3549
+ with sqlite3.connect(args.data_root / SERVER_DATABASE_FILENAME) as connection:
3550
+ result = project_topology.topology(connection)
3551
+ elif args.command == "submission-diagnose":
3552
+ if not args.submission_id:
3553
+ raise ServerConfigurationError("--submission-id is required for submission diagnostics.")
3554
+ initialize(args.data_root)
3555
+ with sqlite3.connect(args.data_root / SERVER_DATABASE_FILENAME) as connection:
3556
+ row = connection.execute("SELECT s.project_id,s.repository_id,s.state,s.admission,s.transport,s.transport_receipt_id,s.transport_received_at,p.run_id,d.state,d.operator_resolution,p.project_id,p.repository_id FROM ep_submissions s LEFT JOIN ep_receipt_run_provenance p ON p.submission_id=s.submission_id LEFT JOIN ep_parity_lifecycle_dispatches d ON d.run_id=p.run_id WHERE s.submission_id=?", (args.submission_id,)).fetchone()
3557
+ if row is None:
3558
+ raise ServerConfigurationError("UNKNOWN_SUBMISSION")
3559
+ project_id, repository_id, state, admission, transport, receipt_id, received_at, run_id, dispatch_state, resolution, dispatch_project, dispatch_repository = row
3560
+ blocked = connection.execute("SELECT run_id,state FROM ep_parity_lifecycle_dispatches WHERE project_id=? AND submission_id!=? AND state IN ('CLAIMED','RUNNING','BLOCKED','FAILED') AND run_id!=? ORDER BY updated_at LIMIT 1", (project_id, args.submission_id, run_id or "")).fetchone()
3561
+ admission_audit = connection.execute("SELECT 1 FROM ep_submission_events WHERE submission_id=? AND event_kind='ADMISSION_GRANTED'", (args.submission_id,)).fetchone()
3562
+ early = None
3563
+ if run_id:
3564
+ early_path = args.data_root / "artifacts" / "projects" / str(project_id) / "runs" / str(run_id) / "early-runner-failure.json"
3565
+ if early_path.is_file():
3566
+ try:
3567
+ early = json.loads(early_path.read_text(encoding="utf-8"))
3568
+ except (OSError, json.JSONDecodeError):
3569
+ early = {"diagnostic_code": "EARLY_FAILURE_EVIDENCE_UNAVAILABLE"}
3570
+ receipt_complete = transport != "FILE_INBOX" or (isinstance(receipt_id, str) and bool(receipt_id) and isinstance(received_at, str) and bool(received_at))
3571
+ scope_complete = run_id is not None and (dispatch_project, dispatch_repository) == (project_id, repository_id)
3572
+ result = {"submission_id": args.submission_id, "project_id": project_id, "repository_id": repository_id, "submission_state": state, "admission": admission, "run_id": run_id, "dispatch_state": dispatch_state, "operator_resolution": resolution, "transport_provenance": "COMPLETE" if receipt_complete else "INCOMPLETE", "admission_audit_provenance": "PRESENT" if admission_audit else "UNAVAILABLE", "receipt_run_provenance": "PRESENT" if run_id else "UNAVAILABLE", "dispatch_scope_provenance": "COMPLETE" if scope_complete else "UNAVAILABLE", "lane_blocker": {"run_id": blocked[0], "state": blocked[1]} if blocked else None, "early_failure": early, "worker_eligible": state == "QUEUED" and admission == "ADMITTED" and blocked is None}
3573
+ elif args.command == "register-topology":
3574
+ if args.declaration is None:
3575
+ raise ServerConfigurationError("--declaration is required for explicit topology registration.")
3576
+ initialize(args.data_root)
3577
+ try:
3578
+ declaration = json.loads(args.declaration.read_text(encoding="utf-8"))
3579
+ except (OSError, json.JSONDecodeError) as error:
3580
+ raise ServerConfigurationError("REPOSITORY_DECLARATION_UNREADABLE") from error
3581
+ with sqlite3.connect(args.data_root / SERVER_DATABASE_FILENAME) as connection:
3582
+ result = project_topology.register_server_local_topology(connection, declaration=declaration)
3583
+ elif args.command == "bootstrap-topology":
3584
+ if not args.project_id or not args.repository_id:
3585
+ raise ServerConfigurationError("--project-id and --repository-id are required for topology bootstrap.")
3586
+ initialize(args.data_root)
3587
+ declaration = {"schema_version": "1.0", "project": {"id": args.project_id, "authority_repository_id": args.repository_id}, "repository": {"id": args.repository_id, "role": "authority"}, "validation": {"kind": "none"}}
3588
+ with sqlite3.connect(args.data_root / SERVER_DATABASE_FILENAME) as connection:
3589
+ result = project_topology.register_server_local_topology(connection, declaration=declaration)
3590
+ elif args.command == "issue-consumer-credential":
3591
+ if not args.project_id or not args.consumer_id:
3592
+ raise ServerConfigurationError("--project-id and --consumer-id are required for credential issuance.")
3593
+ initialize(args.data_root)
3594
+ from .submission_service import issue_consumer_credential
3595
+ with sqlite3.connect(args.data_root / SERVER_DATABASE_FILENAME) as connection:
3596
+ result = issue_consumer_credential(connection, consumer_id=args.consumer_id, project_id=args.project_id)
3597
+ elif args.command == "register-producer-binding":
3598
+ if not all((args.producer_type, args.external_resource_type, args.external_resource_identity, args.project_id, args.repository_id, args.reason)):
3599
+ raise ServerConfigurationError("--producer-type, --external-resource-type, --external-resource-identity, --project-id, --repository-id and --reason are required for producer binding registration.")
3600
+ initialize(args.data_root)
3601
+ with sqlite3.connect(args.data_root / SERVER_DATABASE_FILENAME) as connection:
3602
+ binding = external_producer_binding.register(
3603
+ connection,
3604
+ data_root=args.data_root,
3605
+ producer_type=args.producer_type,
3606
+ external_resource_type=args.external_resource_type,
3607
+ external_resource_identity=args.external_resource_identity,
3608
+ project_id=args.project_id,
3609
+ repository_id=args.repository_id,
3610
+ reason=args.reason,
3611
+ )
3612
+ result = {"binding_id": binding.binding_id, "project_id": binding.project_id, "repository_id": binding.repository_id, "version": binding.version, "result": "REGISTERED"}
3613
+ elif args.command == "list-producer-bindings":
3614
+ initialize(args.data_root)
3615
+ with sqlite3.connect(args.data_root / SERVER_DATABASE_FILENAME) as connection:
3616
+ result = {"bindings": external_producer_binding.list_bindings(connection, data_root=args.data_root)}
3617
+ elif args.command == "deactivate-producer-binding":
3618
+ if not args.binding_id or not args.reason:
3619
+ raise ServerConfigurationError("--binding-id and --reason are required for producer binding deactivation.")
3620
+ initialize(args.data_root)
3621
+ with sqlite3.connect(args.data_root / SERVER_DATABASE_FILENAME) as connection:
3622
+ binding = external_producer_binding.deactivate(connection, data_root=args.data_root, binding_id=args.binding_id, reason=args.reason)
3623
+ result = {"binding_id": binding.binding_id, "project_id": binding.project_id, "repository_id": binding.repository_id, "version": binding.version, "result": "DEACTIVATED"}
3624
+ elif args.command == "provision-declaration":
3625
+ if not args.project_id or not args.repository_id or args.path is None:
3626
+ raise ServerConfigurationError("--project-id, --repository-id and --path are required for declaration provisioning.")
3627
+ initialize(args.data_root)
3628
+ from .repository_attachment import config_path, load_repository_attachment, parse_repository_attachment
3629
+ root = args.path.resolve(strict=True)
3630
+ with sqlite3.connect(args.data_root / SERVER_DATABASE_FILENAME) as connection:
3631
+ row = connection.execute("SELECT attachment_contract FROM ep_repository_registrations WHERE project_id=? AND repository_id=?", (args.project_id, args.repository_id)).fetchone()
3632
+ if row is None:
3633
+ raise ServerConfigurationError("CENTRAL_REPOSITORY_NOT_REGISTERED")
3634
+ declaration = json.loads(str(row[0]))
3635
+ parse_repository_attachment(declaration)
3636
+ target = config_path(root)
3637
+ if target.exists():
3638
+ existing = load_repository_attachment(root)
3639
+ if (existing.project_id, existing.repository_id) != (args.project_id, args.repository_id):
3640
+ raise ServerConfigurationError("REPOSITORY_DECLARATION_CONFLICT")
3641
+ else:
3642
+ target.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
3643
+ target.write_text(json.dumps(declaration, sort_keys=True, indent=2) + "\n", encoding="utf-8")
3644
+ target.chmod(0o600)
3645
+ result = {"project_id": args.project_id, "repository_id": args.repository_id, "path": str(target), "result": "PROVISIONED"}
3646
+ elif args.command in {"bind-repository", "rebind-repository", "unbind-repository", "resolve-repository"}:
3647
+ if not args.project_id or not args.repository_id:
3648
+ raise ServerConfigurationError("--project-id and --repository-id are required for local binding commands.")
3649
+ initialize(args.data_root)
3650
+ with sqlite3.connect(args.data_root / SERVER_DATABASE_FILENAME) as connection:
3651
+ if args.command in {"bind-repository", "rebind-repository"}:
3652
+ if args.path is None:
3653
+ raise ServerConfigurationError("--path is required when binding a repository.")
3654
+ binding = local_repository_binding.bind_local_repository(connection, project_id=args.project_id, repository_id=args.repository_id, local_root=args.path, data_root=args.data_root, rebind=args.command == "rebind-repository")
3655
+ result = {"project_id": binding.project_id, "repository_id": binding.repository_id, "state": binding.state}
3656
+ elif args.command == "unbind-repository":
3657
+ local_repository_binding.unbind_local_repository(connection, project_id=args.project_id, repository_id=args.repository_id)
3658
+ result = {"project_id": args.project_id, "repository_id": args.repository_id, "state": "UNBOUND"}
3659
+ else:
3660
+ binding = local_repository_binding.resolve_execution_repository(connection, project_id=args.project_id, repository_id=args.repository_id, data_root=args.data_root)
3661
+ result = {"project_id": binding.project_id, "repository_id": binding.repository_id, "state": binding.state}
3662
+ else:
3663
+ if not args.agent_id:
3664
+ raise ServerConfigurationError("--agent-id is required for Agent lifecycle commands.")
3665
+ initialize(args.data_root)
3666
+ with sqlite3.connect(args.data_root / SERVER_DATABASE_FILENAME) as connection:
3667
+ if args.command == "pairing-create": result = agent_trust.create_pairing_code(connection, args.agent_id)
3668
+ elif args.command == "agent-status": result = agent_trust.registration_status(connection, args.agent_id)
3669
+ elif args.command == "agent-revoke": result = {"agent_id": args.agent_id, "revoked": agent_trust.revoke(connection, args.agent_id)}
3670
+ else: result = {"agent_id": args.agent_id, "reset": agent_trust.reset(connection, args.agent_id)}
3671
+ except (OSError, RuntimeError, PermissionError, ServerConfigurationError, local_repository_binding.LocalRepositoryBindingError, external_producer_binding.ProducerBindingError) as error:
3672
+ print(json.dumps({"error": str(error), "ready": False}, sort_keys=True))
3673
+ return 2
3674
+ print(json.dumps(result, sort_keys=True))
3675
+ return 0 if result.get("ready", True) else 1
3676
+
3677
+
3678
+ if __name__ == "__main__":
3679
+ raise SystemExit(main())