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,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 2,
|
|
3
|
+
"platform": {
|
|
4
|
+
"id": "engineering-platform",
|
|
5
|
+
"name": "Engineering Platform",
|
|
6
|
+
"version": "2.2.0",
|
|
7
|
+
"generation": 2,
|
|
8
|
+
"documentation_namespace": "engineering-platform",
|
|
9
|
+
"capability_registry_version": 1
|
|
10
|
+
},
|
|
11
|
+
"workspace": {
|
|
12
|
+
"id": "replace-workspace-id",
|
|
13
|
+
"name": "Replace Workspace Name",
|
|
14
|
+
"repository": {"provider": "github", "owner": "replace-owner", "name": "replace-repository", "default_branch": "main"},
|
|
15
|
+
"branding": {"dashboard_title": "Replace Workspace Name"},
|
|
16
|
+
"workspace_authorization": {
|
|
17
|
+
"allowed_roots": [],
|
|
18
|
+
"allowed_repositories": [],
|
|
19
|
+
"denied_repositories": [],
|
|
20
|
+
"symlink_policy": "reject",
|
|
21
|
+
"case_sensitivity": "host"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"providers": {"runtime": "codex_cli", "repository": "github", "service_manager": "launchd", "remote_submission": "icloud_inbox", "private_remote_access": "tailscale", "dashboard": "private_http"}
|
|
25
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Structural identities for repository-defined validation controls."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import shlex
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
CANONICAL_DASHBOARD_COMMAND = "npm run test:engineering-dashboard"
|
|
8
|
+
_CANONICAL_DASHBOARD_TOKENS = ("npm", "run", "test:engineering-dashboard")
|
|
9
|
+
_SHELL_CONTROL_TOKENS = frozenset({";", "&&", "||", "|", "&", "<", ">"})
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def canonical_validation_launcher(command: str) -> str | None:
|
|
13
|
+
"""Return the planned launcher carried by a transparent shell transport.
|
|
14
|
+
|
|
15
|
+
A provider may report its ``/bin/zsh -lc`` transport command rather than
|
|
16
|
+
the command requested by the validation plan. Peel only that one
|
|
17
|
+
lossless transport envelope; compositions and diagnostics remain ineligible.
|
|
18
|
+
"""
|
|
19
|
+
if not isinstance(command, str) or not command.strip():
|
|
20
|
+
return None
|
|
21
|
+
try:
|
|
22
|
+
tokens = tuple(shlex.split(command))
|
|
23
|
+
except ValueError:
|
|
24
|
+
return None
|
|
25
|
+
if len(tokens) == 3 and tokens[0] in {"zsh", "/bin/zsh"} and tokens[1] == "-lc":
|
|
26
|
+
return tokens[2]
|
|
27
|
+
return command
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_canonical_dashboard_command(command: str) -> bool:
|
|
31
|
+
"""Return whether *command* structurally invokes the dashboard launcher.
|
|
32
|
+
|
|
33
|
+
The optional arguments after ``--`` belong to npm's script invocation.
|
|
34
|
+
Browser-related command text, diagnostics, and shell compositions are not
|
|
35
|
+
dashboard validation controls.
|
|
36
|
+
"""
|
|
37
|
+
command = canonical_validation_launcher(command)
|
|
38
|
+
if command is None:
|
|
39
|
+
return False
|
|
40
|
+
try:
|
|
41
|
+
tokens = tuple(shlex.split(command))
|
|
42
|
+
except ValueError:
|
|
43
|
+
return False
|
|
44
|
+
if any(token in _SHELL_CONTROL_TOKENS for token in tokens):
|
|
45
|
+
return False
|
|
46
|
+
return tokens == _CANONICAL_DASHBOARD_TOKENS or (
|
|
47
|
+
len(tokens) > len(_CANONICAL_DASHBOARD_TOKENS)
|
|
48
|
+
and tokens[:len(_CANONICAL_DASHBOARD_TOKENS)] == _CANONICAL_DASHBOARD_TOKENS
|
|
49
|
+
and tokens[len(_CANONICAL_DASHBOARD_TOKENS)] == "--"
|
|
50
|
+
)
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Conservative, diff-derived Engineering validation profile selection."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import argparse
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
DOCUMENTATION_PREFIXES = ("docs/",)
|
|
11
|
+
DASHBOARD_PREFIXES = ("src/engineering_platform/assets/",)
|
|
12
|
+
DASHBOARD_FILES = {"src/engineering_platform/server_console_services.py", "src/engineering_platform/server.py", "tests/engineering/dashboard.spec.mjs", "package.json", "package-lock.json"}
|
|
13
|
+
RUNTIME_PREFIXES = ("src/engineering_platform/", "tests/engineering/", ".github/workflows/")
|
|
14
|
+
VALIDATION_PROFILE_VERSION = "1.0"
|
|
15
|
+
REQUIRED_CONTROLS = {
|
|
16
|
+
"DOCUMENTATION": ("git_diff_check", "documentation_contract"),
|
|
17
|
+
"DASHBOARD": ("git_diff_check", "engineering_python", "console_route_ownership", "ui_localization", "dashboard_browser"),
|
|
18
|
+
"RUNTIME_UI": ("git_diff_check", "engineering_python", "console_route_ownership", "ui_localization", "dashboard_browser"),
|
|
19
|
+
"RUNTIME": ("git_diff_check", "engineering_python", "console_route_ownership", "dashboard_browser"),
|
|
20
|
+
"FULL": ("git_diff_check", "repository_suite"),
|
|
21
|
+
# A governed P-CENTRAL-CORE change retains the full Python/core suite but
|
|
22
|
+
# deliberately does not claim the deferred Operations Console browser
|
|
23
|
+
# qualification. It is selected only by the CI phase boundary below.
|
|
24
|
+
"P_CENTRAL_CORE": ("git_diff_check", "engineering_python"),
|
|
25
|
+
}
|
|
26
|
+
P_CENTRAL_CORE_BRANCH = re.compile(r"^codex/phase-p-central-core(?:-.+)?$")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class ValidationControlLauncher:
|
|
31
|
+
"""One deterministic launcher for a resolved validation-control identity.
|
|
32
|
+
|
|
33
|
+
Profiles select identities; this registry owns the repository-local
|
|
34
|
+
implementation of those identities. Lifecycle code only schedules the
|
|
35
|
+
persisted identities and never branches on a project-specific control.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
validation_id: str
|
|
39
|
+
category: str
|
|
40
|
+
control_identity: str
|
|
41
|
+
command: tuple[str, ...]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ValidationProfileResolutionError(ValueError):
|
|
45
|
+
"""The selected run profile is absent or does not match this registry."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _python_command(*arguments: str) -> tuple[str, ...]:
|
|
49
|
+
return (sys.executable, *arguments)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
CONTROL_LAUNCHERS = {
|
|
53
|
+
"git_diff_check": ValidationControlLauncher(
|
|
54
|
+
"git_diff_check", "repository", "git diff --check", ("git", "diff", "--check"),
|
|
55
|
+
),
|
|
56
|
+
"documentation_contract": ValidationControlLauncher(
|
|
57
|
+
"documentation_contract", "documentation",
|
|
58
|
+
"python3 -m unittest tests.engineering.test_engineering_operational_documentation",
|
|
59
|
+
_python_command("-m", "unittest", "tests.engineering.test_engineering_operational_documentation"),
|
|
60
|
+
),
|
|
61
|
+
"engineering_python": ValidationControlLauncher(
|
|
62
|
+
"engineering_python", "python", "python3 -m unittest discover -s tests/engineering",
|
|
63
|
+
_python_command("-m", "unittest", "discover", "-s", "tests/engineering"),
|
|
64
|
+
),
|
|
65
|
+
"dashboard_browser": ValidationControlLauncher(
|
|
66
|
+
"dashboard_browser", "browser", "npm run test:engineering-dashboard",
|
|
67
|
+
("npm", "run", "test:engineering-dashboard"),
|
|
68
|
+
),
|
|
69
|
+
"ui_localization": ValidationControlLauncher(
|
|
70
|
+
"ui_localization", "browser", "npm run test:ui-localization",
|
|
71
|
+
("npm", "run", "test:ui-localization"),
|
|
72
|
+
),
|
|
73
|
+
"console_route_ownership": ValidationControlLauncher(
|
|
74
|
+
"console_route_ownership", "python", "Console route ownership guard",
|
|
75
|
+
_python_command("tools/qualification/console_route_ownership_guard.py", "--source-root", "src"),
|
|
76
|
+
),
|
|
77
|
+
"repository_suite": ValidationControlLauncher(
|
|
78
|
+
"repository_suite", "repository", "python3 -m unittest discover",
|
|
79
|
+
_python_command("-m", "unittest", "discover"),
|
|
80
|
+
),
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def control_launcher(validation_id: str) -> ValidationControlLauncher | None:
|
|
85
|
+
"""Resolve a persisted required-control identity to its canonical launcher."""
|
|
86
|
+
return CONTROL_LAUNCHERS.get(validation_id)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def control_binding(validation_id: str) -> dict[str, object] | None:
|
|
90
|
+
"""Return the immutable launcher snapshot for one registry control."""
|
|
91
|
+
launcher = control_launcher(validation_id)
|
|
92
|
+
if launcher is None:
|
|
93
|
+
return None
|
|
94
|
+
return {
|
|
95
|
+
"validation_id": launcher.validation_id,
|
|
96
|
+
"required": True,
|
|
97
|
+
"category": launcher.category,
|
|
98
|
+
"control_identity": launcher.control_identity,
|
|
99
|
+
"command": list(launcher.command),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def resolve_producer_profile(payload: object) -> tuple["ValidationProfile", str]:
|
|
104
|
+
"""Resolve a producer-selected profile against the canonical registry.
|
|
105
|
+
|
|
106
|
+
A validation-only request carries the selection as structured execution
|
|
107
|
+
context; prose is never a selection input. The producer may select a
|
|
108
|
+
registry profile, but may not substitute its own control set.
|
|
109
|
+
"""
|
|
110
|
+
if not isinstance(payload, dict):
|
|
111
|
+
raise ValidationProfileResolutionError("Selected validation profile is unavailable.")
|
|
112
|
+
tier, version, controls = payload.get("tier"), payload.get("version"), payload.get("required_controls")
|
|
113
|
+
if not isinstance(tier, str) or tier not in REQUIRED_CONTROLS:
|
|
114
|
+
raise ValidationProfileResolutionError("Selected validation profile is invalid.")
|
|
115
|
+
if version != VALIDATION_PROFILE_VERSION:
|
|
116
|
+
raise ValidationProfileResolutionError("Selected validation profile version is unavailable.")
|
|
117
|
+
expected = REQUIRED_CONTROLS[tier]
|
|
118
|
+
if not isinstance(controls, list) or tuple(controls) != expected:
|
|
119
|
+
raise ValidationProfileResolutionError("Selected validation profile controls are invalid.")
|
|
120
|
+
return ValidationProfile(tier, (), tuple()), f"validation-profile-registry:{tier}@{version}"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def producer_profile_payload(tier: object) -> dict[str, object]:
|
|
124
|
+
"""Build the one allowed producer envelope value for a registry tier.
|
|
125
|
+
|
|
126
|
+
Producers select only the canonical tier. The registry remains the sole
|
|
127
|
+
owner of profile version and required-control identities, so a caller
|
|
128
|
+
cannot create a second profile representation or substitute controls.
|
|
129
|
+
"""
|
|
130
|
+
if not isinstance(tier, str) or tier not in REQUIRED_CONTROLS:
|
|
131
|
+
raise ValidationProfileResolutionError("Selected validation profile is invalid.")
|
|
132
|
+
payload: dict[str, object] = {
|
|
133
|
+
"tier": tier,
|
|
134
|
+
"version": VALIDATION_PROFILE_VERSION,
|
|
135
|
+
"required_controls": list(REQUIRED_CONTROLS[tier]),
|
|
136
|
+
}
|
|
137
|
+
resolve_producer_profile(payload)
|
|
138
|
+
return payload
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def profile_control_bindings(profile: "ValidationProfile") -> tuple[dict[str, object], ...]:
|
|
142
|
+
"""Snapshot every launcher selected by a profile before execution."""
|
|
143
|
+
bindings = tuple(control_binding(validation_id) for validation_id in profile.required_controls)
|
|
144
|
+
if any(binding is None for binding in bindings):
|
|
145
|
+
raise ValidationProfileResolutionError("Selected validation profile launcher is unavailable.")
|
|
146
|
+
return tuple(binding for binding in bindings if binding is not None)
|
|
147
|
+
|
|
148
|
+
@dataclass(frozen=True)
|
|
149
|
+
class ValidationProfile:
|
|
150
|
+
tier: str
|
|
151
|
+
paths: tuple[str, ...]
|
|
152
|
+
commands: tuple[str, ...]
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def required_controls(self) -> tuple[str, ...]:
|
|
156
|
+
return REQUIRED_CONTROLS[self.tier]
|
|
157
|
+
|
|
158
|
+
def classify(paths: list[str] | tuple[str, ...], *, governed_phase: str | None = None) -> ValidationProfile:
|
|
159
|
+
items = tuple(sorted({path.strip() for path in paths if path.strip()}))
|
|
160
|
+
if governed_phase == "P_CENTRAL_CORE":
|
|
161
|
+
return ValidationProfile("P_CENTRAL_CORE", items, ("relevant Engineering Python tests", "P-CENTRAL-CONSOLE browser deferred"))
|
|
162
|
+
if items and all(path.startswith(DOCUMENTATION_PREFIXES) or path.endswith(".md") for path in items):
|
|
163
|
+
return ValidationProfile("DOCUMENTATION", items, ("markdown/link/document-contract validation",))
|
|
164
|
+
if items and all(path.startswith(DASHBOARD_PREFIXES) or path in DASHBOARD_FILES for path in items):
|
|
165
|
+
return ValidationProfile("DASHBOARD", items, ("relevant Engineering Python tests", "npm run test:engineering-dashboard"))
|
|
166
|
+
if any(path.startswith(DASHBOARD_PREFIXES) or path in DASHBOARD_FILES for path in items):
|
|
167
|
+
return ValidationProfile("RUNTIME_UI", items, ("relevant Engineering Python tests", "UI-GOLDEN-LOCALIZATION", "npm run test:engineering-dashboard"))
|
|
168
|
+
if items and all(path.startswith(RUNTIME_PREFIXES) for path in items):
|
|
169
|
+
return ValidationProfile("RUNTIME", items, ("relevant Engineering Python tests", "npm run test:engineering-dashboard when projection is affected"))
|
|
170
|
+
return ValidationProfile("FULL", items, ("full required repository suite",))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def browser_dashboard_required(profile: ValidationProfile) -> bool:
|
|
174
|
+
"""Browser coverage is mandatory except for the governed CORE boundary."""
|
|
175
|
+
return profile.tier not in {"DOCUMENTATION", "P_CENTRAL_CORE"}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def localization_required(profile: ValidationProfile) -> bool:
|
|
179
|
+
"""Make the five-locale gate mandatory whenever a Console surface changes."""
|
|
180
|
+
return profile.tier in {"DASHBOARD", "RUNTIME_UI"}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def phase_for_branch(branch: str | None) -> str | None:
|
|
184
|
+
"""Return the only branch-governed exception to the browser requirement."""
|
|
185
|
+
return "P_CENTRAL_CORE" if isinstance(branch, str) and P_CENTRAL_CORE_BRANCH.fullmatch(branch) else None
|
|
186
|
+
|
|
187
|
+
def changed_paths(root: Path, base: str) -> tuple[str, ...]:
|
|
188
|
+
completed = subprocess.run(("git", "diff", "--name-only", f"{base}...HEAD"), cwd=root, text=True, capture_output=True, check=False)
|
|
189
|
+
if completed.returncode:
|
|
190
|
+
return ()
|
|
191
|
+
return tuple(completed.stdout.splitlines())
|
|
192
|
+
|
|
193
|
+
def main() -> int:
|
|
194
|
+
parser = argparse.ArgumentParser()
|
|
195
|
+
parser.add_argument("--base", required=True)
|
|
196
|
+
parser.add_argument("--github-output")
|
|
197
|
+
parser.add_argument("--branch")
|
|
198
|
+
args = parser.parse_args()
|
|
199
|
+
phase = phase_for_branch(args.branch)
|
|
200
|
+
profile = classify(changed_paths(Path.cwd(), args.base), governed_phase=phase)
|
|
201
|
+
if args.github_output:
|
|
202
|
+
with Path(args.github_output).open("a", encoding="utf-8") as output:
|
|
203
|
+
output.write(f"tier={profile.tier}\n")
|
|
204
|
+
output.write(f"phase={phase or 'DEFAULT'}\n")
|
|
205
|
+
output.write(f"browser_dashboard_required={'true' if browser_dashboard_required(profile) else 'false'}\n")
|
|
206
|
+
output.write(f"localization_required={'true' if localization_required(profile) else 'false'}\n")
|
|
207
|
+
print(profile.tier)
|
|
208
|
+
return 0
|
|
209
|
+
|
|
210
|
+
if __name__ == "__main__":
|
|
211
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""Fail-closed Level 2 checks for an Engineering execution workspace.
|
|
2
|
+
|
|
3
|
+
The checks in this module inspect only the selected target repository. They
|
|
4
|
+
never claim Inbox work, change a branch, contact a remote, or execute an
|
|
5
|
+
engineering action.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import asdict, dataclass
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
import tempfile
|
|
16
|
+
from time import monotonic
|
|
17
|
+
|
|
18
|
+
from .platform_api import PlatformConfiguration, PlatformConfigurationError, RepositoryAuthorization
|
|
19
|
+
from .drift_diagnostics import evidence_for_checks, guidance, persist as persist_drift_evidence
|
|
20
|
+
from .providers import GitProvider
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class WorkspacePreflightCheck:
|
|
25
|
+
identifier: str
|
|
26
|
+
outcome: str
|
|
27
|
+
reason: str
|
|
28
|
+
recovery: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class WorkspacePreflightResult:
|
|
33
|
+
outcome: str
|
|
34
|
+
workspace: str
|
|
35
|
+
target_repository: str
|
|
36
|
+
branch: str
|
|
37
|
+
execution_mode: str
|
|
38
|
+
timestamp: str
|
|
39
|
+
duration_ms: int
|
|
40
|
+
checks: tuple[WorkspacePreflightCheck, ...]
|
|
41
|
+
canonical_target_path: str | None = None
|
|
42
|
+
authorization_match: str | None = None
|
|
43
|
+
authorization_policy: str | None = None
|
|
44
|
+
drift_evidence: tuple[dict[str, str], ...] = ()
|
|
45
|
+
resume_guidance: dict[str, object] | None = None
|
|
46
|
+
|
|
47
|
+
def payload(self, run_id: str | None = None) -> dict[str, object]:
|
|
48
|
+
value = asdict(self)
|
|
49
|
+
value["checks"] = [asdict(check) for check in self.checks]
|
|
50
|
+
value["run_id"] = run_id
|
|
51
|
+
return value
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _check(identifier: str, passed: bool, reason: str, recovery: str) -> WorkspacePreflightCheck:
|
|
55
|
+
return WorkspacePreflightCheck(identifier, "PASS" if passed else "FAIL", reason, recovery)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _git(target: Path, *arguments: str):
|
|
59
|
+
return GitProvider().execute(target, "git", *arguments)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _prompt_value(prompt: str, field: str) -> str | None:
|
|
63
|
+
lines = prompt.splitlines()
|
|
64
|
+
for index, line in enumerate(lines):
|
|
65
|
+
if line.strip().casefold() == f"{field}:".casefold():
|
|
66
|
+
for following in lines[index + 1 :]:
|
|
67
|
+
value = following.strip()
|
|
68
|
+
if value:
|
|
69
|
+
return value
|
|
70
|
+
prefix = f"{field}:"
|
|
71
|
+
if line.strip().casefold().startswith(prefix.casefold()):
|
|
72
|
+
value = line.strip()[len(prefix) :].strip()
|
|
73
|
+
return value or None
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _execution_mode(prompt: str) -> str:
|
|
78
|
+
return "GENESIS" if (_prompt_value(prompt, "Execution Mode") or "").casefold() == "genesis" else "MANAGED"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _resolve_target(root: Path, prompt: str, mode: str, configuration: PlatformConfiguration | None) -> Path | None:
|
|
82
|
+
if mode == "MANAGED":
|
|
83
|
+
return root.resolve()
|
|
84
|
+
requested = _prompt_value(prompt, "Target repository")
|
|
85
|
+
if not requested:
|
|
86
|
+
return None
|
|
87
|
+
return Path(requested).expanduser()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _writable(path: Path) -> bool:
|
|
91
|
+
if not path.is_dir():
|
|
92
|
+
return False
|
|
93
|
+
try:
|
|
94
|
+
descriptor, temporary = tempfile.mkstemp(prefix=".workspace-preflight-", dir=path)
|
|
95
|
+
os.close(descriptor)
|
|
96
|
+
Path(temporary).unlink(missing_ok=True)
|
|
97
|
+
return True
|
|
98
|
+
except OSError:
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _git_index_lock_transaction(target: Path, git_directory: Path) -> tuple[bool, str]:
|
|
103
|
+
"""Prove that Git's real index-lock path can be claimed and released.
|
|
104
|
+
|
|
105
|
+
A generic temporary file proves only directory permissions. Git commits
|
|
106
|
+
through ``index.lock`` specifically, so admission verifies that exact
|
|
107
|
+
atomic create/unlink operation before allowing a managed execution.
|
|
108
|
+
"""
|
|
109
|
+
lock = git_directory / "index.lock"
|
|
110
|
+
descriptor: int | None = None
|
|
111
|
+
created = False
|
|
112
|
+
try:
|
|
113
|
+
descriptor = os.open(lock, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
114
|
+
created = True
|
|
115
|
+
os.close(descriptor)
|
|
116
|
+
descriptor = None
|
|
117
|
+
status = _git(target, "status", "--porcelain=v1", "--untracked-files=no")
|
|
118
|
+
if status.returncode != 0:
|
|
119
|
+
return False, "Git cannot read the repository index while its index lock is held."
|
|
120
|
+
lock.unlink()
|
|
121
|
+
return True, "Git can create, read through and clear the repository index lock."
|
|
122
|
+
except FileExistsError:
|
|
123
|
+
return False, "Git index lock already exists."
|
|
124
|
+
except OSError as error:
|
|
125
|
+
return False, f"Git cannot create the repository index lock: {error.strerror or 'write access denied'}."
|
|
126
|
+
finally:
|
|
127
|
+
if descriptor is not None:
|
|
128
|
+
os.close(descriptor)
|
|
129
|
+
# Only remove a lock we successfully created in this transaction.
|
|
130
|
+
if created and lock.exists():
|
|
131
|
+
try:
|
|
132
|
+
lock.unlink()
|
|
133
|
+
except OSError:
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _persist(root: Path, result: WorkspacePreflightResult, run_id: str | None) -> None:
|
|
138
|
+
directory = root / ".engineering" / "status"
|
|
139
|
+
if not _writable(directory):
|
|
140
|
+
return
|
|
141
|
+
temporary: str | None = None
|
|
142
|
+
try:
|
|
143
|
+
descriptor, temporary = tempfile.mkstemp(prefix=".workspace-preflight-", dir=directory)
|
|
144
|
+
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
|
|
145
|
+
handle.write(json.dumps(result.payload(run_id), separators=(",", ":"), sort_keys=True) + "\n")
|
|
146
|
+
handle.flush()
|
|
147
|
+
os.fsync(handle.fileno())
|
|
148
|
+
os.replace(temporary, directory / "workspace_preflight.json")
|
|
149
|
+
except OSError:
|
|
150
|
+
if temporary:
|
|
151
|
+
Path(temporary).unlink(missing_ok=True)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def execute(root: Path, prompt: str, *, run_id: str | None = None) -> WorkspacePreflightResult:
|
|
155
|
+
"""Run Level 2 workspace checks without mutating the selected repository."""
|
|
156
|
+
started = monotonic()
|
|
157
|
+
timestamp = datetime.now(timezone.utc).isoformat()
|
|
158
|
+
checks: list[WorkspacePreflightCheck] = []
|
|
159
|
+
try:
|
|
160
|
+
configuration = PlatformConfiguration.load(root)
|
|
161
|
+
checks.append(_check("workspace_identity", True, "Workspace identity is available.", "No action required."))
|
|
162
|
+
except PlatformConfigurationError:
|
|
163
|
+
configuration = None
|
|
164
|
+
checks.append(_check("workspace_identity", False, "Workspace identity is unavailable.", "Restore a valid Engineering Platform configuration."))
|
|
165
|
+
mode = _execution_mode(prompt)
|
|
166
|
+
target = _resolve_target(root, prompt, mode, configuration)
|
|
167
|
+
target_display = str(target) if target else "unavailable"
|
|
168
|
+
target_exists = target is not None and target.is_dir()
|
|
169
|
+
try:
|
|
170
|
+
canonical_candidate = str(target.resolve(strict=True)) if target and target_exists else None
|
|
171
|
+
except OSError:
|
|
172
|
+
canonical_candidate = None
|
|
173
|
+
checks.append(_check("target_repository", target_exists, "Target repository resolves to an existing directory." if target_exists else "Target repository cannot be resolved to an existing directory.", "Select an existing engineering target repository."))
|
|
174
|
+
authorization: RepositoryAuthorization | None = None
|
|
175
|
+
if target and target_exists and configuration:
|
|
176
|
+
authorization = configuration.authorize_target_repository(target, mode)
|
|
177
|
+
authorized = bool(authorization and authorization.authorized)
|
|
178
|
+
canonical_target = (authorization.canonical_target if authorization else None) or canonical_candidate
|
|
179
|
+
checks.append(_check(
|
|
180
|
+
"WORKSPACE_TARGET_AUTHORIZED",
|
|
181
|
+
authorized,
|
|
182
|
+
authorization.reason if authorization else "Workspace authorization configuration is unavailable.",
|
|
183
|
+
authorization.recovery if authorization else "Restore trusted workspace authorization configuration.",
|
|
184
|
+
))
|
|
185
|
+
|
|
186
|
+
git_directory: Path | None = None
|
|
187
|
+
if target_exists:
|
|
188
|
+
repository = _git(target, "rev-parse", "--git-dir")
|
|
189
|
+
if repository.returncode == 0 and repository.stdout.strip():
|
|
190
|
+
git_directory = Path(repository.stdout.strip())
|
|
191
|
+
if not git_directory.is_absolute():
|
|
192
|
+
git_directory = (target / git_directory).resolve()
|
|
193
|
+
checks.append(_check("git_repository", git_directory is not None, "Target is a valid Git repository." if git_directory else "Target is not a valid Git repository.", "Initialize or select a valid Git repository."))
|
|
194
|
+
if git_directory:
|
|
195
|
+
checks.append(_check("git_metadata_access", git_directory.is_dir(), "Git metadata is accessible." if git_directory.is_dir() else "Git metadata is inaccessible.", "Restore access to the repository Git metadata."))
|
|
196
|
+
metadata_writable = _writable(git_directory)
|
|
197
|
+
checks.append(_check("git_metadata_writable", metadata_writable, "Git metadata is writable." if metadata_writable else "Git metadata is not writable.", "Restore write access to the repository Git metadata."))
|
|
198
|
+
lock_transaction, lock_reason = _git_index_lock_transaction(target, git_directory)
|
|
199
|
+
checks.append(_check(
|
|
200
|
+
"git_index_lock_transaction",
|
|
201
|
+
lock_transaction,
|
|
202
|
+
lock_reason,
|
|
203
|
+
"Stop competing Git processes and restore write access to the repository index before retrying.",
|
|
204
|
+
))
|
|
205
|
+
status = _git(target, "status", "--porcelain=v1", "--untracked-files=all")
|
|
206
|
+
entries = status.stdout.splitlines() if status.returncode == 0 else []
|
|
207
|
+
staged = any(len(entry) >= 2 and entry[:2] != "??" and entry[0] != " " for entry in entries)
|
|
208
|
+
unstaged = any(len(entry) >= 2 and entry[:2] != "??" and entry[1] != " " for entry in entries)
|
|
209
|
+
untracked = any(entry.startswith("??") for entry in entries)
|
|
210
|
+
checks.extend((
|
|
211
|
+
_check("worktree_staged", not staged, "No staged changes are present." if not staged else "Staged changes are present.", "Commit, stash, or remove staged changes before execution."),
|
|
212
|
+
_check("worktree_unstaged", not unstaged, "No unstaged changes are present." if not unstaged else "Unstaged changes are present.", "Commit, stash, or remove unstaged changes before execution."),
|
|
213
|
+
_check("worktree_untracked", not untracked, "No untracked files are present." if not untracked else "Untracked files are present.", "Commit, remove, or explicitly ignore untracked files before execution."),
|
|
214
|
+
))
|
|
215
|
+
operation_checks = (
|
|
216
|
+
("git_index_lock", git_directory / "index.lock", "Remove the stale Git index lock after confirming no Git process is running."),
|
|
217
|
+
("git_merge", git_directory / "MERGE_HEAD", "Finish or abort the merge before execution."),
|
|
218
|
+
("git_rebase", git_directory / "rebase-merge", "Finish or abort the rebase before execution."),
|
|
219
|
+
("git_cherry_pick", git_directory / "CHERRY_PICK_HEAD", "Finish or abort the cherry-pick before execution."),
|
|
220
|
+
("git_revert", git_directory / "REVERT_HEAD", "Finish or abort the revert before execution."),
|
|
221
|
+
("git_bisect", git_directory / "BISECT_LOG", "Finish or reset the bisect before execution."),
|
|
222
|
+
)
|
|
223
|
+
for identifier, marker, recovery in operation_checks:
|
|
224
|
+
checks.append(_check(identifier, not marker.exists(), "No unfinished Git operation is present." if not marker.exists() else "An unfinished Git operation is present.", recovery))
|
|
225
|
+
branch = "unavailable"
|
|
226
|
+
if git_directory and target:
|
|
227
|
+
branch_result = _git(target, "branch", "--show-current")
|
|
228
|
+
branch = branch_result.stdout.strip() or "detached"
|
|
229
|
+
checks.append(_check("target_repository_identity", branch != "unavailable", "Target repository identity is available." if branch != "unavailable" else "Target repository identity is unavailable.", "Restore repository metadata and branch identity."))
|
|
230
|
+
if mode == "GENESIS":
|
|
231
|
+
checks.append(_check("genesis_local_repository", branch != "detached", "Genesis target is a local repository." if branch != "detached" else "Genesis target has no active local branch.", "Select a local repository with an active branch."))
|
|
232
|
+
else:
|
|
233
|
+
expected = configuration.workspace.default_branch if configuration else "main"
|
|
234
|
+
checks.append(_check("managed_expected_branch", branch == expected, "Managed target is on the expected branch." if branch == expected else f"Managed target is not on the expected branch {expected}.", f"Switch the repository to {expected} before submitting work."))
|
|
235
|
+
remote = _git(target, "remote", "get-url", "origin")
|
|
236
|
+
remote_valid = remote.returncode == 0 and bool(remote.stdout.strip())
|
|
237
|
+
checks.append(_check("managed_remote", remote_valid, "Managed target has a valid origin remote." if remote_valid else "Managed target has no valid origin remote.", "Configure the managed repository origin remote."))
|
|
238
|
+
divergence = _git(target, "rev-list", "--left-right", "--count", "@{upstream}...HEAD") if remote_valid else None
|
|
239
|
+
synchronized = bool(divergence and divergence.returncode == 0 and divergence.stdout.strip() == "0\t0")
|
|
240
|
+
checks.append(_check("managed_synchronization", synchronized, "Managed target is synchronized with its upstream." if synchronized else "Managed target is not synchronized with its upstream.", "Synchronize the expected branch with its configured upstream."))
|
|
241
|
+
workspace = configuration.workspace.name if configuration else "unavailable"
|
|
242
|
+
outcome = "FAIL" if any(check.outcome == "FAIL" for check in checks) else "PASS"
|
|
243
|
+
drift_evidence = persist_drift_evidence(root, evidence_for_checks(
|
|
244
|
+
checks, stage="Workspace Preflight", repository=str((target or root).resolve())
|
|
245
|
+
))
|
|
246
|
+
result = WorkspacePreflightResult(
|
|
247
|
+
outcome, workspace, target_display, branch, mode, timestamp, round((monotonic() - started) * 1000), tuple(checks),
|
|
248
|
+
canonical_target, authorization.matched if authorization else None, authorization.scope if authorization else None,
|
|
249
|
+
drift_evidence, guidance(drift_evidence),
|
|
250
|
+
)
|
|
251
|
+
_persist(root, result, run_id)
|
|
252
|
+
return result
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def latest(root: Path) -> dict[str, object]:
|
|
256
|
+
"""Return compact, safe Workspace Preflight evidence."""
|
|
257
|
+
try:
|
|
258
|
+
payload = json.loads((root / ".engineering" / "status" / "workspace_preflight.json").read_text(encoding="utf-8"))
|
|
259
|
+
except (OSError, json.JSONDecodeError):
|
|
260
|
+
return {}
|
|
261
|
+
if not isinstance(payload, dict):
|
|
262
|
+
return {}
|
|
263
|
+
return {key: payload[key] for key in ("outcome", "workspace", "target_repository", "canonical_target_path", "authorization_match", "authorization_policy", "branch", "execution_mode", "timestamp", "duration_ms", "checks", "drift_evidence", "resume_guidance", "run_id") if key in payload}
|