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,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}