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,647 @@
1
+ """Durable, one-shot provider interruption recovery evidence.
2
+
3
+ This module deliberately owns storage transitions only. The Execution Host
4
+ owns lifecycle decisions and the watcher only decides whether a host may be
5
+ resumed for an already existing run.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from datetime import datetime, timezone
10
+ import argparse
11
+ import json
12
+ import os
13
+ from pathlib import Path
14
+ import sqlite3
15
+ import tempfile
16
+ from uuid import uuid4
17
+
18
+ from .provider_process_identity import ProcessIdentity, capture_process_identity, verify_process_identity
19
+ from .storage import EngineeringStorageError, open_storage, record_artifact, verify_artifact_integrity
20
+ from .execution_lease import liveness as lease_liveness
21
+ from .agent_state import PHASES, StateError, StateStore, redact_diagnostic
22
+
23
+
24
+ RECOVERY_STATES = frozenset({
25
+ "RECOVERY_AVAILABLE", "RECOVERY_STARTING", "RECOVERY_IN_PROGRESS",
26
+ "RECOVERED", "EXHAUSTED", "PRECHECK_FAILED", "AMBIGUOUS",
27
+ })
28
+ ACTIVE_RECOVERY_STATES = frozenset({"RECOVERY_AVAILABLE", "RECOVERY_STARTING", "RECOVERY_IN_PROGRESS"})
29
+ TERMINAL_RECOVERY_STATES = RECOVERY_STATES - ACTIVE_RECOVERY_STATES
30
+ CONTROLLED_INTERRUPTION_PHASES = frozenset({"QUALITY_CONTROL_AGENT"})
31
+ CONTROL_DIRECTORY = Path(".engineering/artifacts/provider-recovery-fault-injection")
32
+
33
+
34
+ def _connection(root: Path, central_database: Path | None = None) -> sqlite3.Connection:
35
+ """Open recovery authority from an explicit CENTRAL binding when supplied."""
36
+ if central_database is None:
37
+ return open_storage(root)
38
+ database = central_database.resolve()
39
+ if not database.is_file():
40
+ raise EngineeringStorageError("CENTRAL recovery database is unavailable")
41
+ connection = sqlite3.connect(database, isolation_level=None)
42
+ connection.execute("PRAGMA foreign_keys=ON")
43
+ connection.execute("PRAGMA busy_timeout=10000")
44
+ return connection
45
+
46
+
47
+ class ControlledInterruptionControlError(ValueError):
48
+ """A bounded operator control request is invalid or unsafe."""
49
+
50
+
51
+ def _now() -> str:
52
+ return datetime.now(timezone.utc).isoformat()
53
+
54
+
55
+ def _control_paths(root: Path, run_id: str, phase: str) -> tuple[Path, Path]:
56
+ directory = root / CONTROL_DIRECTORY
57
+ return directory / "armed" / f"{run_id}-{phase}.json", directory / f"{run_id}-{phase}.json"
58
+
59
+
60
+ def controlled_interruption_status(root: Path, *, run_id: str, phase: str) -> str:
61
+ armed, consumed = _control_paths(root, run_id, phase)
62
+ if consumed.is_file():
63
+ return "CONSUMED"
64
+ return "ARMED" if armed.is_file() else "NOT_ARMED"
65
+
66
+
67
+ def _validate_control_target(root: Path, *, run_id: str, phase: str) -> object:
68
+ if phase not in CONTROLLED_INTERRUPTION_PHASES:
69
+ raise ControlledInterruptionControlError("phase is not supported for controlled interruption")
70
+ try:
71
+ state = StateStore(root / ".engineering" / "engineering-runs").load(run_id)
72
+ except StateError as error:
73
+ raise ControlledInterruptionControlError(str(error)) from error
74
+ if state.terminal:
75
+ raise ControlledInterruptionControlError("run is terminal")
76
+ # The hook is deliberately offered only before its lifecycle boundary. A
77
+ # phase already entered may have an active provider that cannot be raced.
78
+ if state.phase not in PHASES or state.phase in {"QUALITY_CONTROL_AGENT", "REPAIR_AGENT", "FINALIZE_AGENT", "RECONCILE_AGENT", "WAIT_FOR_TERMINAL_EVIDENCE", "WAIT_FOR_OPERATOR_MERGE", "REPOSITORY_CLEANUP"}:
79
+ raise ControlledInterruptionControlError("target phase is already active or has passed")
80
+ return state
81
+
82
+
83
+ def arm_controlled_interruption(root: Path, *, run_id: str, phase: str, armed_by: str | None = None, reason: str | None = None) -> dict[str, object]:
84
+ _validate_control_target(root, run_id=run_id, phase=phase)
85
+ armed, consumed = _control_paths(root, run_id, phase)
86
+ if consumed.is_file():
87
+ raise ControlledInterruptionControlError("controlled interruption is already consumed")
88
+ payload = {"version": 1, "state": "ARMED", "run_id": run_id, "phase": phase, "armed_at": _now(), "armed_by": redact_diagnostic(armed_by or os.environ.get("USER", "operator"), limit=120)}
89
+ if reason:
90
+ payload["reason"] = redact_diagnostic(reason, limit=240)
91
+ armed.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
92
+ try:
93
+ descriptor = os.open(armed, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
94
+ except FileExistsError as error:
95
+ raise ControlledInterruptionControlError("controlled interruption is already armed") from error
96
+ with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
97
+ json.dump(payload, handle, sort_keys=True, separators=(",", ":"))
98
+ handle.write("\n")
99
+ handle.flush()
100
+ os.fsync(handle.fileno())
101
+ return payload
102
+
103
+
104
+ def disarm_controlled_interruption(root: Path, *, run_id: str, phase: str) -> str:
105
+ armed, consumed = _control_paths(root, run_id, phase)
106
+ if consumed.is_file():
107
+ raise ControlledInterruptionControlError("controlled interruption is already consumed and cannot be disarmed")
108
+ try:
109
+ armed.unlink()
110
+ except FileNotFoundError:
111
+ return "NOT_ARMED"
112
+ return "DISARMED"
113
+
114
+
115
+ def consume_controlled_interruption_hook(root: Path, *, run_id: str, phase: str) -> bool:
116
+ """Durably consume the explicit qualification-only interruption hook.
117
+
118
+ The marker is a run-bound artifact rather than a provider recovery row:
119
+ the first invocation has not yet produced the canonical interruption
120
+ evidence needed to create that row. ``O_EXCL`` makes consumption survive
121
+ a host crash in that small interval and prevents an unchanged environment
122
+ setting from firing in a later host. It contains no prompt or provider
123
+ command data.
124
+ """
125
+ requested = os.environ.get("ENGINEERING_PLATFORM_TEST_INTERRUPT_PROVIDER_ONCE")
126
+ armed, path = _control_paths(root, run_id, phase)
127
+ durable_armed = armed.is_file()
128
+ if (requested != f"{run_id}:{phase}" and not durable_armed) or load_recovery_state(root, run_id) is not None:
129
+ return False
130
+ artifact_id = f"provider-recovery-fault-injection:{run_id}:{phase}"
131
+ directory = root / CONTROL_DIRECTORY
132
+ directory.mkdir(mode=0o700, parents=True, exist_ok=True)
133
+ payload = {
134
+ "kind": "CONTROLLED_PROVIDER_INTERRUPTION",
135
+ "run_id": run_id,
136
+ "phase": phase,
137
+ "consumed_at": _now(),
138
+ }
139
+ try:
140
+ if durable_armed:
141
+ # Claim the ARMED record by atomically turning it into the existing
142
+ # consumed marker. A concurrent disarm therefore cannot erase an
143
+ # already-claimed proof, and a crash cannot permit a refire.
144
+ os.replace(armed, path)
145
+ descriptor = os.open(path, os.O_WRONLY | os.O_TRUNC)
146
+ else:
147
+ descriptor = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
148
+ except FileNotFoundError:
149
+ return False
150
+ except FileExistsError:
151
+ return False
152
+ try:
153
+ with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
154
+ json.dump(payload, handle, sort_keys=True, separators=(",", ":"))
155
+ handle.write("\n")
156
+ handle.flush()
157
+ os.fsync(handle.fileno())
158
+ record_artifact(
159
+ root, path, artifact_id=artifact_id,
160
+ artifact_type="CONTROLLED_PROVIDER_INTERRUPTION", content_type="application/json",
161
+ created_at=str(payload["consumed_at"]), run_id=run_id,
162
+ )
163
+ except Exception:
164
+ # The exclusive marker remains intentionally: after an uncertain
165
+ # qualification-hook write, failing closed is safer than firing twice.
166
+ return False
167
+ return True
168
+
169
+
170
+ def main(argv: list[str] | None = None) -> int:
171
+ parser = argparse.ArgumentParser(description="Manage run-scoped controlled provider interruption proof controls")
172
+ parser.add_argument("command", choices=("arm-controlled-interruption", "controlled-interruption-status", "disarm-controlled-interruption"))
173
+ parser.add_argument("--repo", type=Path, default=Path.cwd())
174
+ parser.add_argument("--run-id", required=True)
175
+ parser.add_argument("--phase", required=True)
176
+ parser.add_argument("--reason")
177
+ args = parser.parse_args(argv)
178
+ root = args.repo.resolve()
179
+ try:
180
+ if args.command == "arm-controlled-interruption":
181
+ payload = arm_controlled_interruption(root, run_id=args.run_id, phase=args.phase, reason=args.reason)
182
+ print(json.dumps({"status": "ARMED", **payload}, sort_keys=True))
183
+ elif args.command == "controlled-interruption-status":
184
+ print(json.dumps({"status": controlled_interruption_status(root, run_id=args.run_id, phase=args.phase), "run_id": args.run_id, "phase": args.phase}, sort_keys=True))
185
+ else:
186
+ print(json.dumps({"status": disarm_controlled_interruption(root, run_id=args.run_id, phase=args.phase), "run_id": args.run_id, "phase": args.phase}, sort_keys=True))
187
+ except ControlledInterruptionControlError as error:
188
+ parser.error(str(error))
189
+ return 0
190
+
191
+
192
+ if __name__ == "__main__":
193
+ raise SystemExit(main())
194
+
195
+
196
+ def load_recovery_state(root: Path, run_id: str, *, central_database: Path | None = None) -> dict[str, object] | None:
197
+ connection = _connection(root, central_database)
198
+ try:
199
+ row = connection.execute(
200
+ "SELECT run_id,recovery_ordinal,maximum_attempts,triggering_invocation_id,replacement_invocation_id,"
201
+ "lifecycle_phase,state,requested_at,launch_claimed_at,process_receipt_id,process_pid,process_group,"
202
+ "provider_session_id,provider_confirmed_active_at,completed_at,result,result_evidence_ref,branch,worktree_identity,lease_id,"
203
+ "fault_injection_consumed_at,diagnostic_code FROM provider_recovery_attempts WHERE run_id=?",
204
+ (run_id,),
205
+ ).fetchone()
206
+ finally:
207
+ connection.close()
208
+ if row is None:
209
+ return None
210
+ keys = ("run_id", "recovery_ordinal", "maximum_attempts", "triggering_invocation_id", "replacement_invocation_id",
211
+ "lifecycle_phase", "state", "requested_at", "launch_claimed_at", "process_receipt_id", "process_pid",
212
+ "process_group", "provider_session_id", "provider_confirmed_active_at", "completed_at", "result", "result_evidence_ref", "branch",
213
+ "worktree_identity", "lease_id", "fault_injection_consumed_at", "diagnostic_code")
214
+ return dict(zip(keys, row, strict=True))
215
+
216
+
217
+ def watcher_resume_action(root: Path, run_id: str) -> str | None:
218
+ """Return the sole watcher action derived from durable recovery evidence.
219
+
220
+ The watcher never allocates an invocation or interprets reports. A host
221
+ resume is allowed only to let the host own the corresponding controller
222
+ transition or consume a durable result.
223
+ """
224
+ recovery = load_recovery_state(root, run_id)
225
+ if recovery is None:
226
+ return None
227
+ if recovery.get("state") == "RECOVERY_IN_PROGRESS":
228
+ # A verified session-bound provider is still owned work, even if its
229
+ # original host has disappeared. The watcher must not start a second
230
+ # host merely to rediscover it.
231
+ if reconcile_recovery(root, run_id=run_id) == "SAME_PROVIDER_STILL_ACTIVE":
232
+ return None
233
+ recovery = load_recovery_state(root, run_id)
234
+ if recovery is None:
235
+ return None
236
+ # A live canonical run lease proves another host still owns this run. The
237
+ # watcher must never race it by launching a second resumed host.
238
+ if lease_liveness(root, run_id).get("state") == "LIVE":
239
+ return None
240
+ state = recovery.get("state")
241
+ if state == "RECOVERED":
242
+ connection = open_storage(root)
243
+ try:
244
+ transaction = connection.execute(
245
+ "SELECT phase FROM engineering_transactions WHERE run_id=?", (run_id,)
246
+ ).fetchone()
247
+ finally:
248
+ connection.close()
249
+ # Result consumption persists the next lifecycle phase before the
250
+ # host returns. A later watcher scan must not consume it again.
251
+ if transaction and transaction[0] != recovery.get("lifecycle_phase"):
252
+ return None
253
+ if state == "RECOVERY_AVAILABLE":
254
+ return "RESUME_AVAILABLE"
255
+ if state == "RECOVERY_STARTING":
256
+ return "RECONCILE_STARTING"
257
+ if state == "RECOVERY_IN_PROGRESS":
258
+ return "RECONCILE_IN_PROGRESS"
259
+ if state == "RECOVERED":
260
+ return "CONSUME_RECOVERED"
261
+ return None
262
+
263
+
264
+ def _receipts(root: Path, run_id: str, invocation_id: str, *, central_database: Path | None = None) -> list[dict[str, object]]:
265
+ connection = _connection(root, central_database)
266
+ try:
267
+ rows = connection.execute(
268
+ "SELECT receipt_id,run_id,invocation_id,launch_state,provider_session_id,process_pid,process_group,"
269
+ "process_start_fingerprint,process_executable_identity,started_at,completed_at,outcome,result_evidence_ref FROM provider_invocation_receipts "
270
+ "WHERE run_id=? AND invocation_id=? ORDER BY started_at,receipt_id",
271
+ (run_id, invocation_id),
272
+ ).fetchall()
273
+ finally:
274
+ connection.close()
275
+ keys = (
276
+ "receipt_id", "run_id", "invocation_id", "launch_state", "provider_session_id", "process_pid", "process_group",
277
+ "process_start_fingerprint", "process_executable_identity", "started_at", "completed_at", "outcome", "result_evidence_ref",
278
+ )
279
+ return [dict(zip(keys, row, strict=True)) for row in rows]
280
+
281
+
282
+ def record_pre_execution_launch_failure(root: Path, *, run_id: str, diagnostic_code: str,
283
+ central_database: Path | None = None) -> bool:
284
+ """Record the only proof that a claimed launch did not reach a provider.
285
+
286
+ The real provider adapter invokes its process callback at the spawn
287
+ boundary. Therefore this may only be written by the host while the row is
288
+ still STARTING and no PROCESS_STARTED receipt exists.
289
+ """
290
+ connection = _connection(root, central_database)
291
+ try:
292
+ connection.execute("BEGIN IMMEDIATE")
293
+ row = connection.execute(
294
+ "SELECT replacement_invocation_id,state,provider_session_id FROM provider_recovery_attempts WHERE run_id=?", (run_id,)
295
+ ).fetchone()
296
+ if row is None or row[1] != "RECOVERY_STARTING":
297
+ connection.execute("ROLLBACK")
298
+ return False
299
+ started = connection.execute(
300
+ "SELECT 1 FROM provider_invocation_receipts WHERE run_id=? AND invocation_id=? "
301
+ "AND launch_state='PROCESS_STARTED'",
302
+ (run_id, row[0]),
303
+ ).fetchone()
304
+ if started:
305
+ connection.execute("ROLLBACK")
306
+ return False
307
+ changed = connection.execute(
308
+ "UPDATE provider_recovery_attempts SET diagnostic_code=? WHERE run_id=? AND state='RECOVERY_STARTING'",
309
+ (f"LAUNCH_NOT_STARTED:{diagnostic_code[:120]}", run_id),
310
+ ).rowcount
311
+ connection.execute("COMMIT")
312
+ return changed == 1
313
+ except Exception:
314
+ connection.execute("ROLLBACK")
315
+ raise
316
+ finally:
317
+ connection.close()
318
+
319
+
320
+ def reconcile_recovery(root: Path, *, run_id: str, verifier=verify_process_identity,
321
+ central_database: Path | None = None) -> str:
322
+ """Classify a restart solely from immutable receipts and recovery state.
323
+
324
+ A live provider is accepted only when its persisted session, birth
325
+ fingerprint and executable identity verify as one invocation-bound unit.
326
+ """
327
+ recovery = load_recovery_state(root, run_id, central_database=central_database)
328
+ if recovery is None:
329
+ return "NOT_APPLICABLE"
330
+ state = recovery.get("state")
331
+ invocation_id = recovery.get("replacement_invocation_id")
332
+ if not isinstance(invocation_id, str):
333
+ mark_ambiguous(root, run_id=run_id, expected=str(state), diagnostic_code="missing_replacement_invocation", central_database=central_database)
334
+ return "AMBIGUOUS"
335
+ receipts = _receipts(root, run_id, invocation_id, central_database=central_database)
336
+ claims = [receipt for receipt in receipts if receipt["launch_state"] == "CLAIMED"]
337
+ starts = [receipt for receipt in receipts if receipt["launch_state"] == "PROCESS_STARTED"]
338
+ terminals = [receipt for receipt in receipts if receipt["launch_state"] == "TERMINAL"]
339
+ outcomes = {str(receipt["outcome"]) for receipt in terminals if receipt["outcome"] is not None}
340
+ if len(claims) > 1 or len(starts) > 1 or len(terminals) > 1 or len(outcomes) > 1:
341
+ if state in {"RECOVERY_STARTING", "RECOVERY_IN_PROGRESS"}:
342
+ mark_ambiguous(root, run_id=run_id, expected=str(state), diagnostic_code="contradictory_receipts", central_database=central_database)
343
+ return "AMBIGUOUS"
344
+ if state == "RECOVERY_STARTING":
345
+ if starts or terminals:
346
+ # Receipt ordering/state mismatch is never repaired by a launch.
347
+ mark_ambiguous(root, run_id=run_id, expected="RECOVERY_STARTING", diagnostic_code="starting_receipt_conflict", central_database=central_database)
348
+ return "AMBIGUOUS"
349
+ if not claims:
350
+ return "LAUNCH_UNCLAIMED"
351
+ diagnostic = str(recovery.get("diagnostic_code") or "")
352
+ if diagnostic.startswith("LAUNCH_NOT_STARTED:"):
353
+ return "LAUNCH_CLAIMED_PREEXEC_FAILURE"
354
+ mark_ambiguous(root, run_id=run_id, expected="RECOVERY_STARTING", diagnostic_code="claimed_launch_unresolved", central_database=central_database)
355
+ return "AMBIGUOUS"
356
+ if state == "RECOVERY_IN_PROGRESS":
357
+ if len(starts) != 1:
358
+ mark_ambiguous(root, run_id=run_id, expected="RECOVERY_IN_PROGRESS", diagnostic_code="missing_process_started_receipt", central_database=central_database)
359
+ return "AMBIGUOUS"
360
+ if not terminals:
361
+ start = starts[0]
362
+ session_id = recovery.get("provider_session_id")
363
+ if (
364
+ not isinstance(session_id, str)
365
+ or start.get("provider_session_id") != session_id
366
+ or not isinstance(start.get("process_pid"), int)
367
+ or not isinstance(start.get("process_group"), int)
368
+ or not isinstance(start.get("process_start_fingerprint"), str)
369
+ or not isinstance(start.get("process_executable_identity"), str)
370
+ ):
371
+ mark_ambiguous(root, run_id=run_id, expected="RECOVERY_IN_PROGRESS", diagnostic_code="provider_session_identity_invalid", central_database=central_database)
372
+ return "AMBIGUOUS"
373
+ verification = verifier(ProcessIdentity(
374
+ pid=int(start["process_pid"]), process_group=int(start["process_group"]),
375
+ start_fingerprint=str(start["process_start_fingerprint"]),
376
+ executable_identity=str(start["process_executable_identity"]),
377
+ ))
378
+ if verification == "MATCH":
379
+ return "SAME_PROVIDER_STILL_ACTIVE"
380
+ mark_ambiguous(
381
+ root, run_id=run_id, expected="RECOVERY_IN_PROGRESS",
382
+ diagnostic_code="provider_process_not_active" if verification == "NOT_ACTIVE" else "provider_process_identity_mismatch",
383
+ central_database=central_database,
384
+ )
385
+ return "AMBIGUOUS"
386
+ outcome = next(iter(outcomes), "")
387
+ if outcome == "SUCCESS" and isinstance(terminals[0].get("result_evidence_ref"), str):
388
+ transition_recovery_state(
389
+ root, run_id=run_id, expected="RECOVERY_IN_PROGRESS", target="RECOVERED",
390
+ result="SUCCESS", result_evidence_ref=str(terminals[0]["result_evidence_ref"]),
391
+ central_database=central_database,
392
+ )
393
+ return "RECOVERED"
394
+ if outcome == "INTERRUPTED":
395
+ transition_recovery_state(root, run_id=run_id, expected="RECOVERY_IN_PROGRESS", target="EXHAUSTED", result="INTERRUPTED", central_database=central_database)
396
+ return "EXHAUSTED"
397
+ mark_ambiguous(root, run_id=run_id, expected="RECOVERY_IN_PROGRESS", diagnostic_code="terminal_failure_or_invalid_result", central_database=central_database)
398
+ return "AMBIGUOUS"
399
+ return str(state)
400
+
401
+
402
+ def create_recovery_available(
403
+ root: Path, *, run_id: str, triggering_invocation_id: str, lifecycle_phase: str,
404
+ branch: str | None, worktree_identity: str, lease_id: str | None,
405
+ central_database: Path | None = None,
406
+ ) -> dict[str, object]:
407
+ """Create the only automatic-recovery budget atomically and idempotently."""
408
+ replacement = f"provider-recovery-{run_id}-{uuid4().hex[:12]}"
409
+ now = _now()
410
+ connection = _connection(root, central_database)
411
+ try:
412
+ connection.execute("BEGIN IMMEDIATE")
413
+ connection.execute(
414
+ "INSERT OR IGNORE INTO provider_recovery_attempts("
415
+ "run_id,recovery_ordinal,maximum_attempts,triggering_invocation_id,replacement_invocation_id,"
416
+ "lifecycle_phase,state,requested_at,branch,worktree_identity,lease_id) VALUES(?,?,?,?,?,?,?,?,?,?,?)",
417
+ (run_id, 1, 1, triggering_invocation_id, replacement, lifecycle_phase,
418
+ "RECOVERY_AVAILABLE", now, branch, worktree_identity, lease_id),
419
+ )
420
+ connection.execute("COMMIT")
421
+ except Exception:
422
+ connection.execute("ROLLBACK")
423
+ raise
424
+ finally:
425
+ connection.close()
426
+ state = load_recovery_state(root, run_id, central_database=central_database)
427
+ if state is None:
428
+ raise EngineeringStorageError("Provider recovery evidence could not be persisted.")
429
+ return state
430
+
431
+
432
+ def transition_recovery_state(
433
+ root: Path, *, run_id: str, expected: str, target: str, diagnostic_code: str | None = None,
434
+ result: str | None = None, result_evidence_ref: str | None = None,
435
+ central_database: Path | None = None,
436
+ ) -> bool:
437
+ """Compare-and-swap one durable recovery transition."""
438
+ if expected not in RECOVERY_STATES or target not in RECOVERY_STATES:
439
+ raise ValueError("invalid provider recovery transition")
440
+ completed = _now() if target in TERMINAL_RECOVERY_STATES else None
441
+ connection = _connection(root, central_database)
442
+ try:
443
+ changed = connection.execute(
444
+ "UPDATE provider_recovery_attempts SET state=?,diagnostic_code=COALESCE(?,diagnostic_code),"
445
+ "result=COALESCE(?,result),result_evidence_ref=COALESCE(?,result_evidence_ref),"
446
+ "completed_at=COALESCE(?,completed_at) WHERE run_id=? AND state=?",
447
+ (target, diagnostic_code, result, result_evidence_ref, completed, run_id, expected),
448
+ ).rowcount
449
+ finally:
450
+ connection.close()
451
+ return changed == 1
452
+
453
+
454
+ def claim_replacement_launch(root: Path, *, run_id: str, central_database: Path | None = None) -> dict[str, object] | None:
455
+ """Claim the exact persisted replacement intent once, before process spawn."""
456
+ now = _now()
457
+ connection = _connection(root, central_database)
458
+ try:
459
+ connection.execute("BEGIN IMMEDIATE")
460
+ row = connection.execute(
461
+ "SELECT replacement_invocation_id,state,provider_session_id FROM provider_recovery_attempts WHERE run_id=?", (run_id,)
462
+ ).fetchone()
463
+ if row is None or row[1] != "RECOVERY_STARTING":
464
+ connection.execute("ROLLBACK")
465
+ return None
466
+ provider_session_id = row[2] or f"provider-session-{uuid4().hex}"
467
+ receipt_id = f"provider-launch-{uuid4().hex}"
468
+ connection.execute(
469
+ "INSERT INTO provider_invocation_receipts(receipt_id,run_id,invocation_id,launch_state,provider_session_id,started_at) VALUES(?,?,?,?,?,?)",
470
+ (receipt_id, run_id, row[0], "CLAIMED", provider_session_id, now),
471
+ )
472
+ changed = connection.execute(
473
+ "UPDATE provider_recovery_attempts SET provider_session_id=?,launch_claimed_at=?,process_receipt_id=? "
474
+ "WHERE run_id=? AND state='RECOVERY_STARTING'",
475
+ (provider_session_id, now, receipt_id, run_id),
476
+ ).rowcount
477
+ if changed != 1:
478
+ connection.execute("ROLLBACK")
479
+ return None
480
+ connection.execute("COMMIT")
481
+ return {"receipt_id": receipt_id, "invocation_id": row[0], "provider_session_id": provider_session_id, "claimed_at": now}
482
+ except sqlite3.IntegrityError:
483
+ connection.execute("ROLLBACK")
484
+ return None
485
+ except Exception:
486
+ connection.execute("ROLLBACK")
487
+ raise
488
+ finally:
489
+ connection.close()
490
+
491
+
492
+ def record_provider_started(
493
+ root: Path, *, run_id: str, receipt_id: str, pid: int, process_group: int,
494
+ identity: ProcessIdentity | None = None, central_database: Path | None = None,
495
+ ) -> bool:
496
+ """Append immutable process-start evidence and enter IN_PROGRESS."""
497
+ now = _now()
498
+ connection = _connection(root, central_database)
499
+ try:
500
+ connection.execute("BEGIN IMMEDIATE")
501
+ row = connection.execute(
502
+ "SELECT replacement_invocation_id,state,provider_session_id FROM provider_recovery_attempts WHERE run_id=?", (run_id,)
503
+ ).fetchone()
504
+ if row is None or row[1] != "RECOVERY_STARTING" or not isinstance(row[2], str):
505
+ connection.execute("ROLLBACK")
506
+ return False
507
+ identity = identity or capture_process_identity(pid, process_group)
508
+ if identity is None:
509
+ connection.execute("ROLLBACK")
510
+ return False
511
+ connection.execute(
512
+ "INSERT INTO provider_invocation_receipts(receipt_id,run_id,invocation_id,launch_state,provider_session_id,process_pid,process_group,process_start_fingerprint,process_executable_identity,started_at) VALUES(?,?,?,?,?,?,?,?,?,?)",
513
+ (f"provider-start-{uuid4().hex}", run_id, row[0], "PROCESS_STARTED", row[2], pid, process_group,
514
+ identity.start_fingerprint, identity.executable_identity, now),
515
+ )
516
+ changed = connection.execute(
517
+ "UPDATE provider_recovery_attempts SET state='RECOVERY_IN_PROGRESS',process_receipt_id=?,process_pid=?,process_group=?,provider_confirmed_active_at=? WHERE run_id=? AND state='RECOVERY_STARTING'",
518
+ (receipt_id, pid, process_group, now, run_id),
519
+ ).rowcount
520
+ connection.execute("COMMIT")
521
+ return changed == 1
522
+ except Exception:
523
+ connection.execute("ROLLBACK")
524
+ raise
525
+ finally:
526
+ connection.close()
527
+
528
+
529
+ def record_replacement_terminal(
530
+ root: Path, *, run_id: str, outcome: str, result_evidence_ref: str | None = None,
531
+ central_database: Path | None = None,
532
+ ) -> bool:
533
+ """Append terminal provider evidence once and advance the recovery state.
534
+
535
+ ``SUCCESS`` requires a separately persisted structured-result artifact.
536
+ Interrupted attempt two is exhaustion; any unknown outcome is deliberately
537
+ ambiguous instead of becoming another launch opportunity.
538
+ """
539
+ target = {
540
+ "SUCCESS": "RECOVERED",
541
+ "INTERRUPTED": "EXHAUSTED",
542
+ "FAILED": "AMBIGUOUS",
543
+ }.get(outcome)
544
+ if target is None:
545
+ raise ValueError("invalid provider recovery terminal outcome")
546
+ now = _now()
547
+ connection = _connection(root, central_database)
548
+ try:
549
+ connection.execute("BEGIN IMMEDIATE")
550
+ row = connection.execute(
551
+ "SELECT replacement_invocation_id,state,provider_session_id FROM provider_recovery_attempts WHERE run_id=?", (run_id,)
552
+ ).fetchone()
553
+ if row is None or row[1] != "RECOVERY_IN_PROGRESS":
554
+ connection.execute("ROLLBACK")
555
+ return False
556
+ connection.execute(
557
+ "INSERT INTO provider_invocation_receipts(receipt_id,run_id,invocation_id,launch_state,provider_session_id,started_at,completed_at,outcome,result_evidence_ref) VALUES(?,?,?,?,?,?,?,?,?)",
558
+ (f"provider-terminal-{uuid4().hex}", run_id, row[0], "TERMINAL", row[2], now, now, outcome, result_evidence_ref),
559
+ )
560
+ changed = connection.execute(
561
+ "UPDATE provider_recovery_attempts SET state=?,completed_at=?,result=?,result_evidence_ref=? WHERE run_id=? AND state='RECOVERY_IN_PROGRESS'",
562
+ (target, now, outcome, result_evidence_ref, run_id),
563
+ ).rowcount
564
+ connection.execute("COMMIT")
565
+ return changed == 1
566
+ except Exception:
567
+ connection.execute("ROLLBACK")
568
+ raise
569
+ finally:
570
+ connection.close()
571
+
572
+
573
+ def mark_precheck_failed(root: Path, *, run_id: str, diagnostic_code: str,
574
+ central_database: Path | None = None) -> bool:
575
+ return transition_recovery_state(
576
+ root, run_id=run_id, expected="RECOVERY_AVAILABLE", target="PRECHECK_FAILED",
577
+ diagnostic_code=diagnostic_code, central_database=central_database,
578
+ )
579
+
580
+
581
+ def mark_ambiguous(root: Path, *, run_id: str, expected: str, diagnostic_code: str,
582
+ central_database: Path | None = None) -> bool:
583
+ return transition_recovery_state(
584
+ root, run_id=run_id, expected=expected, target="AMBIGUOUS", diagnostic_code=diagnostic_code,
585
+ central_database=central_database,
586
+ )
587
+
588
+
589
+ def persist_recovery_agent_result(root: Path, *, run_id: str, invocation_id: str, result: object,
590
+ central_database: Path | None = None, artifact_root: Path | None = None) -> str:
591
+ """Persist the bounded structured AgentResult once for post-crash consumption."""
592
+ fields = ("terminal_state", "branch", "pull_request", "terminal_condition", "diagnostic", "repository_path", "commit_sha", "validation_evidence", "quality_evidence", "validation_disposition")
593
+ payload = {field: getattr(result, field) for field in fields}
594
+ artifact_id = f"provider-recovery-result:{run_id}:{invocation_id}"
595
+ directory = ((artifact_root / "provider-recovery-results") if artifact_root else
596
+ (root / ".engineering" / "artifacts" / "provider-recovery-results"))
597
+ directory.mkdir(mode=0o700, parents=True, exist_ok=True)
598
+ path = directory / f"{invocation_id}.json"
599
+ descriptor, temporary = tempfile.mkstemp(prefix=f".{invocation_id}.", suffix=".tmp", dir=directory)
600
+ try:
601
+ os.fchmod(descriptor, 0o600)
602
+ with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
603
+ json.dump(payload, handle, sort_keys=True, separators=(",", ":"))
604
+ handle.write("\n")
605
+ handle.flush()
606
+ os.fsync(handle.fileno())
607
+ os.replace(temporary, path)
608
+ finally:
609
+ Path(temporary).unlink(missing_ok=True)
610
+ record_artifact(
611
+ root, path, artifact_id=artifact_id, artifact_type="PROVIDER_RECOVERY_AGENT_RESULT",
612
+ content_type="application/json", created_at=_now(), run_id=run_id, execution_id=invocation_id,
613
+ central_database=central_database, artifact_root=artifact_root,
614
+ )
615
+ return f"artifact:{artifact_id}"
616
+
617
+
618
+ def load_recovery_agent_result(
619
+ root: Path, reference: str, *, run_id: str, invocation_id: str,
620
+ central_database: Path | None = None, artifact_root: Path | None = None,
621
+ ) -> dict[str, object] | None:
622
+ if not reference.startswith("artifact:"):
623
+ return None
624
+ artifact_id = reference.removeprefix("artifact:")
625
+ if not verify_artifact_integrity(
626
+ root, artifact_id, central_database=central_database, artifact_root=artifact_root,
627
+ ):
628
+ return None
629
+ connection = _connection(root, central_database)
630
+ try:
631
+ row = connection.execute(
632
+ "SELECT storage_location,artifact_type FROM execution_artifact_records "
633
+ "WHERE artifact_id=? AND execution_id=? AND (run_id=? OR run_id IS NULL)",
634
+ (artifact_id, invocation_id, run_id),
635
+ ).fetchone()
636
+ finally:
637
+ connection.close()
638
+ if not row or row[1] != "PROVIDER_RECOVERY_AGENT_RESULT":
639
+ return None
640
+ try:
641
+ authority_root = artifact_root.resolve() if artifact_root is not None else (root / ".engineering").resolve()
642
+ payload_path = (authority_root / str(row[0])).resolve()
643
+ payload_path.relative_to(authority_root)
644
+ payload = json.loads(payload_path.read_text(encoding="utf-8"))
645
+ except (OSError, ValueError, json.JSONDecodeError):
646
+ return None
647
+ return payload if isinstance(payload, dict) else None