forge-autonomy 2.8.1__tar.gz → 2.12.2__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.8.1/forge_autonomy.egg-info → forge_autonomy-2.12.2}/PKG-INFO +1 -1
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/__main__.py +11 -0
- forge_autonomy-2.12.2/forge/advisory_candidate_cli.py +30 -0
- forge_autonomy-2.12.2/forge/advisory_candidate_contract.py +78 -0
- forge_autonomy-2.12.2/forge/advisory_candidate_grant.py +107 -0
- forge_autonomy-2.12.2/forge/advisory_candidate_service.py +197 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/advisory_context.py +9 -5
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/advisory_provider.py +11 -4
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/advisory_service.py +31 -17
- forge_autonomy-2.12.2/forge/api/advisory-candidate-v1.json +939 -0
- forge_autonomy-2.12.2/forge/api/approved-workset-release-v1.json +2421 -0
- forge_autonomy-2.12.2/forge/api/candidate-decisions-v1.json +1585 -0
- forge_autonomy-2.12.2/forge/api/mission-concepts-v1.json +5102 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/server-openapi-v1.json +832 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/server-postman-v1.json +607 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/approved_worklist.py +48 -10
- forge_autonomy-2.12.2/forge/candidate_decision_cli.py +36 -0
- forge_autonomy-2.12.2/forge/candidate_decision_contract.py +81 -0
- forge_autonomy-2.12.2/forge/candidate_decision_grant.py +183 -0
- forge_autonomy-2.12.2/forge/candidate_decision_service.py +248 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/governance_authority.py +5 -1
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/governed_candidate_intake.py +29 -6
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/installation_pairing.py +30 -6
- forge_autonomy-2.12.2/forge/installation_peer_detach.py +157 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/lifecycle/store.py +208 -5
- forge_autonomy-2.12.2/forge/mission_concept_approval.py +224 -0
- forge_autonomy-2.12.2/forge/mission_concept_continuity.py +122 -0
- forge_autonomy-2.12.2/forge/mission_concept_contract.py +123 -0
- forge_autonomy-2.12.2/forge/mission_concept_planning.py +194 -0
- forge_autonomy-2.12.2/forge/mission_concept_provider.py +49 -0
- forge_autonomy-2.12.2/forge/mission_concept_readiness.py +58 -0
- forge_autonomy-2.12.2/forge/mission_concept_registration.py +73 -0
- forge_autonomy-2.12.2/forge/mission_concept_resolver.py +120 -0
- forge_autonomy-2.12.2/forge/mission_concept_service.py +455 -0
- forge_autonomy-2.12.2/forge/mission_concept_setup.py +133 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/architecture_mission.py +1 -1
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/database.py +40 -2
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/dynamic_mission.py +14 -8
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/operational_reset.py +4 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/server_runtime.py +75 -4
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/worklist_activation.py +27 -1
- forge_autonomy-2.12.2/forge/workset_release_cli.py +69 -0
- forge_autonomy-2.12.2/forge/workset_release_grant.py +212 -0
- forge_autonomy-2.12.2/forge/workset_release_journal.py +53 -0
- forge_autonomy-2.12.2/forge/workset_release_package.py +67 -0
- forge_autonomy-2.12.2/forge/workset_release_service.py +231 -0
- forge_autonomy-2.12.2/forge/workset_release_subjects.py +82 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2/forge_autonomy.egg-info}/PKG-INFO +1 -1
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge_autonomy.egg-info/SOURCES.txt +52 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge_autonomy.egg-info/entry_points.txt +7 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/product-version.json +1 -1
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/pyproject.toml +7 -0
- forge_autonomy-2.12.2/tests/test_advisory_candidate_http.py +309 -0
- forge_autonomy-2.12.2/tests/test_candidate_decision_contract.py +12 -0
- forge_autonomy-2.12.2/tests/test_candidate_decisions_http.py +296 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_existing_release_reconciliation.py +4 -0
- forge_autonomy-2.12.2/tests/test_installation_peer_detach.py +323 -0
- forge_autonomy-2.12.2/tests/test_installed_forge_preservation_2739_281.py +76 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_installed_forge_update.py +3 -3
- forge_autonomy-2.12.2/tests/test_mission_concept_continuity.py +266 -0
- forge_autonomy-2.12.2/tests/test_mission_concept_contract.py +77 -0
- forge_autonomy-2.12.2/tests/test_mission_concept_dependencies.py +160 -0
- forge_autonomy-2.12.2/tests/test_mission_concept_output_boundaries.py +56 -0
- forge_autonomy-2.12.2/tests/test_mission_concept_planning.py +108 -0
- forge_autonomy-2.12.2/tests/test_mission_concept_provider.py +132 -0
- forge_autonomy-2.12.2/tests/test_mission_concept_readiness.py +95 -0
- forge_autonomy-2.12.2/tests/test_mission_concept_ready_http.py +234 -0
- forge_autonomy-2.12.2/tests/test_mission_concept_recovery.py +79 -0
- forge_autonomy-2.12.2/tests/test_mission_concept_resolver.py +110 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_production_release_workflow.py +18 -3
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_server_route_qualification.py +37 -1
- forge_autonomy-2.12.2/tests/test_workset_release_chain.py +41 -0
- forge_autonomy-2.12.2/tests/test_workset_release_concurrency.py +18 -0
- forge_autonomy-2.12.2/tests/test_workset_release_denials.py +112 -0
- forge_autonomy-2.12.2/tests/test_workset_release_http.py +75 -0
- forge_autonomy-2.12.2/tests/test_workset_release_integrity.py +101 -0
- forge_autonomy-2.12.2/tests/test_workset_release_recovery.py +26 -0
- forge_autonomy-2.12.2/tests/test_workset_release_selection.py +114 -0
- forge_autonomy-2.12.2/tests/test_workset_release_subjects.py +35 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/MANIFEST.in +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/README.md +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/_version.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/action_derivation_canary_closure.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/action_derivation_evidence.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/action_derivation_qualification.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/action_derivation_reattempt.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/action_frontier.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/advisory_cli.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/advisory_contract.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/advisory_grant.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/ai_architect/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/ai_architect/provider_registry.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/advisory-conversation-v1.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/installed-health-component-registry-1.0.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/operations-read-openapi-v1.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/operations-read-postman-v1.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/parallel-action-peer-graph-v1.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/workspace-review-inbox-v1.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/workspace-status-read-v1.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/workspace-worklist-control-v1.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/workspace-worklist-v1.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/architecture/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/architecture/advisor.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/architecture/presentation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/architecture/workspace.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/autonomous_orchestrator.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/business/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/business/advisor.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/business/ingress.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/business/presentation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/business/workspace.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/canary.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/capabilities/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/capabilities/delegation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/completion/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/completion/assessor.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/completion/effect_report_observer.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/completion/host_control_observer.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/completion/mission.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/completion/repository_observer.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/constitutional/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/constitutional/assessor.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/core/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/core/json_store.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/decision_evidence/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/decision_evidence/repository.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/dispatcher/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/dispatcher/dispatcher.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/ep_simulator.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/execution/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/execution/loop.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/execution/presentation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/execution_host_configuration.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/foundation/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/foundation/loader.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/generation_two.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/generation_two_execution.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/governance/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/governance/execution_policy.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/governance/profiles.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/governed_continuation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/installed_health.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/installed_lifecycle.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/intake.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/integration/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/integration/coordinator.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/integration/repository.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/knowledge/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/knowledge/consumer.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/knowledge/registry.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/lifecycle/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/mission_amendment.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/mission_cli.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/mission_lifecycle.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/mission_lifecycle_cli.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/mission_no_dispatch.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/mission_scheduler.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/action.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/action_derivation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/agent_policy.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/ai_architect_provider.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/ai_architect_session.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/ai_provider_registry.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/architecture_reasoning.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/architecture_review.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/codex_runtime_prompt.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/constitutional_validation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/criterion_assessment.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/criterion_observation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/decision_evidence.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/execution_host.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/foundation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/historical_intent.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/integration.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/intent.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/intent_authoring.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/mission.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/mission_candidate.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/mission_completion.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/mission_effect.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/mission_planner.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/mission_recommendation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/phase_completion.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/planning.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/producer.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/prompt_artifact.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/proposal.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/runtime_prompt.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/solution_template.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/operations_read_api.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/operator_identity.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/parallel_action_contract.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planner/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planner/action_derivation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planner/codex_cli_session.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planner/durable_derivation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planner/engine.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planner/openai_responses.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planner/provider_adapter.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planning/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planning/loader.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planning/registry.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/portfolio_intelligence.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/preserved_lifecycle.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/programme_authorization.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/prompts/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/prompts/codex_cli.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/prompts/generator.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/proposals/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/proposals/generator.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/provider_context.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/provider_security.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/bootstrap_sequence.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/criterion_completion.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/effect_fixture_conformance.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/effect_simulator_fixture.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/architecture_design_only-evidence_only.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/architecture_design_only-git.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/bounded_repository_change-git.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/documentation_only-git.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/effect-report-envelope-v1.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/effect-request-v1.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/effect-result-v1.1.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/effect-result-v1.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/effect-validation-profile-v1.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/errors.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/manifest.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/read_only_assessment-evidence_only.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/terminal-evidence-v1.5.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/terminal-evidence-v1.6.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/generation_one.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/installed_smoke.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/producer_fixture_conformance.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/server_runtime.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/recommendations/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/recommendations/engine.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/repository_truth.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/review/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/review/engine.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/root_identity.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/action_authority.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/action_intents.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/bootstrap.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/data_root.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/evidence.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/execution_context.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/health.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/mission_controller.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/runner.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/service.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/scheduler/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/scheduler/adapter.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/scheduler/ep_effect_v15.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/scheduler/ep_http_adapter.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/scheduler/ep_v12.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/scheduler/scheduler.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/capability-registry-1.0.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/capability.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/common.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/decision-evidence-1.0.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-goal-0.5.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-increment-proposal-0.5.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-mode.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-plan-0.5.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-platform-peer-configuration-1.0.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-platform-peer-configuration-1.1.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-prompt-artifact-0.7.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-proposal-0.6.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/ep-producer-readback-v1.2.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/ep-producer-readback-v1.3.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/evidence-reference-0.5.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/foundation-document.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/governance-profile.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/knowledge-source-0.4.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/knowledge-source.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/mission-candidate-1.0.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/mission-candidate-1.1.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/phase-completion-1.0.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/planning-document-0.5.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/repository-catalog.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/repository.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/solution-template-1.0.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/workspace.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/secure_store.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/serial_correlation_projection.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/solutions/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/solutions/catalogue.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/solutions/generator.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/state/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/state/mission_state.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/worklist_conditions.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/worklist_control.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/workspace/__init__.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/workspace_read_grant.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/workspace_review_grant.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/workspace_review_inbox.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/workspace_worklist_control_grant.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/workspace_worklist_grant.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge_autonomy.egg-info/dependency_links.txt +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge_autonomy.egg-info/requires.txt +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge_autonomy.egg-info/top_level.txt +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/capability-registry-1.0.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/capability.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/common.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/decision-evidence-1.0.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/engineering-goal-0.5.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/engineering-increment-proposal-0.5.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/engineering-mode.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/engineering-plan-0.5.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/engineering-prompt-artifact-0.7.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/engineering-proposal-0.6.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/evidence-reference-0.5.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/foundation-document.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/governance-profile.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/knowledge-source-0.4.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/knowledge-source.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/mission-candidate-1.0.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/mission-candidate-1.1.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/phase-completion-1.0.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/planning-document-0.5.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/repository-catalog.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/repository.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/solution-template-1.0.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/workspace.schema.json +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/setup.cfg +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_action_authority.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_action_derivation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_action_derivation_canary_closure.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_action_derivation_evidence.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_action_derivation_qualification.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_action_frontier.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_action_intents.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_adaptive_action_sizing_design.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_advisory_http.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_agent_policy.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_aggregate_health_roadmap.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ai_architect_provider.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ai_architect_session.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ai_development_projection.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ai_provider_registry.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_approved_worklist.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_architecture_reasoning.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_architecture_review_engine.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_architecture_workspace.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_autonomous_orchestrator.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_bootstrap_adapter.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_bootstrap_mission_canary.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_bootstrap_mission_runner.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_bootstrap_mission_scheduler.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_bootstrap_sequence_qualification.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_business_governance_ingress.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_business_workspace.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_canonical_data_root.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_capability_delegation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_changed_product_coverage.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_cli_transport.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_codex_cli_runtime_prompt.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_codex_cli_session.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_constitutional_validation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_criterion_assessment_contract.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_criterion_completion_release_qualification.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_criterion_ep_http_boundary.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_criterion_planning_context.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_decision_evidence.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_default_project_quality_policy.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_distribution_identity.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_durable_action_derivation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_dynamic_mission_capability.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_effect_completion.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_effect_fixture_conformance.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_engineering_action_architecture.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_engineering_intent_authoring.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_engineering_intent_lifecycle.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_engineering_mission.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_engineering_planning.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_engineering_prompt_artifacts.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_engineering_proposals.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ep_effect_v15.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ep_http_adapter.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ep_simulator.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ep_simulator_rolling_planning.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_execution_context.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_execution_host_configuration.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_execution_host_contract.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_execution_loop.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_forge_console_admin_parity_contract.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_forge_console_configuration_contract.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_forge_console_hosting_contract.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_forge_operations_console_roadmap.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_forge_outer_loop_ci_roadmap.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_forge_v1_bootstrap_scheduler_contract.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_forge_v1_implementation_dag.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_foundation_loader.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_foundation_models.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_four_lane_planning.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_generation_one_bootstrap_qualification.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_generation_two_execution.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_generation_two_mission_intake.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_governance_authority.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_governed_candidate_intake.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_governed_coverage.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_historical_engineering_intent.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_host_control_completion.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_inner_loop_ci_contract.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_installation_pairing.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_installed_dynamic_mission_runtime.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_installed_http_successor_qualification.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_installed_lifecycle.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_integration_coordination.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_knowledge_consumption.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_live_project_roadmap_contract.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_completion_evaluator.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_controller.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_dispatcher.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_effect.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_lifecycle_cli.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_planner.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_recommendation_engine.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_recommendation_lifecycle.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_runtime_projection.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_runtime_scheduler.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_state_store.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_status.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_openai_action_derivation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_operational_reset.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_operations_health.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_operations_read_api.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_operator_identity.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_parallel_action_contract.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_phase_completion.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_platform_contract_documents.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_portfolio_intelligence_operation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_preserved_lifecycle.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_producer_contract.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_product_version_operations.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_productization_reconciliation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_programme_authorization.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_provider_context.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_provider_security.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_pypi_distribution_readback.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_recovered_partial_completion.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_release_operation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_repository_truth.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_role_aware_conversations_contract.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_root_identity.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_bootstrap.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_database.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_evidence.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_health.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_instance_persistence.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_prompt_generation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_service.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_status_projection.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_scheduler_dependency_boundary.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_schemas.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_secure_store.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_serial_correlation_projection.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_server_api_contract.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_server_project_dag_capability.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_server_runtime.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_solution_templates.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_substantive_mission_completion.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_tde_observe_report.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_terminal_continuation_replay.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_worklist_activation.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_worklist_conditions.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_worklist_control.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_workspace_read_grant.py +0 -0
- {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_workspace_review_inbox.py +0 -0
|
@@ -243,6 +243,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
243
243
|
configure_installation.add_argument("--timeout-seconds",type=float,default=10.0)
|
|
244
244
|
installation_commands.add_parser("show")
|
|
245
245
|
installation_commands.add_parser("preflight")
|
|
246
|
+
detach_installation = installation_commands.add_parser("detach")
|
|
247
|
+
detach_installation.add_argument("--operation-id", required=True)
|
|
248
|
+
detach_installation.add_argument("--expected-binding-id", required=True)
|
|
249
|
+
detach_installation.add_argument("--expected-configuration-revision", type=int, required=True)
|
|
250
|
+
installation_detach_status = installation_commands.add_parser("detach-status")
|
|
251
|
+
installation_detach_status.add_argument("--operation-id", required=True)
|
|
246
252
|
execution_host = subparsers.add_parser("execution-host", help="manage the selected Execution Host peer")
|
|
247
253
|
execution_host_commands = execution_host.add_subparsers(dest="execution_host_command", required=True)
|
|
248
254
|
configure = execution_host_commands.add_parser("configure", help="persist one explicit EP peer binding")
|
|
@@ -560,6 +566,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
560
566
|
endpoint=args.endpoint,ep_instance_id=args.expected_instance_id,consumer_id=args.consumer_id,
|
|
561
567
|
credential_reference=args.credential_reference,allow_loopback_http=args.allow_loopback_http,
|
|
562
568
|
timeout_seconds=args.timeout_seconds)
|
|
569
|
+
elif args.installation_peer_command == "detach":
|
|
570
|
+
result = service.detach(operation_id=args.operation_id,
|
|
571
|
+
expected_binding_id=args.expected_binding_id,
|
|
572
|
+
expected_configuration_revision=args.expected_configuration_revision)
|
|
573
|
+
elif args.installation_peer_command == "detach-status": result = service.detach_status(args.operation_id)
|
|
563
574
|
elif args.installation_peer_command == "show": result = service.show()
|
|
564
575
|
else: result = service.preflight()
|
|
565
576
|
print(json.dumps(result,sort_keys=True))
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Private-token HTTP-equivalent Candidate commands; no admin fallback."""
|
|
2
|
+
import argparse,json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from .advisory_candidate_contract import CONTRACT
|
|
5
|
+
from .advisory_candidate_grant import AdvisoryCandidateGrant
|
|
6
|
+
from .advisory_candidate_service import AdvisoryCandidateService
|
|
7
|
+
from .workspace_review_grant import _private_bytes
|
|
8
|
+
|
|
9
|
+
def main(argv=None):
|
|
10
|
+
p=argparse.ArgumentParser(prog='forge-advisory-candidate');p.add_argument('--data-root',required=True);p.add_argument('--token-file',required=True);c=p.add_subparsers(dest='action',required=True);c.add_parser('capability')
|
|
11
|
+
source=c.add_parser('source');source.add_argument('--conversation-id',required=True);source.add_argument('--turn-id',required=True)
|
|
12
|
+
preview=c.add_parser('preview');preview.add_argument('--conversation-id',required=True);preview.add_argument('--proposal-id',required=True);preview.add_argument('--revision',type=int)
|
|
13
|
+
op=c.add_parser('operation');op.add_argument('--conversation-id',required=True);op.add_argument('--proposal-id',required=True);op.add_argument('--operation-id',required=True)
|
|
14
|
+
for name in ['save','register']:s=c.add_parser(name);s.add_argument('--request-file',required=True)
|
|
15
|
+
a=p.parse_args(argv)
|
|
16
|
+
try:
|
|
17
|
+
from .server_runtime import existing_instance
|
|
18
|
+
i=existing_instance(a.data_root);g=AdvisoryCandidateGrant(i.data_root,i.instance_id);t=Path(a.token_file)
|
|
19
|
+
if t.is_symlink() or t.stat().st_mode&0o077 or t.stat().st_size>256:raise ValueError('private token file required')
|
|
20
|
+
token='Bearer '+t.read_text().strip();service=AdvisoryCandidateService(i.data_root,g);method='GET';body=None
|
|
21
|
+
if a.action=='capability':target='/v1/advisory-candidates/capability'
|
|
22
|
+
elif a.action=='source':target='/v1/advisory-candidates/'+a.conversation_id+'/source/'+a.turn_id
|
|
23
|
+
elif a.action=='preview':target='/v1/advisory-candidates/'+a.conversation_id+'/proposals/'+a.proposal_id+('' if a.revision is None else '?revision='+str(a.revision))
|
|
24
|
+
elif a.action=='operation':target='/v1/advisory-candidates/'+a.conversation_id+'/proposals/'+a.proposal_id+'/registrations/'+a.operation_id
|
|
25
|
+
else:
|
|
26
|
+
body=json.loads(_private_bytes(Path(a.request_file)));method='POST';target='/v1/advisory-candidates/'+body['conversation_id']+'/proposals'+('' if a.action=='save' else '/'+body['proposal_id']+'/registrations')
|
|
27
|
+
status,out=service.handle(method,target,token,body);print(json.dumps(out,sort_keys=True));return 0 if status==200 else 1
|
|
28
|
+
except (OSError,ValueError,KeyError,RuntimeError):print(json.dumps({'contract_version':CONTRACT,'error':{'code':'CANDIDATE_CLI_UNAVAILABLE'}}));return 1
|
|
29
|
+
|
|
30
|
+
if __name__=='__main__':raise SystemExit(main())
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Explicit user-authored Candidate fields and immutable advice provenance."""
|
|
2
|
+
from .advisory_contract import digest,text,AdvisoryConflict
|
|
3
|
+
from .approved_worklist import identifier
|
|
4
|
+
from .models.mission_effect import MissionEffectPolicy
|
|
5
|
+
from .lifecycle import MissionCandidate,MissionRecommendation
|
|
6
|
+
|
|
7
|
+
CONTRACT='forge-advisory-candidate/v1'
|
|
8
|
+
|
|
9
|
+
def revision(value):
|
|
10
|
+
if type(value) is not int or value<0:raise ValueError('invalid revision')
|
|
11
|
+
return value
|
|
12
|
+
|
|
13
|
+
def hash_reference(value):
|
|
14
|
+
if not isinstance(value,str) or len(value)!=71 or not value.startswith('sha256:') or any(c not in '0123456789abcdef' for c in value[7:]):raise ValueError('invalid digest')
|
|
15
|
+
return value
|
|
16
|
+
|
|
17
|
+
def exact(value,fields):
|
|
18
|
+
if not isinstance(value,dict) or set(value)!=set(fields.split()) or value.get('contract_version')!=CONTRACT:raise ValueError('closed Candidate contract required')
|
|
19
|
+
return value
|
|
20
|
+
|
|
21
|
+
def fields(value):
|
|
22
|
+
names='title objective business_value engineering_value architectural_value rationale confidence scope exclusions acceptance_criteria architecture_constraints dependencies effect_policy'
|
|
23
|
+
if not isinstance(value,dict) or set(value)!=set(names.split()):raise ValueError('explicit structured fields required')
|
|
24
|
+
for k in ['title','objective','business_value','engineering_value','architectural_value','rationale']:text(value[k],1000 if k!='title' else 256)
|
|
25
|
+
if type(value['confidence']) is not int or not 0<=value['confidence']<=100:raise ValueError('explicit confidence required')
|
|
26
|
+
for k in ['scope','exclusions','acceptance_criteria','architecture_constraints','dependencies']:
|
|
27
|
+
a=value[k]
|
|
28
|
+
if not isinstance(a,list) or len(a)>8 or len(a)!=len(set(a)):raise ValueError('bounded unique fields required')
|
|
29
|
+
for v in a:text(v,1000)
|
|
30
|
+
if not value['scope'] or not value['acceptance_criteria'] or any(len(v.strip())<20 for v in value['acceptance_criteria']):raise ValueError('substantive scope and criteria required')
|
|
31
|
+
policy=MissionEffectPolicy.from_dict(value['effect_policy'])
|
|
32
|
+
if policy.mode in {'DOCUMENTATION_ONLY','ARCHITECTURE_DESIGN_ONLY'} and any(v.endswith('/') for v in policy.write_paths):raise ValueError('document proposals require exact document paths')
|
|
33
|
+
return value
|
|
34
|
+
|
|
35
|
+
def proposal_request(value):
|
|
36
|
+
exact(value,'contract_version instance_id project_id repository_id conversation_id proposal_id turn_id expected_revision expected_conversation_revision context_revision fields')
|
|
37
|
+
for k in ['instance_id','project_id','repository_id','conversation_id','proposal_id','turn_id']:identifier(value[k])
|
|
38
|
+
revision(value['expected_revision']);revision(value['expected_conversation_revision']);hash_reference(value['context_revision']);fields(value['fields'])
|
|
39
|
+
return value
|
|
40
|
+
|
|
41
|
+
def registration_request(value):
|
|
42
|
+
exact(value,'contract_version operation_id instance_id project_id repository_id conversation_id proposal_id proposal_revision proposal_digest expected_conversation_revision context_revision confirm')
|
|
43
|
+
for k in ['operation_id','instance_id','project_id','repository_id','conversation_id','proposal_id']:identifier(value[k])
|
|
44
|
+
if revision(value['proposal_revision'])<1:raise ValueError('proposal revision required')
|
|
45
|
+
revision(value['expected_conversation_revision']);hash_reference(value['proposal_digest']);hash_reference(value['context_revision'])
|
|
46
|
+
if value['confirm'] is not True:raise ValueError('explicit confirmation required')
|
|
47
|
+
return value
|
|
48
|
+
|
|
49
|
+
def candidate_objects(proposal,key,occurred_at):
|
|
50
|
+
"""Only declared user fields populate the existing governance models."""
|
|
51
|
+
if proposal.get('contract_version') == 'forge-chat-first-mission/v1':
|
|
52
|
+
from .mission_concept_registration import candidate_objects as generated_objects
|
|
53
|
+
return generated_objects(proposal,key,occurred_at)
|
|
54
|
+
f=fields(proposal['fields']);source=proposal['source'];rec_id='advice-recommendation-'+key[7:39];can_id='advice-candidate-'+key[7:39]
|
|
55
|
+
rec=MissionRecommendation(id=rec_id,title=f['title'],mission_origin=source['advisor_kind'].lower(),
|
|
56
|
+
business_summary=f['objective'],engineering_summary=f['objective'],business_value=f['business_value'],
|
|
57
|
+
engineering_value=f['engineering_value'],architectural_value=f['architectural_value'],
|
|
58
|
+
repository_evidence=tuple(source['evidence_references']),decision_evidence_reference=source['result_digest'],
|
|
59
|
+
dependencies=tuple(f['dependencies']),alternatives=(),confidence=f['confidence'],recommendation_timestamp=occurred_at,
|
|
60
|
+
known_constraints=tuple(f['exclusions']),evidence_references=(source['request_digest'],source['context_revision']))
|
|
61
|
+
candidate=MissionCandidate(id=can_id,recommendation_id=rec.id,title=f['title'],objective=f['objective'],
|
|
62
|
+
scope=tuple(f['scope']),acceptance_criteria=tuple(f['acceptance_criteria']),
|
|
63
|
+
architecture_constraints=tuple(f['architecture_constraints'])+tuple('EXCLUDED: '+v for v in f['exclusions']),
|
|
64
|
+
dependencies=tuple(f['dependencies']),effect_policy=MissionEffectPolicy.from_dict(f['effect_policy']))
|
|
65
|
+
return rec,candidate
|
|
66
|
+
|
|
67
|
+
def registration_key(principal,proposal):
|
|
68
|
+
return digest([principal,proposal['project_id'],proposal['repository_id'],proposal['conversation_id'],proposal['proposal_id'],proposal['proposal_revision']])
|
|
69
|
+
|
|
70
|
+
def registration_receipt(principal,operation_id,key,proposal,occurred_at):
|
|
71
|
+
if proposal.get('contract_version') == 'forge-chat-first-mission/v1':
|
|
72
|
+
from .mission_concept_registration import registration_receipt as generated_receipt
|
|
73
|
+
return generated_receipt(principal,operation_id,key,proposal,occurred_at)
|
|
74
|
+
from .approved_worklist import timestamp
|
|
75
|
+
timestamp(occurred_at)
|
|
76
|
+
if proposal['principal_reference']!=principal or registration_key(principal,proposal)!=key:raise ValueError('registration correlation differs')
|
|
77
|
+
rec,can=candidate_objects(proposal,key,occurred_at)
|
|
78
|
+
return {'contract_version':CONTRACT,'operation_id':identifier(operation_id),'principal_reference':principal,'registration_key':key,'proposal_digest':proposal['proposal_digest'],'proposal_revision':proposal['proposal_revision'],'source':proposal['source'],'candidate':can.to_dict(),'recommendation_id':rec.id,'candidate_digest':digest(can.to_dict()),'recommendation_digest':digest(rec.to_dict()),'registered_at':occurred_at,'status_at_registration':'RECOMMENDED','rationale':proposal['fields']['rationale']}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Separate owner capability for one conversation and exact Candidate proposal IDs."""
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from datetime import UTC,datetime,timedelta
|
|
5
|
+
from hashlib import sha256
|
|
6
|
+
import argparse,json,secrets
|
|
7
|
+
from .advisory_candidate_contract import CONTRACT
|
|
8
|
+
from .advisory_grant import project_scope,conversation_path
|
|
9
|
+
from .approved_worklist import identifier,timestamp
|
|
10
|
+
from .workspace_review_grant import _locked,_private_bytes,_write_private,_new_token
|
|
11
|
+
from .workspace_worklist_control_grant import current_operator
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class CandidatePrincipal:
|
|
15
|
+
principal_id:str
|
|
16
|
+
instance_id:str
|
|
17
|
+
project_id:str
|
|
18
|
+
repository_id:str
|
|
19
|
+
conversation_id:str
|
|
20
|
+
proposal_ids:tuple
|
|
21
|
+
grant_id:str
|
|
22
|
+
maximum_registrations:int
|
|
23
|
+
@property
|
|
24
|
+
def reference(self):return self.instance_id+':'+self.principal_id
|
|
25
|
+
@property
|
|
26
|
+
def conversation_ids(self):return (self.conversation_id,)
|
|
27
|
+
@property
|
|
28
|
+
def maximum_turns(self):return 8
|
|
29
|
+
|
|
30
|
+
class AdvisoryCandidateGrant:
|
|
31
|
+
def __init__(self,root,instance_id):
|
|
32
|
+
self.root=Path(root);self.instance_id=identifier(instance_id);self.path=self.root/'credentials'/'advisory-candidate'/'grants.json'
|
|
33
|
+
def _records(self,missing=False):
|
|
34
|
+
if missing and not self.path.exists() and not self.path.is_symlink():return []
|
|
35
|
+
v=json.loads(_private_bytes(self.path));names=set('principal_id instance_id project_id repository_id conversation_id proposal_ids grant_id maximum_registrations expires_at state token_digest'.split())
|
|
36
|
+
if not isinstance(v,dict) or set(v)!={'contract_version','instance_id','records'} or v['contract_version']!=CONTRACT or v['instance_id']!=self.instance_id or not isinstance(v['records'],list) or len(v['records'])>64:raise ValueError('invalid Candidate grant store')
|
|
37
|
+
records=v['records'];seen=set()
|
|
38
|
+
for r in records:
|
|
39
|
+
if not isinstance(r,dict) or set(r)!=names:raise ValueError('invalid Candidate grant')
|
|
40
|
+
for k in ['principal_id','instance_id','project_id','repository_id','conversation_id','grant_id']:identifier(r[k])
|
|
41
|
+
timestamp(r['expires_at'])
|
|
42
|
+
if r['instance_id']!=self.instance_id or r['grant_id'] in seen or r['state'] not in {'ACTIVE','REVOKED'} or type(r['maximum_registrations']) is not int or not 1<=r['maximum_registrations']<=8:raise ValueError('invalid grant binding')
|
|
43
|
+
if not isinstance(r['proposal_ids'],list) or not 1<=len(r['proposal_ids'])<=16 or sorted(set(r['proposal_ids']))!=r['proposal_ids']:raise ValueError('bounded proposal identities required')
|
|
44
|
+
for v in r['proposal_ids']:identifier(v)
|
|
45
|
+
h=r['token_digest']
|
|
46
|
+
if not isinstance(h,str) or len(h)!=64 or any(c not in '0123456789abcdef' for c in h):raise ValueError('invalid token digest')
|
|
47
|
+
seen.add(r['grant_id'])
|
|
48
|
+
return records
|
|
49
|
+
def _save(self,records):
|
|
50
|
+
raw=json.dumps({'contract_version':CONTRACT,'instance_id':self.instance_id,'records':records},sort_keys=True).encode()
|
|
51
|
+
if len(raw)>65536:raise ValueError('grant capacity exhausted')
|
|
52
|
+
_write_private(self.path,raw)
|
|
53
|
+
def issue(self,*,principal_id,project_id,repository_id,conversation_id,proposal_ids,expires_at,maximum_registrations,token_path):
|
|
54
|
+
for v in [principal_id,project_id,repository_id,conversation_id]:identifier(v)
|
|
55
|
+
if not isinstance(proposal_ids,(tuple,list)) or not 1<=len(proposal_ids)<=16 or len(set(proposal_ids))!=len(proposal_ids):raise ValueError('bounded proposals required')
|
|
56
|
+
for v in proposal_ids:identifier(v)
|
|
57
|
+
if type(maximum_registrations) is not int or not 1<=maximum_registrations<=8 or not datetime.now(UTC)<timestamp(expires_at)<=datetime.now(UTC)+timedelta(days=30):raise ValueError('bounded grant required')
|
|
58
|
+
token_path=Path(token_path)
|
|
59
|
+
if token_path.resolve()==self.path.resolve():raise ValueError('token/store collision')
|
|
60
|
+
with _locked(self.path):
|
|
61
|
+
current_operator(self.root,self.instance_id);scope=project_scope(self.root,self.instance_id)
|
|
62
|
+
if (scope['project_id'],scope['repository_id'])!=(project_id,repository_id):raise PermissionError('foreign project')
|
|
63
|
+
path=conversation_path(self.root,self.instance_id,project_id,repository_id,conversation_id)
|
|
64
|
+
if path.exists() or path.is_symlink():
|
|
65
|
+
if json.loads(_private_bytes(path))['principal_reference']!=self.instance_id+':'+principal_id:raise PermissionError('foreign advice owner')
|
|
66
|
+
records=self._records(True)
|
|
67
|
+
if len(records)>=64:raise ValueError('grant capacity exhausted')
|
|
68
|
+
token=secrets.token_urlsafe(48);r={**scope,'principal_id':principal_id,'conversation_id':conversation_id,'proposal_ids':sorted(proposal_ids),'grant_id':'candidate-'+secrets.token_hex(16),'maximum_registrations':maximum_registrations,'expires_at':expires_at,'state':'ACTIVE','token_digest':sha256(token.encode()).hexdigest()}
|
|
69
|
+
_new_token(token_path,token)
|
|
70
|
+
try:self._save([*records,r])
|
|
71
|
+
except Exception:token_path.unlink(missing_ok=True);raise
|
|
72
|
+
return {k:v for k,v in r.items() if k!='token_digest'}
|
|
73
|
+
def revoke(self,grant_id):
|
|
74
|
+
identifier(grant_id)
|
|
75
|
+
with _locked(self.path):
|
|
76
|
+
current_operator(self.root,self.instance_id);records=self._records();r=next((r for r in records if r['grant_id']==grant_id),None)
|
|
77
|
+
if r is None:raise ValueError('unknown grant')
|
|
78
|
+
r['state']='REVOKED';self._save(records);return {'grant_id':grant_id,'state':'REVOKED'}
|
|
79
|
+
def authenticate(self,authorization):
|
|
80
|
+
if not isinstance(authorization,str) or not authorization.startswith('Bearer '):return None
|
|
81
|
+
token=authorization[7:]
|
|
82
|
+
if not token or len(token)>256 or any(ord(c)<33 or ord(c)>126 for c in token):return None
|
|
83
|
+
try:
|
|
84
|
+
r=next((r for r in self._records() if secrets.compare_digest(r['token_digest'],sha256(token.encode()).hexdigest())),None)
|
|
85
|
+
if r is None or r['state']!='ACTIVE' or timestamp(r['expires_at'])<=datetime.now(UTC):return None
|
|
86
|
+
return CandidatePrincipal(**{k:r[k] for k in CandidatePrincipal.__dataclass_fields__ if k!='proposal_ids'},proposal_ids=tuple(r['proposal_ids']))
|
|
87
|
+
except (OSError,ValueError,KeyError,TypeError):return None
|
|
88
|
+
def authorize(self,authorization,conversation_id=None,proposal_id=None):
|
|
89
|
+
p=self.authenticate(authorization)
|
|
90
|
+
if p is None:raise PermissionError('Candidate credential unavailable')
|
|
91
|
+
current_operator(self.root,self.instance_id)
|
|
92
|
+
if project_scope(self.root,self.instance_id)!={k:getattr(p,k) for k in ['instance_id','project_id','repository_id']}:raise PermissionError('project changed')
|
|
93
|
+
if conversation_id is not None and conversation_id!=p.conversation_id or proposal_id is not None and proposal_id not in p.proposal_ids:raise PermissionError('foreign proposal scope')
|
|
94
|
+
return p
|
|
95
|
+
|
|
96
|
+
def main(argv=None):
|
|
97
|
+
parser=argparse.ArgumentParser(prog='forge-advisory-candidate-grant');parser.add_argument('--data-root',required=True);c=parser.add_subparsers(dest='action',required=True);i=c.add_parser('issue')
|
|
98
|
+
for k in ['principal-id','project-id','repository-id','conversation-id','expires-at','token-file']:i.add_argument('--'+k,required=True)
|
|
99
|
+
i.add_argument('--proposal-id',action='append',required=True);i.add_argument('--maximum-registrations',type=int,default=1);r=c.add_parser('revoke');r.add_argument('--grant-id',required=True);a=parser.parse_args(argv)
|
|
100
|
+
try:
|
|
101
|
+
from .server_runtime import existing_instance
|
|
102
|
+
instance=existing_instance(a.data_root);g=AdvisoryCandidateGrant(instance.data_root,instance.instance_id)
|
|
103
|
+
out=g.revoke(a.grant_id) if a.action=='revoke' else g.issue(principal_id=a.principal_id,project_id=a.project_id,repository_id=a.repository_id,conversation_id=a.conversation_id,proposal_ids=a.proposal_id,expires_at=a.expires_at,maximum_registrations=a.maximum_registrations,token_path=a.token_file)
|
|
104
|
+
print(json.dumps(out,sort_keys=True));return 0
|
|
105
|
+
except (OSError,ValueError,RuntimeError,PermissionError):print(json.dumps({'contract_version':CONTRACT,'error':{'code':'CANDIDATE_GRANT_UNAVAILABLE'}}));return 1
|
|
106
|
+
|
|
107
|
+
if __name__=='__main__':raise SystemExit(main())
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""A bounded explicit proposal-to-Candidate bridge over canonical governance."""
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from datetime import UTC,datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import json,sqlite3,os
|
|
6
|
+
from urllib.parse import urlsplit,parse_qs
|
|
7
|
+
from .advisory_candidate_contract import CONTRACT,exact,fields,proposal_request,registration_request,candidate_objects,revision,hash_reference,registration_key,registration_receipt
|
|
8
|
+
from .advisory_contract import digest,AdvisoryConflict
|
|
9
|
+
from .advisory_service import AdvisoryService,conversation_lock
|
|
10
|
+
from .advisory_grant import conversation_path
|
|
11
|
+
from .advisory_context import AdvisoryContext
|
|
12
|
+
from .approved_worklist import identifier
|
|
13
|
+
from .workspace_review_grant import _private_bytes,_write_private,_locked
|
|
14
|
+
from .lifecycle import RecommendationLifecycleStore,LifecycleError
|
|
15
|
+
from .worklist_control import control_runtime
|
|
16
|
+
|
|
17
|
+
class AdvisoryCandidateService:
|
|
18
|
+
def __init__(self,root,grant):self.root=Path(root);self.grant=grant
|
|
19
|
+
def _path(self,p,proposal_id):return self.root/'advisory'/'candidate-proposals'/(digest([p.instance_id,p.project_id,p.repository_id,p.conversation_id,proposal_id])[7:]+'.json')
|
|
20
|
+
def _source(self,token,p,turn_id):
|
|
21
|
+
value=AdvisoryService(self.root,self.grant,'unused-for-read').read(token,p.conversation_id,turn_id)
|
|
22
|
+
t=value['original_turn']
|
|
23
|
+
if t['status']!='COMPLETE' or t['execution']!='CONFIRMED' or t['outcome']['output'] is None:raise AdvisoryConflict('ADVICE_NOT_COMPLETE')
|
|
24
|
+
_,current=AdvisoryService(self.root,self.grant,'unused-for-read').context(p,t['request']['selected_sources'])
|
|
25
|
+
if current!=t['request']['context_revision']:raise AdvisoryConflict('SOURCE_CONTEXT_CHANGED')
|
|
26
|
+
return {'turn_id':turn_id,'session_id':t['session_id'],'invocation_id':t['invocation_id'],'request_digest':t['request_digest'],
|
|
27
|
+
'result_digest':t['outcome']['result_digest'],'advisor_kind':t['request']['advisor_kind'],
|
|
28
|
+
'context_revision':current,'conversation_revision':value['current_revision'],
|
|
29
|
+
'selected_sources':t['request']['selected_sources'],'evidence_references':t['context']['evidence_references'],
|
|
30
|
+
'advice_summary':t['outcome']['output']['summary']}
|
|
31
|
+
@contextmanager
|
|
32
|
+
def _lease(self,token,conversation_id,proposal_id):
|
|
33
|
+
p=self.grant.authorize(token,conversation_id,proposal_id)
|
|
34
|
+
path=conversation_path(self.root,p.instance_id,p.project_id,p.repository_id,p.conversation_id)
|
|
35
|
+
with conversation_lock(path),_locked(self.grant.path),_locked(AdvisoryContext(self.root,p.instance_id).path):
|
|
36
|
+
yield self.grant.authorize(token,conversation_id,proposal_id)
|
|
37
|
+
def _load(self,token,p,proposal_id,create=False):
|
|
38
|
+
path=self._path(p,proposal_id)
|
|
39
|
+
if create and not path.exists() and not path.is_symlink():
|
|
40
|
+
return {'contract_version':CONTRACT,'principal_reference':p.reference,'scope':{k:getattr(p,k) for k in ['instance_id','project_id','repository_id']},'conversation_id':p.conversation_id,'proposal_id':proposal_id,'maximum_registrations':p.maximum_registrations,'creation_grant_id':p.grant_id,'revisions':[]}
|
|
41
|
+
try:v=json.loads(_private_bytes(path))
|
|
42
|
+
except FileNotFoundError:raise
|
|
43
|
+
except (OSError,ValueError):raise RuntimeError('private proposal unavailable') from None
|
|
44
|
+
if isinstance(v,dict) and v.get('principal_reference')!=p.reference:raise PermissionError('foreign proposal owner')
|
|
45
|
+
if (not isinstance(v,dict) or set(v)!=set('contract_version principal_reference scope conversation_id proposal_id maximum_registrations creation_grant_id revisions'.split()) or v['contract_version']!=CONTRACT or v['scope']!={k:getattr(p,k) for k in ['instance_id','project_id','repository_id']} or v['proposal_id']!=proposal_id or v['conversation_id']!=p.conversation_id or type(v['maximum_registrations']) is not int or not 1<=v['maximum_registrations']<=8 or not isinstance(v['revisions'],list) or not 1<=len(v['revisions'])<=8):raise RuntimeError('stored proposal binding invalid')
|
|
46
|
+
creation=next((r for r in self.grant._records() if r['grant_id']==v['creation_grant_id']),None)
|
|
47
|
+
if creation is None or creation['maximum_registrations']!=v['maximum_registrations'] or creation['principal_id']!=p.principal_id or creation['conversation_id']!=p.conversation_id or proposal_id not in creation['proposal_ids'] or any(creation[k]!=getattr(p,k) for k in ['instance_id','project_id','repository_id']):raise RuntimeError('original proposal allowance unavailable')
|
|
48
|
+
for n,d in enumerate(v['revisions'],1):
|
|
49
|
+
if not isinstance(d,dict) or set(d)!=set('contract_version principal_reference instance_id project_id repository_id conversation_id proposal_id proposal_revision source fields field_origins proposal_digest'.split()):raise RuntimeError('stored proposal shape invalid')
|
|
50
|
+
if type(d['proposal_revision']) is not int or d['proposal_revision']!=n or d['contract_version']!=CONTRACT or d['principal_reference']!=p.reference or d['proposal_id']!=proposal_id or d['conversation_id']!=p.conversation_id or any(d[k]!=getattr(p,k) for k in ['instance_id','project_id','repository_id']):raise RuntimeError('stored proposal identity invalid')
|
|
51
|
+
if d['field_origins']!={'fields':'EXPLICIT_USER','source.advice_summary':'VALIDATED_ADVICE'}:raise RuntimeError('stored field origins invalid')
|
|
52
|
+
try:fields(d['fields']);actual=self._source(token,p,d['source']['turn_id'])
|
|
53
|
+
except (ValueError,TypeError,KeyError):raise RuntimeError('stored proposal fields invalid') from None
|
|
54
|
+
old={k:x for k,x in d['source'].items() if k!='conversation_revision'}
|
|
55
|
+
if old!={k:x for k,x in actual.items() if k!='conversation_revision'} or type(d['source']['conversation_revision']) is not int or not 1<=d['source']['conversation_revision']<=actual['conversation_revision']:raise RuntimeError('stored source provenance invalid')
|
|
56
|
+
if digest({k:x for k,x in d.items() if k!='proposal_digest'})!=d['proposal_digest']:raise RuntimeError('stored proposal digest invalid')
|
|
57
|
+
return v
|
|
58
|
+
def _save(self,p,proposal_id,value):
|
|
59
|
+
path=self._path(p,proposal_id)
|
|
60
|
+
if not path.exists() and len(list(path.parent.glob('*.json')))>=64:raise AdvisoryConflict('PROPOSAL_CAPACITY_EXHAUSTED')
|
|
61
|
+
raw=json.dumps(value,sort_keys=True,ensure_ascii=True).encode()
|
|
62
|
+
if len(raw)>65536:raise AdvisoryConflict('PROPOSAL_CAPACITY_EXHAUSTED')
|
|
63
|
+
_write_private(path,raw)
|
|
64
|
+
@staticmethod
|
|
65
|
+
def _scope(p,r):
|
|
66
|
+
if any(r[k]!=getattr(p,k) for k in ['instance_id','project_id','repository_id','conversation_id']):raise PermissionError('foreign proposal scope')
|
|
67
|
+
@staticmethod
|
|
68
|
+
def _key(p,d):return registration_key(p.reference,d)
|
|
69
|
+
def _database_path(self,create=False):
|
|
70
|
+
path=self.root/'governance'/'candidates.sqlite'
|
|
71
|
+
if any(x.is_symlink() for x in [self.root,path.parent,path]) or path.resolve().parent.parent!=self.root.resolve():raise RuntimeError('canonical aggregate redirected')
|
|
72
|
+
if create and not path.exists():
|
|
73
|
+
path.parent.mkdir(mode=0o700,parents=True,exist_ok=True)
|
|
74
|
+
fd=os.open(path,os.O_WRONLY|os.O_CREAT|os.O_EXCL|getattr(os,'O_NOFOLLOW',0),0o600);os.close(fd)
|
|
75
|
+
return path
|
|
76
|
+
@contextmanager
|
|
77
|
+
def _read_store(self):
|
|
78
|
+
path=self._database_path()
|
|
79
|
+
if not path.exists():yield None;return
|
|
80
|
+
with RecommendationLifecycleStore.read_only(path) as store:yield store
|
|
81
|
+
@staticmethod
|
|
82
|
+
def _has_table(store,name):return store is not None and store._connection.execute('SELECT 1 FROM sqlite_master WHERE type=? AND name=?',('table',name)).fetchone() is not None
|
|
83
|
+
def _registered(self,store,p,d):
|
|
84
|
+
if not self._has_table(store,'advisory_candidate_registrations'):return None
|
|
85
|
+
value=store.candidate_registration(self._key(p,d),p.reference)
|
|
86
|
+
if value is not None:
|
|
87
|
+
if (set(value)!=set('contract_version operation_id principal_reference registration_key proposal_digest proposal_revision source candidate recommendation_id candidate_digest recommendation_digest registered_at status_at_registration rationale'.split()) or value['contract_version']!=CONTRACT or value['registration_key']!=self._key(p,d) or value['principal_reference']!=p.reference or value['proposal_digest']!=d['proposal_digest'] or value['proposal_revision']!=d['proposal_revision'] or value['source']!=d['source'] or value['status_at_registration']!='RECOMMENDED'):raise RuntimeError('registration provenance unavailable')
|
|
88
|
+
rec,can=candidate_objects(d,self._key(p,d),value['registered_at'])
|
|
89
|
+
if value['candidate']!=json.loads(json.dumps(can.to_dict())) or value['candidate_digest']!=digest(can.to_dict()) or value['recommendation_id']!=rec.id or value['recommendation_digest']!=digest(rec.to_dict()) or value['rationale']!=d['fields']['rationale']:raise RuntimeError('registration subject binding invalid')
|
|
90
|
+
# Current Candidate may legitimately change; original Recommendation is immutable.
|
|
91
|
+
original=store._connection.execute('SELECT document FROM recommendations WHERE recommendation_id=?',(rec.id,)).fetchone()
|
|
92
|
+
if original is None or digest(json.loads(original[0]))!=value['recommendation_digest']:raise RuntimeError('canonical recommendation differs')
|
|
93
|
+
return value
|
|
94
|
+
def _readback(self,token,p,d,receipt,recorded=False):
|
|
95
|
+
self.grant.authorize(token,p.conversation_id,d['proposal_id']);actual=self._source(token,p,d['source']['turn_id'])
|
|
96
|
+
with self._read_store() as store:
|
|
97
|
+
original=self._registered(store,p,d)
|
|
98
|
+
if receipt!=original:raise RuntimeError('canonical receipt differs')
|
|
99
|
+
can=store.get_candidate(receipt['candidate']['id']);rec=store.get_recommendation(receipt['recommendation_id'])
|
|
100
|
+
current={'candidate':can.to_dict(),'candidate_digest':digest(can.to_dict()),'recommendation_status':rec.status.value,'conversation_revision':actual['conversation_revision'],'source_fresh':actual==d['source'],'mission_allocation':store.allocation_for_recommendation(rec.id) is not None}
|
|
101
|
+
return {'contract_version':CONTRACT,'recorded':recorded,'original_receipt':receipt,'current':current}
|
|
102
|
+
def capability(self,token):
|
|
103
|
+
p=self.grant.authorize(token)
|
|
104
|
+
return {'contract_version':CONTRACT,**{k:getattr(p,k) for k in ['instance_id','project_id','repository_id','conversation_id']},'proposal_ids':list(p.proposal_ids),'maximum_registrations':p.maximum_registrations,'registration_authority':'CANDIDATE_ONLY_NO_APPROVAL_OR_EXECUTION','additional_model_calls':0,'maximum_proposals_per_instance':64,'maximum_revisions_per_proposal':8,'read_only':True}
|
|
105
|
+
def source(self,token,conversation_id,turn_id):
|
|
106
|
+
identifier(turn_id);p=self.grant.authorize(token,conversation_id)
|
|
107
|
+
return {'contract_version':CONTRACT,'source':self._source(token,p,turn_id),'read_only':True}
|
|
108
|
+
def save(self,token,r):
|
|
109
|
+
r=proposal_request(r)
|
|
110
|
+
with self._lease(token,r['conversation_id'],r['proposal_id']) as p:
|
|
111
|
+
self._scope(p,r);source=self._source(token,p,r['turn_id'])
|
|
112
|
+
if source['conversation_revision']!=r['expected_conversation_revision'] or source['context_revision']!=r['context_revision']:raise AdvisoryConflict('SOURCE_STALE')
|
|
113
|
+
v=self._load(token,p,r['proposal_id'],True)
|
|
114
|
+
if len(v['revisions'])!=r['expected_revision']:raise AdvisoryConflict('PROPOSAL_STALE')
|
|
115
|
+
if len(v['revisions'])>=8:raise AdvisoryConflict('PROPOSAL_CAPACITY_EXHAUSTED')
|
|
116
|
+
d={'contract_version':CONTRACT,'principal_reference':p.reference,**{k:getattr(p,k) for k in ['instance_id','project_id','repository_id','conversation_id']},'proposal_id':r['proposal_id'],'proposal_revision':len(v['revisions'])+1,'source':source,'fields':r['fields'],'field_origins':{'fields':'EXPLICIT_USER','source.advice_summary':'VALIDATED_ADVICE'}}
|
|
117
|
+
d['proposal_digest']=digest(d);candidate_objects(d,self._key(p,d),'preview-not-registered')
|
|
118
|
+
v['revisions'].append(d);self._save(p,r['proposal_id'],v)
|
|
119
|
+
return {'contract_version':CONTRACT,'proposal':d,'registered':False,'additional_model_calls':0}
|
|
120
|
+
def preview(self,token,conversation_id,proposal_id,number=None):
|
|
121
|
+
p=self.grant.authorize(token,conversation_id,proposal_id);v=self._load(token,p,proposal_id)
|
|
122
|
+
if number is None:number=len(v['revisions'])
|
|
123
|
+
if type(number) is not int or not 1<=number<=len(v['revisions']):raise FileNotFoundError('unknown proposal revision')
|
|
124
|
+
d=v['revisions'][number-1];_,can=candidate_objects(d,self._key(p,d),'preview-not-registered')
|
|
125
|
+
with self._read_store() as store:receipt=self._registered(store,p,d)
|
|
126
|
+
return {'contract_version':CONTRACT,'proposal':d,'latest_revision':len(v['revisions']),'candidate_preview':can.to_dict(),'registration':self._readback(token,p,d,receipt) if receipt else None,'read_only':True}
|
|
127
|
+
def register(self,token,r):
|
|
128
|
+
r=registration_request(r)
|
|
129
|
+
with self._lease(token,r['conversation_id'],r['proposal_id']) as p:
|
|
130
|
+
self._scope(p,r);v=self._load(token,p,r['proposal_id'])
|
|
131
|
+
n=r['proposal_revision']
|
|
132
|
+
if n>len(v['revisions']):raise AdvisoryConflict('PROPOSAL_STALE')
|
|
133
|
+
d=v['revisions'][n-1]
|
|
134
|
+
if d['proposal_digest']!=r['proposal_digest']:raise AdvisoryConflict('PROPOSAL_STALE')
|
|
135
|
+
key=self._key(p,d)
|
|
136
|
+
with self._read_store() as store:
|
|
137
|
+
old=store.candidate_registration_intent(r['operation_id'],p.reference) if self._has_table(store,'advisory_candidate_intents') else None
|
|
138
|
+
receipt=self._registered(store,p,d)
|
|
139
|
+
if old is not None and old['request']!=r:raise AdvisoryConflict('OPERATION_PAYLOAD_CONFLICT')
|
|
140
|
+
if receipt is not None:
|
|
141
|
+
if old is None:
|
|
142
|
+
actual=self._source(token,p,d['source']['turn_id'])
|
|
143
|
+
if n!=len(v['revisions']) or actual!=d['source'] or r['expected_conversation_revision']!=actual['conversation_revision'] or r['context_revision']!=actual['context_revision']:raise AdvisoryConflict('SOURCE_OR_PROPOSAL_STALE')
|
|
144
|
+
with control_runtime(self.root),RecommendationLifecycleStore(self._database_path()) as store:
|
|
145
|
+
self.grant.authorize(token,p.conversation_id,d['proposal_id'])
|
|
146
|
+
store.begin_candidate_registration(r['operation_id'],p.reference,key,{'request':r,'proposal':d,'registered_at':receipt['registered_at']},min(p.maximum_registrations,v['maximum_registrations']))
|
|
147
|
+
return self._readback(token,p,d,receipt)
|
|
148
|
+
actual=self._source(token,p,d['source']['turn_id'])
|
|
149
|
+
if n!=len(v['revisions']) or actual!=d['source'] or r['expected_conversation_revision']!=actual['conversation_revision'] or r['context_revision']!=actual['context_revision']:raise AdvisoryConflict('SOURCE_OR_PROPOSAL_STALE')
|
|
150
|
+
with control_runtime(self.root),RecommendationLifecycleStore(self._database_path(True)) as store:
|
|
151
|
+
p=self.grant.authorize(token,p.conversation_id,d['proposal_id']);actual=self._source(token,p,d['source']['turn_id'])
|
|
152
|
+
if actual!=d['source']:raise AdvisoryConflict('SOURCE_STALE')
|
|
153
|
+
intent=old or {'request':r,'proposal':d,'registered_at':datetime.now(UTC).isoformat()}
|
|
154
|
+
if intent['proposal']!=d:raise RuntimeError('intent proposal differs')
|
|
155
|
+
store.begin_candidate_registration(r['operation_id'],p.reference,key,intent,min(p.maximum_registrations,v['maximum_registrations']))
|
|
156
|
+
p=self.grant.authorize(token,p.conversation_id,d['proposal_id']);self._source(token,p,d['source']['turn_id'])
|
|
157
|
+
rec,can=candidate_objects(d,key,intent['registered_at'])
|
|
158
|
+
receipt=registration_receipt(p.reference,r['operation_id'],key,d,intent['registered_at'])
|
|
159
|
+
receipt,recorded=store.finish_candidate_registration(r['operation_id'],p.reference,key,rec,can,receipt)
|
|
160
|
+
return self._readback(token,p,d,receipt,recorded)
|
|
161
|
+
def operation(self,token,conversation_id,proposal_id,operation_id):
|
|
162
|
+
identifier(operation_id);p=self.grant.authorize(token,conversation_id,proposal_id)
|
|
163
|
+
with self._read_store() as store:op=store.candidate_registration_intent(operation_id,p.reference) if self._has_table(store,'advisory_candidate_intents') else None
|
|
164
|
+
if op is None:raise FileNotFoundError('unknown operation')
|
|
165
|
+
r=registration_request(op['request'])
|
|
166
|
+
if r['conversation_id']!=conversation_id or r['proposal_id']!=proposal_id:raise PermissionError('foreign operation')
|
|
167
|
+
v=self._load(token,p,proposal_id);d=v['revisions'][r['proposal_revision']-1]
|
|
168
|
+
if op['proposal']!=d or r['proposal_digest']!=d['proposal_digest']:raise RuntimeError('operation provenance differs')
|
|
169
|
+
with self._read_store() as store:receipt=self._registered(store,p,d)
|
|
170
|
+
return self._readback(token,p,d,receipt) if receipt else {'contract_version':CONTRACT,'state':'PENDING','operation_id':operation_id,'read_only':True}
|
|
171
|
+
def handle(self,method,target,token,body=None):
|
|
172
|
+
try:
|
|
173
|
+
if self.grant.authenticate(token) is None:return 401,{'contract_version':CONTRACT,'error':{'code':'CANDIDATE_AUTHENTICATION_REQUIRED'}}
|
|
174
|
+
parsed=urlsplit(target);a=parsed.path.split('/');q=parse_qs(parsed.query,keep_blank_values=True)
|
|
175
|
+
if method=='GET' and parsed.path=='/v1/advisory-candidates/capability' and not q:return 200,self.capability(token)
|
|
176
|
+
if len(a)>=4 and a[:3]==['','v1','advisory-candidates']:
|
|
177
|
+
conv=identifier(a[3])
|
|
178
|
+
if method=='GET' and len(a)==6 and a[4]=='source' and not q:return 200,self.source(token,conv,a[5])
|
|
179
|
+
if method=='POST' and len(a)==5 and a[4]=='proposals' and not q:
|
|
180
|
+
if body['conversation_id']!=conv:raise PermissionError('route scope differs')
|
|
181
|
+
return 200,self.save(token,body)
|
|
182
|
+
if len(a)>=6 and a[4]=='proposals':
|
|
183
|
+
prop=identifier(a[5])
|
|
184
|
+
if method=='GET' and len(a)==6 and set(q)<={'revision'} and len(q.get('revision',[]))<=1:
|
|
185
|
+
return 200,self.preview(token,conv,prop,int(q['revision'][0]) if q else None)
|
|
186
|
+
if method=='POST' and len(a)==7 and a[6]=='registrations' and not q:
|
|
187
|
+
if body['conversation_id']!=conv or body['proposal_id']!=prop:raise PermissionError('route scope differs')
|
|
188
|
+
return 200,self.register(token,body)
|
|
189
|
+
if method=='GET' and len(a)==8 and a[6]=='registrations' and not q:return 200,self.operation(token,conv,prop,a[7])
|
|
190
|
+
raise PermissionError('Candidate grant does not authorize route')
|
|
191
|
+
except AdvisoryConflict as e:status=409;code=e.code
|
|
192
|
+
except PermissionError:status=403;code='CANDIDATE_SCOPE_DENIED'
|
|
193
|
+
except FileNotFoundError:status=404;code='CANDIDATE_SUBJECT_NOT_FOUND'
|
|
194
|
+
except LifecycleError:status=409;code='CANDIDATE_LIFECYCLE_CONFLICT'
|
|
195
|
+
except (ValueError,TypeError,KeyError,IndexError):status=400;code='CANDIDATE_REQUEST_INVALID'
|
|
196
|
+
except (RuntimeError,OSError,sqlite3.Error):status=503;code='CANDIDATE_SOURCE_UNAVAILABLE'
|
|
197
|
+
return status,{'contract_version':CONTRACT,'error':{'code':code}}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"""Owner-selected immutable public repository snapshots with current private ACL."""
|
|
2
2
|
from pathlib import Path
|
|
3
|
+
from datetime import UTC,datetime
|
|
3
4
|
from hashlib import sha256
|
|
4
5
|
import argparse
|
|
5
6
|
import json
|
|
6
7
|
import re
|
|
7
8
|
from .advisory_contract import CONTRACT, digest, text, AdvisoryConflict
|
|
8
9
|
from .advisory_grant import project_scope
|
|
9
|
-
from .approved_worklist import identifier
|
|
10
|
+
from .approved_worklist import identifier,timestamp
|
|
10
11
|
from .completion.repository_observer import GitHubRepositoryArtifactReader
|
|
11
12
|
from .execution_host_configuration import read_peer_configuration
|
|
12
13
|
from .workspace_worklist_control_grant import current_operator
|
|
@@ -24,8 +25,10 @@ class AdvisoryContext:
|
|
|
24
25
|
if not isinstance(records,list) or len(records)>16:raise RuntimeError('context catalog invalid')
|
|
25
26
|
seen=set()
|
|
26
27
|
for r in records:
|
|
27
|
-
if not isinstance(r,dict) or set(r)
|
|
28
|
-
try:
|
|
28
|
+
if not isinstance(r,dict) or set(r) not in (fields,fields|{'observed_at'}) or r['state'] not in ('ACTIVE','REVOKED'):raise RuntimeError('context shape invalid')
|
|
29
|
+
try:
|
|
30
|
+
identifier(r['source_id']);text(r['content'],2048)
|
|
31
|
+
if 'observed_at' in r:timestamp(r['observed_at'])
|
|
29
32
|
except (ValueError,TypeError):raise RuntimeError('context content invalid') from None
|
|
30
33
|
if r['source_id'] in seen or r['content_digest']!='sha256:'+sha256(r['content'].encode()).hexdigest() or r['version']!=digest({k:v for k,v in r.items() if k not in ('state','version')}):raise RuntimeError('context provenance invalid')
|
|
31
34
|
seen.add(r['source_id'])
|
|
@@ -44,7 +47,8 @@ class AdvisoryContext:
|
|
|
44
47
|
text(content,2048);records=self._read(True)
|
|
45
48
|
if any(r['source_id']==source_id for r in records) or len(records)>=16:raise ValueError('immutable source exists or capacity exhausted')
|
|
46
49
|
r={'source_id':source_id,'scope':scope,'state':'ACTIVE','repository':binding.repository_identity,
|
|
47
|
-
'revision':revision,'path':path,'content':content,'content_digest':'sha256:'+sha256(content.encode()).hexdigest()
|
|
50
|
+
'revision':revision,'path':path,'content':content,'content_digest':'sha256:'+sha256(content.encode()).hexdigest(),
|
|
51
|
+
'observed_at':datetime.now(UTC).isoformat()}
|
|
48
52
|
r['version']=digest({k:v for k,v in r.items() if k!='state'});self._save([*records,r]);return self.metadata(r)
|
|
49
53
|
|
|
50
54
|
def revoke(self,source_id):
|
|
@@ -60,7 +64,7 @@ class AdvisoryContext:
|
|
|
60
64
|
_write_private(self.path,raw)
|
|
61
65
|
|
|
62
66
|
@staticmethod
|
|
63
|
-
def metadata(r):return {k:v for k,v in r.items() if k not in ('content','scope')}
|
|
67
|
+
def metadata(r):return {k:v for k,v in r.items() if k not in ('content','scope','observed_at')}
|
|
64
68
|
|
|
65
69
|
def available(self,p):
|
|
66
70
|
scope={k:getattr(p,k) for k in ['instance_id','project_id','repository_id']}
|
|
@@ -17,6 +17,13 @@ class AdvisoryNotStarted(PermissionError):pass
|
|
|
17
17
|
class AdvisoryProviderUnavailable(RuntimeError):pass
|
|
18
18
|
|
|
19
19
|
class AdvisoryProvider:
|
|
20
|
+
contract = CONTRACT
|
|
21
|
+
output_schema = OUTPUT_SCHEMA
|
|
22
|
+
instructions = INSTRUCTIONS
|
|
23
|
+
|
|
24
|
+
def validate_output(self, document, admitted):
|
|
25
|
+
return result(document, admitted, admitted['context']['evidence_references'])
|
|
26
|
+
|
|
20
27
|
def __init__(self, runtime, provider_id):
|
|
21
28
|
service=PlanningProviderSecurityService(runtime.database,None,runtime.repository.operators)
|
|
22
29
|
try:self.configuration=CodexCliChatGPTSessionPlanningProviderConfiguration.from_canonical_session(service,provider_id)
|
|
@@ -31,13 +38,13 @@ class AdvisoryProvider:
|
|
|
31
38
|
raise AdvisoryConflict('INVOCATION_UNRESOLVED')
|
|
32
39
|
from .advisory_context import AdvisoryContext
|
|
33
40
|
sources=AdvisoryContext(self.configuration.policy_service.db.path.parent,admitted['request']['instance_id']).selected(type('Scope',(),{k:admitted['request'][k] for k in ['instance_id','project_id','repository_id']})(),admitted['request']['selected_sources'])
|
|
34
|
-
prompt={'contract_version':
|
|
41
|
+
prompt={'contract_version':self.contract,'request':admitted['request'],
|
|
35
42
|
'request_digest':admitted['request_digest'],'session_id':admitted['session_id'],
|
|
36
43
|
'invocation_id':admitted['invocation_id'],'context':admitted['context'],'selected_source_text':[{'reference':'advisory-source:'+r['source_id']+':'+r['version'],'content':r['content']} for r in sources],'prior_turns':history}
|
|
37
44
|
# Conservative byte bound; observed usage remains separately enforced.
|
|
38
45
|
if len(__import__('json').dumps(prompt).encode())>min(policy.input_token_bound,policy.context_token_bound):
|
|
39
46
|
raise ValueError('advisory context exceeds configured bound')
|
|
40
|
-
binding={'request':prompt,'schema':
|
|
47
|
+
binding={'request':prompt,'schema':self.output_schema,'instructions':self.instructions,'policy_digest':_policy_digest(policy)}
|
|
41
48
|
if len(__import__('json').dumps(binding).encode())>min(policy.input_token_bound,policy.context_token_bound):raise ValueError('advisory envelope exceeds configured bound')
|
|
42
49
|
return policy,prompt,digest(binding)
|
|
43
50
|
|
|
@@ -60,7 +67,7 @@ class AdvisoryProvider:
|
|
|
60
67
|
authorize()
|
|
61
68
|
service._commit_generation_transport(permit,policy,_policy_digest(policy),generation_digest)
|
|
62
69
|
except PermissionError as e:raise AdvisoryNotStarted('generation not started') from e
|
|
63
|
-
run=self.transport._run_read_only(policy,None,
|
|
70
|
+
run=self.transport._run_read_only(policy,None,self.output_schema,self.instructions,prompt_document=prompt)
|
|
64
71
|
d=run.diagnostic.document()
|
|
65
72
|
confirmed=run.diagnostic.classification is CodexCliInvocationClassification.COMPLETED_VALID
|
|
66
73
|
known_not_started=run.diagnostic.classification in {CodexCliInvocationClassification.NOT_STARTED,CodexCliInvocationClassification.REJECTED_BEFORE_GENERATION}
|
|
@@ -70,7 +77,7 @@ class AdvisoryProvider:
|
|
|
70
77
|
if confirmed:
|
|
71
78
|
outcome['result_digest']=digest(run.document)
|
|
72
79
|
try:
|
|
73
|
-
output=
|
|
80
|
+
output=self.validate_output(run.document,admitted)
|
|
74
81
|
usage_error=_usage_policy_error(run.usage,policy)
|
|
75
82
|
if usage_error:outcome['error_code']='ADVICE_USAGE_NOT_REPORTED' if run.usage is None else 'ADVICE_USAGE_BOUND_EXCEEDED'
|
|
76
83
|
else:outcome['output']=output
|