engineering-platform 2.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. engineering_platform/ENGINEERING_PLATFORM_CONFIG.json +32 -0
  2. engineering_platform/ENGINEERING_PLATFORM_VERSION.json +15 -0
  3. engineering_platform/__init__.py +1 -0
  4. engineering_platform/__main__.py +7 -0
  5. engineering_platform/agent_state.py +530 -0
  6. engineering_platform/agent_trust.py +174 -0
  7. engineering_platform/assets/dashboard.css +1317 -0
  8. engineering_platform/assets/dashboard.js +8534 -0
  9. engineering_platform/assets/dashboard_locales.mjs +4049 -0
  10. engineering_platform/assets/dashboard_status_store.mjs +41 -0
  11. engineering_platform/assets/operations-console/apple-touch-icon-dark.png +0 -0
  12. engineering_platform/assets/operations-console/apple-touch-icon-light.png +0 -0
  13. engineering_platform/assets/operations-console/icon-dark.png +0 -0
  14. engineering_platform/assets/operations-console/icon-light.png +0 -0
  15. engineering_platform/assets/operations-console/icon-transparent.png +0 -0
  16. engineering_platform/assets/operations-console/manifest.webmanifest +11 -0
  17. engineering_platform/capability_preflight.py +285 -0
  18. engineering_platform/capability_review.py +261 -0
  19. engineering_platform/central_data_transfer.py +195 -0
  20. engineering_platform/central_database.py +245 -0
  21. engineering_platform/central_store_migration.py +1672 -0
  22. engineering_platform/codex_capacity.py +81 -0
  23. engineering_platform/codex_chat.py +226 -0
  24. engineering_platform/codex_observability.py +153 -0
  25. engineering_platform/component_lock.py +40 -0
  26. engineering_platform/component_logging.py +420 -0
  27. engineering_platform/console_presentation.py +14 -0
  28. engineering_platform/console_route_ownership.py +83 -0
  29. engineering_platform/contracts/__init__.py +38 -0
  30. engineering_platform/contracts/ep_consumer.py +391 -0
  31. engineering_platform/contracts/models.py +105 -0
  32. engineering_platform/contracts/projection.py +401 -0
  33. engineering_platform/dashboard_browser_validation.py +206 -0
  34. engineering_platform/dashboard_state.py +630 -0
  35. engineering_platform/dashboard_supervisor.swift +105 -0
  36. engineering_platform/dashboard_translation.py +129 -0
  37. engineering_platform/dependabot_producer.py +349 -0
  38. engineering_platform/drift_diagnostics.py +144 -0
  39. engineering_platform/emergency_recovery.py +268 -0
  40. engineering_platform/engineering_memory.py +139 -0
  41. engineering_platform/ep_consumer_credentials.py +473 -0
  42. engineering_platform/evidence_projection.py +213 -0
  43. engineering_platform/execution_activity.py +218 -0
  44. engineering_platform/execution_context.py +132 -0
  45. engineering_platform/execution_errors.py +42 -0
  46. engineering_platform/execution_evidence.py +24 -0
  47. engineering_platform/execution_executor.py +730 -0
  48. engineering_platform/execution_finalization.py +44 -0
  49. engineering_platform/execution_host.py +3306 -0
  50. engineering_platform/execution_lease.py +365 -0
  51. engineering_platform/execution_lifecycle.py +447 -0
  52. engineering_platform/execution_models.py +43 -0
  53. engineering_platform/execution_readiness.py +166 -0
  54. engineering_platform/execution_reporting.py +1607 -0
  55. engineering_platform/execution_repository.py +253 -0
  56. engineering_platform/execution_timeout_policy.py +56 -0
  57. engineering_platform/execution_timing.py +440 -0
  58. engineering_platform/execution_transaction.py +28 -0
  59. engineering_platform/external_producer_binding.py +235 -0
  60. engineering_platform/file_inbox.py +249 -0
  61. engineering_platform/forensic_attribution.py +338 -0
  62. engineering_platform/forensic_attribution_v2.py +134 -0
  63. engineering_platform/forensic_delta.py +299 -0
  64. engineering_platform/golden_scenario.py +63 -0
  65. engineering_platform/historical_dashboard_configuration.py +171 -0
  66. engineering_platform/host_admin.py +199 -0
  67. engineering_platform/host_preflight.py +231 -0
  68. engineering_platform/installation_relocation.py +122 -0
  69. engineering_platform/investigation_ledger.py +89 -0
  70. engineering_platform/legacy_inbox_migration.py +79 -0
  71. engineering_platform/lifecycle_worker.py +223 -0
  72. engineering_platform/live_status.py +267 -0
  73. engineering_platform/local_api.py +209 -0
  74. engineering_platform/local_api_keychain.py +51 -0
  75. engineering_platform/local_repository_binding.py +138 -0
  76. engineering_platform/managed_autonomy.py +509 -0
  77. engineering_platform/managed_codex_runtime.py +105 -0
  78. engineering_platform/parity_context.py +203 -0
  79. engineering_platform/parity_lifecycle_dispatcher.py +488 -0
  80. engineering_platform/platform_admin.py +13 -0
  81. engineering_platform/platform_api.py +428 -0
  82. engineering_platform/platform_bootstrap.py +385 -0
  83. engineering_platform/platform_components.py +65 -0
  84. engineering_platform/platform_version.py +171 -0
  85. engineering_platform/pr_check_repair.py +276 -0
  86. engineering_platform/pr_evidence_backfill.py +278 -0
  87. engineering_platform/producer.py +209 -0
  88. engineering_platform/project_agent.py +366 -0
  89. engineering_platform/project_agent_service.py +244 -0
  90. engineering_platform/project_topology.py +126 -0
  91. engineering_platform/prompt_history.py +591 -0
  92. engineering_platform/provider_context.py +136 -0
  93. engineering_platform/provider_context_benchmark.py +41 -0
  94. engineering_platform/provider_context_scope.py +90 -0
  95. engineering_platform/provider_interruption.py +168 -0
  96. engineering_platform/provider_process_identity.py +80 -0
  97. engineering_platform/provider_readiness.py +138 -0
  98. engineering_platform/provider_recovery.py +647 -0
  99. engineering_platform/provider_usage.py +497 -0
  100. engineering_platform/providers.py +471 -0
  101. engineering_platform/qualification.py +220 -0
  102. engineering_platform/recommendation_handoff.py +238 -0
  103. engineering_platform/report_analysis.py +193 -0
  104. engineering_platform/repository_attachment.py +171 -0
  105. engineering_platform/repository_handoff.py +95 -0
  106. engineering_platform/resources.py +38 -0
  107. engineering_platform/reviewer_evidence.py +70 -0
  108. engineering_platform/schemas/repository-attachment.schema.json +61 -0
  109. engineering_platform/server.py +3679 -0
  110. engineering_platform/server_console_services.py +2024 -0
  111. engineering_platform/server_relay.py +172 -0
  112. engineering_platform/server_service.py +122 -0
  113. engineering_platform/status_model.py +135 -0
  114. engineering_platform/status_reconciliation.py +34 -0
  115. engineering_platform/storage.py +2440 -0
  116. engineering_platform/submission_cli.py +77 -0
  117. engineering_platform/submission_intake.py +45 -0
  118. engineering_platform/submission_service.py +317 -0
  119. engineering_platform/telemetry.py +951 -0
  120. engineering_platform/templates/workspace-config.json +25 -0
  121. engineering_platform/validation_identity.py +50 -0
  122. engineering_platform/validation_profile.py +211 -0
  123. engineering_platform/workspace_preflight.py +263 -0
  124. engineering_platform/worktree_provenance.py +147 -0
  125. engineering_platform/worktree_tooling.py +18 -0
  126. engineering_platform-2.2.0.dist-info/METADATA +18 -0
  127. engineering_platform-2.2.0.dist-info/RECORD +130 -0
  128. engineering_platform-2.2.0.dist-info/WHEEL +5 -0
  129. engineering_platform-2.2.0.dist-info/entry_points.txt +6 -0
  130. engineering_platform-2.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,144 @@
1
+ """Immutable, read-only diagnostic evidence for failed host qualification checks.
2
+
3
+ This module deliberately translates existing qualification results only. It
4
+ does not decide whether execution is admitted, retried, or resumed.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import asdict, dataclass
10
+ from datetime import datetime, timezone
11
+ import json
12
+ import os
13
+ from pathlib import Path
14
+ import tempfile
15
+ import uuid
16
+ from typing import Iterable
17
+
18
+
19
+ DRIFT_CATEGORIES = frozenset({
20
+ "Runtime Database", "Runtime Identity", "Runtime Schema",
21
+ "Execution Host Version", "Bootstrap Contract", "Checkpoint Format",
22
+ "Memory Format", "Report Format", "Configuration", "Workspace",
23
+ "Repository", "Capability", "Producer Contract", "Execution Policy",
24
+ })
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class DriftEvidence:
29
+ drift_id: str
30
+ category: str
31
+ severity: str
32
+ expected_value: str
33
+ observed_value: str
34
+ resolution_recommendation: str
35
+ detection_timestamp: str
36
+ qualification_stage: str
37
+ affected_component: str
38
+ affected_repository: str
39
+ affected_runtime: str
40
+
41
+ def payload(self) -> dict[str, str]:
42
+ return asdict(self)
43
+
44
+
45
+ def category_for(identifier: str, stage: str) -> str:
46
+ """Map stable check IDs to the canonical, extensible drift taxonomy."""
47
+ identifier = identifier.casefold()
48
+ if identifier in {"telemetry_storage", "storage_schema"}:
49
+ return "Runtime Database" if identifier == "telemetry_storage" else "Runtime Schema"
50
+ if identifier in {"host_identity", "workspace_identity", "target_repository_identity"}:
51
+ return "Runtime Identity"
52
+ if identifier in {"execution_host_version", "runner_version"}:
53
+ return "Execution Host Version"
54
+ if identifier == "bootstrap_contract":
55
+ return "Bootstrap Contract"
56
+ if identifier == "checkpoint_format":
57
+ return "Checkpoint Format"
58
+ if identifier == "memory_format":
59
+ return "Memory Format"
60
+ if identifier == "report_format":
61
+ return "Report Format"
62
+ if identifier == "configuration" or identifier == "configuration_schema":
63
+ return "Configuration"
64
+ if identifier in {"runtime_components", "provider_support", "required_capabilities"}:
65
+ return "Capability"
66
+ if "producer" in identifier:
67
+ return "Producer Contract"
68
+ if identifier == "execution_mode":
69
+ return "Execution Policy"
70
+ if stage == "Workspace Preflight":
71
+ return "Repository" if identifier.startswith(("git_", "worktree_", "managed_")) else "Workspace"
72
+ return "Capability" if stage == "Capability Preflight" else "Workspace"
73
+
74
+
75
+ def evidence_for_checks(
76
+ checks: Iterable[object], *, stage: str, repository: str, runtime: str = "Engineering Platform"
77
+ ) -> tuple[DriftEvidence, ...]:
78
+ """Create deterministic evidence for every failed pre-existing check."""
79
+ now = datetime.now(timezone.utc).isoformat()
80
+ evidence: list[DriftEvidence] = []
81
+ for check in checks:
82
+ if getattr(check, "outcome", None) != "FAIL":
83
+ continue
84
+ identifier = str(getattr(check, "identifier", "unknown"))
85
+ reason = str(getattr(check, "reason", "Observed qualification check failed."))
86
+ recovery = str(getattr(check, "recovery", "Resolve the reported qualification drift."))
87
+ evidence.append(DriftEvidence(
88
+ drift_id=f"drift-{uuid.uuid4().hex}",
89
+ category=category_for(identifier, stage), severity="BLOCKING",
90
+ expected_value=f"{identifier}: PASS", observed_value=reason,
91
+ resolution_recommendation=recovery, detection_timestamp=now,
92
+ qualification_stage=stage, affected_component=identifier,
93
+ affected_repository=repository, affected_runtime=runtime,
94
+ ))
95
+ return tuple(evidence)
96
+
97
+
98
+ def persist(root: Path, evidence: Iterable[DriftEvidence]) -> tuple[dict[str, str], ...]:
99
+ """Append immutable evidence documents; never rewrite a prior observation."""
100
+ items = tuple(evidence)
101
+ if not items:
102
+ return ()
103
+ directory = root / ".engineering" / "drift-evidence"
104
+ try:
105
+ directory.mkdir(mode=0o700, parents=True, exist_ok=True)
106
+ for item in items:
107
+ descriptor, temporary = tempfile.mkstemp(prefix=".drift-", dir=directory)
108
+ with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
109
+ handle.write(json.dumps(item.payload(), sort_keys=True, separators=(",", ":")) + "\n")
110
+ handle.flush()
111
+ os.fsync(handle.fileno())
112
+ os.replace(temporary, directory / f"{item.drift_id}.json")
113
+ except OSError:
114
+ # Existing fail-closed qualification remains authoritative if local
115
+ # diagnostic persistence is unavailable.
116
+ return tuple(item.payload() for item in items)
117
+ return tuple(item.payload() for item in items)
118
+
119
+
120
+ def summary(evidence: Iterable[dict[str, object]]) -> str:
121
+ """Return one compact, operator-facing explanation without source inspection."""
122
+ items = list(evidence)
123
+ if not items:
124
+ return "No drift detected."
125
+ first = items[0]
126
+ return (
127
+ f"{first.get('qualification_stage', 'Qualification')} blocked by "
128
+ f"{first.get('affected_component', 'an unresolved component')} "
129
+ f"({first.get('category', 'Drift')}). Expected: {first.get('expected_value')}. "
130
+ f"Observed: {first.get('observed_value')}. Required action: "
131
+ f"{first.get('resolution_recommendation')}"
132
+ )
133
+
134
+
135
+ def guidance(evidence: Iterable[dict[str, object]]) -> dict[str, object]:
136
+ """Read-only retry/resume advice; it does not change lifecycle authority."""
137
+ items = list(evidence)
138
+ action = items[0].get("resolution_recommendation") if items else "No action required."
139
+ return {
140
+ "retry_appropriate": bool(items),
141
+ "resume_appropriate": False if items else True,
142
+ "operator_intervention_required": bool(items),
143
+ "prerequisite": action,
144
+ }
@@ -0,0 +1,268 @@
1
+ """Fail-closed emergency stop and workspace rollback for one live run."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ import re
9
+ import signal
10
+ import sqlite3
11
+ import time
12
+ from dataclasses import replace
13
+ from datetime import datetime, timezone
14
+
15
+ from .agent_state import StateStore
16
+ from .execution_lease import Lease, liveness, release
17
+ from .execution_timing import complete_active_phase
18
+ from .live_status import write_runner_process
19
+ from .prompt_history import record_prompt_execution
20
+ from .providers import GitProvider, LocalProcessProvider
21
+ from .storage import EngineeringStorageError, load_projection, open_storage, record_emergency_recovery, record_execution_dismissal, store_projection
22
+
23
+
24
+ RUN_ID_PATTERN = re.compile(r"inbox-[a-z0-9-]{6,64}$")
25
+ BRANCH_PATTERN = re.compile(r"codex/[A-Za-z0-9._/-]+$")
26
+
27
+
28
+ class EmergencyRecoveryError(RuntimeError):
29
+ """Raised when a destructive recovery cannot be proven safe."""
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class RecoveryPlan:
34
+ run_id: str
35
+ branch: str
36
+ baseline_branch: str
37
+ baseline_head: str
38
+ process_group: int | None
39
+ host_pid: int
40
+
41
+
42
+ def _git(root: Path, *args: str) -> str:
43
+ result = GitProvider().execute(root, "git", *args)
44
+ if result.returncode:
45
+ raise EmergencyRecoveryError("De Git-werkmap kan niet veilig worden gecontroleerd.")
46
+ return result.stdout.strip()
47
+
48
+
49
+ def _live(root: Path, run_id: str, *, central_database: Path | None = None) -> dict[str, object]:
50
+ live = load_projection(root, "live_status", central_database=central_database) or {}
51
+ if live.get("run_id") != run_id:
52
+ raise EmergencyRecoveryError("Deze uitvoering is niet de huidige uitvoering.")
53
+ if liveness(root, run_id, central_database=central_database).get("state") != "LIVE":
54
+ raise EmergencyRecoveryError("Deze uitvoering is niet meer actief; de noodactie is niet nodig.")
55
+ if live.get("execution_mode") != "MANAGED":
56
+ raise EmergencyRecoveryError("Noodherstel met rollback is alleen beschikbaar voor een beheerde uitvoering.")
57
+ return live
58
+
59
+
60
+ def _runner(root: Path, run_id: str) -> tuple[int, int] | None:
61
+ try:
62
+ runner = json.loads((root / ".engineering" / "status" / "runner_process.json").read_text(encoding="utf-8"))
63
+ except (OSError, json.JSONDecodeError):
64
+ return None
65
+ pid, group = runner.get("pid"), runner.get("process_group")
66
+ if runner.get("run_id") != run_id:
67
+ return None
68
+ if not isinstance(pid, int) or pid <= 0 or not isinstance(group, int) or group <= 0:
69
+ raise EmergencyRecoveryError("De door deze uitvoering beheerde Codex-procesgroep is ongeldig.")
70
+ return pid, group
71
+
72
+
73
+ def _host_pid(root: Path, run_id: str, *, central_database: Path | None = None) -> int:
74
+ connection = open_storage(root) if central_database is None else sqlite3.connect(central_database.resolve(), isolation_level=None)
75
+ try:
76
+ row = connection.execute(
77
+ "SELECT process_id FROM execution_run_leases WHERE run_id=? AND lease_state='ACTIVE' ORDER BY created_at DESC LIMIT 1",
78
+ (run_id,),
79
+ ).fetchone()
80
+ finally:
81
+ connection.close()
82
+ pid = row[0] if row else None
83
+ if not isinstance(pid, int) or pid <= 0:
84
+ raise EmergencyRecoveryError("De Execution Host van deze uitvoering is niet beschikbaar.")
85
+ return pid
86
+
87
+
88
+ def _require_central_project_ownership(database: Path, project_id: str | None, run_id: str) -> None:
89
+ """Reject a dashboard action whose run is not owned by its selected project."""
90
+ if not isinstance(project_id, str) or not project_id:
91
+ raise EmergencyRecoveryError("Er is geen geldig project geselecteerd voor deze noodactie.")
92
+ try:
93
+ connection = sqlite3.connect(database.resolve(), isolation_level=None)
94
+ try:
95
+ row = connection.execute(
96
+ "SELECT project_id FROM ep_execution_runs WHERE run_id=?", (run_id,)
97
+ ).fetchone()
98
+ finally:
99
+ connection.close()
100
+ except sqlite3.Error as error:
101
+ raise EmergencyRecoveryError("De centrale uitvoeringstoestand is niet beschikbaar.") from error
102
+ if row is None or row[0] != project_id:
103
+ raise EmergencyRecoveryError("Deze uitvoering behoort niet tot het geselecteerde project.")
104
+
105
+
106
+ def _process_command(root: Path, pid: int) -> str:
107
+ result = LocalProcessProvider().execute(root, ("ps", "-p", str(pid), "-o", "command="))
108
+ return result.stdout.strip() if result.returncode == 0 else ""
109
+
110
+
111
+ def _plan(
112
+ root: Path, run_id: str, *, central_database: Path | None = None, project_id: str | None = None,
113
+ ) -> RecoveryPlan:
114
+ if not RUN_ID_PATTERN.fullmatch(run_id):
115
+ raise EmergencyRecoveryError("De opgegeven run-ID is ongeldig.")
116
+ if central_database is not None:
117
+ _require_central_project_ownership(central_database, project_id, run_id)
118
+ live = _live(root, run_id, central_database=central_database)
119
+ state = StateStore(root / ".engineering" / "engineering-runs", central_database=central_database, emit_local_projection=central_database is None).load(run_id)
120
+ if state is None:
121
+ raise EmergencyRecoveryError("De actieve uitvoering heeft geen canoniek checkpoint.")
122
+ if any(
123
+ pull_request is not None
124
+ for pull_request in (
125
+ state.pull_request, state.implementation_pull_request,
126
+ state.finalization_pull_request, state.reconciliation_pull_request,
127
+ )
128
+ ):
129
+ raise EmergencyRecoveryError("Er is al een pull request geregistreerd; de noodknop verwijdert geen pull requests of hun branches.")
130
+ recovery = live.get("workspace_recovery")
131
+ if not isinstance(recovery, dict):
132
+ raise EmergencyRecoveryError("Deze uitvoering heeft geen veilige herstelbasis geregistreerd.")
133
+ baseline_branch = recovery.get("baseline_branch")
134
+ baseline_head = recovery.get("baseline_head")
135
+ preexisting = recovery.get("preexisting_branches")
136
+ if (
137
+ recovery.get("baseline_clean") is not True
138
+ or baseline_branch != "main"
139
+ or not isinstance(baseline_head, str)
140
+ or not re.fullmatch(r"[0-9a-f]{40}", baseline_head)
141
+ or not isinstance(preexisting, list)
142
+ ):
143
+ raise EmergencyRecoveryError("De uitvoering begon niet vanaf een aantoonbaar schone main-basis.")
144
+ branch = _git(root, "branch", "--show-current")
145
+ head = _git(root, "rev-parse", "HEAD")
146
+ if head != baseline_head:
147
+ raise EmergencyRecoveryError("Er zijn commits gemaakt; de noodknop verwijdert geen gecommitteerd werk of pull requests.")
148
+ if branch != "main" and (not BRANCH_PATTERN.fullmatch(branch) or branch in preexisting):
149
+ raise EmergencyRecoveryError("De actieve branch is niet aantoonbaar door deze uitvoering aangemaakt.")
150
+ runner = _runner(root, run_id)
151
+ host_pid = _host_pid(root, run_id, central_database=central_database)
152
+ runner_pid, group = runner if runner is not None else (None, None)
153
+ runner_command = _process_command(root, runner_pid) if runner_pid is not None else ""
154
+ host_command = _process_command(root, host_pid)
155
+ if runner_pid is not None and (not runner_command or "codex" not in runner_command.casefold()):
156
+ raise EmergencyRecoveryError("De geregistreerde Codex-runner is niet meer veilig identificeerbaar.")
157
+ if not host_command or ("execution_host" not in host_command and "engineering-execution-host" not in host_command):
158
+ raise EmergencyRecoveryError("De geregistreerde Execution Host is niet meer veilig identificeerbaar.")
159
+ return RecoveryPlan(run_id, branch, baseline_branch, baseline_head, group, host_pid)
160
+
161
+
162
+ def preview(
163
+ root: Path, run_id: object, *, central_database: Path | None = None, project_id: str | None = None,
164
+ ) -> dict[str, object]:
165
+ """Return a display-safe, non-mutating emergency recovery eligibility view."""
166
+ if not isinstance(run_id, str):
167
+ return {"available": False}
168
+ try:
169
+ plan = _plan(root, run_id, central_database=central_database, project_id=project_id)
170
+ except (EmergencyRecoveryError, EngineeringStorageError, OSError):
171
+ return {"available": False}
172
+ return {
173
+ "available": True,
174
+ "run_id": plan.run_id,
175
+ "branch": plan.branch,
176
+ "baseline_branch": plan.baseline_branch,
177
+ "rollback_changes": True,
178
+ "remove_branch": plan.branch != plan.baseline_branch,
179
+ }
180
+
181
+
182
+ def _stop(plan: RecoveryPlan, root: Path) -> None:
183
+ try:
184
+ if plan.process_group is not None:
185
+ try:
186
+ os.killpg(plan.process_group, signal.SIGTERM)
187
+ except ProcessLookupError:
188
+ # Codex can finish between preview and confirmation. The
189
+ # leased Execution Host is still the authoritative stop target.
190
+ pass
191
+ os.kill(plan.host_pid, signal.SIGTERM)
192
+ except ProcessLookupError as error:
193
+ raise EmergencyRecoveryError("De uitvoering stopte al voordat de noodactie kon worden uitgevoerd.") from error
194
+ deadline = time.monotonic() + 3
195
+ while time.monotonic() < deadline:
196
+ if not _process_command(root, plan.host_pid):
197
+ return
198
+ time.sleep(0.1)
199
+ raise EmergencyRecoveryError("De Execution Host reageert niet op de noodstop; er is niets teruggedraaid.")
200
+
201
+
202
+ def _release_lease(root: Path, run_id: str, *, central_database: Path | None = None) -> None:
203
+ connection = open_storage(root) if central_database is None else sqlite3.connect(central_database.resolve(), isolation_level=None)
204
+ try:
205
+ row = connection.execute(
206
+ "SELECT lease_id,host_identity,host_instance_id,acquired_at,last_heartbeat_at,expires_at,lease_state FROM execution_run_leases WHERE run_id=? AND lease_state='ACTIVE' ORDER BY created_at DESC LIMIT 1",
207
+ (run_id,),
208
+ ).fetchone()
209
+ finally:
210
+ connection.close()
211
+ if not row:
212
+ return
213
+ release(root, Lease(row[0], run_id, row[1], row[2], row[3], row[4], row[5], row[6]), central_database=central_database)
214
+
215
+
216
+ def execute(
217
+ root: Path, run_id: str, *, central_database: Path | None = None, project_id: str | None = None,
218
+ ) -> dict[str, object]:
219
+ """Stop exactly one verified host, then restore its clean local baseline."""
220
+ plan = _plan(root, run_id, central_database=central_database, project_id=project_id)
221
+ _stop(plan, root)
222
+ _release_lease(root, run_id, central_database=central_database)
223
+ _git(root, "restore", "--source", plan.baseline_head, "--staged", "--worktree", "--", ".")
224
+ _git(root, "clean", "-fd", "--", ".")
225
+ removed_branch: str | None = None
226
+ if plan.branch != plan.baseline_branch:
227
+ _git(root, "switch", plan.baseline_branch)
228
+ _git(root, "branch", "-D", "--", plan.branch)
229
+ removed_branch = plan.branch
230
+ if _git(root, "rev-parse", "HEAD") != plan.baseline_head or _git(root, "status", "--porcelain", "--untracked-files=all"):
231
+ raise EmergencyRecoveryError("De noodstop is uitgevoerd, maar de werkmap kon niet volledig worden teruggedraaid.")
232
+ write_runner_process(root, run_id, None)
233
+ try:
234
+ state = StateStore(root / ".engineering" / "engineering-runs", central_database=central_database, emit_local_projection=central_database is None).load(run_id)
235
+ except (EngineeringStorageError, ValueError) as error:
236
+ raise EmergencyRecoveryError("De annulering kon niet veilig als eindstatus worden vastgelegd.") from error
237
+ if state is None:
238
+ raise EmergencyRecoveryError("De actieve uitvoering heeft geen canoniek checkpoint.")
239
+ cancelled_at = datetime.now(timezone.utc).isoformat()
240
+ cancelled = replace(
241
+ state, phase="FAILED", terminal=True, next_action="operator_emergency_rollback",
242
+ terminal_condition="operator_emergency_rollback",
243
+ diagnostic="De operator heeft deze uitvoering via de noodstop geannuleerd en de lokale werkmap teruggedraaid.",
244
+ )
245
+ StateStore(root / ".engineering" / "engineering-runs", central_database=central_database, emit_local_projection=central_database is None).save(cancelled)
246
+ complete_active_phase(root, run_id, "TOTAL_EXECUTION", outcome="FAILED", central_database=central_database)
247
+ record_prompt_execution(
248
+ root, run_id=run_id, terminal_state="FAILED", prompt_title=Path(state.prompt_path).stem,
249
+ executed_at=cancelled_at, target_branch=plan.branch,
250
+ central_database=central_database,
251
+ )
252
+ record_execution_dismissal(
253
+ root, run_id=run_id, terminal_state="FAILED", dismissed_at=cancelled_at,
254
+ dismissed_by="dashboard_emergency_recovery",
255
+ central_database=central_database,
256
+ )
257
+ record_emergency_recovery(
258
+ root, run_id=run_id, cancelled_at=cancelled_at, rolled_back=True,
259
+ removed_branch=removed_branch,
260
+ central_database=central_database,
261
+ )
262
+ outcome = {"run_id": run_id, "stopped": True, "rolled_back": True, "removed_branch": removed_branch, "branch": plan.baseline_branch, "cancelled_at": cancelled_at}
263
+ connection = open_storage(root) if central_database is None else sqlite3.connect(central_database.resolve(), isolation_level=None)
264
+ try:
265
+ store_projection(connection, f"emergency_recovery:{run_id}", outcome, classification="RECOVERY_EXPORT")
266
+ finally:
267
+ connection.close()
268
+ return outcome
@@ -0,0 +1,139 @@
1
+ """Local, advisory Engineering Memory persistence.
2
+
3
+ This module deliberately owns only bounded local memory. The runner remains
4
+ the lifecycle orchestrator and repository evidence remains authoritative.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from datetime import datetime, timezone
10
+ import json
11
+ import os
12
+ from pathlib import Path
13
+ import tempfile
14
+
15
+ from .agent_state import TransactionState
16
+
17
+
18
+ def _memory_path(root: Path) -> Path:
19
+ return root / ".engineering" / "memory" / "engineering-memory.json"
20
+
21
+
22
+ def load_engineering_memory(root: Path) -> dict[str, object]:
23
+ try:
24
+ raw = json.loads(_memory_path(root).read_text(encoding="utf-8"))
25
+ return raw if isinstance(raw, dict) else {}
26
+ except (OSError, json.JSONDecodeError):
27
+ return {}
28
+
29
+
30
+ def retrieve_engineering_memory(root: Path, prompt_path: Path) -> str:
31
+ """Return safe advisory metadata; repository evidence remains authoritative."""
32
+ try:
33
+ entries = load_engineering_memory(root).get("transactions", [])
34
+ except AttributeError:
35
+ return "\n\nEngineering Memory: no prior safe transaction metadata is available."
36
+ objective = prompt_path.stem.lower()
37
+ relevant = [
38
+ entry
39
+ for entry in entries[-10:]
40
+ if any(word in objective for word in entry.get("classification", "").split())
41
+ ]
42
+ return (
43
+ "\n\nEngineering Memory (advisory only; repository evidence overrides it): "
44
+ + json.dumps(relevant[-3:], sort_keys=True)
45
+ )
46
+
47
+
48
+ def capture_engineering_memory(
49
+ root: Path, state: TransactionState, reviewer_records: tuple[dict[str, object], ...] = ()
50
+ ) -> None:
51
+ """Atomically store bounded metadata, never prompts, source content or credentials."""
52
+ path = _memory_path(root)
53
+ path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
54
+ raw = load_engineering_memory(root)
55
+ classification = " ".join(
56
+ part
57
+ for part in Path(state.prompt_path).stem.lower().replace("_", "-").split("-")
58
+ if part.isalpha()
59
+ )[:120]
60
+ entry = {
61
+ "classification": classification,
62
+ "repository": state.repository,
63
+ "outcome": state.phase,
64
+ "repair_iterations": state.repair_iterations,
65
+ "implementation_pr": state.implementation_pull_request,
66
+ "finalization_pr": state.finalization_pull_request,
67
+ "confidence": 1.0,
68
+ "usage_count": 0,
69
+ "last_successful_use": datetime.now(timezone.utc).isoformat(),
70
+ }
71
+ reviewer_index = {
72
+ item.get("reviewer"): dict(item)
73
+ for item in raw.get("reviewers", [])
74
+ if isinstance(item, dict) and isinstance(item.get("reviewer"), str)
75
+ }
76
+ for record in reviewer_records:
77
+ reviewer = record.get("reviewer")
78
+ if not isinstance(reviewer, str):
79
+ continue
80
+ previous = reviewer_index.get(reviewer, {})
81
+ usage = int(previous.get("usage_count", 0)) + 1
82
+ successful = int(previous.get("successful_outcomes", 0)) + (
83
+ 0 if record.get("failed") else 1
84
+ )
85
+ accepted = int(previous.get("accepted_recommendations", 0)) + int(
86
+ record.get("accepted_recommendations", 0)
87
+ )
88
+ recommended = (
89
+ int(previous.get("recommendation_count", 0))
90
+ + int(record.get("accepted_recommendations", 0))
91
+ + int(record.get("rejected_recommendations", 0))
92
+ )
93
+ confidence = round(successful / usage, 2)
94
+ reviewer_index[reviewer] = {
95
+ "reviewer": reviewer,
96
+ "capability": record.get("capability", "engineering"),
97
+ "usage_count": usage,
98
+ "successful_outcomes": successful,
99
+ "accepted_recommendations": accepted,
100
+ "recommendation_count": recommended,
101
+ "recommendation_acceptance_rate": round(accepted / recommended, 2)
102
+ if recommended
103
+ else 0.0,
104
+ "average_duration": 0,
105
+ "last_successful_use": datetime.now(timezone.utc).isoformat()
106
+ if not record.get("failed")
107
+ else previous.get("last_successful_use"),
108
+ "future_confidence": confidence,
109
+ }
110
+ reviewers = list(reviewer_index.values())[-50:]
111
+ raw = {
112
+ "schema_version": 2,
113
+ "transactions": [item for item in raw.get("transactions", []) if isinstance(item, dict)][
114
+ -49:
115
+ ]
116
+ + [entry],
117
+ "reviewers": reviewers,
118
+ "capability_metrics": {
119
+ "most_frequently_used": max(reviewers, key=lambda item: item["usage_count"])["reviewer"]
120
+ if reviewers
121
+ else None,
122
+ "highest_value": max(reviewers, key=lambda item: item["future_confidence"])["reviewer"]
123
+ if reviewers
124
+ else None,
125
+ "repository_areas": sorted(
126
+ {str(item.get("capability", "engineering")) for item in reviewers}
127
+ ),
128
+ },
129
+ }
130
+ descriptor, temporary = tempfile.mkstemp(prefix=".memory.", suffix=".tmp", dir=path.parent)
131
+ try:
132
+ with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
133
+ json.dump(raw, handle, indent=2, sort_keys=True)
134
+ handle.write("\n")
135
+ handle.flush()
136
+ os.fsync(handle.fileno())
137
+ os.replace(temporary, path)
138
+ finally:
139
+ Path(temporary).unlink(missing_ok=True)