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,41 @@
1
+ export function normalizeDashboardStatus(status, fallback) {
2
+ return status && typeof status === "object" && !Array.isArray(status)
3
+ ? status
4
+ : fallback;
5
+ }
6
+
7
+ export function normalizeDashboardSnapshot(snapshot) {
8
+ return snapshot && typeof snapshot === "object" && !Array.isArray(snapshot)
9
+ ? snapshot
10
+ : {};
11
+ }
12
+
13
+ export function createDashboardStatusStore({ fallback, render }) {
14
+ if (typeof render !== "function")
15
+ throw new TypeError("A dashboard status renderer is required.");
16
+
17
+ let latestSnapshotSource = null, latestSnapshotRevision = -1;
18
+ const store = {
19
+ status: fallback,
20
+ snapshot: {},
21
+ update(status, snapshot = {}) {
22
+ const nextStatus = normalizeDashboardStatus(status, fallback);
23
+ const nextSnapshot = normalizeDashboardSnapshot(snapshot);
24
+ const source = typeof nextSnapshot.snapshot_source === "string" ? nextSnapshot.snapshot_source : null;
25
+ const revision = Number.isSafeInteger(nextSnapshot.snapshot_revision) && nextSnapshot.snapshot_revision >= 0
26
+ ? nextSnapshot.snapshot_revision
27
+ : null;
28
+ if (source && revision !== null) {
29
+ if (source === latestSnapshotSource && revision < latestSnapshotRevision)
30
+ return { status: store.status, snapshot: store.snapshot };
31
+ if (source !== latestSnapshotSource) latestSnapshotSource = source;
32
+ latestSnapshotRevision = revision;
33
+ }
34
+ store.status = nextStatus;
35
+ store.snapshot = nextSnapshot;
36
+ render(store.status, store.snapshot);
37
+ return { status: store.status, snapshot: store.snapshot };
38
+ },
39
+ };
40
+ return store;
41
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "EP Operations",
3
+ "short_name": "EP Operations",
4
+ "display": "standalone",
5
+ "background_color": "#0a6b9d",
6
+ "theme_color": "#0a6b9d",
7
+ "icons": [
8
+ {"src": "/assets/operations-console/icon-dark.png", "sizes": "1254x1254", "type": "image/png", "purpose": "any"},
9
+ {"src": "/assets/operations-console/icon-light.png", "sizes": "1254x1254", "type": "image/png", "purpose": "any"}
10
+ ]
11
+ }
@@ -0,0 +1,285 @@
1
+ """Fail-closed Level 3 capability admission for Engineering Inbox work.
2
+
3
+ This module evaluates only declared transaction requirements against the local
4
+ Execution Host contract. It never claims an Inbox item, allocates a run or
5
+ touches a target repository.
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 re
16
+ import tempfile
17
+ from time import monotonic
18
+
19
+ from .platform_version import (
20
+ EngineeringPlatformCompatibilityError,
21
+ EngineeringPlatformManifest,
22
+ RunnerCompatibility,
23
+ _semver,
24
+ )
25
+ from .drift_diagnostics import evidence_for_checks, guidance, persist as persist_drift_evidence
26
+ from .provider_readiness import failures as provider_readiness_failures
27
+ from .central_database import capacity_reserve_from_environment
28
+ from .codex_capacity import read_remaining_percent
29
+ from .resources import package_path
30
+
31
+ RECOVERABILITY = frozenset(
32
+ {
33
+ "RETRYABLE",
34
+ "RETRYABLE_AFTER_HOST_REPAIR",
35
+ "REQUIRES_NEW_PROMPT",
36
+ "REQUIRES_OPERATOR_DECISION",
37
+ "NON_RETRYABLE",
38
+ }
39
+ )
40
+ FAILURE_ORIGINS = frozenset(
41
+ {"HOST", "WORKSPACE", "CAPABILITY", "VALIDATION", "ENGINEERING", "GOVERNANCE"}
42
+ )
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class CapabilityCheck:
47
+ identifier: str
48
+ outcome: str
49
+ reason: str
50
+ recovery: str
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class CapabilityPreflightResult:
55
+ outcome: str
56
+ timestamp: str
57
+ duration_ms: int
58
+ checks: tuple[CapabilityCheck, ...]
59
+ recoverability: str
60
+ failure_origin: str | None
61
+ recommendation: str
62
+ drift_evidence: tuple[dict[str, str], ...] = ()
63
+ resume_guidance: dict[str, object] | None = None
64
+
65
+ def payload(self, run_id: str | None = None) -> dict[str, object]:
66
+ value = asdict(self)
67
+ value["checks"] = [asdict(check) for check in self.checks]
68
+ value["run_id"] = run_id
69
+ return value
70
+
71
+
72
+ def _check(identifier: str, passed: bool, reason: str, recovery: str) -> CapabilityCheck:
73
+ return CapabilityCheck(identifier, "PASS" if passed else "FAIL", reason, recovery)
74
+
75
+
76
+ def _value(prompt: str, field: str) -> str | None:
77
+ match = re.search(rf"(?mi)^\s*{re.escape(field)}\s*:\s*([^\n]+)$", prompt)
78
+ return match.group(1).strip() if match else None
79
+
80
+
81
+ def _requirements(prompt: str) -> dict[str, str]:
82
+ """Read a bounded, provider-neutral declaration from the transaction."""
83
+ aliases = {
84
+ "Execution Host Version": "platform_version",
85
+ "Runner Version": "runner_version",
86
+ "Configuration Schema": "configuration_schema",
87
+ "Engineering Database Schema": "storage_schema",
88
+ "Checkpoint Format": "checkpoint_format",
89
+ "Memory Format": "memory_format",
90
+ "Report Format": "report_format",
91
+ "Execution Mode": "execution_mode",
92
+ "Required Runtime Components": "runtime_components",
93
+ "Required Provider Support": "provider_support",
94
+ "Required Capabilities": "capabilities",
95
+ }
96
+ return {key: value for field, key in aliases.items() if (value := _value(prompt, field))}
97
+
98
+
99
+ def _persist(root: Path, result: CapabilityPreflightResult, run_id: str | None) -> None:
100
+ directory = root / ".engineering" / "status"
101
+ if not directory.is_dir():
102
+ return
103
+ temporary: str | None = None
104
+ try:
105
+ descriptor, temporary = tempfile.mkstemp(prefix=".capability-preflight-", dir=directory)
106
+ with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
107
+ handle.write(
108
+ json.dumps(result.payload(run_id), separators=(",", ":"), sort_keys=True) + "\n"
109
+ )
110
+ os.replace(temporary, directory / "capability_preflight.json")
111
+ except OSError:
112
+ if temporary:
113
+ Path(temporary).unlink(missing_ok=True)
114
+
115
+
116
+ def execute(root: Path, prompt: str, *, run_id: str | None = None) -> CapabilityPreflightResult:
117
+ """Evaluate declared requirements before claim; absent declarations are compatible."""
118
+ started, checks = monotonic(), []
119
+ requirements = _requirements(prompt)
120
+ mode = requirements.get("execution_mode", "MANAGED").strip().upper()
121
+ required_providers = provider_readiness_failures(root, require_github=mode != "GENESIS")
122
+ checks.append(
123
+ _check(
124
+ "provider_readiness",
125
+ not required_providers,
126
+ "Required provider sessions are ready." if not required_providers else "Required provider sessions need attention: " + ", ".join(required_providers) + ".",
127
+ "Use the Engineering dashboard provider notification to install or sign in, then retry admission.",
128
+ )
129
+ )
130
+ reserve_percent = capacity_reserve_from_environment()
131
+ if reserve_percent:
132
+ remaining = read_remaining_percent()
133
+ capacity_ready = remaining is not None and remaining >= reserve_percent
134
+ checks.append(
135
+ _check(
136
+ "codex_capacity_reserve",
137
+ capacity_ready,
138
+ (
139
+ f"Codex has {remaining:.0f}% remaining; the configured reserve is {reserve_percent}%."
140
+ if remaining is not None
141
+ else "Codex capacity could not be verified for the configured admission reserve."
142
+ ),
143
+ "Wait for Codex capacity to recover or lower the reserve in Available AI capacity before retrying admission.",
144
+ )
145
+ )
146
+ try:
147
+ manifest = EngineeringPlatformManifest.load(
148
+ package_path("ENGINEERING_PLATFORM_VERSION.json")
149
+ )
150
+ runner = RunnerCompatibility()
151
+ except EngineeringPlatformCompatibilityError as error:
152
+ checks.append(
153
+ _check("host_contract", False, str(error), "Repair the Execution Host contract.")
154
+ )
155
+ manifest = None
156
+ runner = None
157
+ if manifest and runner:
158
+ versions = (
159
+ (
160
+ "platform_version",
161
+ "execution_host_version",
162
+ manifest.platform_version,
163
+ runner.platform_version,
164
+ ),
165
+ ("runner_version", "runner_version", manifest.runner_version, runner.runner_version),
166
+ )
167
+ for requirement, identifier, available, actual in versions:
168
+ required = requirements.get(requirement)
169
+ passed = not required or _semver(actual, identifier) >= _semver(required, requirement)
170
+ checks.append(
171
+ _check(
172
+ identifier,
173
+ passed,
174
+ f"Required {required or 'none'}; available {available}.",
175
+ "Upgrade the Execution Host or submit a compatible prompt.",
176
+ )
177
+ )
178
+ formats = (
179
+ ("checkpoint_format", manifest.checkpoint_format, runner.checkpoint_formats),
180
+ ("memory_format", manifest.memory_format, runner.memory_formats),
181
+ ("report_format", manifest.report_format, runner.report_formats),
182
+ ("storage_schema", manifest.storage_schema, runner.storage_schemas),
183
+ )
184
+ for requirement, available, supported in formats:
185
+ required = (
186
+ int(requirements[requirement])
187
+ if requirements.get(requirement, "").isdigit()
188
+ else available
189
+ )
190
+ checks.append(
191
+ _check(
192
+ requirement,
193
+ required in supported,
194
+ f"Required {required}; supported {sorted(supported)}.",
195
+ "Upgrade the Execution Host or use a supported format.",
196
+ )
197
+ )
198
+ configuration_schema = (
199
+ int(requirements["configuration_schema"])
200
+ if requirements.get("configuration_schema", "").isdigit()
201
+ else 1
202
+ )
203
+ checks.append(
204
+ _check(
205
+ "configuration_schema",
206
+ configuration_schema == 1,
207
+ f"Required {configuration_schema}; supported [1].",
208
+ "Upgrade the Execution Host or use configuration schema 1.",
209
+ )
210
+ )
211
+ mode = requirements.get("execution_mode", "MANAGED").upper()
212
+ checks.append(
213
+ _check(
214
+ "execution_mode",
215
+ mode in {"MANAGED", "GENESIS"},
216
+ f"Execution mode {mode} is supported."
217
+ if mode in {"MANAGED", "GENESIS"}
218
+ else f"Execution mode {mode} is unsupported.",
219
+ "Use Managed or Genesis execution mode.",
220
+ )
221
+ )
222
+ for requirement, identifier, available in (
223
+ ("runtime_components", "runtime_components", {"codex", "python", "git"}),
224
+ ("provider_support", "provider_support", {"launchd"}),
225
+ (
226
+ "capabilities",
227
+ "required_capabilities",
228
+ {
229
+ "workspace_authorization",
230
+ "host_preflight",
231
+ "workspace_preflight",
232
+ "capability_preflight",
233
+ },
234
+ ),
235
+ ):
236
+ requested = {
237
+ item.strip().casefold()
238
+ for item in requirements.get(requirement, "").split(",")
239
+ if item.strip()
240
+ }
241
+ missing = requested - available
242
+ checks.append(
243
+ _check(
244
+ identifier,
245
+ not missing,
246
+ "All declared requirements are available."
247
+ if not missing
248
+ else f"Unsupported requirements: {', '.join(sorted(missing))}.",
249
+ "Install or configure the required host capability before retrying.",
250
+ )
251
+ )
252
+ failed = any(check.outcome == "FAIL" for check in checks)
253
+ drift_evidence = persist_drift_evidence(root, evidence_for_checks(
254
+ checks, stage="Capability Preflight", repository=str(root.resolve())
255
+ ))
256
+ result = CapabilityPreflightResult(
257
+ "FAIL" if failed else "PASS",
258
+ datetime.now(timezone.utc).isoformat(),
259
+ round((monotonic() - started) * 1000),
260
+ tuple(checks),
261
+ "RETRYABLE_AFTER_HOST_REPAIR" if failed else "RETRYABLE",
262
+ "CAPABILITY" if failed else None,
263
+ "Repair or upgrade the Execution Host before resubmitting."
264
+ if failed
265
+ else "Capability admission passed.",
266
+ drift_evidence,
267
+ guidance(drift_evidence),
268
+ )
269
+ _persist(root, result, run_id)
270
+ return result
271
+
272
+
273
+ def latest(root: Path) -> dict[str, object]:
274
+ try:
275
+ payload = json.loads(
276
+ (root / ".engineering/status/capability_preflight.json").read_text(encoding="utf-8")
277
+ )
278
+ except (OSError, json.JSONDecodeError):
279
+ return {}
280
+ if not isinstance(payload, dict):
281
+ return {}
282
+ return {key: payload[key] for key in (
283
+ "outcome", "timestamp", "duration_ms", "checks", "recoverability",
284
+ "failure_origin", "recommendation", "drift_evidence", "resume_guidance", "run_id",
285
+ ) if key in payload}
@@ -0,0 +1,261 @@
1
+ """Deterministic, read-only capability reviewer selection for the Execution Host."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from concurrent.futures import ThreadPoolExecutor
6
+ from dataclasses import dataclass, field
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Callable, Protocol
10
+
11
+ from .agent_state import redact_diagnostic
12
+ from .provider_context import ProviderRole, project_context
13
+ from .provider_context_scope import ContextScope, POLICY_ID, provider_instruction
14
+ from .reviewer_evidence import ReviewerEvidence
15
+
16
+
17
+ REVIEWER_ORDER = (
18
+ "apple_platform",
19
+ "windows_platform",
20
+ "home_assistant_integration",
21
+ "esphome_firmware",
22
+ "pi_renderer",
23
+ "universal_receiver",
24
+ "website",
25
+ "api",
26
+ "repository_governance",
27
+ "validation",
28
+ "documentation",
29
+ "finalization",
30
+ )
31
+ REVIEWER_LABELS = {
32
+ "apple_platform": "Apple Platform Reviewer",
33
+ "windows_platform": "Windows Platform Reviewer",
34
+ "home_assistant_integration": "Home Assistant Integration Reviewer",
35
+ "esphome_firmware": "ESPHome Firmware Reviewer",
36
+ "pi_renderer": "Pi Renderer Reviewer",
37
+ "universal_receiver": "Universal Receiver Reviewer",
38
+ "website": "Website Reviewer",
39
+ "api": "API Reviewer",
40
+ "repository_governance": "Repository Governance Reviewer",
41
+ "validation": "Validation Reviewer",
42
+ "documentation": "Documentation Reviewer",
43
+ "finalization": "Finalization Reviewer",
44
+ }
45
+ PRODUCT_MATCHERS = {
46
+ "apple_platform": (("apps/apple/", "engineering-platform-app", "swiftui", "watchos", "macos", "ios"), "Apple platform capability"),
47
+ "windows_platform": (("apps/windows/", "engineering-platform-windows", "maui", "windows packaging"), "Windows platform capability"),
48
+ "home_assistant_integration": (("custom_components/engineering_platform", "home assistant", "config flow", "options flow", "coordinator", "entity model"), "Home Assistant integration capability"),
49
+ "esphome_firmware": (("esphome", "engineering-platform-esp32", "firmware yaml", ".yaml"), "ESPHome firmware capability"),
50
+ "pi_renderer": (("engineering-platform-pi", "pi renderer", "raspberry pi", "display lifecycle"), "Pi renderer capability"),
51
+ "universal_receiver": (("universal receiver", "vibecast", "browser receiver", "receiver transport"), "Universal Receiver capability"),
52
+ "website": (("engineering-platform-website", "website", "static site", "product messaging"), "Website capability"),
53
+ "api": (("engineering-platform-api", "rest api", "api contract", "api documentation"), "API capability"),
54
+ }
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class ReviewerSelection:
59
+ reviewer: str
60
+ selected_because: str
61
+ confidence: float
62
+ capability: str = "engineering"
63
+
64
+
65
+ @dataclass(frozen=True)
66
+ class ReviewerResult:
67
+ reviewer: str
68
+ contribution: str
69
+ recommendations: tuple[str, ...] = ()
70
+ failed: bool = False
71
+ usage: dict[str, object] = field(default_factory=dict)
72
+ runtime_metadata: dict[str, object] = field(default_factory=dict)
73
+ churn: dict[str, object] = field(default_factory=dict)
74
+ duration_seconds: float | None = None
75
+ usage_snapshots: tuple[dict[str, int], ...] = ()
76
+
77
+
78
+ class ReviewerClient(Protocol):
79
+ def review(
80
+ self,
81
+ root: Path,
82
+ selection: ReviewerSelection,
83
+ objective: str,
84
+ evidence: ReviewerEvidence | None = None,
85
+ ) -> ReviewerResult: ...
86
+
87
+
88
+ def select_reviewers(objective: str, prompt_path: Path, transaction_kind: str, memory: object) -> tuple[ReviewerSelection, ...]:
89
+ """Select only registered reviewers from objective, lifecycle and safe memory evidence."""
90
+ text = f"{prompt_path.name} {objective}".lower()
91
+ selected: dict[str, str] = {}
92
+ for reviewer, (markers, reason) in PRODUCT_MATCHERS.items():
93
+ if any(marker in text for marker in markers):
94
+ selected[reviewer] = reason
95
+ if any(token in text for token in ("governance", "repository", "roadmap", "policy", "bootstrap")):
96
+ selected["repository_governance"] = "repository-governance objective"
97
+ if any(token in text for token in ("test", "ruff", "bandit", "assurance", "validation", "failure")):
98
+ selected["validation"] = "validation-related objective"
99
+ if prompt_path.suffix.lower() == ".md" or any(token in text for token in ("document", "wording", "readme", "backlog")):
100
+ selected["documentation"] = "documentation-oriented objective"
101
+ if transaction_kind == "FINALIZATION" or "finalization" in text:
102
+ selected["finalization"] = "Finalization lifecycle evidence"
103
+ history = _reviewer_memory(memory)
104
+ result: list[ReviewerSelection] = []
105
+ for reviewer in REVIEWER_ORDER:
106
+ reason = selected.get(reviewer)
107
+ if reason is None:
108
+ continue
109
+ confidence = min(1.0, 0.5 + history.get(reviewer, 0.0))
110
+ capability = reviewer if reviewer in PRODUCT_MATCHERS else "engineering"
111
+ result.append(ReviewerSelection(reviewer, reason, confidence, capability))
112
+ return tuple(result)
113
+
114
+
115
+ ReviewerProgressCallback = Callable[[ReviewerSelection, str, ReviewerResult | None], None]
116
+
117
+
118
+ def run_reviews(
119
+ root: Path,
120
+ selections: tuple[ReviewerSelection, ...],
121
+ objective: str,
122
+ client: ReviewerClient | None,
123
+ progress: ReviewerProgressCallback | None = None,
124
+ evidence: ReviewerEvidence | None = None,
125
+ ) -> tuple[ReviewerResult, ...]:
126
+ """Run independent read-only reviews in parallel; any failure remains advisory."""
127
+ if not selections:
128
+ return ()
129
+ if client is None:
130
+ results = tuple(ReviewerResult(item.reviewer, "Reviewer client unavailable; primary review continues.", failed=True) for item in selections)
131
+ if progress:
132
+ for selection, result in zip(selections, results, strict=True):
133
+ progress(selection, "failed", result)
134
+ return results
135
+
136
+ def invoke(selection: ReviewerSelection) -> ReviewerResult:
137
+ if progress:
138
+ progress(selection, "started", None)
139
+ try:
140
+ result = client.review(root, selection, objective, evidence)
141
+ if result.reviewer != selection.reviewer:
142
+ result = ReviewerResult(selection.reviewer, "Reviewer identity mismatch; primary review continues.", failed=True)
143
+ else:
144
+ result = ReviewerResult(
145
+ selection.reviewer,
146
+ redact_diagnostic(result.contribution, limit=240),
147
+ tuple(redact_diagnostic(value, limit=240) for value in result.recommendations[:3]),
148
+ result.failed,
149
+ result.usage,
150
+ result.runtime_metadata,
151
+ result.churn,
152
+ result.duration_seconds,
153
+ )
154
+ except Exception: # Reviewer failure is advisory and cannot block the transaction.
155
+ result = ReviewerResult(selection.reviewer, "Reviewer failed; primary review continues.", failed=True)
156
+ if progress:
157
+ progress(selection, "failed" if result.failed else "completed", result)
158
+ return result
159
+
160
+ with ThreadPoolExecutor(max_workers=len(selections)) as executor:
161
+ completed = {item.reviewer: executor.submit(invoke, item) for item in selections}
162
+ return tuple(completed[item.reviewer].result() for item in selections)
163
+
164
+
165
+ def reconciled_recommendations(results: tuple[ReviewerResult, ...]) -> tuple[str, ...]:
166
+ """Deduplicate safe advisory recommendations for the primary agent prompt."""
167
+ accepted: list[str] = []
168
+ for result in results:
169
+ if result.failed:
170
+ continue
171
+ for recommendation in result.recommendations:
172
+ if recommendation and recommendation not in accepted:
173
+ accepted.append(recommendation)
174
+ return tuple(accepted[:8])
175
+
176
+
177
+ def records_for_storage(selections: tuple[ReviewerSelection, ...], results: tuple[ReviewerResult, ...]) -> tuple[dict[str, object], ...]:
178
+ by_reviewer = {result.reviewer: result for result in results}
179
+ records: list[dict[str, object]] = []
180
+ for selection in selections:
181
+ result = by_reviewer.get(selection.reviewer, ReviewerResult(selection.reviewer, "No result.", failed=True))
182
+ records.append({
183
+ "reviewer": selection.reviewer,
184
+ "capability": selection.capability,
185
+ "selected_because": selection.selected_because,
186
+ "confidence": selection.confidence,
187
+ "contribution": result.contribution,
188
+ "accepted_recommendations": len(result.recommendations) if not result.failed else 0,
189
+ "rejected_recommendations": 0,
190
+ "failed": result.failed,
191
+ "codex_commands_executed": _command_count(result.churn),
192
+ })
193
+ return tuple(records)
194
+
195
+
196
+ def _command_count(churn: object) -> int:
197
+ """Return the safe, per-review command total without sharing agent state."""
198
+ if not isinstance(churn, dict):
199
+ return 0
200
+ value = churn.get("tool_loop_operations", 0)
201
+ return value if isinstance(value, int) and not isinstance(value, bool) and value >= 0 else 0
202
+
203
+
204
+ def _reviewer_memory(memory: object) -> dict[str, float]:
205
+ if not isinstance(memory, dict):
206
+ return {}
207
+ result: dict[str, float] = {}
208
+ for entry in memory.get("reviewers", []):
209
+ if isinstance(entry, dict) and entry.get("reviewer") in REVIEWER_ORDER:
210
+ confidence = entry.get("future_confidence")
211
+ if isinstance(confidence, (int, float)) and 0.0 <= confidence <= 1.0:
212
+ result[str(entry["reviewer"])] = float(confidence)
213
+ return result
214
+
215
+
216
+ def reviewer_prompt(
217
+ selection: ReviewerSelection,
218
+ objective: str,
219
+ evidence: ReviewerEvidence | None = None,
220
+ ) -> str:
221
+ """Build the bounded read-only reviewer instruction without lifecycle authority."""
222
+ projection = project_context(ProviderRole.SPECIALIST_REVIEW, objective)
223
+ prompt: dict[str, object] = {
224
+ "reviewer": REVIEWER_LABELS[selection.reviewer],
225
+ "capability": selection.capability,
226
+ "selected_because": selection.selected_because,
227
+ "objective": projection.text,
228
+ "context_projection": {
229
+ "role": projection.role.value,
230
+ "budget_version": projection.budget_version,
231
+ "source_item_count": projection.source_item_count,
232
+ "omitted_low_priority_count": projection.omitted_low_priority_count,
233
+ },
234
+ "provider_context_scope": {
235
+ "policy": POLICY_ID,
236
+ "initial_scope": ContextScope.NORMAL.value,
237
+ "instruction": provider_instruction(ContextScope.NORMAL),
238
+ },
239
+ "authority": "Read-only inspection and recommendations only. Do not edit, commit, push, merge, create pull requests, finalize, or change lifecycle state.",
240
+ "scope": "Analyse only the declared capability. Cross-capability analysis requires objective repository evidence.",
241
+ }
242
+ if evidence is not None:
243
+ prompt["run_scoped_repository_evidence"] = evidence.to_dict()
244
+ prompt["evidence_instructions"] = (
245
+ "These are host-observed facts for this exact Run ID, collected after "
246
+ "synchronization and before this reviewer wave. Reuse them for ordinary "
247
+ "repository-state questions; do not rediscover branch, HEAD, worktree, "
248
+ "repository identity, or main ancestry with Git/GitHub. They are facts, "
249
+ "not conclusions. Retrieve only narrower additional evidence that your "
250
+ "capability review genuinely requires. This snapshot expires at any "
251
+ "repository mutation, validation, PR/merge, finalization, or cleanup boundary."
252
+ )
253
+ prompt["invocation_read_reuse"] = (
254
+ "Within this one reviewer invocation, reuse already inspected immutable file "
255
+ "content for factual inspection rather than accidentally rereading it. "
256
+ "Do not share content, conclusions, or reasoning with another reviewer or "
257
+ "Run ID. Reread after an edit, generated-artifact refresh, repository "
258
+ "change, validation, PR/merge, finalization, cleanup, or whenever freshness "
259
+ "is uncertain. Deliberate verification reads remain required."
260
+ )
261
+ return json.dumps(prompt, sort_keys=True)