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.

Potentially problematic release.


This version of engineering-platform might be problematic. Click here for more details.

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,428 @@
1
+ """Public, provider-neutral Engineering Platform API.
2
+
3
+ This module is the supported boundary for workspace-aware platform consumers.
4
+ Provider implementations remain private and existing command wrappers remain
5
+ compatible.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ import json
11
+ from copy import deepcopy
12
+ from pathlib import Path
13
+ import os
14
+ import sys
15
+ from .providers import engineering_platform_codex_cli_prefix, registry
16
+ from .resources import package_text
17
+ from .platform_version import CURRENT_PLATFORM_VERSION
18
+
19
+
20
+ class PlatformConfigurationError(ValueError):
21
+ """Raised for invalid or unsupported platform configuration."""
22
+
23
+
24
+ # launchd deliberately starts with a minimal environment. Preserve the
25
+ # resolved launcher rather than depending on a later child process finding a
26
+ # different PATH. The value is local host configuration, not product data.
27
+ RUNTIME_EXECUTABLE_ENVIRONMENT = "ENGINEERING_PLATFORM_CODEX_EXECUTABLE"
28
+ RUNTIME_PATH_FALLBACK = (
29
+ "/opt/homebrew/bin",
30
+ "/usr/local/bin",
31
+ "/usr/bin",
32
+ "/bin",
33
+ "/usr/sbin",
34
+ "/sbin",
35
+ )
36
+
37
+
38
+ def shared_workspace_store(root: Path) -> Path:
39
+ """Resolve the private Engineering store shared by every Git worktree.
40
+
41
+ A linked worktree has a ``.git`` *file* that points at its private Git
42
+ directory. Git's ``commondir`` file then identifies the repository-wide
43
+ Git directory. Keeping local Engineering evidence immediately below that
44
+ common directory makes it independent of the currently checked-out
45
+ worktree while remaining private and repository-scoped.
46
+
47
+ Non-Git callers deliberately retain the old local layout. This keeps
48
+ isolated test fixtures and explicit non-repository tooling self-contained.
49
+ """
50
+ root = root.resolve()
51
+ git_marker = root / ".git"
52
+ if git_marker.is_dir():
53
+ common = git_marker.resolve()
54
+ elif git_marker.is_file():
55
+ try:
56
+ line = git_marker.read_text(encoding="utf-8").strip()
57
+ prefix = "gitdir: "
58
+ if not line.startswith(prefix):
59
+ return root / ".engineering"
60
+ git_dir = Path(line[len(prefix):].strip())
61
+ if not git_dir.is_absolute():
62
+ git_dir = git_marker.parent / git_dir
63
+ common_marker = git_dir.resolve() / "commondir"
64
+ if not common_marker.is_file():
65
+ return root / ".engineering"
66
+ common_path = common_marker.read_text(encoding="utf-8").strip()
67
+ common = Path(common_path)
68
+ if not common.is_absolute():
69
+ common = common_marker.parent / common
70
+ common = common.resolve()
71
+ except OSError:
72
+ return root / ".engineering"
73
+ else:
74
+ return root / ".engineering"
75
+ return common / "engineering-platform"
76
+
77
+
78
+ @dataclass(frozen=True)
79
+ class PlatformIdentity:
80
+ id: str
81
+ name: str
82
+ version: str
83
+ generation: int
84
+ documentation_namespace: str
85
+ capability_registry_version: int
86
+
87
+
88
+ @dataclass(frozen=True)
89
+ class WorkspaceIdentity:
90
+ id: str
91
+ name: str
92
+ repository_provider: str
93
+ repository_owner: str
94
+ repository_name: str
95
+ default_branch: str
96
+ dashboard_title: str
97
+ provisioning_root: str | None
98
+
99
+
100
+ @dataclass(frozen=True)
101
+ class AllowedWorkspaceRoot:
102
+ """A trusted directory which may contain engineering repositories."""
103
+
104
+ path: str
105
+ repository_scope: str
106
+
107
+
108
+ @dataclass(frozen=True)
109
+ class RepositoryAuthorizationPolicy:
110
+ """Fail-closed repository admission policy owned by host configuration."""
111
+
112
+ allowed_roots: tuple[AllowedWorkspaceRoot, ...]
113
+ allowed_repositories: tuple[str, ...]
114
+ denied_repositories: tuple[str, ...]
115
+ symlink_policy: str
116
+ case_sensitivity: str
117
+ legacy: bool = False
118
+
119
+
120
+ @dataclass(frozen=True)
121
+ class RepositoryAuthorization:
122
+ authorized: bool
123
+ canonical_target: str | None
124
+ matched: str | None
125
+ scope: str | None
126
+ reason: str
127
+ recovery: str
128
+
129
+
130
+ @dataclass(frozen=True)
131
+ class RuntimePromptTransport:
132
+ """Provider-neutral runtime-prompt transport; paths stay resolver-owned."""
133
+
134
+ provider: str
135
+ inbox: Path
136
+
137
+
138
+ @dataclass(frozen=True)
139
+ class ExecutionHostIdentity:
140
+ name: str
141
+ version: str
142
+ runtime: str
143
+ runtime_prompt_transport: str
144
+
145
+
146
+ class ExecutionHostConfigurationResolver:
147
+ """The sole host-specific configuration and location resolution boundary."""
148
+
149
+ def __init__(self, root: Path, configuration: "PlatformConfiguration") -> None:
150
+ self._root = root.resolve()
151
+ self._configuration = configuration
152
+
153
+ def resolve_runtime_prompt_transport(self) -> RuntimePromptTransport:
154
+ provider = self._configuration.providers["remote_submission"]
155
+ if provider != "icloud_inbox":
156
+ raise PlatformConfigurationError("Configured Runtime Prompt transport is unsupported.")
157
+ # Root-local dashboard settings and environment overrides are retired
158
+ # from the supported runtime. File ingress is Server-owned; this
159
+ # legacy resolver has one deterministic historical fallback only.
160
+ inbox_root = Path.home() / "Library/Mobile Documents/com~apple~CloudDocs/Engineering Platform"
161
+ return RuntimePromptTransport(provider, inbox_root / "Inbox")
162
+
163
+ def resolve_workspace_store(self) -> Path:
164
+ return shared_workspace_store(self._root)
165
+
166
+ def resolve_status_store(self) -> Path:
167
+ return self.resolve_workspace_store() / "status"
168
+
169
+ def resolve_report_store(self) -> Path:
170
+ return self.resolve_workspace_store() / "reports"
171
+
172
+ def resolve_log_store(self) -> Path:
173
+ return self.resolve_workspace_store() / "logs"
174
+
175
+ def resolve_telemetry_store(self) -> Path:
176
+ return self.resolve_workspace_store() / "engineering.db"
177
+
178
+ def resolve_runtime(self) -> Path:
179
+ if self._configuration.providers["runtime"] != "codex_cli":
180
+ raise PlatformConfigurationError("Configured Execution Host runtime is unsupported.")
181
+ executable = engineering_platform_codex_cli_prefix() / "bin" / "codex"
182
+ if not executable.is_file() or not os.access(executable, os.X_OK):
183
+ raise PlatformConfigurationError("Engineering Platform managed Codex CLI is unavailable.")
184
+ return executable
185
+
186
+ def runtime_environment(self) -> dict[str, str]:
187
+ """Return a child-safe environment pinned to the admitted CLI launcher."""
188
+ executable = self.resolve_runtime()
189
+ entries = [str(executable.parent), *RUNTIME_PATH_FALLBACK]
190
+ entries.extend(os.environ.get("PATH", "").split(":"))
191
+ return {
192
+ RUNTIME_EXECUTABLE_ENVIRONMENT: str(executable),
193
+ "PATH": ":".join(dict.fromkeys(entry for entry in entries if entry)),
194
+ }
195
+
196
+ def resolve_execution_host_identity(self) -> ExecutionHostIdentity:
197
+ return ExecutionHostIdentity(
198
+ self._configuration.platform.name,
199
+ self._configuration.platform.version,
200
+ self._configuration.providers["runtime"],
201
+ self._configuration.providers["remote_submission"],
202
+ )
203
+
204
+
205
+ @dataclass(frozen=True)
206
+ class PlatformConfiguration:
207
+ schema_version: int
208
+ platform: PlatformIdentity
209
+ workspace: WorkspaceIdentity
210
+ providers: dict[str, str]
211
+ repository_authorization: RepositoryAuthorizationPolicy
212
+
213
+ @classmethod
214
+ def load(cls, root: Path) -> "PlatformConfiguration":
215
+ try:
216
+ raw = json.loads(package_text("ENGINEERING_PLATFORM_CONFIG.json"))
217
+ local = root / ".engineering" / "engineering-platform.local.json"
218
+ if local.is_file():
219
+ override = json.loads(local.read_text(encoding="utf-8"))
220
+ if set(override) - {"workspace"}:
221
+ raise PlatformConfigurationError("Local Engineering Platform configuration is incompatible.")
222
+ raw = _merge(raw, override)
223
+ platform, workspace = raw["platform"], raw["workspace"]
224
+ repository, branding = workspace["repository"], workspace["branding"]
225
+ providers = raw["providers"]
226
+ except (OSError, KeyError, TypeError, json.JSONDecodeError) as error:
227
+ raise PlatformConfigurationError("Engineering Platform configuration is invalid.") from error
228
+ expected = {"runtime", "repository", "service_manager", "remote_submission", "private_remote_access", "dashboard"}
229
+ if set(raw) != {"schema_version", "platform", "workspace", "providers"} or raw.get("schema_version") not in {1, 2} or set(providers) != expected or not all(isinstance(v, str) and v for v in providers.values()):
230
+ raise PlatformConfigurationError("Engineering Platform configuration is incompatible.")
231
+ provisioning_root = workspace.get("provisioning_root")
232
+ if provisioning_root is not None and (
233
+ not isinstance(provisioning_root, str)
234
+ or not provisioning_root
235
+ or not Path(provisioning_root).is_absolute()
236
+ ):
237
+ raise PlatformConfigurationError("Engineering Workspace Root is invalid.")
238
+ identity = PlatformIdentity(platform["id"], platform["name"], platform["version"], platform["generation"], platform["documentation_namespace"], platform["capability_registry_version"])
239
+ if identity.id != "engineering-platform" or identity.version != CURRENT_PLATFORM_VERSION or identity.generation != 2:
240
+ raise PlatformConfigurationError("Engineering Platform identity is incompatible.")
241
+ authorization = _authorization_policy(workspace, provisioning_root)
242
+ return cls(int(raw["schema_version"]), identity, WorkspaceIdentity(workspace["id"], workspace["name"], repository["provider"], repository["owner"], repository["name"], repository["default_branch"], branding["dashboard_title"], provisioning_root), dict(providers), authorization)
243
+
244
+ def resolve_allowed_workspace_roots(self) -> tuple[AllowedWorkspaceRoot, ...]:
245
+ return self.repository_authorization.allowed_roots
246
+
247
+ def resolve_repository_authorization_policy(self) -> RepositoryAuthorizationPolicy:
248
+ return self.repository_authorization
249
+
250
+ def resolver(self, root: Path) -> ExecutionHostConfigurationResolver:
251
+ return ExecutionHostConfigurationResolver(root, self)
252
+
253
+ def authorize_target_repository(self, path: Path, execution_mode: str) -> RepositoryAuthorization:
254
+ """Authorize one existing target without broadening host filesystem access."""
255
+ policy = self.repository_authorization
256
+ raw = path.expanduser()
257
+ if not raw.is_absolute():
258
+ return _denied(None, "Target repository path must be absolute.", "Resubmit with an absolute repository path.")
259
+ if ".." in raw.parts:
260
+ return _denied(None, "Target repository path contains traversal.", "Resubmit with a canonical repository path without '..'.")
261
+ if policy.symlink_policy == "reject" and _has_untrusted_symlink_component(raw, policy):
262
+ return _denied(None, "Target repository contains a symlink and the policy rejects symlinks.", "Use a non-symlink repository path or change trusted host configuration.")
263
+ try:
264
+ target = raw.resolve(strict=True)
265
+ except OSError:
266
+ return _denied(None, "Target repository cannot be resolved canonically.", "Correct the target path and retry.")
267
+ if not target.is_dir():
268
+ return _denied(str(target), "Target repository is not a directory.", "Select an existing repository directory.")
269
+ if execution_mode == "MANAGED":
270
+ return RepositoryAuthorization(True, str(target), "managed-host", "managed", "Managed target is the configured execution host.", "No action required.")
271
+ denied = _matching_path(target, policy.denied_repositories, policy.case_sensitivity)
272
+ if denied:
273
+ return _denied(str(target), "Target repository is explicitly denied by trusted host configuration.", "Remove the deny-list entry only when the target is approved.")
274
+ allowed = _matching_path(target, policy.allowed_repositories, policy.case_sensitivity)
275
+ if allowed:
276
+ return RepositoryAuthorization(True, str(target), f"allow-list:{allowed.name}", "explicit_allow_list", "Target repository is explicitly authorized.", "No action required.")
277
+ for root in policy.allowed_roots:
278
+ try:
279
+ canonical_root = _canonical_root(root.path)
280
+ except OSError:
281
+ continue
282
+ if _under_scope(target, canonical_root, root.repository_scope, policy.case_sensitivity):
283
+ return RepositoryAuthorization(True, str(target), f"root:{canonical_root.name}", root.repository_scope, "Target repository matches an authorized workspace root.", "No action required.")
284
+ return _denied(str(target), "Target repository does not match an authorized workspace root or allow-list entry.", "Add an approved root or explicit repository entry in trusted host configuration, then retry.")
285
+
286
+
287
+ def _authorization_policy(workspace: dict[str, object], provisioning_root: str | None) -> RepositoryAuthorizationPolicy:
288
+ raw = workspace.get("workspace_authorization")
289
+ if raw is None:
290
+ roots = () if provisioning_root is None else (AllowedWorkspaceRoot(provisioning_root, "direct_children"),)
291
+ return RepositoryAuthorizationPolicy(roots, (), (), "reject", "host", legacy=True)
292
+ if not isinstance(raw, dict) or set(raw) - {"allowed_roots", "allowed_repositories", "denied_repositories", "symlink_policy", "case_sensitivity"}:
293
+ raise PlatformConfigurationError("Engineering Workspace Authorization is invalid.")
294
+ roots_raw = raw.get("allowed_roots", [])
295
+ allow_raw = raw.get("allowed_repositories", [])
296
+ deny_raw = raw.get("denied_repositories", [])
297
+ if not isinstance(roots_raw, list) or not isinstance(allow_raw, list) or not isinstance(deny_raw, list):
298
+ raise PlatformConfigurationError("Engineering Workspace Authorization is invalid.")
299
+ roots: list[AllowedWorkspaceRoot] = []
300
+ for entry in roots_raw:
301
+ if not isinstance(entry, dict) or set(entry) != {"path", "repository_scope"}:
302
+ raise PlatformConfigurationError("Engineering Workspace Authorization root is invalid.")
303
+ path, scope = entry["path"], entry["repository_scope"]
304
+ if not isinstance(path, str) or not Path(path).is_absolute() or scope not in {"direct_children", "descendants"}:
305
+ raise PlatformConfigurationError("Engineering Workspace Authorization root is invalid.")
306
+ roots.append(AllowedWorkspaceRoot(path, scope))
307
+ if not all(isinstance(path, str) and Path(path).is_absolute() for path in (*allow_raw, *deny_raw)):
308
+ raise PlatformConfigurationError("Engineering Workspace Authorization repository list is invalid.")
309
+ symlink_policy = raw.get("symlink_policy", "reject")
310
+ case_sensitivity = raw.get("case_sensitivity", "host")
311
+ if symlink_policy not in {"reject", "canonicalize_within_root"} or case_sensitivity not in {"host", "sensitive"}:
312
+ raise PlatformConfigurationError("Engineering Workspace Authorization is invalid.")
313
+ return RepositoryAuthorizationPolicy(tuple(roots), tuple(allow_raw), tuple(deny_raw), symlink_policy, case_sensitivity)
314
+
315
+
316
+ def _canonical_root(value: str) -> Path:
317
+ root = Path(value)
318
+ if root.is_symlink() or not root.is_dir() or root == Path(root.anchor):
319
+ raise OSError("invalid workspace root")
320
+ return root.resolve(strict=True)
321
+
322
+
323
+ def _has_untrusted_symlink_component(path: Path, policy: RepositoryAuthorizationPolicy) -> bool:
324
+ """Reject symlinks below a configured path, not host-owned mount aliases.
325
+
326
+ macOS commonly exposes temporary directories through `/var`, itself a
327
+ system symlink. A trusted configured root may therefore contain such a
328
+ host alias. Only components below that exact configured root are untrusted.
329
+ """
330
+ configured_paths = [Path(root.path).expanduser() for root in policy.allowed_roots]
331
+ configured_paths.extend(Path(value).expanduser() for value in policy.allowed_repositories)
332
+ configured_paths.extend(Path(value).expanduser() for value in policy.denied_repositories)
333
+ for configured in configured_paths:
334
+ try:
335
+ relative = path.relative_to(configured)
336
+ except ValueError:
337
+ continue
338
+ current = configured
339
+ for part in relative.parts:
340
+ current /= part
341
+ if current.is_symlink():
342
+ return True
343
+ return False
344
+
345
+
346
+ def _matching_path(target: Path, configured: tuple[str, ...], case_sensitivity: str) -> Path | None:
347
+ for value in configured:
348
+ try:
349
+ candidate = Path(value).resolve(strict=True)
350
+ except OSError:
351
+ continue
352
+ if _same_path(candidate, target, case_sensitivity):
353
+ return candidate
354
+ return None
355
+
356
+
357
+ def _same_path(left: Path, right: Path, case_sensitivity: str) -> bool:
358
+ return _path_parts(left, case_sensitivity) == _path_parts(right, case_sensitivity)
359
+
360
+
361
+ def _under_scope(target: Path, root: Path, scope: str, case_sensitivity: str) -> bool:
362
+ target_parts = _path_parts(target, case_sensitivity)
363
+ root_parts = _path_parts(root, case_sensitivity)
364
+ if len(target_parts) <= len(root_parts) or target_parts[:len(root_parts)] != root_parts:
365
+ return False
366
+ depth = len(target_parts) - len(root_parts)
367
+ return scope == "descendants" or depth == 1
368
+
369
+
370
+ def _path_parts(path: Path, case_sensitivity: str) -> tuple[str, ...]:
371
+ parts = path.parts
372
+ # APFS normally has case-insensitive semantics; explicit `sensitive` keeps
373
+ # exact component matching for hosts and volumes that require it.
374
+ if case_sensitivity == "host" and sys.platform == "darwin":
375
+ return tuple(part.casefold() for part in parts)
376
+ return parts
377
+
378
+
379
+ def _denied(target: str | None, reason: str, recovery: str) -> RepositoryAuthorization:
380
+ return RepositoryAuthorization(False, target, None, None, reason, recovery)
381
+
382
+
383
+ def resolve_allowed_workspace_roots(root: Path) -> tuple[AllowedWorkspaceRoot, ...]:
384
+ return PlatformConfiguration.load(root).resolve_allowed_workspace_roots()
385
+
386
+
387
+ def resolve_repository_authorization_policy(root: Path) -> RepositoryAuthorizationPolicy:
388
+ return PlatformConfiguration.load(root).resolve_repository_authorization_policy()
389
+
390
+
391
+ def authorize_target_repository(root: Path, path: Path, execution_mode: str) -> RepositoryAuthorization:
392
+ return PlatformConfiguration.load(root).authorize_target_repository(path, execution_mode)
393
+
394
+
395
+ def execution_host_configuration(root: Path) -> ExecutionHostConfigurationResolver:
396
+ """Return the canonical resolver; consumers must not derive host paths."""
397
+ return PlatformConfiguration.load(root).resolver(root)
398
+
399
+
400
+ def _merge(base: dict[str, object], override: dict[str, object]) -> dict[str, object]:
401
+ result = deepcopy(base)
402
+ for key, value in override.items():
403
+ if isinstance(value, dict) and isinstance(result.get(key), dict):
404
+ result[key] = _merge(result[key], value)
405
+ else:
406
+ result[key] = value
407
+ return result
408
+
409
+
410
+ def capabilities() -> tuple[str, ...]:
411
+ """Deterministic public capability registry for the 1.5 product boundary."""
412
+ return ("runner", "runtime_provider", "repository_provider", "service_manager_provider", "remote_submission_provider", "private_remote_access_provider", "dashboard", "qualification", "repository_handoff")
413
+
414
+
415
+ def provider_registry(root: Path) -> dict[str, object]:
416
+ """Public provider discovery API; unknown or unavailable providers are explicit."""
417
+ configuration = PlatformConfiguration.load(root)
418
+ active = registry(root)
419
+ if set(active) != {"runtime", "repository", "service_manager", "remote_submission", "private_remote_access"}:
420
+ raise PlatformConfigurationError("Provider registry is incomplete.")
421
+ return {kind: {"selected": configuration.providers[kind], "status": active[kind]} for kind in active}
422
+
423
+
424
+ def bootstrap_repository(root: Path) -> dict[str, object]:
425
+ """Public idempotent consumer bootstrap; never changes product source."""
426
+ from .platform_bootstrap import provision_workspace, validate_repository
427
+ configuration = validate_repository(root)
428
+ return {"platform": configuration.platform.id, "workspace": configuration.workspace.id, "provisioned": {name: str(path) for name, path in provision_workspace(root).items()}}