engineering-platform 2.3.0__tar.gz → 2.3.1__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 (138) hide show
  1. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/PKG-INFO +1 -1
  2. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/pyproject.toml +1 -1
  3. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json +10 -3
  4. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json +4 -4
  5. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/assets/dashboard.css +5 -1
  6. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/assets/dashboard.js +102 -20
  7. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/assets/dashboard_locales.mjs +10 -0
  8. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/component_logging.py +21 -3
  9. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/platform_version.py +1 -1
  10. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/provider_readiness.py +7 -6
  11. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/providers.py +2 -2
  12. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/server.py +41 -4
  13. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/templates/workspace-config.json +18 -4
  14. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform.egg-info/PKG-INFO +1 -1
  15. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/README.md +0 -0
  16. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/setup.cfg +0 -0
  17. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/__init__.py +0 -0
  18. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/__main__.py +0 -0
  19. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/agent_state.py +0 -0
  20. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/agent_trust.py +0 -0
  21. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/assets/dashboard_status_store.mjs +0 -0
  22. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/assets/operations-console/apple-touch-icon-dark.png +0 -0
  23. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/assets/operations-console/apple-touch-icon-light.png +0 -0
  24. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/assets/operations-console/icon-dark.png +0 -0
  25. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/assets/operations-console/icon-light.png +0 -0
  26. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/assets/operations-console/icon-transparent.png +0 -0
  27. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/assets/operations-console/manifest.webmanifest +0 -0
  28. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/capability_preflight.py +0 -0
  29. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/capability_review.py +0 -0
  30. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/central_data_transfer.py +0 -0
  31. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/central_database.py +0 -0
  32. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/central_store_migration.py +0 -0
  33. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/codex_capacity.py +0 -0
  34. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/codex_chat.py +0 -0
  35. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/codex_observability.py +0 -0
  36. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/component_lock.py +0 -0
  37. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/console_presentation.py +0 -0
  38. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/console_route_ownership.py +0 -0
  39. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/contracts/__init__.py +0 -0
  40. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/contracts/ep_consumer.py +0 -0
  41. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/contracts/models.py +0 -0
  42. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/contracts/projection.py +0 -0
  43. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/dashboard_browser_validation.py +0 -0
  44. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/dashboard_state.py +0 -0
  45. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/dashboard_supervisor.swift +0 -0
  46. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/dashboard_translation.py +0 -0
  47. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/dependabot_producer.py +0 -0
  48. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/drift_diagnostics.py +0 -0
  49. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/emergency_recovery.py +0 -0
  50. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/engineering_memory.py +0 -0
  51. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/ep_consumer_credentials.py +0 -0
  52. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/evidence_projection.py +0 -0
  53. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_activity.py +0 -0
  54. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_context.py +0 -0
  55. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_errors.py +0 -0
  56. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_evidence.py +0 -0
  57. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_executor.py +0 -0
  58. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_finalization.py +0 -0
  59. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_host.py +0 -0
  60. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_lease.py +0 -0
  61. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_lifecycle.py +0 -0
  62. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_models.py +0 -0
  63. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_readiness.py +0 -0
  64. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_reporting.py +0 -0
  65. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_repository.py +0 -0
  66. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_timeout_policy.py +0 -0
  67. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_timing.py +0 -0
  68. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/execution_transaction.py +0 -0
  69. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/external_producer_binding.py +0 -0
  70. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/file_inbox.py +0 -0
  71. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/forensic_attribution.py +0 -0
  72. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/forensic_attribution_v2.py +0 -0
  73. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/forensic_delta.py +0 -0
  74. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/golden_scenario.py +0 -0
  75. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/historical_dashboard_configuration.py +0 -0
  76. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/host_admin.py +0 -0
  77. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/host_preflight.py +0 -0
  78. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/installation_relocation.py +0 -0
  79. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/investigation_ledger.py +0 -0
  80. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/legacy_inbox_migration.py +0 -0
  81. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/lifecycle_worker.py +0 -0
  82. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/live_status.py +0 -0
  83. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/local_api.py +0 -0
  84. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/local_api_keychain.py +0 -0
  85. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/local_repository_binding.py +0 -0
  86. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/managed_autonomy.py +0 -0
  87. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/managed_codex_runtime.py +0 -0
  88. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/parity_context.py +0 -0
  89. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/parity_lifecycle_dispatcher.py +0 -0
  90. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/platform_admin.py +0 -0
  91. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/platform_api.py +0 -0
  92. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/platform_bootstrap.py +0 -0
  93. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/platform_components.py +0 -0
  94. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/pr_check_repair.py +0 -0
  95. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/pr_evidence_backfill.py +0 -0
  96. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/producer.py +0 -0
  97. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/project_agent.py +0 -0
  98. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/project_agent_service.py +0 -0
  99. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/project_topology.py +0 -0
  100. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/prompt_history.py +0 -0
  101. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/provider_context.py +0 -0
  102. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/provider_context_benchmark.py +0 -0
  103. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/provider_context_scope.py +0 -0
  104. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/provider_interruption.py +0 -0
  105. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/provider_process_identity.py +0 -0
  106. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/provider_recovery.py +0 -0
  107. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/provider_usage.py +0 -0
  108. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/qualification.py +0 -0
  109. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/qualification_runtime.py +0 -0
  110. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/recommendation_handoff.py +0 -0
  111. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/report_analysis.py +0 -0
  112. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/repository_attachment.py +0 -0
  113. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/repository_handoff.py +0 -0
  114. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/resources.py +0 -0
  115. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/reviewer_evidence.py +0 -0
  116. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/schemas/producer-readback-v1.2.schema.json +0 -0
  117. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/schemas/producer-readback-v1.schema.json +0 -0
  118. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/schemas/repository-attachment.schema.json +0 -0
  119. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/server_console_services.py +0 -0
  120. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/server_relay.py +0 -0
  121. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/server_service.py +0 -0
  122. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/status_model.py +0 -0
  123. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/status_reconciliation.py +0 -0
  124. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/storage.py +0 -0
  125. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/submission_cli.py +0 -0
  126. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/submission_intake.py +0 -0
  127. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/submission_service.py +0 -0
  128. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/telemetry.py +0 -0
  129. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/validation_identity.py +0 -0
  130. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/validation_profile.py +0 -0
  131. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/version_preparation_delivery.py +0 -0
  132. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/workspace_preflight.py +0 -0
  133. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/worktree_provenance.py +0 -0
  134. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform/worktree_tooling.py +0 -0
  135. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform.egg-info/SOURCES.txt +0 -0
  136. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform.egg-info/dependency_links.txt +0 -0
  137. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform.egg-info/entry_points.txt +0 -0
  138. {engineering_platform-2.3.0 → engineering_platform-2.3.1}/src/engineering_platform.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-platform
3
- Version: 2.3.0
3
+ Version: 2.3.1
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.0"
7
+ version = "2.3.1"
8
8
  description = "Local-first Engineering Platform execution operations runtime"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -3,7 +3,7 @@
3
3
  "platform": {
4
4
  "id": "engineering-platform",
5
5
  "name": "Engineering Platform",
6
- "version": "2.3.0",
6
+ "version": "2.3.1",
7
7
  "generation": 2,
8
8
  "documentation_namespace": "engineering-platform",
9
9
  "capability_registry_version": 1
@@ -11,8 +11,15 @@
11
11
  "workspace": {
12
12
  "id": "engineering-platform",
13
13
  "name": "Engineering Platform",
14
- "repository": {"provider": "github", "owner": "pcvantol", "name": "engineering-platform", "default_branch": "main"},
15
- "branding": {"dashboard_title": "EP Operations"},
14
+ "repository": {
15
+ "provider": "github",
16
+ "owner": "pcvantol",
17
+ "name": "engineering-platform",
18
+ "default_branch": "main"
19
+ },
20
+ "branding": {
21
+ "dashboard_title": "EP Operations"
22
+ },
16
23
  "workspace_authorization": {
17
24
  "allowed_roots": [],
18
25
  "allowed_repositories": [],
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "bootstrap_contract": "2026.12",
3
3
  "checkpoint_format": 1,
4
- "dashboard_version": "2.3.0",
4
+ "dashboard_version": "2.3.1",
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.0",
9
+ "platform_version": "2.3.1",
10
10
  "report_format": 2,
11
- "runner_version": "2.3.0",
11
+ "runner_version": "2.3.1",
12
12
  "status_model": 1,
13
13
  "storage_schema": 41,
14
- "watcher_version": "2.3.0"
14
+ "watcher_version": "2.3.1"
15
15
  }
@@ -1298,7 +1298,11 @@ html[data-theme="light"] #componentLogControls :is(input[type="date"],input[type
1298
1298
  area. Do not add a later global bubble scroller: it would force short
1299
1299
  messages to reserve scrollbar chrome and defeat the 2/3 area limit. */
1300
1300
  @media (max-width:620px){.dashboard-status-banner{position:static}}
1301
- .platform-attention{background:#5b4316;border-bottom:1px solid #f4c34f;color:#fff4d6;margin:0 0 18px}.platform-attention[hidden]{display:none}.platform-attention>summary{align-items:center;cursor:pointer;display:flex;gap:9px;list-style:none;padding:10px 16px}.platform-attention>summary::-webkit-details-marker{display:none}.platform-attention>summary::after{content:"▸";margin-left:auto}.platform-attention[open]>summary::after{content:"▾"}.platform-attention>summary strong{color:#ffd768}.platform-attention>summary span{background:#f4c34f;border-radius:999px;color:#2a2415;font-size:12px;font-weight:800;min-width:1.4rem;padding:2px 5px;text-align:center}.platform-attention__items{display:grid;gap:8px;padding:0 12px 12px}.platform-attention__items .dashboard-status-banner{margin:0;position:static}.platform-attention__items .dashboard-status-banner--provider-readiness{align-items:flex-start}.platform-attention__items .provider-readiness-banner__action{align-self:center}
1301
+ .platform-attention{background:#5b4316;border-bottom:1px solid #f4c34f;color:#fff4d6;margin:0 0 18px}.platform-attention[hidden]{display:none}.platform-attention>summary{align-items:center;cursor:pointer;display:flex;gap:9px;list-style:none;padding:10px 16px}.platform-attention>summary::-webkit-details-marker{display:none}.platform-attention>summary::after{content:"▸";font-size:24px;font-weight:700;line-height:1;margin-left:auto}.platform-attention[open]>summary::after{content:"▾"}.platform-attention>summary strong{color:#ffd768}.platform-attention>summary span{background:#f4c34f;border-radius:999px;color:#2a2415;font-size:12px;font-weight:800;min-width:1.4rem;padding:2px 5px;text-align:center}.platform-attention__items{display:grid;gap:8px;padding:0 12px 12px}.platform-attention__items .dashboard-status-banner{margin:0;position:static}.platform-attention__items .dashboard-status-banner--provider-readiness{align-items:flex-start}.platform-attention__items .provider-readiness-banner__action{align-self:center}
1302
+
1303
+ /* An expanded provider failure is its own red banner, not content inside an
1304
+ amber frame. Keep the amber summary, but leave the failure card unframed. */
1305
+ .platform-attention[open]{background:transparent;border:0}.platform-attention[open]>summary{background:#5b4316;border-bottom:1px solid #f4c34f}.platform-attention[open] .platform-attention__items{padding:8px 0 0}
1302
1306
 
1303
1307
  /* Buttons are controls, not text links: every visible action gets a modest
1304
1308
  fill derived from its own ink/border colour. Semantic local-file links and
@@ -3522,6 +3522,21 @@ function healthComponentLabel(component) {
3522
3522
  );
3523
3523
  return definition?.name_key ? t(definition.name_key) : component;
3524
3524
  }
3525
+ function componentLogDetails(entry) {
3526
+ const details = String(entry?.details || ""),
3527
+ component = typeof entry?.target_component === "string" && entry.target_component
3528
+ ? entry.target_component
3529
+ : entry?.component,
3530
+ version = latestPlatformHealth?.components?.[component]?.version;
3531
+ // Old records did not persist a component version. The display therefore
3532
+ // identifies the observed current version, without rewriting historical
3533
+ // evidence or inventing one when health has no version.
3534
+ if (
3535
+ typeof version !== "string" || !version ||
3536
+ /(?:^| · )(?:target_)?component_version:/.test(details)
3537
+ ) return details || "—";
3538
+ return (details ? details + " · " : "") + "component_version: " + version;
3539
+ }
3525
3540
  const LEGACY_TRANSPORT_STATUS_CODES = Object.freeze({
3526
3541
  HEALTHY: "HTTP_INGRESS_HEALTHY", DOWN: "HTTP_INGRESS_DOWN", AVAILABLE: "CLI_INGRESS_AVAILABLE",
3527
3542
  DEGRADED: "FILE_INGRESS_DEGRADED", RUNNING: "FILE_INGRESS_RUNNING", NOT_READY: "FILE_INGRESS_NOT_READY", STOPPED: "FILE_INGRESS_STOPPED",
@@ -4698,7 +4713,7 @@ function componentLogText(entries) {
4698
4713
  entry.level,
4699
4714
  entry.event,
4700
4715
  entry.runId || "—",
4701
- entry.details || "—",
4716
+ componentLogDetails(entry),
4702
4717
  JSON.stringify(entry),
4703
4718
  ].join("\t"));
4704
4719
  return [header, ...rows].join("\n");
@@ -4886,7 +4901,7 @@ function renderComponentLogs() {
4886
4901
  ],
4887
4902
  ["", logEventLabel(entry.event)],
4888
4903
  ["", entry.runId || "—"],
4889
- ["", entry.details || "—"],
4904
+ ["", componentLogDetails(entry)],
4890
4905
  ]) {
4891
4906
  const cell = document.createElement("td");
4892
4907
  cell.className = name;
@@ -6274,6 +6289,32 @@ syncStickyHeaderOffset();
6274
6289
  syncFooterOffset();
6275
6290
  const providerReadinessActions = new Map();
6276
6291
  let providerInteractiveRepairInProgress = false;
6292
+ const PROVIDER_READINESS_RECOVERY_MAX_ATTEMPTS = 3,
6293
+ PROVIDER_READINESS_RECOVERY_DELAY_MS = 2_000;
6294
+ let providerReadinessRecoveryAttempts = 0, providerReadinessRecoveryTimer = null;
6295
+ function resetProviderReadinessRecovery() {
6296
+ providerReadinessRecoveryAttempts = 0;
6297
+ if (providerReadinessRecoveryTimer !== null)
6298
+ window.clearTimeout(providerReadinessRecoveryTimer);
6299
+ providerReadinessRecoveryTimer = null;
6300
+ }
6301
+ function scheduleProviderReadinessRecovery(providers) {
6302
+ const hasTransientFailure = PROVIDER_READINESS_KEYS.some(
6303
+ (key) => providerReadinessState(providers, key) === "CHECK_FAILED",
6304
+ );
6305
+ if (!hasTransientFailure) return resetProviderReadinessRecovery();
6306
+ if (providerReadinessRecoveryAttempts >= PROVIDER_READINESS_RECOVERY_MAX_ATTEMPTS) {
6307
+ providerReadinessRecoveryTimer = null;
6308
+ return;
6309
+ }
6310
+ if (providerReadinessRecoveryTimer !== null)
6311
+ window.clearTimeout(providerReadinessRecoveryTimer);
6312
+ providerReadinessRecoveryAttempts += 1;
6313
+ providerReadinessRecoveryTimer = window.setTimeout(
6314
+ () => void refreshProviderLoginStatus(),
6315
+ providerReadinessRecoveryAttempts * PROVIDER_READINESS_RECOVERY_DELAY_MS,
6316
+ );
6317
+ }
6277
6318
  function providerDisplayName(provider) {
6278
6319
  return provider === "CODEX" || provider === "codex" ? "Codex" : "GitHub";
6279
6320
  }
@@ -6362,14 +6403,17 @@ async function refreshProviderLoginStatus() {
6362
6403
  fetch("/api/provider-login-status", probeOptions),
6363
6404
  fetch("/api/execution-runtime-status", probeOptions),
6364
6405
  ]);
6406
+ let providers = CHECK_FAILED_PROVIDERS;
6365
6407
  try {
6366
6408
  if (providerResult.status !== "fulfilled") throw Error();
6367
6409
  const response = providerResult.value, payload = await response.json();
6368
6410
  if (!response.ok || !payload || typeof payload.providers !== "object") throw Error();
6369
- renderProviderLoginStatus(block, payload.providers);
6411
+ providers = payload.providers;
6412
+ renderProviderLoginStatus(block, providers);
6370
6413
  } catch {
6371
- renderProviderLoginStatus(block, CHECK_FAILED_PROVIDERS);
6414
+ renderProviderLoginStatus(block, providers);
6372
6415
  }
6416
+ scheduleProviderReadinessRecovery(providers);
6373
6417
  try {
6374
6418
  if (runtimeResult.status !== "fulfilled") throw Error();
6375
6419
  const response = runtimeResult.value, runtime = await response.json();
@@ -7126,22 +7170,34 @@ renderPlatformHealth = (payload) => {
7126
7170
  components ? { ...payload, components: components } : payload,
7127
7171
  );
7128
7172
  };
7129
- async function confirmComponentRestart(component) {
7173
+ function componentUptimeSeconds(payload, component) {
7174
+ const uptime = Number(payload?.components?.[component]?.uptime_seconds);
7175
+ return Number.isFinite(uptime) && uptime >= 0 ? uptime : null;
7176
+ }
7177
+ async function confirmComponentRestart(component, previousUptimeSeconds = null) {
7130
7178
  for (let attempt = 0; attempt < 5; attempt++) {
7131
7179
  await new Promise((resolve) => setTimeout(resolve, 1250));
7132
7180
  try {
7133
7181
  const response = await fetch("/health", { cache: "no-store" }),
7134
7182
  payload = await response.json();
7135
7183
  if (response.ok && payload?.components?.[component]?.healthy) {
7136
- restartingPlatformComponents.delete(component);
7137
- renderPlatformHealth(payload);
7138
- $("componentModalStatus").textContent = t("ui.component_restart_available");
7139
- return;
7184
+ const currentUptimeSeconds = componentUptimeSeconds(payload, component),
7185
+ restartConfirmed = previousUptimeSeconds === null ||
7186
+ (currentUptimeSeconds !== null && currentUptimeSeconds < previousUptimeSeconds);
7187
+ if (restartConfirmed) {
7188
+ restartingPlatformComponents.delete(component);
7189
+ renderPlatformHealth(payload);
7190
+ $("componentModalStatus").textContent = t("ui.component_restart_available");
7191
+ return true;
7192
+ }
7140
7193
  }
7141
7194
  renderPlatformHealth(payload);
7142
7195
  } catch {}
7143
7196
  }
7144
- $("componentModalStatus").textContent = t("ui.component_restart_waiting");
7197
+ $("componentModalStatus").textContent = previousUptimeSeconds === null
7198
+ ? t("ui.component_restart_unconfirmed")
7199
+ : t("ui.component_restart_waiting");
7200
+ return false;
7145
7201
  }
7146
7202
  const formatComponentUptimeForMeasuredValues = formatComponentUptime;
7147
7203
  formatComponentUptime = (value) => {
@@ -8470,7 +8526,13 @@ async function restartPlatformComponent(button) {
8470
8526
  t("component.restart_title"),
8471
8527
  );
8472
8528
  if (!confirmed) return;
8529
+ const previousUptimeSeconds = componentUptimeSeconds(
8530
+ latestPlatformHealthPayload,
8531
+ component,
8532
+ );
8473
8533
  button.disabled = true;
8534
+ restartingPlatformComponents.add(component);
8535
+ $("componentModalStatus").textContent = t("ui.component_restart_started");
8474
8536
  try {
8475
8537
  const response = await fetch(
8476
8538
  "/api/components/" + encodeURIComponent(component) + "/restart",
@@ -8481,7 +8543,11 @@ async function restartPlatformComponent(button) {
8481
8543
  },
8482
8544
  ),
8483
8545
  payload = await response.json();
8484
- if (!response.ok) throw Error(t("ui.component_restart_failed"));
8546
+ if (!response.ok) {
8547
+ restartingPlatformComponents.delete(component);
8548
+ $("componentModalStatus").textContent = t("ui.component_restart_failed");
8549
+ return;
8550
+ }
8485
8551
  if (component === "dashboard") {
8486
8552
  $("componentModalStatus").textContent =
8487
8553
  t("ui.component_restart_started");
@@ -8489,9 +8555,12 @@ async function restartPlatformComponent(button) {
8489
8555
  window.setTimeout(() => window.location.reload(), 750);
8490
8556
  return;
8491
8557
  }
8492
- $("componentModalStatus").textContent = t("ui.component_restart_started");
8558
+ void confirmComponentRestart(component, previousUptimeSeconds);
8493
8559
  } catch {
8494
- $("componentModalStatus").textContent = t("ui.component_restart_failed");
8560
+ // Restarting the relay can close this connection before its response is
8561
+ // delivered. Never mistake that expected hand-off for proof of failure;
8562
+ // require a fresh health observation instead.
8563
+ void confirmComponentRestart(component, previousUptimeSeconds);
8495
8564
  } finally {
8496
8565
  button.disabled = false;
8497
8566
  }
@@ -8668,12 +8737,25 @@ document.addEventListener("DOMContentLoaded", () => {
8668
8737
  }).observe(status, { childList: true, characterData: true, subtree: true });
8669
8738
  });
8670
8739
 
8740
+ const RELOCATION_ERROR_CODES = new Set([
8741
+ "LOCATION_REQUIRED", "LOCATION_NOT_WRITABLE", "PLATFORM_DATA_DESTINATION_INVALID",
8742
+ "PLATFORM_DATA_DESTINATION_FILESYSTEM_UNAVAILABLE", "PLATFORM_DATA_DESTINATION_FILESYSTEM_UNSUPPORTED",
8743
+ "PLATFORM_DATA_COPY_VERIFICATION_FAILED", "PLATFORM_DATA_DESTINATION_NOT_PREPARED",
8744
+ "PLATFORM_DATA_DESTINATION_NOT_EMPTY", "PLATFORM_DATA_UNAVAILABLE", "PLATFORM_DATA_DESTINATION_EXISTS",
8745
+ "RELOCATION_KIND_RETIRED", "RELOCATION_ALREADY_PENDING", "RELOCATION_REQUEST_INVALID",
8746
+ "PLATFORM_DATA_RELOCATION_BLOCKED",
8747
+ ]);
8748
+ function relocationErrorText(code) {
8749
+ return t(`configuration.relocation_error.${RELOCATION_ERROR_CODES.has(code) ? code : "UNKNOWN"}`);
8750
+ }
8671
8751
  document.addEventListener("DOMContentLoaded", () => {
8672
- const status = $("centralDatabaseRelocateStatus");
8673
- if (!status) return;
8674
- new MutationObserver(() => {
8675
- if (status.textContent?.trim() === "PLATFORM_DATA_DESTINATION_FILESYSTEM_UNSUPPORTED") {
8676
- status.textContent = t("configuration.relocation_filesystem_unsupported");
8677
- }
8678
- }).observe(status, { childList: true, characterData: true, subtree: true });
8752
+ for (const id of ["centralDatabaseRelocateStatus", "fileInboxRelocateStatus"]) {
8753
+ const status = $(id);
8754
+ if (!status) continue;
8755
+ new MutationObserver(() => {
8756
+ const code = status.textContent?.trim() || "";
8757
+ if (!/^[A-Z][A-Z0-9_]+$/.test(code)) return;
8758
+ status.textContent = relocationErrorText(code);
8759
+ }).observe(status, { childList: true, characterData: true, subtree: true });
8760
+ }
8679
8761
  });
@@ -586,6 +586,7 @@ export const DASHBOARD_MESSAGES = {
586
586
  "ui.component_restart_failed": "The component could not be restarted.",
587
587
  "ui.component_restart_requested": "Restart request sent. The component will be available again shortly.",
588
588
  "ui.component_restart_started": "Component restart started.",
589
+ "ui.component_restart_unconfirmed": "The component is reachable again, but the restart could not be confirmed.",
589
590
  "ui.component_restart_waiting": "The component has not returned healthy yet; monitoring continues.",
590
591
  "ui.component_status": "Component status",
591
592
  "ui.confirm_action": "Confirm action",
@@ -1231,6 +1232,7 @@ export const DASHBOARD_MESSAGES = {
1231
1232
  "ui.component_restart_failed": "De component kon niet worden herstart.",
1232
1233
  "ui.component_restart_requested": "Herstartverzoek verzonden. De component komt zo opnieuw beschikbaar.",
1233
1234
  "ui.component_restart_started": "Componentherstart is gestart.",
1235
+ "ui.component_restart_unconfirmed": "De component is weer bereikbaar, maar de herstart kon niet worden bevestigd.",
1234
1236
  "ui.component_restart_waiting": "De component komt nog niet gezond terug; controle loopt door.",
1235
1237
  "ui.component_status": "Componentstatus",
1236
1238
  "ui.confirm_action": "Bevestig actie",
@@ -4127,3 +4129,11 @@ Object.assign(DASHBOARD_MESSAGES.nl,{"configuration.relocation_filesystem_unsupp
4127
4129
  Object.assign(DASHBOARD_MESSAGES.de,{"configuration.relocation_filesystem_unsupported":"Wählen Sie für eine Verschiebung auf ein anderes Laufwerk oder Volume einen APFS-formatierten Ordner."});
4128
4130
  Object.assign(DASHBOARD_MESSAGES.fr,{"configuration.relocation_filesystem_unsupported":"Pour déplacer les données vers un autre disque ou volume, choisissez un dossier formaté en APFS."});
4129
4131
  Object.assign(DASHBOARD_MESSAGES.es,{"configuration.relocation_filesystem_unsupported":"Para reubicar en otro disco o volumen, elija una carpeta con formato APFS."});
4132
+ Object.assign(DASHBOARD_MESSAGES.en,{"configuration.relocation_error.LOCATION_REQUIRED":"Choose a folder before relocating.","configuration.relocation_error.LOCATION_NOT_WRITABLE":"Choose a local folder that is available and writable.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_INVALID":"The selected folder cannot contain the current platform data.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_FILESYSTEM_UNAVAILABLE":"The destination filesystem could not be checked.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_FILESYSTEM_UNSUPPORTED":"Choose an APFS-formatted folder for a relocation to another disk or volume.","configuration.relocation_error.PLATFORM_DATA_COPY_VERIFICATION_FAILED":"The copied platform data could not be verified safely.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_NOT_PREPARED":"Choose the new folder again before relocating.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_NOT_EMPTY":"The prepared destination folder is no longer empty.","configuration.relocation_error.PLATFORM_DATA_UNAVAILABLE":"The current platform data is unavailable.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_EXISTS":"The selected folder already contains platform data.","configuration.relocation_error.RELOCATION_KIND_RETIRED":"This relocation option is no longer available.","configuration.relocation_error.RELOCATION_ALREADY_PENDING":"Another relocation is already waiting to restart.","configuration.relocation_error.RELOCATION_REQUEST_INVALID":"The relocation request is invalid.","configuration.relocation_error.PLATFORM_DATA_RELOCATION_BLOCKED":"Relocation is unavailable while platform work is active.","configuration.relocation_error.UNKNOWN":"The relocation could not be completed."});
4133
+ Object.assign(DASHBOARD_MESSAGES.nl,{"configuration.relocation_error.LOCATION_REQUIRED":"Kies eerst een map voordat je verplaatst.","configuration.relocation_error.LOCATION_NOT_WRITABLE":"Kies een lokale map die beschikbaar en beschrijfbaar is.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_INVALID":"De gekozen map mag de huidige platformgegevens niet bevatten.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_FILESYSTEM_UNAVAILABLE":"Het bestandssysteem van de doelmap kon niet worden gecontroleerd.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_FILESYSTEM_UNSUPPORTED":"Kies voor verplaatsing naar een andere schijf of volume een APFS-geformatteerde map.","configuration.relocation_error.PLATFORM_DATA_COPY_VERIFICATION_FAILED":"De gekopieerde platformgegevens konden niet veilig worden gecontroleerd.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_NOT_PREPARED":"Kies de nieuwe map opnieuw voordat je verplaatst.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_NOT_EMPTY":"De voorbereide doelmap is niet meer leeg.","configuration.relocation_error.PLATFORM_DATA_UNAVAILABLE":"De huidige platformgegevens zijn niet beschikbaar.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_EXISTS":"De gekozen map bevat al platformgegevens.","configuration.relocation_error.RELOCATION_KIND_RETIRED":"Deze verplaatsingsoptie is niet meer beschikbaar.","configuration.relocation_error.RELOCATION_ALREADY_PENDING":"Er wacht al een andere verplaatsing op een herstart.","configuration.relocation_error.RELOCATION_REQUEST_INVALID":"De verplaatsingsaanvraag is ongeldig.","configuration.relocation_error.PLATFORM_DATA_RELOCATION_BLOCKED":"Verplaatsen is niet beschikbaar terwijl platformwerk actief is.","configuration.relocation_error.UNKNOWN":"De verplaatsing kon niet worden uitgevoerd."});
4134
+ Object.assign(DASHBOARD_MESSAGES.de,{"configuration.relocation_error.LOCATION_REQUIRED":"Wählen Sie vor dem Verschieben einen Ordner aus.","configuration.relocation_error.LOCATION_NOT_WRITABLE":"Wählen Sie einen verfügbaren, beschreibbaren lokalen Ordner aus.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_INVALID":"Der ausgewählte Ordner darf die aktuellen Plattformdaten nicht enthalten.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_FILESYSTEM_UNAVAILABLE":"Das Dateisystem des Zielordners konnte nicht geprüft werden.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_FILESYSTEM_UNSUPPORTED":"Wählen Sie für eine Verschiebung auf ein anderes Laufwerk oder Volume einen APFS-formatierten Ordner.","configuration.relocation_error.PLATFORM_DATA_COPY_VERIFICATION_FAILED":"Die kopierten Plattformdaten konnten nicht sicher geprüft werden.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_NOT_PREPARED":"Wählen Sie vor dem Verschieben den neuen Ordner erneut aus.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_NOT_EMPTY":"Der vorbereitete Zielordner ist nicht mehr leer.","configuration.relocation_error.PLATFORM_DATA_UNAVAILABLE":"Die aktuellen Plattformdaten sind nicht verfügbar.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_EXISTS":"Der ausgewählte Ordner enthält bereits Plattformdaten.","configuration.relocation_error.RELOCATION_KIND_RETIRED":"Diese Verschiebungsoption ist nicht mehr verfügbar.","configuration.relocation_error.RELOCATION_ALREADY_PENDING":"Eine andere Verschiebung wartet bereits auf einen Neustart.","configuration.relocation_error.RELOCATION_REQUEST_INVALID":"Die Verschiebungsanfrage ist ungültig.","configuration.relocation_error.PLATFORM_DATA_RELOCATION_BLOCKED":"Eine Verschiebung ist nicht verfügbar, während Plattformarbeit aktiv ist.","configuration.relocation_error.UNKNOWN":"Die Verschiebung konnte nicht abgeschlossen werden."});
4135
+ Object.assign(DASHBOARD_MESSAGES.fr,{"configuration.relocation_error.LOCATION_REQUIRED":"Choisissez un dossier avant de déplacer les données.","configuration.relocation_error.LOCATION_NOT_WRITABLE":"Choisissez un dossier local disponible et accessible en écriture.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_INVALID":"Le dossier sélectionné ne peut pas contenir les données actuelles de la plateforme.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_FILESYSTEM_UNAVAILABLE":"Le système de fichiers de destination n’a pas pu être vérifié.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_FILESYSTEM_UNSUPPORTED":"Pour déplacer les données vers un autre disque ou volume, choisissez un dossier formaté en APFS.","configuration.relocation_error.PLATFORM_DATA_COPY_VERIFICATION_FAILED":"La copie des données de la plateforme n’a pas pu être vérifiée de manière sûre.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_NOT_PREPARED":"Choisissez à nouveau le nouveau dossier avant de déplacer les données.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_NOT_EMPTY":"Le dossier de destination préparé n’est plus vide.","configuration.relocation_error.PLATFORM_DATA_UNAVAILABLE":"Les données actuelles de la plateforme sont indisponibles.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_EXISTS":"Le dossier sélectionné contient déjà des données de plateforme.","configuration.relocation_error.RELOCATION_KIND_RETIRED":"Cette option de déplacement n’est plus disponible.","configuration.relocation_error.RELOCATION_ALREADY_PENDING":"Un autre déplacement attend déjà un redémarrage.","configuration.relocation_error.RELOCATION_REQUEST_INVALID":"La demande de déplacement n’est pas valide.","configuration.relocation_error.PLATFORM_DATA_RELOCATION_BLOCKED":"Le déplacement est indisponible pendant que le travail de la plateforme est actif.","configuration.relocation_error.UNKNOWN":"Le déplacement n’a pas pu être terminé."});
4136
+ Object.assign(DASHBOARD_MESSAGES.es,{"configuration.relocation_error.LOCATION_REQUIRED":"Elija una carpeta antes de reubicar los datos.","configuration.relocation_error.LOCATION_NOT_WRITABLE":"Elija una carpeta local disponible y con permiso de escritura.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_INVALID":"La carpeta seleccionada no puede contener los datos actuales de la plataforma.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_FILESYSTEM_UNAVAILABLE":"No se pudo comprobar el sistema de archivos de destino.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_FILESYSTEM_UNSUPPORTED":"Para reubicar en otro disco o volumen, elija una carpeta con formato APFS.","configuration.relocation_error.PLATFORM_DATA_COPY_VERIFICATION_FAILED":"No se pudo verificar de forma segura la copia de los datos de la plataforma.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_NOT_PREPARED":"Elija de nuevo la carpeta nueva antes de reubicar los datos.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_NOT_EMPTY":"La carpeta de destino preparada ya no está vacía.","configuration.relocation_error.PLATFORM_DATA_UNAVAILABLE":"Los datos actuales de la plataforma no están disponibles.","configuration.relocation_error.PLATFORM_DATA_DESTINATION_EXISTS":"La carpeta seleccionada ya contiene datos de la plataforma.","configuration.relocation_error.RELOCATION_KIND_RETIRED":"Esta opción de reubicación ya no está disponible.","configuration.relocation_error.RELOCATION_ALREADY_PENDING":"Ya hay otra reubicación esperando para reiniciar.","configuration.relocation_error.RELOCATION_REQUEST_INVALID":"La solicitud de reubicación no es válida.","configuration.relocation_error.PLATFORM_DATA_RELOCATION_BLOCKED":"La reubicación no está disponible mientras el trabajo de la plataforma está activo.","configuration.relocation_error.UNKNOWN":"No se pudo completar la reubicación."});
4137
+ Object.assign(DASHBOARD_MESSAGES.de,{"ui.component_restart_unconfirmed":"Die Komponente ist wieder erreichbar, aber der Neustart konnte nicht bestätigt werden."});
4138
+ Object.assign(DASHBOARD_MESSAGES.fr,{"ui.component_restart_unconfirmed":"Le composant est de nouveau accessible, mais le redémarrage n’a pas pu être confirmé."});
4139
+ Object.assign(DASHBOARD_MESSAGES.es,{"ui.component_restart_unconfirmed":"El componente vuelve a estar disponible, pero no se pudo confirmar el reinicio."});
@@ -15,9 +15,10 @@ from collections.abc import Iterable, Iterator, Mapping
15
15
 
16
16
  from .agent_state import redact_diagnostic
17
17
  from . import central_database as central_database_module
18
- from .storage import EngineeringStorageError, open_storage
18
+ from .storage import ENGINEERING_STORAGE_SCHEMA_VERSION, EngineeringStorageError, open_storage
19
19
  from .providers import GitProvider
20
20
  from .platform_components import PLATFORM_COMPONENT_IDS
21
+ from .platform_version import CURRENT_PLATFORM_VERSION
21
22
 
22
23
  LOG_LEVEL_ENVIRONMENT = "ENGINEERING_PLATFORM_LOG_LEVEL"
23
24
  SERVER_DATA_ROOT_ENVIRONMENT = "EP_SERVER_DATA_ROOT"
@@ -38,6 +39,8 @@ LIFECYCLE_CONTEXT_KEYS = frozenset(
38
39
  "launchd_label",
39
40
  "launch_agent_path",
40
41
  "target_component",
42
+ "component_version",
43
+ "target_component_version",
41
44
  "shutdown_signal",
42
45
  "configuration_scope",
43
46
  "configuration_key",
@@ -367,18 +370,33 @@ def log_event(
367
370
  context: Mapping[str, object] | None = None,
368
371
  ) -> None:
369
372
  """Write one redacted, structured event without exposing arbitrary extras."""
373
+ component = logger.name.rsplit(".", 1)[-1]
374
+ payload_context = dict(context or {})
375
+ payload_context.setdefault("component_version", _component_version(component))
376
+ target = payload_context.get("target_component")
377
+ if isinstance(target, str) and target in PLATFORM_COMPONENT_IDS:
378
+ payload_context.setdefault("target_component_version", _component_version(target))
370
379
  logger.log(
371
380
  level,
372
381
  redact_diagnostic(event, limit=500),
373
382
  extra={
374
- "component": logger.name.rsplit(".", 1)[-1],
383
+ "component": component,
375
384
  "run_id": run_id or "",
376
385
  "diagnostic": diagnostic,
377
- "context": dict(context or {}),
386
+ "context": payload_context,
378
387
  },
379
388
  )
380
389
 
381
390
 
391
+ def _component_version(component: str) -> str:
392
+ """Return the bounded version identity recorded beside a component name."""
393
+ if component == "platform_database":
394
+ return str(ENGINEERING_STORAGE_SCHEMA_VERSION)
395
+ if component in {"http_ingress", "cli_ingress", "file_inbox_ingress"}:
396
+ return "1"
397
+ return CURRENT_PLATFORM_VERSION
398
+
399
+
382
400
  def component_lifecycle_context(
383
401
  root: Path,
384
402
  *,
@@ -12,7 +12,7 @@ from .storage import ENGINEERING_STORAGE_SCHEMA_VERSION
12
12
 
13
13
  SEMVER = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$")
14
14
  CONTRACT = re.compile(r"^(\d{4})\.(0[1-9]|1[0-2])$")
15
- CURRENT_PLATFORM_VERSION = "2.3.0"
15
+ CURRENT_PLATFORM_VERSION = "2.3.1"
16
16
  MANIFEST_FIELDS = frozenset(
17
17
  {
18
18
  "platform_version",
@@ -15,6 +15,7 @@ from .providers import CodexCliProvider, LocalProcessProvider, codex_cli_executa
15
15
 
16
16
 
17
17
  _VERSION = re.compile(r"\b\d+(?:\.\d+)+(?:[-+][0-9A-Za-z.]+)?\b")
18
+ CODEX_READINESS_TIMEOUT_SECONDS = 2
18
19
 
19
20
 
20
21
  def _classify(result: subprocess.CompletedProcess[str] | None) -> str:
@@ -54,8 +55,8 @@ def runtime_details(root: Path) -> dict[str, dict[str, str]]:
54
55
  """Project token-free CLI provenance for the host-wide Console projection."""
55
56
  codex_path = codex_cli_executable() or ""
56
57
  try:
57
- codex_version = _version(CodexCliProvider().command("--version")) if codex_path else ""
58
- except OSError:
58
+ codex_version = _version(CodexCliProvider().command("--version", timeout=CODEX_READINESS_TIMEOUT_SECONDS)) if codex_path else ""
59
+ except (OSError, subprocess.TimeoutExpired):
59
60
  codex_version = ""
60
61
  github_path = shutil.which("gh") or ""
61
62
  try:
@@ -79,8 +80,8 @@ def host_status(root: Path, *, require_github: bool = True) -> dict[str, dict[st
79
80
  codex = CodexCliProvider()
80
81
  codex_installed = codex.status().qualified
81
82
  try:
82
- codex_result = codex.command("login", "status") if codex_installed else None
83
- except OSError:
83
+ codex_result = codex.command("login", "status", timeout=CODEX_READINESS_TIMEOUT_SECONDS) if codex_installed else None
84
+ except (OSError, subprocess.TimeoutExpired):
84
85
  codex_result = None
85
86
  # A server can be queried immediately after launchd restarts it (notably
86
87
  # after a platform-data import). The managed CLI may transiently fail
@@ -89,8 +90,8 @@ def host_status(root: Path, *, require_github: bool = True) -> dict[str, dict[st
89
90
  # as a provider fault, while still failing closed if it persists.
90
91
  if codex_installed and _classify(codex_result) == "CHECK_FAILED":
91
92
  try:
92
- codex_result = codex.command("login", "status")
93
- except OSError:
93
+ codex_result = codex.command("login", "status", timeout=CODEX_READINESS_TIMEOUT_SECONDS)
94
+ except (OSError, subprocess.TimeoutExpired):
94
95
  codex_result = None
95
96
  result = {
96
97
  "codex": {"provider": "CODEX", "state": "UNAVAILABLE" if not codex_installed else _classify(codex_result)},
@@ -216,10 +216,10 @@ class CodexCliProvider(LocalProcessProvider):
216
216
  available = bool(self._executable) and Path(self._executable).is_file() and os.access(self._executable, os.X_OK)
217
217
  return ProviderStatus("codex_cli", "configured", available, "available" if available else "codex unavailable")
218
218
 
219
- def command(self, *args: str) -> subprocess.CompletedProcess[str]:
219
+ def command(self, *args: str, timeout: float | None = None) -> subprocess.CompletedProcess[str]:
220
220
  if not self._executable:
221
221
  raise FileNotFoundError("Engineering Platform managed Codex CLI is unavailable")
222
- return subprocess.run((self._executable, *args), text=True, capture_output=True, check=False)
222
+ return subprocess.run((self._executable, *args), text=True, capture_output=True, check=False, timeout=timeout)
223
223
 
224
224
  def app_server(self) -> subprocess.Popen[str]:
225
225
  """Open the provider-owned interactive Codex app-server channel."""
@@ -69,7 +69,7 @@ from .component_logging import (
69
69
  )
70
70
  from .ep_consumer_credentials import verifier
71
71
  from .parity_context import ParityProjectStore, project_context
72
- from .platform_version import EngineeringPlatformManifest
72
+ from .platform_version import CURRENT_PLATFORM_VERSION, EngineeringPlatformManifest
73
73
  from .providers import (
74
74
  GitHubProvider,
75
75
  CodexCliProvider,
@@ -150,6 +150,13 @@ def _http_json_openapi_document() -> dict[str, object]:
150
150
  "description": "Canonical authenticated submission ingress and platform health.",
151
151
  },
152
152
  "paths": {
153
+ "/v1/producer-compatibility": {
154
+ "get": {
155
+ "summary": "Read the immutable producer compatibility declaration",
156
+ "description": "Read-only installation identity and supported producer contracts; it never admits or executes a submission.",
157
+ "responses": {"200": {"description": "Compatibility declaration v1.0"}},
158
+ },
159
+ },
153
160
  "/health": {
154
161
  "get": {
155
162
  "summary": "Read aggregated Engineering Platform component health",
@@ -1751,6 +1758,7 @@ def status(data_root: Path) -> dict[str, object]:
1751
1758
  running = bool(runtime and _alive(runtime.get("pid")))
1752
1759
  components = _transport_components(data_root, server_running=running)
1753
1760
  components["dashboard_relay"] = _dashboard_relay_component(server_running=running)
1761
+ platform_version = _console_platform_version()
1754
1762
  # One Server-native inventory feeds Components, the titlebar popout and
1755
1763
  # detail modals. It deliberately contains no watcher/check-out model.
1756
1764
  for definition in PLATFORM_COMPONENTS:
@@ -1760,6 +1768,7 @@ def status(data_root: Path) -> dict[str, object]:
1760
1768
  "group": definition.group, "critical": definition.critical, "restart_supported": definition.restart_supported,
1761
1769
  "log_component": definition.id,
1762
1770
  })
1771
+ components[definition.id].setdefault("version", platform_version)
1763
1772
  continue
1764
1773
  healthy = True if definition.id == "platform_database" else running
1765
1774
  components[definition.id] = {
@@ -1768,10 +1777,10 @@ def status(data_root: Path) -> dict[str, object]:
1768
1777
  "log_component": definition.id, "healthy": healthy,
1769
1778
  "status_code": definition.active_status if healthy else definition.inactive_status,
1770
1779
  "detail_code": definition.detail_code,
1771
- **({"version": str(SERVER_STORE_SCHEMA_VERSION)} if definition.id == "platform_database" else {}),
1780
+ "version": str(SERVER_STORE_SCHEMA_VERSION) if definition.id == "platform_database" else platform_version,
1772
1781
  }
1773
1782
  server_component = components["ep_server"]
1774
- server_component["version"] = _console_platform_version()
1783
+ server_component["version"] = platform_version
1775
1784
  started_at = runtime.get("started_at") if runtime else None
1776
1785
  if isinstance(started_at, str):
1777
1786
  try:
@@ -2693,6 +2702,19 @@ class _HealthHandler(http.server.BaseHTTPRequestHandler):
2693
2702
  # traceback that obscures qualification diagnostics.
2694
2703
  return
2695
2704
 
2705
+ def _send_artifact_bytes(self, payload: bytes, instance_id: str) -> None:
2706
+ """Return the verified immutable artifact bytes without JSON re-encoding."""
2707
+ self.send_response(200)
2708
+ self.send_header("Content-Type", "application/json")
2709
+ self.send_header("Content-Length", str(len(payload)))
2710
+ self.send_header("EP-Server-Instance", instance_id)
2711
+ self.send_header("Cache-Control", "no-store")
2712
+ self.end_headers()
2713
+ try:
2714
+ self.wfile.write(payload)
2715
+ except (BrokenPipeError, ConnectionResetError):
2716
+ return
2717
+
2696
2718
  def _send_ndjson(self, entries: list[dict[str, object]]) -> None:
2697
2719
  encoded = ("\n".join(json.dumps(entry, sort_keys=True) for entry in entries) + ("\n" if entries else "")).encode("utf-8")
2698
2720
  self.send_response(200)
@@ -3493,6 +3515,21 @@ class _HealthHandler(http.server.BaseHTTPRequestHandler):
3493
3515
  if request.path in {HTTP_JSON_OPENAPI_PATH, "/openapi.json", "/swagger.json"}:
3494
3516
  self._send(200, _http_json_openapi_document())
3495
3517
  return
3518
+ if request.path == "/v1/producer-compatibility":
3519
+ # This is an installation identity and contract declaration only.
3520
+ # It performs no project lookup, admission, execution, or provider
3521
+ # interaction, so consumers can preflight before POSTing a request.
3522
+ identity = initialize(self.server.data_root).instance_id # type: ignore[attr-defined]
3523
+ self._send(200, {
3524
+ "contract_version": "1.0",
3525
+ "producer": {"id": "engineering-platform", "version": CURRENT_PLATFORM_VERSION},
3526
+ "instance": {"id": identity},
3527
+ "contracts": {
3528
+ "producer_readback": [submission_service.PRODUCER_READBACK_CONTRACT_VERSION],
3529
+ "terminal_evidence": [submission_service.TERMINAL_EVIDENCE_CONTRACT_VERSION],
3530
+ },
3531
+ }, identity)
3532
+ return
3496
3533
  if request.path == "/diagnostics/topology":
3497
3534
  try:
3498
3535
  self._send(200, operations_projection(self.server.data_root), initialize(self.server.data_root).instance_id) # type: ignore[attr-defined]
@@ -3541,7 +3578,7 @@ class _HealthHandler(http.server.BaseHTTPRequestHandler):
3541
3578
  if payload is None:
3542
3579
  self._send(404, {"error": "EVIDENCE_ARTIFACT_NOT_FOUND"})
3543
3580
  else:
3544
- self._send(200, json.loads(payload), initialize(self.server.data_root).instance_id) # type: ignore[attr-defined]
3581
+ self._send_artifact_bytes(payload, initialize(self.server.data_root).instance_id) # type: ignore[attr-defined]
3545
3582
  except (sqlite3.Error, ValueError, json.JSONDecodeError):
3546
3583
  self._send(503, {"error": "CENTRAL_UNAVAILABLE"})
3547
3584
  return
@@ -3,7 +3,7 @@
3
3
  "platform": {
4
4
  "id": "engineering-platform",
5
5
  "name": "Engineering Platform",
6
- "version": "2.3.0",
6
+ "version": "2.3.1",
7
7
  "generation": 2,
8
8
  "documentation_namespace": "engineering-platform",
9
9
  "capability_registry_version": 1
@@ -11,8 +11,15 @@
11
11
  "workspace": {
12
12
  "id": "replace-workspace-id",
13
13
  "name": "Replace Workspace Name",
14
- "repository": {"provider": "github", "owner": "replace-owner", "name": "replace-repository", "default_branch": "main"},
15
- "branding": {"dashboard_title": "Replace Workspace Name"},
14
+ "repository": {
15
+ "provider": "github",
16
+ "owner": "replace-owner",
17
+ "name": "replace-repository",
18
+ "default_branch": "main"
19
+ },
20
+ "branding": {
21
+ "dashboard_title": "Replace Workspace Name"
22
+ },
16
23
  "workspace_authorization": {
17
24
  "allowed_roots": [],
18
25
  "allowed_repositories": [],
@@ -21,5 +28,12 @@
21
28
  "case_sensitivity": "host"
22
29
  }
23
30
  },
24
- "providers": {"runtime": "codex_cli", "repository": "github", "service_manager": "launchd", "remote_submission": "icloud_inbox", "private_remote_access": "tailscale", "dashboard": "private_http"}
31
+ "providers": {
32
+ "runtime": "codex_cli",
33
+ "repository": "github",
34
+ "service_manager": "launchd",
35
+ "remote_submission": "icloud_inbox",
36
+ "private_remote_access": "tailscale",
37
+ "dashboard": "private_http"
38
+ }
25
39
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-platform
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
4
  Summary: Local-first Engineering Platform execution operations runtime
5
5
  Author: Peter van Tol
6
6
  License: MIT