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,218 @@
|
|
|
1
|
+
"""Versioned execution-activity evidence with explicit scope boundaries.
|
|
2
|
+
|
|
3
|
+
Only aggregate counters and Git-derived path metadata are retained. Raw
|
|
4
|
+
commands, prompts, output, tokens, and workspace paths are deliberately out
|
|
5
|
+
of scope for this projection.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from .storage import EngineeringStorageError, open_storage
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
EXECUTION_ACTIVITY_SUMMARY_VERSION = 1
|
|
17
|
+
CODEX_COMMAND_DEFINITION = (
|
|
18
|
+
"One persisted Codex CLI provider invocation. It is not a shell command, "
|
|
19
|
+
"tool call, prompt, token count, or GitHub API request."
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _git(root: Path, *args: str) -> str | None:
|
|
24
|
+
"""Use a small, read-only Git command without retaining its output."""
|
|
25
|
+
from .providers import GitProvider
|
|
26
|
+
try:
|
|
27
|
+
result = GitProvider().execute(root, "git", *args)
|
|
28
|
+
except OSError:
|
|
29
|
+
return None
|
|
30
|
+
return result.stdout.strip() if result.returncode == 0 else None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def live_worktree_snapshot(root: Path) -> dict[str, object]:
|
|
34
|
+
"""Return a volatile current-uncommitted-state snapshot for active runs."""
|
|
35
|
+
status = _git(root, "status", "--porcelain", "--untracked-files=all")
|
|
36
|
+
counts = {"modified": 0, "added": 0, "deleted": 0}
|
|
37
|
+
if status is None:
|
|
38
|
+
availability = "UNAVAILABLE"
|
|
39
|
+
else:
|
|
40
|
+
availability = "AVAILABLE"
|
|
41
|
+
for row in status.splitlines():
|
|
42
|
+
code = row[:2]
|
|
43
|
+
if "D" in code:
|
|
44
|
+
counts["deleted"] += 1
|
|
45
|
+
elif "A" in code or "?" in code:
|
|
46
|
+
counts["added"] += 1
|
|
47
|
+
elif code.strip():
|
|
48
|
+
counts["modified"] += 1
|
|
49
|
+
return {
|
|
50
|
+
"kind": "LIVE_WORKTREE_SNAPSHOT",
|
|
51
|
+
"volatile": True,
|
|
52
|
+
"observed_at": datetime.now(timezone.utc).isoformat(),
|
|
53
|
+
"availability": availability,
|
|
54
|
+
"worktree_identity": "managed_target_checkout",
|
|
55
|
+
"branch": _git(root, "branch", "--show-current") or "UNAVAILABLE",
|
|
56
|
+
"head_sha": _git(root, "rev-parse", "HEAD") or "UNAVAILABLE",
|
|
57
|
+
"uncommitted": counts,
|
|
58
|
+
"meaning": "Current uncommitted worktree state only; never cumulative run delivery.",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _path_sets(names: str | None) -> dict[str, object]:
|
|
63
|
+
added: set[str] = set()
|
|
64
|
+
modified: set[str] = set()
|
|
65
|
+
removed: set[str] = set()
|
|
66
|
+
renamed: list[dict[str, str]] = []
|
|
67
|
+
if names:
|
|
68
|
+
for row in names.splitlines():
|
|
69
|
+
fields = row.split("\t")
|
|
70
|
+
if len(fields) < 2:
|
|
71
|
+
continue
|
|
72
|
+
status = fields[0]
|
|
73
|
+
if status.startswith("R") and len(fields) >= 3:
|
|
74
|
+
renamed.append({"from": fields[1], "to": fields[2]})
|
|
75
|
+
elif status.startswith("A"):
|
|
76
|
+
added.add(fields[1])
|
|
77
|
+
elif status.startswith("D"):
|
|
78
|
+
removed.add(fields[1])
|
|
79
|
+
else:
|
|
80
|
+
modified.add(fields[1])
|
|
81
|
+
unique = added | modified | removed | {item["from"] for item in renamed} | {item["to"] for item in renamed}
|
|
82
|
+
return {
|
|
83
|
+
"added": sorted(added), "modified": sorted(modified), "removed": sorted(removed),
|
|
84
|
+
"renamed": sorted(renamed, key=lambda item: (item["from"], item["to"])),
|
|
85
|
+
"total_unique_changed_paths": len(unique),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _phase_paths(root: Path, baseline: str | None, commit: str | None) -> dict[str, object] | None:
|
|
90
|
+
if not baseline or not commit or _git(root, "rev-parse", commit) is None:
|
|
91
|
+
return None
|
|
92
|
+
return _path_sets(_git(root, "diff", "--name-status", "-M", baseline, commit))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _command_activity(root: Path, run_id: str) -> dict[str, object]:
|
|
96
|
+
connection = open_storage(root)
|
|
97
|
+
try:
|
|
98
|
+
provider_rows = connection.execute(
|
|
99
|
+
"SELECT phase,role,COUNT(*) FROM provider_invocations "
|
|
100
|
+
"WHERE run_id=? AND provider='codex_cli' GROUP BY phase,role", (run_id,)
|
|
101
|
+
).fetchall()
|
|
102
|
+
validation_rows = connection.execute(
|
|
103
|
+
"SELECT COUNT(*) FROM execution_validation_command_invocations WHERE run_id=?", (run_id,)
|
|
104
|
+
).fetchone()
|
|
105
|
+
finally:
|
|
106
|
+
connection.close()
|
|
107
|
+
primary: dict[str, int] = {}
|
|
108
|
+
reviewers: dict[str, dict[str, int]] = {}
|
|
109
|
+
for phase, role, count in provider_rows:
|
|
110
|
+
if not isinstance(phase, str) or not isinstance(role, str) or not isinstance(count, int):
|
|
111
|
+
continue
|
|
112
|
+
if role.casefold().startswith("reviewer"):
|
|
113
|
+
reviewers.setdefault(role, {})[phase] = count
|
|
114
|
+
else:
|
|
115
|
+
primary[phase] = primary.get(phase, 0) + count
|
|
116
|
+
reviewer_total = sum(sum(phases.values()) for phases in reviewers.values())
|
|
117
|
+
primary_total = sum(primary.values())
|
|
118
|
+
host_validation_total = int(validation_rows[0]) if validation_rows and isinstance(validation_rows[0], int) else 0
|
|
119
|
+
return {
|
|
120
|
+
"metric_definition_version": 1,
|
|
121
|
+
"codex_command_definition": CODEX_COMMAND_DEFINITION,
|
|
122
|
+
"primary_codex_commands_by_phase": primary,
|
|
123
|
+
"primary_codex_commands_total": primary_total,
|
|
124
|
+
"reviewer_codex_commands_by_reviewer_and_phase": reviewers,
|
|
125
|
+
"reviewer_codex_commands_total": reviewer_total,
|
|
126
|
+
"host_validation_commands_by_phase": {"VALIDATION": host_validation_total},
|
|
127
|
+
"host_validation_commands_total": host_validation_total,
|
|
128
|
+
"overall_activity_total": primary_total + reviewer_total + host_validation_total,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def cumulative_activity(root: Path, run_id: str) -> dict[str, object]:
|
|
133
|
+
"""Project current persisted counters using the terminal metric definition."""
|
|
134
|
+
return _command_activity(root, run_id)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def build_terminal_activity_summary(root: Path, state: object, bundle: object) -> dict[str, object]:
|
|
138
|
+
"""Build the canonical terminal summary solely from persisted/Git evidence."""
|
|
139
|
+
run_id = str(getattr(state, "run_id"))
|
|
140
|
+
target = Path(str(getattr(bundle, "target_workspace")))
|
|
141
|
+
# The Evidence Bundle owns the authoritative transaction range. Do not
|
|
142
|
+
# recompute it from a later lifecycle commit: that can exclude an earlier
|
|
143
|
+
# implementation merge or silently turn a known range into a different one.
|
|
144
|
+
baseline = getattr(bundle, "transaction_baseline_sha", None)
|
|
145
|
+
if not isinstance(baseline, str) or not baseline:
|
|
146
|
+
baseline = None
|
|
147
|
+
target_sha = str(getattr(bundle, "target_commit"))
|
|
148
|
+
renamed = [
|
|
149
|
+
{"from": source, "to": destination}
|
|
150
|
+
for source, destination in getattr(bundle, "files_renamed", ())
|
|
151
|
+
if isinstance(source, str) and isinstance(destination, str)
|
|
152
|
+
]
|
|
153
|
+
delivery = {
|
|
154
|
+
"added": sorted(set(getattr(bundle, "files_added", ()))),
|
|
155
|
+
"modified": sorted(set(getattr(bundle, "files_modified", ()))),
|
|
156
|
+
"removed": sorted(set(getattr(bundle, "files_removed", ()))),
|
|
157
|
+
"renamed": sorted(renamed, key=lambda item: (item["from"], item["to"])),
|
|
158
|
+
# Evidence Bundle changed_files is the canonical run-delivery path
|
|
159
|
+
# count, including both sides of a proven rename.
|
|
160
|
+
"total_unique_changed_paths": len(set(getattr(bundle, "changed_files", ()))),
|
|
161
|
+
}
|
|
162
|
+
implementation = getattr(state, "implementation_merge_commit", None)
|
|
163
|
+
finalization = getattr(state, "finalization_merge_commit", None)
|
|
164
|
+
phase_attribution: dict[str, object] = {}
|
|
165
|
+
implementation_parent = _git(target, "rev-parse", f"{implementation}^") if implementation else None
|
|
166
|
+
if implementation:
|
|
167
|
+
phase_attribution["implementation"] = _phase_paths(target, implementation_parent, implementation) or "UNAVAILABLE"
|
|
168
|
+
if finalization:
|
|
169
|
+
phase_attribution["finalization"] = _phase_paths(target, implementation, finalization) or "UNAVAILABLE"
|
|
170
|
+
return {
|
|
171
|
+
"summary_version": EXECUTION_ACTIVITY_SUMMARY_VERSION,
|
|
172
|
+
"run_id": run_id,
|
|
173
|
+
"activity": _command_activity(root, run_id),
|
|
174
|
+
"terminal_delivery_diff": {
|
|
175
|
+
"authority": "AUTHORITATIVE_REPOSITORY_EVIDENCE",
|
|
176
|
+
"transaction_baseline_sha": baseline or "UNAVAILABLE",
|
|
177
|
+
"terminal_target_sha": target_sha,
|
|
178
|
+
**delivery,
|
|
179
|
+
"phase_attribution": phase_attribution or "UNAVAILABLE",
|
|
180
|
+
"implementation_pr": getattr(state, "implementation_pull_request", None),
|
|
181
|
+
"finalization_pr": getattr(state, "finalization_pull_request", None),
|
|
182
|
+
"reconciliation_pr": getattr(state, "reconciliation_pull_request", None),
|
|
183
|
+
"implementation_merge_commit": implementation,
|
|
184
|
+
"finalization_merge_commit": finalization,
|
|
185
|
+
"per_pr_changed_file_counts": "GitHub evidence scoped to each PR; never summed into this run total.",
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def persist_terminal_activity_summary(root: Path, summary: dict[str, object]) -> dict[str, object]:
|
|
191
|
+
"""Insert once; legacy runs remain unavailable rather than inferred."""
|
|
192
|
+
run_id = summary.get("run_id")
|
|
193
|
+
if not isinstance(run_id, str):
|
|
194
|
+
raise EngineeringStorageError("Execution activity summary run identity is invalid.")
|
|
195
|
+
payload = json.dumps(summary, sort_keys=True, separators=(",", ":"))
|
|
196
|
+
connection = open_storage(root)
|
|
197
|
+
try:
|
|
198
|
+
connection.execute(
|
|
199
|
+
"INSERT OR IGNORE INTO execution_activity_summaries(run_id,summary_version,payload,persisted_at) VALUES(?,?,?,?)",
|
|
200
|
+
(run_id, EXECUTION_ACTIVITY_SUMMARY_VERSION, payload, datetime.now(timezone.utc).isoformat()),
|
|
201
|
+
)
|
|
202
|
+
row = connection.execute("SELECT payload FROM execution_activity_summaries WHERE run_id=?", (run_id,)).fetchone()
|
|
203
|
+
finally:
|
|
204
|
+
connection.close()
|
|
205
|
+
return json.loads(row[0]) if row else summary
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def terminal_activity_summary(root: Path, run_id: str) -> dict[str, object] | None:
|
|
209
|
+
"""Return only a persisted canonical summary; never reconstruct legacy rows."""
|
|
210
|
+
try:
|
|
211
|
+
connection = open_storage(root)
|
|
212
|
+
try:
|
|
213
|
+
row = connection.execute("SELECT payload FROM execution_activity_summaries WHERE run_id=?", (run_id,)).fetchone()
|
|
214
|
+
finally:
|
|
215
|
+
connection.close()
|
|
216
|
+
return json.loads(row[0]) if row and isinstance(row[0], str) else None
|
|
217
|
+
except (EngineeringStorageError, json.JSONDecodeError):
|
|
218
|
+
return None
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Typed execution selection resolved before lifecycle admission."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .execution_errors import RunnerError
|
|
9
|
+
from .platform_api import PlatformConfiguration, PlatformConfigurationError
|
|
10
|
+
from .providers import GitProvider
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class ExecutionContext:
|
|
15
|
+
execution_mode: str
|
|
16
|
+
host_repository: Path
|
|
17
|
+
target_repository: Path | None
|
|
18
|
+
lifecycle_policy: str
|
|
19
|
+
selected_preflight: str
|
|
20
|
+
action_intent: str = "MUTATING_DELIVERY"
|
|
21
|
+
run_id: str | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _prompt_field_values(objective: str, label: str) -> tuple[str, ...]:
|
|
25
|
+
values: list[str] = []
|
|
26
|
+
lines = objective.splitlines()
|
|
27
|
+
for index, line in enumerate(lines):
|
|
28
|
+
candidate = line.strip()
|
|
29
|
+
prefix = f"{label}:"
|
|
30
|
+
if not candidate.casefold().startswith(prefix.casefold()):
|
|
31
|
+
continue
|
|
32
|
+
inline = candidate[len(prefix):].strip()
|
|
33
|
+
if inline:
|
|
34
|
+
values.append(inline)
|
|
35
|
+
continue
|
|
36
|
+
for candidate in lines[index + 1 :]:
|
|
37
|
+
value = candidate.strip()
|
|
38
|
+
if value:
|
|
39
|
+
values.append(value)
|
|
40
|
+
break
|
|
41
|
+
return tuple(values)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
ACTION_INTENTS = frozenset({"MUTATING_DELIVERY", "VALIDATION_ONLY"})
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def resolve_execution_context(
|
|
48
|
+
objective: str, host_repository: Path, *, action_intent: str = "MUTATING_DELIVERY"
|
|
49
|
+
) -> ExecutionContext:
|
|
50
|
+
"""Resolve the mode and selected target before lifecycle admission."""
|
|
51
|
+
if action_intent not in ACTION_INTENTS:
|
|
52
|
+
raise RunnerError("Execution action intent is invalid.")
|
|
53
|
+
modes = {line.split(":", 1)[1].strip().casefold() for line in objective.splitlines() if line.strip().casefold().startswith("execution mode:")}
|
|
54
|
+
if "genesis" not in modes:
|
|
55
|
+
return ExecutionContext("MANAGED", host_repository.resolve(), None, "managed", "managed_readiness", action_intent)
|
|
56
|
+
if modes != {"genesis"}:
|
|
57
|
+
raise RunnerError("Execution Mode: Genesis conflicts with another execution mode declaration.")
|
|
58
|
+
targets = _prompt_field_values(objective, "Target repository")
|
|
59
|
+
if not targets:
|
|
60
|
+
raise RunnerError("Genesis preflight blocked: prompt must declare one absolute Target repository path.")
|
|
61
|
+
if len(set(targets)) != 1:
|
|
62
|
+
raise RunnerError("Genesis preflight blocked: prompt declares conflicting Target repository paths.")
|
|
63
|
+
target = Path(targets[0]).expanduser()
|
|
64
|
+
if not target.is_absolute():
|
|
65
|
+
raise RunnerError("Genesis preflight blocked: Target repository path must be absolute.")
|
|
66
|
+
target = target.resolve()
|
|
67
|
+
if target == host_repository.resolve():
|
|
68
|
+
raise RunnerError("Genesis preflight blocked: Target repository cannot be the Engineering Platform host repository.")
|
|
69
|
+
return ExecutionContext("GENESIS", host_repository.resolve(), target, "local_only", "genesis_git_workspace", action_intent)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def execution_mode_for(objective: str) -> str:
|
|
73
|
+
return "GENESIS" if any(line.strip().casefold() == "execution mode: genesis" for line in objective.splitlines()) else "MANAGED"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def genesis_target_for(objective: str) -> Path | None:
|
|
77
|
+
targets = _prompt_field_values(objective, "Target repository")
|
|
78
|
+
return Path(targets[0]).expanduser() if len(set(targets)) == 1 else None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def genesis_workspace_preflight(target: Path | None) -> str | None:
|
|
82
|
+
"""Return a bounded, non-destructive Genesis workspace diagnostic."""
|
|
83
|
+
if target is None:
|
|
84
|
+
return "Genesis preflight blocked: prompt must declare an absolute Target repository path."
|
|
85
|
+
if not target.is_absolute() or not target.is_dir():
|
|
86
|
+
return "Genesis preflight blocked: Target repository path is absent or not a directory."
|
|
87
|
+
observed = GitProvider().execute(target, "git", "rev-parse", "--git-dir")
|
|
88
|
+
if observed.returncode:
|
|
89
|
+
return "Genesis preflight blocked: Target repository is not an accessible Git repository."
|
|
90
|
+
git_dir = Path(observed.stdout.strip())
|
|
91
|
+
if not git_dir.is_absolute():
|
|
92
|
+
git_dir = (target / git_dir).resolve()
|
|
93
|
+
if (git_dir / "index.lock").exists():
|
|
94
|
+
return f"Genesis preflight blocked: Git index lock exists at {git_dir / 'index.lock'}; it is not removed automatically."
|
|
95
|
+
if not os.access(git_dir, os.W_OK):
|
|
96
|
+
return f"Genesis preflight blocked: Git metadata directory is not writable: {git_dir}."
|
|
97
|
+
status = GitProvider().execute(target, "git", "status", "--porcelain", "--untracked-files=all")
|
|
98
|
+
if status.returncode:
|
|
99
|
+
return "Genesis preflight blocked: Target repository status could not be inspected."
|
|
100
|
+
return "Genesis preflight blocked: Target repository has tracked or untracked changes." if status.stdout.strip() else None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def additional_workspace_write_roots(root: Path) -> tuple[Path, ...]:
|
|
104
|
+
"""Resolve only configured, non-symlink Genesis write roots."""
|
|
105
|
+
if not (root / ".engineering" / "engineering-platform.local.json").is_file():
|
|
106
|
+
return ()
|
|
107
|
+
try:
|
|
108
|
+
policy = PlatformConfiguration.load(root).resolve_repository_authorization_policy()
|
|
109
|
+
except PlatformConfigurationError as error:
|
|
110
|
+
raise RunnerError(str(error)) from error
|
|
111
|
+
roots: list[Path] = []
|
|
112
|
+
for configured in policy.allowed_roots:
|
|
113
|
+
candidate = Path(configured.path).expanduser()
|
|
114
|
+
if candidate.is_symlink() or not candidate.is_dir() or candidate == Path(candidate.anchor):
|
|
115
|
+
raise RunnerError("Configured Engineering Workspace Root must be an existing non-root directory, not a symlink.")
|
|
116
|
+
roots.append(candidate.resolve())
|
|
117
|
+
for configured in policy.allowed_repositories:
|
|
118
|
+
candidate = Path(configured).expanduser()
|
|
119
|
+
if candidate.is_symlink() or not candidate.is_dir():
|
|
120
|
+
raise RunnerError("Configured Engineering Repository Allow-List entry must be an existing directory, not a symlink.")
|
|
121
|
+
roots.append(candidate.resolve().parent)
|
|
122
|
+
return tuple(dict.fromkeys(roots))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def target_repository_authorization(root: Path, target: Path) -> str | None:
|
|
126
|
+
try:
|
|
127
|
+
authorization = PlatformConfiguration.load(root).authorize_target_repository(target, "GENESIS")
|
|
128
|
+
except PlatformConfigurationError as error:
|
|
129
|
+
return f"Genesis preflight blocked: {error}"
|
|
130
|
+
if authorization.authorized:
|
|
131
|
+
return None
|
|
132
|
+
return f"Genesis preflight blocked: WORKSPACE_TARGET_AUTHORIZED: {authorization.reason} Recovery: {authorization.recovery}"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Normalized errors shared by Execution Host responsibility modules."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RunnerError(RuntimeError):
|
|
6
|
+
"""A fail-closed engineering-runner diagnostic."""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CodexInvocationError(RunnerError):
|
|
10
|
+
"""Separates safe checkpoint diagnostics from bounded console evidence."""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
persistent_diagnostic: str,
|
|
15
|
+
console_detail: str,
|
|
16
|
+
*,
|
|
17
|
+
next_action: str = "inspect_codex_cli",
|
|
18
|
+
terminal_condition: str = "codex_invocation_failed",
|
|
19
|
+
interruption_reason: str | None = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
super().__init__(persistent_diagnostic)
|
|
22
|
+
self.console_detail = console_detail
|
|
23
|
+
self.next_action = next_action
|
|
24
|
+
self.terminal_condition = terminal_condition
|
|
25
|
+
self.interruption_reason = interruption_reason
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def provider_turn_interrupted(self) -> bool:
|
|
29
|
+
"""Whether provider evidence proves a turn ended without a result."""
|
|
30
|
+
return self.terminal_condition == "provider_turn_interrupted" and bool(self.interruption_reason)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CodexHandoffTimeout(RunnerError):
|
|
34
|
+
"""A bounded agent hand-off exceeded its host-owned deadline."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ProviderReadinessBlocked(RunnerError):
|
|
38
|
+
"""A durable checkpoint is waiting for explicit local provider recovery."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, state: object) -> None:
|
|
41
|
+
super().__init__("Provider readiness must be repaired before this action can continue.")
|
|
42
|
+
self.state = state
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Typed, read-only terminal evidence shared by reporting and lifecycle coordination."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class TerminalEvidenceBundle:
|
|
9
|
+
target_workspace: str
|
|
10
|
+
target_repository: str
|
|
11
|
+
target_branch: str
|
|
12
|
+
target_commit: str
|
|
13
|
+
worktree_state: str
|
|
14
|
+
changed_files: tuple[str, ...]
|
|
15
|
+
files_added: tuple[str, ...]
|
|
16
|
+
files_modified: tuple[str, ...]
|
|
17
|
+
files_removed: tuple[str, ...]
|
|
18
|
+
files_renamed: tuple[tuple[str, str], ...]
|
|
19
|
+
diff_check: str
|
|
20
|
+
transaction_baseline: str
|
|
21
|
+
transaction_baseline_sha: str | None
|
|
22
|
+
resulting_commit: str | None
|
|
23
|
+
lease: dict[str, object]
|
|
24
|
+
readiness: dict[str, object] | None
|