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,509 @@
1
+ """Append-only Managed-autonomy evidence and fail-closed qualification read model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timezone
6
+ import hashlib
7
+ import json
8
+ from pathlib import Path
9
+ import sqlite3
10
+ from typing import Iterable
11
+
12
+ from .storage import (
13
+ EngineeringStorageError,
14
+ load_run_lineage,
15
+ load_run_qualification_snapshot,
16
+ load_validation_context,
17
+ open_storage,
18
+ record_run_qualification_snapshot,
19
+ )
20
+
21
+ AUTHORITIES = frozenset(
22
+ {
23
+ "AUTONOMOUS_EP_ACTION",
24
+ "EXPECTED_OPERATOR_GATE",
25
+ "EXTERNAL_PLATFORM_EVENT",
26
+ "UNPLANNED_MANUAL_INTERVENTION",
27
+ "UNKNOWN_AUTHORITY",
28
+ }
29
+ )
30
+ GATE_TYPES = frozenset({"IMPLEMENTATION_MERGE_APPROVAL", "FINALIZATION_MERGE_APPROVAL"})
31
+ GATE_STATUSES = frozenset({"NOT_REQUIRED", "WAITING", "SATISFIED", "UNAVAILABLE"})
32
+ VALIDATION_STATES = frozenset(
33
+ {"PASS", "FAIL", "NOT_EXECUTED", "NOT_APPLICABLE", "UNAVAILABLE", "WAITING"}
34
+ )
35
+ PR_CHECK_STATES = frozenset({"PASS", "FAIL", "WAITING", "UNAVAILABLE"})
36
+ PR_ROLES = frozenset({"IMPLEMENTATION", "FINALIZATION"})
37
+
38
+
39
+ def _connection(root: Path, central_database: Path | None) -> sqlite3.Connection:
40
+ """Open the explicitly supplied CENTRAL authority when one is composed.
41
+
42
+ ``root`` remains only the physical execution context for compatibility
43
+ callers. The standalone runner always supplies ``central_database``.
44
+ """
45
+ if central_database is None:
46
+ return open_storage(root)
47
+ database = central_database.resolve()
48
+ if database.name != "engineering.db":
49
+ raise EngineeringStorageError("CENTRAL_MANAGED_AUTONOMY_DATABASE_INVALID")
50
+ return sqlite3.connect(database, isolation_level=None)
51
+
52
+
53
+ def _now() -> str:
54
+ return datetime.now(timezone.utc).isoformat()
55
+
56
+
57
+ def _safe(value: object, limit: int = 160) -> str:
58
+ if not isinstance(value, str) or not value or len(value) > limit or "\n" in value:
59
+ raise EngineeringStorageError("Managed autonomy evidence value is invalid.")
60
+ return value
61
+
62
+
63
+ def append_action(
64
+ root,
65
+ *,
66
+ run_id: str,
67
+ action: str,
68
+ authority: str,
69
+ actor: str = "execution_host",
70
+ evidence_ref: str = "runtime",
71
+ observed_at: str | None = None,
72
+ central_database: Path | None = None,
73
+ ) -> None:
74
+ """Persist identifier-only evidence; raw prompts and tool output are rejected by shape."""
75
+ if authority not in AUTHORITIES:
76
+ raise EngineeringStorageError("Managed autonomy action authority is invalid.")
77
+ connection = _connection(Path(root), central_database)
78
+ try:
79
+ connection.execute(
80
+ "INSERT INTO managed_autonomy_actions(run_id,action,authority,actor,evidence_ref,observed_at) VALUES(?,?,?,?,?,?)",
81
+ (
82
+ _safe(run_id),
83
+ _safe(action),
84
+ authority,
85
+ _safe(actor),
86
+ _safe(evidence_ref),
87
+ observed_at or _now(),
88
+ ),
89
+ )
90
+ finally:
91
+ connection.close()
92
+
93
+
94
+ def record_gate(
95
+ root,
96
+ *,
97
+ run_id: str,
98
+ gate_type: str,
99
+ status: str,
100
+ related_pr: int | None,
101
+ phase: str,
102
+ resolution_actor: str | None = None,
103
+ requested_at: str | None = None,
104
+ resolved_at: str | None = None,
105
+ central_database: Path | None = None,
106
+ ) -> None:
107
+ if (
108
+ gate_type not in GATE_TYPES
109
+ or status not in GATE_STATUSES
110
+ or (
111
+ related_pr is not None
112
+ and (isinstance(related_pr, bool) or not isinstance(related_pr, int) or related_pr < 1)
113
+ )
114
+ ):
115
+ raise EngineeringStorageError("Managed governance gate is invalid.")
116
+ connection = _connection(Path(root), central_database)
117
+ try:
118
+ connection.execute(
119
+ "INSERT INTO managed_governance_gates(run_id,gate_type,gate_authority,status,requested_at,resolved_at,resolution_actor,related_pr,phase) VALUES(?,?,?,?,?,?,?,?,?) ON CONFLICT(run_id,gate_type) DO UPDATE SET status=excluded.status,resolved_at=excluded.resolved_at,resolution_actor=excluded.resolution_actor,related_pr=excluded.related_pr,phase=excluded.phase",
120
+ (
121
+ _safe(run_id),
122
+ gate_type,
123
+ "OPERATOR",
124
+ status,
125
+ requested_at or _now(),
126
+ resolved_at,
127
+ resolution_actor,
128
+ related_pr,
129
+ _safe(phase),
130
+ ),
131
+ )
132
+ finally:
133
+ connection.close()
134
+
135
+
136
+ def append_validation_observation(
137
+ root,
138
+ *,
139
+ run_id: str,
140
+ control: str,
141
+ state: str,
142
+ required: bool,
143
+ currentness: int = 0,
144
+ observed_at: str | None = None,
145
+ central_database: Path | None = None,
146
+ ) -> None:
147
+ if state not in VALIDATION_STATES or currentness < 0:
148
+ raise EngineeringStorageError("Managed validation observation is invalid.")
149
+ connection = _connection(Path(root), central_database)
150
+ try:
151
+ connection.execute(
152
+ "INSERT INTO managed_validation_observations(run_id,control,state,required,currentness,observed_at) VALUES(?,?,?,?,?,?)",
153
+ (
154
+ _safe(run_id),
155
+ _safe(control),
156
+ state,
157
+ int(required),
158
+ currentness,
159
+ observed_at or _now(),
160
+ ),
161
+ )
162
+ finally:
163
+ connection.close()
164
+
165
+
166
+ def append_pr_check_observation(
167
+ root,
168
+ *,
169
+ run_id: str,
170
+ pr_number: int,
171
+ pr_role: str,
172
+ pr_state: str,
173
+ merge_commit: str | None,
174
+ required_checks_state: str,
175
+ evidence_ref: str,
176
+ currentness: int = 0,
177
+ observed_at: str | None = None,
178
+ central_database: Path | None = None,
179
+ ) -> None:
180
+ """Append a bounded GitHub required-check observation without changing lifecycle."""
181
+ if (
182
+ pr_role not in PR_ROLES
183
+ or required_checks_state not in PR_CHECK_STATES
184
+ or isinstance(pr_number, bool)
185
+ or not isinstance(pr_number, int)
186
+ or pr_number < 1
187
+ or currentness < 0
188
+ or (merge_commit is not None and (not isinstance(merge_commit, str) or len(merge_commit) != 40))
189
+ ):
190
+ raise EngineeringStorageError("Managed PR check observation is invalid.")
191
+ connection = _connection(Path(root), central_database)
192
+ try:
193
+ connection.execute(
194
+ "INSERT INTO managed_pr_check_observations(run_id,pr_number,pr_role,pr_state,merge_state,merge_commit,required_checks_state,evidence_ref,observed_at,currentness) VALUES(?,?,?,?,?,?,?,?,?,?)",
195
+ (
196
+ _safe(run_id), pr_number, pr_role, _safe(pr_state),
197
+ "MERGED" if pr_state == "MERGED" else "NOT_MERGED", merge_commit,
198
+ required_checks_state, _safe(evidence_ref), observed_at or _now(), currentness,
199
+ ),
200
+ )
201
+ finally:
202
+ connection.close()
203
+
204
+
205
+ def _current(rows: Iterable[tuple[object, ...]]) -> tuple[dict[str, str], bool]:
206
+ grouped: dict[str, list[tuple[str, int]]] = {}
207
+ for control, state, _required, currentness, _at in rows:
208
+ grouped.setdefault(str(control), []).append((str(state), int(currentness)))
209
+ result, conflict = {}, False
210
+ for control, values in grouped.items():
211
+ latest = max(item[1] for item in values)
212
+ states = {item[0] for item in values if item[1] == latest}
213
+ conflict |= len(states) != 1
214
+ result[control] = next(iter(states)) if len(states) == 1 else "UNAVAILABLE"
215
+ return result, conflict
216
+
217
+
218
+ def _current_pr_checks(rows: Iterable[tuple[object, ...]]) -> tuple[dict[str, dict[str, object]], bool]:
219
+ """Keep historical observations append-only while selecting current terminal evidence."""
220
+ grouped: dict[str, list[tuple[object, ...]]] = {}
221
+ for row in rows:
222
+ grouped.setdefault(str(row[2]), []).append(row)
223
+ result: dict[str, dict[str, object]] = {}
224
+ conflict = False
225
+ for role, values in grouped.items():
226
+ latest = max((int(row[9]), int(row[0])) for row in values)
227
+ current = [row for row in values if (int(row[9]), int(row[0])) == latest]
228
+ signatures = {(row[3], row[4], row[5], row[6], row[7]) for row in current}
229
+ if len(signatures) != 1:
230
+ conflict = True
231
+ result[role] = {"required_checks_state": "UNAVAILABLE", "conflict": True}
232
+ continue
233
+ row = current[-1]
234
+ result[role] = {
235
+ "pr_number": row[1], "pr_state": row[3], "merge_state": row[4],
236
+ "merge_commit": row[5], "required_checks_state": row[6],
237
+ "evidence_ref": row[7], "observed_at": row[8], "conflict": False,
238
+ "historical_observation_count": len(values) - len(current),
239
+ }
240
+ return result, conflict
241
+
242
+
243
+ def terminal_snapshot(
244
+ root,
245
+ *,
246
+ run_id: str,
247
+ execution_outcome: str,
248
+ implementation_pr: int | None,
249
+ finalization_pr: int | None,
250
+ repository_state: str,
251
+ workspace_state: str,
252
+ main_origin_sync: str,
253
+ worktree_state: str,
254
+ active_blocker: str,
255
+ recovery_required: str,
256
+ retry_parent: str | None = None,
257
+ resume_parent: str | None = None,
258
+ submission_id: str | None = None,
259
+ lineage_available: bool = False,
260
+ reviewer_records: tuple[dict[str, object], ...] = (),
261
+ execution_mode: str = "MANAGED",
262
+ action_intent: str = "MUTATING_DELIVERY",
263
+ persist: bool = False,
264
+ ) -> dict[str, object]:
265
+ if persist:
266
+ persisted = load_run_qualification_snapshot(root, run_id)
267
+ if persisted is not None:
268
+ return persisted
269
+ connection = open_storage(root)
270
+ try:
271
+ actions = connection.execute(
272
+ "SELECT action,authority FROM managed_autonomy_actions WHERE run_id=? ORDER BY id",
273
+ (run_id,),
274
+ ).fetchall()
275
+ gates = connection.execute(
276
+ "SELECT gate_type,status,related_pr FROM managed_governance_gates WHERE run_id=?",
277
+ (run_id,),
278
+ ).fetchall()
279
+ rows = connection.execute(
280
+ "SELECT control,state,required,currentness,observed_at FROM managed_validation_observations WHERE run_id=? ORDER BY id",
281
+ (run_id,),
282
+ ).fetchall()
283
+ pr_rows = connection.execute(
284
+ "SELECT id,pr_number,pr_role,pr_state,merge_state,merge_commit,required_checks_state,evidence_ref,observed_at,currentness FROM managed_pr_check_observations WHERE run_id=? ORDER BY id",
285
+ (run_id,),
286
+ ).fetchall()
287
+ finally:
288
+ connection.close()
289
+ validation, conflict = _current(rows)
290
+ pr_checks, pr_check_conflict = _current_pr_checks(pr_rows)
291
+ try:
292
+ persisted_lineage = load_run_lineage(root, run_id)
293
+ validation_context = load_validation_context(root, run_id)
294
+ except EngineeringStorageError:
295
+ persisted_lineage = validation_context = None
296
+ if persisted_lineage is not None:
297
+ lineage_available = True
298
+ submission_id = str(persisted_lineage["submission_id"])
299
+ retry_parent = persisted_lineage["retry_parent"] if isinstance(persisted_lineage["retry_parent"], str) else None
300
+ resume_parent = persisted_lineage["resume_parent"] if isinstance(persisted_lineage["resume_parent"], str) else None
301
+ fresh = "YES" if persisted_lineage["fresh_submission"] else "NO"
302
+ else:
303
+ fresh = "YES" if lineage_available and retry_parent is None and resume_parent is None else "NO" if lineage_available else "UNAVAILABLE"
304
+ required = {str(row[0]) for row in rows if int(row[2])}
305
+ required_state = "UNRESOLVED"
306
+ profile_projection: dict[str, object] = {}
307
+ if validation_context is not None:
308
+ required = set(validation_context["required_validation_controls"])
309
+ controls = validation_context["controls"]
310
+ results = [controls.get(control, {}).get("result") for control in required]
311
+ required_state = "FAIL" if any(result == "FAIL" for result in results) else "PASS" if results and all(result == "PASS" for result in results) else "UNRESOLVED"
312
+ profile_projection = {key: validation_context[key] for key in (
313
+ "selected_validation_tier", "validation_profile_version", "profile_reference",
314
+ "profile_selection_source", "required_validation_controls", "control_bindings",
315
+ )}
316
+ validation = {**validation, **{key: value.get("result", "UNRESOLVED") for key, value in controls.items()}}
317
+ elif required:
318
+ # Historical evidence has no persisted profile: do not promote it.
319
+ required_state = "UNRESOLVED"
320
+ authorities = [str(row[1]) for row in actions]
321
+ cleanup_attempted = any(
322
+ action == "CLEANUP" and authority == "AUTONOMOUS_EP_ACTION"
323
+ for action, authority in actions
324
+ )
325
+ cleanup_outcome = (
326
+ "COMPLETED" if cleanup_attempted and execution_outcome == "COMPLETE"
327
+ else "FAILED" if cleanup_attempted and execution_outcome in {"BLOCKED", "FAILED"}
328
+ else "NOT_REQUIRED" if action_intent == "VALIDATION_ONLY"
329
+ else "UNAVAILABLE"
330
+ )
331
+ snapshot = {
332
+ "run_id": run_id,
333
+ "terminal_execution_state": execution_outcome,
334
+ "execution_mode": execution_mode,
335
+ "managed_authority_profile": "OPERATOR_OWNED_PR_MERGE",
336
+ "action_intent": action_intent,
337
+ "fresh_submission": fresh,
338
+ "retry_parent": retry_parent or ("NONE" if lineage_available else "UNAVAILABLE"),
339
+ "resume_parent": resume_parent or ("NONE" if lineage_available else "UNAVAILABLE"),
340
+ "submission_id": submission_id or "UNAVAILABLE",
341
+ "implementation_pr": implementation_pr,
342
+ "finalization_pr": finalization_pr,
343
+ "gates": [{"gate_type": row[0], "status": row[1], "related_pr": row[2]} for row in gates],
344
+ "actions": [{"action": row[0], "authority": row[1]} for row in actions],
345
+ "validation_current": validation,
346
+ "required_validation_state": required_state,
347
+ "validation_profile": profile_projection or "UNAVAILABLE",
348
+ "validation_projection_conflict": conflict,
349
+ "pr_checks": pr_checks,
350
+ "pr_check_projection_conflict": pr_check_conflict,
351
+ "repository_state": repository_state,
352
+ "workspace_state": workspace_state,
353
+ "main_origin_sync": main_origin_sync,
354
+ "worktree_state": worktree_state,
355
+ "active_blocking_predecessor": active_blocker,
356
+ "recovery_required": recovery_required,
357
+ "cleanup_outcome": cleanup_outcome,
358
+ "expected_operator_gate_count": len(gates),
359
+ "autonomous_ep_action_count": authorities.count("AUTONOMOUS_EP_ACTION"),
360
+ "external_platform_event_count": authorities.count("EXTERNAL_PLATFORM_EVENT"),
361
+ "unplanned_manual_intervention_count": authorities.count("UNPLANNED_MANUAL_INTERVENTION"),
362
+ "unknown_authority_count": authorities.count("UNKNOWN_AUTHORITY"),
363
+ "reviewer_independence": "NOT_APPLICABLE"
364
+ if not reviewer_records
365
+ else (
366
+ "PROVEN" if all(not row.get("failed") for row in reviewer_records) else "UNAVAILABLE"
367
+ ),
368
+ "observed_at": _now(),
369
+ }
370
+ for role, pr_number in (("IMPLEMENTATION", implementation_pr), ("FINALIZATION", finalization_pr)):
371
+ check = pr_checks.get(role, {})
372
+ snapshot[f"{role.lower()}_delivery"] = (
373
+ "COMPLETE"
374
+ if isinstance(pr_number, int)
375
+ and check.get("pr_number") == pr_number
376
+ and check.get("pr_state") == "MERGED"
377
+ and check.get("merge_state") == "MERGED"
378
+ and isinstance(check.get("merge_commit"), str)
379
+ and len(check["merge_commit"]) == 40
380
+ and check.get("required_checks_state") == "PASS"
381
+ else "UNAVAILABLE"
382
+ )
383
+ snapshot["run_qualification"], snapshot["qualification_failure_reasons"] = evaluate(snapshot)
384
+ # Compatibility projection retained for existing consumers. It has the
385
+ # same run-scoped meaning and must never be mistaken for platform-wide
386
+ # qualification evidence.
387
+ snapshot["managed_autonomy_qualification"] = snapshot["run_qualification"]
388
+ if persist:
389
+ required_controls = snapshot.get("validation_profile", {})
390
+ required_controls = required_controls if isinstance(required_controls, dict) else {}
391
+ required_control_snapshot = {
392
+ "profile_reference": required_controls.get("profile_reference", "UNAVAILABLE"),
393
+ "validation_profile_version": required_controls.get("validation_profile_version", "UNAVAILABLE"),
394
+ "required_validation_controls": required_controls.get("required_validation_controls", ()),
395
+ "validation_current": snapshot["validation_current"],
396
+ }
397
+ snapshot["required_control_snapshot_ref"] = "required-controls:sha256:" + hashlib.sha256(
398
+ json.dumps(required_control_snapshot, sort_keys=True, separators=(",", ":"), default=str).encode()
399
+ ).hexdigest()
400
+ snapshot["terminal_checkpoint_ref"] = f"terminal-checkpoint:{run_id}:{execution_outcome}"
401
+ snapshot["reconciliation_evidence"] = {
402
+ "repository_state": repository_state,
403
+ "workspace_state": workspace_state,
404
+ "main_origin_sync": main_origin_sync,
405
+ "worktree_state": worktree_state,
406
+ "active_blocking_predecessor": active_blocker,
407
+ "recovery_required": recovery_required,
408
+ }
409
+ conflicts = []
410
+ if snapshot["validation_projection_conflict"]:
411
+ conflicts.append("VALIDATION_CURRENT_CONFLICT")
412
+ if snapshot["pr_check_projection_conflict"]:
413
+ conflicts.append("PR_CHECK_CURRENT_CONFLICT")
414
+ snapshot["projection_conflicts"] = conflicts
415
+ # The persisted contract has exactly two outcomes. The existing
416
+ # in-memory compatibility label remains available to old callers.
417
+ if snapshot["run_qualification"] == "EVIDENCE_INSUFFICIENT":
418
+ snapshot["run_qualification"] = "NOT_QUALIFIED"
419
+ snapshot["managed_autonomy_qualification"] = "NOT_QUALIFIED"
420
+ snapshot["persisted_at"] = snapshot["observed_at"]
421
+ identity_payload = {key: value for key, value in snapshot.items() if key not in {"qualification_snapshot_id", "persisted_at", "observed_at"}}
422
+ snapshot["qualification_snapshot_id"] = "qualification:sha256:" + hashlib.sha256(
423
+ json.dumps(identity_payload, sort_keys=True, separators=(",", ":"), default=str).encode()
424
+ ).hexdigest()
425
+ return record_run_qualification_snapshot(root, snapshot)
426
+ return snapshot
427
+
428
+
429
+ def evaluate(snapshot: dict[str, object]) -> tuple[str, list[str]]:
430
+ reasons: list[str] = []
431
+ terminal = snapshot.get("terminal_execution_state")
432
+ if terminal == "FAILED":
433
+ return "NOT_QUALIFIED", ["TERMINAL_EXECUTION_FAILED"]
434
+ if terminal == "BLOCKED":
435
+ return "NOT_QUALIFIED", ["TERMINAL_EXECUTION_BLOCKED"]
436
+ if snapshot.get("fresh_submission") != "YES":
437
+ reasons.append("FRESH_SUBMISSION_UNPROVEN")
438
+ validation_only = snapshot.get("action_intent") == "VALIDATION_ONLY"
439
+ if snapshot.get("terminal_execution_state") != "COMPLETE":
440
+ reasons.append("VALIDATION_EXECUTION_UNPROVEN" if validation_only else "IMPLEMENTATION_DELIVERY_UNPROVEN")
441
+ if snapshot.get("unknown_authority_count", 0):
442
+ reasons.append("EP_ACTION_AUTHORITY_UNPROVEN")
443
+ if snapshot.get("unplanned_manual_intervention_count", 0):
444
+ reasons.append("UNEXPECTED_MANUAL_INTERVENTION")
445
+ gate = {
446
+ row.get("gate_type"): row.get("status")
447
+ for row in snapshot.get("gates", [])
448
+ if isinstance(row, dict)
449
+ }
450
+ if not validation_only:
451
+ if snapshot.get("implementation_delivery") != "COMPLETE":
452
+ reasons.append("IMPLEMENTATION_DELIVERY_UNPROVEN")
453
+ if snapshot.get("finalization_delivery") != "COMPLETE":
454
+ reasons.append("FINALIZATION_DELIVERY_UNPROVEN")
455
+ if gate.get("IMPLEMENTATION_MERGE_APPROVAL") != "SATISFIED":
456
+ reasons.append("IMPLEMENTATION_MERGE_GATE_UNPROVEN")
457
+ if gate.get("FINALIZATION_MERGE_APPROVAL") != "SATISFIED":
458
+ reasons.append("FINALIZATION_MERGE_GATE_UNPROVEN")
459
+ autonomous = {
460
+ row.get("action")
461
+ for row in snapshot.get("actions", [])
462
+ if isinstance(row, dict) and row.get("authority") == "AUTONOMOUS_EP_ACTION"
463
+ }
464
+ if not validation_only and (
465
+ not {
466
+ "IMPLEMENTATION",
467
+ "POST_IMPLEMENTATION_MERGE",
468
+ "FINALIZATION",
469
+ "RECONCILIATION",
470
+ "CLEANUP",
471
+ }
472
+ <= autonomous
473
+ ):
474
+ reasons.append("EP_ACTION_AUTHORITY_UNPROVEN")
475
+ if snapshot.get("required_validation_state") != "PASS":
476
+ reasons.append("REQUIRED_VALIDATION_UNRESOLVED")
477
+ if snapshot.get("cleanup_outcome") not in {"COMPLETED", "NOT_REQUIRED"}:
478
+ reasons.append("CLEANUP_OUTCOME_UNPROVEN")
479
+ if snapshot.get("validation_projection_conflict"):
480
+ reasons.append("EVIDENCE_CONFLICT")
481
+ if snapshot.get("pr_check_projection_conflict"):
482
+ reasons.append("EVIDENCE_CONFLICT")
483
+ for role in (() if validation_only else ("IMPLEMENTATION", "FINALIZATION")):
484
+ if snapshot.get(f"{role.lower()}_pr") is not None:
485
+ check = snapshot.get("pr_checks", {}).get(role, {})
486
+ if check.get("required_checks_state") != "PASS":
487
+ reasons.append(f"{role}_REQUIRED_CHECKS_UNRESOLVED")
488
+ for key, wanted in {
489
+ "repository_state": "MERGED_RECONCILED",
490
+ "workspace_state": "WORKSPACE_READY",
491
+ "main_origin_sync": "YES",
492
+ "worktree_state": "CLEAN",
493
+ "active_blocking_predecessor": "NONE",
494
+ "recovery_required": "NO",
495
+ }.items():
496
+ if snapshot.get(key) != wanted:
497
+ reasons.append(
498
+ "REPOSITORY_FINAL_STATE_UNPROVEN"
499
+ if key in {"repository_state", "main_origin_sync", "worktree_state"}
500
+ else "WORKSPACE_FINAL_STATE_UNPROVEN"
501
+ )
502
+ if snapshot.get("reviewer_independence") not in {"PROVEN", "NOT_APPLICABLE"}:
503
+ reasons.append("REVIEWER_POLICY_UNPROVEN")
504
+ reasons = list(dict.fromkeys(reasons))
505
+ return (
506
+ ("NOT_QUALIFIED", reasons)
507
+ if "UNEXPECTED_MANUAL_INTERVENTION" in reasons
508
+ else (("QUALIFIED", []) if not reasons else ("EVIDENCE_INSUFFICIENT", reasons))
509
+ )
@@ -0,0 +1,105 @@
1
+ """Installation-owned lifecycle for Engineering Platform's managed Codex CLI.
2
+
3
+ This preserves the historical explicit-console repair model: startup only
4
+ observes the EP-owned runtime, while a confirmed operator action provisions or
5
+ updates a version pinned from the npm registry. It never selects a PATH
6
+ ``codex`` executable.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+ import json
12
+ import re
13
+ import shutil
14
+ from threading import Lock
15
+
16
+ from .providers import CodexCliProvider, LocalProcessProvider, codex_cli_executable, engineering_platform_codex_cli_prefix
17
+
18
+
19
+ CODEX_CLI_PACKAGE = "@openai/codex"
20
+ _VERSION = re.compile(r"^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$")
21
+ _VERSION_IN_OUTPUT = re.compile(r"(?<!\d)(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?!\d)")
22
+ _install_lock = Lock()
23
+
24
+
25
+ class ManagedCodexRuntimeError(RuntimeError):
26
+ """The explicit managed-runtime repair could not complete safely."""
27
+
28
+
29
+ def version(value: object) -> str | None:
30
+ candidate = str(value or "").strip().removeprefix("v")
31
+ if _VERSION.fullmatch(candidate):
32
+ return candidate
33
+ match = _VERSION_IN_OUTPUT.search(candidate)
34
+ return match.group(1) if match else None
35
+
36
+
37
+ def version_key(value: str) -> tuple[int, int, int, int, str]:
38
+ base, separator, prerelease = value.partition("-")
39
+ major, minor, patch = (int(part) for part in base.split("."))
40
+ return major, minor, patch, 1 if not separator else 0, prerelease
41
+
42
+
43
+ def npm_executable() -> str | None:
44
+ return shutil.which("npm")
45
+
46
+
47
+ def inspect(root: Path) -> dict[str, object]:
48
+ """Classify only the canonical managed launcher without network activity."""
49
+ executable = engineering_platform_codex_cli_prefix() / "bin" / "codex"
50
+ if not executable.is_file():
51
+ return {"state": "MISSING", "path": str(executable), "remediation_available": npm_executable() is not None}
52
+ if not executable.stat().st_mode & 0o111:
53
+ return {"state": "BROKEN", "path": str(executable), "remediation_available": npm_executable() is not None}
54
+ try:
55
+ completed = CodexCliProvider().command("--version")
56
+ except OSError:
57
+ completed = None
58
+ detected = version((completed.stdout or completed.stderr) if completed is not None and completed.returncode == 0 else None)
59
+ if detected is None:
60
+ return {"state": "BROKEN", "path": str(executable), "remediation_available": npm_executable() is not None}
61
+ return {"state": "READY", "path": str(executable), "version": detected, "remediation_available": npm_executable() is not None}
62
+
63
+
64
+ def _published_version(root: Path, npm: str) -> str:
65
+ try:
66
+ completed = LocalProcessProvider().execute(root, (npm, "view", CODEX_CLI_PACKAGE, "version", "--json"))
67
+ raw = json.loads(completed.stdout) if completed.returncode == 0 else None
68
+ except (OSError, ValueError, json.JSONDecodeError) as error:
69
+ raise ManagedCodexRuntimeError("codex_cli_update_unavailable") from error
70
+ candidates = raw if isinstance(raw, list) else [raw]
71
+ versions = [candidate for item in candidates if (candidate := version(item)) is not None]
72
+ latest = max(versions, key=version_key, default=None)
73
+ if latest is None:
74
+ raise ManagedCodexRuntimeError("codex_cli_update_unavailable")
75
+ return latest
76
+
77
+
78
+ def provision(root: Path, *, allow_current: bool = True) -> dict[str, object]:
79
+ """Install or update the exact published release into EP's owned prefix."""
80
+ with _install_lock:
81
+ npm = npm_executable()
82
+ if npm is None:
83
+ raise ManagedCodexRuntimeError("codex_cli_update_unavailable")
84
+ before = inspect(root)
85
+ if before["state"] == "READY" and not allow_current:
86
+ return {"updated": False, "current_version": before["version"]}
87
+ latest = _published_version(root, npm)
88
+ if before.get("version") == latest:
89
+ return {"updated": False, "current_version": latest}
90
+ try:
91
+ completed = LocalProcessProvider().execute(
92
+ root,
93
+ (npm, "install", "--global", "--prefix", str(engineering_platform_codex_cli_prefix()), f"{CODEX_CLI_PACKAGE}@{latest}"),
94
+ )
95
+ except OSError as error:
96
+ raise ManagedCodexRuntimeError("codex_cli_update_failed") from error
97
+ if completed.returncode:
98
+ diagnostic = f"{completed.stdout}\n{completed.stderr}".casefold()
99
+ raise ManagedCodexRuntimeError(
100
+ "codex_cli_update_permissions_required" if "eacces" in diagnostic or "permission denied" in diagnostic else "codex_cli_update_failed"
101
+ )
102
+ after = inspect(root)
103
+ if after.get("state") != "READY" or not isinstance(after.get("version"), str) or version_key(after["version"]) < version_key(latest):
104
+ raise ManagedCodexRuntimeError("codex_cli_update_failed")
105
+ return {"updated": True, "current_version": after["version"]}