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,385 @@
1
+ """Idempotent repository bootstrap and workspace provisioning API."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import os
6
+ from pathlib import Path
7
+ import shutil
8
+ import sqlite3
9
+
10
+ from .platform_api import PlatformConfiguration, PlatformConfigurationError, shared_workspace_store
11
+
12
+
13
+ WORKSPACE_DIRECTORY = ".engineering"
14
+ LEGACY_WORKSPACE_DIRECTORY = ".djconnect"
15
+ _AUTO_IDENTIFIER_TABLES = frozenset({
16
+ "engineering_artifacts",
17
+ "engineering_component_logs",
18
+ "execution_lease_events",
19
+ "execution_lifecycle_events",
20
+ })
21
+
22
+
23
+ class WorkspaceMigrationBlockedError(RuntimeError):
24
+ """A one-time workspace migration found a component that is still live."""
25
+
26
+ def __init__(self, component: str | None = None) -> None:
27
+ self.component = component or "another component"
28
+ super().__init__("Engineering workspace migration requires running components to stop first.")
29
+
30
+
31
+ def _worktree_roots(root: Path) -> tuple[Path, ...]:
32
+ """Return accessible worktrees that share ``root``'s Git common directory."""
33
+ root = root.resolve()
34
+ shared = shared_workspace_store(root)
35
+ if shared == root / WORKSPACE_DIRECTORY:
36
+ return (root,)
37
+ common = shared.parent
38
+ roots = {common.parent.resolve(), root}
39
+ worktrees = common / "worktrees"
40
+ if worktrees.is_dir():
41
+ for entry in worktrees.iterdir():
42
+ marker = entry / "gitdir"
43
+ try:
44
+ worktree_git_marker = Path(marker.read_text(encoding="utf-8").strip())
45
+ except OSError:
46
+ continue
47
+ candidate = worktree_git_marker.parent.resolve()
48
+ if (candidate / ".git").exists():
49
+ roots.add(candidate)
50
+ return tuple(sorted(roots, key=lambda item: str(item)))
51
+
52
+
53
+ def _history_count(workspace: Path) -> int:
54
+ """Use the immutable history index only to choose an initial store seed."""
55
+ database = workspace / "engineering.db"
56
+ if not database.is_file():
57
+ return 0
58
+ try:
59
+ with sqlite3.connect(f"file:{database}?mode=ro", uri=True) as connection:
60
+ return int(connection.execute("SELECT COUNT(*) FROM prompt_execution_history").fetchone()[0])
61
+ except (sqlite3.DatabaseError, OSError):
62
+ return 0
63
+
64
+
65
+ def _database_tables(connection: sqlite3.Connection, schema: str) -> tuple[str, ...]:
66
+ return tuple(
67
+ row[0]
68
+ for row in connection.execute(
69
+ f"SELECT name FROM {schema}.sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
70
+ )
71
+ )
72
+
73
+
74
+ def _merge_databases(source: Path, destination: Path) -> None:
75
+ """Merge independently written local evidence without discarding run history.
76
+
77
+ Engineering records are append-only by identity. SQLite row ids are local
78
+ implementation details for log/event tables, so those are regenerated at
79
+ the destination while their durable content is retained. Mutable status
80
+ projections take the newest timestamp. Any incompatible schema fails
81
+ closed before either source evidence or the shared store is removed.
82
+ """
83
+ with sqlite3.connect(destination) as connection:
84
+ connection.execute("PRAGMA foreign_keys=OFF")
85
+ connection.execute("ATTACH DATABASE ? AS legacy", (str(source),))
86
+ in_transaction = False
87
+ try:
88
+ current_tables = _database_tables(connection, "main")
89
+ legacy_tables = _database_tables(connection, "legacy")
90
+ if current_tables != legacy_tables:
91
+ raise RuntimeError("Engineering workspace migration found incompatible database schemas.")
92
+ connection.execute("BEGIN IMMEDIATE")
93
+ in_transaction = True
94
+ for table in current_tables:
95
+ columns = [row[1] for row in connection.execute(f"PRAGMA main.table_info({table})")]
96
+ if table in _AUTO_IDENTIFIER_TABLES:
97
+ columns = [column for column in columns if column != "id"]
98
+ if not columns:
99
+ continue
100
+ column_list = ",".join(columns)
101
+ if table in {"engineering_status", "engineering_transactions"}:
102
+ key = "name" if table == "engineering_status" else "run_id"
103
+ for row in connection.execute(f"SELECT {column_list} FROM legacy.{table}"):
104
+ values = dict(zip(columns, row, strict=True))
105
+ current = connection.execute(
106
+ f"SELECT updated_at FROM main.{table} WHERE {key}=?", (values[key],)
107
+ ).fetchone()
108
+ if current is None:
109
+ placeholders = ",".join("?" for _ in columns)
110
+ connection.execute(
111
+ f"INSERT INTO main.{table}({column_list}) VALUES({placeholders})",
112
+ tuple(values[column] for column in columns),
113
+ )
114
+ elif values["updated_at"] > current[0]:
115
+ assignments = ["payload=?", "updated_at=?"]
116
+ parameters: list[object] = [values["payload"], values["updated_at"]]
117
+ if table == "engineering_transactions":
118
+ assignments.insert(1, "phase=?")
119
+ parameters.insert(1, values["phase"])
120
+ parameters.append(values[key])
121
+ connection.execute(
122
+ f"UPDATE main.{table} SET {','.join(assignments)} WHERE {key}=?", parameters
123
+ )
124
+ elif table == "daily_execution_statistics":
125
+ # This is a rebuildable read model. The next telemetry
126
+ # update writes its fresh aggregate; never overwrite a
127
+ # newer shared projection during migration.
128
+ connection.execute(
129
+ f"INSERT OR IGNORE INTO main.{table}({column_list}) SELECT {column_list} FROM legacy.{table}"
130
+ )
131
+ else:
132
+ connection.execute(
133
+ f"INSERT OR IGNORE INTO main.{table}({column_list}) SELECT {column_list} FROM legacy.{table}"
134
+ )
135
+ violations = connection.execute("PRAGMA foreign_key_check").fetchall()
136
+ if violations:
137
+ raise RuntimeError("Engineering workspace migration would violate datastore integrity.")
138
+ connection.execute("COMMIT")
139
+ in_transaction = False
140
+ except Exception:
141
+ if in_transaction:
142
+ connection.execute("ROLLBACK")
143
+ raise
144
+ finally:
145
+ connection.execute("DETACH DATABASE legacy")
146
+
147
+
148
+ def _merge_workspace(source: Path, destination: Path) -> None:
149
+ """Merge one worktree's local evidence into the shared workspace."""
150
+ for child in tuple(source.iterdir()):
151
+ target = destination / child.name
152
+ if child.name == "engineering.db" and target.is_file():
153
+ _merge_databases(child, target)
154
+ child.unlink()
155
+ elif not target.exists():
156
+ shutil.move(str(child), str(target))
157
+ elif child.is_dir() and target.is_dir():
158
+ _merge_workspace(child, target)
159
+ child.rmdir()
160
+ elif child.is_file() and target.is_file() and child.read_bytes() == target.read_bytes():
161
+ child.unlink()
162
+ else:
163
+ raise RuntimeError(f"Engineering workspace migration conflict: {child.name}")
164
+
165
+
166
+ def _link_workspace(worktree: Path, shared: Path) -> None:
167
+ """Expose the shared private store at the established worktree-local path."""
168
+ local = worktree / WORKSPACE_DIRECTORY
169
+ if local.is_symlink():
170
+ if local.resolve() != shared.resolve():
171
+ raise RuntimeError("Engineering workspace points to an unexpected shared store.")
172
+ return
173
+ if local.exists():
174
+ raise RuntimeError("Engineering workspace migration did not consume a local store.")
175
+ os.symlink(shared, local, target_is_directory=True)
176
+
177
+
178
+ def _discard_inactive_component_locks(workspace: Path) -> None:
179
+ """Discard only stale process locks before relocating their directory.
180
+
181
+ A flock is meaningful only in the filesystem currently held by its owner;
182
+ copying that file into a shared workspace would create a false lock. A
183
+ live owner therefore blocks the migration until the normal component
184
+ restart has stopped it. Stale lock files are explicitly non-evidence and
185
+ may be recreated by their owning component after the migration.
186
+ """
187
+ locks = workspace / "locks"
188
+ if not locks.exists():
189
+ return
190
+ if locks.is_symlink() or not locks.is_dir():
191
+ raise RuntimeError("Engineering workspace migration found an invalid component-lock directory.")
192
+ for path in locks.glob("*.lock"):
193
+ try:
194
+ payload = json.loads(path.read_text(encoding="utf-8"))
195
+ process_id = payload.get("pid") if isinstance(payload, dict) else None
196
+ if isinstance(process_id, int) and process_id > 0:
197
+ os.kill(process_id, 0)
198
+ component = payload.get("component") if isinstance(payload, dict) else None
199
+ raise WorkspaceMigrationBlockedError(component if isinstance(component, str) else None)
200
+ except ProcessLookupError:
201
+ continue
202
+ except PermissionError as error:
203
+ raise RuntimeError("Engineering workspace migration cannot verify a component lock owner.") from error
204
+ except (OSError, json.JSONDecodeError):
205
+ continue
206
+ shutil.rmtree(locks)
207
+
208
+
209
+ def migrate_worktree_workspace(root: Path) -> Path:
210
+ """Create one durable Engineering store for every worktree of a repository."""
211
+ root = root.resolve()
212
+ shared = shared_workspace_store(root)
213
+ if shared == root / WORKSPACE_DIRECTORY:
214
+ return shared
215
+ candidates = [worktree / WORKSPACE_DIRECTORY for worktree in _worktree_roots(root)]
216
+ local_directories = [path for path in candidates if path.exists() and not path.is_symlink()]
217
+ if not shared.exists() and local_directories:
218
+ seed = max(local_directories, key=lambda path: (_history_count(path), str(path)))
219
+ shared.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
220
+ shutil.move(str(seed), str(shared))
221
+ shared.mkdir(mode=0o700, parents=True, exist_ok=True)
222
+ if local_directories:
223
+ _discard_inactive_component_locks(shared)
224
+ for local in local_directories:
225
+ if local.exists():
226
+ _discard_inactive_component_locks(local)
227
+ for local in local_directories:
228
+ if not local.exists():
229
+ continue
230
+ _merge_workspace(local, shared)
231
+ local.rmdir()
232
+ for worktree in _worktree_roots(root):
233
+ _link_workspace(worktree, shared)
234
+ return shared.resolve()
235
+
236
+
237
+ def _validate_legacy_merge(source: Path, destination: Path) -> None:
238
+ """Fail closed before moving evidence into an occupied canonical workspace."""
239
+ if source.is_symlink() or destination.is_symlink():
240
+ raise RuntimeError("Engineering workspace migration refuses symbolic links.")
241
+ if source.is_dir() != destination.is_dir():
242
+ raise RuntimeError(f"Engineering workspace migration conflict: {source.name}")
243
+ if source.is_dir():
244
+ for child in source.iterdir():
245
+ target = destination / child.name
246
+ if target.exists():
247
+ _validate_legacy_merge(child, target)
248
+ return
249
+ if source.read_bytes() != destination.read_bytes():
250
+ raise RuntimeError(f"Engineering workspace migration conflict: {source.name}")
251
+
252
+
253
+ def _merge_legacy_workspace(source: Path, destination: Path) -> None:
254
+ """Move prevalidated legacy evidence, dropping only byte-identical duplicates."""
255
+ for child in source.iterdir():
256
+ target = destination / child.name
257
+ if not target.exists():
258
+ shutil.move(str(child), str(target))
259
+ elif child.is_dir():
260
+ _merge_legacy_workspace(child, target)
261
+ child.rmdir()
262
+ else:
263
+ child.unlink()
264
+
265
+
266
+ def _move_legacy_logs(source: Path, workspace: Path) -> None:
267
+ """Preserve a conflicting historic log tail outside the live log files."""
268
+ destination = workspace / "logs" / "legacy"
269
+ if destination.exists():
270
+ _validate_legacy_merge(source, destination)
271
+ _merge_legacy_workspace(source, destination)
272
+ source.rmdir()
273
+ else:
274
+ destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
275
+ shutil.move(str(source), str(destination))
276
+
277
+
278
+ def _archive_legacy_evidence(source: Path, workspace: Path) -> None:
279
+ """Keep a conflicting historic evidence category without replacing live data."""
280
+ destination = workspace / "legacy" / source.name
281
+ if destination.exists():
282
+ _validate_legacy_merge(source, destination)
283
+ _merge_legacy_workspace(source, destination)
284
+ source.rmdir()
285
+ else:
286
+ destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
287
+ shutil.move(str(source), str(destination))
288
+
289
+
290
+ def migrate_legacy_workspace(root: Path) -> Path:
291
+ """Move `.djconnect` evidence to the sole canonical `.engineering` location."""
292
+ root = root.resolve()
293
+ workspace = migrate_worktree_workspace(root)
294
+ legacy = root / LEGACY_WORKSPACE_DIRECTORY
295
+ if not legacy.exists():
296
+ return workspace
297
+ if legacy.is_symlink() or not legacy.is_dir():
298
+ raise RuntimeError("Engineering workspace migration requires a local directory.")
299
+ workspace.mkdir(mode=0o700, parents=True, exist_ok=True)
300
+ archived_categories = {"logs", "qualification"}
301
+ for source in legacy.iterdir():
302
+ if source.name in archived_categories and (workspace / source.name).exists():
303
+ continue
304
+ target = workspace / source.name
305
+ if target.exists():
306
+ _validate_legacy_merge(source, target)
307
+ legacy_logs = legacy / "logs"
308
+ if legacy_logs.exists() and (workspace / "logs").exists():
309
+ _move_legacy_logs(legacy_logs, workspace)
310
+ legacy_qualification = legacy / "qualification"
311
+ if legacy_qualification.exists() and (workspace / "qualification").exists():
312
+ _archive_legacy_evidence(legacy_qualification, workspace)
313
+ _merge_legacy_workspace(legacy, workspace)
314
+ legacy.rmdir()
315
+ return workspace
316
+
317
+
318
+ def provision_workspace(root: Path) -> dict[str, Path]:
319
+ """Provision only platform-owned local directories; safe to repeat."""
320
+ workspace = migrate_legacy_workspace(root)
321
+ return _provision_workspace_paths(root, workspace)
322
+
323
+
324
+ def runtime_workspace(root: Path) -> Path:
325
+ """Return the already-valid shared store without starting a migration.
326
+
327
+ A long-running dashboard is allowed to coexist with a watcher or runner.
328
+ When this worktree already points to the canonical shared store, a normal
329
+ component start must not inspect or merge dormant legacy stores belonging
330
+ to another worktree. Such a merge remains an explicit, fail-closed
331
+ migration through :func:`migrate_legacy_workspace`.
332
+ """
333
+ root = root.resolve()
334
+ shared = shared_workspace_store(root)
335
+ local = root / WORKSPACE_DIRECTORY
336
+ legacy = root / LEGACY_WORKSPACE_DIRECTORY
337
+ if (
338
+ shared != local
339
+ and local.is_symlink()
340
+ and local.resolve() == shared.resolve()
341
+ and shared.is_dir()
342
+ and not legacy.exists()
343
+ ):
344
+ return shared.resolve()
345
+ return migrate_legacy_workspace(root)
346
+
347
+
348
+ def _provision_workspace_paths(root: Path, workspace: Path) -> dict[str, Path]:
349
+ """Create the repeatable directories for an already-selected workspace."""
350
+ PlatformConfiguration.load(root)
351
+ paths = {"workspace": workspace, "reports": workspace / "reports", "status": workspace / "status", "diagnostics": workspace / "logs", "inbox_processing": workspace / "inbox-processing"}
352
+ # A CENTRAL lifecycle must not even create the historical checkpoint
353
+ # directory. Checkout directories retained here are physical temporary
354
+ # execution concerns, not operational truth.
355
+ if not os.environ.get("EP_CENTRAL_OPERATIONAL_DATABASE"):
356
+ paths["runs"] = workspace / "engineering-runs"
357
+ for path in paths.values():
358
+ path.mkdir(mode=0o700, parents=True, exist_ok=True)
359
+ return paths
360
+
361
+
362
+ def provision_runtime_workspace(root: Path) -> dict[str, Path]:
363
+ """Provision a watcher, runner, or dashboard without implicit migration."""
364
+ return _provision_workspace_paths(root, runtime_workspace(root))
365
+
366
+
367
+ def validate_repository(root: Path) -> PlatformConfiguration:
368
+ """Fail closed unless this repository is an explicit platform consumer."""
369
+ if not (root / "BOOTSTRAP.md").is_file() or not (root / ".git").exists():
370
+ raise PlatformConfigurationError("Repository bootstrap compatibility failed.")
371
+ return PlatformConfiguration.load(root)
372
+
373
+
374
+ def render_template(destination: Path, replacements: dict[str, str]) -> Path:
375
+ """Create a deterministic config template without overwriting consumer data."""
376
+ if destination.exists():
377
+ return destination
378
+ template = Path(__file__).with_name("templates") / "workspace-config.json"
379
+ content = template.read_text(encoding="utf-8")
380
+ for key, value in sorted(replacements.items()):
381
+ content = content.replace(key, value)
382
+ json.loads(content)
383
+ destination.parent.mkdir(parents=True, exist_ok=True)
384
+ destination.write_text(content + "\n", encoding="utf-8")
385
+ return destination
@@ -0,0 +1,65 @@
1
+ """The sole installed-Server inventory of supported Platform Components."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class PlatformComponent:
10
+ """Stable product, health and logging identity for one Server component."""
11
+
12
+ id: str
13
+ name_key: str
14
+ kind: str
15
+ group: str
16
+ active_status: str
17
+ inactive_status: str
18
+ detail_code: str
19
+ startup_event: str
20
+ restart_supported: bool = False
21
+ critical: bool = False
22
+ lifecycle_label: str | None = None
23
+
24
+
25
+ PLATFORM_COMPONENTS = (
26
+ PlatformComponent("ep_server", "component.ep_server", "DAEMON", "platform", "EP_SERVER_ACTIVE", "EP_SERVER_UNAVAILABLE", "EP_SERVER_ENDPOINT", "ep_server_started", False, True),
27
+ PlatformComponent("platform_database", "component.platform_database", "STORAGE", "platform", "PLATFORM_DATABASE_HEALTHY", "PLATFORM_DATABASE_UNAVAILABLE", "PLATFORM_DATABASE_STORAGE", "central_log_store_ready", False, True),
28
+ PlatformComponent("lifecycle_worker", "component.lifecycle_worker", "IN_PROCESS_COMPONENT", "platform", "LIFECYCLE_WORKER_ACTIVE", "LIFECYCLE_WORKER_UNAVAILABLE", "LIFECYCLE_WORKER_SERVER_HOSTED", "lifecycle_worker_started", False, True),
29
+ PlatformComponent("operations_console", "component.operations_console", "UI_SERVICE", "access", "OPERATIONS_CONSOLE_AVAILABLE", "OPERATIONS_CONSOLE_UNAVAILABLE", "OPERATIONS_CONSOLE_SERVER_NATIVE", "operations_console_available"),
30
+ PlatformComponent(
31
+ "dashboard_relay", "component.dashboard_relay", "UI_SERVICE", "access",
32
+ "DASHBOARD_RELAY_ACTIVE", "DASHBOARD_RELAY_UNAVAILABLE",
33
+ "DASHBOARD_RELAY_SERVER_NATIVE", "dashboard_relay_available",
34
+ restart_supported=True,
35
+ lifecycle_label="com.engineeringplatform.dashboard-relay",
36
+ ),
37
+ PlatformComponent("http_ingress", "transport.http", "TRANSPORT", "ingress", "HTTP_INGRESS_HEALTHY", "HTTP_INGRESS_DOWN", "CENTRAL_LISTENER_ENDPOINT", "http_ingress_available", False, True),
38
+ PlatformComponent("cli_ingress", "transport.cli", "TRANSPORT", "ingress", "CLI_INGRESS_AVAILABLE", "CLI_INGRESS_DEGRADED", "CANONICAL_SUBMISSION_COMPATIBILITY", "cli_ingress_available", False, True),
39
+ PlatformComponent("file_inbox_ingress", "transport.file", "TRANSPORT", "ingress", "FILE_INGRESS_RUNNING", "FILE_INGRESS_STOPPED", "FILE_INBOX_HEARTBEAT", "file_inbox_service_started"),
40
+ PlatformComponent("dependabot_producer", "component.dependabot_producer", "TRANSPORT", "ingress", "DEPENDABOT_READY", "DEPENDABOT_DEGRADED", "DEPENDABOT_HEARTBEAT", "dependabot_producer_started"),
41
+ )
42
+ PLATFORM_COMPONENT_BY_ID = {component.id: component for component in PLATFORM_COMPONENTS}
43
+ PLATFORM_COMPONENT_IDS = frozenset(PLATFORM_COMPONENT_BY_ID)
44
+ # The installed product has exactly these submission boundaries. The Local
45
+ # Consumer API is historical compatibility code, not a fourth ingress.
46
+ SUPPORTED_SUBMISSION_INGRESSES = ("HTTP_JSON", "INSTALLED_CLI", "FILE_INBOX")
47
+ SUPPORTED_SUBMISSION_INGRESS_COUNT = len(SUPPORTED_SUBMISSION_INGRESSES)
48
+ # Route consumers import this value instead of duplicating a literal inventory.
49
+ PLATFORM_COMPONENT_ROUTE_PATTERN = "(?:" + "|".join(component.id for component in PLATFORM_COMPONENTS) + ")"
50
+
51
+ # These are retired *input* identifiers, not components and never aliases for
52
+ # a supported component. Keeping the bounded denial list beside the canonical
53
+ # model prevents a compatibility parser or route from quietly turning one back
54
+ # into lifecycle, logging or configuration authority.
55
+ RETIRED_COMPONENT_ALIASES = frozenset({
56
+ "dashboard",
57
+ "dashboard_service",
58
+ "dashboard_watcher",
59
+ "finder",
60
+ "inbox",
61
+ "inbox_service",
62
+ "inbox_watcher",
63
+ "watcher",
64
+ })
65
+ RETIRED_COMPONENT_ALIAS_ROUTE_PATTERN = "(?:" + "|".join(sorted(RETIRED_COMPONENT_ALIASES)) + ")"
@@ -0,0 +1,171 @@
1
+ """Deterministic Engineering Platform manifest and compatibility validation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import json
7
+ from pathlib import Path
8
+ import re
9
+
10
+ from .storage import ENGINEERING_STORAGE_SCHEMA_VERSION
11
+
12
+
13
+ SEMVER = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$")
14
+ CONTRACT = re.compile(r"^(\d{4})\.(0[1-9]|1[0-2])$")
15
+ CURRENT_PLATFORM_VERSION = "2.2.0"
16
+ MANIFEST_FIELDS = frozenset(
17
+ {
18
+ "platform_version",
19
+ "runner_version",
20
+ "bootstrap_contract",
21
+ "checkpoint_format",
22
+ "memory_format",
23
+ "report_format",
24
+ "minimum_codex_cli",
25
+ "watcher_version",
26
+ "inbox_protocol",
27
+ "dashboard_version",
28
+ "handoff_protocol",
29
+ "status_model",
30
+ "storage_schema",
31
+ }
32
+ )
33
+
34
+
35
+ class EngineeringPlatformCompatibilityError(ValueError):
36
+ """Raised when a repository's declared engineering contract is unsupported."""
37
+
38
+
39
+ def _semver(value: str, field: str) -> tuple[int, int, int]:
40
+ match = SEMVER.fullmatch(value) if isinstance(value, str) else None
41
+ if not match:
42
+ raise EngineeringPlatformCompatibilityError(
43
+ f"Engineering Platform manifest field {field} must use MAJOR.MINOR.PATCH."
44
+ )
45
+ return tuple(int(part) for part in match.groups())
46
+
47
+
48
+ def _contract(value: str, field: str) -> tuple[int, int]:
49
+ match = CONTRACT.fullmatch(value) if isinstance(value, str) else None
50
+ if not match:
51
+ raise EngineeringPlatformCompatibilityError(
52
+ f"Engineering Platform manifest field {field} must use YYYY.MM."
53
+ )
54
+ return tuple(int(part) for part in match.groups())
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class EngineeringPlatformManifest:
59
+ platform_version: str
60
+ runner_version: str
61
+ bootstrap_contract: str
62
+ checkpoint_format: int
63
+ memory_format: int
64
+ report_format: int
65
+ minimum_codex_cli: str
66
+ watcher_version: str
67
+ inbox_protocol: int
68
+ dashboard_version: str
69
+ handoff_protocol: int
70
+ status_model: int
71
+ storage_schema: int
72
+
73
+ @classmethod
74
+ def load(cls, path: Path) -> "EngineeringPlatformManifest":
75
+ try:
76
+ raw = json.loads(path.read_text(encoding="utf-8"))
77
+ except (OSError, json.JSONDecodeError) as error:
78
+ raise EngineeringPlatformCompatibilityError(
79
+ "Engineering Platform manifest cannot be read."
80
+ ) from error
81
+ if not isinstance(raw, dict) or set(raw) != MANIFEST_FIELDS:
82
+ raise EngineeringPlatformCompatibilityError(
83
+ "Engineering Platform manifest fields are incompatible."
84
+ )
85
+ manifest = cls(**raw)
86
+ _semver(manifest.platform_version, "platform_version")
87
+ _semver(manifest.runner_version, "runner_version")
88
+ _semver(manifest.minimum_codex_cli, "minimum_codex_cli")
89
+ _semver(manifest.watcher_version, "watcher_version")
90
+ _semver(manifest.dashboard_version, "dashboard_version")
91
+ _contract(manifest.bootstrap_contract, "bootstrap_contract")
92
+ for field in (
93
+ "checkpoint_format",
94
+ "memory_format",
95
+ "report_format",
96
+ "inbox_protocol",
97
+ "handoff_protocol",
98
+ "status_model",
99
+ "storage_schema",
100
+ ):
101
+ if not isinstance(getattr(manifest, field), int) or getattr(manifest, field) < 1:
102
+ raise EngineeringPlatformCompatibilityError(
103
+ f"Engineering Platform manifest field {field} must be a positive integer."
104
+ )
105
+ return manifest
106
+
107
+
108
+ @dataclass(frozen=True)
109
+ class RunnerCompatibility:
110
+ platform_version: str = CURRENT_PLATFORM_VERSION
111
+ runner_version: str = CURRENT_PLATFORM_VERSION
112
+ bootstrap_contract: str = "2026.12"
113
+ checkpoint_formats: frozenset[int] = frozenset({1})
114
+ memory_formats: frozenset[int] = frozenset({1, 2})
115
+ report_formats: frozenset[int] = frozenset({1, 2})
116
+ # New runners retain compatibility with prior local stores while accepting
117
+ # the current telemetry-capable schema.
118
+ storage_schemas: frozenset[int] = frozenset(
119
+ range(1, ENGINEERING_STORAGE_SCHEMA_VERSION + 1)
120
+ )
121
+
122
+
123
+ def validate_compatibility(
124
+ manifest: EngineeringPlatformManifest, runner: RunnerCompatibility, detected_codex_cli: str
125
+ ) -> None:
126
+ """Fail closed unless this runner explicitly supports every repository contract."""
127
+ required_platform = _semver(manifest.platform_version, "platform_version")
128
+ actual_platform = _semver(runner.platform_version, "runner platform_version")
129
+ required_runner = _semver(manifest.runner_version, "runner_version")
130
+ actual_runner = _semver(runner.runner_version, "runner_version")
131
+ if required_platform[0] != actual_platform[0]:
132
+ raise EngineeringPlatformCompatibilityError(
133
+ f"Engineering Platform mismatch\nRepository requires: {manifest.platform_version}\nRunner: {runner.platform_version}\nBLOCKED\nEngineering Platform upgrade required."
134
+ )
135
+ if actual_runner < required_runner:
136
+ raise EngineeringPlatformCompatibilityError(
137
+ f"Runner version mismatch\nRepository requires: {manifest.runner_version}\nRunner: {runner.runner_version}\nBLOCKED\nRunner upgrade required."
138
+ )
139
+ required_contract = _contract(manifest.bootstrap_contract, "bootstrap_contract")
140
+ actual_contract = _contract(runner.bootstrap_contract, "runner bootstrap_contract")
141
+ if actual_contract < required_contract:
142
+ raise EngineeringPlatformCompatibilityError(
143
+ f"Bootstrap contract mismatch\nRepository requires: {manifest.bootstrap_contract}\nRunner: {runner.bootstrap_contract}\nBLOCKED\nEngineering Platform upgrade required."
144
+ )
145
+ for label, required, supported in (
146
+ ("Checkpoint format", manifest.checkpoint_format, runner.checkpoint_formats),
147
+ ("Engineering Memory format", manifest.memory_format, runner.memory_formats),
148
+ ("Report format", manifest.report_format, runner.report_formats),
149
+ ("Engineering storage schema", manifest.storage_schema, runner.storage_schemas),
150
+ ):
151
+ if required not in supported:
152
+ detected = ", ".join(str(value) for value in sorted(supported)) or "none"
153
+ raise EngineeringPlatformCompatibilityError(
154
+ f"{label} mismatch\nRepository requires: {required}\nRunner supports: {detected}\nBLOCKED\nEngineering Platform upgrade required."
155
+ )
156
+ required_cli = _semver(manifest.minimum_codex_cli, "minimum_codex_cli")
157
+ detected_cli = _semver(detected_codex_cli, "detected Codex CLI version")
158
+ if detected_cli < required_cli:
159
+ raise EngineeringPlatformCompatibilityError(
160
+ f"Codex CLI version mismatch\nRepository requires: {manifest.minimum_codex_cli}\nDetected: {detected_codex_cli}\nBLOCKED\nCodex CLI upgrade required."
161
+ )
162
+
163
+
164
+ def detected_codex_cli_version(output: str) -> str:
165
+ """Extract a stable semantic version from the local CLI version output."""
166
+ match = re.search(r"\b(\d+\.\d+\.\d+)\b", output)
167
+ if not match:
168
+ raise EngineeringPlatformCompatibilityError(
169
+ "Detected Codex CLI version is invalid. Run `codex --version` and install a supported release."
170
+ )
171
+ return match.group(1)