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.
Files changed (473) hide show
  1. {forge_autonomy-2.8.1/forge_autonomy.egg-info → forge_autonomy-2.12.2}/PKG-INFO +1 -1
  2. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/__main__.py +11 -0
  3. forge_autonomy-2.12.2/forge/advisory_candidate_cli.py +30 -0
  4. forge_autonomy-2.12.2/forge/advisory_candidate_contract.py +78 -0
  5. forge_autonomy-2.12.2/forge/advisory_candidate_grant.py +107 -0
  6. forge_autonomy-2.12.2/forge/advisory_candidate_service.py +197 -0
  7. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/advisory_context.py +9 -5
  8. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/advisory_provider.py +11 -4
  9. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/advisory_service.py +31 -17
  10. forge_autonomy-2.12.2/forge/api/advisory-candidate-v1.json +939 -0
  11. forge_autonomy-2.12.2/forge/api/approved-workset-release-v1.json +2421 -0
  12. forge_autonomy-2.12.2/forge/api/candidate-decisions-v1.json +1585 -0
  13. forge_autonomy-2.12.2/forge/api/mission-concepts-v1.json +5102 -0
  14. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/server-openapi-v1.json +832 -0
  15. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/server-postman-v1.json +607 -0
  16. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/approved_worklist.py +48 -10
  17. forge_autonomy-2.12.2/forge/candidate_decision_cli.py +36 -0
  18. forge_autonomy-2.12.2/forge/candidate_decision_contract.py +81 -0
  19. forge_autonomy-2.12.2/forge/candidate_decision_grant.py +183 -0
  20. forge_autonomy-2.12.2/forge/candidate_decision_service.py +248 -0
  21. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/governance_authority.py +5 -1
  22. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/governed_candidate_intake.py +29 -6
  23. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/installation_pairing.py +30 -6
  24. forge_autonomy-2.12.2/forge/installation_peer_detach.py +157 -0
  25. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/lifecycle/store.py +208 -5
  26. forge_autonomy-2.12.2/forge/mission_concept_approval.py +224 -0
  27. forge_autonomy-2.12.2/forge/mission_concept_continuity.py +122 -0
  28. forge_autonomy-2.12.2/forge/mission_concept_contract.py +123 -0
  29. forge_autonomy-2.12.2/forge/mission_concept_planning.py +194 -0
  30. forge_autonomy-2.12.2/forge/mission_concept_provider.py +49 -0
  31. forge_autonomy-2.12.2/forge/mission_concept_readiness.py +58 -0
  32. forge_autonomy-2.12.2/forge/mission_concept_registration.py +73 -0
  33. forge_autonomy-2.12.2/forge/mission_concept_resolver.py +120 -0
  34. forge_autonomy-2.12.2/forge/mission_concept_service.py +455 -0
  35. forge_autonomy-2.12.2/forge/mission_concept_setup.py +133 -0
  36. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/architecture_mission.py +1 -1
  37. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/database.py +40 -2
  38. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/dynamic_mission.py +14 -8
  39. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/operational_reset.py +4 -0
  40. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/server_runtime.py +75 -4
  41. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/worklist_activation.py +27 -1
  42. forge_autonomy-2.12.2/forge/workset_release_cli.py +69 -0
  43. forge_autonomy-2.12.2/forge/workset_release_grant.py +212 -0
  44. forge_autonomy-2.12.2/forge/workset_release_journal.py +53 -0
  45. forge_autonomy-2.12.2/forge/workset_release_package.py +67 -0
  46. forge_autonomy-2.12.2/forge/workset_release_service.py +231 -0
  47. forge_autonomy-2.12.2/forge/workset_release_subjects.py +82 -0
  48. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2/forge_autonomy.egg-info}/PKG-INFO +1 -1
  49. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge_autonomy.egg-info/SOURCES.txt +52 -0
  50. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge_autonomy.egg-info/entry_points.txt +7 -0
  51. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/product-version.json +1 -1
  52. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/pyproject.toml +7 -0
  53. forge_autonomy-2.12.2/tests/test_advisory_candidate_http.py +309 -0
  54. forge_autonomy-2.12.2/tests/test_candidate_decision_contract.py +12 -0
  55. forge_autonomy-2.12.2/tests/test_candidate_decisions_http.py +296 -0
  56. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_existing_release_reconciliation.py +4 -0
  57. forge_autonomy-2.12.2/tests/test_installation_peer_detach.py +323 -0
  58. forge_autonomy-2.12.2/tests/test_installed_forge_preservation_2739_281.py +76 -0
  59. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_installed_forge_update.py +3 -3
  60. forge_autonomy-2.12.2/tests/test_mission_concept_continuity.py +266 -0
  61. forge_autonomy-2.12.2/tests/test_mission_concept_contract.py +77 -0
  62. forge_autonomy-2.12.2/tests/test_mission_concept_dependencies.py +160 -0
  63. forge_autonomy-2.12.2/tests/test_mission_concept_output_boundaries.py +56 -0
  64. forge_autonomy-2.12.2/tests/test_mission_concept_planning.py +108 -0
  65. forge_autonomy-2.12.2/tests/test_mission_concept_provider.py +132 -0
  66. forge_autonomy-2.12.2/tests/test_mission_concept_readiness.py +95 -0
  67. forge_autonomy-2.12.2/tests/test_mission_concept_ready_http.py +234 -0
  68. forge_autonomy-2.12.2/tests/test_mission_concept_recovery.py +79 -0
  69. forge_autonomy-2.12.2/tests/test_mission_concept_resolver.py +110 -0
  70. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_production_release_workflow.py +18 -3
  71. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_server_route_qualification.py +37 -1
  72. forge_autonomy-2.12.2/tests/test_workset_release_chain.py +41 -0
  73. forge_autonomy-2.12.2/tests/test_workset_release_concurrency.py +18 -0
  74. forge_autonomy-2.12.2/tests/test_workset_release_denials.py +112 -0
  75. forge_autonomy-2.12.2/tests/test_workset_release_http.py +75 -0
  76. forge_autonomy-2.12.2/tests/test_workset_release_integrity.py +101 -0
  77. forge_autonomy-2.12.2/tests/test_workset_release_recovery.py +26 -0
  78. forge_autonomy-2.12.2/tests/test_workset_release_selection.py +114 -0
  79. forge_autonomy-2.12.2/tests/test_workset_release_subjects.py +35 -0
  80. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/MANIFEST.in +0 -0
  81. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/README.md +0 -0
  82. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/__init__.py +0 -0
  83. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/_version.py +0 -0
  84. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/action_derivation_canary_closure.py +0 -0
  85. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/action_derivation_evidence.py +0 -0
  86. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/action_derivation_qualification.py +0 -0
  87. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/action_derivation_reattempt.py +0 -0
  88. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/action_frontier.py +0 -0
  89. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/advisory_cli.py +0 -0
  90. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/advisory_contract.py +0 -0
  91. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/advisory_grant.py +0 -0
  92. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/ai_architect/__init__.py +0 -0
  93. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/ai_architect/provider_registry.py +0 -0
  94. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/advisory-conversation-v1.json +0 -0
  95. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/installed-health-component-registry-1.0.json +0 -0
  96. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/operations-read-openapi-v1.json +0 -0
  97. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/operations-read-postman-v1.json +0 -0
  98. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/parallel-action-peer-graph-v1.json +0 -0
  99. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/workspace-review-inbox-v1.json +0 -0
  100. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/workspace-status-read-v1.json +0 -0
  101. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/workspace-worklist-control-v1.json +0 -0
  102. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/api/workspace-worklist-v1.json +0 -0
  103. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/architecture/__init__.py +0 -0
  104. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/architecture/advisor.py +0 -0
  105. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/architecture/presentation.py +0 -0
  106. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/architecture/workspace.py +0 -0
  107. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/autonomous_orchestrator.py +0 -0
  108. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/business/__init__.py +0 -0
  109. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/business/advisor.py +0 -0
  110. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/business/ingress.py +0 -0
  111. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/business/presentation.py +0 -0
  112. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/business/workspace.py +0 -0
  113. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/canary.py +0 -0
  114. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/capabilities/__init__.py +0 -0
  115. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/capabilities/delegation.py +0 -0
  116. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/completion/__init__.py +0 -0
  117. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/completion/assessor.py +0 -0
  118. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/completion/effect_report_observer.py +0 -0
  119. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/completion/host_control_observer.py +0 -0
  120. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/completion/mission.py +0 -0
  121. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/completion/repository_observer.py +0 -0
  122. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/constitutional/__init__.py +0 -0
  123. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/constitutional/assessor.py +0 -0
  124. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/core/__init__.py +0 -0
  125. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/core/json_store.py +0 -0
  126. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/decision_evidence/__init__.py +0 -0
  127. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/decision_evidence/repository.py +0 -0
  128. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/dispatcher/__init__.py +0 -0
  129. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/dispatcher/dispatcher.py +0 -0
  130. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/ep_simulator.py +0 -0
  131. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/execution/__init__.py +0 -0
  132. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/execution/loop.py +0 -0
  133. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/execution/presentation.py +0 -0
  134. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/execution_host_configuration.py +0 -0
  135. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/foundation/__init__.py +0 -0
  136. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/foundation/loader.py +0 -0
  137. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/generation_two.py +0 -0
  138. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/generation_two_execution.py +0 -0
  139. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/governance/__init__.py +0 -0
  140. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/governance/execution_policy.py +0 -0
  141. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/governance/profiles.py +0 -0
  142. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/governed_continuation.py +0 -0
  143. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/installed_health.py +0 -0
  144. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/installed_lifecycle.py +0 -0
  145. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/intake.py +0 -0
  146. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/integration/__init__.py +0 -0
  147. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/integration/coordinator.py +0 -0
  148. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/integration/repository.py +0 -0
  149. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/knowledge/__init__.py +0 -0
  150. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/knowledge/consumer.py +0 -0
  151. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/knowledge/registry.py +0 -0
  152. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/lifecycle/__init__.py +0 -0
  153. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/mission_amendment.py +0 -0
  154. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/mission_cli.py +0 -0
  155. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/mission_lifecycle.py +0 -0
  156. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/mission_lifecycle_cli.py +0 -0
  157. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/mission_no_dispatch.py +0 -0
  158. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/mission_scheduler.py +0 -0
  159. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/__init__.py +0 -0
  160. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/action.py +0 -0
  161. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/action_derivation.py +0 -0
  162. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/agent_policy.py +0 -0
  163. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/ai_architect_provider.py +0 -0
  164. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/ai_architect_session.py +0 -0
  165. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/ai_provider_registry.py +0 -0
  166. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/architecture_reasoning.py +0 -0
  167. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/architecture_review.py +0 -0
  168. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/codex_runtime_prompt.py +0 -0
  169. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/constitutional_validation.py +0 -0
  170. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/criterion_assessment.py +0 -0
  171. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/criterion_observation.py +0 -0
  172. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/decision_evidence.py +0 -0
  173. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/execution_host.py +0 -0
  174. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/foundation.py +0 -0
  175. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/historical_intent.py +0 -0
  176. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/integration.py +0 -0
  177. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/intent.py +0 -0
  178. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/intent_authoring.py +0 -0
  179. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/mission.py +0 -0
  180. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/mission_candidate.py +0 -0
  181. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/mission_completion.py +0 -0
  182. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/mission_effect.py +0 -0
  183. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/mission_planner.py +0 -0
  184. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/mission_recommendation.py +0 -0
  185. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/phase_completion.py +0 -0
  186. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/planning.py +0 -0
  187. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/producer.py +0 -0
  188. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/prompt_artifact.py +0 -0
  189. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/proposal.py +0 -0
  190. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/runtime_prompt.py +0 -0
  191. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/models/solution_template.py +0 -0
  192. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/operations_read_api.py +0 -0
  193. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/operator_identity.py +0 -0
  194. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/parallel_action_contract.py +0 -0
  195. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planner/__init__.py +0 -0
  196. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planner/action_derivation.py +0 -0
  197. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planner/codex_cli_session.py +0 -0
  198. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planner/durable_derivation.py +0 -0
  199. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planner/engine.py +0 -0
  200. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planner/openai_responses.py +0 -0
  201. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planner/provider_adapter.py +0 -0
  202. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planning/__init__.py +0 -0
  203. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planning/loader.py +0 -0
  204. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/planning/registry.py +0 -0
  205. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/portfolio_intelligence.py +0 -0
  206. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/preserved_lifecycle.py +0 -0
  207. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/programme_authorization.py +0 -0
  208. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/prompts/__init__.py +0 -0
  209. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/prompts/codex_cli.py +0 -0
  210. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/prompts/generator.py +0 -0
  211. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/proposals/__init__.py +0 -0
  212. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/proposals/generator.py +0 -0
  213. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/provider_context.py +0 -0
  214. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/provider_security.py +0 -0
  215. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/__init__.py +0 -0
  216. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/bootstrap_sequence.py +0 -0
  217. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/criterion_completion.py +0 -0
  218. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/effect_fixture_conformance.py +0 -0
  219. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/effect_simulator_fixture.py +0 -0
  220. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/architecture_design_only-evidence_only.json +0 -0
  221. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/architecture_design_only-git.json +0 -0
  222. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/bounded_repository_change-git.json +0 -0
  223. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/documentation_only-git.json +0 -0
  224. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/effect-report-envelope-v1.schema.json +0 -0
  225. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/effect-request-v1.schema.json +0 -0
  226. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/effect-result-v1.1.schema.json +0 -0
  227. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/effect-result-v1.schema.json +0 -0
  228. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/effect-validation-profile-v1.schema.json +0 -0
  229. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/errors.json +0 -0
  230. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/manifest.json +0 -0
  231. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/read_only_assessment-evidence_only.json +0 -0
  232. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/terminal-evidence-v1.5.schema.json +0 -0
  233. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/fixtures/fme-producer-v1/terminal-evidence-v1.6.schema.json +0 -0
  234. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/generation_one.py +0 -0
  235. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/installed_smoke.py +0 -0
  236. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/producer_fixture_conformance.py +0 -0
  237. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/qualification/server_runtime.py +0 -0
  238. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/recommendations/__init__.py +0 -0
  239. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/recommendations/engine.py +0 -0
  240. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/repository_truth.py +0 -0
  241. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/review/__init__.py +0 -0
  242. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/review/engine.py +0 -0
  243. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/root_identity.py +0 -0
  244. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/__init__.py +0 -0
  245. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/action_authority.py +0 -0
  246. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/action_intents.py +0 -0
  247. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/bootstrap.py +0 -0
  248. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/data_root.py +0 -0
  249. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/evidence.py +0 -0
  250. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/execution_context.py +0 -0
  251. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/health.py +0 -0
  252. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/mission_controller.py +0 -0
  253. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/runner.py +0 -0
  254. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/runtime/service.py +0 -0
  255. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/scheduler/__init__.py +0 -0
  256. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/scheduler/adapter.py +0 -0
  257. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/scheduler/ep_effect_v15.py +0 -0
  258. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/scheduler/ep_http_adapter.py +0 -0
  259. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/scheduler/ep_v12.py +0 -0
  260. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/scheduler/scheduler.py +0 -0
  261. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/capability-registry-1.0.schema.json +0 -0
  262. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/capability.schema.json +0 -0
  263. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/common.schema.json +0 -0
  264. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/decision-evidence-1.0.schema.json +0 -0
  265. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-goal-0.5.schema.json +0 -0
  266. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-increment-proposal-0.5.schema.json +0 -0
  267. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-mode.schema.json +0 -0
  268. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-plan-0.5.schema.json +0 -0
  269. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-platform-peer-configuration-1.0.schema.json +0 -0
  270. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-platform-peer-configuration-1.1.schema.json +0 -0
  271. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-prompt-artifact-0.7.schema.json +0 -0
  272. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/engineering-proposal-0.6.schema.json +0 -0
  273. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/ep-producer-readback-v1.2.schema.json +0 -0
  274. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/ep-producer-readback-v1.3.schema.json +0 -0
  275. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/evidence-reference-0.5.schema.json +0 -0
  276. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/foundation-document.schema.json +0 -0
  277. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/governance-profile.schema.json +0 -0
  278. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/knowledge-source-0.4.schema.json +0 -0
  279. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/knowledge-source.schema.json +0 -0
  280. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/mission-candidate-1.0.schema.json +0 -0
  281. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/mission-candidate-1.1.schema.json +0 -0
  282. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/phase-completion-1.0.schema.json +0 -0
  283. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/planning-document-0.5.schema.json +0 -0
  284. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/repository-catalog.schema.json +0 -0
  285. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/repository.schema.json +0 -0
  286. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/solution-template-1.0.schema.json +0 -0
  287. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/schemas/workspace.schema.json +0 -0
  288. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/secure_store.py +0 -0
  289. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/serial_correlation_projection.py +0 -0
  290. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/solutions/__init__.py +0 -0
  291. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/solutions/catalogue.py +0 -0
  292. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/solutions/generator.py +0 -0
  293. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/state/__init__.py +0 -0
  294. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/state/mission_state.py +0 -0
  295. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/worklist_conditions.py +0 -0
  296. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/worklist_control.py +0 -0
  297. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/workspace/__init__.py +0 -0
  298. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/workspace_read_grant.py +0 -0
  299. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/workspace_review_grant.py +0 -0
  300. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/workspace_review_inbox.py +0 -0
  301. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/workspace_worklist_control_grant.py +0 -0
  302. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge/workspace_worklist_grant.py +0 -0
  303. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge_autonomy.egg-info/dependency_links.txt +0 -0
  304. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge_autonomy.egg-info/requires.txt +0 -0
  305. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/forge_autonomy.egg-info/top_level.txt +0 -0
  306. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/capability-registry-1.0.schema.json +0 -0
  307. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/capability.schema.json +0 -0
  308. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/common.schema.json +0 -0
  309. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/decision-evidence-1.0.schema.json +0 -0
  310. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/engineering-goal-0.5.schema.json +0 -0
  311. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/engineering-increment-proposal-0.5.schema.json +0 -0
  312. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/engineering-mode.schema.json +0 -0
  313. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/engineering-plan-0.5.schema.json +0 -0
  314. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/engineering-prompt-artifact-0.7.schema.json +0 -0
  315. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/engineering-proposal-0.6.schema.json +0 -0
  316. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/evidence-reference-0.5.schema.json +0 -0
  317. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/foundation-document.schema.json +0 -0
  318. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/governance-profile.schema.json +0 -0
  319. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/knowledge-source-0.4.schema.json +0 -0
  320. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/knowledge-source.schema.json +0 -0
  321. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/mission-candidate-1.0.schema.json +0 -0
  322. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/mission-candidate-1.1.schema.json +0 -0
  323. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/phase-completion-1.0.schema.json +0 -0
  324. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/planning-document-0.5.schema.json +0 -0
  325. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/repository-catalog.schema.json +0 -0
  326. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/repository.schema.json +0 -0
  327. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/solution-template-1.0.schema.json +0 -0
  328. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/schemas/workspace.schema.json +0 -0
  329. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/setup.cfg +0 -0
  330. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_action_authority.py +0 -0
  331. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_action_derivation.py +0 -0
  332. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_action_derivation_canary_closure.py +0 -0
  333. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_action_derivation_evidence.py +0 -0
  334. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_action_derivation_qualification.py +0 -0
  335. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_action_frontier.py +0 -0
  336. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_action_intents.py +0 -0
  337. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_adaptive_action_sizing_design.py +0 -0
  338. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_advisory_http.py +0 -0
  339. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_agent_policy.py +0 -0
  340. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_aggregate_health_roadmap.py +0 -0
  341. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ai_architect_provider.py +0 -0
  342. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ai_architect_session.py +0 -0
  343. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ai_development_projection.py +0 -0
  344. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ai_provider_registry.py +0 -0
  345. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_approved_worklist.py +0 -0
  346. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_architecture_reasoning.py +0 -0
  347. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_architecture_review_engine.py +0 -0
  348. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_architecture_workspace.py +0 -0
  349. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_autonomous_orchestrator.py +0 -0
  350. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_bootstrap_adapter.py +0 -0
  351. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_bootstrap_mission_canary.py +0 -0
  352. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_bootstrap_mission_runner.py +0 -0
  353. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_bootstrap_mission_scheduler.py +0 -0
  354. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_bootstrap_sequence_qualification.py +0 -0
  355. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_business_governance_ingress.py +0 -0
  356. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_business_workspace.py +0 -0
  357. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_canonical_data_root.py +0 -0
  358. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_capability_delegation.py +0 -0
  359. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_changed_product_coverage.py +0 -0
  360. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_cli_transport.py +0 -0
  361. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_codex_cli_runtime_prompt.py +0 -0
  362. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_codex_cli_session.py +0 -0
  363. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_constitutional_validation.py +0 -0
  364. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_criterion_assessment_contract.py +0 -0
  365. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_criterion_completion_release_qualification.py +0 -0
  366. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_criterion_ep_http_boundary.py +0 -0
  367. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_criterion_planning_context.py +0 -0
  368. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_decision_evidence.py +0 -0
  369. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_default_project_quality_policy.py +0 -0
  370. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_distribution_identity.py +0 -0
  371. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_durable_action_derivation.py +0 -0
  372. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_dynamic_mission_capability.py +0 -0
  373. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_effect_completion.py +0 -0
  374. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_effect_fixture_conformance.py +0 -0
  375. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_engineering_action_architecture.py +0 -0
  376. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_engineering_intent_authoring.py +0 -0
  377. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_engineering_intent_lifecycle.py +0 -0
  378. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_engineering_mission.py +0 -0
  379. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_engineering_planning.py +0 -0
  380. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_engineering_prompt_artifacts.py +0 -0
  381. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_engineering_proposals.py +0 -0
  382. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ep_effect_v15.py +0 -0
  383. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ep_http_adapter.py +0 -0
  384. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ep_simulator.py +0 -0
  385. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_ep_simulator_rolling_planning.py +0 -0
  386. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_execution_context.py +0 -0
  387. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_execution_host_configuration.py +0 -0
  388. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_execution_host_contract.py +0 -0
  389. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_execution_loop.py +0 -0
  390. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_forge_console_admin_parity_contract.py +0 -0
  391. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_forge_console_configuration_contract.py +0 -0
  392. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_forge_console_hosting_contract.py +0 -0
  393. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_forge_operations_console_roadmap.py +0 -0
  394. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_forge_outer_loop_ci_roadmap.py +0 -0
  395. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_forge_v1_bootstrap_scheduler_contract.py +0 -0
  396. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_forge_v1_implementation_dag.py +0 -0
  397. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_foundation_loader.py +0 -0
  398. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_foundation_models.py +0 -0
  399. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_four_lane_planning.py +0 -0
  400. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_generation_one_bootstrap_qualification.py +0 -0
  401. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_generation_two_execution.py +0 -0
  402. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_generation_two_mission_intake.py +0 -0
  403. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_governance_authority.py +0 -0
  404. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_governed_candidate_intake.py +0 -0
  405. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_governed_coverage.py +0 -0
  406. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_historical_engineering_intent.py +0 -0
  407. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_host_control_completion.py +0 -0
  408. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_inner_loop_ci_contract.py +0 -0
  409. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_installation_pairing.py +0 -0
  410. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_installed_dynamic_mission_runtime.py +0 -0
  411. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_installed_http_successor_qualification.py +0 -0
  412. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_installed_lifecycle.py +0 -0
  413. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_integration_coordination.py +0 -0
  414. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_knowledge_consumption.py +0 -0
  415. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_live_project_roadmap_contract.py +0 -0
  416. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_completion_evaluator.py +0 -0
  417. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_controller.py +0 -0
  418. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_dispatcher.py +0 -0
  419. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_effect.py +0 -0
  420. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_lifecycle_cli.py +0 -0
  421. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_planner.py +0 -0
  422. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_recommendation_engine.py +0 -0
  423. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_recommendation_lifecycle.py +0 -0
  424. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_runtime_projection.py +0 -0
  425. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_runtime_scheduler.py +0 -0
  426. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_state_store.py +0 -0
  427. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_mission_status.py +0 -0
  428. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_openai_action_derivation.py +0 -0
  429. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_operational_reset.py +0 -0
  430. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_operations_health.py +0 -0
  431. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_operations_read_api.py +0 -0
  432. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_operator_identity.py +0 -0
  433. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_parallel_action_contract.py +0 -0
  434. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_phase_completion.py +0 -0
  435. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_platform_contract_documents.py +0 -0
  436. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_portfolio_intelligence_operation.py +0 -0
  437. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_preserved_lifecycle.py +0 -0
  438. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_producer_contract.py +0 -0
  439. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_product_version_operations.py +0 -0
  440. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_productization_reconciliation.py +0 -0
  441. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_programme_authorization.py +0 -0
  442. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_provider_context.py +0 -0
  443. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_provider_security.py +0 -0
  444. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_pypi_distribution_readback.py +0 -0
  445. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_recovered_partial_completion.py +0 -0
  446. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_release_operation.py +0 -0
  447. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_repository_truth.py +0 -0
  448. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_role_aware_conversations_contract.py +0 -0
  449. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_root_identity.py +0 -0
  450. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_bootstrap.py +0 -0
  451. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_database.py +0 -0
  452. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_evidence.py +0 -0
  453. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_health.py +0 -0
  454. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_instance_persistence.py +0 -0
  455. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_prompt_generation.py +0 -0
  456. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_service.py +0 -0
  457. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_runtime_status_projection.py +0 -0
  458. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_scheduler_dependency_boundary.py +0 -0
  459. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_schemas.py +0 -0
  460. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_secure_store.py +0 -0
  461. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_serial_correlation_projection.py +0 -0
  462. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_server_api_contract.py +0 -0
  463. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_server_project_dag_capability.py +0 -0
  464. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_server_runtime.py +0 -0
  465. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_solution_templates.py +0 -0
  466. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_substantive_mission_completion.py +0 -0
  467. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_tde_observe_report.py +0 -0
  468. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_terminal_continuation_replay.py +0 -0
  469. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_worklist_activation.py +0 -0
  470. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_worklist_conditions.py +0 -0
  471. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_worklist_control.py +0 -0
  472. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_workspace_read_grant.py +0 -0
  473. {forge_autonomy-2.8.1 → forge_autonomy-2.12.2}/tests/test_workspace_review_inbox.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forge-autonomy
3
- Version: 2.8.1
3
+ Version: 2.12.2
4
4
  Summary: Forge mission planning, producer contracts, and evidence interpretation runtime.
5
5
  Author: Forge maintainers
6
6
  License-Expression: Apache-2.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)!=fields or r['state'] not in ('ACTIVE','REVOKED'):raise RuntimeError('context shape invalid')
28
- try:identifier(r['source_id']);text(r['content'],2048)
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':CONTRACT,'request':admitted['request'],
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':OUTPUT_SCHEMA,'instructions':INSTRUCTIONS,'policy_digest':_policy_digest(policy)}
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,OUTPUT_SCHEMA,INSTRUCTIONS,prompt_document=prompt)
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=result(run.document,admitted,admitted['context']['evidence_references'])
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