engineering-platform 2.3.73__tar.gz → 2.3.75__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 (164) hide show
  1. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/PKG-INFO +1 -1
  2. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/pyproject.toml +1 -1
  3. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json +1 -1
  4. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json +4 -4
  5. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_host.py +24 -2
  6. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/platform_version.py +1 -1
  7. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/server.py +27 -2
  8. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/submission_service.py +63 -10
  9. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/templates/workspace-config.json +1 -1
  10. engineering_platform-2.3.75/src/engineering_platform/terminal_evidence_reconciliation.py +244 -0
  11. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform.egg-info/PKG-INFO +1 -1
  12. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform.egg-info/SOURCES.txt +1 -0
  13. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/README.md +0 -0
  14. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/setup.cfg +0 -0
  15. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/__init__.py +0 -0
  16. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/__main__.py +0 -0
  17. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/agent_state.py +0 -0
  18. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/agent_trust.py +0 -0
  19. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/assets/dashboard.css +0 -0
  20. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/assets/dashboard.js +0 -0
  21. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/assets/dashboard_locales.mjs +0 -0
  22. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/assets/dashboard_status_store.mjs +0 -0
  23. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/assets/dashboard_translation.mjs +0 -0
  24. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/assets/operations-console/apple-touch-icon-dark.png +0 -0
  25. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/assets/operations-console/apple-touch-icon-light.png +0 -0
  26. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/assets/operations-console/icon-dark.png +0 -0
  27. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/assets/operations-console/icon-light.png +0 -0
  28. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/assets/operations-console/icon-transparent.png +0 -0
  29. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/assets/operations-console/manifest.webmanifest +0 -0
  30. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/capability_preflight.py +0 -0
  31. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/capability_review.py +0 -0
  32. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/central_data_transfer.py +0 -0
  33. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/central_database.py +0 -0
  34. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/central_store_migration.py +0 -0
  35. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/codex_capacity.py +0 -0
  36. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/codex_chat.py +0 -0
  37. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/codex_observability.py +0 -0
  38. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/component_lock.py +0 -0
  39. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/component_logging.py +0 -0
  40. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/console_presentation.py +0 -0
  41. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/console_route_ownership.py +0 -0
  42. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/contracts/__init__.py +0 -0
  43. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/contracts/ep_consumer.py +0 -0
  44. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/contracts/models.py +0 -0
  45. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/contracts/projection.py +0 -0
  46. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/dashboard_browser_validation.py +0 -0
  47. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/dashboard_state.py +0 -0
  48. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/dashboard_supervisor.swift +0 -0
  49. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/dashboard_translation.py +0 -0
  50. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/dependabot_producer.py +0 -0
  51. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/development_profile.py +0 -0
  52. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/drift_diagnostics.py +0 -0
  53. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/emergency_recovery.py +0 -0
  54. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/engineering_memory.py +0 -0
  55. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/ep_consumer_credentials.py +0 -0
  56. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/evidence_projection.py +0 -0
  57. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_activity.py +0 -0
  58. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_context.py +0 -0
  59. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_errors.py +0 -0
  60. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_evidence.py +0 -0
  61. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_executor.py +0 -0
  62. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_finalization.py +0 -0
  63. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_host_evidence.py +0 -0
  64. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_lease.py +0 -0
  65. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_lifecycle.py +0 -0
  66. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_models.py +0 -0
  67. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_readiness.py +0 -0
  68. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_reporting.py +0 -0
  69. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_repository.py +0 -0
  70. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_timeout_policy.py +0 -0
  71. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_timing.py +0 -0
  72. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/execution_transaction.py +0 -0
  73. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/external_producer_binding.py +0 -0
  74. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/file_inbox.py +0 -0
  75. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/forensic_attribution.py +0 -0
  76. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/forensic_attribution_v2.py +0 -0
  77. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/forensic_delta.py +0 -0
  78. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/golden_scenario.py +0 -0
  79. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/historical_dashboard_configuration.py +0 -0
  80. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/host_admin.py +0 -0
  81. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/host_preflight.py +0 -0
  82. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/installation_relocation.py +0 -0
  83. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/installation_update_activation.py +0 -0
  84. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/installation_update_admission.py +0 -0
  85. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/installation_update_backup.py +0 -0
  86. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/installation_update_composition.py +0 -0
  87. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/installation_update_executor.py +0 -0
  88. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/installation_update_migration.py +0 -0
  89. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/installation_update_operation.py +0 -0
  90. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/installation_update_plan.py +0 -0
  91. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/installation_update_preparation.py +0 -0
  92. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/investigation_ledger.py +0 -0
  93. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/legacy_inbox_migration.py +0 -0
  94. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/legacy_installation_adoption.py +0 -0
  95. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/lifecycle_worker.py +0 -0
  96. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/live_status.py +0 -0
  97. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/local_api.py +0 -0
  98. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/local_api_keychain.py +0 -0
  99. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/local_repository_binding.py +0 -0
  100. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/managed_autonomy.py +0 -0
  101. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/managed_codex_runtime.py +0 -0
  102. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/operational_installation.py +0 -0
  103. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/operational_installation_lock.py +0 -0
  104. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/operational_installation_record.py +0 -0
  105. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/owner_credential_recovery.py +0 -0
  106. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/parity_context.py +0 -0
  107. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/parity_lifecycle_dispatcher.py +0 -0
  108. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/platform_admin.py +0 -0
  109. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/platform_api.py +0 -0
  110. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/platform_bootstrap.py +0 -0
  111. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/platform_components.py +0 -0
  112. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/pr_check_repair.py +0 -0
  113. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/pr_evidence_backfill.py +0 -0
  114. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/producer.py +0 -0
  115. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/product_installation_readback.py +0 -0
  116. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/project_agent.py +0 -0
  117. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/project_agent_service.py +0 -0
  118. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/project_topology.py +0 -0
  119. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/prompt_history.py +0 -0
  120. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/provider_context.py +0 -0
  121. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/provider_context_benchmark.py +0 -0
  122. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/provider_context_scope.py +0 -0
  123. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/provider_interruption.py +0 -0
  124. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/provider_process_identity.py +0 -0
  125. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/provider_readiness.py +0 -0
  126. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/provider_recovery.py +0 -0
  127. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/provider_usage.py +0 -0
  128. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/providers.py +0 -0
  129. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/qualification.py +0 -0
  130. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/qualification_runtime.py +0 -0
  131. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/recommendation_handoff.py +0 -0
  132. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/reconciliation_adoption.py +0 -0
  133. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/release_operation.py +0 -0
  134. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/report_analysis.py +0 -0
  135. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/repository_attachment.py +0 -0
  136. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/repository_handoff.py +0 -0
  137. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/resources.py +0 -0
  138. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/reviewer_evidence.py +0 -0
  139. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/revision_binding.py +0 -0
  140. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/schemas/producer-readback-v1.2.schema.json +0 -0
  141. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/schemas/producer-readback-v1.schema.json +0 -0
  142. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/schemas/repository-attachment.schema.json +0 -0
  143. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/server_console_services.py +0 -0
  144. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/server_relay.py +0 -0
  145. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/server_service.py +0 -0
  146. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/status_model.py +0 -0
  147. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/status_reconciliation.py +0 -0
  148. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/storage.py +0 -0
  149. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/submission_cli.py +0 -0
  150. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/submission_intake.py +0 -0
  151. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/system_installation_topology.py +0 -0
  152. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/system_server_service.py +0 -0
  153. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/telemetry.py +0 -0
  154. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/validation_identity.py +0 -0
  155. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/validation_profile.py +0 -0
  156. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/version_preparation_delivery.py +0 -0
  157. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/workspace_preflight.py +0 -0
  158. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/worktree_provenance.py +0 -0
  159. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform/worktree_tooling.py +0 -0
  160. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform.egg-info/dependency_links.txt +0 -0
  161. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform.egg-info/entry_points.txt +0 -0
  162. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/src/engineering_platform.egg-info/top_level.txt +0 -0
  163. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/tests/test_adaptive_action_sizing_design.py +0 -0
  164. {engineering_platform-2.3.73 → engineering_platform-2.3.75}/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.73
3
+ Version: 2.3.75
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.73"
7
+ version = "2.3.75"
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.73",
6
+ "version": "2.3.75",
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.73",
4
+ "dashboard_version": "2.3.75",
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.73",
9
+ "platform_version": "2.3.75",
10
10
  "report_format": 2,
11
- "runner_version": "2.3.73",
11
+ "runner_version": "2.3.75",
12
12
  "status_model": 1,
13
13
  "storage_schema": 45,
14
- "watcher_version": "2.3.73"
14
+ "watcher_version": "2.3.75"
15
15
  }
@@ -3663,6 +3663,8 @@ First implementation pull-request publication gate:
3663
3663
  action = {"IMPLEMENTATION": "IMPLEMENTATION_MERGE", "FINALIZATION": "FINALIZATION_MERGE", "RECONCILIATION": "RECONCILIATION_MERGE"}[state.transaction_kind]
3664
3664
  self._managed_action(state, action, "EXPECTED_OPERATOR_GATE", actor="operator", evidence_ref="github_merge")
3665
3665
  state = self._record_merged_evidence(state, pr, evidence)
3666
+ if state.terminal:
3667
+ return state
3666
3668
  if state.owner_authorized and state.transaction_kind == "IMPLEMENTATION":
3667
3669
  return self._start_finalization(state, pr.number)
3668
3670
  if state.owner_authorized and state.transaction_kind == "FINALIZATION":
@@ -4145,6 +4147,26 @@ First implementation pull-request publication gate:
4145
4147
  def _record_merged_evidence(
4146
4148
  self, state: TransactionState, pr: PullRequestEvidence, evidence: RepositoryEvidence
4147
4149
  ) -> TransactionState:
4150
+ expected_head = (
4151
+ (state.implementation_head_sha or state.last_verified_sha)
4152
+ if state.transaction_kind == "IMPLEMENTATION"
4153
+ else (state.finalization_head_sha or state.last_verified_sha)
4154
+ if state.transaction_kind == "FINALIZATION"
4155
+ else None
4156
+ )
4157
+ if (
4158
+ state.transaction_kind in {"IMPLEMENTATION", "FINALIZATION"}
4159
+ and isinstance(expected_head, str)
4160
+ and isinstance(pr.head_sha, str)
4161
+ and pr.head_sha != expected_head
4162
+ ):
4163
+ return self._save_terminal(
4164
+ state,
4165
+ "BLOCKED",
4166
+ "merged_pull_request_candidate_mismatch",
4167
+ "Merged pull-request evidence does not match the exact phase candidate.",
4168
+ )
4169
+ phase_head = pr.head_sha or expected_head
4148
4170
  common = {
4149
4171
  "last_verified_sha": evidence.head_sha,
4150
4172
  "latest_repository_evidence": _repository_summary(evidence),
@@ -4155,7 +4177,7 @@ First implementation pull-request publication gate:
4155
4177
  state,
4156
4178
  implementation_branch=state.branch,
4157
4179
  implementation_pull_request=pr.number,
4158
- implementation_head_sha=state.last_verified_sha,
4180
+ implementation_head_sha=phase_head,
4159
4181
  implementation_merge_commit=pr.merge_commit,
4160
4182
  **common,
4161
4183
  )
@@ -4168,7 +4190,7 @@ First implementation pull-request publication gate:
4168
4190
  state,
4169
4191
  finalization_branch=state.branch or state.finalization_branch,
4170
4192
  finalization_pull_request=pr.number,
4171
- finalization_head_sha=state.last_verified_sha,
4193
+ finalization_head_sha=phase_head,
4172
4194
  finalization_merge_commit=pr.merge_commit,
4173
4195
  **common,
4174
4196
  )
@@ -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.73"
15
+ CURRENT_PLATFORM_VERSION = "2.3.75"
16
16
  MANIFEST_FIELDS = frozenset(
17
17
  {
18
18
  "platform_version",
@@ -124,7 +124,7 @@ SERVER_CONFIGURATION_VERSION = 3
124
124
  # bootstrap is deliberately separate from the retired predecessor migration
125
125
  # machinery: it creates a clean installation only and never accepts a source
126
126
  # database path.
127
- SERVER_STORE_SCHEMA_VERSION = 66
127
+ SERVER_STORE_SCHEMA_VERSION = 67
128
128
  SERVER_ENVIRONMENT_DATA_ROOT = "EP_SERVER_DATA_ROOT"
129
129
  FILE_INBOX_DIRECTORY = "file-inbox"
130
130
  HTTP_JSON_OPENAPI_PATH = "/v1/openapi.json"
@@ -591,6 +591,7 @@ SERVER_REQUIRED_TABLES = frozenset(
591
591
  "ep_forge_planning_context_envelopes",
592
592
  "ep_execution_host_evidence",
593
593
  "ep_technical_diagnostics",
594
+ "ep_terminal_evidence_reconciliation_operations",
594
595
  }
595
596
  )
596
597
  SERVER_REQUIRED_INDEXES = frozenset(
@@ -614,6 +615,7 @@ SERVER_REQUIRED_INDEXES = frozenset(
614
615
  "ep_forge_action_context_envelopes_project_lookup",
615
616
  "ep_forge_planning_context_envelopes_project_lookup",
616
617
  "ep_technical_diagnostics_created_lookup",
618
+ "execution_artifact_records_active_terminal_run",
617
619
  }
618
620
  )
619
621
  SERVER_REQUIRED_VIEWS = frozenset({"execution_submission_run_links"})
@@ -813,6 +815,7 @@ def _install_current_schema(connection: sqlite3.Connection, identity: RuntimeIde
813
815
  _install_execution_host_evidence_schema(connection)
814
816
  _install_technical_diagnostics_schema(connection)
815
817
  owner_credential_recovery.install_schema(connection)
818
+ submission_service.install_terminal_evidence_reconciliation_schema(connection)
816
819
 
817
820
  connection.execute(
818
821
  "INSERT INTO engineering_schema_migrations(version) VALUES(?)",
@@ -1960,6 +1963,25 @@ def _migrate_schema_66(connection: sqlite3.Connection) -> None:
1960
1963
  connection.execute("UPDATE ep_installations SET schema_version=66")
1961
1964
 
1962
1965
 
1966
+ def _migrate_schema_67(connection: sqlite3.Connection) -> None:
1967
+ """Add guarded terminal-evidence projection reconciliation."""
1968
+ connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema66")
1969
+ connection.execute(
1970
+ "CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY,created_at TEXT NOT NULL,"
1971
+ "schema_version INTEGER NOT NULL CHECK(schema_version IN "
1972
+ "(41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67)))"
1973
+ )
1974
+ connection.execute(
1975
+ "INSERT INTO ep_installations SELECT instance_id,created_at,67 "
1976
+ "FROM ep_installations_schema66"
1977
+ )
1978
+ connection.execute("DROP TABLE ep_installations_schema66")
1979
+ submission_service.install_terminal_evidence_reconciliation_schema(connection)
1980
+ connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(67)")
1981
+ connection.execute("UPDATE engineering_metadata SET value='67' WHERE key='installation.schema_version'")
1982
+ connection.execute("UPDATE ep_installations SET schema_version=67")
1983
+
1984
+
1963
1985
  _SERVER_SCHEMA_UPGRADE_STEPS = (
1964
1986
  (42, _migrate_schema_42),
1965
1987
  (43, _migrate_schema_43),
@@ -1986,6 +2008,7 @@ _SERVER_SCHEMA_UPGRADE_STEPS = (
1986
2008
  (64, _migrate_schema_64),
1987
2009
  (65, _migrate_schema_65),
1988
2010
  (66, _migrate_schema_66),
2011
+ (67, _migrate_schema_67),
1989
2012
  )
1990
2013
  _SUPPORTED_SERVER_SCHEMA_VERSIONS = frozenset(
1991
2014
  range(41, SERVER_STORE_SCHEMA_VERSION + 1)
@@ -2026,6 +2049,8 @@ def validate_store(data_root: Path, identity: RuntimeIdentity) -> dict[str, obje
2026
2049
  "ep_execution_host_evidence_immutable_delete",
2027
2050
  "ep_technical_diagnostics_immutable_update",
2028
2051
  "ep_technical_diagnostics_immutable_delete",
2052
+ "ep_terminal_evidence_reconciliation_immutable_update",
2053
+ "ep_terminal_evidence_reconciliation_immutable_delete",
2029
2054
  } <= triggers and integrity == ["ok"] and metadata == {"installation.instance_id": identity.instance_id, "installation.schema_version": str(SERVER_STORE_SCHEMA_VERSION)} and installation is not None
2030
2055
  if not valid:
2031
2056
  raise ServerConfigurationError(
@@ -6174,7 +6199,7 @@ class _HealthHandler(http.server.BaseHTTPRequestHandler):
6174
6199
  self._send(503, {"error": "CENTRAL_UNAVAILABLE"})
6175
6200
  return
6176
6201
  artifact = re.fullmatch(
6177
- r"/v1/projects/([^/]+)/artifacts/((?:terminal-evidence|assurance-findings):[^/]+)",
6202
+ r"/v1/projects/([^/]+)/artifacts/((?:terminal-evidence(?:-reconciled)?|assurance-findings):[^/]+)",
6178
6203
  unquote(request.path),
6179
6204
  )
6180
6205
  if artifact:
@@ -770,6 +770,51 @@ def _findings_artifact_id(run_id: str) -> str:
770
770
  return f"assurance-findings:{run_id}"
771
771
 
772
772
 
773
+ def install_terminal_evidence_reconciliation_schema(connection: sqlite3.Connection) -> None:
774
+ """Install the one-run/one-current-projection repair authority."""
775
+ connection.executescript("""
776
+ CREATE TABLE IF NOT EXISTS ep_terminal_evidence_reconciliation_operations (
777
+ operation_id TEXT PRIMARY KEY,
778
+ run_id TEXT NOT NULL UNIQUE REFERENCES ep_execution_runs(run_id),
779
+ project_id TEXT NOT NULL REFERENCES ep_project_registrations(project_id),
780
+ source_artifact_id TEXT NOT NULL REFERENCES execution_artifact_records(artifact_id),
781
+ source_digest TEXT NOT NULL,
782
+ replacement_artifact_id TEXT NOT NULL UNIQUE REFERENCES execution_artifact_records(artifact_id),
783
+ replacement_digest TEXT NOT NULL,
784
+ reason_code TEXT NOT NULL CHECK(reason_code='MERGE_CANDIDATE_PROJECTION_V1'),
785
+ recorded_at TEXT NOT NULL
786
+ );
787
+ CREATE UNIQUE INDEX IF NOT EXISTS execution_artifact_records_active_terminal_run
788
+ ON execution_artifact_records(COALESCE(ep_run_id,run_id))
789
+ WHERE artifact_type='EP_TERMINAL_EVIDENCE'
790
+ AND projection_status='AVAILABLE'
791
+ AND COALESCE(ep_run_id,run_id) IS NOT NULL;
792
+ CREATE TRIGGER IF NOT EXISTS ep_terminal_evidence_reconciliation_immutable_update
793
+ BEFORE UPDATE ON ep_terminal_evidence_reconciliation_operations
794
+ BEGIN SELECT RAISE(ABORT, 'Terminal evidence reconciliation is immutable'); END;
795
+ CREATE TRIGGER IF NOT EXISTS ep_terminal_evidence_reconciliation_immutable_delete
796
+ BEFORE DELETE ON ep_terminal_evidence_reconciliation_operations
797
+ BEGIN SELECT RAISE(ABORT, 'Terminal evidence reconciliation is immutable'); END;
798
+ """)
799
+
800
+
801
+ def _active_terminal_artifact(
802
+ connection: sqlite3.Connection, run_id: str,
803
+ ) -> tuple[object, ...] | None:
804
+ rows = connection.execute(
805
+ """SELECT artifact_id,digest_algorithm,digest,content_type,integrity_status,storage_location
806
+ FROM execution_artifact_records
807
+ WHERE artifact_type='EP_TERMINAL_EVIDENCE'
808
+ AND projection_status='AVAILABLE'
809
+ AND (ep_run_id=? OR (ep_run_id IS NULL AND run_id=?))
810
+ ORDER BY artifact_id LIMIT 2""",
811
+ (run_id, run_id),
812
+ ).fetchall()
813
+ if len(rows) > 1:
814
+ raise SubmissionError("TERMINAL_EVIDENCE_PROJECTION_AMBIGUOUS", 500)
815
+ return tuple(rows[0]) if rows else None
816
+
817
+
773
818
  def _current_assurance(checkpoint: object) -> tuple[str, list[dict[str, object]], list[dict[str, object]]]:
774
819
  """Project final assurance from one complete current review set.
775
820
 
@@ -973,6 +1018,17 @@ def write_terminal_evidence(
973
1018
  artifact_id = _terminal_artifact_id(run_id)
974
1019
  report_id = f"report:{run_id}"
975
1020
  target = data_root / "artifacts" / "projects" / str(row[1]) / "runs" / run_id / "terminal-evidence-v1.json"
1021
+ with sqlite_connection(database) as connection:
1022
+ active_artifact = _active_terminal_artifact(connection, run_id)
1023
+ if active_artifact is not None and verify_artifact_integrity(
1024
+ repository_root, str(active_artifact[0]), central_database=database,
1025
+ artifact_root=data_root / "artifacts",
1026
+ ):
1027
+ # Reconciliation may revisit a run after its original immutable
1028
+ # projection was superseded through the guarded owning repair route.
1029
+ # Return the one currently selected, integrity-verified artifact; do
1030
+ # not reconstruct or overwrite either historical payload.
1031
+ return str(active_artifact[0])
976
1032
  if _is_retained_v13_terminal_artifact(
977
1033
  target, run_id=run_id, submission_id=str(row[0]), project_id=str(row[1]),
978
1034
  repository_id=str(row[2]),
@@ -980,10 +1036,6 @@ def write_terminal_evidence(
980
1036
  repository_root, artifact_id, central_database=database,
981
1037
  artifact_root=data_root / "artifacts",
982
1038
  ):
983
- # Reconciliation may revisit terminal runs long after a new artifact
984
- # contract ships. The older evidence remains immutable and truthful;
985
- # require its pre-existing CENTRAL digest record rather than adopting
986
- # a same-account filesystem payload as evidence.
987
1039
  return artifact_id
988
1040
  assurance_status, current_reviews, reviews = _current_assurance(checkpoint)
989
1041
  findings = [finding for review in reviews for finding in review.get("findings", [])]
@@ -1162,13 +1214,14 @@ def producer_readback(
1162
1214
  evidence["status"] = "INCOMPLETE"
1163
1215
  elif terminal:
1164
1216
  run.update(timing)
1165
- artifact = connection.execute(
1166
- """SELECT artifact_id,digest_algorithm,digest,content_type,integrity_status,storage_location
1167
- FROM execution_artifact_records WHERE artifact_id=? AND (run_id=? OR ep_run_id=?)""",
1168
- (_terminal_artifact_id(str(run_id)), run_id, run_id),
1169
- ).fetchone()
1217
+ try:
1218
+ artifact = _active_terminal_artifact(connection, str(run_id))
1219
+ except SubmissionError:
1220
+ artifact = None
1221
+ evidence["status"] = "CORRUPT"
1170
1222
  if artifact is None:
1171
- evidence["status"] = "MISSING"
1223
+ if evidence["status"] != "CORRUPT":
1224
+ evidence["status"] = "MISSING"
1172
1225
  elif artifact[1] != "sha256" or not isinstance(artifact[2], str) or len(str(artifact[2])) != 64:
1173
1226
  evidence["status"] = "CORRUPT"
1174
1227
  else:
@@ -3,7 +3,7 @@
3
3
  "platform": {
4
4
  "id": "engineering-platform",
5
5
  "name": "Engineering Platform",
6
- "version": "2.3.73",
6
+ "version": "2.3.75",
7
7
  "generation": 2,
8
8
  "documentation_namespace": "engineering-platform",
9
9
  "capability_registry_version": 1
@@ -0,0 +1,244 @@
1
+ """Guarded reconciliation of one known terminal-evidence projection defect.
2
+
3
+ The route never changes a terminal run, accepted request, assurance record or
4
+ historical artifact bytes. It may add one corrected v1.4 projection only when
5
+ the retained checkpoint and complete current assurance set prove that the
6
+ legacy merge writer projected the execution baseline as its candidate.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import hashlib
12
+ import json
13
+ from pathlib import Path
14
+ import re
15
+ import sqlite3
16
+
17
+ from . import central_database, submission_service
18
+ from .agent_state import RUN_ID_PATTERN, StateError, TransactionState
19
+ from .storage import record_artifact, sqlite_connection
20
+
21
+
22
+ _OPERATION_ID = re.compile(r"[a-z0-9][a-z0-9._:-]{7,127}\Z")
23
+ _SHA = re.compile(r"[0-9a-f]{40}\Z")
24
+ _REASON = "MERGE_CANDIDATE_PROJECTION_V1"
25
+
26
+
27
+ class TerminalEvidenceReconciliationError(ValueError):
28
+ """A stable, secret-free refusal of a terminal-evidence repair."""
29
+
30
+
31
+ def _result(row: tuple[object, ...]) -> dict[str, object]:
32
+ return {
33
+ "operation_id": str(row[0]),
34
+ "run_id": str(row[1]),
35
+ "project_id": str(row[2]),
36
+ "source_artifact_id": str(row[3]),
37
+ "source_digest": "sha256:" + str(row[4]),
38
+ "replacement_artifact_id": str(row[5]),
39
+ "replacement_digest": "sha256:" + str(row[6]),
40
+ "reason_code": str(row[7]),
41
+ "recorded_at": str(row[8]),
42
+ "state": "SUCCEEDED",
43
+ }
44
+
45
+
46
+ def _operation(connection: sqlite3.Connection, operation_id: str) -> tuple[object, ...] | None:
47
+ row = connection.execute(
48
+ """SELECT operation_id,run_id,project_id,source_artifact_id,source_digest,
49
+ replacement_artifact_id,replacement_digest,reason_code,recorded_at
50
+ FROM ep_terminal_evidence_reconciliation_operations
51
+ WHERE operation_id=?""",
52
+ (operation_id,),
53
+ ).fetchone()
54
+ return tuple(row) if row is not None else None
55
+
56
+
57
+ def reconcile_terminal_evidence(
58
+ data_root: Path, *, run_id: str, operation_id: str,
59
+ ) -> dict[str, object]:
60
+ """Add and select one corrected projection while retaining original bytes."""
61
+ if RUN_ID_PATTERN.fullmatch(run_id) is None:
62
+ raise TerminalEvidenceReconciliationError("RUN_ID_INVALID")
63
+ if _OPERATION_ID.fullmatch(operation_id) is None:
64
+ raise TerminalEvidenceReconciliationError("OPERATION_ID_INVALID")
65
+ data_root = data_root.resolve()
66
+ database = central_database.path(data_root)
67
+ artifact_root = data_root / "artifacts"
68
+ with sqlite_connection(database) as connection:
69
+ existing = _operation(connection, operation_id)
70
+ if existing is not None:
71
+ if str(existing[1]) != run_id:
72
+ raise TerminalEvidenceReconciliationError("OPERATION_ID_CONFLICT")
73
+ return _result(existing)
74
+ conflicting = connection.execute(
75
+ "SELECT operation_id FROM ep_terminal_evidence_reconciliation_operations WHERE run_id=?",
76
+ (run_id,),
77
+ ).fetchone()
78
+ if conflicting is not None:
79
+ raise TerminalEvidenceReconciliationError("RUN_ALREADY_RECONCILED")
80
+ active = submission_service._active_terminal_artifact(connection, run_id)
81
+ row = connection.execute(
82
+ """SELECT d.project_id,d.repository_id,d.submission_id,t.payload
83
+ FROM ep_parity_lifecycle_dispatches d
84
+ JOIN engineering_transactions t ON t.run_id=d.run_id
85
+ WHERE d.run_id=? AND d.state='COMPLETE'""",
86
+ (run_id,),
87
+ ).fetchone()
88
+ if active is None or row is None:
89
+ raise TerminalEvidenceReconciliationError("TERMINAL_EVIDENCE_UNAVAILABLE")
90
+ source_id, algorithm, source_digest, _content_type, _integrity, source_location = active
91
+ if algorithm != "sha256" or not isinstance(source_digest, str):
92
+ raise TerminalEvidenceReconciliationError("SOURCE_ARTIFACT_INVALID")
93
+ try:
94
+ source_path = (artifact_root / str(source_location)).resolve()
95
+ source_path.relative_to(artifact_root.resolve())
96
+ source_bytes = source_path.read_bytes()
97
+ document = json.loads(source_bytes)
98
+ checkpoint = TransactionState.from_dict(json.loads(str(row[3])))
99
+ except (OSError, ValueError, TypeError, json.JSONDecodeError, StateError):
100
+ raise TerminalEvidenceReconciliationError("SOURCE_ARTIFACT_INVALID") from None
101
+ if hashlib.sha256(source_bytes).hexdigest() != source_digest:
102
+ raise TerminalEvidenceReconciliationError("SOURCE_ARTIFACT_INVALID")
103
+ assurance_status, current_reviews, _reviews = submission_service._current_assurance(checkpoint)
104
+ profile = checkpoint.assurance_profile
105
+ candidate = profile.get("candidate_sha") if isinstance(profile, dict) else None
106
+ repository = document.get("repository") if isinstance(document, dict) else None
107
+ submission = document.get("submission") if isinstance(document, dict) else None
108
+ producer = document.get("producer") if isinstance(document, dict) else None
109
+ run = document.get("run") if isinstance(document, dict) else None
110
+ assurance = document.get("assurance") if isinstance(document, dict) else None
111
+ if (
112
+ document.get("artifact_type") != "EP_TERMINAL_EVIDENCE"
113
+ or document.get("contract_version") != "1.4"
114
+ or not isinstance(repository, dict)
115
+ or not isinstance(submission, dict)
116
+ or not isinstance(producer, dict)
117
+ or not isinstance(run, dict)
118
+ or not isinstance(assurance, dict)
119
+ or run.get("id") != run_id
120
+ or run.get("outcome") != "COMPLETE"
121
+ or submission.get("id") != row[2]
122
+ or submission.get("project_id") != row[0]
123
+ or repository.get("id") != row[1]
124
+ or not isinstance(producer.get("id"), str)
125
+ or checkpoint.run_id != run_id
126
+ or not checkpoint.terminal
127
+ or checkpoint.phase != "COMPLETE"
128
+ or checkpoint.action_intent != "MUTATING_DELIVERY"
129
+ or assurance_status != "PASS"
130
+ or not isinstance(candidate, str)
131
+ or _SHA.fullmatch(candidate) is None
132
+ or assurance.get("profile") != profile
133
+ or len(current_reviews) < 2
134
+ ):
135
+ raise TerminalEvidenceReconciliationError("RECONCILIATION_PRECONDITION_FAILED")
136
+ projected_candidate = repository.get("candidate")
137
+ if (
138
+ projected_candidate != checkpoint.implementation_head_sha
139
+ or projected_candidate != checkpoint.execution_baseline_sha
140
+ or projected_candidate == candidate
141
+ ):
142
+ raise TerminalEvidenceReconciliationError("RECONCILIATION_DEFECT_NOT_PRESENT")
143
+
144
+ corrected = dict(document)
145
+ corrected["repository"] = {**repository, "candidate": candidate}
146
+ replacement_bytes = submission_service._canonical_json_bytes(corrected)
147
+ replacement_digest = hashlib.sha256(replacement_bytes).hexdigest()
148
+ replacement_id = f"terminal-evidence-reconciled:{run_id}:{replacement_digest[:16]}"
149
+ replacement_path = source_path.with_name(
150
+ f"terminal-evidence-v1-reconciled-{replacement_digest[:16]}.json"
151
+ )
152
+ submission_service._write_immutable_artifact(replacement_path, replacement_bytes)
153
+ record_artifact(
154
+ data_root,
155
+ replacement_path,
156
+ artifact_id=replacement_id,
157
+ artifact_type="EP_TERMINAL_EVIDENCE",
158
+ content_type="application/json",
159
+ created_at=datetime_now(),
160
+ run_id=run_id,
161
+ submission_id=str(row[2]),
162
+ producer_id=str(producer["id"]),
163
+ ep_run_id=run_id,
164
+ ep_submission_id=str(row[2]),
165
+ projection_status="CANDIDATE",
166
+ central_database=database,
167
+ artifact_root=artifact_root,
168
+ )
169
+ recorded_at = datetime_now()
170
+ with sqlite_connection(database) as connection:
171
+ try:
172
+ connection.execute("BEGIN IMMEDIATE")
173
+ existing = _operation(connection, operation_id)
174
+ if existing is not None:
175
+ connection.execute("ROLLBACK")
176
+ if str(existing[1]) != run_id:
177
+ raise TerminalEvidenceReconciliationError("OPERATION_ID_CONFLICT")
178
+ return _result(existing)
179
+ active_now = submission_service._active_terminal_artifact(connection, run_id)
180
+ if active_now is None or str(active_now[0]) != str(source_id) or str(active_now[2]) != source_digest:
181
+ raise TerminalEvidenceReconciliationError("SOURCE_ARTIFACT_CHANGED")
182
+ connection.execute(
183
+ """INSERT INTO ep_terminal_evidence_reconciliation_operations(
184
+ operation_id,run_id,project_id,source_artifact_id,source_digest,
185
+ replacement_artifact_id,replacement_digest,reason_code,recorded_at
186
+ ) VALUES(?,?,?,?,?,?,?,?,?)""",
187
+ (
188
+ operation_id, run_id, str(row[0]), str(source_id), source_digest,
189
+ replacement_id, replacement_digest, _REASON, recorded_at,
190
+ ),
191
+ )
192
+ source_updated = connection.execute(
193
+ """UPDATE execution_artifact_records SET projection_status='SUPERSEDED'
194
+ WHERE artifact_id=? AND digest=? AND projection_status='AVAILABLE'""",
195
+ (str(source_id), source_digest),
196
+ )
197
+ replacement_updated = connection.execute(
198
+ """UPDATE execution_artifact_records SET projection_status='AVAILABLE'
199
+ WHERE artifact_id=? AND digest=? AND projection_status='CANDIDATE'""",
200
+ (replacement_id, replacement_digest),
201
+ )
202
+ if source_updated.rowcount != 1 or replacement_updated.rowcount != 1:
203
+ raise TerminalEvidenceReconciliationError("PROJECTION_TRANSITION_CONFLICT")
204
+ connection.execute("COMMIT")
205
+ except Exception:
206
+ try:
207
+ connection.execute("ROLLBACK")
208
+ except sqlite3.DatabaseError:
209
+ pass
210
+ raise
211
+ result = _operation(connection, operation_id)
212
+ if result is None:
213
+ raise TerminalEvidenceReconciliationError("RECONCILIATION_RECEIPT_UNAVAILABLE")
214
+ return _result(result)
215
+
216
+
217
+ def datetime_now() -> str:
218
+ from datetime import datetime, timezone
219
+
220
+ return datetime.now(timezone.utc).isoformat()
221
+
222
+
223
+ def main(argv: list[str] | None = None) -> int:
224
+ parser = argparse.ArgumentParser(
225
+ prog="engineering-terminal-evidence-reconcile",
226
+ description="Reconcile one exact historical terminal candidate projection.",
227
+ )
228
+ parser.add_argument("--data-root", type=Path, required=True)
229
+ parser.add_argument("--run-id", required=True)
230
+ parser.add_argument("--operation-id", required=True)
231
+ args = parser.parse_args(argv)
232
+ try:
233
+ result = reconcile_terminal_evidence(
234
+ args.data_root, run_id=args.run_id, operation_id=args.operation_id,
235
+ )
236
+ except TerminalEvidenceReconciliationError as error:
237
+ print(json.dumps({"state": "BLOCKED", "reason_code": str(error)}, sort_keys=True))
238
+ return 2
239
+ print(json.dumps(result, sort_keys=True))
240
+ return 0
241
+
242
+
243
+ if __name__ == "__main__":
244
+ raise SystemExit(main())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-platform
3
- Version: 2.3.73
3
+ Version: 2.3.75
4
4
  Summary: Local-first Engineering Platform execution operations runtime
5
5
  Author: Peter van Tol
6
6
  License: MIT
@@ -127,6 +127,7 @@ src/engineering_platform/submission_service.py
127
127
  src/engineering_platform/system_installation_topology.py
128
128
  src/engineering_platform/system_server_service.py
129
129
  src/engineering_platform/telemetry.py
130
+ src/engineering_platform/terminal_evidence_reconciliation.py
130
131
  src/engineering_platform/validation_identity.py
131
132
  src/engineering_platform/validation_profile.py
132
133
  src/engineering_platform/version_preparation_delivery.py