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.
- engineering_platform/ENGINEERING_PLATFORM_CONFIG.json +32 -0
- engineering_platform/ENGINEERING_PLATFORM_VERSION.json +15 -0
- engineering_platform/__init__.py +1 -0
- engineering_platform/__main__.py +7 -0
- engineering_platform/agent_state.py +530 -0
- engineering_platform/agent_trust.py +174 -0
- engineering_platform/assets/dashboard.css +1317 -0
- engineering_platform/assets/dashboard.js +8534 -0
- engineering_platform/assets/dashboard_locales.mjs +4049 -0
- engineering_platform/assets/dashboard_status_store.mjs +41 -0
- engineering_platform/assets/operations-console/apple-touch-icon-dark.png +0 -0
- engineering_platform/assets/operations-console/apple-touch-icon-light.png +0 -0
- engineering_platform/assets/operations-console/icon-dark.png +0 -0
- engineering_platform/assets/operations-console/icon-light.png +0 -0
- engineering_platform/assets/operations-console/icon-transparent.png +0 -0
- engineering_platform/assets/operations-console/manifest.webmanifest +11 -0
- engineering_platform/capability_preflight.py +285 -0
- engineering_platform/capability_review.py +261 -0
- engineering_platform/central_data_transfer.py +195 -0
- engineering_platform/central_database.py +245 -0
- engineering_platform/central_store_migration.py +1672 -0
- engineering_platform/codex_capacity.py +81 -0
- engineering_platform/codex_chat.py +226 -0
- engineering_platform/codex_observability.py +153 -0
- engineering_platform/component_lock.py +40 -0
- engineering_platform/component_logging.py +420 -0
- engineering_platform/console_presentation.py +14 -0
- engineering_platform/console_route_ownership.py +83 -0
- engineering_platform/contracts/__init__.py +38 -0
- engineering_platform/contracts/ep_consumer.py +391 -0
- engineering_platform/contracts/models.py +105 -0
- engineering_platform/contracts/projection.py +401 -0
- engineering_platform/dashboard_browser_validation.py +206 -0
- engineering_platform/dashboard_state.py +630 -0
- engineering_platform/dashboard_supervisor.swift +105 -0
- engineering_platform/dashboard_translation.py +129 -0
- engineering_platform/dependabot_producer.py +349 -0
- engineering_platform/drift_diagnostics.py +144 -0
- engineering_platform/emergency_recovery.py +268 -0
- engineering_platform/engineering_memory.py +139 -0
- engineering_platform/ep_consumer_credentials.py +473 -0
- engineering_platform/evidence_projection.py +213 -0
- engineering_platform/execution_activity.py +218 -0
- engineering_platform/execution_context.py +132 -0
- engineering_platform/execution_errors.py +42 -0
- engineering_platform/execution_evidence.py +24 -0
- engineering_platform/execution_executor.py +730 -0
- engineering_platform/execution_finalization.py +44 -0
- engineering_platform/execution_host.py +3306 -0
- engineering_platform/execution_lease.py +365 -0
- engineering_platform/execution_lifecycle.py +447 -0
- engineering_platform/execution_models.py +43 -0
- engineering_platform/execution_readiness.py +166 -0
- engineering_platform/execution_reporting.py +1607 -0
- engineering_platform/execution_repository.py +253 -0
- engineering_platform/execution_timeout_policy.py +56 -0
- engineering_platform/execution_timing.py +440 -0
- engineering_platform/execution_transaction.py +28 -0
- engineering_platform/external_producer_binding.py +235 -0
- engineering_platform/file_inbox.py +249 -0
- engineering_platform/forensic_attribution.py +338 -0
- engineering_platform/forensic_attribution_v2.py +134 -0
- engineering_platform/forensic_delta.py +299 -0
- engineering_platform/golden_scenario.py +63 -0
- engineering_platform/historical_dashboard_configuration.py +171 -0
- engineering_platform/host_admin.py +199 -0
- engineering_platform/host_preflight.py +231 -0
- engineering_platform/installation_relocation.py +122 -0
- engineering_platform/investigation_ledger.py +89 -0
- engineering_platform/legacy_inbox_migration.py +79 -0
- engineering_platform/lifecycle_worker.py +223 -0
- engineering_platform/live_status.py +267 -0
- engineering_platform/local_api.py +209 -0
- engineering_platform/local_api_keychain.py +51 -0
- engineering_platform/local_repository_binding.py +138 -0
- engineering_platform/managed_autonomy.py +509 -0
- engineering_platform/managed_codex_runtime.py +105 -0
- engineering_platform/parity_context.py +203 -0
- engineering_platform/parity_lifecycle_dispatcher.py +488 -0
- engineering_platform/platform_admin.py +13 -0
- engineering_platform/platform_api.py +428 -0
- engineering_platform/platform_bootstrap.py +385 -0
- engineering_platform/platform_components.py +65 -0
- engineering_platform/platform_version.py +171 -0
- engineering_platform/pr_check_repair.py +276 -0
- engineering_platform/pr_evidence_backfill.py +278 -0
- engineering_platform/producer.py +209 -0
- engineering_platform/project_agent.py +366 -0
- engineering_platform/project_agent_service.py +244 -0
- engineering_platform/project_topology.py +126 -0
- engineering_platform/prompt_history.py +591 -0
- engineering_platform/provider_context.py +136 -0
- engineering_platform/provider_context_benchmark.py +41 -0
- engineering_platform/provider_context_scope.py +90 -0
- engineering_platform/provider_interruption.py +168 -0
- engineering_platform/provider_process_identity.py +80 -0
- engineering_platform/provider_readiness.py +138 -0
- engineering_platform/provider_recovery.py +647 -0
- engineering_platform/provider_usage.py +497 -0
- engineering_platform/providers.py +471 -0
- engineering_platform/qualification.py +220 -0
- engineering_platform/recommendation_handoff.py +238 -0
- engineering_platform/report_analysis.py +193 -0
- engineering_platform/repository_attachment.py +171 -0
- engineering_platform/repository_handoff.py +95 -0
- engineering_platform/resources.py +38 -0
- engineering_platform/reviewer_evidence.py +70 -0
- engineering_platform/schemas/repository-attachment.schema.json +61 -0
- engineering_platform/server.py +3679 -0
- engineering_platform/server_console_services.py +2024 -0
- engineering_platform/server_relay.py +172 -0
- engineering_platform/server_service.py +122 -0
- engineering_platform/status_model.py +135 -0
- engineering_platform/status_reconciliation.py +34 -0
- engineering_platform/storage.py +2440 -0
- engineering_platform/submission_cli.py +77 -0
- engineering_platform/submission_intake.py +45 -0
- engineering_platform/submission_service.py +317 -0
- engineering_platform/telemetry.py +951 -0
- engineering_platform/templates/workspace-config.json +25 -0
- engineering_platform/validation_identity.py +50 -0
- engineering_platform/validation_profile.py +211 -0
- engineering_platform/workspace_preflight.py +263 -0
- engineering_platform/worktree_provenance.py +147 -0
- engineering_platform/worktree_tooling.py +18 -0
- engineering_platform-2.2.0.dist-info/METADATA +18 -0
- engineering_platform-2.2.0.dist-info/RECORD +130 -0
- engineering_platform-2.2.0.dist-info/WHEEL +5 -0
- engineering_platform-2.2.0.dist-info/entry_points.txt +6 -0
- engineering_platform-2.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,951 @@
|
|
|
1
|
+
"""Best-effort, local Execution Host telemetry.
|
|
2
|
+
|
|
3
|
+
Telemetry is operational evidence only. It is deliberately separate from
|
|
4
|
+
transaction checkpoints and cannot change an engineering outcome.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from datetime import datetime, timedelta, timezone
|
|
11
|
+
import json
|
|
12
|
+
from math import sqrt
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import sqlite3
|
|
15
|
+
from threading import Lock, Thread, current_thread
|
|
16
|
+
from time import monotonic
|
|
17
|
+
from typing import Callable, Literal
|
|
18
|
+
from statistics import mean, median
|
|
19
|
+
|
|
20
|
+
from .storage import open_storage
|
|
21
|
+
from .producer import ProducerMetadata
|
|
22
|
+
from .execution_timing import timing_summary
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
TERMINAL_STATES = frozenset({"COMPLETE", "BLOCKED", "FAILED"})
|
|
26
|
+
TELEMETRY_OUTBOX_SOURCES = frozenset({"LIVE_TERMINAL", "RECOVERY", "BACKFILL"})
|
|
27
|
+
_PENDING_WORKERS: set[Thread] = set()
|
|
28
|
+
_PENDING_WORKERS_LOCK = Lock()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class ExecutionTelemetry:
|
|
33
|
+
run_id: str
|
|
34
|
+
arrived_at: datetime
|
|
35
|
+
execution_started_at: datetime
|
|
36
|
+
execution_finished_at: datetime
|
|
37
|
+
terminal_state: str
|
|
38
|
+
execution_seconds: float | None
|
|
39
|
+
input_tokens: int | None
|
|
40
|
+
output_tokens: int | None
|
|
41
|
+
total_tokens: int | None
|
|
42
|
+
execution_mode: str
|
|
43
|
+
workspace: str
|
|
44
|
+
repository: str
|
|
45
|
+
execution_host_version: str
|
|
46
|
+
retry_of: str | None = None
|
|
47
|
+
original_run_id: str | None = None
|
|
48
|
+
retry_generation: int | None = None
|
|
49
|
+
retry_timestamp: str | None = None
|
|
50
|
+
prompt_characters: int | None = None
|
|
51
|
+
runtime_provider: str | None = None
|
|
52
|
+
runtime_model: str | None = None
|
|
53
|
+
reasoning_profile: str | None = None
|
|
54
|
+
configuration_profile: str | None = None
|
|
55
|
+
execution_metadata: dict[str, int] | None = None
|
|
56
|
+
producer: ProducerMetadata = ProducerMetadata()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _utc(value: datetime) -> datetime:
|
|
60
|
+
return value.astimezone(timezone.utc) if value.tzinfo else value.replace(tzinfo=timezone.utc)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _timestamp(value: datetime) -> str:
|
|
64
|
+
return _utc(value).isoformat()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _integer(value: object) -> int | None:
|
|
68
|
+
return value if isinstance(value, int) and not isinstance(value, bool) and value >= 0 else None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _runtime_value(value: object) -> str | None:
|
|
72
|
+
"""Keep a bounded, display-safe runtime profile value for local aggregation."""
|
|
73
|
+
if not isinstance(value, str):
|
|
74
|
+
return None
|
|
75
|
+
normalized = value.strip()
|
|
76
|
+
if not normalized or normalized.casefold() in {"not reported", "unavailable"}:
|
|
77
|
+
return None
|
|
78
|
+
return normalized[:120]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _execution_metadata(value: object) -> str:
|
|
82
|
+
if not isinstance(value, dict):
|
|
83
|
+
return "{}"
|
|
84
|
+
safe = {
|
|
85
|
+
key: item
|
|
86
|
+
for key in ("modified", "created", "deleted", "codex_commands_executed")
|
|
87
|
+
for item in (value.get(key),)
|
|
88
|
+
if isinstance(item, int) and not isinstance(item, bool) and 0 <= item <= 1_000_000
|
|
89
|
+
}
|
|
90
|
+
return json.dumps(safe, separators=(",", ":"), sort_keys=True)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _payload(telemetry: ExecutionTelemetry) -> str:
|
|
94
|
+
"""Serialize only bounded terminal projection data for durable replay."""
|
|
95
|
+
return json.dumps({
|
|
96
|
+
"run_id": telemetry.run_id,
|
|
97
|
+
"arrived_at": _timestamp(telemetry.arrived_at),
|
|
98
|
+
"execution_started_at": _timestamp(telemetry.execution_started_at),
|
|
99
|
+
"execution_finished_at": _timestamp(telemetry.execution_finished_at),
|
|
100
|
+
"terminal_state": telemetry.terminal_state,
|
|
101
|
+
"execution_seconds": telemetry.execution_seconds,
|
|
102
|
+
"input_tokens": _integer(telemetry.input_tokens),
|
|
103
|
+
"output_tokens": _integer(telemetry.output_tokens),
|
|
104
|
+
"total_tokens": _integer(telemetry.total_tokens),
|
|
105
|
+
"execution_mode": telemetry.execution_mode,
|
|
106
|
+
"workspace": telemetry.workspace,
|
|
107
|
+
"repository": telemetry.repository,
|
|
108
|
+
"execution_host_version": telemetry.execution_host_version,
|
|
109
|
+
"retry_of": telemetry.retry_of,
|
|
110
|
+
"original_run_id": telemetry.original_run_id,
|
|
111
|
+
"retry_generation": telemetry.retry_generation,
|
|
112
|
+
"retry_timestamp": telemetry.retry_timestamp,
|
|
113
|
+
"prompt_characters": _integer(telemetry.prompt_characters),
|
|
114
|
+
"runtime_provider": _runtime_value(telemetry.runtime_provider),
|
|
115
|
+
"runtime_model": _runtime_value(telemetry.runtime_model),
|
|
116
|
+
"reasoning_profile": _runtime_value(telemetry.reasoning_profile),
|
|
117
|
+
"configuration_profile": _runtime_value(telemetry.configuration_profile),
|
|
118
|
+
"execution_metadata": json.loads(_execution_metadata(telemetry.execution_metadata)),
|
|
119
|
+
"producer": {
|
|
120
|
+
"producer_id": telemetry.producer.producer_id,
|
|
121
|
+
"producer_type": telemetry.producer.producer_type,
|
|
122
|
+
"producer_version": telemetry.producer.producer_version,
|
|
123
|
+
"correlation_id": telemetry.producer.correlation_id,
|
|
124
|
+
"mission_id": telemetry.producer.mission_id,
|
|
125
|
+
"engineering_action_id": telemetry.producer.engineering_action_id,
|
|
126
|
+
"execution_constraint_version": telemetry.producer.execution_constraint_version,
|
|
127
|
+
},
|
|
128
|
+
}, separators=(",", ":"), sort_keys=True)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _datetime(value: object) -> datetime:
|
|
132
|
+
if not isinstance(value, str):
|
|
133
|
+
raise ValueError("terminal telemetry timestamp is invalid")
|
|
134
|
+
try:
|
|
135
|
+
return _utc(datetime.fromisoformat(value.replace("Z", "+00:00")))
|
|
136
|
+
except ValueError as error:
|
|
137
|
+
raise ValueError("terminal telemetry timestamp is invalid") from error
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _from_payload(raw: object) -> ExecutionTelemetry:
|
|
141
|
+
if not isinstance(raw, dict):
|
|
142
|
+
raise ValueError("terminal telemetry payload is invalid")
|
|
143
|
+
producer = raw.get("producer")
|
|
144
|
+
if not isinstance(producer, dict):
|
|
145
|
+
raise ValueError("terminal telemetry producer is invalid")
|
|
146
|
+
required = ("run_id", "terminal_state", "execution_mode", "workspace", "repository", "execution_host_version")
|
|
147
|
+
if any(not isinstance(raw.get(key), str) or not raw[key] for key in required):
|
|
148
|
+
raise ValueError("terminal telemetry identity is invalid")
|
|
149
|
+
if raw["terminal_state"] not in TERMINAL_STATES or raw["execution_mode"] not in {"MANAGED", "GENESIS"}:
|
|
150
|
+
raise ValueError("terminal telemetry lifecycle is invalid")
|
|
151
|
+
return ExecutionTelemetry(
|
|
152
|
+
run_id=raw["run_id"], arrived_at=_datetime(raw.get("arrived_at")),
|
|
153
|
+
execution_started_at=_datetime(raw.get("execution_started_at")),
|
|
154
|
+
execution_finished_at=_datetime(raw.get("execution_finished_at")),
|
|
155
|
+
terminal_state=raw["terminal_state"], execution_seconds=raw.get("execution_seconds"),
|
|
156
|
+
input_tokens=_integer(raw.get("input_tokens")), output_tokens=_integer(raw.get("output_tokens")),
|
|
157
|
+
total_tokens=_integer(raw.get("total_tokens")), execution_mode=raw["execution_mode"],
|
|
158
|
+
workspace=raw["workspace"], repository=raw["repository"],
|
|
159
|
+
execution_host_version=raw["execution_host_version"], retry_of=raw.get("retry_of"),
|
|
160
|
+
original_run_id=raw.get("original_run_id"), retry_generation=raw.get("retry_generation"),
|
|
161
|
+
retry_timestamp=raw.get("retry_timestamp"), prompt_characters=_integer(raw.get("prompt_characters")),
|
|
162
|
+
runtime_provider=_runtime_value(raw.get("runtime_provider")), runtime_model=_runtime_value(raw.get("runtime_model")),
|
|
163
|
+
reasoning_profile=_runtime_value(raw.get("reasoning_profile")), configuration_profile=_runtime_value(raw.get("configuration_profile")),
|
|
164
|
+
execution_metadata=raw.get("execution_metadata") if isinstance(raw.get("execution_metadata"), dict) else None,
|
|
165
|
+
producer=ProducerMetadata(**{key: producer.get(key) for key in ProducerMetadata.__dataclass_fields__}),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def queue_terminal_telemetry(
|
|
170
|
+
root: Path, telemetry: ExecutionTelemetry, *, source: Literal["LIVE_TERMINAL", "RECOVERY", "BACKFILL"] = "LIVE_TERMINAL",
|
|
171
|
+
central_database: Path | None = None,
|
|
172
|
+
) -> bool:
|
|
173
|
+
"""Synchronously record a terminal telemetry intent before projection work.
|
|
174
|
+
|
|
175
|
+
Repeating the exact request is safe. A contradictory payload for the same
|
|
176
|
+
run is rejected rather than silently replacing terminal evidence.
|
|
177
|
+
"""
|
|
178
|
+
if telemetry.terminal_state not in TERMINAL_STATES or source not in TELEMETRY_OUTBOX_SOURCES:
|
|
179
|
+
raise ValueError("terminal telemetry outbox input is invalid")
|
|
180
|
+
payload = _payload(telemetry)
|
|
181
|
+
# Reject malformed live telemetry before it can become a retry loop.
|
|
182
|
+
_from_payload(json.loads(payload))
|
|
183
|
+
if central_database is None:
|
|
184
|
+
connection = open_storage(root, create=False)
|
|
185
|
+
else:
|
|
186
|
+
connection = sqlite3.connect(central_database.resolve(), isolation_level=None)
|
|
187
|
+
connection.execute("PRAGMA foreign_keys=ON")
|
|
188
|
+
try:
|
|
189
|
+
with connection:
|
|
190
|
+
existing = connection.execute(
|
|
191
|
+
"SELECT payload FROM terminal_telemetry_outbox WHERE run_id=?", (telemetry.run_id,)
|
|
192
|
+
).fetchone()
|
|
193
|
+
if existing is not None:
|
|
194
|
+
if existing[0] != payload:
|
|
195
|
+
raise ValueError("terminal telemetry outbox conflicts with existing run evidence")
|
|
196
|
+
return False
|
|
197
|
+
connection.execute(
|
|
198
|
+
"INSERT INTO terminal_telemetry_outbox(run_id,payload,source,state,created_at) VALUES(?,?,?,'PENDING',?)",
|
|
199
|
+
(telemetry.run_id, payload, source, datetime.now(timezone.utc).isoformat()),
|
|
200
|
+
)
|
|
201
|
+
finally:
|
|
202
|
+
connection.close()
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def materialize_pending_terminal_telemetry(root: Path, *, run_id: str | None = None, limit: int = 25,
|
|
207
|
+
central_database: Path | None = None) -> dict[str, int]:
|
|
208
|
+
"""Idempotently materialize durable intents; failures remain retryable."""
|
|
209
|
+
if limit < 1 or limit > 250:
|
|
210
|
+
raise ValueError("terminal telemetry recovery limit is invalid")
|
|
211
|
+
if central_database is None:
|
|
212
|
+
connection = open_storage(root, create=False)
|
|
213
|
+
else:
|
|
214
|
+
connection = sqlite3.connect(central_database.resolve(), isolation_level=None)
|
|
215
|
+
connection.execute("PRAGMA foreign_keys=ON")
|
|
216
|
+
try:
|
|
217
|
+
query = "SELECT run_id,payload FROM terminal_telemetry_outbox WHERE state IN ('PENDING','FAILED_RETRYABLE')"
|
|
218
|
+
parameters: tuple[object, ...] = ()
|
|
219
|
+
if run_id is not None:
|
|
220
|
+
query += " AND run_id=?"
|
|
221
|
+
parameters = (run_id,)
|
|
222
|
+
rows = connection.execute(query + " ORDER BY created_at,run_id LIMIT ?", parameters + (limit,)).fetchall()
|
|
223
|
+
finally:
|
|
224
|
+
connection.close()
|
|
225
|
+
result = {"processed": 0, "failed": 0, "pending": len(rows)}
|
|
226
|
+
for queued_run_id, payload in rows:
|
|
227
|
+
try:
|
|
228
|
+
telemetry = _from_payload(json.loads(payload))
|
|
229
|
+
if telemetry.run_id != queued_run_id:
|
|
230
|
+
raise ValueError("terminal telemetry outbox run identity is invalid")
|
|
231
|
+
persist_execution(root, telemetry, create=False, central_database=central_database)
|
|
232
|
+
connection = (open_storage(root, create=False) if central_database is None else sqlite3.connect(central_database.resolve(), isolation_level=None))
|
|
233
|
+
try:
|
|
234
|
+
with connection:
|
|
235
|
+
connection.execute(
|
|
236
|
+
"UPDATE terminal_telemetry_outbox SET state='PROCESSED',attempt_count=attempt_count+1,"
|
|
237
|
+
"last_error=NULL,processed_at=? WHERE run_id=?",
|
|
238
|
+
(datetime.now(timezone.utc).isoformat(), queued_run_id),
|
|
239
|
+
)
|
|
240
|
+
finally:
|
|
241
|
+
connection.close()
|
|
242
|
+
result["processed"] += 1
|
|
243
|
+
except Exception as error:
|
|
244
|
+
connection = (open_storage(root, create=False) if central_database is None else sqlite3.connect(central_database.resolve(), isolation_level=None))
|
|
245
|
+
try:
|
|
246
|
+
with connection:
|
|
247
|
+
connection.execute(
|
|
248
|
+
"UPDATE terminal_telemetry_outbox SET state='FAILED_RETRYABLE',attempt_count=attempt_count+1,last_error=? WHERE run_id=?",
|
|
249
|
+
(str(error)[:500], queued_run_id),
|
|
250
|
+
)
|
|
251
|
+
finally:
|
|
252
|
+
connection.close()
|
|
253
|
+
result["failed"] += 1
|
|
254
|
+
return result
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _recovery_telemetry(root: Path, run_id: str, *, central_database: Path | None = None) -> ExecutionTelemetry:
|
|
258
|
+
"""Reconstruct a projection only from structured terminal evidence.
|
|
259
|
+
|
|
260
|
+
This intentionally does not read report prose, infer tokens, or fabricate
|
|
261
|
+
duration values. Missing optional evidence remains unknown.
|
|
262
|
+
"""
|
|
263
|
+
connection = open_storage(root, create=False) if central_database is None else sqlite3.connect(central_database.resolve(), isolation_level=None)
|
|
264
|
+
try:
|
|
265
|
+
transaction = connection.execute(
|
|
266
|
+
"SELECT payload,phase FROM engineering_transactions WHERE run_id=?", (run_id,)
|
|
267
|
+
).fetchone()
|
|
268
|
+
history = connection.execute(
|
|
269
|
+
"SELECT terminal_state,executed_at,execution_metadata FROM prompt_execution_history WHERE run_id=?", (run_id,)
|
|
270
|
+
).fetchone()
|
|
271
|
+
spans = connection.execute(
|
|
272
|
+
"SELECT phase_name,started_at,completed_at FROM execution_phase_spans "
|
|
273
|
+
"WHERE run_id=? AND phase_name IN ('QUEUE_WAIT','TOTAL_EXECUTION') AND outcome='COMPLETE' ORDER BY ordinal",
|
|
274
|
+
(run_id,),
|
|
275
|
+
).fetchall()
|
|
276
|
+
finally:
|
|
277
|
+
connection.close()
|
|
278
|
+
if transaction is None or history is None:
|
|
279
|
+
raise ValueError("canonical terminal evidence is incomplete")
|
|
280
|
+
payload_text, phase = transaction
|
|
281
|
+
terminal_state, executed_at, metadata_text = history
|
|
282
|
+
if phase not in TERMINAL_STATES or terminal_state != phase:
|
|
283
|
+
raise ValueError("canonical terminal evidence is contradictory")
|
|
284
|
+
try:
|
|
285
|
+
checkpoint = json.loads(payload_text)
|
|
286
|
+
metadata = json.loads(metadata_text) if isinstance(metadata_text, str) else {}
|
|
287
|
+
except json.JSONDecodeError as error:
|
|
288
|
+
raise ValueError("canonical terminal evidence is invalid") from error
|
|
289
|
+
if not isinstance(checkpoint, dict) or not isinstance(metadata, dict):
|
|
290
|
+
raise ValueError("canonical terminal evidence is invalid")
|
|
291
|
+
total = next((row for row in spans if row[0] == "TOTAL_EXECUTION" and row[1] and row[2]), None)
|
|
292
|
+
if total is None:
|
|
293
|
+
raise ValueError("canonical terminal timing evidence is unavailable")
|
|
294
|
+
queue = next((row for row in spans if row[0] == "QUEUE_WAIT" and row[1]), None)
|
|
295
|
+
execution_started_at = _datetime(total[1])
|
|
296
|
+
arrived_at = _datetime(queue[1]) if queue is not None else execution_started_at
|
|
297
|
+
finished_at = _datetime(executed_at)
|
|
298
|
+
if finished_at < execution_started_at:
|
|
299
|
+
raise ValueError("canonical terminal timestamps are contradictory")
|
|
300
|
+
repository = checkpoint.get("repository")
|
|
301
|
+
mode = checkpoint.get("execution_mode")
|
|
302
|
+
if not isinstance(repository, str) or not repository or mode not in {"MANAGED", "GENESIS"}:
|
|
303
|
+
raise ValueError("canonical terminal identity is unavailable")
|
|
304
|
+
seconds = checkpoint.get("agent_execution_seconds")
|
|
305
|
+
if not isinstance(seconds, (int, float)) or isinstance(seconds, bool) or seconds < 0:
|
|
306
|
+
seconds = None
|
|
307
|
+
return ExecutionTelemetry(
|
|
308
|
+
run_id=run_id, arrived_at=arrived_at, execution_started_at=execution_started_at,
|
|
309
|
+
execution_finished_at=finished_at, terminal_state=terminal_state,
|
|
310
|
+
execution_seconds=float(seconds) if seconds is not None else None,
|
|
311
|
+
input_tokens=None, output_tokens=None, total_tokens=None, execution_mode=mode,
|
|
312
|
+
workspace=root.resolve().name, repository=repository, execution_host_version="unknown",
|
|
313
|
+
execution_metadata=metadata,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def recover_terminal_telemetry(root: Path, run_id: str, *, source: Literal["RECOVERY", "BACKFILL"] = "RECOVERY", central_database: Path | None = None) -> str:
|
|
318
|
+
"""Perform one governed recovery from canonical terminal evidence.
|
|
319
|
+
|
|
320
|
+
Existing telemetry is left untouched. The result records the operational
|
|
321
|
+
path so callers can audit whether a run was live, recovered, or backfilled.
|
|
322
|
+
"""
|
|
323
|
+
connection = open_storage(root, create=False) if central_database is None else sqlite3.connect(central_database.resolve(), isolation_level=None)
|
|
324
|
+
try:
|
|
325
|
+
if connection.execute("SELECT 1 FROM execution_runs WHERE run_id=?", (run_id,)).fetchone() is not None:
|
|
326
|
+
return "already_materialized"
|
|
327
|
+
finally:
|
|
328
|
+
connection.close()
|
|
329
|
+
telemetry = _recovery_telemetry(root, run_id, central_database=central_database)
|
|
330
|
+
queue_terminal_telemetry(root, telemetry, source=source, central_database=central_database)
|
|
331
|
+
result = materialize_pending_terminal_telemetry(root, run_id=run_id, limit=1, central_database=central_database)
|
|
332
|
+
if result["failed"]:
|
|
333
|
+
raise ValueError("terminal telemetry recovery remains retryable")
|
|
334
|
+
return "recovered" if result["processed"] else "already_queued"
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def recover_missing_terminal_telemetry(root: Path, *, limit: int = 25, central_database: Path | None = None) -> dict[str, int]:
|
|
338
|
+
"""Boundedly repair only missing projections with complete source evidence."""
|
|
339
|
+
if limit < 1 or limit > 250:
|
|
340
|
+
raise ValueError("terminal telemetry recovery limit is invalid")
|
|
341
|
+
connection = open_storage(root, create=False) if central_database is None else sqlite3.connect(central_database.resolve(), isolation_level=None)
|
|
342
|
+
try:
|
|
343
|
+
rows = connection.execute(
|
|
344
|
+
"SELECT history.run_id FROM prompt_execution_history AS history "
|
|
345
|
+
"LEFT JOIN execution_runs AS runs ON runs.run_id=history.run_id "
|
|
346
|
+
"JOIN engineering_transactions AS transaction_row "
|
|
347
|
+
"ON transaction_row.run_id=history.run_id AND transaction_row.phase=history.terminal_state "
|
|
348
|
+
"WHERE history.terminal_state IN ('COMPLETE','BLOCKED','FAILED') AND runs.run_id IS NULL "
|
|
349
|
+
"AND EXISTS (SELECT 1 FROM execution_phase_spans AS spans "
|
|
350
|
+
"WHERE spans.run_id=history.run_id AND spans.phase_name='TOTAL_EXECUTION' "
|
|
351
|
+
"AND spans.outcome='COMPLETE' AND spans.started_at IS NOT NULL AND spans.completed_at IS NOT NULL) "
|
|
352
|
+
"ORDER BY history.executed_at,history.run_id LIMIT ?", (limit,)
|
|
353
|
+
).fetchall()
|
|
354
|
+
finally:
|
|
355
|
+
connection.close()
|
|
356
|
+
result = {"recovered": 0, "failed": 0, "candidates": len(rows)}
|
|
357
|
+
for (run_id,) in rows:
|
|
358
|
+
try:
|
|
359
|
+
if recover_terminal_telemetry(root, run_id, central_database=central_database) == "recovered":
|
|
360
|
+
result["recovered"] += 1
|
|
361
|
+
except Exception:
|
|
362
|
+
# The detailed, redacted failure remains in the outbox when it was
|
|
363
|
+
# queueable; no incomplete source is ever converted to telemetry.
|
|
364
|
+
result["failed"] += 1
|
|
365
|
+
return result
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def persist_execution(
|
|
369
|
+
root: Path,
|
|
370
|
+
telemetry: ExecutionTelemetry,
|
|
371
|
+
*,
|
|
372
|
+
create: bool = True,
|
|
373
|
+
background: bool = False,
|
|
374
|
+
central_database: Path | None = None,
|
|
375
|
+
) -> None:
|
|
376
|
+
"""Persist one immutable run projection and refresh its daily aggregate."""
|
|
377
|
+
if telemetry.terminal_state not in TERMINAL_STATES:
|
|
378
|
+
raise ValueError("telemetry requires a terminal state")
|
|
379
|
+
arrived, started, finished = map(_utc, (telemetry.arrived_at, telemetry.execution_started_at, telemetry.execution_finished_at))
|
|
380
|
+
queue_wait = max(0.0, (started - arrived).total_seconds())
|
|
381
|
+
total_execution_seconds = max(0.0, (finished - arrived).total_seconds())
|
|
382
|
+
execution_seconds = telemetry.execution_seconds
|
|
383
|
+
if execution_seconds is not None and (isinstance(execution_seconds, bool) or execution_seconds < 0):
|
|
384
|
+
raise ValueError("telemetry execution duration is invalid")
|
|
385
|
+
execution_date = finished.date().isoformat()
|
|
386
|
+
if central_database is None:
|
|
387
|
+
connection = open_storage(root, create=create, journal_mode="MEMORY" if background else "DELETE")
|
|
388
|
+
else:
|
|
389
|
+
database = central_database.resolve()
|
|
390
|
+
if not database.is_file():
|
|
391
|
+
raise ValueError("CENTRAL telemetry database is unavailable")
|
|
392
|
+
connection = sqlite3.connect(database, isolation_level=None)
|
|
393
|
+
connection.execute("PRAGMA foreign_keys=ON")
|
|
394
|
+
try:
|
|
395
|
+
# One projection transaction: a crash can leave the durable outbox
|
|
396
|
+
# pending, but never a half-refreshed run/daily aggregate pair.
|
|
397
|
+
with connection:
|
|
398
|
+
connection.execute(
|
|
399
|
+
"""
|
|
400
|
+
INSERT OR IGNORE INTO execution_runs(
|
|
401
|
+
run_id, execution_date, arrived_at, execution_started_at, execution_finished_at,
|
|
402
|
+
queue_wait_seconds, execution_seconds, total_execution_seconds, terminal_state, input_tokens, output_tokens,
|
|
403
|
+
total_tokens, execution_mode, workspace, repository, execution_host_version, retry_of,
|
|
404
|
+
original_run_id, retry_generation, retry_timestamp, prompt_characters,
|
|
405
|
+
runtime_provider, runtime_model, reasoning_profile, configuration_profile
|
|
406
|
+
, producer_id, producer_type, producer_version, correlation_id, mission_id,
|
|
407
|
+
engineering_action_id, execution_constraint_version, execution_metadata
|
|
408
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
409
|
+
""",
|
|
410
|
+
(
|
|
411
|
+
telemetry.run_id,
|
|
412
|
+
execution_date,
|
|
413
|
+
_timestamp(arrived),
|
|
414
|
+
_timestamp(started),
|
|
415
|
+
_timestamp(finished),
|
|
416
|
+
queue_wait,
|
|
417
|
+
execution_seconds,
|
|
418
|
+
total_execution_seconds,
|
|
419
|
+
telemetry.terminal_state,
|
|
420
|
+
_integer(telemetry.input_tokens),
|
|
421
|
+
_integer(telemetry.output_tokens),
|
|
422
|
+
_integer(telemetry.total_tokens),
|
|
423
|
+
telemetry.execution_mode,
|
|
424
|
+
telemetry.workspace,
|
|
425
|
+
telemetry.repository,
|
|
426
|
+
telemetry.execution_host_version,
|
|
427
|
+
telemetry.retry_of,
|
|
428
|
+
telemetry.original_run_id,
|
|
429
|
+
telemetry.retry_generation,
|
|
430
|
+
telemetry.retry_timestamp,
|
|
431
|
+
_integer(telemetry.prompt_characters),
|
|
432
|
+
_runtime_value(telemetry.runtime_provider),
|
|
433
|
+
_runtime_value(telemetry.runtime_model),
|
|
434
|
+
_runtime_value(telemetry.reasoning_profile),
|
|
435
|
+
_runtime_value(telemetry.configuration_profile),
|
|
436
|
+
telemetry.producer.producer_id,
|
|
437
|
+
telemetry.producer.producer_type,
|
|
438
|
+
telemetry.producer.producer_version,
|
|
439
|
+
telemetry.producer.correlation_id,
|
|
440
|
+
telemetry.producer.mission_id,
|
|
441
|
+
telemetry.producer.engineering_action_id,
|
|
442
|
+
telemetry.producer.execution_constraint_version,
|
|
443
|
+
_execution_metadata(telemetry.execution_metadata),
|
|
444
|
+
),
|
|
445
|
+
)
|
|
446
|
+
connection.execute(
|
|
447
|
+
"""INSERT OR IGNORE INTO execution_receipts(
|
|
448
|
+
run_id, producer_id, producer_type, producer_version, mission_id,
|
|
449
|
+
engineering_action_id, correlation_id, execution_constraint_version,
|
|
450
|
+
execution_host, execution_host_version, receipt_timestamp, execution_outcome
|
|
451
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
452
|
+
(
|
|
453
|
+
telemetry.run_id, telemetry.producer.producer_id, telemetry.producer.producer_type,
|
|
454
|
+
telemetry.producer.producer_version, telemetry.producer.mission_id,
|
|
455
|
+
telemetry.producer.engineering_action_id, telemetry.producer.correlation_id,
|
|
456
|
+
telemetry.producer.execution_constraint_version, "Engineering Platform",
|
|
457
|
+
telemetry.execution_host_version, _timestamp(finished), telemetry.terminal_state,
|
|
458
|
+
),
|
|
459
|
+
)
|
|
460
|
+
# Admission reserves the immutable submission-to-run binding in
|
|
461
|
+
# execution_submission_links before any lifecycle work begins.
|
|
462
|
+
# The legacy FK column cannot be populated until this terminal
|
|
463
|
+
# execution_runs row exists, so complete it in this same atomic
|
|
464
|
+
# projection transaction without rewriting historical rows.
|
|
465
|
+
bound = connection.execute(
|
|
466
|
+
"SELECT submission_id FROM execution_submission_links WHERE run_id=?",
|
|
467
|
+
(telemetry.run_id,),
|
|
468
|
+
).fetchone()
|
|
469
|
+
if bound is not None:
|
|
470
|
+
current = connection.execute(
|
|
471
|
+
"SELECT execution_run_id FROM execution_submissions WHERE submission_id=?",
|
|
472
|
+
(bound[0],),
|
|
473
|
+
).fetchone()
|
|
474
|
+
if current is None or current[0] not in (None, telemetry.run_id):
|
|
475
|
+
raise ValueError("Execution submission run binding is inconsistent.")
|
|
476
|
+
connection.execute(
|
|
477
|
+
"UPDATE execution_submissions SET execution_run_id=? "
|
|
478
|
+
"WHERE submission_id=? AND execution_run_id IS NULL",
|
|
479
|
+
(telemetry.run_id, bound[0]),
|
|
480
|
+
)
|
|
481
|
+
connection.execute(
|
|
482
|
+
"""
|
|
483
|
+
INSERT OR REPLACE INTO daily_execution_statistics(
|
|
484
|
+
execution_date, workspace, repository, execution_mode, prompt_count,
|
|
485
|
+
complete_count, blocked_count, failed_count, average_execution_seconds, average_total_execution_seconds,
|
|
486
|
+
average_queue_wait_seconds, input_tokens, output_tokens, total_tokens
|
|
487
|
+
)
|
|
488
|
+
SELECT execution_date, workspace, repository, execution_mode,
|
|
489
|
+
COUNT(*),
|
|
490
|
+
SUM(terminal_state = 'COMPLETE'), SUM(terminal_state = 'BLOCKED'), SUM(terminal_state = 'FAILED'),
|
|
491
|
+
AVG(execution_seconds), AVG(total_execution_seconds), AVG(queue_wait_seconds),
|
|
492
|
+
SUM(input_tokens), SUM(output_tokens), SUM(total_tokens)
|
|
493
|
+
FROM execution_runs
|
|
494
|
+
WHERE execution_date = ? AND workspace = ? AND repository = ? AND execution_mode = ?
|
|
495
|
+
GROUP BY execution_date, workspace, repository, execution_mode
|
|
496
|
+
""",
|
|
497
|
+
(execution_date, telemetry.workspace, telemetry.repository, telemetry.execution_mode),
|
|
498
|
+
)
|
|
499
|
+
finally:
|
|
500
|
+
connection.close()
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def persist_execution_async(
|
|
504
|
+
root: Path, telemetry: ExecutionTelemetry, *, on_error: Callable[[Exception], None] | None = None
|
|
505
|
+
) -> Thread:
|
|
506
|
+
"""Schedule telemetry without ever delaying or failing engineering delivery."""
|
|
507
|
+
def persist() -> None:
|
|
508
|
+
try:
|
|
509
|
+
# The inbox watcher has already established the canonical workspace
|
|
510
|
+
# before telemetry is scheduled. A delayed best-effort worker must
|
|
511
|
+
# never recreate that workspace after its owner has gone away.
|
|
512
|
+
persist_execution(root, telemetry, create=False, background=True)
|
|
513
|
+
except Exception as error: # Best-effort boundary; caller logs only.
|
|
514
|
+
if on_error is not None:
|
|
515
|
+
on_error(error)
|
|
516
|
+
finally:
|
|
517
|
+
with _PENDING_WORKERS_LOCK:
|
|
518
|
+
_PENDING_WORKERS.discard(current_thread())
|
|
519
|
+
|
|
520
|
+
worker = Thread(target=persist, name=f"ep-telemetry-{telemetry.run_id}", daemon=True)
|
|
521
|
+
with _PENDING_WORKERS_LOCK:
|
|
522
|
+
_PENDING_WORKERS.add(worker)
|
|
523
|
+
worker.start()
|
|
524
|
+
return worker
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def wait_for_pending_telemetry(*, timeout: float = 5.0) -> None:
|
|
528
|
+
"""Wait for scheduled best-effort writes when a host is shutting down.
|
|
529
|
+
|
|
530
|
+
The watcher never calls this on its normal prompt-delivery path. It gives
|
|
531
|
+
callers that own a temporary workspace a deterministic way to close it
|
|
532
|
+
only after the explicitly asynchronous telemetry writer is finished.
|
|
533
|
+
"""
|
|
534
|
+
deadline = monotonic() + max(0.0, timeout)
|
|
535
|
+
while True:
|
|
536
|
+
with _PENDING_WORKERS_LOCK:
|
|
537
|
+
workers = tuple(_PENDING_WORKERS)
|
|
538
|
+
if not workers:
|
|
539
|
+
return
|
|
540
|
+
remaining = deadline - monotonic()
|
|
541
|
+
if remaining <= 0:
|
|
542
|
+
return
|
|
543
|
+
for worker in workers:
|
|
544
|
+
worker.join(timeout=remaining)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def daily_statistics(root: Path, *, days: int = 90) -> list[dict[str, object]]:
|
|
548
|
+
"""Return generic daily aggregates, newest day first, for the private dashboard."""
|
|
549
|
+
if not 1 <= days <= 360:
|
|
550
|
+
raise ValueError("telemetry days must be between 1 and 360")
|
|
551
|
+
connection = open_storage(root)
|
|
552
|
+
try:
|
|
553
|
+
rows = connection.execute(
|
|
554
|
+
"""
|
|
555
|
+
SELECT execution_date, SUM(prompt_count), SUM(complete_count), SUM(blocked_count),
|
|
556
|
+
SUM(failed_count), AVG(average_execution_seconds), AVG(average_total_execution_seconds), AVG(average_queue_wait_seconds),
|
|
557
|
+
SUM(input_tokens), SUM(output_tokens), SUM(total_tokens)
|
|
558
|
+
FROM daily_execution_statistics
|
|
559
|
+
GROUP BY execution_date
|
|
560
|
+
ORDER BY execution_date DESC
|
|
561
|
+
LIMIT ?
|
|
562
|
+
""",
|
|
563
|
+
(days,),
|
|
564
|
+
).fetchall()
|
|
565
|
+
finally:
|
|
566
|
+
connection.close()
|
|
567
|
+
keys = (
|
|
568
|
+
"date", "prompt_count", "complete_count", "blocked_count", "failed_count",
|
|
569
|
+
"average_execution_seconds", "average_total_execution_seconds", "average_queue_wait_seconds", "input_tokens",
|
|
570
|
+
"output_tokens", "total_tokens",
|
|
571
|
+
)
|
|
572
|
+
result = [dict(zip(keys, row, strict=True)) for row in rows]
|
|
573
|
+
# Phase detail remains on demand. Keep the legacy trend shape stable
|
|
574
|
+
# without expanding the ninety-day refresh into per-day run projections.
|
|
575
|
+
for row in result:
|
|
576
|
+
row["average_provider_execution_seconds"] = None
|
|
577
|
+
row["average_validation_seconds"] = None
|
|
578
|
+
return result
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def prune_telemetry(root: Path, retention_days: int) -> dict[str, int]:
|
|
582
|
+
"""Remove expired rebuildable telemetry projections without deleting evidence."""
|
|
583
|
+
if retention_days not in {30, 60, 90, 120, 180, 360}:
|
|
584
|
+
raise ValueError("telemetry retention must be an approved interval")
|
|
585
|
+
cutoff = (datetime.now(timezone.utc) - timedelta(days=retention_days)).date().isoformat()
|
|
586
|
+
connection = open_storage(root)
|
|
587
|
+
try:
|
|
588
|
+
with connection:
|
|
589
|
+
daily_rows = connection.execute(
|
|
590
|
+
"DELETE FROM daily_execution_statistics WHERE execution_date < ?", (cutoff,)
|
|
591
|
+
).rowcount
|
|
592
|
+
run_rows = connection.execute(
|
|
593
|
+
"DELETE FROM execution_runs WHERE execution_date < ?", (cutoff,)
|
|
594
|
+
).rowcount
|
|
595
|
+
finally:
|
|
596
|
+
connection.close()
|
|
597
|
+
return {
|
|
598
|
+
"daily_statistics": max(0, int(daily_rows or 0)),
|
|
599
|
+
"execution_runs": max(0, int(run_rows or 0)),
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def clear_telemetry(root: Path) -> dict[str, int]:
|
|
604
|
+
"""Clear rebuildable telemetry projections without touching execution evidence.
|
|
605
|
+
|
|
606
|
+
Execution receipts, reports, prompt history and lifecycle checkpoints are
|
|
607
|
+
evidence records. They are intentionally outside this operator control;
|
|
608
|
+
only the local telemetry projections displayed by the dashboard are reset.
|
|
609
|
+
"""
|
|
610
|
+
connection = open_storage(root)
|
|
611
|
+
try:
|
|
612
|
+
with connection:
|
|
613
|
+
daily_rows = connection.execute("DELETE FROM daily_execution_statistics").rowcount
|
|
614
|
+
run_rows = connection.execute("DELETE FROM execution_runs").rowcount
|
|
615
|
+
finally:
|
|
616
|
+
connection.close()
|
|
617
|
+
return {
|
|
618
|
+
"daily_statistics": max(0, int(daily_rows or 0)),
|
|
619
|
+
"execution_runs": max(0, int(run_rows or 0)),
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
_DASHBOARD_PHASES = (
|
|
624
|
+
"QUEUE_WAIT", "SUBMISSION_CLAIM", "INITIALIZATION", "HOST_PREFLIGHT",
|
|
625
|
+
"WORKSPACE_PREFLIGHT", "CAPABILITY_PREFLIGHT", "EXECUTION_PREPARATION",
|
|
626
|
+
"PROVIDER_EXECUTION", "VALIDATION", "QUALITY_CONTROL", "REPAIR", "REPOSITORY_FINALIZATION",
|
|
627
|
+
"PR_OR_MERGE", "FINALIZATION", "REPORT_GENERATION", "EVIDENCE_PERSISTENCE",
|
|
628
|
+
"REPOSITORY_CLEANUP", "RECONCILIATION", "EXTERNAL_CI_WAIT",
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
# These are timing categories, not lifecycle authority. They let the
|
|
632
|
+
# advisory estimator reuse persisted phase evidence without altering the
|
|
633
|
+
# runner's state machine or inventing a path from incomplete history.
|
|
634
|
+
_LIFECYCLE_STEP_PHASES = {
|
|
635
|
+
"INITIALIZE": frozenset({"INITIALIZATION", "HOST_PREFLIGHT", "WORKSPACE_PREFLIGHT", "CAPABILITY_PREFLIGHT"}),
|
|
636
|
+
"EXECUTE_AGENT": frozenset({"EXECUTION_PREPARATION", "PROVIDER_EXECUTION", "VALIDATION"}),
|
|
637
|
+
"QUALITY_CONTROL_AGENT": frozenset({"QUALITY_CONTROL"}),
|
|
638
|
+
"REPAIR_AGENT": frozenset({"REPAIR"}),
|
|
639
|
+
"FINALIZE_AGENT": frozenset({"REPOSITORY_FINALIZATION", "FINALIZATION", "REPORT_GENERATION", "EVIDENCE_PERSISTENCE", "RECONCILIATION"}),
|
|
640
|
+
"REPOSITORY_CLEANUP": frozenset({"REPOSITORY_CLEANUP"}),
|
|
641
|
+
}
|
|
642
|
+
_MANAGED_ESTIMATE_PATH = ("INITIALIZE", "EXECUTE_AGENT", "QUALITY_CONTROL_AGENT", "REPAIR_AGENT", "WAIT_FOR_OPERATOR_MERGE", "FINALIZE_AGENT", "REPOSITORY_CLEANUP")
|
|
643
|
+
_GENESIS_ESTIMATE_PATH = ("INITIALIZE", "EXECUTE_AGENT", "REPAIR_AGENT", "FINALIZE_AGENT", "REPOSITORY_CLEANUP")
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def _remaining_steps(current_phase: object, execution_mode: object) -> tuple[str, ...]:
|
|
647
|
+
if not isinstance(current_phase, str):
|
|
648
|
+
return ()
|
|
649
|
+
path = _GENESIS_ESTIMATE_PATH if execution_mode == "GENESIS" else _MANAGED_ESTIMATE_PATH
|
|
650
|
+
if current_phase not in path:
|
|
651
|
+
return ()
|
|
652
|
+
remaining = tuple(step for step in path[path.index(current_phase):] if step in _LIFECYCLE_STEP_PHASES)
|
|
653
|
+
# Repair is conditional. Include it only after the runner has actually
|
|
654
|
+
# entered repair; otherwise a healthy run is not priced as a repair run.
|
|
655
|
+
return remaining if current_phase == "REPAIR_AGENT" else tuple(step for step in remaining if step != "REPAIR_AGENT")
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def _phase_step_durations(root: Path, run_id: str) -> dict[str, float]:
|
|
659
|
+
"""Return non-overlapping visible-step durations for one completed run."""
|
|
660
|
+
phase_to_step = {phase: step for step, phases in _LIFECYCLE_STEP_PHASES.items() for phase in phases}
|
|
661
|
+
connection = open_storage(root)
|
|
662
|
+
try:
|
|
663
|
+
rows = connection.execute(
|
|
664
|
+
"SELECT phase_id,phase_name,parent_phase_id,duration_ms,outcome FROM execution_phase_spans WHERE run_id=? ORDER BY ordinal",
|
|
665
|
+
(run_id,),
|
|
666
|
+
).fetchall()
|
|
667
|
+
finally:
|
|
668
|
+
connection.close()
|
|
669
|
+
by_id = {str(phase_id): (str(name), parent) for phase_id, name, parent, _, _ in rows}
|
|
670
|
+
totals: dict[str, float] = {}
|
|
671
|
+
for phase_id, name, parent, duration, outcome in rows:
|
|
672
|
+
if outcome == "ACTIVE" or not isinstance(duration, int) or duration < 0:
|
|
673
|
+
continue
|
|
674
|
+
step = phase_to_step.get(str(name))
|
|
675
|
+
if step is None:
|
|
676
|
+
continue
|
|
677
|
+
ancestor = parent
|
|
678
|
+
while isinstance(ancestor, str) and ancestor in by_id:
|
|
679
|
+
ancestor_name, ancestor = by_id[ancestor]
|
|
680
|
+
if phase_to_step.get(ancestor_name) == step:
|
|
681
|
+
break
|
|
682
|
+
else:
|
|
683
|
+
totals[step] = totals.get(step, 0.0) + duration / 1000
|
|
684
|
+
return totals
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
def _active_phase_elapsed_seconds(root: Path, run_id: object, current_phase: object, now: datetime) -> float:
|
|
688
|
+
if not isinstance(run_id, str) or not run_id or not isinstance(current_phase, str):
|
|
689
|
+
return 0.0
|
|
690
|
+
phase_names = _LIFECYCLE_STEP_PHASES.get(current_phase, frozenset())
|
|
691
|
+
if not phase_names:
|
|
692
|
+
return 0.0
|
|
693
|
+
connection = open_storage(root)
|
|
694
|
+
try:
|
|
695
|
+
rows = connection.execute(
|
|
696
|
+
"SELECT phase_name,started_at FROM execution_phase_spans WHERE run_id=? AND outcome='ACTIVE' ORDER BY ordinal DESC",
|
|
697
|
+
(run_id,),
|
|
698
|
+
).fetchall()
|
|
699
|
+
finally:
|
|
700
|
+
connection.close()
|
|
701
|
+
for phase_name, started_at in rows:
|
|
702
|
+
if phase_name not in phase_names or not isinstance(started_at, str):
|
|
703
|
+
continue
|
|
704
|
+
try:
|
|
705
|
+
started = datetime.fromisoformat(started_at.replace("Z", "+00:00"))
|
|
706
|
+
except ValueError:
|
|
707
|
+
continue
|
|
708
|
+
return max(0.0, (_utc(now) - _utc(started)).total_seconds())
|
|
709
|
+
return 0.0
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def daily_timing_detail(root: Path, execution_date: str) -> dict[str, object]:
|
|
713
|
+
"""Return a bounded, read-only UTC-day timing projection for the dashboard.
|
|
714
|
+
|
|
715
|
+
This deliberately composes ``timing_summary`` per persisted run, keeping
|
|
716
|
+
the browser a renderer and preserving the canonical non-double-counting
|
|
717
|
+
timing rules. ``execution_date`` is the UTC terminal-date already used by
|
|
718
|
+
the seven-day trend projection; the client only formats that stable value
|
|
719
|
+
in the local-user date style.
|
|
720
|
+
"""
|
|
721
|
+
try:
|
|
722
|
+
datetime.strptime(execution_date, "%Y-%m-%d")
|
|
723
|
+
except (TypeError, ValueError):
|
|
724
|
+
raise ValueError("execution date is invalid") from None
|
|
725
|
+
connection = open_storage(root)
|
|
726
|
+
try:
|
|
727
|
+
rows = connection.execute(
|
|
728
|
+
"""SELECT run_id, execution_started_at, terminal_state, total_execution_seconds,
|
|
729
|
+
queue_wait_seconds, runtime_provider, runtime_model, reasoning_profile,
|
|
730
|
+
producer_type, repository
|
|
731
|
+
FROM execution_runs WHERE execution_date=?
|
|
732
|
+
ORDER BY execution_started_at DESC LIMIT 250""",
|
|
733
|
+
(execution_date,),
|
|
734
|
+
).fetchall()
|
|
735
|
+
finally:
|
|
736
|
+
connection.close()
|
|
737
|
+
run_rows: list[dict[str, object]] = []
|
|
738
|
+
summaries: list[dict[str, object]] = []
|
|
739
|
+
for row in rows:
|
|
740
|
+
run_id = str(row[0])
|
|
741
|
+
summary = timing_summary(root, run_id)
|
|
742
|
+
summaries.append(summary)
|
|
743
|
+
phase_available = bool(summary.get("phase_telemetry_available"))
|
|
744
|
+
total = summary.get("total_wall_time_ms") if phase_available else (
|
|
745
|
+
round(float(row[3]) * 1000) if isinstance(row[3], (int, float)) else None
|
|
746
|
+
)
|
|
747
|
+
def measured(name: str) -> int | None:
|
|
748
|
+
value = summary.get(name)
|
|
749
|
+
return int(value) if phase_available and isinstance(value, int) else None
|
|
750
|
+
run_rows.append({
|
|
751
|
+
"run_id": run_id, "started_at": row[1], "status": row[2],
|
|
752
|
+
"total_duration_ms": total, "queue_wait_ms": measured("queue_wait_time_ms"),
|
|
753
|
+
"provider_duration_ms": measured("provider_execution_time_ms"),
|
|
754
|
+
"validation_duration_ms": measured("validation_time_ms"),
|
|
755
|
+
"external_wait_ms": measured("external_wait_time_ms"),
|
|
756
|
+
"report_generation_ms": measured("report_generation_time_ms"),
|
|
757
|
+
"evidence_persistence_ms": measured("evidence_persistence_time_ms"),
|
|
758
|
+
"largest_phase": summary.get("longest_phase") if phase_available else None,
|
|
759
|
+
"producer_type": row[8], "repository": row[9], "provider": row[5],
|
|
760
|
+
"model": row[6], "reasoning_profile": row[7],
|
|
761
|
+
"phase_telemetry": "RECORDED" if phase_available else "NOT_RECORDED",
|
|
762
|
+
})
|
|
763
|
+
def values(key: str) -> list[int]:
|
|
764
|
+
return [int(item[key]) for item in summaries if isinstance(item.get(key), int)]
|
|
765
|
+
def aggregate(items: list[int]) -> dict[str, int] | None:
|
|
766
|
+
return {"average_ms": round(mean(items)), "median_ms": round(median(items)), "total_ms": sum(items), "runs": len(items)} if items else None
|
|
767
|
+
totals = [int(row["total_duration_ms"]) for row in run_rows if isinstance(row["total_duration_ms"], int)]
|
|
768
|
+
summary = {
|
|
769
|
+
"executions": len(run_rows),
|
|
770
|
+
"completed": sum(row["status"] == "COMPLETE" for row in run_rows),
|
|
771
|
+
"blocked": sum(row["status"] == "BLOCKED" for row in run_rows),
|
|
772
|
+
"failed": sum(row["status"] == "FAILED" for row in run_rows),
|
|
773
|
+
"total_wall_time": aggregate(totals),
|
|
774
|
+
"active_processing_time": aggregate(values("active_ep_processing_time_ms")),
|
|
775
|
+
"queue_wait": aggregate(values("queue_wait_time_ms")),
|
|
776
|
+
"provider_execution": aggregate(values("provider_execution_time_ms")),
|
|
777
|
+
"validation": aggregate(values("validation_time_ms")),
|
|
778
|
+
"external_wait": aggregate(values("external_wait_time_ms")),
|
|
779
|
+
"overhead": aggregate(values("overhead_time_ms")),
|
|
780
|
+
"report_generation": aggregate(values("report_generation_time_ms")),
|
|
781
|
+
"evidence_persistence": aggregate(values("evidence_persistence_time_ms")),
|
|
782
|
+
}
|
|
783
|
+
phase_values: dict[str, list[int]] = {phase: [] for phase in _DASHBOARD_PHASES}
|
|
784
|
+
phase_share_values: dict[str, list[int]] = {phase: [] for phase in _DASHBOARD_PHASES}
|
|
785
|
+
for item in summaries:
|
|
786
|
+
# Use the shared run-level category projection. The dashboard must
|
|
787
|
+
# not reconstruct a competing aggregate from raw spans.
|
|
788
|
+
aggregates = item.get("phase_aggregates", ())
|
|
789
|
+
if isinstance(aggregates, list):
|
|
790
|
+
for aggregate_row in aggregates:
|
|
791
|
+
if not isinstance(aggregate_row, dict):
|
|
792
|
+
continue
|
|
793
|
+
phase, value = aggregate_row.get("phase"), aggregate_row.get("duration_ms")
|
|
794
|
+
if isinstance(phase, str) and phase in phase_values and isinstance(value, int):
|
|
795
|
+
phase_values[phase].append(value)
|
|
796
|
+
shares = item.get("phase_share_durations_ms", {})
|
|
797
|
+
if isinstance(shares, dict):
|
|
798
|
+
for phase, value in shares.items():
|
|
799
|
+
if isinstance(phase, str) and phase in phase_share_values and isinstance(value, int):
|
|
800
|
+
phase_share_values[phase].append(value)
|
|
801
|
+
phase_wall_time = sum(
|
|
802
|
+
int(item["total_wall_time_ms"])
|
|
803
|
+
for item in summaries
|
|
804
|
+
if item.get("phase_telemetry_available") and isinstance(item.get("total_wall_time_ms"), int)
|
|
805
|
+
)
|
|
806
|
+
phase_rows = [
|
|
807
|
+
dict(
|
|
808
|
+
{"phase": phase},
|
|
809
|
+
**aggregate(items),
|
|
810
|
+
# Raw durations retain nested and stale-span audit evidence. The
|
|
811
|
+
# percentage uses only each category's overlap with the run's
|
|
812
|
+
# TOTAL_EXECUTION envelope, so a single category cannot exceed
|
|
813
|
+
# 100% of total wall time.
|
|
814
|
+
share_percent=round(sum(phase_share_values[phase]) * 100 / phase_wall_time, 3) if phase_wall_time else None,
|
|
815
|
+
)
|
|
816
|
+
for phase, items in phase_values.items() if aggregate(items)
|
|
817
|
+
]
|
|
818
|
+
consumers = sorted(phase_rows, key=lambda item: (-int(item["total_ms"]), str(item["phase"])))[:3]
|
|
819
|
+
canonical_share_keys = {
|
|
820
|
+
"queue_wait": "queue_share_percent",
|
|
821
|
+
"provider_execution": "provider_share_percent",
|
|
822
|
+
"validation": "validation_share_percent",
|
|
823
|
+
"external_wait": "external_wait_share_percent",
|
|
824
|
+
"overhead": "overhead_share_percent",
|
|
825
|
+
}
|
|
826
|
+
return {
|
|
827
|
+
"date": execution_date, "timezone": "UTC", "summary": summary, "phases": phase_rows,
|
|
828
|
+
"bottlenecks": {"longest_average_phase": max(phase_rows, key=lambda item: int(item["average_ms"]), default=None),
|
|
829
|
+
"largest_accumulated_phase": max(phase_rows, key=lambda item: int(item["total_ms"]), default=None),
|
|
830
|
+
"top_time_consumers": consumers,
|
|
831
|
+
"shares": {
|
|
832
|
+
label: round(mean([int(item[key]) for item in summaries if isinstance(item.get(key), (int, float))]), 3)
|
|
833
|
+
if any(isinstance(item.get(key), (int, float)) for item in summaries) else None
|
|
834
|
+
for label, key in canonical_share_keys.items()
|
|
835
|
+
}},
|
|
836
|
+
"runs": run_rows, "phase_telemetry_available": any(bool(item.get("phase_telemetry_available")) for item in summaries),
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
def execution_timing(root: Path, run_id: str) -> dict[str, float | str]:
|
|
841
|
+
"""Return persisted timing and terminal timestamp evidence for one run."""
|
|
842
|
+
connection = open_storage(root)
|
|
843
|
+
try:
|
|
844
|
+
row = connection.execute(
|
|
845
|
+
"SELECT execution_seconds, total_execution_seconds, execution_finished_at FROM execution_runs WHERE run_id = ?",
|
|
846
|
+
(run_id,),
|
|
847
|
+
).fetchone()
|
|
848
|
+
finally:
|
|
849
|
+
connection.close()
|
|
850
|
+
if row is None:
|
|
851
|
+
return {}
|
|
852
|
+
result: dict[str, float | str] = {}
|
|
853
|
+
for key, value in zip(("execution_seconds", "total_execution_seconds"), row[:2], strict=True):
|
|
854
|
+
if isinstance(value, (int, float)) and not isinstance(value, bool) and value >= 0:
|
|
855
|
+
result[key] = float(value)
|
|
856
|
+
if isinstance(row[2], str):
|
|
857
|
+
result["finished_at"] = row[2]
|
|
858
|
+
return result
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def comparable_duration_estimate(
|
|
862
|
+
root: Path,
|
|
863
|
+
*,
|
|
864
|
+
prompt_characters: object,
|
|
865
|
+
runtime_metadata: object,
|
|
866
|
+
run_id: object = None,
|
|
867
|
+
current_phase: object = None,
|
|
868
|
+
execution_mode: object = None,
|
|
869
|
+
now: datetime | None = None,
|
|
870
|
+
) -> dict[str, float | int | str | bool]:
|
|
871
|
+
"""Return a robust size-adjusted estimate from one exact runtime profile.
|
|
872
|
+
|
|
873
|
+
This is intentionally advisory: it never affects scheduling or engineering
|
|
874
|
+
state. Missing or unreported profile fields yield no estimate rather than
|
|
875
|
+
mixing incomparable providers, models or reasoning settings.
|
|
876
|
+
"""
|
|
877
|
+
characters = _integer(prompt_characters)
|
|
878
|
+
if not characters or not isinstance(runtime_metadata, dict):
|
|
879
|
+
return {}
|
|
880
|
+
signature = tuple(
|
|
881
|
+
_runtime_value(runtime_metadata.get(key))
|
|
882
|
+
for key in ("runtime_provider", "model", "reasoning_profile", "configuration_profile")
|
|
883
|
+
)
|
|
884
|
+
if any(value is None for value in signature):
|
|
885
|
+
return {}
|
|
886
|
+
mode = _runtime_value(execution_mode)
|
|
887
|
+
query = """
|
|
888
|
+
SELECT run_id, execution_seconds, prompt_characters
|
|
889
|
+
FROM execution_runs
|
|
890
|
+
WHERE terminal_state = 'COMPLETE'
|
|
891
|
+
AND runtime_provider = ? AND runtime_model = ?
|
|
892
|
+
AND reasoning_profile = ? AND configuration_profile = ?
|
|
893
|
+
AND execution_seconds IS NOT NULL AND prompt_characters > 0
|
|
894
|
+
"""
|
|
895
|
+
parameters: tuple[object, ...] = signature
|
|
896
|
+
if mode:
|
|
897
|
+
query += " AND execution_mode = ?"
|
|
898
|
+
parameters += (mode,)
|
|
899
|
+
query += " ORDER BY execution_finished_at DESC LIMIT 20"
|
|
900
|
+
connection = open_storage(root)
|
|
901
|
+
try:
|
|
902
|
+
rows = connection.execute(query, parameters).fetchall()
|
|
903
|
+
finally:
|
|
904
|
+
connection.close()
|
|
905
|
+
# A linear character ratio turns a modestly larger prompt into an
|
|
906
|
+
# implausibly long estimate even though each run has fixed startup,
|
|
907
|
+
# validation and reporting work. Use a bounded square-root factor instead:
|
|
908
|
+
# it still reflects substantial input differences without letting sparse
|
|
909
|
+
# historical samples dominate the operator-facing indication.
|
|
910
|
+
scaled = [float(seconds) * min(1.6, max(0.7, sqrt(characters / int(size)))) for _, seconds, size in rows if seconds >= 0]
|
|
911
|
+
if len(scaled) < 2:
|
|
912
|
+
return {}
|
|
913
|
+
ordered = sorted(scaled)
|
|
914
|
+
# The observed spread gives an honest range while avoiding a single old
|
|
915
|
+
# outlier dominating the indicator. The arithmetic mean remains available
|
|
916
|
+
# as transparent diagnostic evidence for a fixed input and sample set.
|
|
917
|
+
lower = ordered[max(0, (len(ordered) - 1) // 4)]
|
|
918
|
+
upper = ordered[min(len(ordered) - 1, (len(ordered) - 1) * 3 // 4)]
|
|
919
|
+
result: dict[str, float | int | str | bool] = {
|
|
920
|
+
"sample_count": len(scaled),
|
|
921
|
+
"average_seconds": round(mean(scaled), 3),
|
|
922
|
+
"lower_seconds": round(min(lower, upper), 3),
|
|
923
|
+
"upper_seconds": round(max(lower, upper), 3),
|
|
924
|
+
"runtime_provider": signature[0],
|
|
925
|
+
"model": signature[1],
|
|
926
|
+
}
|
|
927
|
+
remaining_steps = _remaining_steps(current_phase, execution_mode)
|
|
928
|
+
if not remaining_steps:
|
|
929
|
+
return result
|
|
930
|
+
phase_samples: list[float] = []
|
|
931
|
+
active_elapsed = _active_phase_elapsed_seconds(root, run_id, current_phase, now or datetime.now(timezone.utc))
|
|
932
|
+
for historical_run_id, _, size in rows:
|
|
933
|
+
durations = _phase_step_durations(root, str(historical_run_id))
|
|
934
|
+
phase_seconds = sum(durations.get(step, 0.0) for step in remaining_steps)
|
|
935
|
+
if phase_seconds <= 0:
|
|
936
|
+
continue
|
|
937
|
+
scale = min(1.6, max(0.7, sqrt(characters / int(size))))
|
|
938
|
+
phase_samples.append(max(0.0, phase_seconds * scale - active_elapsed))
|
|
939
|
+
if len(phase_samples) < 2:
|
|
940
|
+
return result
|
|
941
|
+
ordered_phase_samples = sorted(phase_samples)
|
|
942
|
+
lower_phase = ordered_phase_samples[max(0, (len(ordered_phase_samples) - 1) // 4)]
|
|
943
|
+
upper_phase = ordered_phase_samples[min(len(ordered_phase_samples) - 1, (len(ordered_phase_samples) - 1) * 3 // 4)]
|
|
944
|
+
result.update({
|
|
945
|
+
"phase_aware": True,
|
|
946
|
+
"phase_sample_count": len(phase_samples),
|
|
947
|
+
"remaining_lower_seconds": round(min(lower_phase, upper_phase), 3),
|
|
948
|
+
"remaining_upper_seconds": round(max(lower_phase, upper_phase), 3),
|
|
949
|
+
"active_phase_elapsed_seconds": round(active_elapsed, 3),
|
|
950
|
+
})
|
|
951
|
+
return result
|