forge-autonomy 2.7.25__tar.gz → 2.7.26__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.
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/PKG-INFO +1 -1
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/__main__.py +58 -0
- forge_autonomy-2.7.26/forge/completion/host_control_observer.py +164 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/completion/mission.py +53 -1
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/execution/loop.py +44 -2
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/governance_authority.py +11 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/intake.py +26 -14
- forge_autonomy-2.7.26/forge/mission_cli.py +353 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/criterion_assessment.py +42 -5
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/criterion_observation.py +1 -1
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/execution_host.py +1 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/runtime/dynamic_mission.py +53 -2
- forge_autonomy-2.7.26/forge/runtime/mission_controller.py +242 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/runtime/operational_reset.py +6 -2
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/runtime/runner.py +11 -1
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/scheduler/ep_http_adapter.py +91 -12
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/scheduler/ep_v12.py +18 -7
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge_autonomy.egg-info/PKG-INFO +1 -1
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge_autonomy.egg-info/SOURCES.txt +6 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/product-version.json +1 -1
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_bootstrap_mission_runner.py +17 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_criterion_ep_http_boundary.py +4 -3
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_dynamic_mission_capability.py +55 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_ep_http_adapter.py +135 -1
- forge_autonomy-2.7.26/tests/test_host_control_completion.py +442 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_installed_dynamic_mission_runtime.py +79 -1
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_installed_forge_update.py +297 -4
- forge_autonomy-2.7.26/tests/test_mission_controller.py +218 -0
- forge_autonomy-2.7.26/tests/test_mission_status.py +35 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/MANIFEST.in +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/README.md +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/_version.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/action_derivation_canary_closure.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/action_derivation_evidence.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/action_derivation_qualification.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/action_derivation_reattempt.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/ai_architect/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/ai_architect/provider_registry.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/architecture/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/architecture/advisor.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/architecture/presentation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/architecture/workspace.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/autonomous_orchestrator.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/business/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/business/advisor.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/business/ingress.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/business/presentation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/business/workspace.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/canary.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/capabilities/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/capabilities/delegation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/completion/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/completion/assessor.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/completion/repository_observer.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/constitutional/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/constitutional/assessor.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/core/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/core/json_store.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/decision_evidence/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/decision_evidence/repository.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/dispatcher/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/dispatcher/dispatcher.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/execution/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/execution/presentation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/execution_host_configuration.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/foundation/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/foundation/loader.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/generation_two.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/generation_two_execution.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/governance/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/governance/execution_policy.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/governance/profiles.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/integration/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/integration/coordinator.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/integration/repository.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/knowledge/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/knowledge/consumer.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/knowledge/registry.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/lifecycle/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/lifecycle/store.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/mission_amendment.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/mission_scheduler.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/action.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/action_derivation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/agent_policy.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/ai_architect_provider.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/ai_architect_session.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/ai_provider_registry.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/architecture_mission.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/architecture_reasoning.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/architecture_review.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/codex_runtime_prompt.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/constitutional_validation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/decision_evidence.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/foundation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/historical_intent.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/integration.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/intent.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/intent_authoring.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/mission.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/mission_candidate.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/mission_completion.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/mission_planner.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/mission_recommendation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/phase_completion.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/planning.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/producer.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/prompt_artifact.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/proposal.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/runtime_prompt.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/models/solution_template.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/operator_identity.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/planner/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/planner/action_derivation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/planner/codex_cli_session.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/planner/durable_derivation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/planner/engine.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/planner/openai_responses.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/planner/provider_adapter.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/planning/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/planning/loader.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/planning/registry.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/portfolio_intelligence.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/programme_authorization.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/prompts/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/prompts/codex_cli.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/prompts/generator.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/proposals/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/proposals/generator.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/provider_security.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/qualification/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/qualification/bootstrap_sequence.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/qualification/criterion_completion.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/qualification/generation_one.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/qualification/installed_smoke.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/recommendations/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/recommendations/engine.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/repository_truth.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/review/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/review/engine.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/runtime/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/runtime/bootstrap.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/runtime/data_root.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/runtime/database.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/runtime/evidence.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/runtime/execution_context.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/runtime/health.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/runtime/service.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/scheduler/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/scheduler/adapter.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/scheduler/scheduler.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/capability-registry-1.0.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/capability.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/common.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/decision-evidence-1.0.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/engineering-goal-0.5.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/engineering-increment-proposal-0.5.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/engineering-mode.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/engineering-plan-0.5.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/engineering-platform-peer-configuration-1.0.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/engineering-platform-peer-configuration-1.1.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/engineering-prompt-artifact-0.7.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/engineering-proposal-0.6.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/evidence-reference-0.5.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/foundation-document.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/governance-profile.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/knowledge-source-0.4.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/knowledge-source.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/mission-candidate-1.0.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/mission-candidate-1.1.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/phase-completion-1.0.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/planning-document-0.5.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/repository-catalog.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/repository.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/solution-template-1.0.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/schemas/workspace.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/secure_store.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/solutions/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/solutions/catalogue.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/solutions/generator.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/state/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/state/mission_state.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge/workspace/__init__.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge_autonomy.egg-info/dependency_links.txt +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge_autonomy.egg-info/entry_points.txt +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/forge_autonomy.egg-info/top_level.txt +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/pyproject.toml +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/capability-registry-1.0.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/capability.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/common.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/decision-evidence-1.0.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/engineering-goal-0.5.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/engineering-increment-proposal-0.5.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/engineering-mode.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/engineering-plan-0.5.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/engineering-prompt-artifact-0.7.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/engineering-proposal-0.6.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/evidence-reference-0.5.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/foundation-document.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/governance-profile.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/knowledge-source-0.4.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/knowledge-source.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/mission-candidate-1.0.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/mission-candidate-1.1.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/phase-completion-1.0.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/planning-document-0.5.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/repository-catalog.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/repository.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/solution-template-1.0.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/schemas/workspace.schema.json +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/setup.cfg +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_action_derivation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_action_derivation_canary_closure.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_action_derivation_evidence.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_action_derivation_qualification.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_adaptive_action_sizing_design.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_agent_policy.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_aggregate_health_roadmap.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_ai_architect_provider.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_ai_architect_session.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_ai_development_projection.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_ai_provider_registry.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_architecture_reasoning.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_architecture_review_engine.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_architecture_workspace.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_autonomous_orchestrator.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_bootstrap_adapter.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_bootstrap_mission_canary.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_bootstrap_mission_scheduler.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_bootstrap_sequence_qualification.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_business_governance_ingress.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_business_workspace.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_canonical_data_root.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_capability_delegation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_codex_cli_runtime_prompt.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_codex_cli_session.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_constitutional_validation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_criterion_assessment_contract.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_criterion_planning_context.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_decision_evidence.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_default_project_quality_policy.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_distribution_identity.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_durable_action_derivation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_engineering_action_architecture.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_engineering_intent_authoring.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_engineering_intent_lifecycle.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_engineering_mission.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_engineering_planning.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_engineering_prompt_artifacts.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_engineering_proposals.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_execution_context.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_execution_host_configuration.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_execution_host_contract.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_execution_loop.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_existing_release_reconciliation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_forge_console_admin_parity_contract.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_forge_console_configuration_contract.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_forge_console_hosting_contract.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_forge_operations_console_roadmap.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_forge_outer_loop_ci_roadmap.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_forge_v1_bootstrap_scheduler_contract.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_forge_v1_implementation_dag.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_foundation_loader.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_foundation_models.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_generation_one_bootstrap_qualification.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_generation_two_execution.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_generation_two_mission_intake.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_governance_authority.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_governed_coverage.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_historical_engineering_intent.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_inner_loop_ci_contract.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_integration_coordination.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_knowledge_consumption.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_live_project_roadmap_contract.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_mission_completion_evaluator.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_mission_dispatcher.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_mission_planner.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_mission_recommendation_engine.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_mission_recommendation_lifecycle.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_mission_runtime_projection.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_mission_runtime_scheduler.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_mission_state_store.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_openai_action_derivation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_operational_reset.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_operator_identity.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_phase_completion.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_platform_contract_documents.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_portfolio_intelligence_operation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_producer_contract.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_product_version_operations.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_production_release_workflow.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_productization_reconciliation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_programme_authorization.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_provider_security.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_pypi_distribution_readback.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_recovered_partial_completion.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_release_operation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_repository_truth.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_role_aware_conversations_contract.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_runtime_bootstrap.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_runtime_database.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_runtime_evidence.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_runtime_health.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_runtime_instance_persistence.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_runtime_prompt_generation.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_runtime_service.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_runtime_status_projection.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_scheduler_dependency_boundary.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_schemas.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_secure_store.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_solution_templates.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_substantive_mission_completion.py +0 -0
- {forge_autonomy-2.7.25 → forge_autonomy-2.7.26}/tests/test_terminal_continuation_replay.py +0 -0
|
@@ -122,6 +122,29 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
122
122
|
finish_reset.add_argument("--verification-digest")
|
|
123
123
|
finish_reset.add_argument("--cancel-before-apply", action="store_true")
|
|
124
124
|
subparsers.add_parser("status", help="print read-only runtime status as JSON")
|
|
125
|
+
mission = subparsers.add_parser("mission", help="govern and run one selected Mission")
|
|
126
|
+
mission_commands = mission.add_subparsers(dest="mission_command", required=True)
|
|
127
|
+
for name, description in (
|
|
128
|
+
("inspect", "validate Mission input and evidence fit without allocation"),
|
|
129
|
+
("approve-business", "record canonical Business approval"),
|
|
130
|
+
("approve-architecture", "record canonical Architecture approval"),
|
|
131
|
+
("admit", "allocate and admit the canonically approved Mission"),
|
|
132
|
+
):
|
|
133
|
+
command = mission_commands.add_parser(name, help=description)
|
|
134
|
+
command.add_argument("--input", required=True)
|
|
135
|
+
for name, description in (
|
|
136
|
+
("run", "start once and control the selected Mission to a terminal or declared stop"),
|
|
137
|
+
("reopen", "continue the same durable Mission execution"),
|
|
138
|
+
("status", "read the exact Mission status and result"),
|
|
139
|
+
("stop", "request a controlled foreground stop"),
|
|
140
|
+
):
|
|
141
|
+
command = mission_commands.add_parser(name, help=description)
|
|
142
|
+
command.add_argument("--mission-id", required=True)
|
|
143
|
+
if name == "run":
|
|
144
|
+
command.add_argument("--repository-truth", required=True)
|
|
145
|
+
if name in {"run", "reopen"}:
|
|
146
|
+
command.add_argument("--poll-seconds", type=float, default=1.0)
|
|
147
|
+
command.add_argument("--maximum-wait-seconds", type=float, default=3600.0)
|
|
125
148
|
execution_host = subparsers.add_parser("execution-host", help="manage the selected Execution Host peer")
|
|
126
149
|
execution_host_commands = execution_host.add_subparsers(dest="execution_host_command", required=True)
|
|
127
150
|
configure = execution_host_commands.add_parser("configure", help="persist one explicit EP peer binding")
|
|
@@ -159,6 +182,41 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
159
182
|
help="bounded interactive setup timeout in seconds (maximum: 120)",
|
|
160
183
|
)
|
|
161
184
|
args = parser.parse_args(argv)
|
|
185
|
+
if args.command == "mission":
|
|
186
|
+
from . import mission_cli
|
|
187
|
+
try:
|
|
188
|
+
command = args.mission_command
|
|
189
|
+
if command == "inspect":
|
|
190
|
+
result = mission_cli.inspect(args.input)
|
|
191
|
+
elif command in {"approve-business", "approve-architecture"}:
|
|
192
|
+
if args.data_root is None:
|
|
193
|
+
raise ValueError("--data-root is required for Mission governance")
|
|
194
|
+
result = mission_cli.approve(args.data_root, args.input,
|
|
195
|
+
"business" if command == "approve-business" else "architecture")
|
|
196
|
+
elif command == "admit":
|
|
197
|
+
if args.data_root is None:
|
|
198
|
+
raise ValueError("--data-root is required for Mission admission")
|
|
199
|
+
result = mission_cli.admit(args.data_root, args.input)
|
|
200
|
+
elif command == "status":
|
|
201
|
+
result = mission_cli.status(args.data_root, args.mission_id)
|
|
202
|
+
elif command == "stop":
|
|
203
|
+
result = mission_cli.stop(args.data_root, args.mission_id)
|
|
204
|
+
else:
|
|
205
|
+
if args.data_root is None:
|
|
206
|
+
raise ValueError("--data-root is required for Mission execution")
|
|
207
|
+
result = mission_cli.run(args.data_root, args.mission_id,
|
|
208
|
+
truth_path=args.repository_truth if command == "run" else None,
|
|
209
|
+
poll_seconds=args.poll_seconds, maximum_wait_seconds=args.maximum_wait_seconds)
|
|
210
|
+
print(json.dumps(result, sort_keys=True))
|
|
211
|
+
if command == "inspect":
|
|
212
|
+
return 0 if result["status"] == "VALID" else 2
|
|
213
|
+
if command == "stop":
|
|
214
|
+
return 0 if result["stop_requested"] else 2
|
|
215
|
+
if command in {"run", "reopen"}:
|
|
216
|
+
return 0 if result["status"] == "COMPLETED" else 2
|
|
217
|
+
return 0
|
|
218
|
+
except (OSError, ValueError, KeyError, sqlite3.Error, RuntimeError, PermissionError) as error:
|
|
219
|
+
return _failure("mission " + args.mission_command, error)
|
|
162
220
|
if args.command == "server" and args.server_command == "init":
|
|
163
221
|
from .runtime import RuntimeBootstrap
|
|
164
222
|
database = RuntimeBootstrap(data_root=args.data_root, forge_version=canonical_version()).open()
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Interpret EP-owned validation records without executing a command."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from hashlib import sha256
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from typing import Any, Mapping
|
|
8
|
+
|
|
9
|
+
from forge.models.criterion_observation import CriterionObservation, canonical_digest
|
|
10
|
+
from forge.models.mission_completion import mission_criterion_id
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_DIGEST = re.compile(r"sha256:[0-9a-f]{64}\Z")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _canonical(value: object) -> str:
|
|
17
|
+
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _valid_digest(value: object) -> bool:
|
|
21
|
+
return isinstance(value, str) and _DIGEST.fullmatch(value) is not None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def approved_observation_selector(requirement) -> str | None:
|
|
25
|
+
"""Identify an EP unittest observation from its exact approved binding."""
|
|
26
|
+
try:
|
|
27
|
+
command = json.loads(requirement.command)
|
|
28
|
+
except (TypeError, ValueError):
|
|
29
|
+
return None
|
|
30
|
+
if (not isinstance(command, list) or len(command) != 4
|
|
31
|
+
or command[:3] != ["{python}", "-m", "unittest"]
|
|
32
|
+
or not isinstance(command[3], str)):
|
|
33
|
+
return None
|
|
34
|
+
selector = command[3]
|
|
35
|
+
if (len(selector) > 100 or re.fullmatch(
|
|
36
|
+
r"[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)+", selector) is None):
|
|
37
|
+
return None
|
|
38
|
+
validation_id = "unittest_selector_" + sha256(selector.encode("utf-8")).hexdigest()[:16]
|
|
39
|
+
if (requirement.validation_id != validation_id
|
|
40
|
+
or requirement.control_identity != "python3 -m unittest " + selector
|
|
41
|
+
or requirement.control_category != "repository"
|
|
42
|
+
or requirement.validation_profile_version != "1.0"
|
|
43
|
+
or requirement.profile_reference != "validation-profile-registry:FULL@1.0"):
|
|
44
|
+
return None
|
|
45
|
+
return selector
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _control_matches(requirement, context: Mapping[str, Any], run_id: str,
|
|
49
|
+
validated_revision: str | None) -> tuple[bool, str, dict[str, object] | None]:
|
|
50
|
+
if not requirement.control_definition_digest:
|
|
51
|
+
return False, "APPROVED_CONTROL_DEFINITION_MISSING", None
|
|
52
|
+
version = context.get("contract_version")
|
|
53
|
+
if version not in {"1.0", "1.1"} or context.get("status") != "AVAILABLE":
|
|
54
|
+
return False, "HOST_CONTROL_EVIDENCE_UNAVAILABLE", None
|
|
55
|
+
currentness = context.get("currentness")
|
|
56
|
+
if (validated_revision is None or context.get("candidate_sha") != validated_revision
|
|
57
|
+
or context.get("profile_currentness_conflict") is not False
|
|
58
|
+
or not isinstance(currentness, int) or isinstance(currentness, bool) or currentness < 0
|
|
59
|
+
or not _valid_digest(context.get("profile_digest"))):
|
|
60
|
+
return False, "HOST_CONTROL_CANDIDATE_OR_PROFILE_MISMATCH", None
|
|
61
|
+
if (context.get("validation_profile_version") != requirement.validation_profile_version
|
|
62
|
+
or context.get("profile_reference") != requirement.profile_reference):
|
|
63
|
+
return False, "HOST_CONTROL_PROFILE_VERSION_MISMATCH", None
|
|
64
|
+
controls = context.get("controls")
|
|
65
|
+
required = context.get("required_validation_controls")
|
|
66
|
+
if not isinstance(controls, Mapping) or not isinstance(required, list):
|
|
67
|
+
return False, "HOST_CONTROL_BINDINGS_INVALID", None
|
|
68
|
+
observation_controls = context.get("observation_validation_controls", []) if version == "1.1" else []
|
|
69
|
+
if not isinstance(observation_controls, list):
|
|
70
|
+
return False, "HOST_CONTROL_BINDINGS_INVALID", None
|
|
71
|
+
required_binding = required.count(requirement.validation_id) == 1
|
|
72
|
+
observation_matches = [item for item in observation_controls
|
|
73
|
+
if isinstance(item, Mapping)
|
|
74
|
+
and item.get("validation_id") == requirement.validation_id]
|
|
75
|
+
observation_binding = (version == "1.1" and len(observation_matches) == 1
|
|
76
|
+
and approved_observation_selector(requirement) is not None)
|
|
77
|
+
if required_binding == observation_binding:
|
|
78
|
+
return False, "HOST_CONTROL_BINDING_MISSING_OR_DUPLICATE", None
|
|
79
|
+
command_identity = json.loads(requirement.command)
|
|
80
|
+
definition = {"validation_profile_version": context["validation_profile_version"],
|
|
81
|
+
"profile_reference": context["profile_reference"],
|
|
82
|
+
"validation_id": requirement.validation_id,
|
|
83
|
+
"category": requirement.control_category,
|
|
84
|
+
"control_identity": requirement.control_identity,
|
|
85
|
+
"command_identity": command_identity}
|
|
86
|
+
digest = "sha256:" + sha256(_canonical(definition).encode("utf-8")).hexdigest()
|
|
87
|
+
if digest != requirement.control_definition_digest:
|
|
88
|
+
return False, "HOST_CONTROL_DEFINITION_MISMATCH", None
|
|
89
|
+
control = (controls.get(requirement.validation_id) if required_binding
|
|
90
|
+
else observation_matches[0])
|
|
91
|
+
if not isinstance(control, Mapping):
|
|
92
|
+
return False, "HOST_CONTROL_RECORD_MISSING", None
|
|
93
|
+
if (control.get("validation_id") != requirement.validation_id
|
|
94
|
+
or control.get("category") != requirement.control_category
|
|
95
|
+
or control.get("control_identity") != requirement.control_identity
|
|
96
|
+
or control.get("control_definition_digest") != digest
|
|
97
|
+
or control.get("required_for_profile") is not required_binding
|
|
98
|
+
or control.get("currentness") != currentness):
|
|
99
|
+
return False, "HOST_CONTROL_RECORD_CONFLICT", None
|
|
100
|
+
if (control.get("execution_status") != "EXECUTED" or control.get("result") != "PASS"
|
|
101
|
+
or control.get("evidence_authority") != "command_terminal"
|
|
102
|
+
or control.get("evidence_ref") != "command_terminal"
|
|
103
|
+
or control.get("exit_code") != 0
|
|
104
|
+
or not isinstance(control.get("command_id"), str) or not control["command_id"]):
|
|
105
|
+
return False, "HOST_CONTROL_NOT_EXECUTED_AND_PASSED", None
|
|
106
|
+
detail = control.get("result_detail")
|
|
107
|
+
if not isinstance(detail, Mapping):
|
|
108
|
+
return False, "HOST_CONTROL_RESULT_DETAIL_MISSING", None
|
|
109
|
+
if (detail.get("status") != "AVAILABLE" or detail.get("capture_status") != "AVAILABLE"
|
|
110
|
+
or detail.get("schema") != "deterministic-validation-result-detail-v1"
|
|
111
|
+
or detail.get("run_id") != run_id
|
|
112
|
+
or detail.get("command_id") != control["command_id"]
|
|
113
|
+
or detail.get("validation_id") != requirement.validation_id
|
|
114
|
+
or detail.get("exit_code") != 0
|
|
115
|
+
or not isinstance(detail.get("artifact_id"), str) or not detail["artifact_id"]
|
|
116
|
+
or not _valid_digest(detail.get("digest")) or not _valid_digest(detail.get("output_digest"))):
|
|
117
|
+
return False, "HOST_CONTROL_RESULT_DETAIL_INVALID", None
|
|
118
|
+
count = detail.get("test_count")
|
|
119
|
+
minimum_test_count = (max(1, requirement.minimum_test_count) if observation_binding
|
|
120
|
+
else requirement.minimum_test_count)
|
|
121
|
+
if minimum_test_count and (not isinstance(count, int) or isinstance(count, bool)
|
|
122
|
+
or count < minimum_test_count
|
|
123
|
+
or detail.get("test_count_source") != "unittest_terminal_summary"):
|
|
124
|
+
return False, "HOST_CONTROL_TEST_COUNT_INSUFFICIENT", None
|
|
125
|
+
return True, "APPROVED_HOST_CONTROL_EXECUTED_AND_PASSED", {
|
|
126
|
+
"validation_id": requirement.validation_id, "control_identity": requirement.control_identity,
|
|
127
|
+
"control_definition_digest": digest, "validation_profile_version": requirement.validation_profile_version,
|
|
128
|
+
"profile_reference": requirement.profile_reference, "category": requirement.control_category,
|
|
129
|
+
"candidate_sha": validated_revision, "run_id": run_id, "command_id": control["command_id"],
|
|
130
|
+
"result_detail_digest": detail["digest"], "output_digest": detail["output_digest"],
|
|
131
|
+
"test_count": count, "execution_status": "EXECUTED", "result": "PASS", "exit_code": 0,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class HostControlCriterionObserver:
|
|
136
|
+
def observe(self, mission, reference, evidence) -> tuple[CriterionObservation, ...]:
|
|
137
|
+
context = evidence.validation_controls
|
|
138
|
+
observations = []
|
|
139
|
+
for contract in mission.criterion_assessment_contracts:
|
|
140
|
+
for requirement in contract.requirements:
|
|
141
|
+
if requirement.kind != "host_control":
|
|
142
|
+
continue
|
|
143
|
+
control_context = context if isinstance(context, Mapping) else {}
|
|
144
|
+
delivered = reference.repository_revision
|
|
145
|
+
candidate = reference.candidate_revision
|
|
146
|
+
if (contract.validity_policy == "current_revision" and candidate != delivered
|
|
147
|
+
and control_context.get("candidate_sha") == candidate):
|
|
148
|
+
valid, reason, measured = False, "HOST_CONTROL_DELIVERY_REVISION_NOT_VALIDATED", None
|
|
149
|
+
else:
|
|
150
|
+
expected_revision = (delivered if contract.validity_policy == "current_revision" else
|
|
151
|
+
delivered if control_context.get("candidate_sha") == delivered else candidate)
|
|
152
|
+
valid, reason, measured = _control_matches(
|
|
153
|
+
requirement, control_context, evidence.host_run_id, expected_revision)
|
|
154
|
+
observations.append(CriterionObservation(
|
|
155
|
+
mission.id, canonical_digest(mission.to_dict()), mission_criterion_id(mission.id, contract.criterion),
|
|
156
|
+
contract.digest, requirement.requirement_id, reference.receipt_id, reference.action_id,
|
|
157
|
+
reference.report_id, reference.repository_revision, reference.repository_evidence_digest,
|
|
158
|
+
"host_control", requirement.control_identity, "ep-terminal-artifact",
|
|
159
|
+
reference.repository_evidence_digest if valid else None,
|
|
160
|
+
_canonical(measured) if valid else None, "PASS" if valid else "UNAVAILABLE", reason,
|
|
161
|
+
requirement_digest=requirement.digest, candidate_revision=reference.candidate_revision,
|
|
162
|
+
schema_version="1.1",
|
|
163
|
+
))
|
|
164
|
+
return tuple(observations)
|
|
@@ -67,6 +67,34 @@ def _canonical_execution_references(
|
|
|
67
67
|
return references
|
|
68
68
|
|
|
69
69
|
|
|
70
|
+
def _host_control_observation_matches(observation, requirement, validity_policy: str) -> bool:
|
|
71
|
+
if (observation.schema_version != "1.1" or observation.result != "PASS"
|
|
72
|
+
or observation.reason != "APPROVED_HOST_CONTROL_EXECUTED_AND_PASSED"
|
|
73
|
+
or observation.content_digest != observation.repository_evidence_digest
|
|
74
|
+
or observation.observed_json is None):
|
|
75
|
+
return False
|
|
76
|
+
try:
|
|
77
|
+
record = json.loads(observation.observed_json)
|
|
78
|
+
except (TypeError, ValueError):
|
|
79
|
+
return False
|
|
80
|
+
return (isinstance(record, dict)
|
|
81
|
+
and record.get("validation_id") == requirement.validation_id
|
|
82
|
+
and record.get("control_identity") == requirement.control_identity
|
|
83
|
+
and record.get("control_definition_digest") == requirement.control_definition_digest
|
|
84
|
+
and record.get("validation_profile_version") == requirement.validation_profile_version
|
|
85
|
+
and record.get("profile_reference") == requirement.profile_reference
|
|
86
|
+
and record.get("category") == requirement.control_category
|
|
87
|
+
and (record.get("candidate_sha") == observation.repository_revision
|
|
88
|
+
if validity_policy == "current_revision" else
|
|
89
|
+
record.get("candidate_sha") in {observation.candidate_revision, observation.repository_revision})
|
|
90
|
+
and record.get("execution_status") == "EXECUTED"
|
|
91
|
+
and record.get("result") == "PASS" and record.get("exit_code") == 0
|
|
92
|
+
and (requirement.minimum_test_count == 0 or
|
|
93
|
+
isinstance(record.get("test_count"), int)
|
|
94
|
+
and not isinstance(record["test_count"], bool)
|
|
95
|
+
and record["test_count"] >= requirement.minimum_test_count))
|
|
96
|
+
|
|
97
|
+
|
|
70
98
|
class MissionCompletionEvaluator:
|
|
71
99
|
"""Interpret approved predicates over Forge-observed facts; never provider PASS."""
|
|
72
100
|
|
|
@@ -132,7 +160,31 @@ class MissionCompletionEvaluator:
|
|
|
132
160
|
if contract.validity_policy == "current_revision":
|
|
133
161
|
candidates = [obs for obs in candidates if obs.repository_revision == truth.revision]
|
|
134
162
|
source = mission.repository_evidence_source
|
|
135
|
-
if requirement.kind
|
|
163
|
+
if requirement.kind == "host_control":
|
|
164
|
+
if not requirement.control_definition_digest:
|
|
165
|
+
result, why, matched = "UNSATISFIED", "UNSUPPORTED_AUTHORITATIVE_EVIDENCE_SOURCE", ()
|
|
166
|
+
elif any(obs.source_kind != "host_control" or obs.source_identity != requirement.control_identity
|
|
167
|
+
or obs.artifact_path != "ep-terminal-artifact"
|
|
168
|
+
or obs.requirement_digest != requirement.digest or obs.json_pointer != ""
|
|
169
|
+
for obs in candidates):
|
|
170
|
+
result, why, matched = "UNSATISFIED", "OBSERVATION_SOURCE_MISMATCH", ()
|
|
171
|
+
elif not candidates:
|
|
172
|
+
result, why, matched = "UNSATISFIED", "CURRENT_OBSERVATION_MISSING", ()
|
|
173
|
+
else:
|
|
174
|
+
if contract.validity_policy == "historical_delivery":
|
|
175
|
+
passing = [obs for index, obs in enumerate(candidates)
|
|
176
|
+
if _host_control_observation_matches(obs, requirement, contract.validity_policy)
|
|
177
|
+
and not any(later.repository_revision == obs.repository_revision
|
|
178
|
+
and not _host_control_observation_matches(later, requirement, contract.validity_policy)
|
|
179
|
+
for later in candidates[index + 1:])]
|
|
180
|
+
selected = passing[-1] if passing else candidates[-1]
|
|
181
|
+
else:
|
|
182
|
+
selected = candidates[-1]
|
|
183
|
+
proven = _host_control_observation_matches(selected, requirement, contract.validity_policy)
|
|
184
|
+
result = "PROVEN" if proven else "UNSATISFIED"
|
|
185
|
+
why = "APPROVED_HOST_CONTROL_EXECUTED_AND_PASSED" if proven else selected.reason
|
|
186
|
+
matched = (selected.id,)
|
|
187
|
+
elif requirement.kind != "repository_json":
|
|
136
188
|
result, why, matched = "UNSATISFIED", "UNSUPPORTED_AUTHORITATIVE_EVIDENCE_SOURCE", ()
|
|
137
189
|
elif source is None:
|
|
138
190
|
result, why, matched = "UNSATISFIED", "APPROVED_REPOSITORY_SOURCE_MISSING", ()
|
|
@@ -19,7 +19,7 @@ from forge.dispatcher import MissionDispatcher
|
|
|
19
19
|
from forge.governance import ApprovalRecord, ExecutionPolicy, ExecutionPolicyKind, PauseBoundary, execution_policy_for_profile
|
|
20
20
|
from forge.models.action import EngineeringAction, EngineeringActionStatus
|
|
21
21
|
from forge.models.architecture_mission import ArchitectureMission
|
|
22
|
-
from forge.models.execution_host import ExecutionHost, ExecutionHostEvidence
|
|
22
|
+
from forge.models.execution_host import ExecutionEvidenceOutcome, ExecutionHost, ExecutionHostEvidence
|
|
23
23
|
from forge.models.producer import RepositoryRevisionBinding
|
|
24
24
|
from forge.models.mission_completion import (MissionCompletionEvidence,
|
|
25
25
|
MissionCriterionEvaluationStatus)
|
|
@@ -134,6 +134,7 @@ class ExecutionLoop:
|
|
|
134
134
|
completion_evaluator: MissionCompletionEvaluator | None = None,
|
|
135
135
|
runtime_database: RuntimeDatabase | None = None,
|
|
136
136
|
repository_revision_binding_factory: RepositoryRevisionBindingFactory | None = None,
|
|
137
|
+
keep_running: Callable[[], bool] | None = None,
|
|
137
138
|
) -> None:
|
|
138
139
|
if not all((host_id, workspace_id, repository_id)):
|
|
139
140
|
raise ExecutionLoopError("execution host, workspace, and repository identities are required")
|
|
@@ -150,14 +151,19 @@ class ExecutionLoop:
|
|
|
150
151
|
self._completion_evaluator = completion_evaluator or MissionCompletionEvaluator()
|
|
151
152
|
self._runtime_database = runtime_database
|
|
152
153
|
self._repository_revision_binding_factory = repository_revision_binding_factory
|
|
154
|
+
self._keep_running = keep_running or (lambda: True)
|
|
153
155
|
|
|
154
156
|
def run(self) -> MissionExecutionState | None:
|
|
155
157
|
"""Run the one dispatched Mission until terminal or awaiting host evidence."""
|
|
158
|
+
if not self._keep_running():
|
|
159
|
+
return None
|
|
156
160
|
record = self._dispatcher.dispatch()
|
|
157
161
|
if record is None:
|
|
158
162
|
return None
|
|
159
163
|
state = self._states.get(record.mission_id)
|
|
160
164
|
state = self._states.set_execution_policy(state.mission_id, self._execution_policy.to_dict(), occurred_at=self._clock())
|
|
165
|
+
if not self._keep_running():
|
|
166
|
+
return state
|
|
161
167
|
if state.status is MissionExecutionStatus.CREATED:
|
|
162
168
|
try:
|
|
163
169
|
state = self._plan(state)
|
|
@@ -209,6 +215,8 @@ class ExecutionLoop:
|
|
|
209
215
|
approval: ApprovalRecord | None = None) -> MissionExecutionState:
|
|
210
216
|
"""Resume durable work; a blocked or failed Action needs explicit authority."""
|
|
211
217
|
state = self._states.get(mission_id)
|
|
218
|
+
if not self._keep_running():
|
|
219
|
+
return state
|
|
212
220
|
if state.status is MissionExecutionStatus.AWAITING_APPROVAL:
|
|
213
221
|
if approval is None:
|
|
214
222
|
raise ExecutionLoopError("governance-paused Mission requires an approval record")
|
|
@@ -241,6 +249,27 @@ class ExecutionLoop:
|
|
|
241
249
|
raise ExecutionLoopError("recovery authorization is valid only for blocked or failed Missions")
|
|
242
250
|
return self.run() if self._dispatcher.resume() else self._runner().run(mission_id)
|
|
243
251
|
|
|
252
|
+
def resume_existing_successor(self, mission_id: str) -> MissionExecutionState:
|
|
253
|
+
"""Retry an assessed continuation whose approved next Action already exists."""
|
|
254
|
+
state = self._states.get(mission_id)
|
|
255
|
+
marker = state.resume.get("terminal_continuation")
|
|
256
|
+
remaining = tuple(item for item in state.actions if item["status"] != EngineeringActionStatus.COMPLETE.value)
|
|
257
|
+
if (state.status is not MissionExecutionStatus.BLOCKED
|
|
258
|
+
or state.waiting_reason != "mission_criteria_unmet_no_valid_successor"
|
|
259
|
+
or not isinstance(marker, Mapping) or marker.get("phase") != "ASSESSED"
|
|
260
|
+
or marker.get("mission_complete") is not False
|
|
261
|
+
or not remaining
|
|
262
|
+
or any(item["status"] != EngineeringActionStatus.READY.value for item in remaining)
|
|
263
|
+
or state.execution_evidence is None
|
|
264
|
+
or state.execution_evidence.get("outcome") != ExecutionEvidenceOutcome.COMPLETE.value):
|
|
265
|
+
raise ExecutionLoopError("existing-successor continuation is unavailable for this Mission")
|
|
266
|
+
state = self._states.transition(
|
|
267
|
+
mission_id, MissionExecutionStatus.ACTIVE, occurred_at=self._clock(),
|
|
268
|
+
reason="existing_successor_continuation_requested",
|
|
269
|
+
)
|
|
270
|
+
self._dispatcher.recover(mission_id)
|
|
271
|
+
return self._runner().run(mission_id)
|
|
272
|
+
|
|
244
273
|
def approve_delegation(self, mission_id: str, delegation_id: str, approval: ApprovalRecord) -> MissionExecutionState:
|
|
245
274
|
"""Record governance approval for one exact delegation without invoking a provider."""
|
|
246
275
|
state = self._states.get(mission_id)
|
|
@@ -289,6 +318,8 @@ class ExecutionLoop:
|
|
|
289
318
|
if input_value.mission.id != state.mission_id:
|
|
290
319
|
raise ExecutionLoopError("planner input must belong to the active Mission")
|
|
291
320
|
plan, derivation = self._select_plan(input_value, state)
|
|
321
|
+
if not self._keep_running():
|
|
322
|
+
return state
|
|
292
323
|
return self._persist_planned_result(state, plan, derivation)
|
|
293
324
|
|
|
294
325
|
def resume_authorized_next_derivation(
|
|
@@ -333,6 +364,8 @@ class ExecutionLoop:
|
|
|
333
364
|
|
|
334
365
|
def _persist_planned_result(self, state: MissionExecutionState, plan: MissionPlan,
|
|
335
366
|
derivation: Mapping[str, Any] | None) -> MissionExecutionState:
|
|
367
|
+
if not self._keep_running():
|
|
368
|
+
return state
|
|
336
369
|
actions = tuple(action for intent in plan.intents for action in intent.actions)
|
|
337
370
|
if not actions:
|
|
338
371
|
raise ExecutionLoopError("approved Mission planning produced no executable Engineering Actions")
|
|
@@ -540,7 +573,8 @@ class ExecutionLoop:
|
|
|
540
573
|
clock=self._clock, correlation_id_factory=self._correlation_id_factory,
|
|
541
574
|
completion_context=completion, replan_after_evidence=self._replan_after_evidence,
|
|
542
575
|
evidence_progression_gate=self._pause_after_evidence,
|
|
543
|
-
repository_revision_binding_factory=self._repository_revision_binding_factory
|
|
576
|
+
repository_revision_binding_factory=self._repository_revision_binding_factory,
|
|
577
|
+
keep_running=self._keep_running)
|
|
544
578
|
|
|
545
579
|
def _pause_after_evidence(self, state: MissionExecutionState, actions: tuple[EngineeringAction, ...],
|
|
546
580
|
evidence: ExecutionHostEvidence, mission_complete: bool) -> MissionExecutionState | None:
|
|
@@ -596,8 +630,14 @@ class ExecutionLoop:
|
|
|
596
630
|
return state
|
|
597
631
|
|
|
598
632
|
self._assert_current_replan_evidence(state, replanning, evidence)
|
|
633
|
+
# An approved successor can already be part of the initial dynamic
|
|
634
|
+
# plan. Continue that immutable Action instead of deriving a duplicate.
|
|
635
|
+
if remaining:
|
|
636
|
+
return state
|
|
599
637
|
self._assert_successor_bounds(state, replanning.mission)
|
|
600
638
|
plan, derivation = self._select_plan(replanning, state)
|
|
639
|
+
if not self._keep_running():
|
|
640
|
+
return state
|
|
601
641
|
assert derivation is not None
|
|
602
642
|
previous_fingerprints = {
|
|
603
643
|
item["work_fingerprint"] for record in state.planning_history
|
|
@@ -631,6 +671,8 @@ class ExecutionLoop:
|
|
|
631
671
|
if isinstance(continuation, Mapping):
|
|
632
672
|
resume["terminal_continuation"] = {**continuation, "phase": "SUCCESSOR_READY"}
|
|
633
673
|
try:
|
|
674
|
+
if not self._keep_running():
|
|
675
|
+
return state
|
|
634
676
|
return self._states.transition(
|
|
635
677
|
state.mission_id, MissionExecutionStatus.ACTIVE, occurred_at=self._clock(),
|
|
636
678
|
reason="dynamic_successor_materialized", intents=(*state.intents, *new_intents),
|
|
@@ -201,6 +201,7 @@ class ArchitecturePlanningEvidence:
|
|
|
201
201
|
maximum_actions: int | None = None
|
|
202
202
|
maximum_consecutive_no_progress_actions: int | None = None
|
|
203
203
|
repository_evidence_source: ApprovedRepositoryEvidenceSource | None = None
|
|
204
|
+
mission_spec_digest: str | None = None
|
|
204
205
|
|
|
205
206
|
def __post_init__(self) -> None:
|
|
206
207
|
if not all((self.scope, self.write_scopes, self.non_goals, self.risk_inputs, self.human_gates,
|
|
@@ -211,6 +212,10 @@ class ArchitecturePlanningEvidence:
|
|
|
211
212
|
self.criterion_assessment_contracts, self.maximum_actions,
|
|
212
213
|
self.maximum_consecutive_no_progress_actions, self.repository_evidence_source,
|
|
213
214
|
))
|
|
215
|
+
if self.mission_spec_digest is not None and (not isinstance(self.mission_spec_digest, str)
|
|
216
|
+
or len(self.mission_spec_digest) != 71 or not self.mission_spec_digest.startswith("sha256:")
|
|
217
|
+
or any(character not in "0123456789abcdef" for character in self.mission_spec_digest[7:])):
|
|
218
|
+
raise ValueError("Mission specification digest is invalid")
|
|
214
219
|
|
|
215
220
|
def to_dict(self) -> dict[str, object]:
|
|
216
221
|
value = asdict(self)
|
|
@@ -227,6 +232,8 @@ class ArchitecturePlanningEvidence:
|
|
|
227
232
|
value.pop("repository_evidence_source")
|
|
228
233
|
else:
|
|
229
234
|
value["repository_evidence_source"] = self.repository_evidence_source.to_dict()
|
|
235
|
+
if self.mission_spec_digest is None:
|
|
236
|
+
value.pop("mission_spec_digest")
|
|
230
237
|
return value
|
|
231
238
|
|
|
232
239
|
@property
|
|
@@ -271,6 +278,10 @@ class MissionPlanningEvidenceEnvelope:
|
|
|
271
278
|
raise ValueError("approval envelope has invalid, stale, conflicting, or cross-installation lineage")
|
|
272
279
|
if planning.provenance_revision != subject_revision:
|
|
273
280
|
raise ValueError("planning evidence revision is stale")
|
|
281
|
+
if planning.mission_spec_digest is not None and (
|
|
282
|
+
tuple(sorted(business.get("scope", ()))) != tuple(sorted(planning.scope))
|
|
283
|
+
or tuple(sorted(business.get("gates", ()))) != tuple(sorted(planning.human_gates))):
|
|
284
|
+
raise ValueError("Architecture planning exceeds or changes the exact Business-approved scope and gates")
|
|
274
285
|
evidence = architecture.get("evidence")
|
|
275
286
|
approved_digest = evidence.get("planning_digest") if isinstance(evidence, dict) else None
|
|
276
287
|
if (planning.criterion_assessment_contracts and approved_digest is None
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from dataclasses import dataclass
|
|
5
|
+
from dataclasses import dataclass, replace
|
|
6
6
|
import json
|
|
7
7
|
from typing import Callable, Sequence
|
|
8
8
|
|
|
@@ -12,6 +12,7 @@ from forge.models.mission import EngineeringMission, MissionStatus
|
|
|
12
12
|
from forge.models.architecture_mission import ArchitectureMission, ArchitectureMissionStatus
|
|
13
13
|
from forge.state import MissionExecutionState, MissionExecutionStatus, MissionStateStore, MissionStateStoreError
|
|
14
14
|
from forge.governance_authority import CanonicalGovernanceRepository, MissionPlanningEvidenceEnvelope
|
|
15
|
+
from forge.models.criterion_observation import canonical_digest
|
|
15
16
|
from forge.runtime.database import RuntimeDatabaseError
|
|
16
17
|
|
|
17
18
|
|
|
@@ -50,19 +51,7 @@ class MissionIntake:
|
|
|
50
51
|
This is the canonical governance bridge. It preserves the legacy
|
|
51
52
|
bootstrap admission method below for its separately-versioned contract.
|
|
52
53
|
"""
|
|
53
|
-
self.
|
|
54
|
-
if mission.status is not ArchitectureMissionStatus.APPROVED_FOR_ENGINEERING:
|
|
55
|
-
raise MissionIntakeError("Mission Intake requires an engineering-approved Architecture Mission")
|
|
56
|
-
planning = envelope.planning
|
|
57
|
-
if mission.criterion_assessment_contracts or planning.criterion_assessment_contracts:
|
|
58
|
-
if (mission.candidate_id != envelope.subject_id
|
|
59
|
-
or tuple(sorted(mission.scope)) != tuple(sorted(planning.scope))
|
|
60
|
-
or mission.architecture_review_reference != envelope.architecture_decision_id
|
|
61
|
-
or mission.criterion_assessment_contracts != planning.criterion_assessment_contracts
|
|
62
|
-
or mission.maximum_actions != planning.maximum_actions
|
|
63
|
-
or mission.maximum_consecutive_no_progress_actions != planning.maximum_consecutive_no_progress_actions
|
|
64
|
-
or mission.repository_evidence_source != planning.repository_evidence_source):
|
|
65
|
-
raise MissionIntakeError("Mission Intake criterion contract differs from canonical Architecture approval")
|
|
54
|
+
self.validate_canonical_mission_contract(mission, envelope, repository)
|
|
66
55
|
source = "canonical-governance-envelope:" + envelope.digest
|
|
67
56
|
allocation = repository.database._connection.execute(
|
|
68
57
|
"SELECT mission_id FROM mission_id_allocations WHERE source = ?", (source,)
|
|
@@ -119,6 +108,29 @@ class MissionIntake:
|
|
|
119
108
|
raise MissionIntakeError("Mission Intake could not create canonical Mission state") from error
|
|
120
109
|
return MissionStateStore._decode(json.dumps(document, sort_keys=True, separators=(",", ":")))
|
|
121
110
|
|
|
111
|
+
def validate_canonical_mission_contract(
|
|
112
|
+
self, mission: ArchitectureMission, envelope: MissionPlanningEvidenceEnvelope,
|
|
113
|
+
repository: CanonicalGovernanceRepository,
|
|
114
|
+
) -> None:
|
|
115
|
+
"""Check all approval and Mission content before any allocation."""
|
|
116
|
+
self.validate_approved_evidence(envelope, repository)
|
|
117
|
+
if mission.status is not ArchitectureMissionStatus.APPROVED_FOR_ENGINEERING:
|
|
118
|
+
raise MissionIntakeError("Mission Intake requires an engineering-approved Architecture Mission")
|
|
119
|
+
planning = envelope.planning
|
|
120
|
+
if (planning.mission_spec_digest is not None
|
|
121
|
+
and planning.mission_spec_digest != canonical_digest(
|
|
122
|
+
replace(mission, id="MISSION-PREVIEW").to_dict())):
|
|
123
|
+
raise MissionIntakeError("Mission content differs from the exact Architecture-approved specification")
|
|
124
|
+
if mission.criterion_assessment_contracts or planning.criterion_assessment_contracts:
|
|
125
|
+
if (mission.candidate_id != envelope.subject_id
|
|
126
|
+
or tuple(sorted(mission.scope)) != tuple(sorted(planning.scope))
|
|
127
|
+
or mission.architecture_review_reference != envelope.architecture_decision_id
|
|
128
|
+
or mission.criterion_assessment_contracts != planning.criterion_assessment_contracts
|
|
129
|
+
or mission.maximum_actions != planning.maximum_actions
|
|
130
|
+
or mission.maximum_consecutive_no_progress_actions != planning.maximum_consecutive_no_progress_actions
|
|
131
|
+
or mission.repository_evidence_source != planning.repository_evidence_source):
|
|
132
|
+
raise MissionIntakeError("Mission Intake criterion contract differs from canonical Architecture approval")
|
|
133
|
+
|
|
122
134
|
def admit(
|
|
123
135
|
self,
|
|
124
136
|
mission: EngineeringMission,
|