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,447 @@
1
+ """Read-only, run-scoped execution lifecycle projection for the console.
2
+
3
+ This module deliberately projects persisted Execution Host checkpoints only. It
4
+ does not coordinate transitions, repair, retry, resume, liveness, or telemetry.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ from pathlib import Path
10
+
11
+ from .storage import EngineeringStorageError, load_run_qualification_snapshot, open_storage
12
+ from .agent_state import TransactionState
13
+ from .provider_recovery import load_recovery_state
14
+ from .status_reconciliation import is_stale_rolling_status_block
15
+
16
+
17
+ TERMINAL = frozenset({"COMPLETE", "BLOCKED", "FAILED"})
18
+ _MANAGED_PATH = (
19
+ "START", "INITIALIZE", "CAPABILITY_REVIEW", "EXECUTE_AGENT", "LOCAL_REPOSITORY_VALIDATION", "QUALITY_CONTROL_AGENT", "REPAIR_AGENT",
20
+ "WAIT_FOR_OPERATOR_MERGE", "FINALIZE_AGENT", "WAIT_FOR_FINALIZATION_MERGE",
21
+ "RECONCILE_AGENT", "REPOSITORY_CLEANUP", "TERMINAL",
22
+ )
23
+ _FINALIZATION_REPAIR_PATH = (
24
+ "START", "INITIALIZE", "CAPABILITY_REVIEW", "EXECUTE_AGENT", "QUALITY_CONTROL_AGENT", "REPAIR_AGENT",
25
+ "WAIT_FOR_OPERATOR_MERGE", "FINALIZE_AGENT", "FINALIZATION_REPAIR_AGENT", "WAIT_FOR_FINALIZATION_MERGE",
26
+ "RECONCILE_AGENT", "REPOSITORY_CLEANUP", "TERMINAL",
27
+ )
28
+ # Genesis has no pull-request merge boundary. This is presentation of the
29
+ # existing mode contract, not a new execution sequence.
30
+ _GENESIS_PATH = (
31
+ "START", "INITIALIZE", "CAPABILITY_REVIEW", "EXECUTE_AGENT", "QUALITY_CONTROL_AGENT", "REPAIR_AGENT",
32
+ "FINALIZE_AGENT", "REPOSITORY_CLEANUP", "TERMINAL",
33
+ )
34
+ # A reconciliation-only run remains part of the full delivery narrative. Its
35
+ # earlier delivery phases are explicitly skipped rather than silently omitted,
36
+ # so the dashboard never renders a misleading half-workflow.
37
+ _STATUS_RECONCILIATION_PATH = tuple(step for step in _MANAGED_PATH if step != "LOCAL_REPOSITORY_VALIDATION")
38
+
39
+ # This is a presentation-only association. The Execution Host stays the
40
+ # authority for phase timing; this projection only groups persisted evidence
41
+ # under the corresponding visible lifecycle step.
42
+ _STEP_PHASES = {
43
+ "START": frozenset({"QUEUE_WAIT", "SUBMISSION_CLAIM"}),
44
+ "INITIALIZE": frozenset({"INITIALIZATION", "HOST_PREFLIGHT", "WORKSPACE_PREFLIGHT", "CAPABILITY_PREFLIGHT"}),
45
+ "CAPABILITY_REVIEW": frozenset({"CAPABILITY_REVIEW"}),
46
+ "EXECUTE_AGENT": frozenset({"EXECUTION_PREPARATION", "PROVIDER_EXECUTION", "VALIDATION"}),
47
+ "LOCAL_REPOSITORY_VALIDATION": frozenset({"VALIDATION", "PROVIDER_EXECUTION"}),
48
+ "QUALITY_CONTROL_AGENT": frozenset({"QUALITY_CONTROL", "PROVIDER_EXECUTION", "VALIDATION"}),
49
+ "REPAIR_AGENT": frozenset({"REPAIR", "PROVIDER_EXECUTION", "VALIDATION"}),
50
+ "FINALIZATION_REPAIR_AGENT": frozenset({"REPAIR", "PROVIDER_EXECUTION", "VALIDATION"}),
51
+ "WAIT_FOR_OPERATOR_MERGE": frozenset({"PR_OR_MERGE", "EXTERNAL_CI_WAIT"}),
52
+ "FINALIZE_AGENT": frozenset({"REPOSITORY_FINALIZATION", "FINALIZATION"}),
53
+ "RECONCILE_AGENT": frozenset({"RECONCILIATION", "REPORT_GENERATION", "EVIDENCE_PERSISTENCE"}),
54
+ "REPOSITORY_CLEANUP": frozenset({"REPOSITORY_CLEANUP"}),
55
+ "TERMINAL": frozenset({"TOTAL_EXECUTION"}),
56
+ }
57
+
58
+
59
+ def intended_path(
60
+ execution_mode: object,
61
+ transaction_kind: object = "IMPLEMENTATION",
62
+ implementation_pull_request: object = None,
63
+ ) -> tuple[str, ...]:
64
+ """Return the canonical display path for one existing execution mode."""
65
+ if transaction_kind == "RECONCILIATION" or (
66
+ transaction_kind == "FINALIZATION" and implementation_pull_request is None
67
+ ):
68
+ return _STATUS_RECONCILIATION_PATH
69
+ if transaction_kind == "FINALIZATION":
70
+ return _FINALIZATION_REPAIR_PATH
71
+ return _GENESIS_PATH if execution_mode == "GENESIS" else _MANAGED_PATH
72
+
73
+
74
+ def _checkpoint(value: object) -> dict[str, object]:
75
+ if not isinstance(value, str):
76
+ return {}
77
+ try:
78
+ parsed = json.loads(value)
79
+ except json.JSONDecodeError:
80
+ return {}
81
+ return parsed if isinstance(parsed, dict) else {}
82
+
83
+
84
+ def _nonnegative_int(value: object) -> int:
85
+ return value if isinstance(value, int) and not isinstance(value, bool) and value >= 0 else 0
86
+
87
+
88
+ def _pull_request_recorded(value: object) -> bool:
89
+ return isinstance(value, int) and not isinstance(value, bool) and value > 0
90
+
91
+
92
+ def _display_phase(phase: str, checkpoint: dict[str, object]) -> str:
93
+ """Map internal evidence polling onto its visible lifecycle boundary."""
94
+ # GitHub plus origin/main evidence has already proven this hand-off was
95
+ # merged. The watcher owns the subsequent resume, but the console must
96
+ # not keep showing an obsolete operator action in that small interval.
97
+ if phase == "WAIT_FOR_OPERATOR_MERGE" and checkpoint.get("next_action") == "resume_verified_merge":
98
+ return "FINALIZE_AGENT" if checkpoint.get("transaction_kind") == "IMPLEMENTATION" else "RECONCILE_AGENT"
99
+ # Finalization has its own PR hand-off. Do not project it back onto the
100
+ # implementation merge once that implementation PR is already merged.
101
+ if phase == "WAIT_FOR_OPERATOR_MERGE" and checkpoint.get("transaction_kind") == "FINALIZATION":
102
+ return (
103
+ "WAIT_FOR_RECONCILIATION_MERGE"
104
+ if checkpoint.get("implementation_pull_request") is None
105
+ else "WAIT_FOR_FINALIZATION_MERGE"
106
+ )
107
+ if phase == "WAIT_FOR_OPERATOR_MERGE" and checkpoint.get("transaction_kind") == "RECONCILIATION":
108
+ return "WAIT_FOR_RECONCILIATION_MERGE"
109
+ if phase == "REPAIR_AGENT" and checkpoint.get("transaction_kind") == "FINALIZATION":
110
+ return "FINALIZATION_REPAIR_AGENT"
111
+ if phase != "WAIT_FOR_TERMINAL_EVIDENCE":
112
+ return phase
113
+ # Required-check polling happens before the operator merge hand-off. It
114
+ # is not a completed merge, even though its PR timing evidence exists.
115
+ # A finalization transaction uses the same internal polling phase after
116
+ # the implementation merge, so it remains on its own visible step.
117
+ if checkpoint.get("transaction_kind") == "FINALIZATION":
118
+ return "RECONCILE_AGENT" if checkpoint.get("implementation_pull_request") is None else "FINALIZE_AGENT"
119
+ if checkpoint.get("transaction_kind") == "RECONCILIATION":
120
+ return "RECONCILE_AGENT"
121
+ return "WAIT_FOR_OPERATOR_MERGE"
122
+
123
+
124
+ def projection(root: Path, run_id: str | None) -> dict[str, object]:
125
+ """Project persisted lifecycle evidence for exactly one ``run_id``.
126
+
127
+ Missing event history is explicitly represented as unavailable. The
128
+ terminal checkpoint may still be shown, but no intermediate progress is
129
+ invented from reports, commits, timing or prompt content.
130
+ """
131
+ if not isinstance(run_id, str) or not run_id:
132
+ return {"run_id": run_id, "available": False, "steps": []}
133
+ try:
134
+ connection = open_storage(root, create=False)
135
+ try:
136
+ row = connection.execute(
137
+ "SELECT payload,phase FROM engineering_transactions WHERE run_id=?", (run_id,)
138
+ ).fetchone()
139
+ events = connection.execute(
140
+ "SELECT phase,checkpoint,recorded_at FROM execution_lifecycle_events WHERE run_id=? ORDER BY id",
141
+ (run_id,),
142
+ ).fetchall()
143
+ mode_row = connection.execute(
144
+ "SELECT execution_mode FROM execution_runs WHERE run_id=?", (run_id,)
145
+ ).fetchone()
146
+ phase_spans = connection.execute(
147
+ "SELECT phase_id,parent_phase_id,phase_name,attempt,started_at,completed_at,duration_ms,outcome,metadata "
148
+ "FROM execution_phase_spans WHERE run_id=? ORDER BY ordinal",
149
+ (run_id,),
150
+ ).fetchall()
151
+ finally:
152
+ connection.close()
153
+ except EngineeringStorageError:
154
+ return {"run_id": run_id, "available": False, "steps": []}
155
+ if row is None:
156
+ return {"run_id": run_id, "available": False, "steps": []}
157
+ checkpoint = _checkpoint(row[0])
158
+ phase = str(row[1])
159
+ mode = checkpoint.get("execution_mode") or (mode_row[0] if mode_row else None) or "MANAGED"
160
+ transaction_kind = checkpoint.get("transaction_kind") or "IMPLEMENTATION"
161
+ path = intended_path(mode, transaction_kind, checkpoint.get("implementation_pull_request"))
162
+ display_phase = _display_phase(phase, checkpoint)
163
+ observed: dict[str, dict[str, object]] = {}
164
+ repair_iterations = 0
165
+ implementation_merge_required = _pull_request_recorded(checkpoint.get("implementation_pull_request"))
166
+ finalization_merge_required = _pull_request_recorded(checkpoint.get("finalization_pull_request"))
167
+ reconciliation_merge_required = _pull_request_recorded(checkpoint.get("reconciliation_pull_request"))
168
+ recorded_pull_request = any(
169
+ _pull_request_recorded(checkpoint.get(field))
170
+ for field in ("pull_request", "implementation_pull_request", "finalization_pull_request", "reconciliation_pull_request")
171
+ )
172
+ if checkpoint.get("transaction_kind") == "FINALIZATION":
173
+ finalization_merge_required = finalization_merge_required or _pull_request_recorded(checkpoint.get("pull_request"))
174
+ if checkpoint.get("transaction_kind") == "RECONCILIATION":
175
+ reconciliation_merge_required = reconciliation_merge_required or _pull_request_recorded(checkpoint.get("pull_request"))
176
+ else:
177
+ implementation_merge_required = implementation_merge_required or _pull_request_recorded(checkpoint.get("pull_request"))
178
+ verified_merge_resume_kinds: set[str] = set()
179
+ if checkpoint.get("next_action") == "resume_verified_merge":
180
+ verified_merge_resume_kinds.add(str(transaction_kind))
181
+ for event_phase, event_checkpoint, recorded_at in events:
182
+ event = _checkpoint(event_checkpoint)
183
+ if event.get("next_action") == "resume_verified_merge":
184
+ verified_merge_resume_kinds.add(str(event.get("transaction_kind") or "IMPLEMENTATION"))
185
+ recorded_pull_request = recorded_pull_request or any(
186
+ _pull_request_recorded(event.get(field))
187
+ for field in ("pull_request", "implementation_pull_request", "finalization_pull_request", "reconciliation_pull_request")
188
+ )
189
+ event_step = _display_phase(str(event_phase), event)
190
+ if event_step in path and event_step not in {"START", "TERMINAL"}:
191
+ observed.setdefault(event_step, {"started_at": recorded_at})
192
+ if event_phase == "REPAIR_AGENT":
193
+ repair_iterations = max(repair_iterations, _nonnegative_int(event.get("repair_iterations")))
194
+ implementation_merge_required = (
195
+ implementation_merge_required or event_step == "WAIT_FOR_OPERATOR_MERGE"
196
+ )
197
+ finalization_merge_required = (
198
+ finalization_merge_required or event_step == "WAIT_FOR_FINALIZATION_MERGE"
199
+ )
200
+ reconciliation_merge_required = (
201
+ reconciliation_merge_required or event_step == "WAIT_FOR_RECONCILIATION_MERGE"
202
+ )
203
+ repair_iterations = max(repair_iterations, _nonnegative_int(checkpoint.get("repair_iterations")))
204
+ evidence_available = bool(events)
205
+ terminal_state = phase if phase in TERMINAL else None
206
+ try:
207
+ status_reconciliation_block = is_stale_rolling_status_block(
208
+ TransactionState.from_dict(checkpoint)
209
+ ) and not recorded_pull_request
210
+ except ValueError:
211
+ status_reconciliation_block = False
212
+ # Reaching the pull-request hand-off is not evidence that the pull request
213
+ # was merged. A later finalization step (or a successful terminal state)
214
+ # is the first lifecycle evidence that can make the merge node complete.
215
+ implementation_merge_completed = (
216
+ terminal_state == "COMPLETE"
217
+ or "FINALIZE_AGENT" in observed
218
+ or "REPOSITORY_CLEANUP" in observed
219
+ or "IMPLEMENTATION" in verified_merge_resume_kinds
220
+ )
221
+ finalization_merge_completed = (
222
+ terminal_state == "COMPLETE"
223
+ or "REPOSITORY_CLEANUP" in observed
224
+ or "FINALIZATION" in verified_merge_resume_kinds
225
+ )
226
+ reconciliation_merge_completed = (
227
+ terminal_state == "COMPLETE"
228
+ or "REPOSITORY_CLEANUP" in observed
229
+ or "RECONCILIATION" in verified_merge_resume_kinds
230
+ )
231
+ # The managed contract permits a no-PR path and a single implementation-PR
232
+ # path. A terminal pre-flight block is not merge evidence. Omit boundaries
233
+ # that have no persisted PR evidence instead of inventing an operator wait.
234
+ if mode != "GENESIS":
235
+ path = tuple(
236
+ step_id for step_id in path
237
+ if not (
238
+ step_id == "WAIT_FOR_OPERATOR_MERGE"
239
+ and (not implementation_merge_required or status_reconciliation_block)
240
+ and ("FINALIZE_AGENT" in observed or "REPOSITORY_CLEANUP" in observed or terminal_state == "COMPLETE" or status_reconciliation_block)
241
+ )
242
+ and not (
243
+ step_id == "WAIT_FOR_FINALIZATION_MERGE"
244
+ and (not finalization_merge_required or status_reconciliation_block)
245
+ and ("REPOSITORY_CLEANUP" in observed or terminal_state == "COMPLETE" or status_reconciliation_block)
246
+ )
247
+ and not (
248
+ step_id == "WAIT_FOR_RECONCILIATION_MERGE"
249
+ and (not reconciliation_merge_required or status_reconciliation_block)
250
+ and ("REPOSITORY_CLEANUP" in observed or terminal_state == "COMPLETE" or status_reconciliation_block)
251
+ )
252
+ )
253
+ quality_phase_ids = {
254
+ str(phase_id) for phase_id, _, phase_name, *_ in phase_spans
255
+ if phase_name == "QUALITY_CONTROL"
256
+ }
257
+ repair_phase_ids = {
258
+ str(phase_id) for phase_id, _, phase_name, *_ in phase_spans
259
+ if phase_name == "REPAIR"
260
+ }
261
+ parent_by_phase_id = {
262
+ str(phase_id): str(parent_phase_id)
263
+ for phase_id, parent_phase_id, *_ in phase_spans
264
+ if parent_phase_id is not None
265
+ }
266
+ metadata_by_phase_id = {
267
+ str(phase_id): _checkpoint(metadata)
268
+ for phase_id, *_, metadata in phase_spans
269
+ }
270
+
271
+ def is_nested_below(parent_phase_id: object, owner_phase_ids: set[str]) -> bool:
272
+ """Return whether a span is nested below a visible lifecycle owner."""
273
+ phase_id = str(parent_phase_id) if parent_phase_id is not None else None
274
+ seen: set[str] = set()
275
+ while phase_id and phase_id not in seen:
276
+ if phase_id in owner_phase_ids:
277
+ return True
278
+ seen.add(phase_id)
279
+ phase_id = parent_by_phase_id.get(phase_id)
280
+ return False
281
+
282
+ def is_local_validation_phase(phase_id: object, parent_phase_id: object) -> bool:
283
+ """Return whether a span belongs to the local-validation invocation."""
284
+ current_id = str(phase_id)
285
+ current_parent = parent_phase_id
286
+ seen: set[str] = set()
287
+ while current_id and current_id not in seen:
288
+ if metadata_by_phase_id.get(current_id, {}).get("lifecycle_step") == "LOCAL_REPOSITORY_VALIDATION":
289
+ return True
290
+ seen.add(current_id)
291
+ current_id = str(current_parent) if current_parent is not None else ""
292
+ current_parent = parent_by_phase_id.get(current_id)
293
+ return False
294
+
295
+ def belongs_to_step(step_id: str, phase_id: object, parent_phase_id: object, phase_name: object) -> bool:
296
+ """Keep nested provider timing with its visible lifecycle owner."""
297
+ name = str(phase_name)
298
+ nested_in_quality = is_nested_below(parent_phase_id, quality_phase_ids)
299
+ nested_in_repair = is_nested_below(parent_phase_id, repair_phase_ids)
300
+ local_validation = is_local_validation_phase(phase_id, parent_phase_id)
301
+ if step_id == "EXECUTE_AGENT":
302
+ return name in _STEP_PHASES[step_id] and not nested_in_quality and not nested_in_repair and not local_validation
303
+ if step_id == "LOCAL_REPOSITORY_VALIDATION":
304
+ return name in _STEP_PHASES[step_id] and local_validation
305
+ if step_id == "QUALITY_CONTROL_AGENT":
306
+ return name == "QUALITY_CONTROL" or nested_in_quality
307
+ if step_id in {"REPAIR_AGENT", "FINALIZATION_REPAIR_AGENT"}:
308
+ return name == "REPAIR" or nested_in_repair
309
+ return name in _STEP_PHASES.get(step_id, frozenset())
310
+
311
+ steps: list[dict[str, object]] = []
312
+ for order, step_id in enumerate(path):
313
+ state = "PENDING"
314
+ step: dict[str, object] = {
315
+ "id": step_id,
316
+ "order": order,
317
+ "presentation_key": (
318
+ "lifecycle.step.autonomous_quality_repair"
319
+ if mode == "GENESIS" and step_id == "REPAIR_AGENT"
320
+ else
321
+ "lifecycle.step.repair_agent"
322
+ if step_id == "FINALIZATION_REPAIR_AGENT"
323
+ else f"lifecycle.step.{step_id.lower()}"
324
+ ),
325
+ "state": state,
326
+ }
327
+ if step_id == "START":
328
+ step["state"] = "START"
329
+ elif step_id == "TERMINAL":
330
+ step["state"] = terminal_state or "PENDING"
331
+ if terminal_state:
332
+ step["terminal_outcome"] = terminal_state
333
+ elif step_id in {"WAIT_FOR_OPERATOR_MERGE", "WAIT_FOR_FINALIZATION_MERGE", "WAIT_FOR_RECONCILIATION_MERGE"} and step_id in observed:
334
+ step.update(observed[step_id])
335
+ merge_completed = (
336
+ implementation_merge_completed
337
+ if step_id == "WAIT_FOR_OPERATOR_MERGE"
338
+ else finalization_merge_completed if step_id == "WAIT_FOR_FINALIZATION_MERGE" else reconciliation_merge_completed
339
+ )
340
+ if merge_completed:
341
+ step["state"] = "COMPLETED"
342
+ elif display_phase == step_id and terminal_state is None:
343
+ step["state"] = "ACTIVE"
344
+ else:
345
+ # The PR exists, but it has not been merged. In particular,
346
+ # bounded validation repair must leave the merge visibly
347
+ # blocked and must not render a completion checkmark.
348
+ step["state"] = "BLOCKED"
349
+ if step_id == "WAIT_FOR_OPERATOR_MERGE" and display_phase == "REPAIR_AGENT":
350
+ step["action_key"] = "state.repair_bounded_validation_failure"
351
+ elif step_id in observed:
352
+ step.update(observed[step_id])
353
+ step["state"] = "ACTIVE" if display_phase == step_id and terminal_state is None else "COMPLETED"
354
+ elif step_id == display_phase and terminal_state is None:
355
+ step["state"] = "ACTIVE"
356
+ if (
357
+ step_id == "WAIT_FOR_FINALIZATION_MERGE"
358
+ and display_phase == "FINALIZATION_REPAIR_AGENT"
359
+ and terminal_state is None
360
+ ):
361
+ step["state"] = "BLOCKED"
362
+ step["action_key"] = "state.repair_bounded_validation_failure"
363
+ if (
364
+ transaction_kind == "RECONCILIATION"
365
+ and step_id in {"EXECUTE_AGENT", "LOCAL_REPOSITORY_VALIDATION", "QUALITY_CONTROL_AGENT", "REPAIR_AGENT", "WAIT_FOR_OPERATOR_MERGE", "FINALIZE_AGENT", "WAIT_FOR_FINALIZATION_MERGE"}
366
+ and step_id not in observed
367
+ ):
368
+ step["state"] = "SKIPPED"
369
+ step["presentation_detail_key"] = "lifecycle.detail.not_part_of_reconciliation"
370
+ if step_id in {"REPAIR_AGENT", "FINALIZATION_REPAIR_AGENT"} and repair_iterations:
371
+ step["iteration_count"] = repair_iterations
372
+ if step_id in {"REPAIR_AGENT", "FINALIZATION_REPAIR_AGENT"} and step["state"] not in {"PENDING", "SKIPPED"}:
373
+ step["repair_evidence_key"] = (
374
+ "lifecycle.detail_autonomous_quality_repair_evidence"
375
+ if mode == "GENESIS" and step_id == "REPAIR_AGENT"
376
+ else "lifecycle.detail_repair_evidence"
377
+ )
378
+ audit = checkpoint.get("repair_audit")
379
+ if isinstance(audit, (list, tuple)) and audit:
380
+ step["repair_audit"] = list(audit)
381
+ if step_id == "QUALITY_CONTROL_AGENT" and step["state"] not in {"PENDING", "SKIPPED"}:
382
+ evidence = checkpoint.get("quality_evidence")
383
+ if isinstance(evidence, (list, tuple)) and evidence:
384
+ step["quality_evidence"] = list(evidence)
385
+ if step_id == "LOCAL_REPOSITORY_VALIDATION" and step["state"] not in {"PENDING", "SKIPPED"}:
386
+ audit = checkpoint.get("local_validation_audit")
387
+ if isinstance(audit, (list, tuple)) and audit:
388
+ step["repair_audit"] = list(audit)
389
+ step["iteration_count"] = checkpoint.get("local_validation_iterations", 0)
390
+ spans = [
391
+ {
392
+ "phase": phase_name,
393
+ "attempt": attempt,
394
+ "started_at": started_at,
395
+ "finished_at": completed_at,
396
+ "duration_ms": duration_ms,
397
+ "outcome": outcome,
398
+ }
399
+ for phase_id, parent_phase_id, phase_name, attempt, started_at, completed_at, duration_ms, outcome, _ in phase_spans
400
+ if belongs_to_step(step_id, phase_id, parent_phase_id, phase_name)
401
+ ]
402
+ # Timing is supporting evidence, not lifecycle authority. In
403
+ # particular, admission can record a short reconciliation span before
404
+ # the end-reconciliation lifecycle step is ever reached. Do not let
405
+ # that span invent start/end timestamps or a completed phase for a
406
+ # pending (or explicitly skipped) step.
407
+ if spans and step["state"] not in {"PENDING", "SKIPPED"}:
408
+ step["timing"] = {
409
+ "started_at": min(str(span["started_at"]) for span in spans if span["started_at"]),
410
+ "finished_at": max((str(span["finished_at"]) for span in spans if span["finished_at"]), default=None),
411
+ "spans": spans,
412
+ }
413
+ steps.append(step)
414
+ recovery = None
415
+ if status_reconciliation_block:
416
+ recovery = {"kind": "status_reconciliation", "run_id": run_id}
417
+ else:
418
+ # Provider recovery is durable SQLite evidence. Do not consult the
419
+ # historical checkpoint ledger here: it can be stale after a host
420
+ # interruption, whereas this row is atomically advanced with the
421
+ # replacement provider receipt.
422
+ provider_recovery = load_recovery_state(root, run_id)
423
+ if provider_recovery is not None:
424
+ recovery = {
425
+ "kind": "provider_interruption",
426
+ "state": provider_recovery.get("state"),
427
+ "result": provider_recovery.get("result"),
428
+ "triggering_invocation_id": provider_recovery.get("triggering_invocation_id"),
429
+ "replacement_invocation_id": provider_recovery.get("replacement_invocation_id"),
430
+ "recovery_ordinal": provider_recovery.get("recovery_ordinal"),
431
+ "maximum_attempts": provider_recovery.get("maximum_attempts"),
432
+ "lifecycle_phase": provider_recovery.get("lifecycle_phase"),
433
+ }
434
+ qualification = load_run_qualification_snapshot(root, run_id) or {}
435
+ return {
436
+ "run_id": run_id,
437
+ "execution_mode": mode,
438
+ "available": evidence_available,
439
+ "terminal_state": terminal_state,
440
+ "current_step": display_phase if display_phase in path else None,
441
+ "steps": steps,
442
+ "recovery": recovery,
443
+ "qualification": {
444
+ "required_validation_state": qualification.get("required_validation_state", "UNAVAILABLE"),
445
+ "run_qualification": qualification.get("run_qualification", "UNAVAILABLE"),
446
+ },
447
+ }
@@ -0,0 +1,43 @@
1
+ """Provider-neutral execution domain models shared across host components."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class RepositoryEvidence:
9
+ repository: str
10
+ branch: str
11
+ head_sha: str
12
+ clean: bool
13
+ main_contains_head: bool = False
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class PullRequestEvidence:
18
+ number: int
19
+ state: str
20
+ checks_terminal: bool
21
+ checks_passed: bool
22
+ merge_commit: str | None = None
23
+ is_draft: bool = False
24
+ failed_checks: tuple[str, ...] = ()
25
+ head_branch: str | None = None
26
+ base_branch: str | None = None
27
+ merge_state_status: str | None = None
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class AgentResult:
32
+ terminal_state: str
33
+ branch: str | None = None
34
+ pull_request: int | None = None
35
+ terminal_condition: str = "repository_reconciled"
36
+ diagnostic: str | None = None
37
+ repository_path: str | None = None
38
+ commit_sha: str | None = None
39
+ validation_evidence: tuple[dict[str, str], ...] = ()
40
+ quality_evidence: tuple[dict[str, str], ...] = ()
41
+ # A failed validation never becomes a pass. This merely tells the host
42
+ # when a separate validation-environment recovery is warranted.
43
+ validation_disposition: str = "product_failure"
@@ -0,0 +1,166 @@
1
+ """Typed readiness profiles selected before an Execution Host lifecycle starts."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ from enum import StrEnum
6
+ from pathlib import Path
7
+ from typing import Callable
8
+ from datetime import datetime, timezone
9
+
10
+
11
+ class Requirement(StrEnum):
12
+ REQUIRED = "REQUIRED"
13
+ OPTIONAL = "OPTIONAL"
14
+ NOT_APPLICABLE = "NOT_APPLICABLE"
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class ReadinessProfile:
19
+ profile_id: str
20
+ version: int
21
+ execution_mode: str
22
+ repository: Requirement
23
+ remote: Requirement
24
+ upstream: Requirement
25
+ clean_worktree: Requirement
26
+ branch: Requirement
27
+ workspace_authorization: Requirement
28
+ host_qualification: Requirement
29
+ capability_qualification: Requirement
30
+ providers: Requirement
31
+ datastore: Requirement
32
+ active_run_lease: Requirement
33
+ producer_contract: Requirement
34
+ additional_constraints: tuple[str, ...] = ()
35
+
36
+
37
+ PLATFORM_HOST = ReadinessProfile("platform_host", 1, "ANY", Requirement.NOT_APPLICABLE, Requirement.NOT_APPLICABLE, Requirement.NOT_APPLICABLE, Requirement.NOT_APPLICABLE, Requirement.NOT_APPLICABLE, Requirement.NOT_APPLICABLE, Requirement.REQUIRED, Requirement.NOT_APPLICABLE, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.NOT_APPLICABLE, Requirement.NOT_APPLICABLE)
38
+ MANAGED_REPOSITORY = ReadinessProfile("managed_repository", 1, "MANAGED", Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED)
39
+ GENESIS_TARGET = ReadinessProfile("genesis_target", 1, "GENESIS", Requirement.REQUIRED, Requirement.NOT_APPLICABLE, Requirement.NOT_APPLICABLE, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED, Requirement.REQUIRED)
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class ReadinessResult:
44
+ profile: ReadinessProfile
45
+ ready: bool
46
+ diagnostic: str | None = None
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class ReadinessFacts:
51
+ host_ready: bool
52
+ repository_present: bool
53
+ repository_clean: bool | None
54
+ lease_available: bool
55
+ remote_present: bool | None = None
56
+ upstream_present: bool | None = None
57
+ branch_present: bool | None = None
58
+ workspace_authorized: bool | None = None
59
+ capabilities_available: bool | None = None
60
+ providers_available: bool | None = None
61
+ datastore_healthy: bool | None = None
62
+ producer_contract_valid: bool | None = None
63
+
64
+ @classmethod
65
+ def from_preflight(cls, *, host: object, workspace: object, capability: object, lease_available: bool) -> "ReadinessFacts":
66
+ """Adapt already-observed preflight outcomes without performing any probe."""
67
+ def field(subject: object, name: str, default: object = None) -> object:
68
+ return subject.get(name, default) if isinstance(subject, dict) else getattr(subject, name, default)
69
+
70
+ def outcome(subject: object, identifier: str) -> bool | None:
71
+ for item in field(subject, "checks", ()):
72
+ if field(item, "identifier") == identifier:
73
+ return field(item, "outcome") == "PASS"
74
+ return None
75
+
76
+ host_ready = field(host, "outcome") == "PASS"
77
+ repository_present = outcome(workspace, "target_repository") is True
78
+ staged, unstaged, untracked = (outcome(workspace, identifier) for identifier in ("worktree_staged", "worktree_unstaged", "worktree_untracked"))
79
+ repository_clean = (
80
+ outcome(workspace, "clean_worktree")
81
+ if None in {staged, unstaged, untracked}
82
+ else all((staged, unstaged, untracked))
83
+ )
84
+ capabilities_ready = field(capability, "outcome") == "PASS"
85
+ return cls(
86
+ host_ready,
87
+ repository_present,
88
+ repository_clean,
89
+ lease_available,
90
+ remote_present=outcome(workspace, "managed_remote"),
91
+ upstream_present=outcome(workspace, "managed_synchronization"),
92
+ branch_present=outcome(workspace, "managed_expected_branch")
93
+ if outcome(workspace, "managed_expected_branch") is not None
94
+ else outcome(workspace, "genesis_local_repository"),
95
+ workspace_authorized=outcome(workspace, "WORKSPACE_TARGET_AUTHORIZED"),
96
+ capabilities_available=capabilities_ready,
97
+ providers_available=outcome(capability, "provider_support"),
98
+ datastore_healthy=outcome(host, "telemetry_storage"),
99
+ producer_contract_valid=outcome(capability, "producer_contract"),
100
+ )
101
+
102
+
103
+ @dataclass(frozen=True)
104
+ class ReadinessDecision:
105
+ passed: bool
106
+ profile_id: str
107
+ profile_version: int
108
+ execution_mode: str
109
+ failed_requirements: tuple[str, ...]
110
+ facts: ReadinessFacts
111
+ evaluated_at: str
112
+ diagnostic: str | None = None
113
+
114
+
115
+ def selected_profile(execution_mode: str) -> ReadinessProfile:
116
+ if execution_mode == "GENESIS":
117
+ return GENESIS_TARGET
118
+ if execution_mode == "MANAGED":
119
+ return MANAGED_REPOSITORY
120
+ raise ValueError(f"Unsupported execution readiness mode: {execution_mode}")
121
+
122
+
123
+ def evaluate(
124
+ profile: ReadinessProfile,
125
+ *,
126
+ host_root: Path,
127
+ target_root: Path | None,
128
+ managed_clean: Callable[[Path], bool],
129
+ genesis_preflight: Callable[[Path | None], str | None],
130
+ ) -> ReadinessResult:
131
+ """Evaluate only the selected profile; never mix Genesis and Managed checks."""
132
+ if profile.profile_id == PLATFORM_HOST.profile_id:
133
+ return ReadinessResult(profile, host_root.is_dir(), None if host_root.is_dir() else "Execution Host repository is unavailable.")
134
+ if profile.profile_id == GENESIS_TARGET.profile_id:
135
+ diagnostic = genesis_preflight(target_root)
136
+ return ReadinessResult(profile, diagnostic is None, diagnostic)
137
+ if managed_clean(host_root):
138
+ return ReadinessResult(profile, True)
139
+ return ReadinessResult(profile, False, "working tree is not clean; unrelated work will not be touched")
140
+
141
+
142
+ def decide(profile: ReadinessProfile, facts: ReadinessFacts) -> ReadinessDecision:
143
+ """Fail-closed policy evaluation over facts; it never probes the environment."""
144
+ failed: list[str] = []
145
+ requirements = (
146
+ ("host_qualification", profile.host_qualification, facts.host_ready),
147
+ ("repository", profile.repository, facts.repository_present),
148
+ ("remote", profile.remote, facts.remote_present),
149
+ ("upstream", profile.upstream, facts.upstream_present),
150
+ ("clean_worktree", profile.clean_worktree, facts.repository_clean),
151
+ ("branch", profile.branch, facts.branch_present),
152
+ ("workspace_authorization", profile.workspace_authorization, facts.workspace_authorized),
153
+ ("capability_qualification", profile.capability_qualification, facts.capabilities_available),
154
+ ("providers", profile.providers, facts.providers_available),
155
+ ("datastore", profile.datastore, facts.datastore_healthy),
156
+ ("active_run_lease", profile.active_run_lease, facts.lease_available),
157
+ ("producer_contract", profile.producer_contract, facts.producer_contract_valid),
158
+ )
159
+ for identifier, requirement, observed in requirements:
160
+ if requirement is Requirement.REQUIRED and observed is not True:
161
+ failed.append(identifier)
162
+ return ReadinessDecision(
163
+ not failed, profile.profile_id, profile.version, profile.execution_mode,
164
+ tuple(failed), facts, datetime.now(timezone.utc).isoformat(),
165
+ None if not failed else "Readiness requirements are not satisfied: " + ", ".join(failed),
166
+ )