forge-autonomy 2.7.24__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.24 → forge_autonomy-2.7.26}/PKG-INFO +11 -1
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/README.md +10 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/__main__.py +58 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/architecture/workspace.py +2 -1
- forge_autonomy-2.7.26/forge/completion/host_control_observer.py +164 -0
- forge_autonomy-2.7.26/forge/completion/mission.py +229 -0
- forge_autonomy-2.7.26/forge/completion/repository_observer.py +132 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/execution/loop.py +159 -10
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/governance_authority.py +55 -1
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/intake.py +28 -4
- forge_autonomy-2.7.26/forge/mission_cli.py +353 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/action_derivation.py +28 -6
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/architecture_mission.py +29 -1
- forge_autonomy-2.7.26/forge/models/criterion_assessment.py +267 -0
- forge_autonomy-2.7.26/forge/models/criterion_observation.py +73 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/execution_host.py +8 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/mission_completion.py +35 -2
- forge_autonomy-2.7.26/forge/models/mission_planner.py +453 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planner/codex_cli_session.py +88 -11
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planner/durable_derivation.py +5 -2
- forge_autonomy-2.7.26/forge/qualification/bootstrap_sequence.py +110 -0
- forge_autonomy-2.7.26/forge/qualification/criterion_completion.py +373 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/database.py +19 -1
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/dynamic_mission.py +107 -33
- forge_autonomy-2.7.26/forge/runtime/mission_controller.py +242 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/operational_reset.py +6 -2
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/runner.py +108 -8
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/scheduler/ep_http_adapter.py +91 -12
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/scheduler/ep_v12.py +19 -7
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/state/mission_state.py +47 -50
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge_autonomy.egg-info/PKG-INFO +11 -1
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge_autonomy.egg-info/SOURCES.txt +17 -1
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/product-version.json +1 -1
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_bootstrap_mission_runner.py +17 -0
- forge_autonomy-2.7.26/tests/test_bootstrap_sequence_qualification.py +147 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_codex_cli_session.py +43 -3
- forge_autonomy-2.7.26/tests/test_criterion_assessment_contract.py +247 -0
- forge_autonomy-2.7.26/tests/test_criterion_ep_http_boundary.py +148 -0
- forge_autonomy-2.7.26/tests/test_criterion_planning_context.py +226 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_dynamic_mission_capability.py +72 -31
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_ep_http_adapter.py +135 -1
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_execution_loop.py +18 -23
- forge_autonomy-2.7.26/tests/test_host_control_completion.py +442 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_installed_dynamic_mission_runtime.py +120 -8
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_installed_forge_update.py +440 -9
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_completion_evaluator.py +30 -10
- forge_autonomy-2.7.26/tests/test_mission_controller.py +218 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_state_store.py +20 -0
- forge_autonomy-2.7.26/tests/test_mission_status.py +35 -0
- forge_autonomy-2.7.26/tests/test_recovered_partial_completion.py +52 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_database.py +34 -0
- forge_autonomy-2.7.26/tests/test_substantive_mission_completion.py +339 -0
- forge_autonomy-2.7.26/tests/test_terminal_continuation_replay.py +358 -0
- forge_autonomy-2.7.24/forge/completion/mission.py +0 -120
- forge_autonomy-2.7.24/forge/models/mission_planner.py +0 -241
- forge_autonomy-2.7.24/forge/qualification/bootstrap_sequence.py +0 -287
- forge_autonomy-2.7.24/tests/test_bootstrap_sequence_qualification.py +0 -114
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/MANIFEST.in +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/_version.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/action_derivation_canary_closure.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/action_derivation_evidence.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/action_derivation_qualification.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/action_derivation_reattempt.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/ai_architect/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/ai_architect/provider_registry.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/architecture/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/architecture/advisor.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/architecture/presentation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/autonomous_orchestrator.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/business/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/business/advisor.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/business/ingress.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/business/presentation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/business/workspace.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/canary.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/capabilities/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/capabilities/delegation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/completion/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/completion/assessor.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/constitutional/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/constitutional/assessor.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/core/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/core/json_store.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/decision_evidence/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/decision_evidence/repository.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/dispatcher/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/dispatcher/dispatcher.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/execution/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/execution/presentation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/execution_host_configuration.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/foundation/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/foundation/loader.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/generation_two.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/generation_two_execution.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/governance/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/governance/execution_policy.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/governance/profiles.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/integration/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/integration/coordinator.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/integration/repository.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/knowledge/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/knowledge/consumer.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/knowledge/registry.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/lifecycle/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/lifecycle/store.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/mission_amendment.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/mission_scheduler.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/action.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/agent_policy.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/ai_architect_provider.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/ai_architect_session.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/ai_provider_registry.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/architecture_reasoning.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/architecture_review.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/codex_runtime_prompt.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/constitutional_validation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/decision_evidence.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/foundation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/historical_intent.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/integration.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/intent.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/intent_authoring.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/mission.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/mission_candidate.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/mission_recommendation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/phase_completion.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/planning.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/producer.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/prompt_artifact.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/proposal.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/runtime_prompt.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/solution_template.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/operator_identity.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planner/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planner/action_derivation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planner/engine.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planner/openai_responses.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planner/provider_adapter.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planning/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planning/loader.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planning/registry.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/portfolio_intelligence.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/programme_authorization.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/prompts/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/prompts/codex_cli.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/prompts/generator.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/proposals/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/proposals/generator.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/provider_security.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/qualification/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/qualification/generation_one.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/qualification/installed_smoke.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/recommendations/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/recommendations/engine.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/repository_truth.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/review/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/review/engine.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/bootstrap.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/data_root.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/evidence.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/execution_context.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/health.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/service.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/scheduler/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/scheduler/adapter.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/scheduler/scheduler.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/capability-registry-1.0.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/capability.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/common.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/decision-evidence-1.0.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-goal-0.5.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-increment-proposal-0.5.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-mode.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-plan-0.5.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-platform-peer-configuration-1.0.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-platform-peer-configuration-1.1.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-prompt-artifact-0.7.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-proposal-0.6.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/evidence-reference-0.5.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/foundation-document.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/governance-profile.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/knowledge-source-0.4.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/knowledge-source.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/mission-candidate-1.0.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/mission-candidate-1.1.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/phase-completion-1.0.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/planning-document-0.5.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/repository-catalog.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/repository.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/solution-template-1.0.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/workspace.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/secure_store.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/solutions/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/solutions/catalogue.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/solutions/generator.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/state/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/workspace/__init__.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge_autonomy.egg-info/dependency_links.txt +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge_autonomy.egg-info/entry_points.txt +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge_autonomy.egg-info/top_level.txt +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/pyproject.toml +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/capability-registry-1.0.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/capability.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/common.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/decision-evidence-1.0.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/engineering-goal-0.5.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/engineering-increment-proposal-0.5.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/engineering-mode.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/engineering-plan-0.5.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/engineering-prompt-artifact-0.7.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/engineering-proposal-0.6.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/evidence-reference-0.5.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/foundation-document.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/governance-profile.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/knowledge-source-0.4.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/knowledge-source.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/mission-candidate-1.0.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/mission-candidate-1.1.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/phase-completion-1.0.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/planning-document-0.5.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/repository-catalog.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/repository.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/solution-template-1.0.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/workspace.schema.json +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/setup.cfg +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_action_derivation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_action_derivation_canary_closure.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_action_derivation_evidence.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_action_derivation_qualification.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_adaptive_action_sizing_design.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_agent_policy.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_aggregate_health_roadmap.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_ai_architect_provider.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_ai_architect_session.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_ai_development_projection.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_ai_provider_registry.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_architecture_reasoning.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_architecture_review_engine.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_architecture_workspace.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_autonomous_orchestrator.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_bootstrap_adapter.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_bootstrap_mission_canary.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_bootstrap_mission_scheduler.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_business_governance_ingress.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_business_workspace.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_canonical_data_root.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_capability_delegation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_codex_cli_runtime_prompt.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_constitutional_validation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_decision_evidence.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_default_project_quality_policy.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_distribution_identity.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_durable_action_derivation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_engineering_action_architecture.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_engineering_intent_authoring.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_engineering_intent_lifecycle.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_engineering_mission.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_engineering_planning.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_engineering_prompt_artifacts.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_engineering_proposals.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_execution_context.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_execution_host_configuration.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_execution_host_contract.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_existing_release_reconciliation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_forge_console_admin_parity_contract.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_forge_console_configuration_contract.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_forge_console_hosting_contract.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_forge_operations_console_roadmap.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_forge_outer_loop_ci_roadmap.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_forge_v1_bootstrap_scheduler_contract.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_forge_v1_implementation_dag.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_foundation_loader.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_foundation_models.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_generation_one_bootstrap_qualification.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_generation_two_execution.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_generation_two_mission_intake.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_governance_authority.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_governed_coverage.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_historical_engineering_intent.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_inner_loop_ci_contract.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_integration_coordination.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_knowledge_consumption.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_live_project_roadmap_contract.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_dispatcher.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_planner.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_recommendation_engine.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_recommendation_lifecycle.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_runtime_projection.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_runtime_scheduler.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_openai_action_derivation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_operational_reset.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_operator_identity.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_phase_completion.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_platform_contract_documents.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_portfolio_intelligence_operation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_producer_contract.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_product_version_operations.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_production_release_workflow.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_productization_reconciliation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_programme_authorization.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_provider_security.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_pypi_distribution_readback.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_release_operation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_repository_truth.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_role_aware_conversations_contract.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_bootstrap.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_evidence.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_health.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_instance_persistence.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_prompt_generation.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_service.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_status_projection.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_scheduler_dependency_boundary.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_schemas.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_secure_store.py +0 -0
- {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_solution_templates.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: forge-autonomy
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.26
|
|
4
4
|
Summary: Forge mission planning, producer contracts, and evidence interpretation runtime.
|
|
5
5
|
Author: Forge maintainers
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -134,6 +134,16 @@ and atomically activates a versioned slot. It is not packaged as a Forge
|
|
|
134
134
|
Runtime command and does not replace Forge Platform's normal installer/update
|
|
135
135
|
composition boundary.
|
|
136
136
|
|
|
137
|
+
## Mission 3 acceptance preflight
|
|
138
|
+
|
|
139
|
+
The [18 September 2026 Mission 3 preflight report](docs/operations/MISSION_3_PREFLIGHT_ACCEPTANCE_2026_09_18.md)
|
|
140
|
+
records `AUTONOMY_E2E_ACCEPTANCE = NIET_GEHAALD` and
|
|
141
|
+
`MISSION_EXECUTION_STATE = NIET_GESTART`. Installed artifact and authenticated
|
|
142
|
+
peer checks passed, but the installed criterion-to-evidence composition cannot
|
|
143
|
+
prove the required dynamic successor Action. No production reset or Mission was
|
|
144
|
+
started. The report includes the unchanged C01–C20 and preserves historical
|
|
145
|
+
failures; it does not authorize a repair or replacement attempt.
|
|
146
|
+
|
|
137
147
|
## Operational-history reset
|
|
138
148
|
|
|
139
149
|
Forge schema 38 provides a bounded, product-owned maintenance service under
|
|
@@ -117,6 +117,16 @@ and atomically activates a versioned slot. It is not packaged as a Forge
|
|
|
117
117
|
Runtime command and does not replace Forge Platform's normal installer/update
|
|
118
118
|
composition boundary.
|
|
119
119
|
|
|
120
|
+
## Mission 3 acceptance preflight
|
|
121
|
+
|
|
122
|
+
The [18 September 2026 Mission 3 preflight report](docs/operations/MISSION_3_PREFLIGHT_ACCEPTANCE_2026_09_18.md)
|
|
123
|
+
records `AUTONOMY_E2E_ACCEPTANCE = NIET_GEHAALD` and
|
|
124
|
+
`MISSION_EXECUTION_STATE = NIET_GESTART`. Installed artifact and authenticated
|
|
125
|
+
peer checks passed, but the installed criterion-to-evidence composition cannot
|
|
126
|
+
prove the required dynamic successor Action. No production reset or Mission was
|
|
127
|
+
started. The report includes the unchanged C01–C20 and preserves historical
|
|
128
|
+
failures; it does not authorize a repair or replacement attempt.
|
|
129
|
+
|
|
120
130
|
## Operational-history reset
|
|
121
131
|
|
|
122
132
|
Forge schema 38 provides a bounded, product-owned maintenance service under
|
|
@@ -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()
|
|
@@ -107,7 +107,8 @@ class ArchitectureWorkspace:
|
|
|
107
107
|
mission, revision = self._current(mission_id)
|
|
108
108
|
if mission.status is not ArchitectureMissionStatus.ARCHITECTURE_REVIEW:
|
|
109
109
|
raise ArchitectureWorkspaceError("only Missions in architecture_review may be refined")
|
|
110
|
-
allowed = {"scope", "engineering_constraints", "acceptance_criteria", "technical_assumptions", "dependencies", "required_capabilities", "required_disciplines", "risks"
|
|
110
|
+
allowed = {"scope", "engineering_constraints", "acceptance_criteria", "technical_assumptions", "dependencies", "required_capabilities", "required_disciplines", "risks", "criterion_assessment_contracts", "maximum_actions",
|
|
111
|
+
"maximum_consecutive_no_progress_actions", "repository_evidence_source"}
|
|
111
112
|
if not changes or set(changes) - allowed:
|
|
112
113
|
raise ArchitectureWorkspaceError("architecture refinement may update only engineering governance fields")
|
|
113
114
|
refined = replace(mission, **changes)
|
|
@@ -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)
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Deterministic Architecture Mission completion evaluation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from hashlib import sha256
|
|
6
|
+
import json
|
|
7
|
+
from typing import Any, Mapping, Sequence
|
|
8
|
+
|
|
9
|
+
from forge.models.architecture_mission import ArchitectureMission
|
|
10
|
+
from forge.models.mission_completion import (
|
|
11
|
+
CanonicalExecutionEvidenceReference,
|
|
12
|
+
MissionCompletionEvaluation,
|
|
13
|
+
MissionCompletionEvidence,
|
|
14
|
+
MissionCriterionEvaluation,
|
|
15
|
+
MissionCriterionEvaluationStatus,
|
|
16
|
+
RepositoryTruthReference,
|
|
17
|
+
mission_criterion_id,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MissionCompletionEvaluationError(ValueError):
|
|
22
|
+
"""Completion input is stale, malformed, or outside the approved Mission."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _digest(value: object) -> str:
|
|
26
|
+
return "sha256:" + sha256(
|
|
27
|
+
json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
|
|
28
|
+
).hexdigest()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _truth_reference(document: Mapping[str, Any]) -> RepositoryTruthReference:
|
|
32
|
+
try:
|
|
33
|
+
values = tuple(document[item] for item in ("source_id", "revision", "locator", "content_digest"))
|
|
34
|
+
if any(not isinstance(item, str) or not item for item in values):
|
|
35
|
+
raise TypeError
|
|
36
|
+
return RepositoryTruthReference(*values)
|
|
37
|
+
except (KeyError, TypeError, ValueError) as error:
|
|
38
|
+
raise MissionCompletionEvaluationError("current Repository Truth provenance is incomplete") from error
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _canonical_execution_references(
|
|
42
|
+
mission_id: str, evidence: Sequence[Mapping[str, Any]],
|
|
43
|
+
) -> dict[str, CanonicalExecutionEvidenceReference]:
|
|
44
|
+
references: dict[str, CanonicalExecutionEvidenceReference] = {}
|
|
45
|
+
for document in evidence:
|
|
46
|
+
if document.get("outcome") != "complete":
|
|
47
|
+
continue
|
|
48
|
+
repository = document.get("repository_evidence")
|
|
49
|
+
if not isinstance(repository, Mapping) or repository.get("mission_id") != mission_id:
|
|
50
|
+
continue
|
|
51
|
+
try:
|
|
52
|
+
values = (
|
|
53
|
+
document["receipt_id"], repository["action_id"], document["report_id"],
|
|
54
|
+
repository["repository_revision"], repository["content_digest"],
|
|
55
|
+
)
|
|
56
|
+
if any(not isinstance(item, str) or not item for item in values):
|
|
57
|
+
raise TypeError
|
|
58
|
+
reference = CanonicalExecutionEvidenceReference(*values, candidate_revision=repository.get('candidate_revision'))
|
|
59
|
+
except (KeyError, TypeError, ValueError):
|
|
60
|
+
continue
|
|
61
|
+
if any(document.get(key) != repository.get(key) for key in ("correlation_id", "host_run_id", "report_id")):
|
|
62
|
+
continue
|
|
63
|
+
previous = references.get(reference.receipt_id)
|
|
64
|
+
if previous is not None and previous != reference:
|
|
65
|
+
raise MissionCompletionEvaluationError("execution receipt identity binds conflicting terminal evidence")
|
|
66
|
+
references[reference.receipt_id] = reference
|
|
67
|
+
return references
|
|
68
|
+
|
|
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
|
+
|
|
98
|
+
class MissionCompletionEvaluator:
|
|
99
|
+
"""Interpret approved predicates over Forge-observed facts; never provider PASS."""
|
|
100
|
+
|
|
101
|
+
def evaluate(self, mission, repository_truth, execution_evidence, evidence):
|
|
102
|
+
if not mission.acceptance_criteria:
|
|
103
|
+
raise MissionCompletionEvaluationError("approved Mission has no acceptance criteria")
|
|
104
|
+
mission_digest = _digest(mission.to_dict())
|
|
105
|
+
truth = _truth_reference(repository_truth)
|
|
106
|
+
identifiers = {mission_criterion_id(mission.id, criterion): criterion for criterion in mission.acceptance_criteria}
|
|
107
|
+
canonical = _canonical_execution_references(mission.id, execution_evidence)
|
|
108
|
+
contracts = {mission_criterion_id(mission.id, item.criterion): item
|
|
109
|
+
for item in mission.criterion_assessment_contracts}
|
|
110
|
+
bindings = {} if evidence is None else {item.criterion_id: item for item in evidence.bindings}
|
|
111
|
+
if evidence is not None:
|
|
112
|
+
if evidence.mission_id != mission.id or evidence.mission_digest != mission_digest:
|
|
113
|
+
raise MissionCompletionEvaluationError("completion evidence does not bind the approved Mission")
|
|
114
|
+
if set(bindings) - set(identifiers):
|
|
115
|
+
raise MissionCompletionEvaluationError("completion evidence references an unknown Mission criterion")
|
|
116
|
+
evaluations = []
|
|
117
|
+
for criterion_id, criterion in identifiers.items():
|
|
118
|
+
contract, binding = contracts.get(criterion_id), bindings.get(criterion_id)
|
|
119
|
+
reason = None
|
|
120
|
+
if contract is None:
|
|
121
|
+
reason = "APPROVED_ASSESSMENT_CONTRACT_MISSING"
|
|
122
|
+
elif evidence is not None and evidence.schema_version != "2.0":
|
|
123
|
+
reason = "LEGACY_ASSOCIATIONS_ARE_NOT_SUBSTANTIVE_EVIDENCE"
|
|
124
|
+
elif binding is None:
|
|
125
|
+
reason = "MISSING_AUTHORITATIVE_OBSERVATION"
|
|
126
|
+
elif binding.contract_digest != contract.digest:
|
|
127
|
+
reason = "ASSESSMENT_CONTRACT_MISMATCH"
|
|
128
|
+
elif binding.repository_truth != truth:
|
|
129
|
+
reason = "STALE_REPOSITORY_TRUTH"
|
|
130
|
+
elif any(canonical.get(ref.receipt_id) != ref for ref in binding.execution_evidence):
|
|
131
|
+
reason = "NON_CANONICAL_EXECUTION_EVIDENCE"
|
|
132
|
+
references = () if binding is None else binding.execution_evidence
|
|
133
|
+
observations = () if binding is None else binding.observations
|
|
134
|
+
requirement_results = []
|
|
135
|
+
if reason is None:
|
|
136
|
+
reference_map = {ref.receipt_id: ref for ref in references}
|
|
137
|
+
seen = {}
|
|
138
|
+
for observation in observations:
|
|
139
|
+
reference = reference_map.get(observation.receipt_id)
|
|
140
|
+
if (reference is None or observation.mission_id != mission.id
|
|
141
|
+
or observation.mission_digest != mission_digest
|
|
142
|
+
or observation.criterion_id != criterion_id
|
|
143
|
+
or observation.contract_digest != contract.digest
|
|
144
|
+
or observation.action_id != reference.action_id
|
|
145
|
+
or observation.report_id != reference.report_id
|
|
146
|
+
or observation.repository_revision != reference.repository_revision
|
|
147
|
+
or observation.candidate_revision != reference.candidate_revision
|
|
148
|
+
or observation.repository_evidence_digest != reference.repository_evidence_digest):
|
|
149
|
+
reason = "OBSERVATION_PROVENANCE_MISMATCH"
|
|
150
|
+
break
|
|
151
|
+
identity = (observation.receipt_id, observation.requirement_id)
|
|
152
|
+
if identity in seen and seen[identity] != observation:
|
|
153
|
+
raise MissionCompletionEvaluationError("conflicting immutable criterion observations")
|
|
154
|
+
seen[identity] = observation
|
|
155
|
+
if observation.requirement_id not in {req.requirement_id for req in contract.requirements}:
|
|
156
|
+
raise MissionCompletionEvaluationError("observation references unknown criterion requirement")
|
|
157
|
+
if reason is None:
|
|
158
|
+
for requirement in contract.requirements:
|
|
159
|
+
candidates = [obs for obs in observations if obs.requirement_id == requirement.requirement_id]
|
|
160
|
+
if contract.validity_policy == "current_revision":
|
|
161
|
+
candidates = [obs for obs in candidates if obs.repository_revision == truth.revision]
|
|
162
|
+
source = mission.repository_evidence_source
|
|
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":
|
|
188
|
+
result, why, matched = "UNSATISFIED", "UNSUPPORTED_AUTHORITATIVE_EVIDENCE_SOURCE", ()
|
|
189
|
+
elif source is None:
|
|
190
|
+
result, why, matched = "UNSATISFIED", "APPROVED_REPOSITORY_SOURCE_MISSING", ()
|
|
191
|
+
elif any(obs.source_kind != requirement.kind or obs.source_identity != source.github_repository
|
|
192
|
+
or obs.artifact_path != requirement.artifact_path
|
|
193
|
+
or obs.requirement_digest != requirement.digest
|
|
194
|
+
or obs.json_pointer != requirement.json_pointer for obs in candidates):
|
|
195
|
+
result, why, matched = "UNSATISFIED", "OBSERVATION_SOURCE_MISMATCH", ()
|
|
196
|
+
elif not candidates:
|
|
197
|
+
result, why, matched = "UNSATISFIED", "CURRENT_OBSERVATION_MISSING", ()
|
|
198
|
+
elif any(len({(obs.content_digest, obs.observed_json, obs.result)
|
|
199
|
+
for obs in candidates if obs.repository_revision == revision
|
|
200
|
+
and obs.result != 'UNAVAILABLE'}) > 1
|
|
201
|
+
for revision in {obs.repository_revision for obs in candidates}):
|
|
202
|
+
result, why, matched = "UNSATISFIED", "CONFLICTING_IMMUTABLE_ARTIFACT_OBSERVATIONS", ()
|
|
203
|
+
else:
|
|
204
|
+
# A historical-delivery assertion describes an accomplished event.
|
|
205
|
+
# A new current-property failure is not a rewrite of that old event.
|
|
206
|
+
passing = [obs for obs in candidates if obs.result == "PASS"
|
|
207
|
+
and obs.observed_json == requirement.expected_json
|
|
208
|
+
and obs.content_digest is not None]
|
|
209
|
+
selected = passing[-1] if passing and contract.validity_policy == "historical_delivery" else candidates[-1]
|
|
210
|
+
proven = (selected.result == "PASS" and selected.observed_json == requirement.expected_json
|
|
211
|
+
and selected.content_digest is not None)
|
|
212
|
+
result = "PROVEN" if proven else "UNSATISFIED"
|
|
213
|
+
why = "APPROVED_JSON_ASSERTION_MATCHED" if proven else selected.reason
|
|
214
|
+
matched = (selected.id,)
|
|
215
|
+
requirement_results.append({"requirement_id": requirement.requirement_id,
|
|
216
|
+
"requirement_digest": requirement.digest, "status": result, "reason": why,
|
|
217
|
+
"observation_ids": list(matched)})
|
|
218
|
+
reason = ("ALL_APPROVED_REQUIREMENTS_PROVEN" if all(item["status"] == "PROVEN" for item in requirement_results)
|
|
219
|
+
else "REQUIRED_OBSERVATIONS_UNPROVEN")
|
|
220
|
+
status = (MissionCriterionEvaluationStatus.PROVEN if reason == "ALL_APPROVED_REQUIREMENTS_PROVEN"
|
|
221
|
+
else MissionCriterionEvaluationStatus.UNSATISFIED)
|
|
222
|
+
evaluations.append(MissionCriterionEvaluation(
|
|
223
|
+
criterion_id, criterion, status, reason, references, truth,
|
|
224
|
+
None if contract is None else contract.digest, tuple(requirement_results), observations,
|
|
225
|
+
))
|
|
226
|
+
return MissionCompletionEvaluation(
|
|
227
|
+
mission.id, mission_digest, tuple(evaluations), None if evidence is None else evidence.digest,
|
|
228
|
+
evidence=evidence,
|
|
229
|
+
)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Bounded read-only observations at an approved immutable repository revision.
|
|
2
|
+
|
|
3
|
+
Only structural facts explicitly approved by Architecture are assessed. This
|
|
4
|
+
does not execute repository code or interpret artifacts as behavioral test PASS.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from hashlib import sha256
|
|
9
|
+
import json
|
|
10
|
+
from typing import Any
|
|
11
|
+
from urllib.error import HTTPError, URLError
|
|
12
|
+
from urllib.parse import quote
|
|
13
|
+
from urllib.request import Request, HTTPRedirectHandler, build_opener
|
|
14
|
+
|
|
15
|
+
from forge.models.criterion_observation import CriterionObservation, canonical_digest
|
|
16
|
+
from forge.models.mission_completion import mission_criterion_id
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RepositoryObservationUnavailable(ValueError):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _NoRedirect(HTTPRedirectHandler):
|
|
24
|
+
def redirect_request(self, *args, **kwargs):
|
|
25
|
+
raise RepositoryObservationUnavailable('REPOSITORY_SOURCE_REDIRECT_REJECTED')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GitHubRepositoryArtifactReader:
|
|
29
|
+
"""Public HTTPS source only; no local checkout, token discovery or arbitrary URL."""
|
|
30
|
+
|
|
31
|
+
maximum_bytes = 1024 * 1024
|
|
32
|
+
|
|
33
|
+
def read(self, repository: str, revision: str, path: str) -> bytes:
|
|
34
|
+
from forge.models.criterion_assessment import ApprovedRepositoryEvidenceSource, CriterionEvidenceRequirement
|
|
35
|
+
ApprovedRepositoryEvidenceSource('validation', repository)
|
|
36
|
+
CriterionEvidenceRequirement('validation', kind='repository_json', artifact_path=path,
|
|
37
|
+
expected_json='null')
|
|
38
|
+
if len(revision) != 40 or any(c not in '0123456789abcdef' for c in revision):
|
|
39
|
+
raise RepositoryObservationUnavailable('IMMUTABLE_REPOSITORY_REVISION_REQUIRED')
|
|
40
|
+
url = 'https://raw.githubusercontent.com/' + repository + '/' + revision + '/' + quote(path, safe='/')
|
|
41
|
+
request = Request(url, headers={'Accept': 'application/json', 'User-Agent': 'Forge-criterion-observer/1.0'})
|
|
42
|
+
try:
|
|
43
|
+
with build_opener(_NoRedirect()).open(request, timeout=15) as response:
|
|
44
|
+
data = response.read(self.maximum_bytes + 1)
|
|
45
|
+
except HTTPError as error:
|
|
46
|
+
code = 'REPOSITORY_ARTIFACT_ABSENT' if error.code == 404 else 'REPOSITORY_SOURCE_UNAVAILABLE'
|
|
47
|
+
raise RepositoryObservationUnavailable(code) from error
|
|
48
|
+
except (URLError, TimeoutError, OSError) as error:
|
|
49
|
+
raise RepositoryObservationUnavailable('REPOSITORY_SOURCE_UNAVAILABLE') from error
|
|
50
|
+
if len(data) > self.maximum_bytes:
|
|
51
|
+
raise RepositoryObservationUnavailable('REPOSITORY_ARTIFACT_SIZE_LIMIT')
|
|
52
|
+
return data
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _json(data: bytes) -> Any:
|
|
56
|
+
def pairs(values):
|
|
57
|
+
result = {}
|
|
58
|
+
for key, value in values:
|
|
59
|
+
if key in result:
|
|
60
|
+
raise ValueError('duplicate JSON key')
|
|
61
|
+
result[key] = value
|
|
62
|
+
return result
|
|
63
|
+
def invalid_constant(_value):
|
|
64
|
+
raise ValueError('non-finite JSON number')
|
|
65
|
+
return json.loads(data.decode('utf-8'), object_pairs_hook=pairs, parse_constant=invalid_constant)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _pointer(value: Any, pointer: str) -> Any:
|
|
69
|
+
if pointer == '':
|
|
70
|
+
return value
|
|
71
|
+
for part in pointer[1:].split('/'):
|
|
72
|
+
key = part.replace('~1', '/').replace('~0', '~')
|
|
73
|
+
if isinstance(value, list):
|
|
74
|
+
if not key.isascii() or not key.isdigit() or (key != '0' and key.startswith('0')):
|
|
75
|
+
raise KeyError(key)
|
|
76
|
+
value = value[int(key)]
|
|
77
|
+
elif isinstance(value, dict):
|
|
78
|
+
value = value[key]
|
|
79
|
+
else:
|
|
80
|
+
raise KeyError(key)
|
|
81
|
+
return value
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class RepositoryCriterionObserver:
|
|
85
|
+
"""Collect facts from the approved source for the exact accepted delivery."""
|
|
86
|
+
|
|
87
|
+
def __init__(self, reader=None):
|
|
88
|
+
self.reader = reader or GitHubRepositoryArtifactReader()
|
|
89
|
+
|
|
90
|
+
def observe(self, mission, reference, repository_id: str) -> tuple[CriterionObservation, ...]:
|
|
91
|
+
source = mission.repository_evidence_source
|
|
92
|
+
if source is None:
|
|
93
|
+
return ()
|
|
94
|
+
if source.repository_id != repository_id:
|
|
95
|
+
raise ValueError('approved Repository Evidence source differs from the installed repository')
|
|
96
|
+
observations = []
|
|
97
|
+
cache: dict[str, bytes | RepositoryObservationUnavailable] = {}
|
|
98
|
+
for contract in mission.criterion_assessment_contracts:
|
|
99
|
+
for requirement in contract.requirements:
|
|
100
|
+
if requirement.kind != 'repository_json':
|
|
101
|
+
continue
|
|
102
|
+
path = requirement.artifact_path
|
|
103
|
+
if path not in cache:
|
|
104
|
+
try:
|
|
105
|
+
cache[path] = self.reader.read(source.github_repository, reference.repository_revision, path)
|
|
106
|
+
except RepositoryObservationUnavailable as error:
|
|
107
|
+
cache[path] = error
|
|
108
|
+
data = cache[path]
|
|
109
|
+
observed_json, content_digest = None, None
|
|
110
|
+
if isinstance(data, RepositoryObservationUnavailable):
|
|
111
|
+
result, reason = 'UNAVAILABLE', str(data)
|
|
112
|
+
else:
|
|
113
|
+
content_digest = 'sha256:' + sha256(data).hexdigest()
|
|
114
|
+
try:
|
|
115
|
+
value = _pointer(_json(data), requirement.json_pointer)
|
|
116
|
+
observed_json = json.dumps(value, sort_keys=True, separators=(',', ':'),
|
|
117
|
+
ensure_ascii=False, allow_nan=False)
|
|
118
|
+
result = 'PASS' if observed_json == requirement.expected_json else 'FAIL'
|
|
119
|
+
reason = 'APPROVED_JSON_ASSERTION_MATCHED' if result == 'PASS' else 'JSON_ASSERTION_MISMATCH'
|
|
120
|
+
except (KeyError, IndexError):
|
|
121
|
+
result, reason = 'FAIL', 'JSON_POINTER_ABSENT'
|
|
122
|
+
except (ValueError, UnicodeError, RecursionError):
|
|
123
|
+
result, reason = 'FAIL', 'INVALID_JSON_ARTIFACT'
|
|
124
|
+
observations.append(CriterionObservation(
|
|
125
|
+
mission.id, canonical_digest(mission.to_dict()), mission_criterion_id(mission.id, contract.criterion),
|
|
126
|
+
contract.digest, requirement.requirement_id, reference.receipt_id, reference.action_id,
|
|
127
|
+
reference.report_id, reference.repository_revision, reference.repository_evidence_digest,
|
|
128
|
+
'repository_json', source.github_repository, path, content_digest, observed_json, result, reason,
|
|
129
|
+
requirement_digest=requirement.digest, json_pointer=requirement.json_pointer,
|
|
130
|
+
candidate_revision=reference.candidate_revision,
|
|
131
|
+
))
|
|
132
|
+
return tuple(observations)
|