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,172 @@
|
|
|
1
|
+
"""Server-owned lifecycle for the bounded Tailnet Console relay.
|
|
2
|
+
|
|
3
|
+
The relay is an access adapter only: it forwards the Tailnet listener to the
|
|
4
|
+
installed Server's loopback Console. It owns no submission, project, File
|
|
5
|
+
Inbox, Action or run state.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from html import escape
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
import shutil
|
|
13
|
+
from urllib.error import URLError
|
|
14
|
+
from urllib.request import urlopen
|
|
15
|
+
|
|
16
|
+
from .platform_components import PLATFORM_COMPONENT_BY_ID
|
|
17
|
+
from .providers import LaunchdProvider, LocalProcessProvider
|
|
18
|
+
from .resources import package_path
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
RUNTIME_DIRECTORY = "runtime"
|
|
22
|
+
RELAY_BINARY_FILENAME = "engineering-dashboard-relay"
|
|
23
|
+
# This identifier is retained solely to migrate an already installed legacy
|
|
24
|
+
# relay. The canonical component model is the only active lifecycle owner.
|
|
25
|
+
LEGACY_RELAY_LABEL = "com.djconnect.engineering-dashboard-relay"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class RelayCutoverError(RuntimeError):
|
|
29
|
+
"""Fail-closed error for the exact relay identity cutover."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class PreCutoverState:
|
|
34
|
+
legacy_plist_present: bool
|
|
35
|
+
legacy_loaded: bool
|
|
36
|
+
legacy_process_active: bool
|
|
37
|
+
legacy_functional_probe: bool | None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _definition_label() -> str:
|
|
41
|
+
label = PLATFORM_COMPONENT_BY_ID["dashboard_relay"].lifecycle_label
|
|
42
|
+
if not label:
|
|
43
|
+
raise RuntimeError("Dashboard Relay has no canonical lifecycle label.")
|
|
44
|
+
return label
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def relay_binary(data_root: Path) -> Path:
|
|
48
|
+
"""Return the installation-owned relay binary path, never a checkout path."""
|
|
49
|
+
return data_root.resolve() / RUNTIME_DIRECTORY / RELAY_BINARY_FILENAME
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def build_relay(data_root: Path) -> Path:
|
|
53
|
+
"""Compile the package-owned relay into the Server installation runtime."""
|
|
54
|
+
compiler = shutil.which("swiftc")
|
|
55
|
+
if compiler is None:
|
|
56
|
+
raise RuntimeError("Swift compiler is unavailable; Dashboard Relay cannot start.")
|
|
57
|
+
binary = relay_binary(data_root)
|
|
58
|
+
binary.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
59
|
+
completed = LocalProcessProvider().execute(
|
|
60
|
+
data_root.resolve(), (compiler, str(package_path("dashboard_supervisor.swift")), "-o", str(binary)),
|
|
61
|
+
)
|
|
62
|
+
if completed.returncode:
|
|
63
|
+
raise RuntimeError("Dashboard Relay compilation failed.")
|
|
64
|
+
binary.chmod(0o700)
|
|
65
|
+
return binary
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def launch_agent_path() -> Path:
|
|
69
|
+
return Path.home() / "Library" / "LaunchAgents" / f"{_definition_label()}.plist"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def legacy_launch_agent_path() -> Path:
|
|
73
|
+
"""Return the one retired relay plist that may be migrated."""
|
|
74
|
+
return Path.home() / "Library" / "LaunchAgents" / f"{LEGACY_RELAY_LABEL}.plist"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def relay_functional_probe() -> bool:
|
|
78
|
+
"""Perform the bounded, token-free relay health check used for commit."""
|
|
79
|
+
try:
|
|
80
|
+
with urlopen("http://127.0.0.1:8765/health", timeout=2) as response: # noqa: S310 -- fixed loopback endpoint
|
|
81
|
+
return 200 <= response.status < 300
|
|
82
|
+
except (OSError, URLError):
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def pre_cutover_state(launchd: LaunchdProvider, *, probe=relay_functional_probe) -> PreCutoverState:
|
|
87
|
+
"""Capture the non-secret legacy state that rollback must restore exactly."""
|
|
88
|
+
legacy = legacy_launch_agent_path()
|
|
89
|
+
loaded = launchd.inspect(LEGACY_RELAY_LABEL)
|
|
90
|
+
runtime = launchd.runtime_status(LEGACY_RELAY_LABEL) if loaded else None
|
|
91
|
+
active = bool(runtime and runtime.qualified)
|
|
92
|
+
return PreCutoverState(legacy.is_file(), loaded, active, probe() if active else None)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _unload_and_verify(launchd: LaunchdProvider, label: str, plist: Path, *, code: str) -> None:
|
|
96
|
+
if not launchd.inspect(label):
|
|
97
|
+
return
|
|
98
|
+
launchd.uninstall(plist)
|
|
99
|
+
if launchd.inspect(label):
|
|
100
|
+
raise RelayCutoverError(code)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _restore_legacy(launchd: LaunchdProvider, state: PreCutoverState, legacy: Path, neutral: Path, *, probe=relay_functional_probe) -> None:
|
|
104
|
+
"""Restore exactly the captured legacy loaded state after neutral is absent."""
|
|
105
|
+
_unload_and_verify(launchd, _definition_label(), neutral, code="ROLLBACK_NEUTRAL_UNLOAD_UNVERIFIED")
|
|
106
|
+
neutral.unlink(missing_ok=True)
|
|
107
|
+
if state.legacy_loaded:
|
|
108
|
+
if not state.legacy_plist_present:
|
|
109
|
+
raise RelayCutoverError("ROLLBACK_LEGACY_PLIST_MISSING")
|
|
110
|
+
launchd.install(LEGACY_RELAY_LABEL, legacy)
|
|
111
|
+
if not launchd.inspect(LEGACY_RELAY_LABEL):
|
|
112
|
+
raise RelayCutoverError("ROLLBACK_LEGACY_LOAD_UNVERIFIED")
|
|
113
|
+
restored = launchd.runtime_status(LEGACY_RELAY_LABEL).qualified
|
|
114
|
+
if restored != state.legacy_process_active:
|
|
115
|
+
raise RelayCutoverError("ROLLBACK_LEGACY_PROCESS_STATE_MISMATCH")
|
|
116
|
+
if state.legacy_functional_probe is True and not probe():
|
|
117
|
+
raise RelayCutoverError("ROLLBACK_LEGACY_FUNCTIONAL_PROBE_FAILED")
|
|
118
|
+
elif launchd.inspect(LEGACY_RELAY_LABEL):
|
|
119
|
+
raise RelayCutoverError("ROLLBACK_LEGACY_STATE_MISMATCH")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def render_launch_agent(binary: Path) -> Path:
|
|
123
|
+
"""Render the one canonical Relay LaunchAgent from an installed binary."""
|
|
124
|
+
destination = launch_agent_path()
|
|
125
|
+
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
126
|
+
destination.write_text(
|
|
127
|
+
f'<?xml version="1.0" encoding="UTF-8"?><plist version="1.0"><dict><key>Label</key><string>{_definition_label()}</string><key>ProgramArguments</key><array><string>{escape(str(binary))}</string></array><key>RunAtLoad</key><true/><key>KeepAlive</key><true/></dict></plist>',
|
|
128
|
+
encoding="utf-8",
|
|
129
|
+
)
|
|
130
|
+
return destination
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def install(data_root: Path, *, probe=relay_functional_probe) -> dict[str, str]:
|
|
134
|
+
"""Install the canonical relay and retire its single migration source.
|
|
135
|
+
|
|
136
|
+
The commit point is after neutral loaded/process/functional proof. Before
|
|
137
|
+
that point the untouched legacy plist and captured lifecycle state are the
|
|
138
|
+
sole rollback authority.
|
|
139
|
+
"""
|
|
140
|
+
binary = build_relay(data_root)
|
|
141
|
+
plist = render_launch_agent(binary)
|
|
142
|
+
launchd = LaunchdProvider()
|
|
143
|
+
legacy = legacy_launch_agent_path()
|
|
144
|
+
state = pre_cutover_state(launchd, probe=probe)
|
|
145
|
+
if launchd.inspect(_definition_label()):
|
|
146
|
+
raise RelayCutoverError("NEUTRAL_ALREADY_LOADED")
|
|
147
|
+
if state.legacy_loaded:
|
|
148
|
+
_unload_and_verify(launchd, LEGACY_RELAY_LABEL, legacy, code="LEGACY_UNLOAD_UNVERIFIED")
|
|
149
|
+
try:
|
|
150
|
+
launchd.install(_definition_label(), plist)
|
|
151
|
+
if not launchd.inspect(_definition_label()):
|
|
152
|
+
raise RelayCutoverError("NEUTRAL_LOAD_UNVERIFIED")
|
|
153
|
+
if not launchd.runtime_status(_definition_label()).qualified:
|
|
154
|
+
raise RelayCutoverError("NEUTRAL_PROCESS_UNHEALTHY")
|
|
155
|
+
if not probe():
|
|
156
|
+
raise RelayCutoverError("NEUTRAL_FUNCTIONAL_PROBE_FAILED")
|
|
157
|
+
except Exception as error:
|
|
158
|
+
_restore_legacy(launchd, state, legacy, plist, probe=probe)
|
|
159
|
+
raise RelayCutoverError("RELAY_CUTOVER_ROLLED_BACK") from error
|
|
160
|
+
# Commit point: legacy is proven inactive, and neutral is loaded, active,
|
|
161
|
+
# and functionally healthy. Only now may the migration source be retired.
|
|
162
|
+
if state.legacy_plist_present:
|
|
163
|
+
legacy.unlink()
|
|
164
|
+
return {"component": "dashboard_relay", "binary": str(binary), "launch_agent": str(plist)}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def uninstall() -> dict[str, str]:
|
|
168
|
+
"""Remove only the canonical relay LaunchAgent; Server data is untouched."""
|
|
169
|
+
plist = launch_agent_path()
|
|
170
|
+
LaunchdProvider().uninstall(plist)
|
|
171
|
+
plist.unlink(missing_ok=True)
|
|
172
|
+
return {"component": "dashboard_relay", "launch_agent": str(plist)}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""macOS LaunchAgent lifecycle for the installed EP Server.
|
|
2
|
+
|
|
3
|
+
The Server remains the lifecycle and CENTRAL authority. This module only
|
|
4
|
+
installs one fixed per-user supervisor for its foreground ``serve`` command;
|
|
5
|
+
it never starts a second daemon or accepts arbitrary commands.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
import plistlib
|
|
13
|
+
import platform
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
from typing import Callable, Mapping, Sequence
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
LABEL = "com.engineeringplatform.server"
|
|
20
|
+
DEFAULT_PATH = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ServerServiceError(ValueError):
|
|
24
|
+
"""Raised when the bounded EP Server service cannot be managed safely."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class ServicePaths:
|
|
29
|
+
data_root: Path
|
|
30
|
+
launch_agents_dir: Path
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def plist_path(self) -> Path:
|
|
34
|
+
return self.launch_agents_dir / f"{LABEL}.plist"
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def log_dir(self) -> Path:
|
|
38
|
+
return self.data_root / "runtime"
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def stderr_log(self) -> Path:
|
|
42
|
+
return self.log_dir / "server-launchagent.err.log"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def default_paths(data_root: Path, home: Path | None = None) -> ServicePaths:
|
|
46
|
+
return ServicePaths(data_root.resolve(), (home or Path.home()).expanduser() / "Library" / "LaunchAgents")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _domain() -> str:
|
|
50
|
+
return f"gui/{os.getuid()}"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
Runner = Callable[[Sequence[str]], subprocess.CompletedProcess[str]]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _launchctl(arguments: Sequence[str], runner: Runner | None = None) -> subprocess.CompletedProcess[str]:
|
|
57
|
+
if platform.system() != "Darwin":
|
|
58
|
+
raise ServerServiceError("EP Server LaunchAgent lifecycle is supported only on macOS.")
|
|
59
|
+
return (runner or (lambda command: subprocess.run(command, capture_output=True, text=True, check=False)))(["launchctl", *arguments])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _installed_interpreter(candidate: str | Path | None = None) -> Path:
|
|
63
|
+
# Preserve a virtual-environment launcher symlink. Resolving it would
|
|
64
|
+
# turn ``<venv>/bin/python`` into its base interpreter, which no longer
|
|
65
|
+
# has the EP package installed when launchd invokes it.
|
|
66
|
+
executable = Path(candidate or sys.executable).expanduser().absolute()
|
|
67
|
+
if not executable.is_file() or not os.access(executable, os.X_OK):
|
|
68
|
+
raise ServerServiceError("The EP Server interpreter is not an executable installed runtime.")
|
|
69
|
+
return executable
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def plist_payload(paths: ServicePaths, interpreter: Path) -> dict[str, object]:
|
|
73
|
+
return {
|
|
74
|
+
"Label": LABEL,
|
|
75
|
+
"ProgramArguments": [str(interpreter), "-m", "engineering_platform.server", "serve", "--data-root", str(paths.data_root)],
|
|
76
|
+
"WorkingDirectory": str(paths.data_root),
|
|
77
|
+
"RunAtLoad": True,
|
|
78
|
+
"KeepAlive": {"SuccessfulExit": False},
|
|
79
|
+
"ProcessType": "Background",
|
|
80
|
+
"EnvironmentVariables": {
|
|
81
|
+
"PATH": DEFAULT_PATH,
|
|
82
|
+
"PYTHONNOUSERSITE": "1",
|
|
83
|
+
"PYTHONSAFEPATH": "1",
|
|
84
|
+
"EP_SERVER_DATA_ROOT": str(paths.data_root),
|
|
85
|
+
},
|
|
86
|
+
"StandardOutPath": "/dev/null",
|
|
87
|
+
"StandardErrorPath": str(paths.stderr_log),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def write_plist(paths: ServicePaths, interpreter: Path) -> Path:
|
|
92
|
+
paths.launch_agents_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
93
|
+
paths.log_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
94
|
+
content = plistlib.dumps(plist_payload(paths, interpreter), fmt=plistlib.FMT_XML, sort_keys=True)
|
|
95
|
+
temporary = paths.plist_path.with_suffix(".plist.tmp")
|
|
96
|
+
temporary.write_bytes(content)
|
|
97
|
+
temporary.chmod(0o644)
|
|
98
|
+
os.replace(temporary, paths.plist_path)
|
|
99
|
+
paths.plist_path.chmod(0o644)
|
|
100
|
+
return paths.plist_path
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def install(data_root: Path, *, interpreter: str | Path | None = None, home: Path | None = None,
|
|
104
|
+
runner: Runner | None = None) -> Mapping[str, str]:
|
|
105
|
+
paths = default_paths(data_root, home)
|
|
106
|
+
if not paths.data_root.is_dir() or not (paths.data_root / "server.json").is_file():
|
|
107
|
+
raise ServerServiceError("EP Server must be initialized before its LaunchAgent is installed.")
|
|
108
|
+
plist = write_plist(paths, _installed_interpreter(interpreter))
|
|
109
|
+
result = _launchctl(("bootstrap", _domain(), str(plist)), runner)
|
|
110
|
+
if result.returncode and "service already loaded" not in (result.stderr or "").lower():
|
|
111
|
+
raise ServerServiceError("Unable to bootstrap EP Server LaunchAgent.")
|
|
112
|
+
return {"state": "installed", "label": LABEL, "plist": str(plist), "data_root": str(paths.data_root), "stderr_log": str(paths.stderr_log)}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def uninstall(data_root: Path, *, home: Path | None = None, runner: Runner | None = None) -> Mapping[str, str]:
|
|
116
|
+
paths = default_paths(data_root, home)
|
|
117
|
+
if paths.plist_path.exists():
|
|
118
|
+
result = _launchctl(("bootout", _domain(), str(paths.plist_path)), runner)
|
|
119
|
+
if result.returncode and not any(marker in (result.stderr or "").lower() for marker in ("could not find service", "no such process", "not found")):
|
|
120
|
+
raise ServerServiceError("Unable to unload EP Server LaunchAgent.")
|
|
121
|
+
paths.plist_path.unlink(missing_ok=True)
|
|
122
|
+
return {"state": "uninstalled", "label": LABEL, "plist": str(paths.plist_path), "data_root": str(paths.data_root)}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Canonical, bounded engineering-status model and atomic projections."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import tempfile
|
|
10
|
+
|
|
11
|
+
from .agent_state import redact_diagnostic
|
|
12
|
+
from .platform_version import EngineeringPlatformManifest
|
|
13
|
+
from .storage import open_storage, store_projection
|
|
14
|
+
|
|
15
|
+
SCHEMA_VERSION = 1
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build(manifest: EngineeringPlatformManifest, **values: object) -> dict[str, object]:
|
|
19
|
+
"""Build the sole status payload; callers cannot publish arbitrary content."""
|
|
20
|
+
payload = {
|
|
21
|
+
"schema_version": SCHEMA_VERSION,
|
|
22
|
+
"platform_version": manifest.platform_version,
|
|
23
|
+
"watcher_version": manifest.watcher_version,
|
|
24
|
+
"dashboard_version": manifest.dashboard_version,
|
|
25
|
+
"inbox_protocol": manifest.inbox_protocol,
|
|
26
|
+
"watcher_state": "WATCHER_IDLE",
|
|
27
|
+
"current_phase": None,
|
|
28
|
+
"current_action": None,
|
|
29
|
+
"run_id": None,
|
|
30
|
+
"job_id": None,
|
|
31
|
+
"runner_pid": None,
|
|
32
|
+
"submitted_filename": None,
|
|
33
|
+
"prompt_title": None,
|
|
34
|
+
"last_executed_filename": None,
|
|
35
|
+
"last_executed_title": None,
|
|
36
|
+
"last_executed_run": None,
|
|
37
|
+
"last_executed_phase": None,
|
|
38
|
+
"blocking_predecessor_run": None,
|
|
39
|
+
"blocking_predecessor_phase": None,
|
|
40
|
+
"blocking_predecessor_filename": None,
|
|
41
|
+
"blocking_predecessor_title": None,
|
|
42
|
+
"predecessor_recovery_action": None,
|
|
43
|
+
"received_timestamp": None,
|
|
44
|
+
"elapsed_seconds": 0,
|
|
45
|
+
"queue_depth": 0,
|
|
46
|
+
"queue_items": [],
|
|
47
|
+
"repair_iteration": 0,
|
|
48
|
+
"implementation_pr": None,
|
|
49
|
+
"finalization_pr": None,
|
|
50
|
+
"reconciliation_pr": None,
|
|
51
|
+
"implementation_state": None,
|
|
52
|
+
"finalization_state": None,
|
|
53
|
+
"validation_summary": None,
|
|
54
|
+
"merge_status_check": None,
|
|
55
|
+
"repository_state": "MERGED_RECONCILED",
|
|
56
|
+
"workspace_state": "WORKSPACE_READY",
|
|
57
|
+
"owner_authorized": False,
|
|
58
|
+
"resume_available": False,
|
|
59
|
+
"latest_report": None,
|
|
60
|
+
"latest_completed_run": None,
|
|
61
|
+
"diagnostic": None,
|
|
62
|
+
"last_update": datetime.now(timezone.utc).isoformat(),
|
|
63
|
+
}
|
|
64
|
+
payload.update({key: value for key, value in values.items() if key in payload})
|
|
65
|
+
payload["diagnostic"] = (
|
|
66
|
+
redact_diagnostic(str(payload["diagnostic"])) if payload["diagnostic"] else None
|
|
67
|
+
)
|
|
68
|
+
return payload
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def publish(root: Path, payload: dict[str, object]) -> None:
|
|
72
|
+
"""Persist canonical watcher state, then publish derived JSON/Markdown."""
|
|
73
|
+
root.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
74
|
+
# Production callers pass ``<repository>/.engineering/status``. Keep
|
|
75
|
+
# the public projector usable for an isolated status directory too (for
|
|
76
|
+
# recovery/export tooling), without accidentally resolving its datastore
|
|
77
|
+
# at filesystem root.
|
|
78
|
+
repository_root = (
|
|
79
|
+
root.parent.parent
|
|
80
|
+
if root.name == "status" and root.parent.name == ".engineering"
|
|
81
|
+
else root
|
|
82
|
+
)
|
|
83
|
+
connection = open_storage(repository_root)
|
|
84
|
+
try:
|
|
85
|
+
store_projection(connection, "watcher_status", payload)
|
|
86
|
+
finally:
|
|
87
|
+
connection.close()
|
|
88
|
+
markdown = "\n".join(
|
|
89
|
+
(
|
|
90
|
+
"# Engineering Platform",
|
|
91
|
+
"",
|
|
92
|
+
"## Current State",
|
|
93
|
+
f"`{payload['watcher_state']}` — `{payload['current_phase'] or 'idle'}`",
|
|
94
|
+
"",
|
|
95
|
+
"## Active Job",
|
|
96
|
+
f"Run: `{payload['run_id'] or 'none'}` ",
|
|
97
|
+
f"File: `{payload['submitted_filename'] or 'none'}` ",
|
|
98
|
+
f"Prompt: `{payload['prompt_title'] or 'none'}`",
|
|
99
|
+
f"Queue: `{payload['queue_depth']}`",
|
|
100
|
+
"",
|
|
101
|
+
"## Pull Requests",
|
|
102
|
+
f"Implementation: `{payload['implementation_pr'] or 'none'}` ",
|
|
103
|
+
f"Finalization: `{payload['finalization_pr'] or 'none'}`",
|
|
104
|
+
f"Reconciliation: `{payload['reconciliation_pr'] or 'none'}`",
|
|
105
|
+
"",
|
|
106
|
+
"## Repository",
|
|
107
|
+
f"Repository: `{payload['repository_state']}` ",
|
|
108
|
+
f"Workspace: `{payload['workspace_state']}`",
|
|
109
|
+
"",
|
|
110
|
+
"## Authorization",
|
|
111
|
+
f"Owner-authorized: `{payload['owner_authorized']}`; release and deployment authority: `absent`",
|
|
112
|
+
"",
|
|
113
|
+
"## Latest Report",
|
|
114
|
+
f"`{payload['latest_report'] or 'none'}`",
|
|
115
|
+
"",
|
|
116
|
+
"## Predecessor Gate",
|
|
117
|
+
f"Blocking run: `{payload['blocking_predecessor_run'] or 'none'}` ",
|
|
118
|
+
f"Blocking prompt: `{payload['blocking_predecessor_title'] or payload['blocking_predecessor_filename'] or 'none'}` ",
|
|
119
|
+
str(payload["predecessor_recovery_action"] or "none"),
|
|
120
|
+
"",
|
|
121
|
+
"## Diagnostic",
|
|
122
|
+
str(payload["diagnostic"] or "none"),
|
|
123
|
+
"",
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
for name, content in (
|
|
127
|
+
("status.json", json.dumps(payload, indent=2, sort_keys=True) + "\n"),
|
|
128
|
+
("status.md", markdown),
|
|
129
|
+
):
|
|
130
|
+
descriptor, temporary = tempfile.mkstemp(prefix=f".{name}.", dir=root)
|
|
131
|
+
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
|
|
132
|
+
handle.write(content)
|
|
133
|
+
handle.flush()
|
|
134
|
+
os.fsync(handle.fileno())
|
|
135
|
+
os.replace(temporary, root / name)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Fail-closed recognition of a stale rolling-status preflight block."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from .agent_state import TransactionState
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_ROLLING_RECORDS = re.compile(r"\brolling(?:\s+status)?\s+records?\b", re.IGNORECASE)
|
|
10
|
+
_STALE_STATUS_INDICATOR = re.compile(
|
|
11
|
+
r"\b(?:finalization|reconcil(?:e|iation|ed)|pending|stale|state)\b",
|
|
12
|
+
re.IGNORECASE,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def is_stale_rolling_status_block(state: TransactionState) -> bool:
|
|
17
|
+
"""Recognize only the no-PR preflight block eligible for Finalization.
|
|
18
|
+
|
|
19
|
+
Diagnostics are advisory and are never delivery evidence. They merely
|
|
20
|
+
identify the already blocked, no-PR transaction for the existing dedicated
|
|
21
|
+
governance-only Finalization path. Any run-scoped PR evidence, a different
|
|
22
|
+
terminal state, or an unrelated diagnostic remains fail-closed.
|
|
23
|
+
"""
|
|
24
|
+
diagnostic = state.diagnostic or ""
|
|
25
|
+
return (
|
|
26
|
+
state.phase == "BLOCKED"
|
|
27
|
+
and state.terminal_condition == "external_blocked"
|
|
28
|
+
and state.transaction_kind == "IMPLEMENTATION"
|
|
29
|
+
and state.pull_request is None
|
|
30
|
+
and state.implementation_pull_request is None
|
|
31
|
+
and state.finalization_pull_request is None
|
|
32
|
+
and bool(_ROLLING_RECORDS.search(diagnostic))
|
|
33
|
+
and bool(_STALE_STATUS_INDICATOR.search(diagnostic))
|
|
34
|
+
)
|