forge-autonomy 2.7.24__tar.gz → 2.7.26__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/PKG-INFO +11 -1
  2. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/README.md +10 -0
  3. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/__main__.py +58 -0
  4. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/architecture/workspace.py +2 -1
  5. forge_autonomy-2.7.26/forge/completion/host_control_observer.py +164 -0
  6. forge_autonomy-2.7.26/forge/completion/mission.py +229 -0
  7. forge_autonomy-2.7.26/forge/completion/repository_observer.py +132 -0
  8. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/execution/loop.py +159 -10
  9. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/governance_authority.py +55 -1
  10. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/intake.py +28 -4
  11. forge_autonomy-2.7.26/forge/mission_cli.py +353 -0
  12. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/action_derivation.py +28 -6
  13. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/architecture_mission.py +29 -1
  14. forge_autonomy-2.7.26/forge/models/criterion_assessment.py +267 -0
  15. forge_autonomy-2.7.26/forge/models/criterion_observation.py +73 -0
  16. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/execution_host.py +8 -0
  17. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/mission_completion.py +35 -2
  18. forge_autonomy-2.7.26/forge/models/mission_planner.py +453 -0
  19. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planner/codex_cli_session.py +88 -11
  20. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planner/durable_derivation.py +5 -2
  21. forge_autonomy-2.7.26/forge/qualification/bootstrap_sequence.py +110 -0
  22. forge_autonomy-2.7.26/forge/qualification/criterion_completion.py +373 -0
  23. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/database.py +19 -1
  24. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/dynamic_mission.py +107 -33
  25. forge_autonomy-2.7.26/forge/runtime/mission_controller.py +242 -0
  26. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/operational_reset.py +6 -2
  27. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/runner.py +108 -8
  28. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/scheduler/ep_http_adapter.py +91 -12
  29. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/scheduler/ep_v12.py +19 -7
  30. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/state/mission_state.py +47 -50
  31. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge_autonomy.egg-info/PKG-INFO +11 -1
  32. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge_autonomy.egg-info/SOURCES.txt +17 -1
  33. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/product-version.json +1 -1
  34. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_bootstrap_mission_runner.py +17 -0
  35. forge_autonomy-2.7.26/tests/test_bootstrap_sequence_qualification.py +147 -0
  36. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_codex_cli_session.py +43 -3
  37. forge_autonomy-2.7.26/tests/test_criterion_assessment_contract.py +247 -0
  38. forge_autonomy-2.7.26/tests/test_criterion_ep_http_boundary.py +148 -0
  39. forge_autonomy-2.7.26/tests/test_criterion_planning_context.py +226 -0
  40. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_dynamic_mission_capability.py +72 -31
  41. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_ep_http_adapter.py +135 -1
  42. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_execution_loop.py +18 -23
  43. forge_autonomy-2.7.26/tests/test_host_control_completion.py +442 -0
  44. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_installed_dynamic_mission_runtime.py +120 -8
  45. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_installed_forge_update.py +440 -9
  46. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_completion_evaluator.py +30 -10
  47. forge_autonomy-2.7.26/tests/test_mission_controller.py +218 -0
  48. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_state_store.py +20 -0
  49. forge_autonomy-2.7.26/tests/test_mission_status.py +35 -0
  50. forge_autonomy-2.7.26/tests/test_recovered_partial_completion.py +52 -0
  51. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_database.py +34 -0
  52. forge_autonomy-2.7.26/tests/test_substantive_mission_completion.py +339 -0
  53. forge_autonomy-2.7.26/tests/test_terminal_continuation_replay.py +358 -0
  54. forge_autonomy-2.7.24/forge/completion/mission.py +0 -120
  55. forge_autonomy-2.7.24/forge/models/mission_planner.py +0 -241
  56. forge_autonomy-2.7.24/forge/qualification/bootstrap_sequence.py +0 -287
  57. forge_autonomy-2.7.24/tests/test_bootstrap_sequence_qualification.py +0 -114
  58. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/MANIFEST.in +0 -0
  59. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/__init__.py +0 -0
  60. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/_version.py +0 -0
  61. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/action_derivation_canary_closure.py +0 -0
  62. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/action_derivation_evidence.py +0 -0
  63. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/action_derivation_qualification.py +0 -0
  64. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/action_derivation_reattempt.py +0 -0
  65. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/ai_architect/__init__.py +0 -0
  66. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/ai_architect/provider_registry.py +0 -0
  67. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/architecture/__init__.py +0 -0
  68. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/architecture/advisor.py +0 -0
  69. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/architecture/presentation.py +0 -0
  70. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/autonomous_orchestrator.py +0 -0
  71. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/business/__init__.py +0 -0
  72. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/business/advisor.py +0 -0
  73. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/business/ingress.py +0 -0
  74. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/business/presentation.py +0 -0
  75. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/business/workspace.py +0 -0
  76. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/canary.py +0 -0
  77. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/capabilities/__init__.py +0 -0
  78. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/capabilities/delegation.py +0 -0
  79. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/completion/__init__.py +0 -0
  80. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/completion/assessor.py +0 -0
  81. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/constitutional/__init__.py +0 -0
  82. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/constitutional/assessor.py +0 -0
  83. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/core/__init__.py +0 -0
  84. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/core/json_store.py +0 -0
  85. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/decision_evidence/__init__.py +0 -0
  86. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/decision_evidence/repository.py +0 -0
  87. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/dispatcher/__init__.py +0 -0
  88. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/dispatcher/dispatcher.py +0 -0
  89. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/execution/__init__.py +0 -0
  90. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/execution/presentation.py +0 -0
  91. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/execution_host_configuration.py +0 -0
  92. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/foundation/__init__.py +0 -0
  93. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/foundation/loader.py +0 -0
  94. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/generation_two.py +0 -0
  95. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/generation_two_execution.py +0 -0
  96. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/governance/__init__.py +0 -0
  97. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/governance/execution_policy.py +0 -0
  98. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/governance/profiles.py +0 -0
  99. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/integration/__init__.py +0 -0
  100. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/integration/coordinator.py +0 -0
  101. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/integration/repository.py +0 -0
  102. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/knowledge/__init__.py +0 -0
  103. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/knowledge/consumer.py +0 -0
  104. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/knowledge/registry.py +0 -0
  105. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/lifecycle/__init__.py +0 -0
  106. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/lifecycle/store.py +0 -0
  107. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/mission_amendment.py +0 -0
  108. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/mission_scheduler.py +0 -0
  109. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/__init__.py +0 -0
  110. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/action.py +0 -0
  111. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/agent_policy.py +0 -0
  112. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/ai_architect_provider.py +0 -0
  113. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/ai_architect_session.py +0 -0
  114. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/ai_provider_registry.py +0 -0
  115. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/architecture_reasoning.py +0 -0
  116. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/architecture_review.py +0 -0
  117. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/codex_runtime_prompt.py +0 -0
  118. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/constitutional_validation.py +0 -0
  119. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/decision_evidence.py +0 -0
  120. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/foundation.py +0 -0
  121. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/historical_intent.py +0 -0
  122. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/integration.py +0 -0
  123. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/intent.py +0 -0
  124. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/intent_authoring.py +0 -0
  125. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/mission.py +0 -0
  126. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/mission_candidate.py +0 -0
  127. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/mission_recommendation.py +0 -0
  128. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/phase_completion.py +0 -0
  129. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/planning.py +0 -0
  130. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/producer.py +0 -0
  131. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/prompt_artifact.py +0 -0
  132. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/proposal.py +0 -0
  133. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/runtime_prompt.py +0 -0
  134. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/models/solution_template.py +0 -0
  135. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/operator_identity.py +0 -0
  136. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planner/__init__.py +0 -0
  137. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planner/action_derivation.py +0 -0
  138. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planner/engine.py +0 -0
  139. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planner/openai_responses.py +0 -0
  140. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planner/provider_adapter.py +0 -0
  141. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planning/__init__.py +0 -0
  142. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planning/loader.py +0 -0
  143. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/planning/registry.py +0 -0
  144. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/portfolio_intelligence.py +0 -0
  145. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/programme_authorization.py +0 -0
  146. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/prompts/__init__.py +0 -0
  147. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/prompts/codex_cli.py +0 -0
  148. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/prompts/generator.py +0 -0
  149. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/proposals/__init__.py +0 -0
  150. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/proposals/generator.py +0 -0
  151. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/provider_security.py +0 -0
  152. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/qualification/__init__.py +0 -0
  153. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/qualification/generation_one.py +0 -0
  154. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/qualification/installed_smoke.py +0 -0
  155. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/recommendations/__init__.py +0 -0
  156. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/recommendations/engine.py +0 -0
  157. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/repository_truth.py +0 -0
  158. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/review/__init__.py +0 -0
  159. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/review/engine.py +0 -0
  160. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/__init__.py +0 -0
  161. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/bootstrap.py +0 -0
  162. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/data_root.py +0 -0
  163. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/evidence.py +0 -0
  164. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/execution_context.py +0 -0
  165. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/health.py +0 -0
  166. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/runtime/service.py +0 -0
  167. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/scheduler/__init__.py +0 -0
  168. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/scheduler/adapter.py +0 -0
  169. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/scheduler/scheduler.py +0 -0
  170. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/capability-registry-1.0.schema.json +0 -0
  171. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/capability.schema.json +0 -0
  172. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/common.schema.json +0 -0
  173. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/decision-evidence-1.0.schema.json +0 -0
  174. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-goal-0.5.schema.json +0 -0
  175. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-increment-proposal-0.5.schema.json +0 -0
  176. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-mode.schema.json +0 -0
  177. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-plan-0.5.schema.json +0 -0
  178. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-platform-peer-configuration-1.0.schema.json +0 -0
  179. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-platform-peer-configuration-1.1.schema.json +0 -0
  180. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-prompt-artifact-0.7.schema.json +0 -0
  181. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/engineering-proposal-0.6.schema.json +0 -0
  182. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/evidence-reference-0.5.schema.json +0 -0
  183. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/foundation-document.schema.json +0 -0
  184. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/governance-profile.schema.json +0 -0
  185. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/knowledge-source-0.4.schema.json +0 -0
  186. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/knowledge-source.schema.json +0 -0
  187. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/mission-candidate-1.0.schema.json +0 -0
  188. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/mission-candidate-1.1.schema.json +0 -0
  189. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/phase-completion-1.0.schema.json +0 -0
  190. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/planning-document-0.5.schema.json +0 -0
  191. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/repository-catalog.schema.json +0 -0
  192. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/repository.schema.json +0 -0
  193. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/solution-template-1.0.schema.json +0 -0
  194. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/schemas/workspace.schema.json +0 -0
  195. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/secure_store.py +0 -0
  196. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/solutions/__init__.py +0 -0
  197. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/solutions/catalogue.py +0 -0
  198. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/solutions/generator.py +0 -0
  199. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/state/__init__.py +0 -0
  200. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge/workspace/__init__.py +0 -0
  201. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge_autonomy.egg-info/dependency_links.txt +0 -0
  202. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge_autonomy.egg-info/entry_points.txt +0 -0
  203. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/forge_autonomy.egg-info/top_level.txt +0 -0
  204. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/pyproject.toml +0 -0
  205. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/capability-registry-1.0.schema.json +0 -0
  206. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/capability.schema.json +0 -0
  207. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/common.schema.json +0 -0
  208. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/decision-evidence-1.0.schema.json +0 -0
  209. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/engineering-goal-0.5.schema.json +0 -0
  210. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/engineering-increment-proposal-0.5.schema.json +0 -0
  211. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/engineering-mode.schema.json +0 -0
  212. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/engineering-plan-0.5.schema.json +0 -0
  213. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/engineering-prompt-artifact-0.7.schema.json +0 -0
  214. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/engineering-proposal-0.6.schema.json +0 -0
  215. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/evidence-reference-0.5.schema.json +0 -0
  216. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/foundation-document.schema.json +0 -0
  217. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/governance-profile.schema.json +0 -0
  218. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/knowledge-source-0.4.schema.json +0 -0
  219. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/knowledge-source.schema.json +0 -0
  220. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/mission-candidate-1.0.schema.json +0 -0
  221. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/mission-candidate-1.1.schema.json +0 -0
  222. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/phase-completion-1.0.schema.json +0 -0
  223. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/planning-document-0.5.schema.json +0 -0
  224. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/repository-catalog.schema.json +0 -0
  225. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/repository.schema.json +0 -0
  226. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/solution-template-1.0.schema.json +0 -0
  227. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/schemas/workspace.schema.json +0 -0
  228. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/setup.cfg +0 -0
  229. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_action_derivation.py +0 -0
  230. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_action_derivation_canary_closure.py +0 -0
  231. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_action_derivation_evidence.py +0 -0
  232. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_action_derivation_qualification.py +0 -0
  233. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_adaptive_action_sizing_design.py +0 -0
  234. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_agent_policy.py +0 -0
  235. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_aggregate_health_roadmap.py +0 -0
  236. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_ai_architect_provider.py +0 -0
  237. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_ai_architect_session.py +0 -0
  238. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_ai_development_projection.py +0 -0
  239. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_ai_provider_registry.py +0 -0
  240. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_architecture_reasoning.py +0 -0
  241. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_architecture_review_engine.py +0 -0
  242. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_architecture_workspace.py +0 -0
  243. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_autonomous_orchestrator.py +0 -0
  244. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_bootstrap_adapter.py +0 -0
  245. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_bootstrap_mission_canary.py +0 -0
  246. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_bootstrap_mission_scheduler.py +0 -0
  247. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_business_governance_ingress.py +0 -0
  248. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_business_workspace.py +0 -0
  249. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_canonical_data_root.py +0 -0
  250. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_capability_delegation.py +0 -0
  251. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_codex_cli_runtime_prompt.py +0 -0
  252. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_constitutional_validation.py +0 -0
  253. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_decision_evidence.py +0 -0
  254. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_default_project_quality_policy.py +0 -0
  255. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_distribution_identity.py +0 -0
  256. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_durable_action_derivation.py +0 -0
  257. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_engineering_action_architecture.py +0 -0
  258. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_engineering_intent_authoring.py +0 -0
  259. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_engineering_intent_lifecycle.py +0 -0
  260. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_engineering_mission.py +0 -0
  261. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_engineering_planning.py +0 -0
  262. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_engineering_prompt_artifacts.py +0 -0
  263. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_engineering_proposals.py +0 -0
  264. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_execution_context.py +0 -0
  265. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_execution_host_configuration.py +0 -0
  266. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_execution_host_contract.py +0 -0
  267. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_existing_release_reconciliation.py +0 -0
  268. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_forge_console_admin_parity_contract.py +0 -0
  269. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_forge_console_configuration_contract.py +0 -0
  270. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_forge_console_hosting_contract.py +0 -0
  271. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_forge_operations_console_roadmap.py +0 -0
  272. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_forge_outer_loop_ci_roadmap.py +0 -0
  273. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_forge_v1_bootstrap_scheduler_contract.py +0 -0
  274. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_forge_v1_implementation_dag.py +0 -0
  275. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_foundation_loader.py +0 -0
  276. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_foundation_models.py +0 -0
  277. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_generation_one_bootstrap_qualification.py +0 -0
  278. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_generation_two_execution.py +0 -0
  279. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_generation_two_mission_intake.py +0 -0
  280. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_governance_authority.py +0 -0
  281. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_governed_coverage.py +0 -0
  282. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_historical_engineering_intent.py +0 -0
  283. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_inner_loop_ci_contract.py +0 -0
  284. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_integration_coordination.py +0 -0
  285. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_knowledge_consumption.py +0 -0
  286. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_live_project_roadmap_contract.py +0 -0
  287. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_dispatcher.py +0 -0
  288. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_planner.py +0 -0
  289. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_recommendation_engine.py +0 -0
  290. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_recommendation_lifecycle.py +0 -0
  291. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_runtime_projection.py +0 -0
  292. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_mission_runtime_scheduler.py +0 -0
  293. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_openai_action_derivation.py +0 -0
  294. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_operational_reset.py +0 -0
  295. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_operator_identity.py +0 -0
  296. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_phase_completion.py +0 -0
  297. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_platform_contract_documents.py +0 -0
  298. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_portfolio_intelligence_operation.py +0 -0
  299. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_producer_contract.py +0 -0
  300. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_product_version_operations.py +0 -0
  301. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_production_release_workflow.py +0 -0
  302. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_productization_reconciliation.py +0 -0
  303. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_programme_authorization.py +0 -0
  304. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_provider_security.py +0 -0
  305. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_pypi_distribution_readback.py +0 -0
  306. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_release_operation.py +0 -0
  307. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_repository_truth.py +0 -0
  308. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_role_aware_conversations_contract.py +0 -0
  309. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_bootstrap.py +0 -0
  310. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_evidence.py +0 -0
  311. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_health.py +0 -0
  312. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_instance_persistence.py +0 -0
  313. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_prompt_generation.py +0 -0
  314. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_service.py +0 -0
  315. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_runtime_status_projection.py +0 -0
  316. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_scheduler_dependency_boundary.py +0 -0
  317. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_schemas.py +0 -0
  318. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_secure_store.py +0 -0
  319. {forge_autonomy-2.7.24 → forge_autonomy-2.7.26}/tests/test_solution_templates.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forge-autonomy
3
- Version: 2.7.24
3
+ Version: 2.7.26
4
4
  Summary: Forge mission planning, producer contracts, and evidence interpretation runtime.
5
5
  Author: Forge maintainers
6
6
  License-Expression: Apache-2.0
@@ -134,6 +134,16 @@ and atomically activates a versioned slot. It is not packaged as a Forge
134
134
  Runtime command and does not replace Forge Platform's normal installer/update
135
135
  composition boundary.
136
136
 
137
+ ## Mission 3 acceptance preflight
138
+
139
+ The [18 September 2026 Mission 3 preflight report](docs/operations/MISSION_3_PREFLIGHT_ACCEPTANCE_2026_09_18.md)
140
+ records `AUTONOMY_E2E_ACCEPTANCE = NIET_GEHAALD` and
141
+ `MISSION_EXECUTION_STATE = NIET_GESTART`. Installed artifact and authenticated
142
+ peer checks passed, but the installed criterion-to-evidence composition cannot
143
+ prove the required dynamic successor Action. No production reset or Mission was
144
+ started. The report includes the unchanged C01–C20 and preserves historical
145
+ failures; it does not authorize a repair or replacement attempt.
146
+
137
147
  ## Operational-history reset
138
148
 
139
149
  Forge schema 38 provides a bounded, product-owned maintenance service under
@@ -117,6 +117,16 @@ and atomically activates a versioned slot. It is not packaged as a Forge
117
117
  Runtime command and does not replace Forge Platform's normal installer/update
118
118
  composition boundary.
119
119
 
120
+ ## Mission 3 acceptance preflight
121
+
122
+ The [18 September 2026 Mission 3 preflight report](docs/operations/MISSION_3_PREFLIGHT_ACCEPTANCE_2026_09_18.md)
123
+ records `AUTONOMY_E2E_ACCEPTANCE = NIET_GEHAALD` and
124
+ `MISSION_EXECUTION_STATE = NIET_GESTART`. Installed artifact and authenticated
125
+ peer checks passed, but the installed criterion-to-evidence composition cannot
126
+ prove the required dynamic successor Action. No production reset or Mission was
127
+ started. The report includes the unchanged C01–C20 and preserves historical
128
+ failures; it does not authorize a repair or replacement attempt.
129
+
120
130
  ## Operational-history reset
121
131
 
122
132
  Forge schema 38 provides a bounded, product-owned maintenance service under
@@ -122,6 +122,29 @@ def main(argv: list[str] | None = None) -> int:
122
122
  finish_reset.add_argument("--verification-digest")
123
123
  finish_reset.add_argument("--cancel-before-apply", action="store_true")
124
124
  subparsers.add_parser("status", help="print read-only runtime status as JSON")
125
+ mission = subparsers.add_parser("mission", help="govern and run one selected Mission")
126
+ mission_commands = mission.add_subparsers(dest="mission_command", required=True)
127
+ for name, description in (
128
+ ("inspect", "validate Mission input and evidence fit without allocation"),
129
+ ("approve-business", "record canonical Business approval"),
130
+ ("approve-architecture", "record canonical Architecture approval"),
131
+ ("admit", "allocate and admit the canonically approved Mission"),
132
+ ):
133
+ command = mission_commands.add_parser(name, help=description)
134
+ command.add_argument("--input", required=True)
135
+ for name, description in (
136
+ ("run", "start once and control the selected Mission to a terminal or declared stop"),
137
+ ("reopen", "continue the same durable Mission execution"),
138
+ ("status", "read the exact Mission status and result"),
139
+ ("stop", "request a controlled foreground stop"),
140
+ ):
141
+ command = mission_commands.add_parser(name, help=description)
142
+ command.add_argument("--mission-id", required=True)
143
+ if name == "run":
144
+ command.add_argument("--repository-truth", required=True)
145
+ if name in {"run", "reopen"}:
146
+ command.add_argument("--poll-seconds", type=float, default=1.0)
147
+ command.add_argument("--maximum-wait-seconds", type=float, default=3600.0)
125
148
  execution_host = subparsers.add_parser("execution-host", help="manage the selected Execution Host peer")
126
149
  execution_host_commands = execution_host.add_subparsers(dest="execution_host_command", required=True)
127
150
  configure = execution_host_commands.add_parser("configure", help="persist one explicit EP peer binding")
@@ -159,6 +182,41 @@ def main(argv: list[str] | None = None) -> int:
159
182
  help="bounded interactive setup timeout in seconds (maximum: 120)",
160
183
  )
161
184
  args = parser.parse_args(argv)
185
+ if args.command == "mission":
186
+ from . import mission_cli
187
+ try:
188
+ command = args.mission_command
189
+ if command == "inspect":
190
+ result = mission_cli.inspect(args.input)
191
+ elif command in {"approve-business", "approve-architecture"}:
192
+ if args.data_root is None:
193
+ raise ValueError("--data-root is required for Mission governance")
194
+ result = mission_cli.approve(args.data_root, args.input,
195
+ "business" if command == "approve-business" else "architecture")
196
+ elif command == "admit":
197
+ if args.data_root is None:
198
+ raise ValueError("--data-root is required for Mission admission")
199
+ result = mission_cli.admit(args.data_root, args.input)
200
+ elif command == "status":
201
+ result = mission_cli.status(args.data_root, args.mission_id)
202
+ elif command == "stop":
203
+ result = mission_cli.stop(args.data_root, args.mission_id)
204
+ else:
205
+ if args.data_root is None:
206
+ raise ValueError("--data-root is required for Mission execution")
207
+ result = mission_cli.run(args.data_root, args.mission_id,
208
+ truth_path=args.repository_truth if command == "run" else None,
209
+ poll_seconds=args.poll_seconds, maximum_wait_seconds=args.maximum_wait_seconds)
210
+ print(json.dumps(result, sort_keys=True))
211
+ if command == "inspect":
212
+ return 0 if result["status"] == "VALID" else 2
213
+ if command == "stop":
214
+ return 0 if result["stop_requested"] else 2
215
+ if command in {"run", "reopen"}:
216
+ return 0 if result["status"] == "COMPLETED" else 2
217
+ return 0
218
+ except (OSError, ValueError, KeyError, sqlite3.Error, RuntimeError, PermissionError) as error:
219
+ return _failure("mission " + args.mission_command, error)
162
220
  if args.command == "server" and args.server_command == "init":
163
221
  from .runtime import RuntimeBootstrap
164
222
  database = RuntimeBootstrap(data_root=args.data_root, forge_version=canonical_version()).open()
@@ -107,7 +107,8 @@ class ArchitectureWorkspace:
107
107
  mission, revision = self._current(mission_id)
108
108
  if mission.status is not ArchitectureMissionStatus.ARCHITECTURE_REVIEW:
109
109
  raise ArchitectureWorkspaceError("only Missions in architecture_review may be refined")
110
- allowed = {"scope", "engineering_constraints", "acceptance_criteria", "technical_assumptions", "dependencies", "required_capabilities", "required_disciplines", "risks"}
110
+ allowed = {"scope", "engineering_constraints", "acceptance_criteria", "technical_assumptions", "dependencies", "required_capabilities", "required_disciplines", "risks", "criterion_assessment_contracts", "maximum_actions",
111
+ "maximum_consecutive_no_progress_actions", "repository_evidence_source"}
111
112
  if not changes or set(changes) - allowed:
112
113
  raise ArchitectureWorkspaceError("architecture refinement may update only engineering governance fields")
113
114
  refined = replace(mission, **changes)
@@ -0,0 +1,164 @@
1
+ """Interpret EP-owned validation records without executing a command."""
2
+ from __future__ import annotations
3
+
4
+ from hashlib import sha256
5
+ import json
6
+ import re
7
+ from typing import Any, Mapping
8
+
9
+ from forge.models.criterion_observation import CriterionObservation, canonical_digest
10
+ from forge.models.mission_completion import mission_criterion_id
11
+
12
+
13
+ _DIGEST = re.compile(r"sha256:[0-9a-f]{64}\Z")
14
+
15
+
16
+ def _canonical(value: object) -> str:
17
+ return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False)
18
+
19
+
20
+ def _valid_digest(value: object) -> bool:
21
+ return isinstance(value, str) and _DIGEST.fullmatch(value) is not None
22
+
23
+
24
+ def approved_observation_selector(requirement) -> str | None:
25
+ """Identify an EP unittest observation from its exact approved binding."""
26
+ try:
27
+ command = json.loads(requirement.command)
28
+ except (TypeError, ValueError):
29
+ return None
30
+ if (not isinstance(command, list) or len(command) != 4
31
+ or command[:3] != ["{python}", "-m", "unittest"]
32
+ or not isinstance(command[3], str)):
33
+ return None
34
+ selector = command[3]
35
+ if (len(selector) > 100 or re.fullmatch(
36
+ r"[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)+", selector) is None):
37
+ return None
38
+ validation_id = "unittest_selector_" + sha256(selector.encode("utf-8")).hexdigest()[:16]
39
+ if (requirement.validation_id != validation_id
40
+ or requirement.control_identity != "python3 -m unittest " + selector
41
+ or requirement.control_category != "repository"
42
+ or requirement.validation_profile_version != "1.0"
43
+ or requirement.profile_reference != "validation-profile-registry:FULL@1.0"):
44
+ return None
45
+ return selector
46
+
47
+
48
+ def _control_matches(requirement, context: Mapping[str, Any], run_id: str,
49
+ validated_revision: str | None) -> tuple[bool, str, dict[str, object] | None]:
50
+ if not requirement.control_definition_digest:
51
+ return False, "APPROVED_CONTROL_DEFINITION_MISSING", None
52
+ version = context.get("contract_version")
53
+ if version not in {"1.0", "1.1"} or context.get("status") != "AVAILABLE":
54
+ return False, "HOST_CONTROL_EVIDENCE_UNAVAILABLE", None
55
+ currentness = context.get("currentness")
56
+ if (validated_revision is None or context.get("candidate_sha") != validated_revision
57
+ or context.get("profile_currentness_conflict") is not False
58
+ or not isinstance(currentness, int) or isinstance(currentness, bool) or currentness < 0
59
+ or not _valid_digest(context.get("profile_digest"))):
60
+ return False, "HOST_CONTROL_CANDIDATE_OR_PROFILE_MISMATCH", None
61
+ if (context.get("validation_profile_version") != requirement.validation_profile_version
62
+ or context.get("profile_reference") != requirement.profile_reference):
63
+ return False, "HOST_CONTROL_PROFILE_VERSION_MISMATCH", None
64
+ controls = context.get("controls")
65
+ required = context.get("required_validation_controls")
66
+ if not isinstance(controls, Mapping) or not isinstance(required, list):
67
+ return False, "HOST_CONTROL_BINDINGS_INVALID", None
68
+ observation_controls = context.get("observation_validation_controls", []) if version == "1.1" else []
69
+ if not isinstance(observation_controls, list):
70
+ return False, "HOST_CONTROL_BINDINGS_INVALID", None
71
+ required_binding = required.count(requirement.validation_id) == 1
72
+ observation_matches = [item for item in observation_controls
73
+ if isinstance(item, Mapping)
74
+ and item.get("validation_id") == requirement.validation_id]
75
+ observation_binding = (version == "1.1" and len(observation_matches) == 1
76
+ and approved_observation_selector(requirement) is not None)
77
+ if required_binding == observation_binding:
78
+ return False, "HOST_CONTROL_BINDING_MISSING_OR_DUPLICATE", None
79
+ command_identity = json.loads(requirement.command)
80
+ definition = {"validation_profile_version": context["validation_profile_version"],
81
+ "profile_reference": context["profile_reference"],
82
+ "validation_id": requirement.validation_id,
83
+ "category": requirement.control_category,
84
+ "control_identity": requirement.control_identity,
85
+ "command_identity": command_identity}
86
+ digest = "sha256:" + sha256(_canonical(definition).encode("utf-8")).hexdigest()
87
+ if digest != requirement.control_definition_digest:
88
+ return False, "HOST_CONTROL_DEFINITION_MISMATCH", None
89
+ control = (controls.get(requirement.validation_id) if required_binding
90
+ else observation_matches[0])
91
+ if not isinstance(control, Mapping):
92
+ return False, "HOST_CONTROL_RECORD_MISSING", None
93
+ if (control.get("validation_id") != requirement.validation_id
94
+ or control.get("category") != requirement.control_category
95
+ or control.get("control_identity") != requirement.control_identity
96
+ or control.get("control_definition_digest") != digest
97
+ or control.get("required_for_profile") is not required_binding
98
+ or control.get("currentness") != currentness):
99
+ return False, "HOST_CONTROL_RECORD_CONFLICT", None
100
+ if (control.get("execution_status") != "EXECUTED" or control.get("result") != "PASS"
101
+ or control.get("evidence_authority") != "command_terminal"
102
+ or control.get("evidence_ref") != "command_terminal"
103
+ or control.get("exit_code") != 0
104
+ or not isinstance(control.get("command_id"), str) or not control["command_id"]):
105
+ return False, "HOST_CONTROL_NOT_EXECUTED_AND_PASSED", None
106
+ detail = control.get("result_detail")
107
+ if not isinstance(detail, Mapping):
108
+ return False, "HOST_CONTROL_RESULT_DETAIL_MISSING", None
109
+ if (detail.get("status") != "AVAILABLE" or detail.get("capture_status") != "AVAILABLE"
110
+ or detail.get("schema") != "deterministic-validation-result-detail-v1"
111
+ or detail.get("run_id") != run_id
112
+ or detail.get("command_id") != control["command_id"]
113
+ or detail.get("validation_id") != requirement.validation_id
114
+ or detail.get("exit_code") != 0
115
+ or not isinstance(detail.get("artifact_id"), str) or not detail["artifact_id"]
116
+ or not _valid_digest(detail.get("digest")) or not _valid_digest(detail.get("output_digest"))):
117
+ return False, "HOST_CONTROL_RESULT_DETAIL_INVALID", None
118
+ count = detail.get("test_count")
119
+ minimum_test_count = (max(1, requirement.minimum_test_count) if observation_binding
120
+ else requirement.minimum_test_count)
121
+ if minimum_test_count and (not isinstance(count, int) or isinstance(count, bool)
122
+ or count < minimum_test_count
123
+ or detail.get("test_count_source") != "unittest_terminal_summary"):
124
+ return False, "HOST_CONTROL_TEST_COUNT_INSUFFICIENT", None
125
+ return True, "APPROVED_HOST_CONTROL_EXECUTED_AND_PASSED", {
126
+ "validation_id": requirement.validation_id, "control_identity": requirement.control_identity,
127
+ "control_definition_digest": digest, "validation_profile_version": requirement.validation_profile_version,
128
+ "profile_reference": requirement.profile_reference, "category": requirement.control_category,
129
+ "candidate_sha": validated_revision, "run_id": run_id, "command_id": control["command_id"],
130
+ "result_detail_digest": detail["digest"], "output_digest": detail["output_digest"],
131
+ "test_count": count, "execution_status": "EXECUTED", "result": "PASS", "exit_code": 0,
132
+ }
133
+
134
+
135
+ class HostControlCriterionObserver:
136
+ def observe(self, mission, reference, evidence) -> tuple[CriterionObservation, ...]:
137
+ context = evidence.validation_controls
138
+ observations = []
139
+ for contract in mission.criterion_assessment_contracts:
140
+ for requirement in contract.requirements:
141
+ if requirement.kind != "host_control":
142
+ continue
143
+ control_context = context if isinstance(context, Mapping) else {}
144
+ delivered = reference.repository_revision
145
+ candidate = reference.candidate_revision
146
+ if (contract.validity_policy == "current_revision" and candidate != delivered
147
+ and control_context.get("candidate_sha") == candidate):
148
+ valid, reason, measured = False, "HOST_CONTROL_DELIVERY_REVISION_NOT_VALIDATED", None
149
+ else:
150
+ expected_revision = (delivered if contract.validity_policy == "current_revision" else
151
+ delivered if control_context.get("candidate_sha") == delivered else candidate)
152
+ valid, reason, measured = _control_matches(
153
+ requirement, control_context, evidence.host_run_id, expected_revision)
154
+ observations.append(CriterionObservation(
155
+ mission.id, canonical_digest(mission.to_dict()), mission_criterion_id(mission.id, contract.criterion),
156
+ contract.digest, requirement.requirement_id, reference.receipt_id, reference.action_id,
157
+ reference.report_id, reference.repository_revision, reference.repository_evidence_digest,
158
+ "host_control", requirement.control_identity, "ep-terminal-artifact",
159
+ reference.repository_evidence_digest if valid else None,
160
+ _canonical(measured) if valid else None, "PASS" if valid else "UNAVAILABLE", reason,
161
+ requirement_digest=requirement.digest, candidate_revision=reference.candidate_revision,
162
+ schema_version="1.1",
163
+ ))
164
+ return tuple(observations)
@@ -0,0 +1,229 @@
1
+ """Deterministic Architecture Mission completion evaluation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from hashlib import sha256
6
+ import json
7
+ from typing import Any, Mapping, Sequence
8
+
9
+ from forge.models.architecture_mission import ArchitectureMission
10
+ from forge.models.mission_completion import (
11
+ CanonicalExecutionEvidenceReference,
12
+ MissionCompletionEvaluation,
13
+ MissionCompletionEvidence,
14
+ MissionCriterionEvaluation,
15
+ MissionCriterionEvaluationStatus,
16
+ RepositoryTruthReference,
17
+ mission_criterion_id,
18
+ )
19
+
20
+
21
+ class MissionCompletionEvaluationError(ValueError):
22
+ """Completion input is stale, malformed, or outside the approved Mission."""
23
+
24
+
25
+ def _digest(value: object) -> str:
26
+ return "sha256:" + sha256(
27
+ json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
28
+ ).hexdigest()
29
+
30
+
31
+ def _truth_reference(document: Mapping[str, Any]) -> RepositoryTruthReference:
32
+ try:
33
+ values = tuple(document[item] for item in ("source_id", "revision", "locator", "content_digest"))
34
+ if any(not isinstance(item, str) or not item for item in values):
35
+ raise TypeError
36
+ return RepositoryTruthReference(*values)
37
+ except (KeyError, TypeError, ValueError) as error:
38
+ raise MissionCompletionEvaluationError("current Repository Truth provenance is incomplete") from error
39
+
40
+
41
+ def _canonical_execution_references(
42
+ mission_id: str, evidence: Sequence[Mapping[str, Any]],
43
+ ) -> dict[str, CanonicalExecutionEvidenceReference]:
44
+ references: dict[str, CanonicalExecutionEvidenceReference] = {}
45
+ for document in evidence:
46
+ if document.get("outcome") != "complete":
47
+ continue
48
+ repository = document.get("repository_evidence")
49
+ if not isinstance(repository, Mapping) or repository.get("mission_id") != mission_id:
50
+ continue
51
+ try:
52
+ values = (
53
+ document["receipt_id"], repository["action_id"], document["report_id"],
54
+ repository["repository_revision"], repository["content_digest"],
55
+ )
56
+ if any(not isinstance(item, str) or not item for item in values):
57
+ raise TypeError
58
+ reference = CanonicalExecutionEvidenceReference(*values, candidate_revision=repository.get('candidate_revision'))
59
+ except (KeyError, TypeError, ValueError):
60
+ continue
61
+ if any(document.get(key) != repository.get(key) for key in ("correlation_id", "host_run_id", "report_id")):
62
+ continue
63
+ previous = references.get(reference.receipt_id)
64
+ if previous is not None and previous != reference:
65
+ raise MissionCompletionEvaluationError("execution receipt identity binds conflicting terminal evidence")
66
+ references[reference.receipt_id] = reference
67
+ return references
68
+
69
+
70
+ def _host_control_observation_matches(observation, requirement, validity_policy: str) -> bool:
71
+ if (observation.schema_version != "1.1" or observation.result != "PASS"
72
+ or observation.reason != "APPROVED_HOST_CONTROL_EXECUTED_AND_PASSED"
73
+ or observation.content_digest != observation.repository_evidence_digest
74
+ or observation.observed_json is None):
75
+ return False
76
+ try:
77
+ record = json.loads(observation.observed_json)
78
+ except (TypeError, ValueError):
79
+ return False
80
+ return (isinstance(record, dict)
81
+ and record.get("validation_id") == requirement.validation_id
82
+ and record.get("control_identity") == requirement.control_identity
83
+ and record.get("control_definition_digest") == requirement.control_definition_digest
84
+ and record.get("validation_profile_version") == requirement.validation_profile_version
85
+ and record.get("profile_reference") == requirement.profile_reference
86
+ and record.get("category") == requirement.control_category
87
+ and (record.get("candidate_sha") == observation.repository_revision
88
+ if validity_policy == "current_revision" else
89
+ record.get("candidate_sha") in {observation.candidate_revision, observation.repository_revision})
90
+ and record.get("execution_status") == "EXECUTED"
91
+ and record.get("result") == "PASS" and record.get("exit_code") == 0
92
+ and (requirement.minimum_test_count == 0 or
93
+ isinstance(record.get("test_count"), int)
94
+ and not isinstance(record["test_count"], bool)
95
+ and record["test_count"] >= requirement.minimum_test_count))
96
+
97
+
98
+ class MissionCompletionEvaluator:
99
+ """Interpret approved predicates over Forge-observed facts; never provider PASS."""
100
+
101
+ def evaluate(self, mission, repository_truth, execution_evidence, evidence):
102
+ if not mission.acceptance_criteria:
103
+ raise MissionCompletionEvaluationError("approved Mission has no acceptance criteria")
104
+ mission_digest = _digest(mission.to_dict())
105
+ truth = _truth_reference(repository_truth)
106
+ identifiers = {mission_criterion_id(mission.id, criterion): criterion for criterion in mission.acceptance_criteria}
107
+ canonical = _canonical_execution_references(mission.id, execution_evidence)
108
+ contracts = {mission_criterion_id(mission.id, item.criterion): item
109
+ for item in mission.criterion_assessment_contracts}
110
+ bindings = {} if evidence is None else {item.criterion_id: item for item in evidence.bindings}
111
+ if evidence is not None:
112
+ if evidence.mission_id != mission.id or evidence.mission_digest != mission_digest:
113
+ raise MissionCompletionEvaluationError("completion evidence does not bind the approved Mission")
114
+ if set(bindings) - set(identifiers):
115
+ raise MissionCompletionEvaluationError("completion evidence references an unknown Mission criterion")
116
+ evaluations = []
117
+ for criterion_id, criterion in identifiers.items():
118
+ contract, binding = contracts.get(criterion_id), bindings.get(criterion_id)
119
+ reason = None
120
+ if contract is None:
121
+ reason = "APPROVED_ASSESSMENT_CONTRACT_MISSING"
122
+ elif evidence is not None and evidence.schema_version != "2.0":
123
+ reason = "LEGACY_ASSOCIATIONS_ARE_NOT_SUBSTANTIVE_EVIDENCE"
124
+ elif binding is None:
125
+ reason = "MISSING_AUTHORITATIVE_OBSERVATION"
126
+ elif binding.contract_digest != contract.digest:
127
+ reason = "ASSESSMENT_CONTRACT_MISMATCH"
128
+ elif binding.repository_truth != truth:
129
+ reason = "STALE_REPOSITORY_TRUTH"
130
+ elif any(canonical.get(ref.receipt_id) != ref for ref in binding.execution_evidence):
131
+ reason = "NON_CANONICAL_EXECUTION_EVIDENCE"
132
+ references = () if binding is None else binding.execution_evidence
133
+ observations = () if binding is None else binding.observations
134
+ requirement_results = []
135
+ if reason is None:
136
+ reference_map = {ref.receipt_id: ref for ref in references}
137
+ seen = {}
138
+ for observation in observations:
139
+ reference = reference_map.get(observation.receipt_id)
140
+ if (reference is None or observation.mission_id != mission.id
141
+ or observation.mission_digest != mission_digest
142
+ or observation.criterion_id != criterion_id
143
+ or observation.contract_digest != contract.digest
144
+ or observation.action_id != reference.action_id
145
+ or observation.report_id != reference.report_id
146
+ or observation.repository_revision != reference.repository_revision
147
+ or observation.candidate_revision != reference.candidate_revision
148
+ or observation.repository_evidence_digest != reference.repository_evidence_digest):
149
+ reason = "OBSERVATION_PROVENANCE_MISMATCH"
150
+ break
151
+ identity = (observation.receipt_id, observation.requirement_id)
152
+ if identity in seen and seen[identity] != observation:
153
+ raise MissionCompletionEvaluationError("conflicting immutable criterion observations")
154
+ seen[identity] = observation
155
+ if observation.requirement_id not in {req.requirement_id for req in contract.requirements}:
156
+ raise MissionCompletionEvaluationError("observation references unknown criterion requirement")
157
+ if reason is None:
158
+ for requirement in contract.requirements:
159
+ candidates = [obs for obs in observations if obs.requirement_id == requirement.requirement_id]
160
+ if contract.validity_policy == "current_revision":
161
+ candidates = [obs for obs in candidates if obs.repository_revision == truth.revision]
162
+ source = mission.repository_evidence_source
163
+ if requirement.kind == "host_control":
164
+ if not requirement.control_definition_digest:
165
+ result, why, matched = "UNSATISFIED", "UNSUPPORTED_AUTHORITATIVE_EVIDENCE_SOURCE", ()
166
+ elif any(obs.source_kind != "host_control" or obs.source_identity != requirement.control_identity
167
+ or obs.artifact_path != "ep-terminal-artifact"
168
+ or obs.requirement_digest != requirement.digest or obs.json_pointer != ""
169
+ for obs in candidates):
170
+ result, why, matched = "UNSATISFIED", "OBSERVATION_SOURCE_MISMATCH", ()
171
+ elif not candidates:
172
+ result, why, matched = "UNSATISFIED", "CURRENT_OBSERVATION_MISSING", ()
173
+ else:
174
+ if contract.validity_policy == "historical_delivery":
175
+ passing = [obs for index, obs in enumerate(candidates)
176
+ if _host_control_observation_matches(obs, requirement, contract.validity_policy)
177
+ and not any(later.repository_revision == obs.repository_revision
178
+ and not _host_control_observation_matches(later, requirement, contract.validity_policy)
179
+ for later in candidates[index + 1:])]
180
+ selected = passing[-1] if passing else candidates[-1]
181
+ else:
182
+ selected = candidates[-1]
183
+ proven = _host_control_observation_matches(selected, requirement, contract.validity_policy)
184
+ result = "PROVEN" if proven else "UNSATISFIED"
185
+ why = "APPROVED_HOST_CONTROL_EXECUTED_AND_PASSED" if proven else selected.reason
186
+ matched = (selected.id,)
187
+ elif requirement.kind != "repository_json":
188
+ result, why, matched = "UNSATISFIED", "UNSUPPORTED_AUTHORITATIVE_EVIDENCE_SOURCE", ()
189
+ elif source is None:
190
+ result, why, matched = "UNSATISFIED", "APPROVED_REPOSITORY_SOURCE_MISSING", ()
191
+ elif any(obs.source_kind != requirement.kind or obs.source_identity != source.github_repository
192
+ or obs.artifact_path != requirement.artifact_path
193
+ or obs.requirement_digest != requirement.digest
194
+ or obs.json_pointer != requirement.json_pointer for obs in candidates):
195
+ result, why, matched = "UNSATISFIED", "OBSERVATION_SOURCE_MISMATCH", ()
196
+ elif not candidates:
197
+ result, why, matched = "UNSATISFIED", "CURRENT_OBSERVATION_MISSING", ()
198
+ elif any(len({(obs.content_digest, obs.observed_json, obs.result)
199
+ for obs in candidates if obs.repository_revision == revision
200
+ and obs.result != 'UNAVAILABLE'}) > 1
201
+ for revision in {obs.repository_revision for obs in candidates}):
202
+ result, why, matched = "UNSATISFIED", "CONFLICTING_IMMUTABLE_ARTIFACT_OBSERVATIONS", ()
203
+ else:
204
+ # A historical-delivery assertion describes an accomplished event.
205
+ # A new current-property failure is not a rewrite of that old event.
206
+ passing = [obs for obs in candidates if obs.result == "PASS"
207
+ and obs.observed_json == requirement.expected_json
208
+ and obs.content_digest is not None]
209
+ selected = passing[-1] if passing and contract.validity_policy == "historical_delivery" else candidates[-1]
210
+ proven = (selected.result == "PASS" and selected.observed_json == requirement.expected_json
211
+ and selected.content_digest is not None)
212
+ result = "PROVEN" if proven else "UNSATISFIED"
213
+ why = "APPROVED_JSON_ASSERTION_MATCHED" if proven else selected.reason
214
+ matched = (selected.id,)
215
+ requirement_results.append({"requirement_id": requirement.requirement_id,
216
+ "requirement_digest": requirement.digest, "status": result, "reason": why,
217
+ "observation_ids": list(matched)})
218
+ reason = ("ALL_APPROVED_REQUIREMENTS_PROVEN" if all(item["status"] == "PROVEN" for item in requirement_results)
219
+ else "REQUIRED_OBSERVATIONS_UNPROVEN")
220
+ status = (MissionCriterionEvaluationStatus.PROVEN if reason == "ALL_APPROVED_REQUIREMENTS_PROVEN"
221
+ else MissionCriterionEvaluationStatus.UNSATISFIED)
222
+ evaluations.append(MissionCriterionEvaluation(
223
+ criterion_id, criterion, status, reason, references, truth,
224
+ None if contract is None else contract.digest, tuple(requirement_results), observations,
225
+ ))
226
+ return MissionCompletionEvaluation(
227
+ mission.id, mission_digest, tuple(evaluations), None if evidence is None else evidence.digest,
228
+ evidence=evidence,
229
+ )
@@ -0,0 +1,132 @@
1
+ """Bounded read-only observations at an approved immutable repository revision.
2
+
3
+ Only structural facts explicitly approved by Architecture are assessed. This
4
+ does not execute repository code or interpret artifacts as behavioral test PASS.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from hashlib import sha256
9
+ import json
10
+ from typing import Any
11
+ from urllib.error import HTTPError, URLError
12
+ from urllib.parse import quote
13
+ from urllib.request import Request, HTTPRedirectHandler, build_opener
14
+
15
+ from forge.models.criterion_observation import CriterionObservation, canonical_digest
16
+ from forge.models.mission_completion import mission_criterion_id
17
+
18
+
19
+ class RepositoryObservationUnavailable(ValueError):
20
+ pass
21
+
22
+
23
+ class _NoRedirect(HTTPRedirectHandler):
24
+ def redirect_request(self, *args, **kwargs):
25
+ raise RepositoryObservationUnavailable('REPOSITORY_SOURCE_REDIRECT_REJECTED')
26
+
27
+
28
+ class GitHubRepositoryArtifactReader:
29
+ """Public HTTPS source only; no local checkout, token discovery or arbitrary URL."""
30
+
31
+ maximum_bytes = 1024 * 1024
32
+
33
+ def read(self, repository: str, revision: str, path: str) -> bytes:
34
+ from forge.models.criterion_assessment import ApprovedRepositoryEvidenceSource, CriterionEvidenceRequirement
35
+ ApprovedRepositoryEvidenceSource('validation', repository)
36
+ CriterionEvidenceRequirement('validation', kind='repository_json', artifact_path=path,
37
+ expected_json='null')
38
+ if len(revision) != 40 or any(c not in '0123456789abcdef' for c in revision):
39
+ raise RepositoryObservationUnavailable('IMMUTABLE_REPOSITORY_REVISION_REQUIRED')
40
+ url = 'https://raw.githubusercontent.com/' + repository + '/' + revision + '/' + quote(path, safe='/')
41
+ request = Request(url, headers={'Accept': 'application/json', 'User-Agent': 'Forge-criterion-observer/1.0'})
42
+ try:
43
+ with build_opener(_NoRedirect()).open(request, timeout=15) as response:
44
+ data = response.read(self.maximum_bytes + 1)
45
+ except HTTPError as error:
46
+ code = 'REPOSITORY_ARTIFACT_ABSENT' if error.code == 404 else 'REPOSITORY_SOURCE_UNAVAILABLE'
47
+ raise RepositoryObservationUnavailable(code) from error
48
+ except (URLError, TimeoutError, OSError) as error:
49
+ raise RepositoryObservationUnavailable('REPOSITORY_SOURCE_UNAVAILABLE') from error
50
+ if len(data) > self.maximum_bytes:
51
+ raise RepositoryObservationUnavailable('REPOSITORY_ARTIFACT_SIZE_LIMIT')
52
+ return data
53
+
54
+
55
+ def _json(data: bytes) -> Any:
56
+ def pairs(values):
57
+ result = {}
58
+ for key, value in values:
59
+ if key in result:
60
+ raise ValueError('duplicate JSON key')
61
+ result[key] = value
62
+ return result
63
+ def invalid_constant(_value):
64
+ raise ValueError('non-finite JSON number')
65
+ return json.loads(data.decode('utf-8'), object_pairs_hook=pairs, parse_constant=invalid_constant)
66
+
67
+
68
+ def _pointer(value: Any, pointer: str) -> Any:
69
+ if pointer == '':
70
+ return value
71
+ for part in pointer[1:].split('/'):
72
+ key = part.replace('~1', '/').replace('~0', '~')
73
+ if isinstance(value, list):
74
+ if not key.isascii() or not key.isdigit() or (key != '0' and key.startswith('0')):
75
+ raise KeyError(key)
76
+ value = value[int(key)]
77
+ elif isinstance(value, dict):
78
+ value = value[key]
79
+ else:
80
+ raise KeyError(key)
81
+ return value
82
+
83
+
84
+ class RepositoryCriterionObserver:
85
+ """Collect facts from the approved source for the exact accepted delivery."""
86
+
87
+ def __init__(self, reader=None):
88
+ self.reader = reader or GitHubRepositoryArtifactReader()
89
+
90
+ def observe(self, mission, reference, repository_id: str) -> tuple[CriterionObservation, ...]:
91
+ source = mission.repository_evidence_source
92
+ if source is None:
93
+ return ()
94
+ if source.repository_id != repository_id:
95
+ raise ValueError('approved Repository Evidence source differs from the installed repository')
96
+ observations = []
97
+ cache: dict[str, bytes | RepositoryObservationUnavailable] = {}
98
+ for contract in mission.criterion_assessment_contracts:
99
+ for requirement in contract.requirements:
100
+ if requirement.kind != 'repository_json':
101
+ continue
102
+ path = requirement.artifact_path
103
+ if path not in cache:
104
+ try:
105
+ cache[path] = self.reader.read(source.github_repository, reference.repository_revision, path)
106
+ except RepositoryObservationUnavailable as error:
107
+ cache[path] = error
108
+ data = cache[path]
109
+ observed_json, content_digest = None, None
110
+ if isinstance(data, RepositoryObservationUnavailable):
111
+ result, reason = 'UNAVAILABLE', str(data)
112
+ else:
113
+ content_digest = 'sha256:' + sha256(data).hexdigest()
114
+ try:
115
+ value = _pointer(_json(data), requirement.json_pointer)
116
+ observed_json = json.dumps(value, sort_keys=True, separators=(',', ':'),
117
+ ensure_ascii=False, allow_nan=False)
118
+ result = 'PASS' if observed_json == requirement.expected_json else 'FAIL'
119
+ reason = 'APPROVED_JSON_ASSERTION_MATCHED' if result == 'PASS' else 'JSON_ASSERTION_MISMATCH'
120
+ except (KeyError, IndexError):
121
+ result, reason = 'FAIL', 'JSON_POINTER_ABSENT'
122
+ except (ValueError, UnicodeError, RecursionError):
123
+ result, reason = 'FAIL', 'INVALID_JSON_ARTIFACT'
124
+ observations.append(CriterionObservation(
125
+ mission.id, canonical_digest(mission.to_dict()), mission_criterion_id(mission.id, contract.criterion),
126
+ contract.digest, requirement.requirement_id, reference.receipt_id, reference.action_id,
127
+ reference.report_id, reference.repository_revision, reference.repository_evidence_digest,
128
+ 'repository_json', source.github_repository, path, content_digest, observed_json, result, reason,
129
+ requirement_digest=requirement.digest, json_pointer=requirement.json_pointer,
130
+ candidate_revision=reference.candidate_revision,
131
+ ))
132
+ return tuple(observations)