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,79 @@
1
+ """One-shot migration of historical iCloud Inbox archives.
2
+
3
+ This tool is migration-only: it moves retained evidence into the current
4
+ archive layout and never admits, queues, dispatches or executes work.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import json
10
+ import os
11
+ from pathlib import Path
12
+ import shutil
13
+
14
+
15
+ def _move(source: Path, destination: Path) -> None:
16
+ destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
17
+ try:
18
+ os.replace(source, destination)
19
+ except OSError:
20
+ shutil.move(str(source), str(destination))
21
+
22
+
23
+ def migrate_icloud_archives(repo: Path, root: Path) -> dict[str, int]:
24
+ """Move historical archive evidence, leaving no runtime transport behind."""
25
+ targets = {
26
+ "Running": repo / ".engineering" / "inbox" / "Running",
27
+ "Completed": repo / ".engineering" / "inbox" / "Completed",
28
+ "Failed": repo / ".engineering" / "inbox" / "Failed",
29
+ "Reports": repo / ".engineering" / "reports",
30
+ }
31
+ moved = deleted = 0
32
+ for name, target in targets.items():
33
+ source_directory = root / name
34
+ if not source_directory.is_dir():
35
+ continue
36
+ target.mkdir(mode=0o700, parents=True, exist_ok=True)
37
+ for source in source_directory.iterdir():
38
+ if source.is_symlink() or not source.is_file():
39
+ continue
40
+ destination = target / source.name
41
+ if destination.exists():
42
+ source.unlink()
43
+ deleted += 1
44
+ else:
45
+ _move(source, destination)
46
+ moved += 1
47
+ # A skipped symlink or non-file is intentionally retained as
48
+ # historical evidence. It must not turn a safe archive migration
49
+ # into a partial failure merely because the source directory is no
50
+ # longer empty.
51
+ try:
52
+ source_directory.rmdir()
53
+ except OSError:
54
+ pass
55
+ for name in ("status.json", "status.md"):
56
+ source, destination = root / name, repo / ".engineering" / "status" / name
57
+ if not source.is_file() or source.is_symlink():
58
+ continue
59
+ destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
60
+ if destination.exists():
61
+ source.unlink()
62
+ deleted += 1
63
+ else:
64
+ _move(source, destination)
65
+ moved += 1
66
+ return {"moved": moved, "deleted_duplicates": deleted}
67
+
68
+
69
+ def main(argv: list[str] | None = None) -> int:
70
+ parser = argparse.ArgumentParser()
71
+ parser.add_argument("--repo", type=Path, required=True)
72
+ parser.add_argument("--icloud-root", type=Path, required=True)
73
+ args = parser.parse_args(argv)
74
+ print(json.dumps(migrate_icloud_archives(args.repo.resolve(), args.icloud_root.resolve()), sort_keys=True))
75
+ return 0
76
+
77
+
78
+ if __name__ == "__main__":
79
+ raise SystemExit(main())
@@ -0,0 +1,223 @@
1
+ """Installed CENTRAL lifecycle-worker composition.
2
+
3
+ The worker deliberately has a very small authority: it observes CENTRAL's
4
+ durable submission records and asks :class:`ParityLifecycleDispatcher` to
5
+ continue one eligible item. Claiming, run identity, historical admission,
6
+ recovery, provider invocation and finalization remain in that dispatcher and
7
+ the preserved EngineeringRunner.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ import sqlite3
13
+ from threading import Event, Lock, Thread
14
+ from time import monotonic
15
+ from typing import Callable, Protocol
16
+
17
+ from .parity_lifecycle_dispatcher import ParityLifecycleDispatcher
18
+ from . import central_database
19
+
20
+
21
+ WORKER_RUNNING = "RUNNING"
22
+ WORKER_STOPPED = "STOPPED"
23
+ WORKER_DEGRADED = "DEGRADED"
24
+ OPERATOR_MERGE_RESUME_SECONDS = 60.0
25
+
26
+
27
+ class Dispatcher(Protocol):
28
+ def dispatch(self, submission_id: str) -> object: ...
29
+ def reconcile_terminal_history(self) -> None: ...
30
+
31
+
32
+ DispatcherFactory = Callable[[], Dispatcher]
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class LifecycleWorkerDiagnostics:
37
+ state: str
38
+ observed: int
39
+ dispatched: int
40
+ failures: int
41
+ last_submission_id: str | None
42
+ last_error: str | None
43
+
44
+ def to_dict(self) -> dict[str, object]:
45
+ return {
46
+ "state": self.state,
47
+ "observed": self.observed,
48
+ "dispatched": self.dispatched,
49
+ "failures": self.failures,
50
+ "last_submission_id": self.last_submission_id,
51
+ "last_error": self.last_error,
52
+ }
53
+
54
+
55
+ class LifecycleWorker:
56
+ """One installation-owned observer with one active lifecycle per project."""
57
+
58
+ def __init__(self, data_root, *, dispatcher_factory: DispatcherFactory | None = None,
59
+ idle_seconds: float = 0.25, failure_seconds: float = 1.0) -> None:
60
+ self.data_root = data_root.resolve()
61
+ self._dispatcher_factory = dispatcher_factory or (lambda: ParityLifecycleDispatcher(self.data_root))
62
+ self._idle_seconds = idle_seconds
63
+ self._failure_seconds = failure_seconds
64
+ self._stop = Event()
65
+ self._ready = Event()
66
+ self._lock = Lock()
67
+ self._thread: Thread | None = None
68
+ self._inflight: set[str] = set()
69
+ self._next_merge_resume_at: dict[str, float] = {}
70
+ self._diagnostics = LifecycleWorkerDiagnostics(WORKER_STOPPED, 0, 0, 0, None, None)
71
+
72
+ def diagnostics(self) -> LifecycleWorkerDiagnostics:
73
+ with self._lock:
74
+ return self._diagnostics
75
+
76
+ def _replace(self, **changes: object) -> None:
77
+ with self._lock:
78
+ values = self._diagnostics.to_dict()
79
+ values.update(changes)
80
+ self._diagnostics = LifecycleWorkerDiagnostics(**values) # type: ignore[arg-type]
81
+
82
+ def eligible_submission_ids(self) -> list[str]:
83
+ """Return one FIFO candidate per project; claims remain dispatcher-owned."""
84
+ with sqlite3.connect(self.data_root / "engineering.db") as connection:
85
+ rows = connection.execute("""SELECT s.submission_id,s.project_id,d.state,d.claimed_at,s.created_at
86
+ FROM ep_submissions s
87
+ LEFT JOIN ep_parity_lifecycle_dispatches d ON d.submission_id=s.submission_id
88
+ WHERE s.state='QUEUED' AND s.admission='ADMITTED'
89
+ AND (
90
+ d.submission_id IS NULL OR d.state IN ('CLAIMED','RUNNING')
91
+ OR (
92
+ d.state='BLOCKED' AND d.operator_resolution='OPEN'
93
+ AND EXISTS (
94
+ SELECT 1 FROM engineering_transactions wait_state
95
+ WHERE wait_state.run_id=d.run_id
96
+ AND wait_state.phase='WAIT_FOR_OPERATOR_MERGE'
97
+ AND COALESCE(json_extract(wait_state.payload, '$.terminal'), 0) IN (0, 'false')
98
+ )
99
+ )
100
+ )
101
+ AND NOT EXISTS (
102
+ SELECT 1 FROM ep_parity_lifecycle_dispatches prior
103
+ WHERE prior.project_id=s.project_id AND (
104
+ (prior.state IN ('CLAIMED','RUNNING') AND prior.submission_id!=s.submission_id)
105
+ OR (prior.state IN ('BLOCKED','FAILED') AND prior.operator_resolution='OPEN'
106
+ AND prior.submission_id!=s.submission_id)
107
+ OR (prior.operator_resolution='RETRIED' AND prior.resolution_submission_id!=s.submission_id)
108
+ )
109
+ )
110
+ ORDER BY s.project_id,
111
+ CASE WHEN d.state IN ('CLAIMED','RUNNING') THEN 0 ELSE 1 END,
112
+ COALESCE(d.claimed_at,s.created_at),s.created_at,s.submission_id""").fetchall()
113
+ candidates: list[str] = []
114
+ projects: set[str] = set()
115
+ for submission_id, project_id, _state, _claimed_at, _created_at in rows:
116
+ if str(project_id) in projects:
117
+ continue
118
+ projects.add(str(project_id))
119
+ candidates.append(str(submission_id))
120
+ return candidates
121
+
122
+ def _dispatch(self, submission_id: str) -> None:
123
+ try:
124
+ self._dispatcher_factory().dispatch(submission_id)
125
+ except Exception as error: # Dispatcher persists its own terminal/recovery boundary.
126
+ current = self.diagnostics()
127
+ self._replace(state=WORKER_DEGRADED, failures=current.failures + 1,
128
+ last_error=type(error).__name__)
129
+ else:
130
+ current = self.diagnostics()
131
+ self._replace(state=WORKER_RUNNING, dispatched=current.dispatched + 1)
132
+ finally:
133
+ with self._lock:
134
+ self._inflight.discard(submission_id)
135
+
136
+ def run_once(self) -> bool:
137
+ with self._lock:
138
+ inflight = frozenset(self._inflight)
139
+ now = monotonic()
140
+ candidates = [
141
+ submission_id for submission_id in self.eligible_submission_ids()
142
+ if submission_id not in inflight
143
+ and now >= self._next_merge_resume_at.get(submission_id, 0.0)
144
+ ]
145
+ if not candidates:
146
+ return False
147
+ for submission_id in candidates:
148
+ current = self.diagnostics()
149
+ self._replace(observed=current.observed + 1, last_submission_id=submission_id, last_error=None)
150
+ with self._lock:
151
+ self._inflight.add(submission_id)
152
+ # A nonterminal merge wait returns quickly after its one
153
+ # authoritative remote poll. Keep that same canonical run
154
+ # resumable, but never race it with another local lease
155
+ # acquisition before the next bounded poll window.
156
+ self._next_merge_resume_at[submission_id] = now + OPERATOR_MERGE_RESUME_SECONDS
157
+ Thread(
158
+ target=self._dispatch,
159
+ args=(submission_id,),
160
+ name=f"engineering-platform-lifecycle-{submission_id[:12]}",
161
+ daemon=True,
162
+ ).start()
163
+ return True
164
+
165
+ def _loop(self) -> None:
166
+ self._replace(state=WORKER_RUNNING)
167
+ self._ready.set()
168
+ while not self._stop.is_set():
169
+ # CENTRAL is the sole operational store. Its maintenance routine
170
+ # is interval-bound and skips every active lifecycle.
171
+ central_database.run_periodic_maintenance(self.data_root)
172
+ self.run_once()
173
+ # Even a successful nonterminal continuation gets a short yield:
174
+ # recovery must never become a busy loop if the historical runner
175
+ # deliberately returns a resumable checkpoint.
176
+ self._stop.wait(self._failure_seconds if self.diagnostics().state == WORKER_DEGRADED else self._idle_seconds)
177
+ self._replace(state=WORKER_STOPPED)
178
+
179
+ def _reconcile_terminal_history(self) -> None:
180
+ """Backfill historical Console rows without delaying Server readiness."""
181
+ reconcile = getattr(self._dispatcher_factory(), "reconcile_terminal_history", None)
182
+ if not callable(reconcile):
183
+ return
184
+ try:
185
+ reconcile()
186
+ except Exception as error:
187
+ # Terminal-history projection is additive only. A stale retained
188
+ # row must not prevent the HTTP Server from accepting fresh
189
+ # CENTRAL submissions or prevent the worker from servicing a
190
+ # project queue.
191
+ current = self.diagnostics()
192
+ self._replace(failures=current.failures + 1, last_error=type(error).__name__)
193
+
194
+ def start(self) -> None:
195
+ if self._thread is not None and self._thread.is_alive():
196
+ return
197
+ self._stop.clear()
198
+ self._ready.clear()
199
+ self._thread = Thread(target=self._loop, name="engineering-platform-lifecycle-worker", daemon=True)
200
+ self._thread.start()
201
+ # CENTRAL owns claims; the preserved Console owns terminal evidence.
202
+ # Historical projection is additive, so it must never make Server
203
+ # readiness depend on an old report or a retained stale row.
204
+ Thread(
205
+ target=self._reconcile_terminal_history,
206
+ name="engineering-platform-terminal-history-reconciliation",
207
+ daemon=True,
208
+ ).start()
209
+
210
+ def wait_until_running(self, timeout: float = 5.0) -> bool:
211
+ """Wait for the child loop's real readiness transition.
212
+
213
+ Server-owned producers depend on lifecycle admission being available;
214
+ this is a composition boundary, not a timing heuristic.
215
+ """
216
+ return self._ready.wait(timeout) and self.diagnostics().state == WORKER_RUNNING
217
+
218
+ def stop(self, timeout: float = 2.0) -> None:
219
+ self._stop.set()
220
+ if self._thread is not None:
221
+ self._thread.join(timeout)
222
+ if self._thread is None or not self._thread.is_alive():
223
+ self._replace(state=WORKER_STOPPED)
@@ -0,0 +1,267 @@
1
+ """Atomic local status projection for the foreground engineering runner."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timezone
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ import tempfile
10
+ from typing import Mapping
11
+
12
+ from .agent_state import TransactionState, redact_diagnostic
13
+ from .storage import EngineeringStorageError, load_execution_context_snapshot, load_forge_governance_handoff_snapshot, load_projection, open_storage, store_projection
14
+ from .providers import GitProvider
15
+ from .execution_activity import cumulative_activity, live_worktree_snapshot
16
+
17
+ _REVIEWER_PROJECTION_PHASE = "CAPABILITY_REVIEW"
18
+
19
+
20
+ def _successful_reviewer_agents(value: object) -> list[dict[str, object]]:
21
+ """Return immutable review evidence only after every reviewer succeeded."""
22
+ if not isinstance(value, list):
23
+ return []
24
+ reviewers = [item for item in value if isinstance(item, dict)]
25
+ if not reviewers or len(reviewers) != len(value):
26
+ return []
27
+ return reviewers if all(item.get("status") == "completed" for item in reviewers) else []
28
+
29
+
30
+ def write_live_status(
31
+ root: Path,
32
+ state: TransactionState,
33
+ action: str,
34
+ reviewer_agents: list[dict[str, object]] | None = None,
35
+ runtime_metadata: Mapping[str, str] | None = None,
36
+ workspace_progress: Mapping[str, int] | None = None,
37
+ transient_action: str | None = None,
38
+ ) -> Path:
39
+ """Atomically publish the advisory current transaction state."""
40
+ directory = root / ".engineering" / "status"
41
+ directory.mkdir(mode=0o700, parents=True, exist_ok=True)
42
+ path = directory / "current.json"
43
+ checkout = (
44
+ Path(state.genesis_repository_path).expanduser()
45
+ if state.execution_mode == "GENESIS" and state.genesis_repository_path
46
+ else root
47
+ )
48
+ try:
49
+ observed_branch = GitProvider().execute(checkout, "git", "branch", "--show-current").stdout.strip()
50
+ except OSError:
51
+ observed_branch = ""
52
+ try:
53
+ prompt_characters = len(Path(state.prompt_path).read_text(encoding="utf-8"))
54
+ except OSError:
55
+ prompt_characters = None
56
+ previous_reviewers: list[dict[str, object]] = []
57
+ previous_runtime: dict[str, str] = {}
58
+ previous_transient_action: str | None = None
59
+ previous_recovery: dict[str, object] | None = None
60
+ try:
61
+ previous_file = json.loads(path.read_text(encoding="utf-8"))
62
+ if previous_file.get("run_id") == state.run_id and isinstance(previous_file.get("transient_action"), str):
63
+ previous_transient_action = redact_diagnostic(previous_file["transient_action"], limit=160)
64
+ except (OSError, json.JSONDecodeError):
65
+ pass
66
+ try:
67
+ previous = load_projection(root, "live_status") or {}
68
+ except EngineeringStorageError:
69
+ # A live operation must not publish a filesystem-only state if the
70
+ # canonical store is unavailable.
71
+ raise
72
+ reviewer_projection_active = state.phase == _REVIEWER_PROJECTION_PHASE
73
+ if previous.get("run_id") == state.run_id:
74
+ stored_reviewers = previous.get("reviewer_agents")
75
+ if (
76
+ reviewer_projection_active
77
+ and reviewer_agents is None
78
+ and isinstance(stored_reviewers, list)
79
+ ):
80
+ previous_reviewers = [item for item in stored_reviewers if isinstance(item, dict)]
81
+ elif not reviewer_projection_active:
82
+ # Once the review is wholly successful, retain its compact,
83
+ # completed result as historical evidence for this live run. A
84
+ # partial, failed or malformed projection is never carried into a
85
+ # later lifecycle phase.
86
+ previous_reviewers = _successful_reviewer_agents(stored_reviewers)
87
+ if runtime_metadata is None and isinstance(previous.get("runtime_metadata"), dict):
88
+ previous_runtime = {
89
+ key: value[:120]
90
+ for key, value in previous["runtime_metadata"].items()
91
+ if key in {"runtime_provider", "model", "reasoning_profile", "configuration_profile", "codex_cli_installation_path"}
92
+ and isinstance(value, str)
93
+ }
94
+ if isinstance(previous.get("workspace_recovery"), dict):
95
+ previous_recovery = dict(previous["workspace_recovery"])
96
+ safe_runtime = previous_runtime if runtime_metadata is None else {
97
+ key: value[:120]
98
+ for key, value in runtime_metadata.items()
99
+ if key in {"runtime_provider", "model", "reasoning_profile", "configuration_profile", "codex_cli_installation_path"}
100
+ and isinstance(value, str)
101
+ }
102
+ previous_progress = previous.get("workspace_progress") if previous.get("run_id") == state.run_id else None
103
+ safe_progress = previous_progress if workspace_progress is None else workspace_progress
104
+ if not isinstance(safe_progress, Mapping):
105
+ safe_progress = {"modified": 0, "created": 0, "deleted": 0}
106
+ safe_progress = {
107
+ key: max(0, int(safe_progress.get(key, 0)))
108
+ for key in ("modified", "created", "deleted", "codex_commands_executed")
109
+ if isinstance(safe_progress.get(key, 0), int)
110
+ }
111
+ if previous_recovery is None:
112
+ try:
113
+ provider = GitProvider()
114
+ head_result = provider.execute(checkout, "git", "rev-parse", "HEAD")
115
+ status_result = provider.execute(checkout, "git", "status", "--porcelain", "--untracked-files=all")
116
+ branches_result = provider.execute(checkout, "git", "for-each-ref", "--format=%(refname:short)", "refs/heads")
117
+ if head_result.returncode or status_result.returncode or branches_result.returncode:
118
+ raise OSError("Git recovery baseline is unavailable")
119
+ baseline_head = head_result.stdout.strip()
120
+ baseline_status = status_result.stdout.strip()
121
+ branches = branches_result.stdout.splitlines()
122
+ previous_recovery = {
123
+ "baseline_branch": observed_branch,
124
+ "baseline_head": baseline_head,
125
+ "baseline_clean": not baseline_status,
126
+ "preexisting_branches": [branch for branch in branches if branch],
127
+ }
128
+ except OSError:
129
+ previous_recovery = {"baseline_clean": False}
130
+ terminal_phase = state.phase in {"COMPLETE", "BLOCKED", "FAILED"}
131
+ # SQLite recovery evidence is authoritative across host restarts. The
132
+ # checkpoint ledger is retained only as a compatibility projection.
133
+ try:
134
+ from .provider_recovery import load_recovery_state
135
+ recovery_record = load_recovery_state(root, state.run_id)
136
+ except EngineeringStorageError:
137
+ recovery_record = None
138
+ if recovery_record is None:
139
+ recovery_record = state.provider_recovery_attempts[0] if state.provider_recovery_attempts else None
140
+ recovery_result = recovery_record.get("result") if isinstance(recovery_record, dict) else None
141
+ recovery_state = recovery_record.get("state") if isinstance(recovery_record, dict) else None
142
+ provider_recovery = {
143
+ "state": (
144
+ "RECOVERING" if recovery_state in {"RECOVERY_AVAILABLE", "RECOVERY_STARTING", "RECOVERY_IN_PROGRESS"} or recovery_result == "ACTIVE" else
145
+ "RECOVERED" if recovery_state == "RECOVERED" or recovery_result == "RECOVERED" else
146
+ "EXHAUSTED" if recovery_state == "EXHAUSTED" or recovery_result == "INTERRUPTED_AGAIN" else
147
+ "NOT_APPLICABLE"
148
+ ),
149
+ "automatic_recovery_attempt": "1/1" if recovery_record else "0/1",
150
+ "trigger": (recovery_record.get("classification") or "provider_turn_interrupted") if isinstance(recovery_record, dict) else None,
151
+ "original_provider_invocation": (recovery_record.get("triggering_invocation_id") or recovery_record.get("original_invocation_id")) if isinstance(recovery_record, dict) else None,
152
+ "replacement_provider_invocation": recovery_record.get("replacement_invocation_id") if isinstance(recovery_record, dict) else None,
153
+ "result": recovery_result,
154
+ }
155
+ payload = {
156
+ "run_id": state.run_id,
157
+ "phase": state.phase,
158
+ "current_action": redact_diagnostic(action),
159
+ "objective": state.prompt_path,
160
+ "implementation_pr": state.implementation_pull_request,
161
+ "finalization_pr": state.finalization_pull_request,
162
+ "reconciliation_pr": state.reconciliation_pull_request,
163
+ "pull_request": state.pull_request,
164
+ "waiting_for_merge_since": state.waiting_for_merge_since,
165
+ "repair_iteration": state.repair_iterations,
166
+ "repository_state": "MERGED_RECONCILED" if state.phase == "COMPLETE" else state.phase if terminal_phase else "ACTIVE",
167
+ "workspace_state": "WORKSPACE_READY" if state.phase == "COMPLETE" else "TERMINAL" if terminal_phase else "ACTIVE",
168
+ "last_update": datetime.now(timezone.utc).isoformat(),
169
+ "elapsed_seconds": 0,
170
+ "prompt_characters": prompt_characters,
171
+ "diagnostic": state.diagnostic,
172
+ "resume_command": None if terminal_phase else f"engineering-execution-host {state.prompt_path} --run-id {state.run_id} --resume",
173
+ "execution_mode": state.execution_mode,
174
+ "target_repository": checkout.name if state.execution_mode == "GENESIS" else state.repository,
175
+ "checkout_path": str(checkout),
176
+ "active_branch": observed_branch or state.branch or "unavailable",
177
+ # Reviewer progress is phase-scoped, while a fully successful review
178
+ # remains compact historical evidence during the rest of this active
179
+ # run. The dashboard renders that retained list as completed, never as
180
+ # live work.
181
+ "reviewer_agents": (
182
+ reviewer_agents if reviewer_projection_active and reviewer_agents is not None
183
+ else previous_reviewers
184
+ ),
185
+ "runtime_metadata": safe_runtime,
186
+ "workspace_progress": safe_progress,
187
+ # This is intentionally absent from a terminal receipt/report. It is
188
+ # a current dirty-worktree observation, not delivery evidence.
189
+ "live_worktree_snapshot": None if terminal_phase else live_worktree_snapshot(checkout),
190
+ "cumulative_activity": cumulative_activity(root, state.run_id),
191
+ # A recovery baseline is captured once, after admission has proven the
192
+ # workspace clean. It allows the emergency control to fail closed when
193
+ # a run has commits, a pre-existing branch, or an unknown base.
194
+ "workspace_recovery": previous_recovery,
195
+ "provider_recovery": provider_recovery,
196
+ }
197
+ transient = None if state.terminal or terminal_phase else transient_action or previous_transient_action
198
+ try:
199
+ payload["execution_context"] = load_execution_context_snapshot(root, state.run_id)
200
+ payload["forge_governance_handoff"] = load_forge_governance_handoff_snapshot(root, state.run_id)
201
+ except EngineeringStorageError:
202
+ payload["execution_context"] = None
203
+ payload["forge_governance_handoff"] = None
204
+ connection = open_storage(root)
205
+ try:
206
+ store_projection(connection, "live_status", payload)
207
+ finally:
208
+ connection.close()
209
+ # This action title is intentionally absent from the stored projection.
210
+ # It is a short-lived UI hint sourced from Codex reasoning metadata only.
211
+ file_payload = dict(payload)
212
+ if transient:
213
+ file_payload["transient_action"] = transient
214
+ descriptor, temporary = tempfile.mkstemp(prefix=".current.", suffix=".tmp", dir=directory)
215
+ try:
216
+ with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
217
+ json.dump(file_payload, handle, indent=2, sort_keys=True)
218
+ handle.write("\n")
219
+ handle.flush()
220
+ os.fsync(handle.fileno())
221
+ os.replace(temporary, path)
222
+ finally:
223
+ Path(temporary).unlink(missing_ok=True)
224
+ return path
225
+
226
+
227
+ def print_live_status(root: Path) -> int:
228
+ try:
229
+ current = load_projection(root, "live_status")
230
+ except EngineeringStorageError:
231
+ print("Current engineering status is unavailable.")
232
+ return 2
233
+ if current is None:
234
+ print("No active engineering status is available.")
235
+ return 1
236
+ print(
237
+ f"Run:\n{current['run_id']}\n\nCurrent Phase:\n{current['phase']}\n\nImplementation PR:\n{current['implementation_pr']}\n\nRepair Iteration:\n{current['repair_iteration']}\n\nCurrent Action:\n{current['current_action']}\n\nElapsed:\n{current['elapsed_seconds']}s"
238
+ )
239
+ return 0
240
+
241
+
242
+ def write_runner_process(root: Path, run_id: str, process: Mapping[str, int] | None) -> None:
243
+ """Atomically record only the Execution Host-owned Codex process group."""
244
+ directory = root / ".engineering" / "status"
245
+ directory.mkdir(mode=0o700, parents=True, exist_ok=True)
246
+ path = directory / "runner_process.json"
247
+ if process is None:
248
+ try:
249
+ recorded = json.loads(path.read_text(encoding="utf-8"))
250
+ except (OSError, json.JSONDecodeError):
251
+ recorded = {}
252
+ if recorded.get("run_id") == run_id:
253
+ path.unlink(missing_ok=True)
254
+ return
255
+ pid, process_group = process.get("pid"), process.get("process_group")
256
+ if not isinstance(pid, int) or pid <= 0 or not isinstance(process_group, int) or process_group <= 0:
257
+ return
258
+ descriptor, temporary = tempfile.mkstemp(prefix=".runner-process.", suffix=".tmp", dir=directory)
259
+ try:
260
+ with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
261
+ json.dump({"run_id": run_id, "pid": pid, "process_group": process_group}, handle, sort_keys=True)
262
+ handle.write("\n")
263
+ handle.flush()
264
+ os.fsync(handle.fileno())
265
+ os.replace(temporary, path)
266
+ finally:
267
+ Path(temporary).unlink(missing_ok=True)