engineering-platform 2.3.95__tar.gz → 2.3.97__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 (173) hide show
  1. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/PKG-INFO +1 -1
  2. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/pyproject.toml +1 -1
  3. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json +1 -1
  4. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json +4 -4
  5. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/assets/dashboard_locales.mjs +8 -0
  6. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_executor.py +24 -0
  7. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_host.py +25 -8
  8. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_timeout_policy.py +22 -12
  9. engineering_platform-2.3.97/src/engineering_platform/managed_workspace_recovery.py +174 -0
  10. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/platform_version.py +1 -1
  11. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/server.py +27 -1
  12. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/server_console_services.py +1 -1
  13. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/templates/workspace-config.json +1 -1
  14. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform.egg-info/PKG-INFO +1 -1
  15. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform.egg-info/SOURCES.txt +1 -0
  16. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/README.md +0 -0
  17. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/setup.cfg +0 -0
  18. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/__init__.py +0 -0
  19. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/__main__.py +0 -0
  20. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/agent_state.py +0 -0
  21. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/agent_trust.py +0 -0
  22. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/assets/dashboard.css +0 -0
  23. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/assets/dashboard.js +0 -0
  24. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/assets/dashboard_status_store.mjs +0 -0
  25. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/assets/dashboard_translation.mjs +0 -0
  26. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/assets/operations-console/apple-touch-icon-dark.png +0 -0
  27. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/assets/operations-console/apple-touch-icon-light.png +0 -0
  28. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/assets/operations-console/icon-dark.png +0 -0
  29. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/assets/operations-console/icon-light.png +0 -0
  30. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/assets/operations-console/icon-transparent.png +0 -0
  31. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/assets/operations-console/manifest.webmanifest +0 -0
  32. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/assurance_scope.py +0 -0
  33. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/capability_preflight.py +0 -0
  34. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/capability_review.py +0 -0
  35. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/central_data_transfer.py +0 -0
  36. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/central_database.py +0 -0
  37. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/central_operational_reset.py +0 -0
  38. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/central_store_migration.py +0 -0
  39. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/codex_capacity.py +0 -0
  40. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/codex_chat.py +0 -0
  41. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/codex_observability.py +0 -0
  42. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/component_lock.py +0 -0
  43. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/component_logging.py +0 -0
  44. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/console_presentation.py +0 -0
  45. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/console_route_ownership.py +0 -0
  46. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/contracts/__init__.py +0 -0
  47. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/contracts/ep_consumer.py +0 -0
  48. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/contracts/models.py +0 -0
  49. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/contracts/projection.py +0 -0
  50. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/dashboard_browser_validation.py +0 -0
  51. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/dashboard_state.py +0 -0
  52. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/dashboard_supervisor.swift +0 -0
  53. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/dashboard_translation.py +0 -0
  54. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/dependabot_producer.py +0 -0
  55. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/development_profile.py +0 -0
  56. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/drift_diagnostics.py +0 -0
  57. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/emergency_recovery.py +0 -0
  58. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/engineering_memory.py +0 -0
  59. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/ep_consumer_credentials.py +0 -0
  60. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/evidence_projection.py +0 -0
  61. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_activity.py +0 -0
  62. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_context.py +0 -0
  63. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_errors.py +0 -0
  64. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_evidence.py +0 -0
  65. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_finalization.py +0 -0
  66. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_host_evidence.py +0 -0
  67. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_lease.py +0 -0
  68. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_lifecycle.py +0 -0
  69. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_models.py +0 -0
  70. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_readiness.py +0 -0
  71. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_reporting.py +0 -0
  72. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_repository.py +0 -0
  73. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_timing.py +0 -0
  74. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/execution_transaction.py +0 -0
  75. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/external_producer_binding.py +0 -0
  76. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/file_inbox.py +0 -0
  77. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/forensic_attribution.py +0 -0
  78. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/forensic_attribution_v2.py +0 -0
  79. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/forensic_delta.py +0 -0
  80. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/golden_scenario.py +0 -0
  81. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/historical_dashboard_configuration.py +0 -0
  82. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/host_admin.py +0 -0
  83. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/host_preflight.py +0 -0
  84. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/installation_relocation.py +0 -0
  85. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/installation_update_activation.py +0 -0
  86. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/installation_update_admission.py +0 -0
  87. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/installation_update_backup.py +0 -0
  88. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/installation_update_composition.py +0 -0
  89. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/installation_update_executor.py +0 -0
  90. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/installation_update_migration.py +0 -0
  91. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/installation_update_operation.py +0 -0
  92. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/installation_update_plan.py +0 -0
  93. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/installation_update_preparation.py +0 -0
  94. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/investigation_ledger.py +0 -0
  95. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/legacy_inbox_migration.py +0 -0
  96. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/legacy_installation_adoption.py +0 -0
  97. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/lifecycle_worker.py +0 -0
  98. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/live_status.py +0 -0
  99. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/local_api.py +0 -0
  100. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/local_api_keychain.py +0 -0
  101. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/local_repository_binding.py +0 -0
  102. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/managed_autonomy.py +0 -0
  103. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/managed_codex_runtime.py +0 -0
  104. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/managed_workspace_readiness.py +0 -0
  105. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/merge_delegation.py +0 -0
  106. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/operational_installation.py +0 -0
  107. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/operational_installation_lock.py +0 -0
  108. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/operational_installation_record.py +0 -0
  109. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/owner_credential_recovery.py +0 -0
  110. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/parity_context.py +0 -0
  111. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/parity_lifecycle_dispatcher.py +0 -0
  112. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/platform_admin.py +0 -0
  113. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/platform_api.py +0 -0
  114. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/platform_bootstrap.py +0 -0
  115. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/platform_components.py +0 -0
  116. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/pr_check_repair.py +0 -0
  117. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/pr_evidence_backfill.py +0 -0
  118. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/producer.py +0 -0
  119. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/product_installation_readback.py +0 -0
  120. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/project_agent.py +0 -0
  121. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/project_agent_service.py +0 -0
  122. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/project_topology.py +0 -0
  123. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/prompt_history.py +0 -0
  124. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/provider_context.py +0 -0
  125. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/provider_context_benchmark.py +0 -0
  126. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/provider_context_scope.py +0 -0
  127. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/provider_interruption.py +0 -0
  128. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/provider_process_identity.py +0 -0
  129. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/provider_readiness.py +0 -0
  130. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/provider_recovery.py +0 -0
  131. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/provider_usage.py +0 -0
  132. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/providers.py +0 -0
  133. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/qualification.py +0 -0
  134. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/qualification_runtime.py +0 -0
  135. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/recommendation_handoff.py +0 -0
  136. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/reconciliation_adoption.py +0 -0
  137. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/release_operation.py +0 -0
  138. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/report_analysis.py +0 -0
  139. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/repository_attachment.py +0 -0
  140. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/repository_handoff.py +0 -0
  141. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/resources.py +0 -0
  142. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/reviewer_evidence.py +0 -0
  143. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/revision_binding.py +0 -0
  144. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/schemas/producer-readback-v1.2.schema.json +0 -0
  145. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/schemas/producer-readback-v1.3.schema.json +0 -0
  146. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/schemas/producer-readback-v1.schema.json +0 -0
  147. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/schemas/repository-attachment.schema.json +0 -0
  148. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/server_relay.py +0 -0
  149. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/server_service.py +0 -0
  150. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/status_model.py +0 -0
  151. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/status_reconciliation.py +0 -0
  152. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/storage.py +0 -0
  153. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/submission_cli.py +0 -0
  154. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/submission_intake.py +0 -0
  155. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/submission_service.py +0 -0
  156. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/system_installation_topology.py +0 -0
  157. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/system_server_service.py +0 -0
  158. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/telemetry.py +0 -0
  159. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/telemetry_contract.py +0 -0
  160. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/telemetry_export.py +0 -0
  161. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/telemetry_metrics.py +0 -0
  162. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/terminal_evidence_reconciliation.py +0 -0
  163. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/validation_identity.py +0 -0
  164. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/validation_profile.py +0 -0
  165. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/version_preparation_delivery.py +0 -0
  166. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/workspace_preflight.py +0 -0
  167. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/worktree_provenance.py +0 -0
  168. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform/worktree_tooling.py +0 -0
  169. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform.egg-info/dependency_links.txt +0 -0
  170. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform.egg-info/entry_points.txt +0 -0
  171. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/src/engineering_platform.egg-info/top_level.txt +0 -0
  172. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/tests/test_adaptive_action_sizing_design.py +0 -0
  173. {engineering_platform-2.3.95 → engineering_platform-2.3.97}/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.95
3
+ Version: 2.3.97
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.95"
7
+ version = "2.3.97"
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.95",
6
+ "version": "2.3.97",
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.95",
4
+ "dashboard_version": "2.3.97",
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.95",
9
+ "platform_version": "2.3.97",
10
10
  "report_format": 2,
11
- "runner_version": "2.3.95",
11
+ "runner_version": "2.3.97",
12
12
  "status_model": 1,
13
13
  "storage_schema": 45,
14
- "watcher_version": "2.3.95"
14
+ "watcher_version": "2.3.97"
15
15
  }
@@ -4283,3 +4283,11 @@ Object.assign(DASHBOARD_MESSAGES.nl, {"lifecycle.assurance_integral_evidence":"I
4283
4283
  Object.assign(DASHBOARD_MESSAGES.de, {"lifecycle.assurance_integral_evidence":"Geprüfte Auswirkungsbereiche: {coverage} · Frühere Befunde erneut bewertet: {dispositions}"});
4284
4284
  Object.assign(DASHBOARD_MESSAGES.fr, {"lifecycle.assurance_integral_evidence":"Surfaces d’impact examinées : {coverage} · Constats antérieurs réévalués : {dispositions}"});
4285
4285
  Object.assign(DASHBOARD_MESSAGES.es, {"lifecycle.assurance_integral_evidence":"Superficies de impacto revisadas: {coverage} · Hallazgos anteriores reevaluados: {dispositions}"});
4286
+
4287
+ // Provider-timeouts-v2 exposes both the inactivity boundary and the immutable
4288
+ // absolute maximum so operators can understand why productive work continues.
4289
+ Object.assign(DASHBOARD_MESSAGES.en, {"configuration.timeout_policy_description":"Progress-aware host limits per provider action. Concrete execution progress resets the inactivity boundary; the absolute maximum always applies.","configuration.timeout_5_max":"5 min maximum","configuration.timeout_10_idle_30_max":"10 min without progress · 30 min maximum","configuration.timeout_15_idle_45_max":"15 min without progress · 45 min maximum"});
4290
+ Object.assign(DASHBOARD_MESSAGES.nl, {"configuration.timeout_policy_description":"Voortgangsbewuste hostgrenzen per provideractie. Concrete uitvoeringsvoortgang reset de inactiviteitsgrens; het absolute maximum blijft altijd gelden.","configuration.timeout_5_max":"maximaal 5 min","configuration.timeout_10_idle_30_max":"10 min zonder voortgang · maximaal 30 min","configuration.timeout_15_idle_45_max":"15 min zonder voortgang · maximaal 45 min"});
4291
+ Object.assign(DASHBOARD_MESSAGES.de, {"configuration.timeout_policy_description":"Fortschrittsabhängige Hostgrenzen pro Provider-Aktion. Konkreter Ausführungsfortschritt setzt die Inaktivitätsgrenze zurück; das absolute Maximum gilt immer.","configuration.timeout_5_max":"maximal 5 Min.","configuration.timeout_10_idle_30_max":"10 Min. ohne Fortschritt · maximal 30 Min.","configuration.timeout_15_idle_45_max":"15 Min. ohne Fortschritt · maximal 45 Min."});
4292
+ Object.assign(DASHBOARD_MESSAGES.fr, {"configuration.timeout_policy_description":"Limites hôte sensibles à la progression pour chaque action fournisseur. Une progression concrète réinitialise la limite d’inactivité ; le maximum absolu reste applicable.","configuration.timeout_5_max":"5 min maximum","configuration.timeout_10_idle_30_max":"10 min sans progression · 30 min maximum","configuration.timeout_15_idle_45_max":"15 min sans progression · 45 min maximum"});
4293
+ Object.assign(DASHBOARD_MESSAGES.es, {"configuration.timeout_policy_description":"Límites del host sensibles al progreso para cada acción del proveedor. El progreso concreto restablece el límite de inactividad; el máximo absoluto siempre se aplica.","configuration.timeout_5_max":"máximo 5 min","configuration.timeout_10_idle_30_max":"10 min sin progreso · máximo 30 min","configuration.timeout_15_idle_45_max":"15 min sin progreso · máximo 45 min"});
@@ -408,6 +408,7 @@ class CodexCliClient:
408
408
  self._command_callback: Callable[..., None] | None = None
409
409
  self._workspace_progress_callback: Callable[[dict[str, int]], None] | None = None
410
410
  self._handoff_deadline_callback: Callable[[], bool] | None = None
411
+ self._deadline_progress_callback: Callable[[], None] | None = None
411
412
  # A process boundary remains published until this client has observed
412
413
  # the owned session disappear. A callback failure must not make an
413
414
  # otherwise live provider look as though it has already exited.
@@ -480,6 +481,15 @@ class CodexCliClient:
480
481
  """Set a host-owned deadline check for an externally observable hand-off."""
481
482
  self._handoff_deadline_callback = callback
482
483
 
484
+ def set_deadline_progress_callback(self, callback: Callable[[], None] | None) -> None:
485
+ """Set the transient sink that advances a progress-aware deadline.
486
+
487
+ Only bounded provider events call this sink. Reasoning text is
488
+ intentionally excluded, so repeatedly emitting thoughts cannot keep
489
+ an invocation alive. The host still applies an absolute maximum.
490
+ """
491
+ self._deadline_progress_callback = callback
492
+
483
493
  def available(self) -> bool:
484
494
  return self.provider.command("--version").returncode == 0
485
495
 
@@ -855,6 +865,7 @@ class CodexCliClient:
855
865
  and self._runtime_metadata_callback is None
856
866
  and self._workspace_progress_callback is None
857
867
  and self._handoff_deadline_callback is None
868
+ and self._deadline_progress_callback is None
858
869
  ):
859
870
  return self.provider.invoke(root, command, environment=environment)
860
871
  process = self.provider.spawn_invocation(root, command, environment=environment)
@@ -902,6 +913,8 @@ class CodexCliClient:
902
913
  self.last_execution_metadata.update(progress)
903
914
  if progress != last_workspace_progress:
904
915
  self._workspace_progress_callback(dict(self.last_execution_metadata))
916
+ if self._deadline_progress_callback is not None:
917
+ self._deadline_progress_callback()
905
918
  last_workspace_progress = progress
906
919
  observed_metadata = extract_codex_runtime_metadata(line)
907
920
  if len(observed_metadata) > 1:
@@ -914,6 +927,17 @@ class CodexCliClient:
914
927
  except json.JSONDecodeError:
915
928
  activity = None
916
929
  event = None
930
+ if (
931
+ self._deadline_progress_callback is not None
932
+ and isinstance(event, dict)
933
+ and event.get("type") in {"item.started", "item.updated", "item.completed"}
934
+ and isinstance(event.get("item"), dict)
935
+ and event["item"].get("type") in {
936
+ "command_execution", "file_change", "web_search",
937
+ "mcp_tool_call", "agent_message",
938
+ }
939
+ ):
940
+ self._deadline_progress_callback()
917
941
  if activity is not None and self._activity_callback is not None:
918
942
  self._activity_callback(activity)
919
943
  transient_action = project_codex_live_action_name(event)
@@ -1777,18 +1777,31 @@ class EngineeringRunner:
1777
1777
  command_callback(command_boundary)
1778
1778
  invocation_started = datetime.now(timezone.utc).isoformat()
1779
1779
  set_handoff_deadline = getattr(self.agent, "set_handoff_deadline_callback", None)
1780
+ set_deadline_progress = getattr(self.agent, "set_deadline_progress_callback", None)
1780
1781
  deadline_started = time.monotonic()
1782
+ deadline_last_progress = deadline_started
1781
1783
  timeout = agent_timeout(
1782
1784
  phase=state.phase, repair=repair, quality=quality,
1783
1785
  local_validation=local_validation,
1784
1786
  )
1785
- if callable(set_handoff_deadline):
1786
- # Every managed provider action has a host-owned maximum. The
1787
- # client terminates the whole invocation process group when it
1788
- # expires, so an inherited stdout pipe cannot strand the worker.
1789
- set_handoff_deadline(
1790
- lambda: time.monotonic() - deadline_started >= timeout.seconds
1787
+ def record_deadline_progress() -> None:
1788
+ nonlocal deadline_last_progress
1789
+ deadline_last_progress = time.monotonic()
1790
+ def provider_deadline_expired() -> bool:
1791
+ now = time.monotonic()
1792
+ return (
1793
+ now - deadline_started >= timeout.maximum_seconds
1794
+ or now - deadline_last_progress >= timeout.seconds
1791
1795
  )
1796
+ if callable(set_deadline_progress):
1797
+ set_deadline_progress(record_deadline_progress)
1798
+ if callable(set_handoff_deadline):
1799
+ # Productive work may continue beyond the inactivity boundary,
1800
+ # while the immutable absolute maximum still caps autonomous
1801
+ # authority. The client terminates the whole invocation process
1802
+ # group when either boundary expires, so an inherited stdout pipe
1803
+ # cannot strand the worker.
1804
+ set_handoff_deadline(provider_deadline_expired)
1792
1805
  prior_validation_run_id = os.environ.get("ENGINEERING_PLATFORM_VALIDATION_RUN_ID")
1793
1806
  os.environ["ENGINEERING_PLATFORM_VALIDATION_RUN_ID"] = state.run_id
1794
1807
  try:
@@ -1829,8 +1842,10 @@ class EngineeringRunner:
1829
1842
  if repair or state.transaction_kind == "FINALIZATION" or state.phase.upper() == "FINALIZE_AGENT":
1830
1843
  raise
1831
1844
  raise CodexInvocationError(
1832
- f"Provider action exceeded the {timeout.seconds // 60}-minute host-owned deadline.",
1833
- "The provider invocation was stopped after its configured workflow deadline.",
1845
+ "Provider action exceeded its progress-aware host-owned deadline "
1846
+ f"({timeout.seconds // 60} minutes without observable progress or "
1847
+ f"{timeout.maximum_seconds // 60} minutes total).",
1848
+ "The provider invocation was stopped after its configured inactivity or absolute workflow boundary.",
1834
1849
  next_action="inspect_codex_cli",
1835
1850
  terminal_condition="provider_invocation_timeout",
1836
1851
  ) from error
@@ -1907,6 +1922,8 @@ class EngineeringRunner:
1907
1922
  process_callback(None)
1908
1923
  if callable(set_handoff_deadline):
1909
1924
  set_handoff_deadline(None)
1925
+ if callable(set_deadline_progress):
1926
+ set_deadline_progress(None)
1910
1927
  durable_recovery = self._recovery_state(state.run_id)
1911
1928
  replacement_id = (
1912
1929
  durable_recovery.get("replacement_invocation_id")
@@ -1,32 +1,42 @@
1
1
  """Versioned, host-owned time limits for bounded provider work.
2
2
 
3
- The limits are deliberately not dashboard preferences. They cap autonomous
4
- provider authority and keep a provider process that leaves its output open
5
- from owning a project queue indefinitely.
3
+ The limits are deliberately not dashboard preferences. Primary provider
4
+ actions use both an inactivity limit and an absolute maximum. Observable
5
+ command, file, tool, web, or terminal-result progress may extend an action
6
+ beyond the inactivity limit, but never beyond the absolute maximum. This
7
+ keeps productive work alive without allowing output spam or an inherited open
8
+ pipe to own a project queue indefinitely.
6
9
  """
7
10
  from __future__ import annotations
8
11
 
9
12
  from dataclasses import dataclass
10
13
 
11
14
 
12
- POLICY_VERSION = "provider-timeouts-v1"
15
+ POLICY_VERSION = "provider-timeouts-v2"
13
16
 
14
17
 
15
18
  @dataclass(frozen=True)
16
19
  class ProviderTimeout:
17
- """One named execution boundary and its immutable maximum duration."""
20
+ """One named progress-aware execution boundary."""
18
21
 
19
22
  key: str
23
+ # Existing consumers use ``seconds`` for the visible per-action limit. In
24
+ # v2 it is explicitly the maximum interval without observable progress.
20
25
  seconds: int
26
+ maximum_seconds: int
21
27
 
28
+ def __post_init__(self) -> None:
29
+ if self.seconds <= 0 or self.maximum_seconds < self.seconds:
30
+ raise ValueError("provider timeout boundaries are invalid")
22
31
 
23
- SPECIALIST_REVIEW = ProviderTimeout("specialist_review", 5 * 60)
24
- IMPLEMENTATION = ProviderTimeout("implementation", 15 * 60)
25
- LOCAL_REPOSITORY_VALIDATION = ProviderTimeout("local_repository_validation", 15 * 60)
26
- AUTONOMOUS_QUALITY_CONTROL = ProviderTimeout("autonomous_quality_control", 10 * 60)
27
- REPAIR = ProviderTimeout("repair", 15 * 60)
28
- FINALIZATION = ProviderTimeout("finalization", 15 * 60)
29
- END_RECONCILIATION = ProviderTimeout("end_reconciliation", 10 * 60)
32
+
33
+ SPECIALIST_REVIEW = ProviderTimeout("specialist_review", 5 * 60, 5 * 60)
34
+ IMPLEMENTATION = ProviderTimeout("implementation", 15 * 60, 45 * 60)
35
+ LOCAL_REPOSITORY_VALIDATION = ProviderTimeout("local_repository_validation", 15 * 60, 45 * 60)
36
+ AUTONOMOUS_QUALITY_CONTROL = ProviderTimeout("autonomous_quality_control", 10 * 60, 30 * 60)
37
+ REPAIR = ProviderTimeout("repair", 15 * 60, 45 * 60)
38
+ FINALIZATION = ProviderTimeout("finalization", 15 * 60, 45 * 60)
39
+ END_RECONCILIATION = ProviderTimeout("end_reconciliation", 10 * 60, 30 * 60)
30
40
 
31
41
  WORKFLOW_TIMEOUTS = (
32
42
  SPECIALIST_REVIEW,
@@ -0,0 +1,174 @@
1
+ """Owner-operated recovery for one abandoned Managed execution checkout."""
2
+ from __future__ import annotations
3
+
4
+ from datetime import datetime, timezone
5
+ from hashlib import sha256
6
+ import json
7
+ import os
8
+ from pathlib import Path, PurePosixPath
9
+ import re
10
+ import shutil
11
+ import sqlite3
12
+ import tempfile
13
+
14
+ from .execution_errors import RunnerError
15
+ from .execution_repository import SubprocessRepositoryClient
16
+ from .local_repository_binding import resolve_execution_repository
17
+ from .providers import GitProvider
18
+
19
+
20
+ class ManagedWorkspaceRecoveryError(ValueError):
21
+ """Stable fail-closed error for abandoned-workspace recovery."""
22
+
23
+
24
+ _OPERATION = re.compile(r"[a-z0-9][a-z0-9-]{7,95}")
25
+ _BRANCH = re.compile(r"(?!main$)[A-Za-z0-9][A-Za-z0-9._/-]{0,127}")
26
+ _SHA = re.compile(r"[0-9a-f]{40}")
27
+ _MAX_FILES = 2048
28
+ _MAX_BYTES = 64 * 1024 * 1024
29
+
30
+
31
+ def _now() -> str:
32
+ return datetime.now(timezone.utc).isoformat()
33
+
34
+
35
+ def _digest(path: Path) -> str:
36
+ value = sha256()
37
+ with path.open("rb") as stream:
38
+ for chunk in iter(lambda: stream.read(1024 * 1024), b""):
39
+ value.update(chunk)
40
+ return "sha256:" + value.hexdigest()
41
+
42
+
43
+ def _command(provider: GitProvider, root: Path, *arguments: str) -> str:
44
+ try:
45
+ return provider.command(root, "git", *arguments)
46
+ except RuntimeError as error:
47
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_GIT_FAILED") from error
48
+
49
+
50
+ def _relative_untracked(provider: GitProvider, root: Path) -> tuple[Path, ...]:
51
+ raw = _command(provider, root, "ls-files", "--others", "--exclude-standard", "-z")
52
+ values = tuple(value for value in raw.split("\0") if value)
53
+ if len(values) > _MAX_FILES:
54
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_TOO_MANY_FILES")
55
+ paths: list[Path] = []
56
+ total = 0
57
+ for value in values:
58
+ posix = PurePosixPath(value)
59
+ if posix.is_absolute() or not posix.parts or any(part in {"", ".", ".."} for part in posix.parts):
60
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_PATH_INVALID")
61
+ source = root.joinpath(*posix.parts)
62
+ try:
63
+ metadata = source.lstat()
64
+ except OSError as error:
65
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_SOURCE_UNAVAILABLE") from error
66
+ if not source.is_file() or source.is_symlink():
67
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_SOURCE_UNSUPPORTED")
68
+ total += metadata.st_size
69
+ if total > _MAX_BYTES:
70
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_TOO_LARGE")
71
+ paths.append(source)
72
+ return tuple(paths)
73
+
74
+
75
+ def _write_recovery_backup(*, root: Path, destination: Path, operation_id: str,
76
+ project_id: str, repository_id: str, branch: str,
77
+ head_sha: str, provider: GitProvider) -> dict[str, object]:
78
+ destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
79
+ if destination.exists():
80
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_DESTINATION_EXISTS")
81
+ staging = Path(tempfile.mkdtemp(prefix=f".{operation_id}-", dir=destination.parent))
82
+ os.chmod(staging, 0o700)
83
+ try:
84
+ patch = staging / "tracked.patch"
85
+ patch.write_text(_command(provider, root, "diff", "--binary", "--full-index", "HEAD"), encoding="utf-8")
86
+ patch.chmod(0o600)
87
+ untracked_root = staging / "untracked"
88
+ untracked_root.mkdir(mode=0o700)
89
+ files: list[dict[str, object]] = []
90
+ for source in _relative_untracked(provider, root):
91
+ relative = source.relative_to(root)
92
+ target = untracked_root / relative
93
+ target.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
94
+ shutil.copyfile(source, target, follow_symlinks=False)
95
+ target.chmod(0o600)
96
+ if _digest(source) != _digest(target):
97
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_BACKUP_MISMATCH")
98
+ files.append({"path": relative.as_posix(), "size": target.stat().st_size,
99
+ "sha256": _digest(target)})
100
+ manifest: dict[str, object] = {
101
+ "contract_version": "managed-workspace-recovery/v1",
102
+ "operation_id": operation_id, "project_id": project_id,
103
+ "repository_id": repository_id, "branch": branch, "head_sha": head_sha,
104
+ "created_at": _now(), "tracked_patch": {"path": "tracked.patch", "sha256": _digest(patch)},
105
+ "untracked_files": files,
106
+ }
107
+ manifest_path = staging / "manifest.json"
108
+ manifest_path.write_text(json.dumps(manifest, sort_keys=True, indent=2) + "\n", encoding="utf-8")
109
+ manifest_path.chmod(0o600)
110
+ os.replace(staging, destination)
111
+ return manifest
112
+ except Exception:
113
+ shutil.rmtree(staging, ignore_errors=True)
114
+ raise
115
+
116
+
117
+ def recover(*, connection: sqlite3.Connection, data_root: Path, project_id: str,
118
+ repository_id: str, operation_id: str, expected_branch: str,
119
+ expected_head: str, backup_root: Path,
120
+ provider: GitProvider | None = None) -> dict[str, object]:
121
+ """Preserve abandoned WIP, restore synchronized main, and remove its local branch."""
122
+ if _OPERATION.fullmatch(operation_id) is None:
123
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_OPERATION_INVALID")
124
+ if _BRANCH.fullmatch(expected_branch) is None or ".." in expected_branch or expected_branch.endswith("/"):
125
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_BRANCH_INVALID")
126
+ if _SHA.fullmatch(expected_head) is None:
127
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_HEAD_INVALID")
128
+ try:
129
+ backup_root = backup_root.expanduser().resolve()
130
+ data_root = data_root.resolve()
131
+ except OSError as error:
132
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_PATH_UNAVAILABLE") from error
133
+ if backup_root == data_root or backup_root.is_relative_to(data_root):
134
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_BACKUP_UNSAFE")
135
+ active = connection.execute(
136
+ "SELECT COUNT(*) FROM execution_run_leases WHERE lease_state='ACTIVE'"
137
+ ).fetchone()
138
+ if active is None or int(active[0]) != 0:
139
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_ACTIVE_LEASE")
140
+ binding = resolve_execution_repository(
141
+ connection, project_id=project_id, repository_id=repository_id, data_root=data_root,
142
+ )
143
+ root = binding.local_root
144
+ git = provider or GitProvider()
145
+ client = SubprocessRepositoryClient(git)
146
+ before = client.inspect(root)
147
+ if (before.branch != expected_branch or before.head_sha != expected_head
148
+ or before.repository != client.trusted_origin_identity(root)
149
+ or client.workspace_operation_active(root)):
150
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_EXPECTATION_MISMATCH")
151
+ destination = backup_root / operation_id
152
+ manifest = _write_recovery_backup(
153
+ root=root, destination=destination, operation_id=operation_id,
154
+ project_id=project_id, repository_id=repository_id, branch=expected_branch,
155
+ head_sha=expected_head, provider=git,
156
+ )
157
+ _command(git, root, "reset", "--hard", expected_head)
158
+ _command(git, root, "clean", "-fd")
159
+ client.synchronize_main(root)
160
+ if git.execute(root, "git", "show-ref", "--verify", "--quiet", f"refs/heads/{expected_branch}").returncode == 0:
161
+ _command(git, root, "branch", "-D", expected_branch)
162
+ after = client.inspect(root)
163
+ if (after.branch != "main" or not after.clean or not after.main_contains_head
164
+ or after.repository != before.repository):
165
+ raise ManagedWorkspaceRecoveryError("MANAGED_WORKSPACE_RECOVERY_RESULT_UNCERTAIN")
166
+ receipt = {
167
+ **manifest, "completed_at": _now(), "result": "RECOVERED",
168
+ "backup_path": str(destination), "final_branch": after.branch,
169
+ "final_head_sha": after.head_sha, "final_clean": after.clean,
170
+ }
171
+ receipt_path = destination / "receipt.json"
172
+ receipt_path.write_text(json.dumps(receipt, sort_keys=True, indent=2) + "\n", encoding="utf-8")
173
+ receipt_path.chmod(0o600)
174
+ return receipt
@@ -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.95"
15
+ CURRENT_PLATFORM_VERSION = "2.3.97"
16
16
  MANIFEST_FIELDS = frozenset(
17
17
  {
18
18
  "platform_version",
@@ -78,6 +78,7 @@ from . import storage
78
78
  from . import telemetry_export
79
79
  from . import managed_codex_runtime
80
80
  from . import managed_workspace_readiness
81
+ from . import managed_workspace_recovery
81
82
  from . import provider_readiness
82
83
  from .platform_components import (
83
84
  PLATFORM_COMPONENT_BY_ID,
@@ -7544,7 +7545,7 @@ def health(data_root: Path) -> dict[str, object]:
7544
7545
 
7545
7546
  def build_parser() -> argparse.ArgumentParser:
7546
7547
  parser = argparse.ArgumentParser(prog="engineering-platform-server", description="Manage the standalone Engineering Platform Server foundation")
7547
- parser.add_argument("command", choices=("init", "start", "serve", "stop", "status", "health", "operational-diagnose", "operational-qualify", "operational-readback", "operational-update-assess", "operational-inventory", "system-service-inventory", "legacy-adoption-inspect", "legacy-adoption-authorize", "installation-update-plan", "installation-update-prepare", "installation-update-admit", "installation-update-apply", "installation-update-resume", "installation-update-status", "owner-consumer-readback", "owner-credential-recover", "owner-credential-recovery-adopt-peer-configuration", "owner-credential-recovery-status", "service-install", "service-uninstall", "relay-install", "relay-uninstall", "pairing-create", "agent-status", "agent-revoke", "agent-reset", "topology", "submission-diagnose", "bootstrap-topology", "register-topology", "provision-declaration", "issue-consumer-credential", "issue-development-consumer-credential", "grant-operator-capability", "revoke-operator-capability", "inspect-assurance-target", "select-assurance-target", "revoke-assurance-target", "reserve-merge-delegation", "activate-merge-delegation", "revoke-merge-delegation", "bind-repository", "rebind-repository", "unbind-repository", "resolve-repository", "register-producer-binding", "list-producer-bindings", "deactivate-producer-binding"))
7548
+ parser.add_argument("command", choices=("init", "start", "serve", "stop", "status", "health", "operational-diagnose", "operational-qualify", "operational-readback", "operational-update-assess", "operational-inventory", "system-service-inventory", "legacy-adoption-inspect", "legacy-adoption-authorize", "installation-update-plan", "installation-update-prepare", "installation-update-admit", "installation-update-apply", "installation-update-resume", "installation-update-status", "owner-consumer-readback", "owner-credential-recover", "owner-credential-recovery-adopt-peer-configuration", "owner-credential-recovery-status", "service-install", "service-uninstall", "relay-install", "relay-uninstall", "pairing-create", "agent-status", "agent-revoke", "agent-reset", "topology", "submission-diagnose", "bootstrap-topology", "register-topology", "provision-declaration", "issue-consumer-credential", "issue-development-consumer-credential", "grant-operator-capability", "revoke-operator-capability", "inspect-assurance-target", "select-assurance-target", "revoke-assurance-target", "reserve-merge-delegation", "activate-merge-delegation", "revoke-merge-delegation", "bind-repository", "rebind-repository", "unbind-repository", "resolve-repository", "recover-managed-workspace", "register-producer-binding", "list-producer-bindings", "deactivate-producer-binding"))
7548
7549
  parser.add_argument("--data-root", type=Path, default=default_data_root())
7549
7550
  parser.add_argument("--runtime-profile", choices=("operational", "development"), default="operational")
7550
7551
  parser.add_argument("--development-venv", type=Path)
@@ -7565,6 +7566,9 @@ def build_parser() -> argparse.ArgumentParser:
7565
7566
  parser.add_argument("--reason")
7566
7567
  parser.add_argument("--capability", choices=("QUEUE_HOLD_RESUME", "QUEUE_DECLINE"))
7567
7568
  parser.add_argument("--operation-id")
7569
+ parser.add_argument("--expected-branch")
7570
+ parser.add_argument("--expected-head")
7571
+ parser.add_argument("--backup-root", type=Path)
7568
7572
  parser.add_argument("--delegation-id")
7569
7573
  parser.add_argument("--mission-id")
7570
7574
  parser.add_argument("--mission-revision")
@@ -8593,6 +8597,27 @@ def main(argv: list[str] | None = None) -> int:
8593
8597
  target.write_text(json.dumps(declaration, sort_keys=True, indent=2) + "\n", encoding="utf-8")
8594
8598
  target.chmod(0o600)
8595
8599
  result = {"project_id": args.project_id, "repository_id": args.repository_id, "path": str(target), "result": "PROVISIONED"}
8600
+ elif args.command == "recover-managed-workspace":
8601
+ if not all((args.project_id, args.repository_id, args.operation_id,
8602
+ args.expected_branch, args.expected_head)) or args.backup_root is None:
8603
+ raise ServerConfigurationError(
8604
+ "Managed workspace recovery requires project, repository, operation, expected branch/head and backup root."
8605
+ )
8606
+ if status(args.data_root)["running"]:
8607
+ raise ServerConfigurationError("MANAGED_WORKSPACE_RECOVERY_SERVER_ACTIVE")
8608
+ initialize(args.data_root)
8609
+ try:
8610
+ from .platform_admin import require_installation_owner
8611
+ require_installation_owner(args.data_root)
8612
+ with storage.sqlite_connection(args.data_root / SERVER_DATABASE_FILENAME) as connection:
8613
+ result = managed_workspace_recovery.recover(
8614
+ connection=connection, data_root=args.data_root,
8615
+ project_id=args.project_id, repository_id=args.repository_id,
8616
+ operation_id=args.operation_id, expected_branch=args.expected_branch,
8617
+ expected_head=args.expected_head, backup_root=args.backup_root,
8618
+ )
8619
+ except PermissionError as error:
8620
+ raise ServerConfigurationError("PLATFORM_ADMIN_FORBIDDEN") from error
8596
8621
  elif args.command in {"bind-repository", "rebind-repository", "unbind-repository", "resolve-repository"}:
8597
8622
  if not args.project_id or not args.repository_id:
8598
8623
  raise ServerConfigurationError("--project-id and --repository-id are required for local binding commands.")
@@ -8637,6 +8662,7 @@ def main(argv: list[str] | None = None) -> int:
8637
8662
  server_service.ServerServiceError,
8638
8663
  development_profile.DevelopmentProfileError,
8639
8664
  local_repository_binding.LocalRepositoryBindingError,
8665
+ managed_workspace_recovery.ManagedWorkspaceRecoveryError,
8640
8666
  external_producer_binding.ProducerBindingError) as error:
8641
8667
  print(json.dumps({"error": str(error), "ready": False}, sort_keys=True))
8642
8668
  return 2
@@ -2019,7 +2019,7 @@ def render_console_document(
2019
2019
  <div class="card" id="technicalDiagnosticsCard"><strong id="technicalDiagnosticsTitle" data-i18n="technical.diagnostics"></strong><p id="diag"></p></div>
2020
2020
  </div></details>
2021
2021
  <details class="card card--context workspace-card" id="workspaceCard" data-testid="engineering-workspace"><summary><strong data-i18n="section.workspace"></strong></summary><p class="field"><span class="label" data-workspace-label="workspace.name" data-i18n="workspace.name"></span><span>$WORKSPACE_ID</span></p><div class="field"><span class="label" data-workspace-label="ui.workspace_location" data-i18n="ui.workspace_location"></span><pre>$WORKSPACE_LOCATION</pre></div><p class="field" id="workspaceFreeDiskSpace"><span class="label" data-workspace-label="workspace.free_disk_space" data-i18n="workspace.free_disk_space"></span><span>$WORKSPACE_FREE_DISK_SPACE</span></p><p class="field"><span class="label" data-workspace-label="detail.tracked_files" data-i18n="detail.tracked_files"></span><span>$TRACKED_FILES</span></p><p class="field"><span class="label" data-workspace-label="workspace.current_branch" data-i18n="workspace.current_branch"></span><code id="workspaceBranch">$WORKSPACE_BRANCH</code></p><p class="field"><span class="label" data-workspace-label="workspace.current_commit" data-i18n="workspace.current_commit"></span><code id="workspaceCommit">$WORKSPACE_COMMIT</code></p><p class="field" id="workspaceOriginMain" $ORIGIN_MAIN_HIDDEN><span class="label" data-workspace-label="workspace.origin_main_commit" data-i18n="workspace.origin_main_commit"></span><code id="workspaceOriginMainCommit">$ORIGIN_MAIN_COMMIT</code></p>$WORKSPACE_OPEN_PULL_REQUESTS<div class="workspace-branch-actions"><button class="workspace-branch-main" id="workspaceBranchMain" type="button" $WORKSPACE_MAIN_ACTION_HIDDEN data-i18n="workspace.branch_main_action"></button></div></details>
2022
- <details class="card card--context workspace-card configuration-card" id="configuration" data-testid="dashboard-configuration"><summary><strong data-i18n="section.configuration"></strong></summary><p class="category-description" data-i18n="description.configuration"></p><section class="configuration-server-settings" id="configurationServerSettings" aria-labelledby="configurationServerSettingsTitle"><h2 id="configurationServerSettingsTitle" data-i18n="configuration.server_settings"></h2><div class="configuration-controls"><label for="configurationInboxScanInterval"><span data-i18n="configuration.inbox_scan_interval"></span><select id="configurationInboxScanInterval"><option value="5" data-i18n="configuration.seconds_5"></option><option value="15" data-i18n="configuration.seconds_15"></option><option value="30" data-i18n="configuration.seconds_30"></option><option value="60" data-i18n="configuration.seconds_60"></option></select></label><label for="configurationOpenPrInterval"><span data-i18n="configuration.open_pr_interval"></span><select id="configurationOpenPrInterval"><option value="30" data-i18n="configuration.seconds_30"></option><option value="60" data-i18n="configuration.seconds_60"></option></select></label></div></section><div class="configuration-controls"><label for="configurationLogRetention"><span data-i18n="configuration.log_retention"></span><select id="configurationLogRetention"><option value="30"></option><option value="60"></option><option value="90"></option><option value="120"></option><option value="180"></option><option value="360"></option></select></label><label for="configurationLogLevel"><span data-i18n="configuration.log_level"></span><select id="configurationLogLevel"><option value="INFO" data-i18n="filter.info"></option><option value="DEBUG" data-i18n="filter.debug"></option></select></label><label for="configurationDashboardStreamInterval"><span data-i18n="configuration.dashboard_stream_interval"></span><select id="configurationDashboardStreamInterval"><option value="1"></option><option value="2"></option><option value="3"></option><option value="4"></option><option value="5"></option><option value="6"></option><option value="7"></option><option value="8"></option><option value="9"></option><option value="10"></option></select></label><label for="configurationPlatformHealthInterval"><span data-i18n="configuration.platform_health_interval"></span><select id="configurationPlatformHealthInterval"><option value="5" data-i18n="configuration.seconds_5"></option><option value="15" data-i18n="configuration.seconds_15"></option><option value="30" data-i18n="configuration.seconds_30"></option><option value="60" data-i18n="configuration.seconds_60"></option></select></label><label for="configurationComponentDetailsInterval"><span data-i18n="configuration.component_details_interval"></span><select id="configurationComponentDetailsInterval"><option value="5" data-i18n="configuration.seconds_5"></option><option value="15" data-i18n="configuration.seconds_15"></option><option value="30" data-i18n="configuration.seconds_30"></option><option value="60" data-i18n="configuration.seconds_60"></option></select></label><p id="configurationStatus" role="status" aria-live="polite"></p></div><section class="configuration-readonly-settings" aria-labelledby="configurationReadonlySettingsTitle"><h2 id="configurationReadonlySettingsTitle" data-i18n="configuration.readonly_platform_settings"></h2><p class="field configuration-field"><span class="label"><span data-i18n="configuration.operator_merge_interval"></span><span class="configuration-info" role="img" tabindex="0" data-i18n-title="configuration.operator_merge_interval_help" data-i18n-aria-label="configuration.operator_merge_interval_help">i</span></span><span data-i18n="configuration.seconds_60"></span></p><p class="field configuration-field"><span class="label"><span data-i18n="configuration.required_checks_interval"></span><span class="configuration-info" role="img" tabindex="0" data-i18n-title="configuration.required_checks_interval_help" data-i18n-aria-label="configuration.required_checks_interval_help">i</span></span><span data-i18n="configuration.seconds_15"></span></p><p class="field configuration-field"><span class="label"><span data-i18n="configuration.lease_heartbeat_interval"></span><span class="configuration-info" role="img" tabindex="0" data-i18n-title="configuration.lease_heartbeat_interval_help" data-i18n-aria-label="configuration.lease_heartbeat_interval_help">i</span></span><span data-i18n="configuration.seconds_15"></span></p><p class="field configuration-field"><span class="label"><span data-i18n="configuration.lease_timeout"></span><span class="configuration-info" role="img" tabindex="0" data-i18n-title="configuration.lease_timeout_help" data-i18n-aria-label="configuration.lease_timeout_help">i</span></span><span data-i18n="configuration.seconds_90"></span></p><p class="field configuration-field"><span class="label"><span data-i18n="configuration.github_retry_backoff"></span><span class="configuration-info" role="img" tabindex="0" data-i18n-title="configuration.github_retry_backoff_help" data-i18n-aria-label="configuration.github_retry_backoff_help">i</span></span><span data-i18n="configuration.github_retry_backoff_value"></span></p></section><section class="configuration-timeout-policy" aria-labelledby="configurationTimeoutPolicyTitle"><h2 id="configurationTimeoutPolicyTitle" data-i18n="configuration.timeout_policy"></h2><p data-i18n="configuration.timeout_policy_description"></p><p class="field configuration-field"><span class="label" data-i18n="configuration.timeout.specialist_review"></span><span data-i18n="configuration.minutes_5"></span></p><p class="field configuration-field"><span class="label" data-i18n="configuration.timeout.implementation"></span><span data-i18n="configuration.minutes_15"></span></p><p class="field configuration-field"><span class="label" data-i18n="configuration.timeout.local_repository_validation"></span><span data-i18n="configuration.minutes_15"></span></p><p class="field configuration-field"><span class="label" data-i18n="configuration.timeout.autonomous_quality_control"></span><span data-i18n="configuration.minutes_10"></span></p><p class="field configuration-field"><span class="label" data-i18n="configuration.timeout.repair"></span><span data-i18n="configuration.minutes_15"></span></p><p class="field configuration-field"><span class="label" data-i18n="configuration.timeout.finalization"></span><span data-i18n="configuration.minutes_15"></span></p><p class="field configuration-field"><span class="label" data-i18n="configuration.timeout.end_reconciliation"></span><span data-i18n="configuration.minutes_10"></span></p></section></details>
2022
+ <details class="card card--context workspace-card configuration-card" id="configuration" data-testid="dashboard-configuration"><summary><strong data-i18n="section.configuration"></strong></summary><p class="category-description" data-i18n="description.configuration"></p><section class="configuration-server-settings" id="configurationServerSettings" aria-labelledby="configurationServerSettingsTitle"><h2 id="configurationServerSettingsTitle" data-i18n="configuration.server_settings"></h2><div class="configuration-controls"><label for="configurationInboxScanInterval"><span data-i18n="configuration.inbox_scan_interval"></span><select id="configurationInboxScanInterval"><option value="5" data-i18n="configuration.seconds_5"></option><option value="15" data-i18n="configuration.seconds_15"></option><option value="30" data-i18n="configuration.seconds_30"></option><option value="60" data-i18n="configuration.seconds_60"></option></select></label><label for="configurationOpenPrInterval"><span data-i18n="configuration.open_pr_interval"></span><select id="configurationOpenPrInterval"><option value="30" data-i18n="configuration.seconds_30"></option><option value="60" data-i18n="configuration.seconds_60"></option></select></label></div></section><div class="configuration-controls"><label for="configurationLogRetention"><span data-i18n="configuration.log_retention"></span><select id="configurationLogRetention"><option value="30"></option><option value="60"></option><option value="90"></option><option value="120"></option><option value="180"></option><option value="360"></option></select></label><label for="configurationLogLevel"><span data-i18n="configuration.log_level"></span><select id="configurationLogLevel"><option value="INFO" data-i18n="filter.info"></option><option value="DEBUG" data-i18n="filter.debug"></option></select></label><label for="configurationDashboardStreamInterval"><span data-i18n="configuration.dashboard_stream_interval"></span><select id="configurationDashboardStreamInterval"><option value="1"></option><option value="2"></option><option value="3"></option><option value="4"></option><option value="5"></option><option value="6"></option><option value="7"></option><option value="8"></option><option value="9"></option><option value="10"></option></select></label><label for="configurationPlatformHealthInterval"><span data-i18n="configuration.platform_health_interval"></span><select id="configurationPlatformHealthInterval"><option value="5" data-i18n="configuration.seconds_5"></option><option value="15" data-i18n="configuration.seconds_15"></option><option value="30" data-i18n="configuration.seconds_30"></option><option value="60" data-i18n="configuration.seconds_60"></option></select></label><label for="configurationComponentDetailsInterval"><span data-i18n="configuration.component_details_interval"></span><select id="configurationComponentDetailsInterval"><option value="5" data-i18n="configuration.seconds_5"></option><option value="15" data-i18n="configuration.seconds_15"></option><option value="30" data-i18n="configuration.seconds_30"></option><option value="60" data-i18n="configuration.seconds_60"></option></select></label><p id="configurationStatus" role="status" aria-live="polite"></p></div><section class="configuration-readonly-settings" aria-labelledby="configurationReadonlySettingsTitle"><h2 id="configurationReadonlySettingsTitle" data-i18n="configuration.readonly_platform_settings"></h2><p class="field configuration-field"><span class="label"><span data-i18n="configuration.operator_merge_interval"></span><span class="configuration-info" role="img" tabindex="0" data-i18n-title="configuration.operator_merge_interval_help" data-i18n-aria-label="configuration.operator_merge_interval_help">i</span></span><span data-i18n="configuration.seconds_60"></span></p><p class="field configuration-field"><span class="label"><span data-i18n="configuration.required_checks_interval"></span><span class="configuration-info" role="img" tabindex="0" data-i18n-title="configuration.required_checks_interval_help" data-i18n-aria-label="configuration.required_checks_interval_help">i</span></span><span data-i18n="configuration.seconds_15"></span></p><p class="field configuration-field"><span class="label"><span data-i18n="configuration.lease_heartbeat_interval"></span><span class="configuration-info" role="img" tabindex="0" data-i18n-title="configuration.lease_heartbeat_interval_help" data-i18n-aria-label="configuration.lease_heartbeat_interval_help">i</span></span><span data-i18n="configuration.seconds_15"></span></p><p class="field configuration-field"><span class="label"><span data-i18n="configuration.lease_timeout"></span><span class="configuration-info" role="img" tabindex="0" data-i18n-title="configuration.lease_timeout_help" data-i18n-aria-label="configuration.lease_timeout_help">i</span></span><span data-i18n="configuration.seconds_90"></span></p><p class="field configuration-field"><span class="label"><span data-i18n="configuration.github_retry_backoff"></span><span class="configuration-info" role="img" tabindex="0" data-i18n-title="configuration.github_retry_backoff_help" data-i18n-aria-label="configuration.github_retry_backoff_help">i</span></span><span data-i18n="configuration.github_retry_backoff_value"></span></p></section><section class="configuration-timeout-policy" aria-labelledby="configurationTimeoutPolicyTitle"><h2 id="configurationTimeoutPolicyTitle" data-i18n="configuration.timeout_policy"></h2><p data-i18n="configuration.timeout_policy_description"></p><p class="field configuration-field"><span class="label" data-i18n="configuration.timeout.specialist_review"></span><span data-i18n="configuration.timeout_5_max"></span></p><p class="field configuration-field"><span class="label" data-i18n="configuration.timeout.implementation"></span><span data-i18n="configuration.timeout_15_idle_45_max"></span></p><p class="field configuration-field"><span class="label" data-i18n="configuration.timeout.local_repository_validation"></span><span data-i18n="configuration.timeout_15_idle_45_max"></span></p><p class="field configuration-field"><span class="label" data-i18n="configuration.timeout.autonomous_quality_control"></span><span data-i18n="configuration.timeout_10_idle_30_max"></span></p><p class="field configuration-field"><span class="label" data-i18n="configuration.timeout.repair"></span><span data-i18n="configuration.timeout_15_idle_45_max"></span></p><p class="field configuration-field"><span class="label" data-i18n="configuration.timeout.finalization"></span><span data-i18n="configuration.timeout_15_idle_45_max"></span></p><p class="field configuration-field"><span class="label" data-i18n="configuration.timeout.end_reconciliation"></span><span data-i18n="configuration.timeout_10_idle_30_max"></span></p></section></details>
2023
2023
  </main></div>
2024
2024
  <footer class="footer" aria-live="polite"><span class="footer__item"><span class="label" id="platformVersionLabel" data-i18n="footer.platform_name"></span> <span class="footer__version-word" id="platformVersionWord" data-i18n="footer.version_word"></span> <span id="platformVersion" data-i18n="format.loading"></span></span><span class="footer__separator" aria-hidden="true">·</span><span class="footer__item" id="lastRefresh" data-i18n="format.loading"></span><span class="footer__separator" aria-hidden="true">·</span><span class="footer__item" id="updateMode" data-i18n="format.loading"></span></footer><span id="dashboardVersion" hidden></span><span id="workerVersion" hidden></span>
2025
2025
  <script>window.ENGINEERING_PLATFORM_DASHBOARD_BUILD="$BUILD_COMMIT";</script>
@@ -3,7 +3,7 @@
3
3
  "platform": {
4
4
  "id": "engineering-platform",
5
5
  "name": "Engineering Platform",
6
- "version": "2.3.95",
6
+ "version": "2.3.97",
7
7
  "generation": 2,
8
8
  "documentation_namespace": "engineering-platform",
9
9
  "capability_registry_version": 1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-platform
3
- Version: 2.3.95
3
+ Version: 2.3.97
4
4
  Summary: Local-first Engineering Platform execution operations runtime
5
5
  Author: Peter van Tol
6
6
  License: MIT
@@ -78,6 +78,7 @@ src/engineering_platform/local_repository_binding.py
78
78
  src/engineering_platform/managed_autonomy.py
79
79
  src/engineering_platform/managed_codex_runtime.py
80
80
  src/engineering_platform/managed_workspace_readiness.py
81
+ src/engineering_platform/managed_workspace_recovery.py
81
82
  src/engineering_platform/merge_delegation.py
82
83
  src/engineering_platform/operational_installation.py
83
84
  src/engineering_platform/operational_installation_lock.py