engineering-platform 2.3.58__tar.gz → 2.3.66__tar.gz

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 (162) hide show
  1. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/PKG-INFO +1 -1
  2. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/pyproject.toml +1 -1
  3. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json +1 -1
  4. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json +4 -4
  5. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/agent_state.py +14 -3
  6. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/assets/dashboard.css +3 -3
  7. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/assets/dashboard.js +40 -21
  8. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/assets/dashboard_locales.mjs +16 -10
  9. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/console_route_ownership.py +1 -0
  10. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/dashboard_state.py +23 -0
  11. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/dashboard_supervisor.swift +10 -1
  12. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/evidence_projection.py +35 -2
  13. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_executor.py +51 -3
  14. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_host.py +383 -83
  15. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_reporting.py +6 -0
  16. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/installation_update_admission.py +26 -2
  17. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/installation_update_executor.py +30 -1
  18. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/installation_update_operation.py +6 -4
  19. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/live_status.py +26 -0
  20. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/operational_installation_record.py +30 -0
  21. engineering_platform-2.3.66/src/engineering_platform/owner_credential_recovery.py +1150 -0
  22. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/platform_version.py +1 -1
  23. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/server.py +295 -22
  24. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/server_console_services.py +1 -2
  25. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/server_relay.py +32 -4
  26. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/templates/workspace-config.json +1 -1
  27. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform.egg-info/PKG-INFO +1 -1
  28. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform.egg-info/SOURCES.txt +1 -0
  29. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/README.md +0 -0
  30. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/setup.cfg +0 -0
  31. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/__init__.py +0 -0
  32. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/__main__.py +0 -0
  33. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/agent_trust.py +0 -0
  34. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/assets/dashboard_status_store.mjs +0 -0
  35. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/assets/dashboard_translation.mjs +0 -0
  36. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/assets/operations-console/apple-touch-icon-dark.png +0 -0
  37. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/assets/operations-console/apple-touch-icon-light.png +0 -0
  38. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/assets/operations-console/icon-dark.png +0 -0
  39. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/assets/operations-console/icon-light.png +0 -0
  40. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/assets/operations-console/icon-transparent.png +0 -0
  41. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/assets/operations-console/manifest.webmanifest +0 -0
  42. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/capability_preflight.py +0 -0
  43. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/capability_review.py +0 -0
  44. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/central_data_transfer.py +0 -0
  45. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/central_database.py +0 -0
  46. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/central_store_migration.py +0 -0
  47. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/codex_capacity.py +0 -0
  48. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/codex_chat.py +0 -0
  49. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/codex_observability.py +0 -0
  50. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/component_lock.py +0 -0
  51. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/component_logging.py +0 -0
  52. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/console_presentation.py +0 -0
  53. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/contracts/__init__.py +0 -0
  54. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/contracts/ep_consumer.py +0 -0
  55. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/contracts/models.py +0 -0
  56. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/contracts/projection.py +0 -0
  57. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/dashboard_browser_validation.py +0 -0
  58. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/dashboard_translation.py +0 -0
  59. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/dependabot_producer.py +0 -0
  60. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/development_profile.py +0 -0
  61. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/drift_diagnostics.py +0 -0
  62. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/emergency_recovery.py +0 -0
  63. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/engineering_memory.py +0 -0
  64. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/ep_consumer_credentials.py +0 -0
  65. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_activity.py +0 -0
  66. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_context.py +0 -0
  67. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_errors.py +0 -0
  68. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_evidence.py +0 -0
  69. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_finalization.py +0 -0
  70. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_host_evidence.py +0 -0
  71. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_lease.py +0 -0
  72. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_lifecycle.py +0 -0
  73. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_models.py +0 -0
  74. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_readiness.py +0 -0
  75. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_repository.py +0 -0
  76. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_timeout_policy.py +0 -0
  77. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_timing.py +0 -0
  78. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/execution_transaction.py +0 -0
  79. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/external_producer_binding.py +0 -0
  80. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/file_inbox.py +0 -0
  81. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/forensic_attribution.py +0 -0
  82. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/forensic_attribution_v2.py +0 -0
  83. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/forensic_delta.py +0 -0
  84. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/golden_scenario.py +0 -0
  85. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/historical_dashboard_configuration.py +0 -0
  86. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/host_admin.py +0 -0
  87. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/host_preflight.py +0 -0
  88. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/installation_relocation.py +0 -0
  89. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/installation_update_activation.py +0 -0
  90. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/installation_update_backup.py +0 -0
  91. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/installation_update_composition.py +0 -0
  92. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/installation_update_migration.py +0 -0
  93. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/installation_update_plan.py +0 -0
  94. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/installation_update_preparation.py +0 -0
  95. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/investigation_ledger.py +0 -0
  96. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/legacy_inbox_migration.py +0 -0
  97. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/legacy_installation_adoption.py +0 -0
  98. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/lifecycle_worker.py +0 -0
  99. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/local_api.py +0 -0
  100. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/local_api_keychain.py +0 -0
  101. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/local_repository_binding.py +0 -0
  102. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/managed_autonomy.py +0 -0
  103. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/managed_codex_runtime.py +0 -0
  104. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/operational_installation.py +0 -0
  105. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/operational_installation_lock.py +0 -0
  106. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/parity_context.py +0 -0
  107. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/parity_lifecycle_dispatcher.py +0 -0
  108. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/platform_admin.py +0 -0
  109. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/platform_api.py +0 -0
  110. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/platform_bootstrap.py +0 -0
  111. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/platform_components.py +0 -0
  112. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/pr_check_repair.py +0 -0
  113. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/pr_evidence_backfill.py +0 -0
  114. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/producer.py +0 -0
  115. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/product_installation_readback.py +0 -0
  116. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/project_agent.py +0 -0
  117. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/project_agent_service.py +0 -0
  118. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/project_topology.py +0 -0
  119. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/prompt_history.py +0 -0
  120. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/provider_context.py +0 -0
  121. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/provider_context_benchmark.py +0 -0
  122. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/provider_context_scope.py +0 -0
  123. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/provider_interruption.py +0 -0
  124. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/provider_process_identity.py +0 -0
  125. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/provider_readiness.py +0 -0
  126. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/provider_recovery.py +0 -0
  127. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/provider_usage.py +0 -0
  128. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/providers.py +0 -0
  129. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/qualification.py +0 -0
  130. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/qualification_runtime.py +0 -0
  131. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/recommendation_handoff.py +0 -0
  132. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/release_operation.py +0 -0
  133. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/report_analysis.py +0 -0
  134. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/repository_attachment.py +0 -0
  135. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/repository_handoff.py +0 -0
  136. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/resources.py +0 -0
  137. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/reviewer_evidence.py +0 -0
  138. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/revision_binding.py +0 -0
  139. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/schemas/producer-readback-v1.2.schema.json +0 -0
  140. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/schemas/producer-readback-v1.schema.json +0 -0
  141. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/schemas/repository-attachment.schema.json +0 -0
  142. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/server_service.py +0 -0
  143. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/status_model.py +0 -0
  144. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/status_reconciliation.py +0 -0
  145. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/storage.py +0 -0
  146. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/submission_cli.py +0 -0
  147. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/submission_intake.py +0 -0
  148. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/submission_service.py +0 -0
  149. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/system_installation_topology.py +0 -0
  150. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/system_server_service.py +0 -0
  151. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/telemetry.py +0 -0
  152. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/validation_identity.py +0 -0
  153. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/validation_profile.py +0 -0
  154. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/version_preparation_delivery.py +0 -0
  155. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/workspace_preflight.py +0 -0
  156. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/worktree_provenance.py +0 -0
  157. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform/worktree_tooling.py +0 -0
  158. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform.egg-info/dependency_links.txt +0 -0
  159. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform.egg-info/entry_points.txt +0 -0
  160. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/src/engineering_platform.egg-info/top_level.txt +0 -0
  161. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/tests/test_adaptive_action_sizing_design.py +0 -0
  162. {engineering_platform-2.3.58 → engineering_platform-2.3.66}/tests/test_role_task_model_policy_design.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-platform
3
- Version: 2.3.58
3
+ Version: 2.3.66
4
4
  Summary: Local-first Engineering Platform execution operations runtime
5
5
  Author: Peter van Tol
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "engineering-platform"
7
- version = "2.3.58"
7
+ version = "2.3.66"
8
8
  description = "Local-first Engineering Platform execution operations runtime"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.14,<3.15"
@@ -3,7 +3,7 @@
3
3
  "platform": {
4
4
  "id": "engineering-platform",
5
5
  "name": "Engineering Platform",
6
- "version": "2.3.58",
6
+ "version": "2.3.66",
7
7
  "generation": 2,
8
8
  "documentation_namespace": "engineering-platform",
9
9
  "capability_registry_version": 1
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "bootstrap_contract": "2026.12",
3
3
  "checkpoint_format": 1,
4
- "dashboard_version": "2.3.58",
4
+ "dashboard_version": "2.3.66",
5
5
  "handoff_protocol": 1,
6
6
  "memory_format": 2,
7
7
  "minimum_codex_cli": "0.146.0",
8
8
  "inbox_protocol": 1,
9
- "platform_version": "2.3.58",
9
+ "platform_version": "2.3.66",
10
10
  "report_format": 2,
11
- "runner_version": "2.3.58",
11
+ "runner_version": "2.3.66",
12
12
  "status_model": 1,
13
13
  "storage_schema": 44,
14
- "watcher_version": "2.3.58"
14
+ "watcher_version": "2.3.66"
15
15
  }
@@ -301,27 +301,38 @@ class TransactionState:
301
301
  not isinstance(item, dict) or set(item) != audit_fields
302
302
  or not all(isinstance(value, str) and value and len(value) <= MAX_DIAGNOSTIC_LENGTH and value == redact_diagnostic(value) for value in item.values())
303
303
  or not item["iteration"].isdigit() or int(item["iteration"]) < 1
304
- or item["outcome"] not in {"validated", "validation_failed", "agent_failed"}
304
+ or item["outcome"] not in {"validated", "validation_failed", "agent_failed", "assessment_rejected"}
305
305
  or (item["commit_sha"] != "not_recorded" and not re.fullmatch(r"[0-9a-f]{40}", item["commit_sha"]))
306
306
  for item in state.local_validation_audit
307
307
  )
308
308
  ):
309
309
  raise StateError("checkpoint local validation audit is invalid or unsafe")
310
310
  repair_audit_fields = audit_fields | {"repair_id", "origin", "input_candidate_sha", "dispatch_id"}
311
+ repair_delivery_scope_fields = repair_audit_fields | {"pre_repair_pull_request"}
312
+ repair_delivery_authority_fields = repair_delivery_scope_fields | {"repair_branch", "repair_base", "first_pr_authorized"}
311
313
  if (
312
314
  not isinstance(state.repair_audit, tuple)
313
315
  or len(state.repair_audit) > 3
314
316
  or any(
315
- not isinstance(item, dict) or set(item) not in (audit_fields, repair_audit_fields)
317
+ not isinstance(item, dict) or set(item) not in (audit_fields, repair_audit_fields, repair_delivery_scope_fields, repair_delivery_authority_fields)
316
318
  or not all(isinstance(value, str) and value and len(value) <= MAX_DIAGNOSTIC_LENGTH and value == redact_diagnostic(value) for value in item.values())
317
319
  or not item["iteration"].isdigit() or int(item["iteration"]) < 1
318
320
  or item["outcome"] not in {"planned", "submitted_for_recheck", "agent_failed", "agent_timed_out"}
319
321
  or (item["commit_sha"] != "not_recorded" and not re.fullmatch(r"[0-9a-f]{40}", item["commit_sha"]))
320
- or (set(item) == repair_audit_fields and (
322
+ or (set(item) in (repair_audit_fields, repair_delivery_scope_fields, repair_delivery_authority_fields) and (
321
323
  not re.fullmatch(r"repair:[A-Za-z0-9_.:-]+:[1-3]", item["repair_id"])
322
324
  or item["origin"] not in {"validation", "quality", "security", "hosted", "finalization"}
323
325
  or (item["input_candidate_sha"] != "not_recorded" and not re.fullmatch(r"[0-9a-f]{40}", item["input_candidate_sha"]))
324
326
  ))
327
+ or (set(item) in (repair_delivery_scope_fields, repair_delivery_authority_fields) and (
328
+ item["pre_repair_pull_request"] != "none"
329
+ and not (item["pre_repair_pull_request"].isdigit() and int(item["pre_repair_pull_request"]) >= 1)
330
+ ))
331
+ or (set(item) == repair_delivery_authority_fields and (
332
+ (item["pre_repair_pull_request"] != "none" and item["first_pr_authorized"] != "no")
333
+ or (item["pre_repair_pull_request"] == "none" and item["first_pr_authorized"] not in {"yes", "no"})
334
+ or item["repair_branch"] == "none" or item["repair_base"] != "main"
335
+ ))
325
336
  for item in state.repair_audit
326
337
  )
327
338
  or len({item.get("repair_id", f"legacy:{item['iteration']}") for item in state.repair_audit}) != len(state.repair_audit)
@@ -273,7 +273,7 @@ html[data-theme="light"] :is(#rateLimitReset,#codexCliUpdate){background:#e8fff5
273
273
  .prompt-history-row{cursor:pointer}.prompt-history-lineage{color:#b9b6c0;display:block;font:11px/1.35 system-ui;margin-top:4px;white-space:normal}
274
274
  /* The shared modal shell owns the prompt-detail frame and header. This
275
275
  component owns only its evidence layout and its inner document cards. */
276
- .prompt-detail-modal__panel{display:grid;grid-template-rows:auto auto minmax(0,1fr);height:100%;overflow:hidden}.prompt-detail-modal__content{align-items:start;box-sizing:border-box;display:grid;gap:18px;grid-template-columns:minmax(0,1fr) minmax(280px,.82fr);min-height:0;overflow:auto;overscroll-behavior:contain;padding:0 12px 10px;scrollbar-gutter:stable both-edges}.prompt-detail-leftbar,.prompt-detail-rightbar{align-content:start;align-items:start;display:grid;gap:18px;grid-row:1;min-width:0}.prompt-detail-leftbar{grid-column:1}.prompt-detail-rightbar{grid-column:2}.prompt-detail-leftbar :is(.prompt-detail-card--execution-summary,.prompt-detail-sidebar),.prompt-detail-rightbar :is(.prompt-detail-card--execution-context,.prompt-detail-card--pull-requests){grid-column:auto;grid-row:auto}.prompt-detail-sidebar{align-content:start;align-items:start;display:grid;gap:18px}.prompt-detail-card{border:1px solid var(--category-color);border-radius:14px;padding:18px}.prompt-detail-card--wide{grid-column:1/-1}.prompt-detail-card h3{color:var(--category-color);font-size:18px;margin:0 0 14px}.prompt-detail-card .field{margin:0 0 14px}.prompt-detail-card .field:last-child{margin-bottom:0}.prompt-detail-card pre{overflow-wrap:anywhere;white-space:pre-wrap}.prompt-detail-card--execution-context{min-width:0;overflow:hidden}.prompt-detail-card--execution-context :is(.field,.label,span,li){min-width:0;overflow-wrap:anywhere;word-break:break-word}.detail-list-field ul{margin:6px 0 0;padding-inline-start:22px}@media (max-width:620px){.prompt-detail-modal__content{grid-template-columns:1fr;padding-inline:8px}.prompt-detail-leftbar,.prompt-detail-rightbar{grid-column:1;grid-row:auto}}
276
+ .prompt-detail-modal__panel{display:grid;grid-template-rows:auto auto minmax(0,1fr);height:100%;overflow:hidden}.prompt-detail-modal__content{align-items:start;box-sizing:border-box;display:grid;gap:18px;grid-template-columns:minmax(0,1fr) minmax(280px,.82fr);min-height:0;overflow:auto;overscroll-behavior:contain;padding:0 12px 10px;scrollbar-gutter:stable both-edges}.prompt-detail-leftbar,.prompt-detail-rightbar{align-content:start;align-items:start;display:grid;gap:18px;grid-row:1;min-width:0}.prompt-detail-leftbar{grid-column:1}.prompt-detail-rightbar{grid-column:2}.prompt-detail-leftbar :is(.prompt-detail-card--execution-summary,.prompt-detail-sidebar),.prompt-detail-rightbar :is(.prompt-detail-card--execution-context,.prompt-detail-card--pull-requests){grid-column:auto;grid-row:auto}.prompt-detail-sidebar{align-content:start;align-items:start;display:grid;gap:18px}.prompt-detail-card{border:1px solid var(--category-color);border-radius:14px;padding:18px}.prompt-detail-card--wide{grid-column:1/-1}.prompt-detail-card h3{color:var(--category-color);font-size:18px;margin:0 0 14px}.prompt-detail-card .field{margin:0 0 14px}.prompt-detail-card .field:last-child{margin-bottom:0}.prompt-detail-card pre{overflow-wrap:anywhere;white-space:pre-wrap}.prompt-detail-diagnostic>span:last-child{white-space:pre-wrap}.prompt-detail-card--execution-context{min-width:0;overflow:hidden}.prompt-detail-card--execution-context :is(.field,.label,span,li){min-width:0;overflow-wrap:anywhere;word-break:break-word}.detail-list-field ul{margin:6px 0 0;padding-inline-start:22px}@media (max-width:620px){.prompt-detail-modal__content{grid-template-columns:1fr;padding-inline:8px}.prompt-detail-leftbar,.prompt-detail-rightbar{grid-column:1;grid-row:auto}}
277
277
  .prompt-detail-provider-review{display:grid;gap:18px;grid-column:1/-1;grid-template-columns:minmax(0,1fr) minmax(260px,.58fr)}@media (max-width:760px){.prompt-detail-provider-review{grid-template-columns:1fr}}
278
278
  .prompt-detail-provider-review-stack{display:grid;gap:18px;grid-column:1/-1}.prompt-detail-provider-review>.prompt-detail-card{align-self:stretch;box-sizing:border-box}.prompt-detail-card--commit-timeline{display:flex;flex-direction:column;min-height:0}.prompt-detail-commit-timeline__list{align-content:start;display:grid;flex:1;gap:10px;list-style:none;margin:0;max-height:none;min-height:0;overflow:auto;padding:0 8px 0 0;scrollbar-gutter:stable}.prompt-detail-commit-timeline__item{border-top:1px solid color-mix(in srgb,var(--category-color) 38%,transparent);display:grid;gap:3px;padding-top:10px}.prompt-detail-commit-timeline__item:first-child{border-top:0;padding-top:0}.prompt-detail-commit-timeline__meta{color:var(--dashboard-modal-muted);font-size:12px;line-height:1.35}.prompt-detail-commit-timeline__item code{font-size:12px;overflow-wrap:anywhere}.prompt-detail-commit-timeline__item>span{font-size:13px;line-height:1.4;overflow-wrap:anywhere}
279
279
  .prompt-detail-commit-timeline__phase{border-top:1px solid color-mix(in srgb,var(--category-color) 52%,transparent);list-style:none;padding:13px 0 0}.prompt-detail-commit-timeline__phase:first-child{border-top:0;padding-top:0}.prompt-detail-commit-timeline__phase h4{color:var(--category-color);font-size:12px;letter-spacing:.04em;margin:0 0 9px;text-transform:uppercase}.prompt-detail-commit-timeline__phase-entries{display:grid;gap:10px;list-style:none;margin:0;padding:0 0 2px}.prompt-detail-commit-timeline__phase-entries .prompt-detail-commit-timeline__item{border-top:0;padding-top:0}
@@ -639,7 +639,7 @@ html[data-theme="light"] :is(.log-table,.telemetry-table) tbody tr:hover>td:firs
639
639
  #currentRun{--category-color:#65c5d9;--category-secondary:#a7e7f2;background:#1e2b33}
640
640
  #currentRun{container-name:active-execution;container-type:inline-size}
641
641
  #currentRun .current-run__grid,#currentRun .current-run__grid>*{min-width:0}
642
- @container active-execution (min-width:760px){#currentRun .current-run__grid{grid-template-columns:repeat(2,minmax(0,1fr))}#currentRun #currentDiagnostic{grid-column:1 / -1}}
642
+ @container active-execution (min-width:760px){#currentRun .current-run__grid{grid-template-columns:repeat(2,minmax(0,1fr))}#currentRun #executionIdentity{grid-column:1}#currentRun .card:has(#executionEstimate){grid-column:2}#currentRun #executionContext,#currentRun #currentDiagnostic{grid-column:1 / -1}}
643
643
  #currentRun .card,#currentRun .card:has(#currentTime),#currentRun .card:has(#executionEstimate),#currentRun .card:has(#runId),#currentRun .card:has(#executionMode){--category-color:#65c5d9;--category-secondary:#a7e7f2;background:#1b2931;border-color:#65c5d9}
644
644
  #currentRun :is(.execution-lifecycle,.execution-context--primary){background:#1b2931!important}
645
645
  #engineering-dashboard-content>#technicalDetails{--category-color:var(--house-style);--section-secondary:#a7e7f2;background:#302a24;border-color:var(--house-style)}
@@ -985,7 +985,7 @@ html[data-theme="light"] ::-webkit-scrollbar-thumb:hover{background:#0a6b9d}
985
985
  /* Telemetry can contain multiple long tables. Keep its outer dialog within
986
986
  the visual viewport and scroll only its evidence body on small screens. */
987
987
  .telemetry-detail-modal{--modal-accent:#fb7185;--category-color:var(--modal-accent);--modal-outer-gutter:24px;--modal-panel-max-width:1120px;--modal-shell-inset:24px;--modal-shell-padding:0}.telemetry-detail-modal__panel{--modal-header-inset:var(--dashboard-modal-panel-padding);display:grid;grid-template-rows:auto auto minmax(0,1fr);height:100%;max-height:100%;overflow:hidden;width:min(1120px,100%)}.telemetry-detail-modal__content{max-height:none;min-height:0;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain;padding:0 4px 4px}@media(max-width:620px){.telemetry-detail-modal__panel{--modal-header-inset:18px;padding:18px}.telemetry-detail-modal__content{padding-inline:2px}}
988
- .lifecycle-detail-modal{--modal-default-accent:#65c5d9}.lifecycle-detail-modal__panel{background:var(--modal-surface);border:2px solid var(--modal-accent);border-radius:var(--dashboard-modal-radius);box-sizing:border-box;color:var(--dashboard-modal-ink);display:grid;grid-template-rows:auto minmax(0,1fr);max-width:min(680px,calc(100vw - 32px));overflow:hidden;padding:var(--dashboard-modal-panel-padding);width:100%}.lifecycle-detail-modal__content{color:var(--dashboard-modal-ink);display:grid;gap:18px;max-height:none;min-height:0;overflow:auto;overscroll-behavior:contain;padding:4px}.lifecycle-detail-modal__content .field>span:last-child{color:var(--dashboard-modal-ink)}.lifecycle-detail-modal__content h3{color:var(--modal-accent);font-size:16px;margin:0 0 8px}.lifecycle-detail-modal :is(.label,.lifecycle-detail-modal__phase-list strong){color:var(--modal-secondary-accent)}.lifecycle-detail-modal__phase-list{display:grid;gap:8px;list-style:none;margin:0;padding:0}.lifecycle-detail-modal__phase-list li{align-items:baseline;border-bottom:1px solid color-mix(in srgb,var(--modal-accent) 32%,transparent);display:flex;gap:12px;justify-content:space-between;padding:8px 0}.lifecycle-detail-modal__phase-list li:last-child{border-bottom:0}.lifecycle-detail-modal__phase-list span{color:var(--dashboard-modal-muted);text-align:end}html[data-theme="light"] .lifecycle-detail-modal :is(.label,.lifecycle-detail-modal__phase-list strong){color:color-mix(in srgb,var(--modal-secondary-accent) 76%,#182230)}@media(max-width:620px){.lifecycle-detail-modal__panel{max-width:calc(100vw - 32px);padding:18px}.lifecycle-detail-modal__phase-list li{align-items:flex-start;flex-direction:column;gap:4px}.lifecycle-detail-modal__phase-list span{text-align:start}}
988
+ .lifecycle-detail-modal{--modal-default-accent:#65c5d9}.lifecycle-detail-modal__panel{background:var(--modal-surface);border:2px solid var(--modal-accent);border-radius:var(--dashboard-modal-radius);box-sizing:border-box;color:var(--dashboard-modal-ink);display:grid;grid-template-rows:auto minmax(0,1fr);max-width:min(680px,calc(100vw - 32px));overflow:hidden;padding:var(--dashboard-modal-panel-padding);width:100%}.lifecycle-detail-modal__content{color:var(--dashboard-modal-ink);display:grid;gap:18px;max-height:none;min-height:0;overflow:auto;overscroll-behavior:contain;padding:4px}.lifecycle-detail-modal__content .field>span:last-child{color:var(--dashboard-modal-ink)}.lifecycle-detail-modal__content h3{color:var(--modal-accent);font-size:16px;margin:0 0 8px}.lifecycle-detail-modal :is(.label,.lifecycle-detail-modal__phase-list strong){color:var(--modal-secondary-accent)}.lifecycle-detail-modal__phase-list{display:grid;gap:8px;list-style:none;margin:0;padding:0}.lifecycle-detail-modal__phase-list li{align-items:baseline;border-bottom:1px solid color-mix(in srgb,var(--modal-accent) 32%,transparent);display:flex;gap:12px;justify-content:space-between;padding:8px 0}.lifecycle-detail-modal__phase-list li:last-child{border-bottom:0}.lifecycle-detail-modal__phase-list span{color:var(--dashboard-modal-muted);text-align:start}html[data-theme="light"] .lifecycle-detail-modal :is(.label,.lifecycle-detail-modal__phase-list strong){color:color-mix(in srgb,var(--modal-secondary-accent) 76%,#182230)}@media(max-width:620px){.lifecycle-detail-modal__panel{max-width:calc(100vw - 32px);padding:18px}.lifecycle-detail-modal__phase-list li{align-items:flex-start;flex-direction:column;gap:4px}.lifecycle-detail-modal__phase-list span{text-align:start}}
989
989
  /* A telemetry Run-ID is a redundant activator inside an interactive row.
990
990
  The row owns its continuous rose selection surface, never the text itself. */
991
991
  .telemetry-run-link{-webkit-appearance:none;appearance:none;background:transparent;border:0;color:inherit;cursor:pointer;font:inherit;line-height:inherit;min-height:0;min-width:0;padding:0;text-align:left;text-decoration:none;white-space:nowrap}.telemetry-run-link:is(:hover,:active){background:transparent!important;color:inherit!important}.telemetry-run-link:is(:focus,:focus-visible){border-color:transparent!important;box-shadow:none!important;outline:0!important}
@@ -1863,6 +1863,8 @@ function renderExecutionContext(context, execution = {}) {
1863
1863
  [t("detail.target_checkout"), execution.checkout_path],
1864
1864
  [t("detail.target_branch"), execution.target_branch],
1865
1865
  [t("ui.active_branch"), execution.active_branch],
1866
+ [t("detail.candidate_sha"), execution.candidate_sha],
1867
+ [t("detail.repair_iteration"), execution.repair_iteration],
1866
1868
  ].filter(([, value]) => executionContextValue(value));
1867
1869
  if (!context || typeof context !== "object") {
1868
1870
  card.replaceChildren(
@@ -2244,7 +2246,7 @@ function lifecycleRepairEvidence(step) {
2244
2246
  dynamicField(t("detail.failed_checks"), item.failed_checks),
2245
2247
  dynamicField(t("detail.proposed_action"), item.proposed_action),
2246
2248
  dynamicField(t("detail.ai_repair_summary"), item.agent_summary),
2247
- lifecycleDetailField(t("detail.commit"), String(item.commit_sha || t("detail.not_recorded"))),
2249
+ lifecycleDetailField(t("detail.commit"), item.commit_sha === "not_recorded" ? t("detail.not_recorded") : String(item.commit_sha || t("detail.not_recorded"))),
2248
2250
  lifecycleDetailField(t("detail.outcome"), t("lifecycle.repair_outcome." + outcome, {}, outcome || t("detail.not_recorded"))),
2249
2251
  );
2250
2252
  section.append(heading, grid);
@@ -2386,18 +2388,24 @@ function lifecycleFlow(projection, { historical = false } = {}) {
2386
2388
  return section;
2387
2389
  }
2388
2390
  function activeRunPullRequestEntries(execution) {
2389
- const repository = String(execution?.target_repository || "").trim();
2390
- if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository)) return [];
2391
+ const repository = String(execution?.github_repository || execution?.target_repository || "").trim();
2392
+ const githubRepository = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository)
2393
+ ? repository : null;
2394
+ const seen = new Set();
2391
2395
  return [
2396
+ ["bound", execution?.pull_request],
2392
2397
  ["implementation", execution?.implementation_pr],
2393
2398
  ["finalization", execution?.finalization_pr],
2394
2399
  ].flatMap(([role, value]) => {
2395
2400
  const number = Number(value);
2396
- if (!Number.isInteger(number) || number <= 0) return [];
2401
+ if (!Number.isInteger(number) || number <= 0 || seen.has(number)) return [];
2402
+ seen.add(number);
2397
2403
  return [{
2398
2404
  role,
2399
2405
  number,
2400
- url: `https://github.com/${repository.split("/").map(encodeURIComponent).join("/")}/pull/${number}`,
2406
+ url: githubRepository
2407
+ ? `https://github.com/${githubRepository.split("/").map(encodeURIComponent).join("/")}/pull/${number}`
2408
+ : null,
2401
2409
  }];
2402
2410
  });
2403
2411
  }
@@ -2408,19 +2416,22 @@ function renderActivePullRequests(execution, lifecycle) {
2408
2416
  const entries = activeRunPullRequestEntries(execution);
2409
2417
  if (!entries.length) return;
2410
2418
  const labels = {
2419
+ bound: t("detail.bound_pull_request"),
2411
2420
  implementation: t("detail.implementation_pull_request"),
2412
2421
  finalization: t("detail.finalization_pull_request"),
2413
2422
  };
2414
2423
  const fields = entries.map((entry) => {
2415
2424
  const field = detailField(labels[entry.role], "");
2416
- const link = document.createElement("a");
2417
- link.className = "prompt-detail-pr-link";
2418
- link.href = entry.url;
2419
- link.target = "_blank";
2420
- link.rel = "noopener noreferrer";
2421
- link.textContent = `#${entry.number} ↗`;
2422
- link.setAttribute("aria-label", `${labels[entry.role]} #${entry.number}`);
2423
- field.lastElementChild.replaceChildren(link);
2425
+ const value = entry.url ? document.createElement("a") : document.createElement("span");
2426
+ value.className = "prompt-detail-pr-link";
2427
+ value.textContent = `#${entry.number}${entry.url ? " ↗" : ""}`;
2428
+ value.setAttribute("aria-label", `${labels[entry.role]} #${entry.number}`);
2429
+ if (entry.url) {
2430
+ value.href = entry.url;
2431
+ value.target = "_blank";
2432
+ value.rel = "noopener noreferrer";
2433
+ }
2434
+ field.lastElementChild.replaceChildren(value);
2424
2435
  return field;
2425
2436
  });
2426
2437
  const card = promptDetailCard(
@@ -3759,6 +3770,7 @@ const COMPONENT_DETAIL_CODES = new Set([
3759
3770
  "LIFECYCLE_WORKER_SERVER_HOSTED", "OPERATIONS_CONSOLE_SERVER_NATIVE",
3760
3771
  "DASHBOARD_RELAY_TAILSCALE_AVAILABLE", "DASHBOARD_RELAY_LAUNCH_AGENT_UNLOADED",
3761
3772
  "DASHBOARD_RELAY_PROCESS_INACTIVE", "DASHBOARD_RELAY_LIFECYCLE_UNAVAILABLE",
3773
+ "DASHBOARD_RELAY_TAILSCALE_UNAVAILABLE", "DASHBOARD_RELAY_ENDPOINT_UNREACHABLE",
3762
3774
  ]);
3763
3775
  function transportState(code) {
3764
3776
  const raw = String(code || ""), normalized = LEGACY_TRANSPORT_STATUS_CODES[raw] || raw;
@@ -3895,6 +3907,19 @@ function showComponentModal(payload) {
3895
3907
  payload.healthy ? t("component.health_healthy") : t("component.health_unhealthy"),
3896
3908
  );
3897
3909
  componentDetailField(fields, t("component.version"), payload.version);
3910
+ if (payload.component === "dashboard_relay") {
3911
+ componentDetailEndpoint(fields, t("component.relay_access"), payload.relay_endpoint);
3912
+ componentDetailField(fields, t("component.tailscale_ipv4"), payload.tailscale_ipv4);
3913
+ componentDetailField(
3914
+ fields,
3915
+ t("component.relay_probe"),
3916
+ payload.relay_reachable === true
3917
+ ? t("component.relay_reachable")
3918
+ : payload.relay_reachable === false
3919
+ ? t("component.relay_unreachable")
3920
+ : null,
3921
+ );
3922
+ }
3898
3923
  if (payload.kind === "TRANSPORT") {
3899
3924
  const transportTimestamp = (value) => value ? formatTimestamp(value) : null;
3900
3925
  if (payload.component === "http_ingress")
@@ -7808,7 +7833,8 @@ function promptDetailExecutionSections(history) {
7808
7833
  const dynamicRows = [];
7809
7834
  const dynamicDiagnosticField = (label, source) => {
7810
7835
  const original = executionContextValue(source), value = formatDiagnostic(original || t("detail.not_recorded"));
7811
- const field = detailField(label, value, true);
7836
+ const field = detailField(label, value);
7837
+ field.classList.add("prompt-detail-diagnostic");
7812
7838
  if (dynamicDiagnosticSource(original)) dynamicRows.push({ source: original, element: field.lastElementChild, format: formatDiagnosticProjection });
7813
7839
  return field;
7814
7840
  };
@@ -7912,19 +7938,12 @@ function promptDetailUsageSection(usage) {
7912
7938
  usage_snapshot_count: t("detail.usage_snapshot_count"),
7913
7939
  intermediate_usage_delta_available: t("detail.intermediate_usage_delta_available"),
7914
7940
  maximum_incremental_input_tokens: t("detail.maximum_incremental_input_tokens"),
7915
- actual_single_request_context_size: t("detail.actual_single_request_context_size"),
7916
- active_context_size: t("detail.active_context_size"),
7917
7941
  estimated_credits: t("detail.estimated_credits"),
7918
7942
  estimated_eur: t("detail.estimated_eur"),
7919
- speed_state: t("detail.speed_state"),
7920
7943
  usage_authority: t("detail.usage_authority"),
7921
7944
  };
7922
7945
  const visible = Object.entries(usage).filter(([key, value]) => labels[key] && value !== null && typeof value !== "object");
7923
7946
  const displayValue = (key, value) => {
7924
- if (["actual_single_request_context_size", "active_context_size"].includes(key) && value === "UNAVAILABLE") {
7925
- return t("format.unavailable");
7926
- }
7927
- if (key === "speed_state") return t(`provider_usage.speed.${String(value).toLowerCase()}`, {}, String(value));
7928
7947
  if (key === "usage_authority") return t(`provider_usage.authority.${String(value).toLowerCase()}`, {}, String(value));
7929
7948
  return value;
7930
7949
  };
@@ -3391,11 +3391,11 @@ Object.assign(DASHBOARD_MESSAGES.nl, {"lifecycle.detail_assurance":"Kwaliteits-
3391
3391
  Object.assign(DASHBOARD_MESSAGES.de, {"lifecycle.detail_assurance":"Qualitäts- und Sicherheitsprüfung","lifecycle.repair_rounds":"Reparaturrunden: {used}/{maximum}","lifecycle.assurance_no_findings":"Keine Befunde erfasst"});
3392
3392
  Object.assign(DASHBOARD_MESSAGES.fr, {"lifecycle.detail_assurance":"Assurance qualité et sécurité","lifecycle.repair_rounds":"Cycles de correction : {used}/{maximum}","lifecycle.assurance_no_findings":"Aucune conclusion enregistrée"});
3393
3393
  Object.assign(DASHBOARD_MESSAGES.es, {"lifecycle.detail_assurance":"Garantía de calidad y seguridad","lifecycle.repair_rounds":"Rondas de reparación: {used}/{maximum}","lifecycle.assurance_no_findings":"No se registraron hallazgos"});
3394
- Object.assign(DASHBOARD_MESSAGES.en, {"lifecycle.detail_repair_evidence":"Executed pull request check repairs","lifecycle.detail_repair_iteration":"Repair iteration {iteration}","lifecycle.repair_outcome.submitted_for_recheck":"Submitted for recheck","lifecycle.repair_outcome.agent_failed":"Agent repair failed","lifecycle.repair_outcome.agent_timed_out":"Agent repair timed out"});
3395
- Object.assign(DASHBOARD_MESSAGES.nl, {"lifecycle.detail_repair_evidence":"Uitgevoerd PR-controleherstel","lifecycle.detail_repair_iteration":"Hersteliteratie {iteration}","lifecycle.repair_outcome.submitted_for_recheck":"Ingediend voor hercontrole","lifecycle.repair_outcome.agent_failed":"Agentherstel mislukt","lifecycle.repair_outcome.agent_timed_out":"Agentherstel heeft de tijdslimiet overschreden"});
3396
- Object.assign(DASHBOARD_MESSAGES.de, {"lifecycle.detail_repair_evidence":"Durchgeführte PR-Prüfreparaturen","lifecycle.detail_repair_iteration":"Reparaturiteration {iteration}","lifecycle.repair_outcome.submitted_for_recheck":"Zur erneuten Prüfung eingereicht","lifecycle.repair_outcome.agent_failed":"Agentenreparatur fehlgeschlagen","lifecycle.repair_outcome.agent_timed_out":"Zeitlimit der Agentenreparatur überschritten"});
3397
- Object.assign(DASHBOARD_MESSAGES.fr, {"lifecycle.detail_repair_evidence":"Réparations de contrôles de pull request effectuées","lifecycle.detail_repair_iteration":"Itération de réparation {iteration}","lifecycle.repair_outcome.submitted_for_recheck":"Soumis pour nouvelle vérification","lifecycle.repair_outcome.agent_failed":"Réparation par l’agent échouée","lifecycle.repair_outcome.agent_timed_out":"La réparation par l’agent a dépassé le délai"});
3398
- Object.assign(DASHBOARD_MESSAGES.es, {"lifecycle.detail_repair_evidence":"Reparaciones de comprobaciones de solicitud realizadas","lifecycle.detail_repair_iteration":"Iteración de reparación {iteration}","lifecycle.repair_outcome.submitted_for_recheck":"Enviado para nueva comprobación","lifecycle.repair_outcome.agent_failed":"La reparación del agente falló","lifecycle.repair_outcome.agent_timed_out":"La reparación del agente superó el tiempo límite"});
3394
+ Object.assign(DASHBOARD_MESSAGES.en, {"lifecycle.detail_repair_evidence":"Executed pull request check repairs","lifecycle.detail_repair_iteration":"Repair iteration {iteration}","lifecycle.repair_outcome.planned":"Planned","lifecycle.repair_outcome.submitted_for_recheck":"Submitted for recheck","lifecycle.repair_outcome.agent_failed":"Agent repair failed","lifecycle.repair_outcome.agent_timed_out":"Agent repair timed out"});
3395
+ Object.assign(DASHBOARD_MESSAGES.nl, {"lifecycle.detail_repair_evidence":"Uitgevoerd PR-controleherstel","lifecycle.detail_repair_iteration":"Hersteliteratie {iteration}","lifecycle.repair_outcome.planned":"Gepland","lifecycle.repair_outcome.submitted_for_recheck":"Ingediend voor hercontrole","lifecycle.repair_outcome.agent_failed":"Agentherstel mislukt","lifecycle.repair_outcome.agent_timed_out":"Agentherstel heeft de tijdslimiet overschreden"});
3396
+ Object.assign(DASHBOARD_MESSAGES.de, {"lifecycle.detail_repair_evidence":"Durchgeführte PR-Prüfreparaturen","lifecycle.detail_repair_iteration":"Reparaturiteration {iteration}","lifecycle.repair_outcome.planned":"Geplant","lifecycle.repair_outcome.submitted_for_recheck":"Zur erneuten Prüfung eingereicht","lifecycle.repair_outcome.agent_failed":"Agentenreparatur fehlgeschlagen","lifecycle.repair_outcome.agent_timed_out":"Zeitlimit der Agentenreparatur überschritten"});
3397
+ Object.assign(DASHBOARD_MESSAGES.fr, {"lifecycle.detail_repair_evidence":"Réparations de contrôles de pull request effectuées","lifecycle.detail_repair_iteration":"Itération de réparation {iteration}","lifecycle.repair_outcome.planned":"Planifiée","lifecycle.repair_outcome.submitted_for_recheck":"Soumis pour nouvelle vérification","lifecycle.repair_outcome.agent_failed":"Réparation par l’agent échouée","lifecycle.repair_outcome.agent_timed_out":"La réparation par l’agent a dépassé le délai"});
3398
+ Object.assign(DASHBOARD_MESSAGES.es, {"lifecycle.detail_repair_evidence":"Reparaciones de comprobaciones de solicitud realizadas","lifecycle.detail_repair_iteration":"Iteración de reparación {iteration}","lifecycle.repair_outcome.planned":"Planificada","lifecycle.repair_outcome.submitted_for_recheck":"Enviado para nueva comprobación","lifecycle.repair_outcome.agent_failed":"La reparación del agente falló","lifecycle.repair_outcome.agent_timed_out":"La reparación del agente superó el tiempo límite"});
3399
3399
  Object.assign(DASHBOARD_MESSAGES.en, {"lifecycle.step.autonomous_quality_repair":"Autonomous quality repair","lifecycle.detail_autonomous_quality_repair_evidence":"Completed quality repair iterations"});
3400
3400
  Object.assign(DASHBOARD_MESSAGES.nl, {"lifecycle.step.autonomous_quality_repair":"Autonoom kwaliteitsherstel","lifecycle.detail_autonomous_quality_repair_evidence":"Uitgevoerde kwaliteitshersteliteraties"});
3401
3401
  Object.assign(DASHBOARD_MESSAGES.de, {"lifecycle.step.autonomous_quality_repair":"Autonome Qualitätsreparatur","lifecycle.detail_autonomous_quality_repair_evidence":"Durchgeführte Qualitätsreparaturiterationen"});
@@ -3722,11 +3722,11 @@ Object.assign(DASHBOARD_MESSAGES.de, {"configuration.provider_readiness_interval
3722
3722
  Object.assign(DASHBOARD_MESSAGES.fr, {"configuration.provider_readiness_interval":"Intervalle de vérification de disponibilité des fournisseurs","configuration.provider_readiness_interval_help":"À quelle fréquence ce tableau de bord ouvert revérifie la disponibilité locale de Codex et GitHub sans démarrer d’exécution.","configuration.minute_1":"1 minute","configuration.minutes_5":"5 minutes","configuration.minutes_10":"10 minutes"});
3723
3723
  Object.assign(DASHBOARD_MESSAGES.es, {"configuration.provider_readiness_interval":"Intervalo de comprobación de disponibilidad de proveedores","configuration.provider_readiness_interval_help":"Con qué frecuencia este panel abierto vuelve a comprobar la disponibilidad local de Codex y GitHub sin iniciar una ejecución.","configuration.minute_1":"1 minuto","configuration.minutes_5":"5 minutos","configuration.minutes_10":"10 minutos"});
3724
3724
 
3725
- Object.assign(DASHBOARD_MESSAGES.en, {"detail.pull_requests":"Pull requests","detail.implementation_pull_request":"Implementation pull request","detail.finalization_pull_request":"Finalization pull request"});
3726
- Object.assign(DASHBOARD_MESSAGES.nl, {"detail.pull_requests":"Pull requests","detail.implementation_pull_request":"Implementatie-pullrequest","detail.finalization_pull_request":"Finalisatie-pullrequest"});
3727
- Object.assign(DASHBOARD_MESSAGES.de, {"detail.pull_requests":"Pull Requests","detail.implementation_pull_request":"Implementierungs-Pull-Request","detail.finalization_pull_request":"Finalisierungs-Pull-Request"});
3728
- Object.assign(DASHBOARD_MESSAGES.fr, {"detail.pull_requests":"Pull requests","detail.implementation_pull_request":"Pull request d’implémentation","detail.finalization_pull_request":"Pull request de finalisation"});
3729
- Object.assign(DASHBOARD_MESSAGES.es, {"detail.pull_requests":"Solicitudes de extracción","detail.implementation_pull_request":"Solicitud de extracción de implementación","detail.finalization_pull_request":"Solicitud de extracción de finalización"});
3725
+ Object.assign(DASHBOARD_MESSAGES.en, {"detail.pull_requests":"Pull requests","detail.bound_pull_request":"Bound pull request","detail.implementation_pull_request":"Implementation pull request","detail.finalization_pull_request":"Finalization pull request","detail.candidate_sha":"Candidate SHA"});
3726
+ Object.assign(DASHBOARD_MESSAGES.nl, {"detail.pull_requests":"Pull requests","detail.bound_pull_request":"Gebonden pull request","detail.implementation_pull_request":"Implementatie-pullrequest","detail.finalization_pull_request":"Finalisatie-pullrequest","detail.candidate_sha":"Kandidaat-SHA"});
3727
+ Object.assign(DASHBOARD_MESSAGES.de, {"detail.pull_requests":"Pull Requests","detail.bound_pull_request":"Gebundener Pull Request","detail.implementation_pull_request":"Implementierungs-Pull-Request","detail.finalization_pull_request":"Finalisierungs-Pull-Request","detail.candidate_sha":"Kandidaten-SHA"});
3728
+ Object.assign(DASHBOARD_MESSAGES.fr, {"detail.pull_requests":"Pull requests","detail.bound_pull_request":"Pull request lié","detail.implementation_pull_request":"Pull request d’implémentation","detail.finalization_pull_request":"Pull request de finalisation","detail.candidate_sha":"SHA candidat"});
3729
+ Object.assign(DASHBOARD_MESSAGES.es, {"detail.pull_requests":"Solicitudes de extracción","detail.bound_pull_request":"Solicitud de extracción vinculada","detail.implementation_pull_request":"Solicitud de extracción de implementación","detail.finalization_pull_request":"Solicitud de extracción de finalización","detail.candidate_sha":"SHA candidato"});
3730
3730
  Object.assign(DASHBOARD_MESSAGES.en, {"detail.pull_request_commits":"Commits","detail.pull_request_checks":"GitHub checks","detail.pull_request_changed_files":"Files changed"});
3731
3731
  Object.assign(DASHBOARD_MESSAGES.nl, {"detail.pull_request_commits":"Commits","detail.pull_request_checks":"GitHub-controles","detail.pull_request_changed_files":"Gewijzigde bestanden"});
3732
3732
  Object.assign(DASHBOARD_MESSAGES.de, {"detail.pull_request_commits":"Commits","detail.pull_request_checks":"GitHub-Prüfungen","detail.pull_request_changed_files":"Geänderte Dateien"});
@@ -3904,6 +3904,12 @@ Object.assign(DASHBOARD_MESSAGES.es, {
3904
3904
  "ui.execution_status": "Estado de ejecución", "configuration.dashboard_settings": "Configuración de la consola de operaciones", "configuration.dashboard_stream_interval": "Intervalo de actualización de la consola de operaciones", "configuration.inbox_scan_interval": "Intervalo de exploración de entrada File Inbox", "configuration.operator_merge_interval_help": "Con qué frecuencia el Lifecycle Worker comprueba si ha cambiado el estado de una pull request que espera una fusión del operador.", "configuration.timeout_policy_description": "Límites fijos administrados por el host para cada acción del proveedor. Estos límites no se pueden editar en la consola de operaciones.", "dashboard.status_invalid": "El estado de la consola de operaciones no es válido.", "dashboard.status_unavailable": "La consola de operaciones no está disponible.", "header.skip": "Ir al contenido de la consola de operaciones", "logs.dashboard_unavailable": "El registro de la plataforma no está disponible.", "queue.defer_description": "¿Mover {title} fuera de la cola activa? El envío se conserva como entrada File Inbox diferida y no se ejecuta hasta que se devuelva explícitamente.", "queue.defer_failed": "El envío de entrada File Inbox no se pudo diferir de forma segura.", "queue.managed_branch_blocked": "La entrada File Inbox está en pausa porque este espacio de trabajo está en una rama de trabajo. El Execution Host solo puede reclamar trabajo desde main.", "queue.managed_branch_recovery": "La restauración devuelve un espacio de trabajo limpio a main y reinicia el Lifecycle Worker. No cambia ningún elemento de la cola.", "queue.managed_branch_recovery_ready": "Espacio de trabajo restaurado a main; el Lifecycle Worker vuelve a estar activo.", "queue.managed_branch_recovery_title": "Restaurar el espacio de trabajo de entrada File Inbox", "queue.runtime_invocation_blocked": "La entrada File Inbox está esperando porque la CLI local de Codex no puede iniciarse. Repare la instalación administrada de la CLI de Codex antes de volver a intentarlo.", "recovery.details": "¿Reintentar esta ejecución terminal mediante la ruta normal de entrada File Inbox? Engineering Platform vuelve a realizar la comprobación previa actual antes de crear una ejecución de sustitución. La ejecución fallida y sus evidencias no cambian.", "recovery.ready": "La recuperación está en cola; el Lifecycle Worker reclamará la ejecución de sustitución.", "refresh.refreshing": "Actualizando la consola de operaciones…", "status_reconciliation.confirmation": "Se colocará una conciliación Finalization específica en la cola de envíos de Engineering Platform. No cambia ningún comportamiento de producto ni de ejecución.", "ui.codex_cli_update_confirmation": "¿Instalar la actualización disponible de la CLI de Codex en esta máquina? La consola de operaciones verificará después la versión instalada.", "workspace.worktree_switch_confirmation": "¿Cambiar Engineering Platform a {branch} en {path}?\n\nEngineering Platform vuelve a comprobar:\n• no hay ninguna ejecución activa;\n• la cola de entrada File Inbox está vacía;\n• este árbol de trabajo registrado está limpio y exactamente en {branch}.\n\nDespués reinicia la consola de operaciones, el Lifecycle Worker y el Server Relay desde este árbol de trabajo.", "component.dashboard_relay": "Server Relay", "dashboard.health.DASHBOARD_RELAY_ACTIVE": "Server Relay activo", "dashboard.health.DASHBOARD_RELAY_UNAVAILABLE": "Server Relay no disponible", "dashboard.health.watcher": "Entrada File Inbox", "dashboard.health.watcher_state": "Estado del Lifecycle Worker", "state.WATCHER_IDLE": "No hay ejecución activa",
3905
3905
  });
3906
3906
 
3907
+ Object.assign(DASHBOARD_MESSAGES.en, {"component.relay_access":"Relay access","component.tailscale_ipv4":"Tailscale IPv4","component.relay_probe":"Functional relay check","component.relay_reachable":"Reachable","component.relay_unreachable":"Unreachable","component.detail.DASHBOARD_RELAY_TAILSCALE_UNAVAILABLE":"Tailscale address unavailable","component.detail.DASHBOARD_RELAY_ENDPOINT_UNREACHABLE":"Relay endpoint unreachable"});
3908
+ Object.assign(DASHBOARD_MESSAGES.nl, {"component.relay_access":"Relay-toegang","component.tailscale_ipv4":"Tailscale-IPv4","component.relay_probe":"Functionele relaycontrole","component.relay_reachable":"Bereikbaar","component.relay_unreachable":"Niet bereikbaar","component.detail.DASHBOARD_RELAY_TAILSCALE_UNAVAILABLE":"Tailscale-adres niet beschikbaar","component.detail.DASHBOARD_RELAY_ENDPOINT_UNREACHABLE":"Relay-endpoint niet bereikbaar"});
3909
+ Object.assign(DASHBOARD_MESSAGES.de, {"component.relay_access":"Relay-Zugriff","component.tailscale_ipv4":"Tailscale-IPv4","component.relay_probe":"Funktionale Relay-Prüfung","component.relay_reachable":"Erreichbar","component.relay_unreachable":"Nicht erreichbar","component.detail.DASHBOARD_RELAY_TAILSCALE_UNAVAILABLE":"Tailscale-Adresse nicht verfügbar","component.detail.DASHBOARD_RELAY_ENDPOINT_UNREACHABLE":"Relay-Endpunkt nicht erreichbar"});
3910
+ Object.assign(DASHBOARD_MESSAGES.fr, {"component.relay_access":"Accès au relais","component.tailscale_ipv4":"IPv4 Tailscale","component.relay_probe":"Vérification fonctionnelle du relais","component.relay_reachable":"Accessible","component.relay_unreachable":"Inaccessible","component.detail.DASHBOARD_RELAY_TAILSCALE_UNAVAILABLE":"Adresse Tailscale indisponible","component.detail.DASHBOARD_RELAY_ENDPOINT_UNREACHABLE":"Point d’accès du relais inaccessible"});
3911
+ Object.assign(DASHBOARD_MESSAGES.es, {"component.relay_access":"Acceso al relé","component.tailscale_ipv4":"IPv4 de Tailscale","component.relay_probe":"Comprobación funcional del relé","component.relay_reachable":"Accesible","component.relay_unreachable":"No accesible","component.detail.DASHBOARD_RELAY_TAILSCALE_UNAVAILABLE":"Dirección de Tailscale no disponible","component.detail.DASHBOARD_RELAY_ENDPOINT_UNREACHABLE":"Punto de acceso del relé no accesible"});
3912
+
3907
3913
  export class LocalizationContractError extends Error {
3908
3914
  constructor({ locale, key, surface = "Console" }) {
3909
3915
  super(`LOCALIZATION_MISSING locale=${locale} key=${key} surface=${surface}`);
@@ -37,6 +37,7 @@ ROUTE_OWNERSHIP_MATRIX: tuple[ConsoleRoute, ...] = (
37
37
  ConsoleRoute(("GET",), r"/(?:favicon\.ico|apple-touch-icon(?:-precomposed)?\.png)", PLATFORM, "console_shell", "Console icons"),
38
38
  ConsoleRoute(("GET",), r"/assets/[A-Za-z0-9_./-]+", PLATFORM, "console_shell", "Installed Console assets"),
39
39
  ConsoleRoute(("GET",), r"/health", PLATFORM, "platform_components", "Platform Components health", True),
40
+ ConsoleRoute(("GET",), r"/api/health", PLATFORM, "platform_components", "Platform Components health compatibility alias", True),
40
41
  ConsoleRoute(("GET",), r"/api/platform-status", PLATFORM, "platform_components", "Platform Components projection", True),
41
42
  ConsoleRoute(("GET",), r"/api/(?:dashboard-snapshot|status|events)", PLATFORM, "platform_components", "Platform status projection or stream", True),
42
43
  ConsoleRoute(("GET",), rf"/api/components/{PLATFORM_COMPONENT_ROUTE_PATTERN}/details", PLATFORM, "platform_components", "Component detail popout", True),
@@ -39,6 +39,21 @@ def _successful_reviewer_agents(value: object) -> list[dict[str, object]]:
39
39
  return reviewers if all(item.get("status") == "completed" for item in reviewers) else []
40
40
 
41
41
 
42
+ def _run_workspace_preflight(root: Path, run_id: object) -> dict[str, object]:
43
+ """Return workspace-preflight evidence only when it belongs to this run.
44
+
45
+ The dashboard may retain the most recent workspace preflight for technical
46
+ diagnostics, but the active-run card must never borrow a branch result
47
+ from another run.
48
+ """
49
+ if not isinstance(run_id, str) or not run_id:
50
+ return {}
51
+ preflight = latest_workspace_preflight(root)
52
+ if not isinstance(preflight, dict) or preflight.get("run_id") != run_id:
53
+ return {}
54
+ return preflight
55
+
56
+
42
57
  def _active_runner_checkpoint(root: Path) -> tuple[dict[str, object], dict[str, object]] | None:
43
58
  """Return a current checkpoint only while its recorded runner group exists.
44
59
 
@@ -349,6 +364,7 @@ def status(root: Path) -> bytes:
349
364
  try:
350
365
  if live is None:
351
366
  raise ValueError("No canonical live status")
367
+ run_workspace_preflight = _run_workspace_preflight(root, live.get("run_id"))
352
368
  live_liveness = lease_liveness(root, live.get("run_id"))
353
369
  if runner_liveness is not None and runner_liveness.get("state") == "LIVE":
354
370
  live_liveness = {**live_liveness, **runner_liveness}
@@ -392,8 +408,15 @@ def status(root: Path) -> bytes:
392
408
  "predecessor_recovery_action": watcher.get("predecessor_recovery_action"),
393
409
  "execution_mode": live.get("execution_mode"),
394
410
  "target_repository": live.get("target_repository"),
411
+ "github_repository": live.get("github_repository"),
395
412
  "checkout_path": live.get("checkout_path"),
413
+ # This is the observed branch from the preflight owned by the
414
+ # displayed run; active_branch remains the current checkout.
415
+ "target_branch": run_workspace_preflight.get("branch"),
396
416
  "active_branch": live.get("active_branch"),
417
+ "candidate_sha": live.get("candidate_sha"),
418
+ "repair_iteration": live.get("repair_iteration"),
419
+ "workspace_preflight": run_workspace_preflight,
397
420
  # Only a wholly successful specialist review survives its
398
421
  # live phase as historical evidence for this active run. A
399
422
  # partial, failed or stale reviewer projection remains hidden.
@@ -8,17 +8,26 @@ let loopbackAddress = "127.0.0.1"
8
8
  func tailscaleAddress() -> String? {
9
9
  let process = Process()
10
10
  let output = Pipe()
11
+ let reader = output.fileHandleForReading
12
+ let writer = output.fileHandleForWriting
13
+ defer {
14
+ reader.closeFile()
15
+ writer.closeFile()
16
+ }
11
17
  process.executableURL = URL(fileURLWithPath: "/usr/local/bin/tailscale")
12
18
  process.arguments = ["ip", "-4"]
13
19
  process.standardOutput = output
14
20
  do {
15
21
  try process.run()
22
+ // The child owns its duplicated descriptor. Closing the parent's
23
+ // write end prevents retries from retaining one pipe per attempt.
24
+ writer.closeFile()
16
25
  process.waitUntilExit()
17
26
  } catch {
18
27
  return nil
19
28
  }
20
29
  guard process.terminationStatus == 0,
21
- let text = String(data: output.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8),
30
+ let text = String(data: reader.readDataToEndOfFile(), encoding: .utf8),
22
31
  let value = text.split(whereSeparator: \.isNewline).first
23
32
  else { return nil }
24
33
  return String(value)
@@ -113,8 +113,9 @@ def project_output(command: Iterable[str], output: str, exit_code: int) -> Evide
113
113
  class ToolProxyEnvironment:
114
114
  """Temporary PATH proxy. It retains no output after the invocation ends."""
115
115
 
116
- def __init__(self) -> None:
116
+ def __init__(self, *, deny_delivery_mutations: bool = False) -> None:
117
117
  self._temporary: TemporaryDirectory[str] | None = None
118
+ self._deny_delivery_mutations = deny_delivery_mutations
118
119
 
119
120
  def __enter__(self) -> Mapping[str, str]:
120
121
  self._temporary = TemporaryDirectory(prefix="engineering-platform-evidence-")
@@ -149,6 +150,10 @@ class ToolProxyEnvironment:
149
150
  environment["ENGINEERING_PLATFORM_EVIDENCE_ORIGINAL_PATH"] = environment.get("PATH", os.defpath)
150
151
  environment["PATH"] = f"{directory}{os.pathsep}{environment['ENGINEERING_PLATFORM_EVIDENCE_ORIGINAL_PATH']}"
151
152
  environment["ENGINEERING_PLATFORM_CONTEXT_ESCALATION_FILE"] = str(directory / "context-escalations.jsonl")
153
+ if self._deny_delivery_mutations:
154
+ # This is deliberately an invocation-scoped capability boundary,
155
+ # not a prompt convention. The validation adapter opts into it.
156
+ environment["ENGINEERING_PLATFORM_DENY_DELIVERY_MUTATIONS"] = "1"
152
157
  return environment
153
158
 
154
159
  def __exit__(self, *_: object) -> None:
@@ -172,13 +177,20 @@ class ToolProxyEnvironment:
172
177
  def proxy_main(name: str | None = None) -> None:
173
178
  """Run the proxied command and emit only its bounded invocation-local view."""
174
179
  name = name or Path(sys.argv[0]).name
180
+ if os.environ.get("ENGINEERING_PLATFORM_DENY_DELIVERY_MUTATIONS") == "1" and _is_delivery_mutation(name, sys.argv[1:]):
181
+ sys.stderr.write("Read-only assessment tool policy refused a delivery mutation.\n")
182
+ raise SystemExit(126)
175
183
  original_path = os.environ.get("ENGINEERING_PLATFORM_EVIDENCE_ORIGINAL_PATH", os.defpath)
176
184
  executable = shutil.which(name, path=original_path)
177
185
  if executable is None:
178
186
  raise SystemExit(f"Evidence proxy could not resolve {name}.")
179
187
  completed = subprocess.run( # nosec B603
180
188
  (executable, *sys.argv[1:]), text=True, capture_output=True,
181
- env={**os.environ, "PATH": original_path}, check=False,
189
+ # Retain the interposer for descendants. Restoring only the original
190
+ # PATH let a proxied interpreter launch an unmediated `gh` child.
191
+ # The original path remains separately available for this launcher's
192
+ # own executable resolution above, avoiding recursive proxy loops.
193
+ env=os.environ.copy(), check=False,
182
194
  )
183
195
  raw = f"{completed.stdout}{completed.stderr}"
184
196
  if os.environ.get("ENGINEERING_PLATFORM_EVIDENCE_EXPAND") == "1":
@@ -188,6 +200,27 @@ def proxy_main(name: str | None = None) -> None:
188
200
  raise SystemExit(completed.returncode)
189
201
 
190
202
 
203
+ def _is_delivery_mutation(name: str, arguments: Iterable[str]) -> bool:
204
+ """Recognize the small Git/GitHub write surface relevant to assessment.
205
+
206
+ This runs inside the PATH interposer, so it remains effective even if a
207
+ caller accidentally grants an owner-authorized lifecycle flag elsewhere.
208
+ It intentionally permits read-only inspection and CI-status observation.
209
+ """
210
+ args = tuple(arguments)
211
+ if name == "gh":
212
+ if len(args) >= 2 and args[0] == "pr" and args[1] in {"create", "edit", "ready", "merge"}:
213
+ return True
214
+ return bool(args and args[0] == "api" and any(value in {"PATCH", "POST", "PUT", "DELETE"} for value in args))
215
+ if name != "git" or not args:
216
+ return False
217
+ # Git accepts global options before the command (`git -C repo push`).
218
+ index = 0
219
+ while index < len(args) and args[index].startswith("-"):
220
+ index += 2 if args[index] in {"-C", "-c", "--git-dir", "--work-tree", "--namespace"} else 1
221
+ return index < len(args) and args[index] in {"commit", "push", "switch", "checkout", "branch", "merge", "reset", "rebase"}
222
+
223
+
191
224
  def context_escalation_main(_: str | None = None) -> None:
192
225
  """Validate and record one bounded historical-context escalation."""
193
226
  from .provider_context_scope import ContextEscalationReason, ContextEscalationRequest, HistoryBoundaryKind
@@ -50,6 +50,27 @@ _UNITTEST_FAILURE = re.compile(r"^(?:FAIL|ERROR): [^(]+ \(([^)]+)\)$", re.MULTIL
50
50
  _UNITTEST_COUNTS = re.compile(r"FAILED \((?P<details>[^)]*)\)")
51
51
  _UNITTEST_COUNT = re.compile(r"\b(?P<name>failures|errors)=(?P<count>\d+)\b")
52
52
  _TURN_ABORTED = re.compile(r'"type"\s*:\s*"turn_aborted"[^\n]*"reason"\s*:\s*"interrupted"', re.IGNORECASE)
53
+ _ASSESSMENT_STATES = frozenset({"COMPLETE", "WAITING", "FAILED", "BLOCKED"})
54
+
55
+
56
+ def _parse_validation_assessment(raw: object) -> AgentResult:
57
+ """Validate the small read-only contract before projecting AgentResult.
58
+
59
+ JSON Schema constrains cooperative providers, but terminal lifecycle code
60
+ must fail closed for malformed, old, or adapter-produced output too.
61
+ """
62
+ if not isinstance(raw, dict) or set(raw) != {"terminal_state", "diagnostic", "pull_request"}:
63
+ raise TypeError("validation assessment fields are invalid")
64
+ state, diagnostic, pull_request = raw["terminal_state"], raw["diagnostic"], raw["pull_request"]
65
+ if not isinstance(state, str) or state not in _ASSESSMENT_STATES:
66
+ raise TypeError("validation assessment terminal state is invalid")
67
+ if not isinstance(diagnostic, str) or len(diagnostic) > 500:
68
+ raise TypeError("validation assessment diagnostic is invalid")
69
+ if pull_request is not None and (
70
+ not isinstance(pull_request, int) or isinstance(pull_request, bool) or pull_request < 1
71
+ ):
72
+ raise TypeError("validation assessment pull request is invalid")
73
+ return AgentResult(state, pull_request=pull_request, diagnostic=diagnostic)
53
74
 
54
75
 
55
76
  def provider_turn_interruption(stdout: str, stderr: str) -> str | None:
@@ -520,6 +541,7 @@ class CodexCliClient:
520
541
  self.last_context_escalations = ()
521
542
  self.last_execution_seconds = None
522
543
  self.last_runtime_metadata = self._runtime_metadata()
544
+ assessment_contract = getattr(self, "_validation_contract", False)
523
545
  schema = {
524
546
  "type": "object",
525
547
  "additionalProperties": False,
@@ -572,6 +594,19 @@ class CodexCliClient:
572
594
  },
573
595
  },
574
596
  }
597
+ if assessment_contract:
598
+ # A local-validation turn is an assessment of host receipts, not
599
+ # a delivery turn. Keep the legacy PR echo only as a bounded
600
+ # compatibility signal; lifecycle fields are projected by EP.
601
+ schema = {
602
+ "type": "object", "additionalProperties": False,
603
+ "required": ["terminal_state", "diagnostic", "pull_request"],
604
+ "properties": {
605
+ "terminal_state": {"type": "string", "enum": ["COMPLETE", "WAITING", "FAILED", "BLOCKED"]},
606
+ "diagnostic": {"type": "string", "maxLength": 500},
607
+ "pull_request": {"type": ["integer", "null"]},
608
+ },
609
+ }
575
610
  with tempfile.NamedTemporaryFile(
576
611
  "w", encoding="utf-8", suffix=".json", delete=False
577
612
  ) as handle:
@@ -588,11 +623,19 @@ class CodexCliClient:
588
623
  str(root),
589
624
  "--json",
590
625
  ]
626
+ if assessment_contract:
627
+ # These supported CLI switches keep account authentication
628
+ # while excluding user-configured MCP/app/tool surfaces and
629
+ # user- or project-scoped execpolicy rules from this
630
+ # receipt-only role. In particular, an inherited allow rule
631
+ # must not move an assessment command outside the read-only
632
+ # sandbox selected by the host.
633
+ command.extend(("--ignore-user-config", "--ignore-rules", "--ephemeral"))
591
634
  for extra_root in extra_roots:
592
635
  command.extend(("--add-dir", str(extra_root)))
593
636
  command.extend(("--output-schema", str(schema_path), prompt))
594
637
  started = time.monotonic()
595
- proxy = ToolProxyEnvironment()
638
+ proxy = ToolProxyEnvironment(deny_delivery_mutations=assessment_contract)
596
639
  with proxy as environment:
597
640
  completed = self._run_invocation(tuple(command), root, environment)
598
641
  self.last_context_escalations = proxy.context_escalations()
@@ -623,7 +666,10 @@ class CodexCliClient:
623
666
  )
624
667
  try:
625
668
  raw = json.loads(_codex_final_message(completed.stdout))
626
- result = AgentResult(**raw)
669
+ if assessment_contract:
670
+ result = _parse_validation_assessment(raw)
671
+ else:
672
+ result = AgentResult(**raw)
627
673
  if not isinstance(result.validation_evidence, (list, tuple)) or not isinstance(result.quality_evidence, (list, tuple)):
628
674
  raise TypeError("execution evidence must be a list")
629
675
  if result.validation_disposition not in {"product_failure", "environmental_instability"}:
@@ -649,7 +695,7 @@ class CodexCliClient:
649
695
  if result.diagnostic is not None:
650
696
  result = replace(result, diagnostic=redact_diagnostic(result.diagnostic))
651
697
  return result
652
- except (IndexError, json.JSONDecodeError, TypeError) as error:
698
+ except (IndexError, KeyError, json.JSONDecodeError, TypeError) as error:
653
699
  interruption = provider_turn_interruption(completed.stdout, completed.stderr)
654
700
  raise CodexInvocationError(
655
701
  "Provider turn interrupted before returning the required structured terminal result."
@@ -671,10 +717,12 @@ class CodexCliClient:
671
717
  if hasattr(self, "_sandbox_override"):
672
718
  raise RunnerError("nested validation sandbox override is invalid")
673
719
  self._sandbox_override = "read-only"
720
+ self._validation_contract = True
674
721
  try:
675
722
  return self.invoke(root, prompt)
676
723
  finally:
677
724
  del self._sandbox_override
725
+ del self._validation_contract
678
726
 
679
727
  def _run_invocation(
680
728
  self, command: tuple[str, ...], root: Path, environment: Mapping[str, str] | None = None