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.

Potentially problematic release.


This version of engineering-platform might be problematic. Click here for more details.

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,471 @@
1
+ """Qualified provider contracts and current local implementations.
2
+
3
+ Provider selection is configuration-owned. These protocols deliberately expose
4
+ diagnostics only; they do not grant execution, repository or network authority.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass
9
+ from ipaddress import IPv4Address, IPv4Network
10
+ import os
11
+ import pwd
12
+ from pathlib import Path
13
+ import re
14
+ import shutil
15
+ import subprocess
16
+ import sys
17
+ from typing import Mapping, Protocol, Sequence
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class ProviderStatus:
22
+ name: str
23
+ version: str
24
+ qualified: bool
25
+ detail: str
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class LaunchdRuntimeDetails:
30
+ """Host observation for one owned LaunchAgent, without control authority."""
31
+
32
+ label: str
33
+ loaded: bool
34
+ active: bool
35
+ pid: int | None
36
+ last_exit_code: str | None
37
+ memory_kib: int | None = None
38
+ uptime_seconds: int | None = None
39
+
40
+
41
+ def _elapsed_seconds(value: str) -> int | None:
42
+ """Parse macOS ``ps etime`` without treating malformed host output as fact."""
43
+ match = re.fullmatch(r"(?:(\d+)-)?(?:(\d{1,2}):)?(\d{1,2}):(\d{2})", value)
44
+ if not match:
45
+ return None
46
+ days, hours, minutes, seconds = (int(part or 0) for part in match.groups())
47
+ if minutes >= 60 or seconds >= 60:
48
+ return None
49
+ return days * 86_400 + hours * 3_600 + minutes * 60 + seconds
50
+
51
+
52
+ class RuntimeProvider(Protocol):
53
+ def status(self) -> ProviderStatus: ...
54
+
55
+
56
+ class ProcessProvider(Protocol):
57
+ """The sole boundary for local child-process execution."""
58
+
59
+ def execute(
60
+ self, root: Path, arguments: Sequence[str], *, environment: Mapping[str, str] | None = None,
61
+ ) -> subprocess.CompletedProcess[str]: ...
62
+
63
+ def spawn(self, root: Path, arguments: Sequence[str]) -> subprocess.Popen[str]: ...
64
+
65
+ def spawn_detached(self, root: Path, arguments: Sequence[str], environment: Mapping[str, str]) -> subprocess.Popen[bytes]: ...
66
+
67
+
68
+ class LocalProcessProvider:
69
+ """Default local process adapter; orchestration code never imports subprocess for work."""
70
+
71
+ def execute(
72
+ self, root: Path, arguments: Sequence[str], *, environment: Mapping[str, str] | None = None,
73
+ ) -> subprocess.CompletedProcess[str]:
74
+ return subprocess.run(
75
+ arguments, cwd=root, env=dict(environment) if environment is not None else None,
76
+ text=True, capture_output=True, check=False,
77
+ )
78
+
79
+ def spawn(self, root: Path, arguments: Sequence[str]) -> subprocess.Popen[str]:
80
+ return subprocess.Popen(
81
+ tuple(arguments), cwd=root, text=True, stdout=subprocess.PIPE,
82
+ stderr=subprocess.STDOUT, start_new_session=True,
83
+ )
84
+
85
+ def spawn_detached(self, root: Path, arguments: Sequence[str], environment: Mapping[str, str]) -> subprocess.Popen[bytes]:
86
+ return subprocess.Popen(
87
+ tuple(arguments), cwd=root, env=dict(environment), start_new_session=True,
88
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
89
+ )
90
+
91
+
92
+ def installed_python_environment() -> dict[str, str]:
93
+ """Return the Server-owned interpreter environment for child validation.
94
+
95
+ An installed Engineering Platform Server is executed by its installation
96
+ virtual environment. Child validation must retain that interpreter when a
97
+ repository asks for the conventional ``python -m ...`` command; otherwise
98
+ it can accidentally resolve a different system Python.
99
+ """
100
+ # Keep the venv launcher path intact. Resolving it selects the framework
101
+ # base interpreter and lets a child validation command escape the EP venv.
102
+ executable = Path(sys.executable).expanduser().absolute()
103
+ environment = dict(os.environ)
104
+ environment["PATH"] = str(executable.parent) + os.pathsep + environment.get("PATH", "")
105
+ virtual_environment = executable.parent.parent
106
+ if (virtual_environment / "pyvenv.cfg").is_file():
107
+ environment["VIRTUAL_ENV"] = str(virtual_environment)
108
+ return environment
109
+
110
+
111
+ class DeterministicValidationExecutor:
112
+ """Run one resolved validation control outside provider-agent dispatch."""
113
+
114
+ def __init__(self, process: ProcessProvider | None = None) -> None:
115
+ self.process = process or LocalProcessProvider()
116
+
117
+ def run(self, root: Path, command: tuple[str, ...]) -> "DeterministicValidationResult":
118
+ try:
119
+ completed = self.process.execute(root, command, environment=installed_python_environment())
120
+ stdout = completed.stdout
121
+ stderr = completed.stderr
122
+ return DeterministicValidationResult(
123
+ exit_code=completed.returncode,
124
+ stdout=stdout if isinstance(stdout, str) else None,
125
+ stderr=stderr if isinstance(stderr, str) else None,
126
+ diagnostic_capture_available=isinstance(stdout, str) and isinstance(stderr, str),
127
+ )
128
+ except OSError:
129
+ return DeterministicValidationResult(
130
+ exit_code=None, stdout=None, stderr=None,
131
+ diagnostic_capture_available=False,
132
+ )
133
+
134
+
135
+ @dataclass(frozen=True)
136
+ class DeterministicValidationResult:
137
+ """One deterministic command outcome, including non-authoritative output."""
138
+
139
+ exit_code: int | None
140
+ stdout: str | None
141
+ stderr: str | None
142
+ diagnostic_capture_available: bool
143
+
144
+
145
+ class RepositoryProvider(Protocol):
146
+ def status(self, root: Path) -> ProviderStatus: ...
147
+ def command(self, root: Path, *args: str) -> str: ...
148
+
149
+
150
+ class ServiceManagerProvider(Protocol):
151
+ def status(self) -> ProviderStatus: ...
152
+ def install(self, label: str, plist: Path) -> None: ...
153
+ def uninstall(self, plist: Path) -> None: ...
154
+
155
+
156
+ class RemoteSubmissionProvider(Protocol):
157
+ def status(self) -> ProviderStatus: ...
158
+
159
+
160
+ class PrivateRemoteAccessProvider(Protocol):
161
+ def status(self) -> ProviderStatus: ...
162
+
163
+
164
+ MANAGED_CODEX_CLI_PREFIX_ENVIRONMENT = "EP_MANAGED_CODEX_CLI_PREFIX"
165
+
166
+
167
+ def default_engineering_platform_codex_cli_prefix() -> Path:
168
+ """Return the stable account-owned default, independent of ``$HOME``.
169
+
170
+ A runner may deliberately receive an isolated HOME for tool state. That
171
+ must never manufacture a second EP-managed CLI installation location.
172
+ """
173
+ try:
174
+ account_home = Path(pwd.getpwuid(os.getuid()).pw_dir)
175
+ except (KeyError, OSError):
176
+ account_home = Path.home()
177
+ return account_home / ".local" / "share" / "engineering-platform" / "codex-cli"
178
+
179
+
180
+ def engineering_platform_codex_cli_prefix() -> Path:
181
+ """Return the installation-pinned CLI prefix, never a process HOME path."""
182
+ configured = os.environ.get(MANAGED_CODEX_CLI_PREFIX_ENVIRONMENT)
183
+ if configured:
184
+ candidate = Path(configured).expanduser()
185
+ if candidate.is_absolute():
186
+ return candidate.resolve(strict=False)
187
+ return default_engineering_platform_codex_cli_prefix()
188
+
189
+
190
+ def codex_cli_executable() -> str | None:
191
+ """Return only Engineering Platform's managed Codex CLI executable."""
192
+ managed = engineering_platform_codex_cli_prefix() / "bin" / "codex"
193
+ if managed.is_file() and os.access(managed, os.X_OK):
194
+ return str(managed)
195
+ return None
196
+
197
+
198
+ class CodexCliProvider(LocalProcessProvider):
199
+ """Codex process adapter pinned exclusively to EP's managed launcher."""
200
+
201
+ def __init__(self, executable: str | None = None) -> None:
202
+ del executable # Runtime injection must not bypass EP's managed CLI.
203
+ self._executable = codex_cli_executable() or ""
204
+
205
+ def managed_installation_path(self) -> str | None:
206
+ """Return provenance only when this invocation is pinned to EP's CLI."""
207
+ managed = engineering_platform_codex_cli_prefix() / "bin" / "codex"
208
+ return str(engineering_platform_codex_cli_prefix()) if self._executable == str(managed) else None
209
+
210
+ def _arguments(self, arguments: Sequence[str]) -> tuple[str, ...]:
211
+ if arguments and arguments[0] == "codex":
212
+ return (self._executable, *arguments[1:])
213
+ return tuple(arguments)
214
+
215
+ def status(self) -> ProviderStatus:
216
+ available = bool(self._executable) and Path(self._executable).is_file() and os.access(self._executable, os.X_OK)
217
+ return ProviderStatus("codex_cli", "configured", available, "available" if available else "codex unavailable")
218
+
219
+ def command(self, *args: str) -> subprocess.CompletedProcess[str]:
220
+ if not self._executable:
221
+ raise FileNotFoundError("Engineering Platform managed Codex CLI is unavailable")
222
+ return subprocess.run((self._executable, *args), text=True, capture_output=True, check=False)
223
+
224
+ def app_server(self) -> subprocess.Popen[str]:
225
+ """Open the provider-owned interactive Codex app-server channel."""
226
+ if not self._executable:
227
+ raise FileNotFoundError("Engineering Platform managed Codex CLI is unavailable")
228
+ return subprocess.Popen(
229
+ (self._executable, "app-server"), stdin=subprocess.PIPE, stdout=subprocess.PIPE,
230
+ stderr=subprocess.DEVNULL, text=True, bufsize=1,
231
+ )
232
+
233
+ def close_app_server(self, process: subprocess.Popen[str]) -> None:
234
+ process.terminate()
235
+ try:
236
+ process.wait(timeout=1)
237
+ except subprocess.TimeoutExpired:
238
+ process.kill()
239
+ process.wait(timeout=1)
240
+ for stream in (process.stdin, process.stdout):
241
+ if stream is not None:
242
+ stream.close()
243
+
244
+ def invoke(
245
+ self,
246
+ root: Path,
247
+ arguments: tuple[str, ...],
248
+ *,
249
+ timeout: float | None = None,
250
+ environment: Mapping[str, str] | None = None,
251
+ input_text: str | None = None,
252
+ ) -> subprocess.CompletedProcess[str]:
253
+ """Execute a complete Codex command; callers never spawn its CLI directly."""
254
+ command = self._arguments(arguments)
255
+ # Reviewer invocations are bounded advisory work. Primary execution
256
+ # streams through ``spawn`` and does not supply a timeout here.
257
+ if environment is None and input_text is None:
258
+ return self.execute(root, command)
259
+ # The executable is this provider's configured Codex launcher, never a
260
+ # caller-selected command. Remaining values are Codex CLI arguments.
261
+ return subprocess.run(
262
+ (self._executable, *command[1:]), cwd=root,
263
+ env=dict(environment) if environment is not None else None, timeout=timeout,
264
+ text=True, input=input_text, capture_output=True, check=False,
265
+ )
266
+
267
+ def spawn_invocation(
268
+ self, root: Path, arguments: tuple[str, ...], *, environment: Mapping[str, str] | None = None
269
+ ) -> subprocess.Popen[str]:
270
+ command = self._arguments(arguments)
271
+ if environment is None:
272
+ return self.spawn(root, command)
273
+ return subprocess.Popen(
274
+ (self._executable, *command[1:]), cwd=root, env=dict(environment),
275
+ text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, start_new_session=True,
276
+ )
277
+
278
+
279
+ class GitProvider(LocalProcessProvider):
280
+ """Local Git provider, deliberately separate from the GitHub API provider."""
281
+
282
+ def execute(self, root: Path, *args: str) -> subprocess.CompletedProcess[str]:
283
+ return super().execute(root, args)
284
+
285
+ def command(self, root: Path, *args: str) -> str:
286
+ """Run Git and expose its bounded text result to repository orchestration."""
287
+ completed = self.execute(root, *args)
288
+ if completed.returncode:
289
+ raise RuntimeError(completed.stderr.strip() or completed.stdout.strip() or "git command failed")
290
+ return completed.stdout.strip()
291
+
292
+
293
+ class GitHubProvider:
294
+ def status(self, root: Path) -> ProviderStatus:
295
+ remote = GitProvider().execute(root, "git", "remote", "get-url", "origin")
296
+ qualified = remote.returncode == 0 and "github" in remote.stdout.lower()
297
+ return ProviderStatus("github", "configured", qualified, remote.stdout.strip() if qualified else "GitHub origin unavailable")
298
+
299
+ def github(self, *args: str) -> str:
300
+ completed = subprocess.run(("gh", *args), text=True, capture_output=True, check=False)
301
+ if completed.returncode:
302
+ raise RuntimeError(completed.stderr.strip() or "GitHub provider command failed")
303
+ return completed.stdout.strip()
304
+
305
+
306
+ class LaunchdProvider:
307
+ def status(self) -> ProviderStatus:
308
+ available = shutil.which("launchctl") is not None
309
+ return ProviderStatus("launchd", "configured", available, "available" if available else "launchctl unavailable")
310
+
311
+ def install(self, label: str, plist: Path) -> None:
312
+ subprocess.run(("launchctl", "bootout", f"gui/{__import__('os').getuid()}", str(plist)), check=False, capture_output=True)
313
+ subprocess.run(("launchctl", "bootstrap", f"gui/{__import__('os').getuid()}", str(plist)), check=False)
314
+
315
+ def uninstall(self, plist: Path) -> None:
316
+ subprocess.run(("launchctl", "bootout", f"gui/{__import__('os').getuid()}", str(plist)), check=False)
317
+
318
+ def inspect(self, label: str) -> bool:
319
+ executable = shutil.which("launchctl")
320
+ if not executable:
321
+ return False
322
+ return subprocess.run((executable, "print", f"gui/{__import__('os').getuid()}/{label}"), text=True, capture_output=True, check=False).returncode == 0
323
+
324
+ def runtime_status(self, label: str) -> ProviderStatus:
325
+ """Return whether one owned LaunchAgent has a live service process.
326
+
327
+ ``launchctl print`` succeeding only proves that a job remains loaded.
328
+ A KeepAlive job can be loaded while repeatedly exiting, so reporting it
329
+ as healthy would project a stale "active" status to the dashboard.
330
+ """
331
+ details = self.runtime_details(label)
332
+ if not details.loaded:
333
+ if shutil.which("launchctl") is None:
334
+ return ProviderStatus("launchd", "configured", False, "launchctl unavailable")
335
+ return ProviderStatus("launchd", "configured", False, "LaunchAgent is not loaded")
336
+ if details.active:
337
+ return ProviderStatus("launchd", "configured", True, "LaunchAgent process is active")
338
+ return ProviderStatus(
339
+ "launchd",
340
+ "configured",
341
+ False,
342
+ "LaunchAgent is loaded but has no active process",
343
+ )
344
+
345
+ def runtime_details(self, label: str) -> LaunchdRuntimeDetails:
346
+ """Read the actual LaunchAgent host state used by component detail views."""
347
+ executable = shutil.which("launchctl")
348
+ if not executable:
349
+ return LaunchdRuntimeDetails(label, False, False, None, None)
350
+ completed = subprocess.run(
351
+ (executable, "print", f"gui/{os.getuid()}/{label}"),
352
+ text=True,
353
+ capture_output=True,
354
+ check=False,
355
+ )
356
+ if completed.returncode:
357
+ return LaunchdRuntimeDetails(label, False, False, None, None)
358
+ output = completed.stdout
359
+ active_count = re.search(r"(?m)^\s*active count\s*=\s*(\d+)", output)
360
+ pid_match = re.search(r"(?m)^\s*pid\s*=\s*([1-9]\d*)", output)
361
+ last_exit = re.search(r"(?m)^\s*last exit code\s*=\s*(.+)$", output)
362
+ pid = int(pid_match.group(1)) if pid_match else None
363
+ active = (active_count is not None and int(active_count.group(1)) > 0) or pid is not None
364
+ memory_kib, uptime_seconds = self._process_metrics(pid) if active and pid else (None, None)
365
+ return LaunchdRuntimeDetails(
366
+ label, True, active, pid,
367
+ last_exit.group(1).strip() if last_exit else None,
368
+ memory_kib, uptime_seconds,
369
+ )
370
+
371
+ @staticmethod
372
+ def _process_metrics(pid: int) -> tuple[int | None, int | None]:
373
+ """Read memory and elapsed time for a launchd-proven process only."""
374
+ executable = shutil.which("ps")
375
+ if not executable:
376
+ return None, None
377
+ completed = subprocess.run(
378
+ (executable, "-o", "rss=", "-o", "etime=", "-p", str(pid)),
379
+ text=True,
380
+ capture_output=True,
381
+ check=False,
382
+ )
383
+ if completed.returncode:
384
+ return None, None
385
+ columns = completed.stdout.strip().split()
386
+ if len(columns) != 2 or not columns[0].isdigit():
387
+ return None, None
388
+ return int(columns[0]), _elapsed_seconds(columns[1])
389
+
390
+ def restart(self, label: str) -> None:
391
+ executable = shutil.which("launchctl")
392
+ if not executable:
393
+ raise OSError("launchctl unavailable")
394
+ completed = subprocess.run((executable, "kickstart", "-k", f"gui/{__import__('os').getuid()}/{label}"), text=True, capture_output=True, check=False)
395
+ if completed.returncode:
396
+ raise OSError(completed.stderr.strip() or "launchd restart failed")
397
+
398
+ def quiesce(self, label: str, plist: Path) -> None:
399
+ """Temporarily unload one owned LaunchAgent for bounded maintenance."""
400
+ executable = shutil.which("launchctl")
401
+ if not executable:
402
+ raise OSError("launchctl unavailable")
403
+ completed = subprocess.run(
404
+ (executable, "bootout", f"gui/{os.getuid()}", str(plist)),
405
+ text=True,
406
+ capture_output=True,
407
+ check=False,
408
+ )
409
+ if completed.returncode:
410
+ raise OSError(completed.stderr.strip() or "launchd quiesce failed")
411
+ # A process signal is not enough: both observations must prove launchd
412
+ # no longer owns a runnable job, preventing KeepAlive replacement.
413
+ if self.inspect(label) or self.inspect(label):
414
+ raise OSError("LaunchAgent remained loaded after maintenance quiesce")
415
+
416
+ def resume(self, label: str, plist: Path) -> None:
417
+ """Reload a temporarily quiesced owned LaunchAgent and start it."""
418
+ executable = shutil.which("launchctl")
419
+ if not executable:
420
+ raise OSError("launchctl unavailable")
421
+ completed = subprocess.run(
422
+ (executable, "bootstrap", f"gui/{os.getuid()}", str(plist)),
423
+ text=True,
424
+ capture_output=True,
425
+ check=False,
426
+ )
427
+ if completed.returncode:
428
+ raise OSError(completed.stderr.strip() or "launchd resume failed")
429
+ self.restart(label)
430
+
431
+
432
+ class ICloudInboxProvider:
433
+ def status(self) -> ProviderStatus:
434
+ return ProviderStatus("icloud_inbox", "configured", True, "workspace path is resolved by the watcher")
435
+
436
+
437
+ class TailscaleProvider:
438
+ _TAILSCALE_NETWORK = IPv4Network("100.64.0.0/10")
439
+
440
+ def ipv4_address(self) -> str | None:
441
+ """Return only the local, routable Tailscale IPv4 address.
442
+
443
+ This is a read-only diagnostic query. It never changes Tailnet
444
+ configuration, ACLs, Funnel, or port-forwarding state.
445
+ """
446
+ executable = shutil.which("tailscale")
447
+ if not executable:
448
+ return None
449
+ observed = subprocess.run((executable, "ip", "-4"), text=True, capture_output=True, check=False)
450
+ if observed.returncode:
451
+ return None
452
+ for candidate in observed.stdout.splitlines():
453
+ try:
454
+ address = IPv4Address(candidate.strip())
455
+ except ValueError:
456
+ continue
457
+ if address in self._TAILSCALE_NETWORK:
458
+ return str(address)
459
+ return None
460
+
461
+ def status(self) -> ProviderStatus:
462
+ executable = shutil.which("tailscale")
463
+ if not executable:
464
+ return ProviderStatus("tailscale", "configured", False, "tailscale unavailable")
465
+ observed = subprocess.run((executable, "status", "--json"), text=True, capture_output=True, check=False)
466
+ return ProviderStatus("tailscale", "configured", observed.returncode == 0, "connected" if observed.returncode == 0 else "not connected")
467
+
468
+
469
+ def registry(root: Path) -> dict[str, ProviderStatus]:
470
+ """Return the deterministic current-provider registry."""
471
+ return {"runtime": CodexCliProvider().status(), "repository": GitHubProvider().status(root), "service_manager": LaunchdProvider().status(), "remote_submission": ICloudInboxProvider().status(), "private_remote_access": TailscaleProvider().status()}
@@ -0,0 +1,220 @@
1
+ """Deterministic local qualification for Engineering Platform capabilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from datetime import datetime, timezone
7
+ import json
8
+ from pathlib import Path
9
+ import time
10
+
11
+ from .platform_version import EngineeringPlatformManifest
12
+ from .resources import package_path
13
+ from .platform_api import PlatformConfiguration
14
+ from .providers import CodexCliProvider, GitProvider
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class QualificationScenario:
19
+ capability: str
20
+ expected_behavior: str
21
+
22
+
23
+ SCENARIOS = tuple(
24
+ QualificationScenario(
25
+ capability,
26
+ "Deterministic local contract passes; failures report evidence without changing lifecycle authority.",
27
+ )
28
+ for capability in (
29
+ "Repository Initialization",
30
+ "Checkpoint Resume",
31
+ "Implementation Lifecycle",
32
+ "Validation Loop",
33
+ "Repair Loop",
34
+ "Owner Authorization",
35
+ "Ready For Review",
36
+ "Automatic Merge",
37
+ "Repository Reconciliation",
38
+ "Finalization",
39
+ "Repository Cleanup",
40
+ "Engineering Memory",
41
+ "Progress Reporting",
42
+ "Engineering Reports",
43
+ "Capability-aware Reviewers",
44
+ "Diagnostics",
45
+ "BLOCKED Recovery",
46
+ "Failure Recovery",
47
+ "Long-running Transactions",
48
+ "Remote Status Model",
49
+ "Private Dashboard",
50
+ "Repository Handoff",
51
+ "Remote Engineering Readiness",
52
+ "Platform Identity",
53
+ "Workspace Identity",
54
+ "Provider Registry",
55
+ "Capability Registry",
56
+ "Public Platform API",
57
+ "Configuration Hierarchy",
58
+ "Configuration Migration",
59
+ "Provider Compatibility",
60
+ "Extraction Readiness Audit",
61
+ "Repository Bootstrap",
62
+ "Project Template",
63
+ "Workspace Provisioning",
64
+ "Genesis Lifecycle",
65
+ "Strict Inbox Sequencing",
66
+ "Local Engineering Evidence Storage",
67
+ "Component Logging and Read-only Advice",
68
+ )
69
+ )
70
+
71
+
72
+ def execute_qualification(
73
+ root: Path,
74
+ checks: dict[str, bool] | None = None,
75
+ *,
76
+ ep_repository_root: Path | None = None,
77
+ evidence_root: Path | None = None,
78
+ ) -> dict[str, object]:
79
+ """Execute all registered local scenarios and write immutable local evidence.
80
+
81
+ ``evidence_root`` is for read-only source checkouts such as the Golden
82
+ scenario. It changes only where the receipt is stored, never the
83
+ repository whose contracts are being checked.
84
+ """
85
+ started = time.monotonic()
86
+ manifest = EngineeringPlatformManifest.load(
87
+ package_path("ENGINEERING_PLATFORM_VERSION.json")
88
+ )
89
+ supplied = checks or {}
90
+ results = []
91
+ for scenario in SCENARIOS:
92
+ passed = supplied.get(
93
+ scenario.capability,
94
+ _default_check(root, scenario.capability, ep_repository_root=ep_repository_root),
95
+ )
96
+ results.append(
97
+ {
98
+ "capability": scenario.capability,
99
+ "status": "PASS" if passed else "FAIL",
100
+ "duration_ms": 0,
101
+ "diagnostic": None if passed else "Scenario contract failed.",
102
+ "evidence": scenario.expected_behavior,
103
+ }
104
+ )
105
+ passed = sum(item["status"] == "PASS" for item in results)
106
+ report = {
107
+ "engineering_platform_version": manifest.platform_version,
108
+ "repository_version": _repository_version(root),
109
+ "codex_cli_version": _codex_version(),
110
+ "qualification": "PASS" if passed == len(results) else "FAIL",
111
+ "executed_at": datetime.now(timezone.utc).isoformat(),
112
+ "duration_ms": round((time.monotonic() - started) * 1000),
113
+ "scenarios": results,
114
+ "coverage_percent": round(passed * 100 / len(results), 1),
115
+ "failures": len(results) - passed,
116
+ "blocked": 0,
117
+ }
118
+ _write_report(evidence_root or root, report)
119
+ return report
120
+
121
+
122
+ def dashboard(report: dict[str, object]) -> str:
123
+ scenarios = report["scenarios"]
124
+ return "\n".join(
125
+ (
126
+ "Engineering Platform Qualification",
127
+ f"Version: {report['engineering_platform_version']}",
128
+ f"Qualification: {report['qualification']}",
129
+ f"Scenarios: {sum(item['status'] == 'PASS' for item in scenarios)} / {len(scenarios)}",
130
+ f"Failures: {report['failures']}",
131
+ f"Blocked: {report['blocked']}",
132
+ f"Coverage: {report['coverage_percent']}%",
133
+ )
134
+ )
135
+
136
+
137
+ def latest_qualification(root: Path) -> dict[str, object] | None:
138
+ directory = root / ".engineering" / "qualification"
139
+ reports = sorted(directory.glob("qualification-*.json"))
140
+ if not reports:
141
+ return None
142
+ try:
143
+ raw = json.loads(reports[-1].read_text(encoding="utf-8"))
144
+ return raw if isinstance(raw, dict) else None
145
+ except (OSError, json.JSONDecodeError):
146
+ return None
147
+
148
+
149
+ def _default_check(
150
+ root: Path, capability: str, *, ep_repository_root: Path | None = None
151
+ ) -> bool:
152
+ ep_source = (
153
+ ep_repository_root / "src" / "engineering_platform"
154
+ if ep_repository_root is not None
155
+ else None
156
+ )
157
+
158
+ def source_file(name: str) -> bool:
159
+ return bool(ep_source and (ep_source / name).is_file())
160
+
161
+ def configuration_is_compatible() -> bool:
162
+ try:
163
+ return PlatformConfiguration.load(root).platform.version == EngineeringPlatformManifest.load(
164
+ package_path("ENGINEERING_PLATFORM_VERSION.json")
165
+ ).platform_version
166
+ except (OSError, ValueError):
167
+ return False
168
+ contracts = {
169
+ "Repository Initialization": bool(ep_repository_root and (ep_repository_root / "BOOTSTRAP.md").is_file()),
170
+ "Checkpoint Resume": source_file("agent_state.py"),
171
+ "Engineering Memory": source_file("execution_host.py"),
172
+ "Capability-aware Reviewers": source_file("capability_review.py"),
173
+ "Remote Status Model": source_file("status_model.py"),
174
+ "Private Dashboard": source_file("server.py") and source_file("console_presentation.py"),
175
+ "Repository Handoff": source_file("repository_handoff.py"),
176
+ "Remote Engineering Readiness": source_file("execution_readiness.py"),
177
+ "Platform Identity": configuration_is_compatible(),
178
+ "Workspace Identity": configuration_is_compatible(),
179
+ "Provider Registry": configuration_is_compatible(),
180
+ "Capability Registry": source_file("platform_api.py"),
181
+ "Public Platform API": source_file("platform_api.py"),
182
+ "Configuration Hierarchy": configuration_is_compatible(),
183
+ "Configuration Migration": configuration_is_compatible(),
184
+ "Provider Compatibility": configuration_is_compatible(),
185
+ "Extraction Readiness Audit": bool(
186
+ ep_repository_root
187
+ and (ep_repository_root / "scripts" / "engineering" / "audit_ep_extraction_baseline.py").is_file()
188
+ ),
189
+ "Repository Bootstrap": source_file("platform_bootstrap.py"),
190
+ "Project Template": bool(ep_source and (ep_source / "templates" / "workspace-config.json").is_file()),
191
+ "Workspace Provisioning": source_file("platform_bootstrap.py"),
192
+ "Genesis Lifecycle": source_file("execution_host.py"),
193
+ "Strict Inbox Sequencing": source_file("file_inbox.py"),
194
+ "Local Engineering Evidence Storage": source_file("file_inbox.py") and source_file("submission_intake.py"),
195
+ "Component Logging and Read-only Advice": source_file("component_logging.py") and source_file("codex_chat.py"),
196
+ }
197
+ return contracts.get(capability, source_file("execution_host.py"))
198
+
199
+
200
+ def _write_report(root: Path, report: dict[str, object]) -> None:
201
+ directory = root / ".engineering" / "qualification"
202
+ directory.mkdir(mode=0o700, parents=True, exist_ok=True)
203
+ stamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H-%M-%SZ")
204
+ (directory / f"qualification-{stamp}.json").write_text(
205
+ json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8"
206
+ )
207
+ (directory / f"qualification-{stamp}.md").write_text(dashboard(report) + "\n", encoding="utf-8")
208
+
209
+
210
+ def _repository_version(root: Path) -> str:
211
+ completed = GitProvider().execute(root, "git", "rev-parse", "HEAD")
212
+ return completed.stdout.strip() if completed.returncode == 0 else "unavailable"
213
+
214
+
215
+ def _codex_version() -> str:
216
+ try:
217
+ completed = CodexCliProvider().command("--version")
218
+ except OSError:
219
+ return "unavailable"
220
+ return completed.stdout.strip() if completed.returncode == 0 else "unavailable"