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,253 @@
|
|
|
1
|
+
"""Repository and GitHub coordination behind canonical providers."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import re
|
|
7
|
+
import time
|
|
8
|
+
from typing import Protocol
|
|
9
|
+
|
|
10
|
+
from .execution_errors import RunnerError
|
|
11
|
+
from .execution_models import PullRequestEvidence, RepositoryEvidence
|
|
12
|
+
from .providers import GitProvider, GitHubProvider
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
GIT_SYNC_LOCK_RETRY_ATTEMPTS = 3
|
|
16
|
+
GIT_SYNC_LOCK_RETRY_DELAY_SECONDS = 0.5
|
|
17
|
+
_TRANSIENT_INDEX_LOCK_CONFLICT = re.compile(
|
|
18
|
+
r"(?:index\.lock.*(?:file exists|already exists)|another git process)",
|
|
19
|
+
re.IGNORECASE,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def github_repository_slug(remote: str) -> str:
|
|
24
|
+
"""Return the canonical owner/repository slug for GitHub origin forms."""
|
|
25
|
+
value = remote.strip().removesuffix(".git")
|
|
26
|
+
if value.startswith("git@github.com:"):
|
|
27
|
+
return value.removeprefix("git@github.com:")
|
|
28
|
+
if value.startswith("https://github.com/"):
|
|
29
|
+
return value.removeprefix("https://github.com/")
|
|
30
|
+
return value
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RepositoryClient(Protocol):
|
|
34
|
+
def inspect(self, root: Path) -> RepositoryEvidence: ...
|
|
35
|
+
def main_contains(self, root: Path, sha: str) -> bool: ...
|
|
36
|
+
def refresh_main_reference(self, root: Path) -> None: ...
|
|
37
|
+
def remote_main_contains(self, root: Path, sha: str) -> bool: ...
|
|
38
|
+
def synchronize_main(self, root: Path) -> None: ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class GitHubClient(Protocol):
|
|
42
|
+
def pull_request(self, number: int) -> PullRequestEvidence: ...
|
|
43
|
+
def pull_request_for_head_branch(self, branch: str) -> PullRequestEvidence | None: ...
|
|
44
|
+
def ready(self, number: int) -> None: ...
|
|
45
|
+
def normalize_markdown_body(self, number: int) -> bool: ...
|
|
46
|
+
def merge(self, number: int) -> None: ...
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SubprocessRepositoryClient:
|
|
50
|
+
"""Repository workflow implementation; process ownership remains GitProvider."""
|
|
51
|
+
def __init__(self, provider: GitProvider | None = None) -> None:
|
|
52
|
+
self.provider = provider or GitProvider()
|
|
53
|
+
|
|
54
|
+
def _run(self, root: Path, *args: str) -> str:
|
|
55
|
+
try: return self.provider.command(root, *args)
|
|
56
|
+
except RuntimeError as error: raise RunnerError(str(error)) from error
|
|
57
|
+
|
|
58
|
+
def inspect(self, root: Path) -> RepositoryEvidence:
|
|
59
|
+
if not (root / "BOOTSTRAP.md").is_file() or not (root / ".git").exists():
|
|
60
|
+
raise RunnerError("this is not a repository with canonical BOOTSTRAP.md")
|
|
61
|
+
remote = self._run(root, "git", "remote", "get-url", "origin")
|
|
62
|
+
repository = github_repository_slug(remote)
|
|
63
|
+
branch, head_sha = self._run(root, "git", "branch", "--show-current"), self._run(root, "git", "rev-parse", "HEAD")
|
|
64
|
+
clean = not self._run(root, "git", "status", "--porcelain", "--untracked-files=all")
|
|
65
|
+
contained = self.provider.execute(root, "git", "merge-base", "--is-ancestor", head_sha, "main").returncode == 0
|
|
66
|
+
return RepositoryEvidence(repository, branch, head_sha, clean, contained)
|
|
67
|
+
|
|
68
|
+
def main_contains(self, root: Path, sha: str) -> bool:
|
|
69
|
+
return self.provider.execute(root, "git", "merge-base", "--is-ancestor", sha, "main").returncode == 0
|
|
70
|
+
|
|
71
|
+
def refresh_main_reference(self, root: Path) -> None:
|
|
72
|
+
"""Refresh remote main evidence without changing the shared checkout."""
|
|
73
|
+
self._synchronize_command(root, "git", "fetch", "origin", "main")
|
|
74
|
+
|
|
75
|
+
def remote_main_contains(self, root: Path, sha: str) -> bool:
|
|
76
|
+
return self.provider.execute(root, "git", "merge-base", "--is-ancestor", sha, "origin/main").returncode == 0
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def _is_transient_index_lock_conflict(error: RuntimeError) -> bool:
|
|
80
|
+
"""Recognize only Git's contention signal, never a permission failure."""
|
|
81
|
+
return bool(_TRANSIENT_INDEX_LOCK_CONFLICT.search(str(error)))
|
|
82
|
+
|
|
83
|
+
def _synchronize_command(self, root: Path, *args: str) -> None:
|
|
84
|
+
"""Run one synchronization step with bounded, non-destructive lock retries."""
|
|
85
|
+
for attempt in range(1, GIT_SYNC_LOCK_RETRY_ATTEMPTS + 1):
|
|
86
|
+
try:
|
|
87
|
+
self._run(root, *args)
|
|
88
|
+
return
|
|
89
|
+
except RunnerError as error:
|
|
90
|
+
if (
|
|
91
|
+
attempt == GIT_SYNC_LOCK_RETRY_ATTEMPTS
|
|
92
|
+
or not self._is_transient_index_lock_conflict(error)
|
|
93
|
+
):
|
|
94
|
+
raise
|
|
95
|
+
time.sleep(GIT_SYNC_LOCK_RETRY_DELAY_SECONDS * attempt)
|
|
96
|
+
|
|
97
|
+
def _managed_sync_diagnostics(self, root: Path) -> str:
|
|
98
|
+
"""Return bounded, read-only context for a failed managed synchronization."""
|
|
99
|
+
execute = getattr(self.provider, "execute", None)
|
|
100
|
+
if not callable(execute):
|
|
101
|
+
return (
|
|
102
|
+
"target_branch=main authoritative_ref=origin/main local_sha=unavailable "
|
|
103
|
+
"remote_sha=unavailable configured_upstream=unavailable fast_forward_state=unknown"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def value(*args: str) -> str:
|
|
107
|
+
try:
|
|
108
|
+
completed = execute(root, "git", *args)
|
|
109
|
+
except (OSError, RuntimeError):
|
|
110
|
+
return "unavailable"
|
|
111
|
+
if completed.returncode:
|
|
112
|
+
return "unavailable"
|
|
113
|
+
candidate = completed.stdout.strip()
|
|
114
|
+
return candidate if re.fullmatch(r"[0-9a-f]{40}|[A-Za-z0-9_./@{}-]+", candidate) else "unavailable"
|
|
115
|
+
|
|
116
|
+
local_sha = value("rev-parse", "--verify", "main")
|
|
117
|
+
remote_sha = value("rev-parse", "--verify", "origin/main")
|
|
118
|
+
upstream = value("rev-parse", "--abbrev-ref", "--symbolic-full-name", "main@{upstream}")
|
|
119
|
+
state = "unknown"
|
|
120
|
+
if local_sha != "unavailable" and remote_sha != "unavailable":
|
|
121
|
+
try:
|
|
122
|
+
local_is_ancestor = execute(
|
|
123
|
+
root, "git", "merge-base", "--is-ancestor", "main", "origin/main"
|
|
124
|
+
).returncode == 0
|
|
125
|
+
remote_is_ancestor = execute(
|
|
126
|
+
root, "git", "merge-base", "--is-ancestor", "origin/main", "main"
|
|
127
|
+
).returncode == 0
|
|
128
|
+
except (OSError, RuntimeError):
|
|
129
|
+
pass
|
|
130
|
+
else:
|
|
131
|
+
state = (
|
|
132
|
+
"synchronized" if local_is_ancestor and remote_is_ancestor
|
|
133
|
+
else "fast_forward_possible" if local_is_ancestor
|
|
134
|
+
else "local_ahead" if remote_is_ancestor
|
|
135
|
+
else "diverged"
|
|
136
|
+
)
|
|
137
|
+
return (
|
|
138
|
+
"target_branch=main authoritative_ref=origin/main "
|
|
139
|
+
f"local_sha={local_sha} remote_sha={remote_sha} configured_upstream={upstream} "
|
|
140
|
+
f"fast_forward_state={state}"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def _managed_sync_error_detail(error: RunnerError) -> str:
|
|
145
|
+
"""Keep a Git failure useful without persisting unbounded process output."""
|
|
146
|
+
return re.sub(r"\s+", " ", str(error)).strip()[:512]
|
|
147
|
+
|
|
148
|
+
def synchronize_main(self, root: Path) -> None:
|
|
149
|
+
self._synchronize_command(root, "git", "switch", "main")
|
|
150
|
+
# Managed synchronization has one authority. Do not let any local
|
|
151
|
+
# branch.*.merge, pull.*, or upstream configuration select its source.
|
|
152
|
+
try:
|
|
153
|
+
self._synchronize_command(root, "git", "fetch", "origin", "main")
|
|
154
|
+
except RunnerError as error:
|
|
155
|
+
raise RunnerError(
|
|
156
|
+
"MANAGED_MAIN_FETCH_FAILED: operation=git-fetch-origin-main "
|
|
157
|
+
f"{self._managed_sync_diagnostics(root)}; {self._managed_sync_error_detail(error)}"
|
|
158
|
+
) from error
|
|
159
|
+
try:
|
|
160
|
+
self._synchronize_command(root, "git", "merge", "--ff-only", "origin/main")
|
|
161
|
+
except RunnerError as error:
|
|
162
|
+
raise RunnerError(
|
|
163
|
+
"MANAGED_MAIN_FAST_FORWARD_FAILED: operation=git-merge-ff-only-origin-main "
|
|
164
|
+
f"{self._managed_sync_diagnostics(root)}; {self._managed_sync_error_detail(error)}"
|
|
165
|
+
) from error
|
|
166
|
+
|
|
167
|
+
def cleanup_transaction(self, root: Path, branches: tuple[str | None, ...]) -> str:
|
|
168
|
+
self._run(root, "git", "fetch", "--prune"); self.synchronize_main(root)
|
|
169
|
+
if not self.inspect(root).clean: raise RunnerError("Cleanup blocked: workspace is not clean.")
|
|
170
|
+
removed: list[str] = []; squash_reconciled: list[str] = []
|
|
171
|
+
for branch in dict.fromkeys(branch for branch in branches if branch):
|
|
172
|
+
if branch == "main": raise RunnerError("Cleanup blocked: transaction branch resolves to main.")
|
|
173
|
+
if self.provider.execute(root, "git", "show-ref", "--verify", "--quiet", f"refs/heads/{branch}").returncode != 0: continue
|
|
174
|
+
if self.provider.execute(root, "git", "branch", "-d", branch).returncode:
|
|
175
|
+
if self.provider.execute(root, "git", "branch", "-D", branch).returncode:
|
|
176
|
+
raise RunnerError(f"Cleanup blocked: transaction branch {branch} could not be safely removed.")
|
|
177
|
+
squash_reconciled.append(branch)
|
|
178
|
+
removed.append(branch)
|
|
179
|
+
evidence = self.inspect(root)
|
|
180
|
+
if evidence.branch != "main" or not evidence.clean or not evidence.main_contains_head:
|
|
181
|
+
raise RunnerError("Cleanup blocked: main synchronization or workspace verification failed.")
|
|
182
|
+
squash = f"; squash-reconciled={','.join(squash_reconciled)}" if squash_reconciled else ""
|
|
183
|
+
return f"fetched/pruned; main synchronized; removed={','.join(removed) or 'already-absent'}{squash}"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class GhCliClient:
|
|
187
|
+
def __init__(self, provider: GitHubProvider | None = None, repository: str | None = None) -> None:
|
|
188
|
+
self.provider = provider or GitHubProvider()
|
|
189
|
+
self.repository = repository
|
|
190
|
+
|
|
191
|
+
def _github(self, *args: str) -> str:
|
|
192
|
+
scoped = (*args, "--repo", self.repository) if self.repository else args
|
|
193
|
+
return self.provider.github(*scoped)
|
|
194
|
+
|
|
195
|
+
def pull_request(self, number: int) -> PullRequestEvidence:
|
|
196
|
+
try: raw = json.loads(self._github("pr", "view", str(number), "--json", "number,state,isDraft,mergeCommit,statusCheckRollup,headRefName,baseRefName,mergeStateStatus"))
|
|
197
|
+
except RuntimeError as error: raise RunnerError(str(error)) from error
|
|
198
|
+
# GitHub can append an empty rollup entry to an otherwise completed
|
|
199
|
+
# merged PR. It is not a check and must not keep terminal evidence in
|
|
200
|
+
# an endless polling loop.
|
|
201
|
+
checks = [
|
|
202
|
+
item
|
|
203
|
+
for item in (raw.get("statusCheckRollup") or [])
|
|
204
|
+
if isinstance(item, dict) and isinstance(item.get("status"), str)
|
|
205
|
+
]
|
|
206
|
+
terminal = bool(checks) and all(item.get("status") == "COMPLETED" for item in checks)
|
|
207
|
+
passed = terminal and all(item.get("conclusion") in {"SUCCESS", "NEUTRAL", "SKIPPED"} for item in checks)
|
|
208
|
+
failed = tuple(str(item.get("name") or "unnamed check") for item in checks if item.get("status") == "COMPLETED" and item.get("conclusion") not in {"SUCCESS", "NEUTRAL", "SKIPPED"})
|
|
209
|
+
merge = raw.get("mergeCommit") or {}
|
|
210
|
+
return PullRequestEvidence(
|
|
211
|
+
raw["number"], raw["state"], terminal, passed, merge.get("oid"), raw["isDraft"], failed,
|
|
212
|
+
raw.get("headRefName"), raw.get("baseRefName"), raw.get("mergeStateStatus"),
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def pull_request_for_head_branch(self, branch: str) -> PullRequestEvidence | None:
|
|
216
|
+
"""Return exactly one current PR for an already-checkpointed branch."""
|
|
217
|
+
try:
|
|
218
|
+
raw = json.loads(self._github(
|
|
219
|
+
"pr", "list", "--head", branch, "--state", "all", "--json", "number"
|
|
220
|
+
))
|
|
221
|
+
except RuntimeError as error:
|
|
222
|
+
raise RunnerError(str(error)) from error
|
|
223
|
+
numbers = [item.get("number") for item in raw if isinstance(item, dict) and isinstance(item.get("number"), int)]
|
|
224
|
+
if not numbers:
|
|
225
|
+
return None
|
|
226
|
+
if len(numbers) != 1:
|
|
227
|
+
raise RunnerError("Finalization recovery found more than one pull request for its checkpointed branch.")
|
|
228
|
+
return self.pull_request(numbers[0])
|
|
229
|
+
def ready(self, number: int) -> None:
|
|
230
|
+
try: self._github("pr", "ready", str(number))
|
|
231
|
+
except RuntimeError as error:
|
|
232
|
+
if "already ready" not in str(error).lower(): raise RunnerError(str(error)) from error
|
|
233
|
+
|
|
234
|
+
def normalize_markdown_body(self, number: int) -> bool:
|
|
235
|
+
"""Repair only a fully escaped PR body generated by an agent."""
|
|
236
|
+
try:
|
|
237
|
+
raw = json.loads(self._github("pr", "view", str(number), "--json", "body"))
|
|
238
|
+
except (RuntimeError, json.JSONDecodeError) as error:
|
|
239
|
+
raise RunnerError("Pull request Markdown could not be inspected.") from error
|
|
240
|
+
body = raw.get("body") if isinstance(raw, dict) else None
|
|
241
|
+
# Preserve real Markdown, including code snippets that deliberately
|
|
242
|
+
# contain an escaped newline. Only a one-line serialized body is bad.
|
|
243
|
+
if not isinstance(body, str) or "\n" in body or "\\n" not in body:
|
|
244
|
+
return False
|
|
245
|
+
normalized = body.replace("\\r\\n", "\n").replace("\\n", "\n")
|
|
246
|
+
try:
|
|
247
|
+
self._github("pr", "edit", str(number), "--body", normalized)
|
|
248
|
+
except RuntimeError as error:
|
|
249
|
+
raise RunnerError("Pull request Markdown could not be normalized.") from error
|
|
250
|
+
return True
|
|
251
|
+
def merge(self, number: int) -> None:
|
|
252
|
+
try: self._github("pr", "merge", str(number), "--squash", "--delete-branch")
|
|
253
|
+
except RuntimeError as error: raise RunnerError(str(error)) from error
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Versioned, host-owned time limits for bounded provider work.
|
|
2
|
+
|
|
3
|
+
The limits are deliberately not dashboard preferences. They cap autonomous
|
|
4
|
+
provider authority and keep a provider process that leaves its output open
|
|
5
|
+
from owning a project queue indefinitely.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
POLICY_VERSION = "provider-timeouts-v1"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class ProviderTimeout:
|
|
17
|
+
"""One named execution boundary and its immutable maximum duration."""
|
|
18
|
+
|
|
19
|
+
key: str
|
|
20
|
+
seconds: int
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
SPECIALIST_REVIEW = ProviderTimeout("specialist_review", 5 * 60)
|
|
24
|
+
IMPLEMENTATION = ProviderTimeout("implementation", 15 * 60)
|
|
25
|
+
LOCAL_REPOSITORY_VALIDATION = ProviderTimeout("local_repository_validation", 15 * 60)
|
|
26
|
+
AUTONOMOUS_QUALITY_CONTROL = ProviderTimeout("autonomous_quality_control", 10 * 60)
|
|
27
|
+
REPAIR = ProviderTimeout("repair", 15 * 60)
|
|
28
|
+
FINALIZATION = ProviderTimeout("finalization", 15 * 60)
|
|
29
|
+
END_RECONCILIATION = ProviderTimeout("end_reconciliation", 10 * 60)
|
|
30
|
+
|
|
31
|
+
WORKFLOW_TIMEOUTS = (
|
|
32
|
+
SPECIALIST_REVIEW,
|
|
33
|
+
IMPLEMENTATION,
|
|
34
|
+
LOCAL_REPOSITORY_VALIDATION,
|
|
35
|
+
AUTONOMOUS_QUALITY_CONTROL,
|
|
36
|
+
REPAIR,
|
|
37
|
+
FINALIZATION,
|
|
38
|
+
END_RECONCILIATION,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def agent_timeout(*, phase: str, repair: bool = False, quality: bool = False,
|
|
43
|
+
local_validation: bool = False) -> ProviderTimeout:
|
|
44
|
+
"""Return the deadline for one primary-provider workflow action."""
|
|
45
|
+
if repair:
|
|
46
|
+
return REPAIR
|
|
47
|
+
if local_validation:
|
|
48
|
+
return LOCAL_REPOSITORY_VALIDATION
|
|
49
|
+
if quality:
|
|
50
|
+
return AUTONOMOUS_QUALITY_CONTROL
|
|
51
|
+
normalized = phase.upper()
|
|
52
|
+
if normalized == "FINALIZATION":
|
|
53
|
+
return FINALIZATION
|
|
54
|
+
if normalized in {"RECONCILE_AGENT", "END_RECONCILIATION"}:
|
|
55
|
+
return END_RECONCILIATION
|
|
56
|
+
return IMPLEMENTATION
|