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