engineering-platform 2.3.88__tar.gz → 2.3.90__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.
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/PKG-INFO +1 -1
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/pyproject.toml +1 -1
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json +1 -1
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json +4 -4
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/central_operational_reset.py +34 -8
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_host.py +37 -13
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_repository.py +138 -18
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/merge_delegation.py +127 -13
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/platform_version.py +1 -1
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/server.py +103 -5
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/submission_service.py +2 -1
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/templates/workspace-config.json +1 -1
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform.egg-info/PKG-INFO +1 -1
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/README.md +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/setup.cfg +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/__init__.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/__main__.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/agent_state.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/agent_trust.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/assets/dashboard.css +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/assets/dashboard.js +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/assets/dashboard_locales.mjs +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/assets/dashboard_status_store.mjs +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/assets/dashboard_translation.mjs +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/assets/operations-console/apple-touch-icon-dark.png +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/assets/operations-console/apple-touch-icon-light.png +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/assets/operations-console/icon-dark.png +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/assets/operations-console/icon-light.png +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/assets/operations-console/icon-transparent.png +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/assets/operations-console/manifest.webmanifest +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/assurance_scope.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/capability_preflight.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/capability_review.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/central_data_transfer.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/central_database.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/central_store_migration.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/codex_capacity.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/codex_chat.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/codex_observability.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/component_lock.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/component_logging.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/console_presentation.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/console_route_ownership.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/contracts/__init__.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/contracts/ep_consumer.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/contracts/models.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/contracts/projection.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/dashboard_browser_validation.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/dashboard_state.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/dashboard_supervisor.swift +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/dashboard_translation.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/dependabot_producer.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/development_profile.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/drift_diagnostics.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/emergency_recovery.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/engineering_memory.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/ep_consumer_credentials.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/evidence_projection.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_activity.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_context.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_errors.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_evidence.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_executor.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_finalization.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_host_evidence.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_lease.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_lifecycle.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_models.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_readiness.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_reporting.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_timeout_policy.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_timing.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/execution_transaction.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/external_producer_binding.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/file_inbox.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/forensic_attribution.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/forensic_attribution_v2.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/forensic_delta.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/golden_scenario.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/historical_dashboard_configuration.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/host_admin.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/host_preflight.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/installation_relocation.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/installation_update_activation.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/installation_update_admission.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/installation_update_backup.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/installation_update_composition.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/installation_update_executor.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/installation_update_migration.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/installation_update_operation.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/installation_update_plan.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/installation_update_preparation.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/investigation_ledger.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/legacy_inbox_migration.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/legacy_installation_adoption.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/lifecycle_worker.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/live_status.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/local_api.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/local_api_keychain.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/local_repository_binding.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/managed_autonomy.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/managed_codex_runtime.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/operational_installation.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/operational_installation_lock.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/operational_installation_record.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/owner_credential_recovery.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/parity_context.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/parity_lifecycle_dispatcher.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/platform_admin.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/platform_api.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/platform_bootstrap.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/platform_components.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/pr_check_repair.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/pr_evidence_backfill.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/producer.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/product_installation_readback.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/project_agent.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/project_agent_service.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/project_topology.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/prompt_history.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/provider_context.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/provider_context_benchmark.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/provider_context_scope.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/provider_interruption.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/provider_process_identity.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/provider_readiness.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/provider_recovery.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/provider_usage.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/providers.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/qualification.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/qualification_runtime.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/recommendation_handoff.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/reconciliation_adoption.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/release_operation.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/report_analysis.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/repository_attachment.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/repository_handoff.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/resources.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/reviewer_evidence.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/revision_binding.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/schemas/producer-readback-v1.2.schema.json +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/schemas/producer-readback-v1.schema.json +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/schemas/repository-attachment.schema.json +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/server_console_services.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/server_relay.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/server_service.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/status_model.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/status_reconciliation.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/storage.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/submission_cli.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/submission_intake.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/system_installation_topology.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/system_server_service.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/telemetry.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/telemetry_contract.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/telemetry_export.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/telemetry_metrics.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/terminal_evidence_reconciliation.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/validation_identity.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/validation_profile.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/version_preparation_delivery.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/workspace_preflight.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/worktree_provenance.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform/worktree_tooling.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform.egg-info/SOURCES.txt +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform.egg-info/dependency_links.txt +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform.egg-info/entry_points.txt +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/src/engineering_platform.egg-info/top_level.txt +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/tests/test_adaptive_action_sizing_design.py +0 -0
- {engineering_platform-2.3.88 → engineering_platform-2.3.90}/tests/test_role_task_model_policy_design.py +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bootstrap_contract": "2026.12",
|
|
3
3
|
"checkpoint_format": 1,
|
|
4
|
-
"dashboard_version": "2.3.
|
|
4
|
+
"dashboard_version": "2.3.90",
|
|
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.
|
|
9
|
+
"platform_version": "2.3.90",
|
|
10
10
|
"report_format": 2,
|
|
11
|
-
"runner_version": "2.3.
|
|
11
|
+
"runner_version": "2.3.90",
|
|
12
12
|
"status_model": 1,
|
|
13
13
|
"storage_schema": 45,
|
|
14
|
-
"watcher_version": "2.3.
|
|
14
|
+
"watcher_version": "2.3.90"
|
|
15
15
|
}
|
|
@@ -36,7 +36,7 @@ from .storage import sqlite_connection
|
|
|
36
36
|
|
|
37
37
|
PROFILE = "EP_CENTRAL_OPERATIONAL_HISTORY_V1"
|
|
38
38
|
PLAN_VERSION = 2
|
|
39
|
-
SCHEMA_VERSION =
|
|
39
|
+
SCHEMA_VERSION = 72
|
|
40
40
|
_OPERATION = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{7,127}")
|
|
41
41
|
_INSTANCE_ID = re.compile(
|
|
42
42
|
r"[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"
|
|
@@ -66,6 +66,7 @@ INSTALLATION_AND_CONFIGURATION = frozenset({
|
|
|
66
66
|
"ep_project_registrations", "ep_repository_registrations",
|
|
67
67
|
"ep_agent_registrations", "ep_agent_repository_attachments",
|
|
68
68
|
"ep_local_repository_bindings", "ep_external_producer_bindings",
|
|
69
|
+
"ep_assurance_target_selections",
|
|
69
70
|
"execution_migration_provenance",
|
|
70
71
|
})
|
|
71
72
|
SECURITY_AND_AUTHORITY_LEDGER = frozenset({
|
|
@@ -222,6 +223,31 @@ def _implementation_digest() -> str:
|
|
|
222
223
|
})
|
|
223
224
|
|
|
224
225
|
|
|
226
|
+
# The published 2.3.89 reset implementation can leave an operation at
|
|
227
|
+
# ARTIFACTS_ARCHIVED when operational engineering_metadata rows are present.
|
|
228
|
+
# Permit only that exact installed implementation to cross the fixed apply
|
|
229
|
+
# boundary; the plan, backup, identity, schema, generation and fences are still
|
|
230
|
+
# verified by apply before any database effect.
|
|
231
|
+
_ARCHIVED_RESET_COMPATIBILITY = frozenset({
|
|
232
|
+
("2.3.89", "UNAVAILABLE_IN_INSTALLED_PACKAGE",
|
|
233
|
+
"sha256:17ec03725e15672ef9511382e0b3d2c72e3f502823a35b89bea170882f4a14b8"),
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _implementation_binding_valid(plan: dict[str, object], state: str) -> bool:
|
|
238
|
+
binding = (
|
|
239
|
+
plan.get("product_version"), plan.get("implementation_source_revision"),
|
|
240
|
+
plan.get("implementation_digest"),
|
|
241
|
+
)
|
|
242
|
+
current = (
|
|
243
|
+
CURRENT_PLATFORM_VERSION, _implementation_source_revision(),
|
|
244
|
+
_implementation_digest(),
|
|
245
|
+
)
|
|
246
|
+
return binding == current or (
|
|
247
|
+
state == "ARTIFACTS_ARCHIVED" and binding in _ARCHIVED_RESET_COMPATIBILITY
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
225
251
|
def _implementation_source_revision() -> str:
|
|
226
252
|
try:
|
|
227
253
|
module = Path(__file__).resolve()
|
|
@@ -2301,11 +2327,7 @@ def apply(data_root: Path, *, operation_id: str, plan_digest: str) -> dict[str,
|
|
|
2301
2327
|
raise OperationalResetError("PRESERVED_BINDINGS_CHANGED")
|
|
2302
2328
|
if _schema_objects(connection) != plan["schema_objects"]:
|
|
2303
2329
|
raise OperationalResetError("SCHEMA_OBJECTS_CHANGED")
|
|
2304
|
-
if (
|
|
2305
|
-
plan.get("product_version") != CURRENT_PLATFORM_VERSION
|
|
2306
|
-
or plan.get("implementation_source_revision") != _implementation_source_revision()
|
|
2307
|
-
or plan.get("implementation_digest") != _implementation_digest()
|
|
2308
|
-
):
|
|
2330
|
+
if not _implementation_binding_valid(plan, "ARTIFACTS_ARCHIVED"):
|
|
2309
2331
|
raise OperationalResetError("IMPLEMENTATION_PROVENANCE_CHANGED")
|
|
2310
2332
|
generation_before = int(connection.execute(
|
|
2311
2333
|
"SELECT generation FROM ep_operational_dataset_state WHERE singleton=1"
|
|
@@ -2315,11 +2337,15 @@ def apply(data_root: Path, *, operation_id: str, plan_digest: str) -> dict[str,
|
|
|
2315
2337
|
# Immutable-evidence and maintenance-block triggers are removed and
|
|
2316
2338
|
# recreated inside this one uncommitted transaction. Other writers
|
|
2317
2339
|
# cannot observe an unfenced schema window.
|
|
2340
|
+
reset_trigger_tables = (
|
|
2341
|
+
OPERATIONAL_HISTORY | DERIVED_CACHE_OR_PROJECTION
|
|
2342
|
+
| {"engineering_metadata"}
|
|
2343
|
+
)
|
|
2318
2344
|
triggers = [
|
|
2319
2345
|
(str(name), str(sql)) for name, sql in connection.execute(
|
|
2320
2346
|
"SELECT name,sql FROM sqlite_master WHERE type='trigger' AND tbl_name IN ("
|
|
2321
|
-
+ ",".join("?" for _ in
|
|
2322
|
-
tuple(sorted(
|
|
2347
|
+
+ ",".join("?" for _ in reset_trigger_tables) + ")",
|
|
2348
|
+
tuple(sorted(reset_trigger_tables)),
|
|
2323
2349
|
).fetchall() if sql is not None
|
|
2324
2350
|
]
|
|
2325
2351
|
for name, _sql in triggers:
|
|
@@ -3898,12 +3898,14 @@ First implementation pull-request publication gate:
|
|
|
3898
3898
|
try:
|
|
3899
3899
|
with sqlite_connection(self.store.central_database) as connection:
|
|
3900
3900
|
grant = merge_delegation.load(connection, state.merge_delegation_id)
|
|
3901
|
+
selection_current = bool(grant and merge_delegation.target_selection_permits(connection, grant))
|
|
3901
3902
|
except (EngineeringStorageError, sqlite3.Error):
|
|
3902
3903
|
return False
|
|
3903
|
-
return bool(grant and grant.
|
|
3904
|
-
and grant.assurance_profile_revision == "1"
|
|
3904
|
+
return bool(grant and selection_current and grant.assurance_profile_revision == "1"
|
|
3905
3905
|
and grant.github_repository == state.repository
|
|
3906
|
-
== merge_delegation.
|
|
3906
|
+
and (grant.assurance_profile_id == merge_delegation.REPOSITORY_ASSURANCE_ID
|
|
3907
|
+
or (grant.assurance_profile_id == "qualification-autonomous-qs"
|
|
3908
|
+
and state.repository == merge_delegation.AUTONOMOUS_ASSURANCE_REPOSITORY)))
|
|
3907
3909
|
|
|
3908
3910
|
def _recover_autonomous_pr_assurance(
|
|
3909
3911
|
self, state: TransactionState, candidate: PullRequestEvidence,
|
|
@@ -4013,7 +4015,8 @@ First implementation pull-request publication gate:
|
|
|
4013
4015
|
(state.run_id,),
|
|
4014
4016
|
).fetchone()
|
|
4015
4017
|
grant = merge_delegation.load(connection, state.merge_delegation_id)
|
|
4016
|
-
|
|
4018
|
+
selection_current = bool(grant and merge_delegation.target_selection_permits(connection, grant))
|
|
4019
|
+
if scope is None or grant is None or not selection_current or not isinstance(accepted, dict):
|
|
4017
4020
|
return None
|
|
4018
4021
|
if Path(str(scope[2])).resolve() != self.root.resolve():
|
|
4019
4022
|
return None
|
|
@@ -4057,17 +4060,21 @@ First implementation pull-request publication gate:
|
|
|
4057
4060
|
qualifier = getattr(self.github, "delegated_merge_qualification", None)
|
|
4058
4061
|
if not callable(qualifier):
|
|
4059
4062
|
return None
|
|
4060
|
-
autonomous_profile = (
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4063
|
+
autonomous_profile = (grant.assurance_profile_revision == "1" and
|
|
4064
|
+
grant.assurance_profile_id in {
|
|
4065
|
+
"qualification-autonomous-qs",
|
|
4066
|
+
merge_delegation.REPOSITORY_ASSURANCE_ID})
|
|
4064
4067
|
if grant.assurance_profile_id or grant.assurance_profile_revision:
|
|
4065
4068
|
if (not autonomous_profile
|
|
4066
|
-
or grant.
|
|
4069
|
+
or (grant.assurance_profile_id == "qualification-autonomous-qs"
|
|
4070
|
+
and grant.github_repository != merge_delegation.AUTONOMOUS_ASSURANCE_REPOSITORY)
|
|
4067
4071
|
or not self._autonomous_merge_assurance_passes(state, expected_head)):
|
|
4068
4072
|
return None
|
|
4073
|
+
selected_profile = (merge_delegation.REPOSITORY_ASSURANCE_PROFILE
|
|
4074
|
+
if grant.assurance_profile_id == merge_delegation.REPOSITORY_ASSURANCE_ID
|
|
4075
|
+
else merge_delegation.AUTONOMOUS_ASSURANCE_PROFILE)
|
|
4069
4076
|
qualification = (qualifier(fresh.number, expected_head,
|
|
4070
|
-
assurance_profile=
|
|
4077
|
+
assurance_profile=selected_profile)
|
|
4071
4078
|
if autonomous_profile else qualifier(fresh.number, expected_head))
|
|
4072
4079
|
if (not isinstance(qualification, dict) or qualification.get("conclusion") != "PASS"
|
|
4073
4080
|
or qualification.get("exact_qualified_sha") != expected_head
|
|
@@ -4094,7 +4101,8 @@ First implementation pull-request publication gate:
|
|
|
4094
4101
|
return None
|
|
4095
4102
|
with sqlite_connection(self.store.central_database) as connection:
|
|
4096
4103
|
current_grant = merge_delegation.load(connection, state.merge_delegation_id)
|
|
4097
|
-
|
|
4104
|
+
current_selection = bool(current_grant and merge_delegation.target_selection_permits(connection, current_grant))
|
|
4105
|
+
if (current_grant is None or not current_selection or not current_grant.permits(
|
|
4098
4106
|
project_id=str(scope[0]), repository_id=str(scope[1]),
|
|
4099
4107
|
mission_id=str(accepted.get("mission_id")), mission_revision=str(mission_revision),
|
|
4100
4108
|
role=state.transaction_kind, base_branch="main",
|
|
@@ -4108,7 +4116,9 @@ First implementation pull-request publication gate:
|
|
|
4108
4116
|
policy_reader = getattr(self.github, "_autonomous_effective_policy", None)
|
|
4109
4117
|
if not callable(policy_reader):
|
|
4110
4118
|
return None
|
|
4111
|
-
current_policy = policy_reader()
|
|
4119
|
+
current_policy = (policy_reader(assurance_profile=selected_profile)
|
|
4120
|
+
if grant.assurance_profile_id == merge_delegation.REPOSITORY_ASSURANCE_ID
|
|
4121
|
+
else policy_reader())
|
|
4112
4122
|
if (not isinstance(current_policy, dict)
|
|
4113
4123
|
or current_policy.get("digest") != current_grant.assurance_policy_digest):
|
|
4114
4124
|
return None
|
|
@@ -4118,7 +4128,21 @@ First implementation pull-request publication gate:
|
|
|
4118
4128
|
attempted = replace(state, delegated_merge_attempt=attempt,
|
|
4119
4129
|
delegated_merge_actor_reference=None)
|
|
4120
4130
|
self.store.save(attempted)
|
|
4121
|
-
|
|
4131
|
+
# Linearize revocation against the external merge call. A revoke
|
|
4132
|
+
# committed before this lock is observed; one waiting for this lock
|
|
4133
|
+
# can only commit after the bounded call has returned.
|
|
4134
|
+
with sqlite_connection(self.store.central_database) as authority:
|
|
4135
|
+
authority.execute("BEGIN IMMEDIATE")
|
|
4136
|
+
final_grant = merge_delegation.load(authority, state.merge_delegation_id)
|
|
4137
|
+
if (final_grant is None or not final_grant.permits(
|
|
4138
|
+
project_id=str(scope[0]), repository_id=str(scope[1]),
|
|
4139
|
+
mission_id=str(accepted.get("mission_id")),
|
|
4140
|
+
mission_revision=str(mission_revision),
|
|
4141
|
+
role=state.transaction_kind, base_branch="main")
|
|
4142
|
+
or not merge_delegation.target_selection_permits(authority, final_grant)
|
|
4143
|
+
or final_grant != current_grant):
|
|
4144
|
+
raise ValueError("merge authority changed before the external call")
|
|
4145
|
+
self.github.merge(fresh.number, expected_head_sha=expected_head)
|
|
4122
4146
|
merged = self.github.pull_request(fresh.number)
|
|
4123
4147
|
self.repository.refresh_main_reference(self.root)
|
|
4124
4148
|
if (merged.state == "MERGED" and merged.head_sha == expected_head
|
|
@@ -360,10 +360,17 @@ class GhCliClient:
|
|
|
360
360
|
except RuntimeError as error:
|
|
361
361
|
raise RunnerError("Pull request Markdown could not be normalized.") from error
|
|
362
362
|
return True
|
|
363
|
-
def _autonomous_effective_policy(self) -> dict[str, object]:
|
|
363
|
+
def _autonomous_effective_policy(self, *, assurance_profile: str = "qualification-autonomous-qs@1") -> dict[str, object]:
|
|
364
364
|
"""Read all active main rules and classic protection without inferring zero from errors."""
|
|
365
|
-
if
|
|
365
|
+
if (assurance_profile == "qualification-autonomous-qs@1"
|
|
366
|
+
and self.repository != "pcvantol/forge-mission-qualification"):
|
|
366
367
|
raise RunnerError("Autonomous assurance profile has a different repository scope.")
|
|
368
|
+
if assurance_profile not in {"qualification-autonomous-qs@1", "repository-autonomous-qs@1"}:
|
|
369
|
+
raise RunnerError("Unknown delegated merge assurance profile.")
|
|
370
|
+
repository_profile = assurance_profile == "repository-autonomous-qs@1"
|
|
371
|
+
if repository_profile and (not isinstance(self.repository, str)
|
|
372
|
+
or re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", self.repository) is None):
|
|
373
|
+
raise RunnerError("Autonomous assurance requires an exact repository.")
|
|
367
374
|
try:
|
|
368
375
|
raw_rules = json.loads(self._github("api", f"repos/{self.repository}/rules/branches/main?per_page=100"))
|
|
369
376
|
except (RuntimeError, json.JSONDecodeError) as error:
|
|
@@ -415,6 +422,13 @@ class GhCliClient:
|
|
|
415
422
|
required_apps: dict[str, set[int]] = {}
|
|
416
423
|
strict_values: list[bool] = []
|
|
417
424
|
required_approvals: list[int] = []
|
|
425
|
+
required_thread_resolution = False
|
|
426
|
+
classic_conversations = classic.get("required_conversation_resolution") if classic else None
|
|
427
|
+
if repository_profile and classic_conversations is not None:
|
|
428
|
+
if (not isinstance(classic_conversations, dict)
|
|
429
|
+
or not isinstance(classic_conversations.get("enabled"), bool)):
|
|
430
|
+
raise RunnerError("Classic review-thread policy is incomplete.")
|
|
431
|
+
required_thread_resolution = classic_conversations["enabled"]
|
|
418
432
|
classic_checks = classic.get("required_status_checks") if classic else None
|
|
419
433
|
if classic_checks is not None:
|
|
420
434
|
if not isinstance(classic_checks, dict) or not isinstance(classic_checks.get("strict"), bool):
|
|
@@ -442,9 +456,10 @@ class GhCliClient:
|
|
|
442
456
|
if (isinstance(count, bool) or not isinstance(count, int) or count < 0
|
|
443
457
|
or classic_reviews.get("require_code_owner_reviews") is True
|
|
444
458
|
or classic_reviews.get("require_last_push_approval") is True
|
|
445
|
-
or classic_reviews.get("required_review_thread_resolution") is True):
|
|
459
|
+
or (classic_reviews.get("required_review_thread_resolution") is True and not repository_profile)):
|
|
446
460
|
raise RunnerError("Classic review policy requires unsupported or unknown approval evidence.")
|
|
447
461
|
required_approvals.append(count)
|
|
462
|
+
required_thread_resolution |= classic_reviews.get("required_review_thread_resolution") is True
|
|
448
463
|
supported = {"required_status_checks", "pull_request", "non_fast_forward", "deletion", "required_linear_history"}
|
|
449
464
|
for rule in verified_rules:
|
|
450
465
|
rule_type, parameters = rule.get("type"), rule.get("parameters")
|
|
@@ -467,13 +482,28 @@ class GhCliClient:
|
|
|
467
482
|
elif rule_type == "pull_request":
|
|
468
483
|
if not isinstance(parameters, dict):
|
|
469
484
|
raise RunnerError("Ruleset pull-request policy is incomplete.")
|
|
485
|
+
if repository_profile:
|
|
486
|
+
supported_parameters = {
|
|
487
|
+
"allowed_merge_methods", "dismiss_stale_reviews_on_push",
|
|
488
|
+
"require_code_owner_review", "require_extra_approval_for_unattributed_changes",
|
|
489
|
+
"require_last_push_approval", "required_approving_review_count",
|
|
490
|
+
"required_review_thread_resolution", "required_reviewers",
|
|
491
|
+
}
|
|
492
|
+
if (set(parameters) - supported_parameters
|
|
493
|
+
or parameters.get("require_extra_approval_for_unattributed_changes") is True
|
|
494
|
+
or parameters.get("require_last_push_approval") is True
|
|
495
|
+
or ("allowed_merge_methods" in parameters
|
|
496
|
+
and (not isinstance(parameters["allowed_merge_methods"], list)
|
|
497
|
+
or "squash" not in parameters["allowed_merge_methods"]))):
|
|
498
|
+
raise RunnerError("Ruleset pull-request policy has an unsupported merge condition.")
|
|
470
499
|
count = parameters.get("required_approving_review_count")
|
|
471
500
|
if (isinstance(count, bool) or not isinstance(count, int) or count < 0
|
|
472
501
|
or parameters.get("require_code_owner_review") is True
|
|
473
|
-
or parameters.get("required_review_thread_resolution") is True
|
|
502
|
+
or (parameters.get("required_review_thread_resolution") is True and not repository_profile)
|
|
474
503
|
or parameters.get("required_reviewers")):
|
|
475
504
|
raise RunnerError("Ruleset review policy requires unsupported or unknown approval evidence.")
|
|
476
505
|
required_approvals.append(count)
|
|
506
|
+
required_thread_resolution |= parameters.get("required_review_thread_resolution") is True
|
|
477
507
|
if not required_checks or not strict_values or not all(strict_values):
|
|
478
508
|
raise RunnerError("Autonomous assurance requires strict protected status checks.")
|
|
479
509
|
effective_rule_types = {rule.get("type") for rule in verified_rules}
|
|
@@ -484,16 +514,74 @@ class GhCliClient:
|
|
|
484
514
|
policy = {"source": "github-effective-main", "classic": classic,
|
|
485
515
|
"rulesets": verified_rules, "required_checks": sorted(required_checks),
|
|
486
516
|
"required_apps": {name: sorted(apps) for name, apps in sorted(required_apps.items())},
|
|
487
|
-
"required_approvals": max(required_approvals, default=0), "strict_checks": True
|
|
488
|
-
|
|
517
|
+
"required_approvals": max(required_approvals, default=0), "strict_checks": True,
|
|
518
|
+
"required_thread_resolution": required_thread_resolution}
|
|
519
|
+
if repository_profile:
|
|
520
|
+
# GitHub embeds observation URLs and other changing metadata in
|
|
521
|
+
# protection responses. Bind only the effective rule semantics.
|
|
522
|
+
classic_binding = None
|
|
523
|
+
if classic is not None:
|
|
524
|
+
classic_binding = {
|
|
525
|
+
"enforce_admins": classic["enforce_admins"]["enabled"],
|
|
526
|
+
"allow_force_pushes": classic["allow_force_pushes"]["enabled"],
|
|
527
|
+
"allow_deletions": classic["allow_deletions"]["enabled"],
|
|
528
|
+
"required_conversation_resolution": None if classic_conversations is None
|
|
529
|
+
else classic_conversations["enabled"],
|
|
530
|
+
"required_status_checks": None if classic_checks is None else {
|
|
531
|
+
"strict": classic_checks["strict"],
|
|
532
|
+
"contexts": sorted(classic_checks["contexts"]),
|
|
533
|
+
"checks": sorted(({"context": item["context"],
|
|
534
|
+
"app_id": item.get("app_id")}
|
|
535
|
+
for item in classic_checks["checks"]),
|
|
536
|
+
key=lambda item: (item["context"], str(item["app_id"]))),
|
|
537
|
+
},
|
|
538
|
+
"required_pull_request_reviews": None if classic_reviews is None else {
|
|
539
|
+
"required_approving_review_count": classic_reviews["required_approving_review_count"],
|
|
540
|
+
"dismiss_stale_reviews": classic_reviews.get("dismiss_stale_reviews", False),
|
|
541
|
+
"require_code_owner_reviews": classic_reviews.get("require_code_owner_reviews", False),
|
|
542
|
+
"require_last_push_approval": classic_reviews.get("require_last_push_approval", False),
|
|
543
|
+
"required_review_thread_resolution": classic_reviews.get("required_review_thread_resolution", False),
|
|
544
|
+
},
|
|
545
|
+
}
|
|
546
|
+
stable_rules = []
|
|
547
|
+
for item in verified_rules:
|
|
548
|
+
parameters = item.get("parameters")
|
|
549
|
+
if isinstance(parameters, dict):
|
|
550
|
+
parameters = dict(parameters)
|
|
551
|
+
if item["type"] == "required_status_checks":
|
|
552
|
+
parameters["required_status_checks"] = sorted(
|
|
553
|
+
parameters["required_status_checks"],
|
|
554
|
+
key=lambda check: (check["context"], str(check.get("integration_id"))))
|
|
555
|
+
elif item["type"] == "pull_request" and "allowed_merge_methods" in parameters:
|
|
556
|
+
parameters["allowed_merge_methods"] = sorted(parameters["allowed_merge_methods"])
|
|
557
|
+
stable_rules.append({"ruleset_id": item["ruleset_id"],
|
|
558
|
+
"type": item["type"], "parameters": parameters})
|
|
559
|
+
policy_binding = {
|
|
560
|
+
"version": "repository-effective-main/v1",
|
|
561
|
+
"repository": self.repository,
|
|
562
|
+
"classic": classic_binding,
|
|
563
|
+
"rulesets": sorted(stable_rules,
|
|
564
|
+
key=lambda item: (item["ruleset_id"], item["type"],
|
|
565
|
+
json.dumps(item["parameters"], sort_keys=True))),
|
|
566
|
+
"required_checks": policy["required_checks"],
|
|
567
|
+
"required_apps": policy["required_apps"],
|
|
568
|
+
"required_approvals": policy["required_approvals"],
|
|
569
|
+
"required_thread_resolution": required_thread_resolution,
|
|
570
|
+
"strict_checks": True,
|
|
571
|
+
}
|
|
572
|
+
else:
|
|
573
|
+
# Preserve the original synthetic profile's immutable digest semantics.
|
|
574
|
+
policy_binding = {key: value for key, value in policy.items()
|
|
575
|
+
if key != "required_thread_resolution"}
|
|
576
|
+
policy["digest"] = "sha256:" + hashlib.sha256(json.dumps(policy_binding, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
|
489
577
|
return policy
|
|
490
578
|
|
|
491
579
|
def delegated_merge_qualification(self, number: int, head_sha: str, *, assurance_profile: str | None = None) -> dict[str, object]:
|
|
492
580
|
"""Fresh protected checks and independent head-bound GitHub reviews."""
|
|
493
|
-
autonomous = assurance_profile
|
|
581
|
+
autonomous = assurance_profile in {"qualification-autonomous-qs@1", "repository-autonomous-qs@1"}
|
|
494
582
|
if assurance_profile is not None and not autonomous:
|
|
495
583
|
raise RunnerError("Unknown delegated merge assurance profile.")
|
|
496
|
-
policy = self._autonomous_effective_policy() if autonomous else None
|
|
584
|
+
policy = self._autonomous_effective_policy(assurance_profile=assurance_profile) if autonomous else None
|
|
497
585
|
if policy is None:
|
|
498
586
|
checks = self.qualification_for_exact_head(number, head_sha)
|
|
499
587
|
else:
|
|
@@ -506,12 +594,24 @@ class GhCliClient:
|
|
|
506
594
|
if not isinstance(raw, dict) or raw.get("headRefOid") != head_sha or raw.get("baseRefName") != "main":
|
|
507
595
|
raise RunnerError("Autonomous PR qualification has a changed head or base.")
|
|
508
596
|
rollup = raw.get("statusCheckRollup")
|
|
509
|
-
if not isinstance(rollup, list) or not rollup
|
|
510
|
-
not isinstance(item, dict) or item.get("status") != "COMPLETED"
|
|
511
|
-
or item.get("conclusion") not in {"SUCCESS", "NEUTRAL", "SKIPPED"} for item in rollup
|
|
512
|
-
):
|
|
597
|
+
if not isinstance(rollup, list) or not rollup:
|
|
513
598
|
raise RunnerError("Autonomous PR required checks are incomplete or failed.")
|
|
514
|
-
observed =
|
|
599
|
+
observed: set[str] = set()
|
|
600
|
+
for item in rollup:
|
|
601
|
+
if not isinstance(item, dict):
|
|
602
|
+
raise RunnerError("Autonomous PR required checks are incomplete or failed.")
|
|
603
|
+
if item.get("__typename") == "StatusContext" or (
|
|
604
|
+
"context" in item and "state" in item and "name" not in item
|
|
605
|
+
):
|
|
606
|
+
name = item.get("context")
|
|
607
|
+
passed = item.get("state") == "SUCCESS"
|
|
608
|
+
else:
|
|
609
|
+
name = item.get("name")
|
|
610
|
+
passed = (item.get("status") == "COMPLETED" and
|
|
611
|
+
item.get("conclusion") in {"SUCCESS", "NEUTRAL", "SKIPPED"})
|
|
612
|
+
if not isinstance(name, str) or not name or not passed:
|
|
613
|
+
raise RunnerError("Autonomous PR required checks are incomplete or failed.")
|
|
614
|
+
observed.add(name)
|
|
515
615
|
if not set(policy["required_checks"]) <= observed:
|
|
516
616
|
raise RunnerError("Autonomous PR is missing effective required checks.")
|
|
517
617
|
if policy["required_apps"]:
|
|
@@ -562,17 +662,37 @@ class GhCliClient:
|
|
|
562
662
|
raise RunnerError("Delegated merge requires protected independent review policy.")
|
|
563
663
|
latest: dict[str, tuple[str, str]] = {}
|
|
564
664
|
for review in reviews:
|
|
565
|
-
if not isinstance(review, dict):
|
|
566
|
-
|
|
567
|
-
reviewer =
|
|
568
|
-
|
|
569
|
-
|
|
665
|
+
if not isinstance(review, dict) or not isinstance(review.get("user"), dict):
|
|
666
|
+
raise RunnerError("Delegated merge review evidence is incomplete.")
|
|
667
|
+
reviewer = review["user"].get("login")
|
|
668
|
+
decision = review.get("state")
|
|
669
|
+
if not isinstance(reviewer, str) or not reviewer or not isinstance(decision, str):
|
|
670
|
+
raise RunnerError("Delegated merge review evidence is incomplete.")
|
|
671
|
+
latest[reviewer] = (decision, str(review.get("commit_id")))
|
|
672
|
+
if any(decision == "CHANGES_REQUESTED" for decision, _commit in latest.values()):
|
|
673
|
+
raise RunnerError("Delegated merge has an unresolved changes-requested review.")
|
|
570
674
|
approvals = sorted(
|
|
571
675
|
reviewer for reviewer, (decision, commit) in latest.items()
|
|
572
676
|
if reviewer != author and decision == "APPROVED" and commit == head_sha
|
|
573
677
|
)
|
|
574
678
|
if len(approvals) < required:
|
|
575
679
|
raise RunnerError("Delegated merge lacks required independent approvals for this exact head.")
|
|
680
|
+
if policy is not None and policy["required_thread_resolution"]:
|
|
681
|
+
owner, repository_name = self.repository.split("/", 1)
|
|
682
|
+
query = ("query { repository(owner:\"" + owner + "\",name:\"" + repository_name
|
|
683
|
+
+ "\") { pullRequest(number:" + str(number)
|
|
684
|
+
+ ") { reviewThreads(first:100) { totalCount nodes { isResolved } } } } }")
|
|
685
|
+
try:
|
|
686
|
+
response = json.loads(self._github("api", "graphql", "-f", "query=" + query))
|
|
687
|
+
threads = response["data"]["repository"]["pullRequest"]["reviewThreads"]
|
|
688
|
+
except (RuntimeError, ValueError, KeyError, TypeError) as error:
|
|
689
|
+
raise RunnerError("Required review-thread resolution could not be read.") from error
|
|
690
|
+
nodes = threads.get("nodes") if isinstance(threads, dict) else None
|
|
691
|
+
if (not isinstance(nodes, list) or not isinstance(threads.get("totalCount"), int)
|
|
692
|
+
or threads["totalCount"] != len(nodes) or len(nodes) >= 100
|
|
693
|
+
or any(not isinstance(item, dict) or item.get("isResolved") is not True
|
|
694
|
+
for item in nodes)):
|
|
695
|
+
raise RunnerError("Delegated merge has unresolved or unreadable review threads.")
|
|
576
696
|
return {**checks, "reviewers": approvals, "required_approvals": required}
|
|
577
697
|
|
|
578
698
|
def merge(self, number: int, *, expected_head_sha: str | None = None) -> None:
|