engineering-platform 2.2.0__tar.gz → 2.3.0__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 (140) hide show
  1. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/PKG-INFO +1 -1
  2. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/pyproject.toml +1 -1
  3. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json +1 -1
  4. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json +4 -4
  5. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/agent_state.py +79 -1
  6. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/assets/dashboard.css +14 -6
  7. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/assets/dashboard.js +151 -6
  8. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/assets/dashboard_locales.mjs +80 -0
  9. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/capability_preflight.py +10 -1
  10. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/capability_review.py +37 -1
  11. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/central_data_transfer.py +117 -22
  12. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/central_database.py +23 -1
  13. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/component_logging.py +9 -1
  14. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/console_route_ownership.py +1 -0
  15. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/dependabot_producer.py +2 -2
  16. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_executor.py +44 -4
  17. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_host.py +364 -107
  18. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_lifecycle.py +8 -0
  19. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_models.py +1 -0
  20. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_repository.py +91 -2
  21. engineering_platform-2.3.0/src/engineering_platform/installation_relocation.py +212 -0
  22. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/lifecycle_worker.py +5 -2
  23. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/managed_autonomy.py +3 -1
  24. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/parity_context.py +4 -2
  25. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/parity_lifecycle_dispatcher.py +58 -16
  26. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/platform_bootstrap.py +16 -0
  27. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/platform_version.py +1 -1
  28. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/provider_readiness.py +10 -0
  29. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/provider_recovery.py +25 -9
  30. engineering_platform-2.3.0/src/engineering_platform/qualification_runtime.py +189 -0
  31. engineering_platform-2.3.0/src/engineering_platform/schemas/producer-readback-v1.2.schema.json +34 -0
  32. engineering_platform-2.3.0/src/engineering_platform/schemas/producer-readback-v1.schema.json +1 -0
  33. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/server.py +359 -30
  34. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/server_service.py +39 -0
  35. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/storage.py +2 -1
  36. engineering_platform-2.3.0/src/engineering_platform/submission_service.py +805 -0
  37. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/templates/workspace-config.json +1 -1
  38. engineering_platform-2.3.0/src/engineering_platform/version_preparation_delivery.py +437 -0
  39. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform.egg-info/PKG-INFO +1 -1
  40. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform.egg-info/SOURCES.txt +4 -0
  41. engineering_platform-2.2.0/src/engineering_platform/installation_relocation.py +0 -122
  42. engineering_platform-2.2.0/src/engineering_platform/submission_service.py +0 -317
  43. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/README.md +0 -0
  44. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/setup.cfg +0 -0
  45. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/__init__.py +0 -0
  46. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/__main__.py +0 -0
  47. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/agent_trust.py +0 -0
  48. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/assets/dashboard_status_store.mjs +0 -0
  49. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/assets/operations-console/apple-touch-icon-dark.png +0 -0
  50. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/assets/operations-console/apple-touch-icon-light.png +0 -0
  51. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/assets/operations-console/icon-dark.png +0 -0
  52. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/assets/operations-console/icon-light.png +0 -0
  53. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/assets/operations-console/icon-transparent.png +0 -0
  54. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/assets/operations-console/manifest.webmanifest +0 -0
  55. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/central_store_migration.py +0 -0
  56. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/codex_capacity.py +0 -0
  57. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/codex_chat.py +0 -0
  58. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/codex_observability.py +0 -0
  59. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/component_lock.py +0 -0
  60. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/console_presentation.py +0 -0
  61. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/contracts/__init__.py +0 -0
  62. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/contracts/ep_consumer.py +0 -0
  63. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/contracts/models.py +0 -0
  64. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/contracts/projection.py +0 -0
  65. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/dashboard_browser_validation.py +0 -0
  66. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/dashboard_state.py +0 -0
  67. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/dashboard_supervisor.swift +0 -0
  68. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/dashboard_translation.py +0 -0
  69. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/drift_diagnostics.py +0 -0
  70. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/emergency_recovery.py +0 -0
  71. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/engineering_memory.py +0 -0
  72. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/ep_consumer_credentials.py +0 -0
  73. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/evidence_projection.py +0 -0
  74. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_activity.py +0 -0
  75. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_context.py +0 -0
  76. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_errors.py +0 -0
  77. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_evidence.py +0 -0
  78. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_finalization.py +0 -0
  79. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_lease.py +0 -0
  80. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_readiness.py +0 -0
  81. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_reporting.py +0 -0
  82. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_timeout_policy.py +0 -0
  83. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_timing.py +0 -0
  84. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/execution_transaction.py +0 -0
  85. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/external_producer_binding.py +0 -0
  86. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/file_inbox.py +0 -0
  87. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/forensic_attribution.py +0 -0
  88. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/forensic_attribution_v2.py +0 -0
  89. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/forensic_delta.py +0 -0
  90. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/golden_scenario.py +0 -0
  91. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/historical_dashboard_configuration.py +0 -0
  92. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/host_admin.py +0 -0
  93. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/host_preflight.py +0 -0
  94. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/investigation_ledger.py +0 -0
  95. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/legacy_inbox_migration.py +0 -0
  96. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/live_status.py +0 -0
  97. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/local_api.py +0 -0
  98. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/local_api_keychain.py +0 -0
  99. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/local_repository_binding.py +0 -0
  100. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/managed_codex_runtime.py +0 -0
  101. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/platform_admin.py +0 -0
  102. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/platform_api.py +0 -0
  103. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/platform_components.py +0 -0
  104. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/pr_check_repair.py +0 -0
  105. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/pr_evidence_backfill.py +0 -0
  106. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/producer.py +0 -0
  107. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/project_agent.py +0 -0
  108. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/project_agent_service.py +0 -0
  109. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/project_topology.py +0 -0
  110. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/prompt_history.py +0 -0
  111. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/provider_context.py +0 -0
  112. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/provider_context_benchmark.py +0 -0
  113. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/provider_context_scope.py +0 -0
  114. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/provider_interruption.py +0 -0
  115. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/provider_process_identity.py +0 -0
  116. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/provider_usage.py +0 -0
  117. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/providers.py +0 -0
  118. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/qualification.py +0 -0
  119. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/recommendation_handoff.py +0 -0
  120. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/report_analysis.py +0 -0
  121. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/repository_attachment.py +0 -0
  122. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/repository_handoff.py +0 -0
  123. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/resources.py +0 -0
  124. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/reviewer_evidence.py +0 -0
  125. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/schemas/repository-attachment.schema.json +0 -0
  126. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/server_console_services.py +0 -0
  127. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/server_relay.py +0 -0
  128. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/status_model.py +0 -0
  129. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/status_reconciliation.py +0 -0
  130. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/submission_cli.py +0 -0
  131. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/submission_intake.py +0 -0
  132. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/telemetry.py +0 -0
  133. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/validation_identity.py +0 -0
  134. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/validation_profile.py +0 -0
  135. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/workspace_preflight.py +0 -0
  136. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/worktree_provenance.py +0 -0
  137. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform/worktree_tooling.py +0 -0
  138. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform.egg-info/dependency_links.txt +0 -0
  139. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform.egg-info/entry_points.txt +0 -0
  140. {engineering_platform-2.2.0 → engineering_platform-2.3.0}/src/engineering_platform.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-platform
3
- Version: 2.2.0
3
+ Version: 2.3.0
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.2.0"
7
+ version = "2.3.0"
8
8
  description = "Local-first Engineering Platform execution operations runtime"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -3,7 +3,7 @@
3
3
  "platform": {
4
4
  "id": "engineering-platform",
5
5
  "name": "Engineering Platform",
6
- "version": "2.2.0",
6
+ "version": "2.3.0",
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.2.0",
4
+ "dashboard_version": "2.3.0",
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.2.0",
9
+ "platform_version": "2.3.0",
10
10
  "report_format": 2,
11
- "runner_version": "2.2.0",
11
+ "runner_version": "2.3.0",
12
12
  "status_model": 1,
13
13
  "storage_schema": 41,
14
- "watcher_version": "2.2.0"
14
+ "watcher_version": "2.3.0"
15
15
  }
@@ -135,6 +135,12 @@ class TransactionState:
135
135
  agent_execution_seconds: float | None = None
136
136
  validation_evidence: tuple[dict[str, str], ...] = ()
137
137
  quality_evidence: tuple[dict[str, str], ...] = ()
138
+ # Versioned post-implementation assurance. These records are immutable
139
+ # observations; a later review appends a new record instead of changing an
140
+ # earlier finding into a pass.
141
+ assurance_profile: dict[str, str] | None = None
142
+ assurance_reviews: tuple[dict[str, object], ...] = ()
143
+ assurance_resolutions: tuple[dict[str, str], ...] = ()
138
144
  repair_iterations: int = 0
139
145
  repair_audit: tuple[dict[str, str], ...] = ()
140
146
  local_validation_iterations: int = 0
@@ -172,6 +178,9 @@ class TransactionState:
172
178
  "agent_execution_seconds": None,
173
179
  "validation_evidence": (),
174
180
  "quality_evidence": (),
181
+ "assurance_profile": None,
182
+ "assurance_reviews": (),
183
+ "assurance_resolutions": (),
175
184
  "repair_iterations": 0,
176
185
  "repair_audit": (),
177
186
  "local_validation_iterations": 0,
@@ -194,6 +203,10 @@ class TransactionState:
194
203
  raw = {**raw, "validation_evidence": tuple(raw["validation_evidence"])}
195
204
  if isinstance(raw.get("quality_evidence"), list):
196
205
  raw = {**raw, "quality_evidence": tuple(raw["quality_evidence"])}
206
+ if isinstance(raw.get("assurance_reviews"), list):
207
+ raw = {**raw, "assurance_reviews": tuple(raw["assurance_reviews"])}
208
+ if isinstance(raw.get("assurance_resolutions"), list):
209
+ raw = {**raw, "assurance_resolutions": tuple(raw["assurance_resolutions"])}
197
210
  if isinstance(raw.get("repair_audit"), list):
198
211
  raw = {**raw, "repair_audit": tuple(raw["repair_audit"])}
199
212
  if isinstance(raw.get("local_validation_audit"), list):
@@ -274,17 +287,24 @@ class TransactionState:
274
287
  )
275
288
  ):
276
289
  raise StateError("checkpoint local validation audit is invalid or unsafe")
290
+ repair_audit_fields = audit_fields | {"repair_id", "origin", "input_candidate_sha", "dispatch_id"}
277
291
  if (
278
292
  not isinstance(state.repair_audit, tuple)
279
293
  or len(state.repair_audit) > 3
280
294
  or any(
281
- not isinstance(item, dict) or set(item) != audit_fields
295
+ not isinstance(item, dict) or set(item) not in (audit_fields, repair_audit_fields)
282
296
  or not all(isinstance(value, str) and value and len(value) <= MAX_DIAGNOSTIC_LENGTH and value == redact_diagnostic(value) for value in item.values())
283
297
  or not item["iteration"].isdigit() or int(item["iteration"]) < 1
284
298
  or item["outcome"] not in {"planned", "submitted_for_recheck", "agent_failed", "agent_timed_out"}
285
299
  or (item["commit_sha"] != "not_recorded" and not re.fullmatch(r"[0-9a-f]{40}", item["commit_sha"]))
300
+ or (set(item) == repair_audit_fields and (
301
+ not re.fullmatch(r"repair:[A-Za-z0-9_.:-]+:[1-3]", item["repair_id"])
302
+ or item["origin"] not in {"validation", "quality", "security", "hosted", "finalization"}
303
+ or (item["input_candidate_sha"] != "not_recorded" and not re.fullmatch(r"[0-9a-f]{40}", item["input_candidate_sha"]))
304
+ ))
286
305
  for item in state.repair_audit
287
306
  )
307
+ or len({item.get("repair_id", f"legacy:{item['iteration']}") for item in state.repair_audit}) != len(state.repair_audit)
288
308
  ):
289
309
  raise StateError("checkpoint repair audit is invalid or unsafe")
290
310
  if (
@@ -357,6 +377,64 @@ class TransactionState:
357
377
  )
358
378
  ):
359
379
  raise StateError("checkpoint quality evidence is invalid or unsafe")
380
+ profile_fields = {"version", "digest", "candidate_sha"}
381
+ current_profile_fields = profile_fields | {"criteria_digest"}
382
+ if state.assurance_profile is not None and (
383
+ not isinstance(state.assurance_profile, dict)
384
+ or set(state.assurance_profile) not in (profile_fields, current_profile_fields)
385
+ or not all(isinstance(value, str) and value for value in state.assurance_profile.values())
386
+ or not re.fullmatch(r"sha256:[0-9a-f]{64}", state.assurance_profile["digest"])
387
+ or not re.fullmatch(r"[0-9a-f]{40}", state.assurance_profile["candidate_sha"])
388
+ ):
389
+ raise StateError("checkpoint assurance profile is invalid")
390
+ review_fields = {"reviewer", "status", "candidate_sha", "profile_digest", "invocation_id", "findings"}
391
+ current_review_fields = review_fields | {"contract_version", "started_at", "completed_at"}
392
+ finding_fields = {"id", "fingerprint", "category", "criterion", "observation", "severity", "confidence", "blocking", "disposition"}
393
+ current_finding_fields = finding_fields | {"evidence_ref"}
394
+ if (
395
+ not isinstance(state.assurance_reviews, tuple)
396
+ or len(state.assurance_reviews) > 16
397
+ or any(
398
+ not isinstance(review, dict) or set(review) not in (review_fields, current_review_fields)
399
+ or review.get("reviewer") not in {"quality", "security"}
400
+ or review.get("status") not in {"PASS", "FAIL", "UNRESOLVED"}
401
+ or not isinstance(review.get("candidate_sha"), str) or not re.fullmatch(r"[0-9a-f]{40}", review["candidate_sha"])
402
+ or not isinstance(review.get("profile_digest"), str) or not re.fullmatch(r"sha256:[0-9a-f]{64}", review["profile_digest"])
403
+ or not isinstance(review.get("invocation_id"), str) or not review["invocation_id"]
404
+ or not isinstance(review.get("findings"), list) or len(review["findings"]) > 12
405
+ or any(
406
+ not isinstance(finding, dict) or set(finding) not in (finding_fields, current_finding_fields)
407
+ or not all(isinstance(value, str) and value and len(value) <= 240 and value == redact_diagnostic(value, limit=240) for key, value in finding.items() if key != "blocking")
408
+ or finding.get("severity") not in {"LOW", "MEDIUM", "HIGH", "CRITICAL"}
409
+ or finding.get("confidence") not in {"LOW", "MEDIUM", "HIGH"}
410
+ or finding.get("disposition") not in {"OPEN", "RESOLVED", "REJECTED", "NON_BLOCKING"}
411
+ or not isinstance(finding.get("blocking"), bool)
412
+ or ("evidence_ref" in finding and (not isinstance(finding["evidence_ref"], str) or not finding["evidence_ref"]))
413
+ for finding in review["findings"]
414
+ )
415
+ or (set(review) == current_review_fields and (
416
+ review.get("contract_version") != "1.0"
417
+ or not isinstance(review.get("started_at"), str)
418
+ or not isinstance(review.get("completed_at"), str)
419
+ ))
420
+ for review in state.assurance_reviews
421
+ )
422
+ ):
423
+ raise StateError("checkpoint assurance review evidence is invalid")
424
+ resolution_fields = {"finding_id", "disposition", "resolution_ref", "candidate_sha"}
425
+ if (
426
+ not isinstance(state.assurance_resolutions, tuple)
427
+ or len(state.assurance_resolutions) > 128
428
+ or any(
429
+ not isinstance(item, dict) or set(item) != resolution_fields
430
+ or not all(isinstance(value, str) and value and len(value) <= MAX_DIAGNOSTIC_LENGTH and value == redact_diagnostic(value) for value in item.values())
431
+ or item["disposition"] not in {"RESOLVED", "REJECTED", "RISK_ACCEPTED"}
432
+ or not re.fullmatch(r"[0-9a-f]{40}", item["candidate_sha"])
433
+ for item in state.assurance_resolutions
434
+ )
435
+ or len({item["finding_id"] for item in state.assurance_resolutions}) != len(state.assurance_resolutions)
436
+ ):
437
+ raise StateError("checkpoint assurance resolutions are invalid or unsafe")
360
438
  if not isinstance(state.terminal, bool) or state.terminal != (state.phase in {"COMPLETE", "BLOCKED", "FAILED"}):
361
439
  raise StateError("checkpoint terminal flag conflicts with phase")
362
440
  return state
@@ -332,6 +332,7 @@ html[data-theme="light"] #promptHistory .execution-history-action{background:#ff
332
332
  .confirmation-modal h2[data-modal-glyph="question"]::before{align-items:center;border:2px solid currentColor;border-radius:50%;box-sizing:border-box;display:inline-flex;height:var(--modal-title-glyph-size);justify-content:center;width:var(--modal-title-glyph-size);vertical-align:-.12em}
333
333
  .confirmation-modal h2[data-modal-glyph="question"]::before{content:"?"}
334
334
  .confirmation-modal h2[data-modal-glyph="warning"]::before{content:"⚠"}
335
+ .installation-relocation-modal h2[data-modal-glyph="warning"]::before{content:"!" !important}
335
336
  .report-view-modal__title[data-modal-glyph="analysis"]::before{content:"✦"}
336
337
  :is(.component-modal h2,.confirmation-modal h2,.report-view-modal__title,.prompt-detail-modal__header h2,.prompt-chat-modal__header h2){font-size:clamp(18px,2vw,20px);line-height:1.2}
337
338
 
@@ -850,6 +851,7 @@ html[data-theme="light"] .dashboard-modal-shell :is(.label,.field>.label){color:
850
851
  actions therefore use the shared orange action language rather than blue. */
851
852
  #operatorMergeWaitModal .dashboard-modal-shell__action{text-decoration:none}
852
853
  #confirmationModal.dashboard-modal-shell--destructive .dashboard-modal-shell__action--destructive{background:#3a2028!important;border-color:#ff718f!important;color:#ffd9e1!important}#confirmationModal.dashboard-modal-shell--destructive .dashboard-modal-shell__action--destructive:hover:not(:disabled){background:#ff718f!important;border-color:#ff718f!important;color:#23131a!important}html[data-theme="light"] #confirmationModal.dashboard-modal-shell--destructive .dashboard-modal-shell__action--destructive{background:#fff1f4!important;border-color:#ff718f!important;color:#b32649!important}
854
+ #centralDataImportWarningModal .dashboard-modal-shell__action--destructive{background:#3a2028!important;border-color:#ff718f!important;color:#ffd9e1!important}#centralDataImportWarningModal .dashboard-modal-shell__action--destructive:hover:not(:disabled){background:#ff718f!important;border-color:#ff718f!important;color:#23131a!important}html[data-theme="light"] #centralDataImportWarningModal .dashboard-modal-shell__action--destructive{background:#fff1f4!important;border-color:#ff718f!important;color:#b32649!important}
853
855
  #operatorMergeWaitModal .dashboard-modal-shell__action{border-color:#f0b66a!important;color:#fff0dc!important}#operatorMergeWaitModal .dashboard-modal-shell__action--primary{background:#3b281b!important;border-color:#f0b66a!important;color:#fff0dc!important}#operatorMergeWaitModal .dashboard-modal-shell__action--destructive{background:#3a2028!important;border-color:#ff718f!important;color:#ffd9e1!important}#operatorMergeWaitModal .dashboard-modal-shell__action--primary:hover:not(:disabled){background:var(--house-style)!important;border-color:var(--house-style)!important;color:var(--button-ink)!important}#operatorMergeWaitModal .dashboard-modal-shell__action--destructive:hover:not(:disabled){background:#ff718f!important;border-color:#ff718f!important;color:#23131a!important}html[data-theme="light"] #operatorMergeWaitModal .dashboard-modal-shell__action{background:#fff!important;border-color:#d68b23!important;color:#643a13!important}html[data-theme="light"] #operatorMergeWaitModal .dashboard-modal-shell__action--primary{background:#fff8ef!important}html[data-theme="light"] #operatorMergeWaitModal .dashboard-modal-shell__action--destructive{background:#fff1f4!important;border-color:#ff718f!important;color:#b32649!important}
854
856
  /* Every modal uses a header tint derived from its own accent. The component
855
857
  modal retains the same structure, with extra space below its ruler. */
@@ -869,7 +871,7 @@ html:not([data-theme="light"]) .dashboard-modal-shell{--modal-header-surface:col
869
871
  .predecessor-retry::before{content:"↻";font:700 17px/1 system-ui;margin-right:8px;vertical-align:-1px}
870
872
  .execution-dismiss::before{content:"⊘"}
871
873
  #componentLogs .log-table{table-layout:auto;width:100%}#componentLogs .log-table th:last-child,#componentLogs .log-table td:last-child{width:100%}
872
- #queueList .queue-item{grid-template-columns:1.25rem minmax(0,1fr) auto}.queue-defer{background:#3b281b;border:1px solid #f0b66a;border-radius:8px;color:#fff0dc;font:600 12px system-ui;min-height:32px;padding:5px 9px}.queue-defer:hover:not(:disabled){background:var(--house-style)!important;border-color:var(--house-style)!important;color:#201812!important}.queue-defer:disabled{cursor:wait;opacity:.7}html[data-theme="light"] .queue-defer{background:#fff8ef;border-color:#d68b23;color:#643a13}html[data-theme="light"] .queue-defer:hover:not(:disabled){background:var(--house-style)!important;border-color:var(--house-style)!important;color:#201812!important}
874
+ #queueList .queue-item{grid-template-columns:1.25rem minmax(0,1fr) auto}.queue-item__actions{align-items:flex-end;display:flex;flex-direction:column;gap:6px;justify-self:end}.queue-defer{background:#3b281b;border:1px solid #f0b66a;border-radius:8px;color:#fff0dc;font:600 12px system-ui;min-height:32px;padding:5px 9px}.queue-defer:hover:not(:disabled){background:var(--house-style)!important;border-color:var(--house-style)!important;color:#201812!important}.queue-defer:disabled{cursor:wait;opacity:.7}.queue-defer--destructive{background:#3a2028;border-color:#ff718f;color:#ffd9e1}.queue-defer--destructive:hover:not(:disabled){background:#ff718f!important;border-color:#ff718f!important;color:#23131a!important}html[data-theme="light"] .queue-defer{background:#fff8ef;border-color:#d68b23;color:#643a13}html[data-theme="light"] .queue-defer:hover:not(:disabled){background:var(--house-style)!important;border-color:var(--house-style)!important;color:#201812!important}html[data-theme="light"] .queue-defer--destructive{background:#fff1f4;border-color:#ff718f;color:#b32649}html[data-theme="light"] .queue-defer--destructive:hover:not(:disabled){background:#ff718f!important;border-color:#ff718f!important;color:#23131a!important}
873
875
 
874
876
  /* Shared semantic action variants prevent individual surfaces from drifting. */
875
877
  .dashboard-action{align-items:center;border:1px solid;border-radius:50%;box-sizing:border-box;display:inline-flex;font:18px/1 system-ui;height:32px;justify-content:center;min-height:32px;min-width:32px;padding:0;width:32px}
@@ -970,7 +972,7 @@ html[data-theme="light"] ::-webkit-scrollbar-thumb:hover{background:#0a6b9d}
970
972
  /* Keep the input contract last: individual cards may colour their content,
971
973
  but they must never replace the selected-control border. */
972
974
  :where(input,select,textarea):is(:focus,:focus-visible){border-color:var(--dashboard-input-focus-border)!important;outline:var(--dashboard-focus-outline-width) solid var(--dashboard-input-focus-border)!important;outline-offset:var(--dashboard-focus-outline-offset)!important;box-shadow:0 0 0 var(--dashboard-focus-ring-size) var(--dashboard-input-focus-ring)!important}
973
- :where(button,summary,[role="button"],[tabindex]):not(#engineering-dashboard-content):not(:where(.dashboard-modal-shell,.component-log-row,.prompt-history-row,.telemetry-row,.execution-lifecycle__node,.theme-toggle,.section-state-toggle,.auto-refresh-toggle,.log-sortable,.log-table-wrap,.telemetry-scroll,.telemetry-detail-table-scroll)):is(:focus,:focus-visible){outline:var(--dashboard-focus-outline-width) solid var(--dashboard-input-focus-border)!important;outline-offset:var(--dashboard-focus-outline-offset)!important;box-shadow:0 0 0 var(--dashboard-focus-ring-size) var(--dashboard-input-focus-ring)!important}
975
+ :where(button,summary,[role="button"],[tabindex]):not(#engineering-dashboard-content):not(:where(.dashboard-modal-shell,.component-log-row,.prompt-history-row,.telemetry-row,.execution-lifecycle__node,.local-folder-link,.theme-toggle,.section-state-toggle,.auto-refresh-toggle,.log-sortable,.log-table-wrap,.telemetry-scroll,.telemetry-detail-table-scroll)):is(:focus,:focus-visible){outline:var(--dashboard-focus-outline-width) solid var(--dashboard-input-focus-border)!important;outline-offset:var(--dashboard-focus-outline-offset)!important;box-shadow:0 0 0 var(--dashboard-focus-ring-size) var(--dashboard-input-focus-ring)!important}
974
976
  /* Every dialog owns a full-viewport, safe-area-aware shell. The visible panel
975
977
  is therefore always separated from iPhone edges, including modal families
976
978
  added after the original report/detail implementations. */
@@ -994,8 +996,14 @@ html[data-theme="light"] ::-webkit-scrollbar-thumb:hover{background:#0a6b9d}
994
996
  /* Relocation and component actions keep their position while optional actions change. */
995
997
  .component-modal__actions{display:grid;gap:10px;grid-template-columns:repeat(2,minmax(0,1fr));margin-top:18px}.component-modal__actions .component-modal__restart{margin:0;min-height:44px;min-width:0;width:100%}.component-modal__actions .dashboard-modal-shell__action{grid-column:2;margin:0;min-width:0;width:100%}.installation-relocation-modal{padding:max(16px,env(safe-area-inset-top)) max(16px,env(safe-area-inset-right)) max(16px,env(safe-area-inset-bottom)) max(16px,env(safe-area-inset-left))!important}.installation-relocation-modal .dashboard-modal-shell__panel{display:grid;gap:18px;overflow:hidden}.installation-relocation-modal .dashboard-modal-shell__panel>p{margin:0}.installation-relocation-modal__locations{display:grid;gap:12px;margin:0}.installation-relocation-modal__locations div{display:grid;gap:4px}.installation-relocation-modal__locations dt{color:var(--modal-secondary-accent);font-size:12px;font-weight:700;letter-spacing:.05em;text-transform:uppercase}.installation-relocation-modal__locations dd{margin:0;overflow-wrap:anywhere}.installation-relocation-modal .dashboard-modal-shell__actions{justify-content:flex-start;margin-top:0}.installation-relocation-modal .dashboard-modal-shell__action{min-width:10rem}.configuration-central-database__actions{align-items:center;display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}
996
998
  .footer__version-word{font-weight:400;text-transform:none}
999
+ #centralDataImportModal{--modal-panel-max-width:1100px}
1000
+ #centralDataImportStatus.configuration-central-data-import__error{color:#ff718f;font-weight:600}
1001
+ #centralDataImport::before{content:"↑"}
1002
+ #centralDataImportModal .dashboard-modal-shell__header h2::before{content:"↑"}
1003
+ #centralDatabaseRelocateModal .dashboard-modal-shell__header h2::before{content:"↗"}
1004
+ .installation-relocation-modal .dashboard-modal-shell__action:disabled{background:#35353d!important;border-color:#686873!important;color:#aaaab4!important;cursor:not-allowed}
997
1005
  .installation-relocation-modal .dashboard-modal-shell__header{grid-template-columns:minmax(0,1fr) auto}.installation-relocation-modal .dashboard-modal-shell__header h2::before{content:"⚙︎";display:inline-block;font-size:20px;line-height:1;margin-right:.42em;vertical-align:-.04em}
998
- .configuration-central-database__location-value{align-items:center;display:flex;flex-wrap:wrap;gap:10px}.configuration-central-database__location-link{flex:1 1 28rem}.configuration-central-database__export,.configuration-central-database__relocate{align-items:center;background-color:color-mix(in srgb,currentColor 14%,var(--dashboard-button-fill-surface));border:1px solid var(--house-style);border-radius:7px;color:var(--house-style);cursor:pointer;display:inline-flex;flex:none;font:600 13px/1 system-ui;gap:7px;min-height:34px;padding:7px 10px;text-decoration:none;white-space:nowrap}.configuration-central-database__export::before,.configuration-central-database__relocate::before{font-size:16px;font-weight:700;line-height:1}.configuration-central-database__export::before{content:"↓"}.configuration-central-database__relocate::before{content:"↗"}.configuration-central-database__export:hover:not(:disabled),.configuration-central-database__relocate:hover:not(:disabled){background:var(--house-style);color:var(--button-ink)}@media (max-width:620px){.configuration-central-database__export,.configuration-central-database__relocate{flex-basis:100%;justify-content:center}.configuration-central-database__location-link{flex-basis:100%}}
1006
+ .configuration-central-database__location-value{align-items:center;display:flex;flex-wrap:wrap;gap:10px}.configuration-central-database__location-link{flex:1 1 28rem}.configuration-central-database__export,.configuration-central-database__relocate{align-items:center;background-color:color-mix(in srgb,currentColor 14%,var(--dashboard-button-fill-surface));border:1px solid var(--house-style);border-radius:7px;box-sizing:border-box;color:var(--house-style);cursor:pointer;display:inline-flex;flex:none;font:600 13px/1 system-ui;gap:7px;min-height:34px;padding:7px 10px;text-decoration:none;white-space:nowrap}.configuration-central-database__export::before,.configuration-central-database__relocate::before{font-size:16px;font-weight:700;line-height:1}.configuration-central-database__export::before{content:"↓"}.configuration-central-database__relocate::before{content:"↗"}.configuration-central-database__export:hover:not(:disabled),.configuration-central-database__relocate:hover:not(:disabled){background:var(--house-style);color:var(--button-ink)}@media (max-width:620px){.configuration-central-database__export,.configuration-central-database__relocate{flex-basis:100%;justify-content:center}.configuration-central-database__location-link{flex-basis:100%}}
999
1007
  .configuration-card :is(.configuration-provider-status h2,.configuration-validation-environment h3,.configuration-central-database__header h2,.configuration-server-settings h2,.configuration-readonly-settings h2,.configuration-timeout-policy h2,.configuration-controls h2,.configuration-dashboard-settings h2,.configuration-provider-login-status h2){font-size:16px;line-height:1.25}
1000
1008
  /* A stored run-scoped conversation gets a filled, heavier history glyph. */
1001
1009
  .prompt-history-chat--recorded{font-weight:800}
@@ -1036,7 +1044,7 @@ dialog.dashboard-modal-shell,dialog.dashboard-modal-shell:is(:focus,:focus-visib
1036
1044
  every visible interactive focus treatment in the house-style orange,
1037
1045
  including anchor actions inside dialogs. Deliberately non-focusing shells,
1038
1046
  table-row selection surfaces and lifecycle geometry remain excluded above. */
1039
- :where(a[href],button,input,select,textarea,[role="button"],[tabindex]):not(:where(.dashboard-modal-shell,.component-log-row,.prompt-history-row,.telemetry-row,.execution-lifecycle__node,.theme-toggle,.section-state-toggle,.auto-refresh-toggle,.log-sortable,.log-table-wrap,.telemetry-scroll,.telemetry-detail-table-scroll)):is(:focus,:focus-visible){border-color:var(--house-style)!important;outline-color:var(--house-style)!important;box-shadow:0 0 0 var(--dashboard-focus-ring-size) var(--dashboard-input-focus-ring)!important}
1047
+ :where(a[href],button,input,select,textarea,[role="button"],[tabindex]):not(:where(.dashboard-modal-shell,.component-log-row,.prompt-history-row,.telemetry-row,.execution-lifecycle__node,.local-folder-link,.theme-toggle,.section-state-toggle,.auto-refresh-toggle,.log-sortable,.log-table-wrap,.telemetry-scroll,.telemetry-detail-table-scroll)):is(:focus,:focus-visible){border-color:var(--house-style)!important;outline-color:var(--house-style)!important;box-shadow:0 0 0 var(--dashboard-focus-ring-size) var(--dashboard-input-focus-ring)!important}
1040
1048
  /* Table scrollers are keyboard-scroll targets, not selected controls. Their
1041
1049
  outer surface never receives a focus ring; only independently interactive
1042
1050
  sortable headers retain their contained focus edge. */
@@ -1093,7 +1101,7 @@ html[data-theme="light"] .prompt-detail-modal__content .execution-lifecycle--his
1093
1101
  .workspace-worktrees__active{color:#69e6ad;display:inline-block;font:700 14px/1 system-ui;margin-right:7px;vertical-align:1px}html[data-theme="light"] .workspace-worktrees__active{color:#187843}
1094
1102
  /* Local paths remain text controls. Their only action is copying the displayed
1095
1103
  path, so the dashboard neither opens Finder nor follows file:// URLs. */
1096
- .local-folder-link{-webkit-appearance:none;appearance:none;background:transparent!important;border:0;color:var(--modal-secondary-accent)!important;cursor:pointer;font:inherit;overflow-wrap:anywhere;padding:0;text-align:left;text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:3px}.local-folder-link:is(:hover,:active,:focus,:focus-visible):not(:disabled){background:transparent!important;box-shadow:none!important;color:var(--modal-secondary-accent)!important;text-decoration-thickness:2px}.local-folder-link:focus-visible{outline:2px solid var(--category-color);outline-offset:3px}.local-folder-link:disabled{cursor:default;opacity:1;text-decoration:none}.workspace-database__content .local-folder-link,.rate-limit-provider-path .local-folder-link{display:block;min-width:0;white-space:pre-wrap}
1104
+ .local-folder-link{-webkit-appearance:none;appearance:none;background:transparent!important;border:0;color:var(--modal-secondary-accent)!important;cursor:pointer;font:inherit;overflow-wrap:anywhere;padding:0;text-align:left;text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:3px}.local-folder-link:is(:hover,:active,:focus,:focus-visible):not(:disabled){background:transparent!important;box-shadow:none!important;color:var(--modal-secondary-accent)!important;outline:0!important;outline-offset:0!important;text-decoration-thickness:2px}.local-folder-link:disabled{cursor:default;opacity:1;text-decoration:none}.workspace-database__content .local-folder-link,.rate-limit-provider-path .local-folder-link{display:block;min-width:0;white-space:pre-wrap}
1097
1105
  .runtime-directory-link{font-family:ui-monospace,SFMono-Regular,Menlo,monospace!important}
1098
1106
 
1099
1107
  /* The Inbox location chooser inherits the exact indigo accent of its queue. */
@@ -1257,7 +1265,7 @@ html[data-theme="light"] #componentLogControls :is(input[type="date"],input[type
1257
1265
  /* Controls whose visible shape is a pseudo-element or a native checkbox have
1258
1266
  no normal border to recolour, so receive the same one-pixel edge directly. */
1259
1267
  :is(.dashboard-titlebar .theme-toggle,.dashboard-titlebar .section-state-toggle):is(:focus,:focus-visible)::before,
1260
- :is(.dashboard-health__button,.auto-refresh-toggle input,.local-folder-link):is(:focus,:focus-visible){
1268
+ :is(.dashboard-health__button,.auto-refresh-toggle input):is(:focus,:focus-visible){
1261
1269
  box-shadow:none!important;
1262
1270
  outline:1px solid var(--house-style)!important;
1263
1271
  outline-offset:0!important;
@@ -106,6 +106,11 @@ function reviewerStatusLabel(value, fallback = t("format.not_available")) {
106
106
  const normalized = reviewerKey(raw) === "uitgevoerd" ? "completed" : reviewerKey(raw);
107
107
  return t(`reviewer.status.${normalized}`, {}, raw.replaceAll("_", " "));
108
108
  }
109
+ function assuranceStatusLabel(value, fallback = t("format.not_available")) {
110
+ const raw = String(value || "").trim();
111
+ if (!raw) return fallback;
112
+ return t(`lifecycle.assurance_status.${reviewerKey(raw)}`, {}, raw);
113
+ }
109
114
  function reviewerCapabilityLabel(value, fallback = t("format.not_available")) {
110
115
  const raw = String(value || "").trim();
111
116
  return raw ? enumLabel(raw.toUpperCase(), raw) : fallback;
@@ -914,6 +919,7 @@ function queueItems(x, queueDepth) {
914
919
  const row = document.createElement("li"),
915
920
  number = document.createElement("span"),
916
921
  body = document.createElement("div"),
922
+ actions = document.createElement("div"),
917
923
  title = document.createElement("span"),
918
924
  meta = document.createElement("div"),
919
925
  modified = Date.parse(item.modified_at || ""),
@@ -928,6 +934,7 @@ function queueItems(x, queueDepth) {
928
934
  number.textContent = String(index + 1);
929
935
  title.className = "queue-item__title";
930
936
  meta.className = "queue-item__meta";
937
+ actions.className = "queue-item__actions";
931
938
  title.textContent = displayTitle;
932
939
  meta.textContent = t("queue.filename", {
933
940
  filename,
@@ -935,25 +942,73 @@ function queueItems(x, queueDepth) {
935
942
  ? locale.dateTime(new Date(modified))
936
943
  : t("format.timestamp_unavailable"),
937
944
  });
938
- const defer = item.queue_source === "CENTRAL" ? null : document.createElement("button");
945
+ const central = item.queue_source === "CENTRAL";
946
+ const mutable = !central || ["QUEUED", "DEFERRED", "QUARANTINED"].includes(item.queue_state);
947
+ const defer = mutable ? document.createElement("button") : null;
939
948
  if (defer) {
940
949
  defer.className = "queue-defer";
941
950
  defer.type = "button";
942
- defer.textContent = t("queue.defer_action");
943
- defer.title = t("queue.defer_action");
944
- defer.setAttribute("aria-label", t("queue.defer_action"));
951
+ const held = central && ["DEFERRED", "QUARANTINED"].includes(item.queue_state);
952
+ const actionKey = held ? "queue.resume" : "queue.defer";
953
+ defer.textContent = t(`${actionKey}_action`);
954
+ defer.title = t(`${actionKey}_action`);
955
+ defer.setAttribute("aria-label", t(`${actionKey}_action`));
945
956
  defer.addEventListener("click", (event) => {
946
957
  event.preventDefault();
947
958
  event.stopPropagation();
948
- deferQueueItem(item, defer);
959
+ if (item.queue_source === "CENTRAL") {
960
+ queueDisposition(item, held ? "QUEUED" : "DEFERRED", held ? "Operator resumed this submission from Operations Console." : "Operator deferred this submission from Operations Console.", defer);
961
+ } else deferQueueItem(item, defer);
949
962
  });
950
963
  }
951
964
  body.append(title, meta);
952
965
  row.append(number, body);
953
- if (defer) row.append(defer);
966
+ if (defer) actions.append(defer);
967
+ if (item.queue_source === "CENTRAL" && ["QUEUED", "QUARANTINED"].includes(item.queue_state)) {
968
+ (item.queue_state === "QUEUED"
969
+ ? [["QUARANTINED", "queue.quarantine", "Operator quarantined this submission from Operations Console."],
970
+ ["DECLINED", "queue.decline", "Operator declined this submission from Operations Console."]]
971
+ : [["DECLINED", "queue.decline", "Operator declined this quarantined submission from Operations Console."]]
972
+ ).forEach(([disposition, actionKey, reason]) => {
973
+ const action = document.createElement("button");
974
+ action.className = `queue-defer${disposition === "DECLINED" ? " queue-defer--destructive" : ""}`;
975
+ action.type = "button";
976
+ action.textContent = t(`${actionKey}_action`);
977
+ action.title = t(`${actionKey}_action`);
978
+ action.setAttribute("aria-label", t(`${actionKey}_action`));
979
+ action.addEventListener("click", (event) => {
980
+ event.preventDefault(); event.stopPropagation();
981
+ queueDisposition(item, disposition, reason, action);
982
+ });
983
+ actions.append(action);
984
+ });
985
+ }
986
+ if (actions.childElementCount) row.append(actions);
954
987
  container.append(row);
955
988
  });
956
989
  }
990
+ function queueDisposition(item, disposition, reason, button) {
991
+ const submissionId = String(item?.submission_id || item?.filename || "");
992
+ if (!submissionId) return;
993
+ const actionKey = { DEFERRED: "queue.defer", QUEUED: "queue.resume", QUARANTINED: "queue.quarantine", DECLINED: "queue.decline" }[disposition];
994
+ if (!actionKey) return;
995
+ confirmDashboardAction(
996
+ t(`${actionKey}_title`), t(`${actionKey}_description`, { title: submissionId }), t(`${actionKey}_action`),
997
+ { destructive: disposition === "DECLINED" },
998
+ )
999
+ .then((confirmed) => {
1000
+ if (!confirmed) return;
1001
+ button.disabled = true;
1002
+ const operationId = crypto.randomUUID();
1003
+ return fetch("/api/queue-disposition", { method: "POST", headers: { "Content-Type": "application/json" },
1004
+ body: JSON.stringify({ contract_version: "1.0", operation_id: operationId, submission_id: submissionId,
1005
+ expected_state: item.queue_state, expected_revision: item.disposition_revision, disposition, reason }) })
1006
+ .then(async (response) => ({ ok: response.ok, body: await response.json().catch(() => ({})) }))
1007
+ .then((result) => { if (!result.ok) throw Error(result.body.error || t("queue.defer_failed")); return refreshDashboard(); })
1008
+ .catch((error) => showDashboardError(error.message, t("queue.defer_failed")))
1009
+ .finally(() => { button.disabled = false; });
1010
+ });
1011
+ }
957
1012
  function deferQueueItem(item, button) {
958
1013
  const filename = String(item?.filename || "");
959
1014
  if (!filename) return;
@@ -1971,6 +2026,30 @@ function lifecycleQualityEvidence(step) {
1971
2026
  void localizeDynamicEvidence(dynamicRows);
1972
2027
  return section;
1973
2028
  }
2029
+ function lifecycleAssuranceEvidence(step) {
2030
+ const reviews = Array.isArray(step?.assurance_reviews) ? step.assurance_reviews : [];
2031
+ if (!reviews.length) return null;
2032
+ const section = document.createElement("section");
2033
+ section.className = "lifecycle-detail-modal__quality-evidence";
2034
+ const rounds = step?.repair_rounds || {};
2035
+ section.append(Object.assign(document.createElement("h3"), { textContent: t("lifecycle.detail_assurance") }));
2036
+ if (Number.isFinite(Number(rounds.used))) section.append(Object.assign(document.createElement("p"), {
2037
+ className: "estimate-meta", textContent: t("lifecycle.repair_rounds", { used: rounds.used, maximum: rounds.maximum || 3 }),
2038
+ }));
2039
+ const list = document.createElement("ol"); list.className = "lifecycle-detail-modal__phase-list";
2040
+ for (const review of reviews) {
2041
+ if (!review || typeof review !== "object") continue;
2042
+ const findings = Array.isArray(review.findings) ? review.findings : [];
2043
+ const item = document.createElement("li");
2044
+ const role = String(review.reviewer || ""); const status = String(review.status || "UNRESOLVED");
2045
+ item.append(Object.assign(document.createElement("strong"), { textContent: `${reviewerLabel(role, role)} · ${assuranceStatusLabel(status, status)}` }));
2046
+ const summary = findings.map((finding) => String(finding?.observation || "").trim()).filter(Boolean).join("; ");
2047
+ item.append(Object.assign(document.createElement("span"), { textContent: summary || t("lifecycle.assurance_no_findings") }));
2048
+ list.append(item);
2049
+ }
2050
+ if (!list.childElementCount) return null;
2051
+ section.append(list); return section;
2052
+ }
1974
2053
  function lifecycleRepairEvidence(step) {
1975
2054
  const audit = Array.isArray(step?.repair_audit) ? step.repair_audit : [];
1976
2055
  if (!audit.length) return null;
@@ -2060,6 +2139,8 @@ function openLifecycleDetail(step, trigger) {
2060
2139
  content.append(phaseTiming);
2061
2140
  const qualityEvidence = lifecycleQualityEvidence(step);
2062
2141
  if (qualityEvidence) content.append(qualityEvidence);
2142
+ const assuranceEvidence = lifecycleAssuranceEvidence(step);
2143
+ if (assuranceEvidence) content.append(assuranceEvidence);
2063
2144
  const repairEvidence = lifecycleRepairEvidence(step);
2064
2145
  if (repairEvidence) content.append(repairEvidence);
2065
2146
  if (!modal.open) modal.showModal();
@@ -8532,3 +8613,67 @@ if (NO_PROJECT_SELECTED) {
8532
8613
  // Hydrate that same shared shell so footer facts are factual rather than
8533
8614
  // permanently displaying loading placeholders; it contains no project state.
8534
8615
  startDashboardUpdates();
8616
+
8617
+ document.addEventListener("change", (event) => {
8618
+ if (!(event.target instanceof HTMLInputElement) || event.target.id !== "centralDataImportFile") return;
8619
+ const status = $("centralDataImportStatus");
8620
+ if (!status) return;
8621
+ status.textContent = "";
8622
+ status.classList.remove("configuration-central-data-import__error");
8623
+ delete status.dataset.importErrorMessage;
8624
+ }, true);
8625
+
8626
+ document.addEventListener("DOMContentLoaded", () => {
8627
+ const warning = document.getElementById("centralDataImportWarningModal");
8628
+ const confirm = document.getElementById("centralDataImportConfirm");
8629
+ const proceed = document.getElementById("centralDataImportProceed");
8630
+ if (!warning || !confirm || !proceed) return;
8631
+ if (warning.parentElement !== document.body) document.body.append(warning);
8632
+ document.querySelectorAll("[data-close-central-import-warning]").forEach((control) => {
8633
+ control.addEventListener("click", () => warning.close());
8634
+ });
8635
+ confirm.addEventListener("click", (event) => {
8636
+ if (confirm.dataset.importConfirmed === "true") {
8637
+ delete confirm.dataset.importConfirmed;
8638
+ return;
8639
+ }
8640
+ event.stopImmediatePropagation();
8641
+ warning.showModal();
8642
+ }, true);
8643
+ proceed.addEventListener("click", () => {
8644
+ confirm.dataset.importConfirmed = "true";
8645
+ warning.close();
8646
+ confirm.click();
8647
+ });
8648
+ });
8649
+
8650
+ document.addEventListener("DOMContentLoaded", () => {
8651
+ const status = $("centralDataImportStatus");
8652
+ const archive = $("centralDataImportFile");
8653
+ if (archive) archive.accept = ".epdata,application/vnd.engineering-platform.epdata+zip";
8654
+ if (!status) return;
8655
+ const errors = new Set(["CENTRAL_IMPORT_BLOCKED","CENTRAL_IMPORT_UPLOAD_INCOMPLETE","CENTRAL_IMPORT_ALREADY_PENDING","CENTRAL_IMPORT_REQUEST_INVALID","CENTRAL_IMPORT_FAILED","CENTRAL_DATABASE_UNAVAILABLE","CENTRAL_ARCHIVE_SIZE_INVALID","CENTRAL_ARCHIVE_MEMBER_INVALID","CENTRAL_ARCHIVE_MANIFEST_INVALID","CENTRAL_ARCHIVE_SCHEMA_INVALID","CENTRAL_ARCHIVE_SCHEMA_INCOMPATIBLE","CENTRAL_ARCHIVE_INTEGRITY_INVALID","CENTRAL_ARCHIVE_INVALID"]);
8656
+ new MutationObserver(() => {
8657
+ const code = status.textContent?.trim() || "";
8658
+ const failed = Boolean(code)
8659
+ && code !== t("configuration.relocation_restarting")
8660
+ && code !== t("configuration.central_data_import_restarting");
8661
+ status.classList.toggle("configuration-central-data-import__error", failed);
8662
+ if (failed && status.dataset.importErrorMessage !== code) {
8663
+ const message = t(`configuration.central_data_import_error.${errors.has(code) ? code : "CENTRAL_IMPORT_FAILED"}`);
8664
+ status.dataset.importErrorMessage = message;
8665
+ status.textContent = message;
8666
+ }
8667
+ if (!failed) delete status.dataset.importErrorMessage;
8668
+ }).observe(status, { childList: true, characterData: true, subtree: true });
8669
+ });
8670
+
8671
+ document.addEventListener("DOMContentLoaded", () => {
8672
+ const status = $("centralDatabaseRelocateStatus");
8673
+ if (!status) return;
8674
+ new MutationObserver(() => {
8675
+ if (status.textContent?.trim() === "PLATFORM_DATA_DESTINATION_FILESYSTEM_UNSUPPORTED") {
8676
+ status.textContent = t("configuration.relocation_filesystem_unsupported");
8677
+ }
8678
+ }).observe(status, { childList: true, characterData: true, subtree: true });
8679
+ });