forge-autonomy 2.7.35__tar.gz → 2.7.36__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 (337) hide show
  1. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/PKG-INFO +1 -1
  2. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/__main__.py +67 -0
  3. forge_autonomy-2.7.36/forge/preserved_lifecycle.py +458 -0
  4. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/qualification/server_runtime.py +78 -0
  5. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge_autonomy.egg-info/PKG-INFO +1 -1
  6. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge_autonomy.egg-info/SOURCES.txt +2 -0
  7. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/product-version.json +1 -1
  8. forge_autonomy-2.7.36/tests/test_preserved_lifecycle.py +197 -0
  9. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/MANIFEST.in +0 -0
  10. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/README.md +0 -0
  11. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/__init__.py +0 -0
  12. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/_version.py +0 -0
  13. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/action_derivation_canary_closure.py +0 -0
  14. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/action_derivation_evidence.py +0 -0
  15. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/action_derivation_qualification.py +0 -0
  16. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/action_derivation_reattempt.py +0 -0
  17. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/ai_architect/__init__.py +0 -0
  18. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/ai_architect/provider_registry.py +0 -0
  19. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/api/installed-health-component-registry-1.0.json +0 -0
  20. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/api/operations-read-openapi-v1.json +0 -0
  21. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/api/operations-read-postman-v1.json +0 -0
  22. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/api/server-openapi-v1.json +0 -0
  23. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/api/server-postman-v1.json +0 -0
  24. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/architecture/__init__.py +0 -0
  25. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/architecture/advisor.py +0 -0
  26. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/architecture/presentation.py +0 -0
  27. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/architecture/workspace.py +0 -0
  28. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/autonomous_orchestrator.py +0 -0
  29. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/business/__init__.py +0 -0
  30. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/business/advisor.py +0 -0
  31. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/business/ingress.py +0 -0
  32. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/business/presentation.py +0 -0
  33. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/business/workspace.py +0 -0
  34. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/canary.py +0 -0
  35. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/capabilities/__init__.py +0 -0
  36. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/capabilities/delegation.py +0 -0
  37. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/completion/__init__.py +0 -0
  38. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/completion/assessor.py +0 -0
  39. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/completion/host_control_observer.py +0 -0
  40. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/completion/mission.py +0 -0
  41. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/completion/repository_observer.py +0 -0
  42. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/constitutional/__init__.py +0 -0
  43. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/constitutional/assessor.py +0 -0
  44. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/core/__init__.py +0 -0
  45. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/core/json_store.py +0 -0
  46. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/decision_evidence/__init__.py +0 -0
  47. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/decision_evidence/repository.py +0 -0
  48. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/dispatcher/__init__.py +0 -0
  49. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/dispatcher/dispatcher.py +0 -0
  50. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/ep_simulator.py +0 -0
  51. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/execution/__init__.py +0 -0
  52. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/execution/loop.py +0 -0
  53. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/execution/presentation.py +0 -0
  54. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/execution_host_configuration.py +0 -0
  55. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/foundation/__init__.py +0 -0
  56. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/foundation/loader.py +0 -0
  57. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/generation_two.py +0 -0
  58. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/generation_two_execution.py +0 -0
  59. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/governance/__init__.py +0 -0
  60. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/governance/execution_policy.py +0 -0
  61. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/governance/profiles.py +0 -0
  62. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/governance_authority.py +0 -0
  63. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/installed_health.py +0 -0
  64. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/installed_lifecycle.py +0 -0
  65. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/intake.py +0 -0
  66. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/integration/__init__.py +0 -0
  67. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/integration/coordinator.py +0 -0
  68. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/integration/repository.py +0 -0
  69. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/knowledge/__init__.py +0 -0
  70. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/knowledge/consumer.py +0 -0
  71. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/knowledge/registry.py +0 -0
  72. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/lifecycle/__init__.py +0 -0
  73. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/lifecycle/store.py +0 -0
  74. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/mission_amendment.py +0 -0
  75. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/mission_cli.py +0 -0
  76. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/mission_scheduler.py +0 -0
  77. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/__init__.py +0 -0
  78. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/action.py +0 -0
  79. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/action_derivation.py +0 -0
  80. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/agent_policy.py +0 -0
  81. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/ai_architect_provider.py +0 -0
  82. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/ai_architect_session.py +0 -0
  83. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/ai_provider_registry.py +0 -0
  84. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/architecture_mission.py +0 -0
  85. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/architecture_reasoning.py +0 -0
  86. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/architecture_review.py +0 -0
  87. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/codex_runtime_prompt.py +0 -0
  88. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/constitutional_validation.py +0 -0
  89. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/criterion_assessment.py +0 -0
  90. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/criterion_observation.py +0 -0
  91. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/decision_evidence.py +0 -0
  92. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/execution_host.py +0 -0
  93. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/foundation.py +0 -0
  94. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/historical_intent.py +0 -0
  95. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/integration.py +0 -0
  96. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/intent.py +0 -0
  97. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/intent_authoring.py +0 -0
  98. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/mission.py +0 -0
  99. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/mission_candidate.py +0 -0
  100. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/mission_completion.py +0 -0
  101. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/mission_planner.py +0 -0
  102. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/mission_recommendation.py +0 -0
  103. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/phase_completion.py +0 -0
  104. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/planning.py +0 -0
  105. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/producer.py +0 -0
  106. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/prompt_artifact.py +0 -0
  107. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/proposal.py +0 -0
  108. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/runtime_prompt.py +0 -0
  109. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/models/solution_template.py +0 -0
  110. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/operations_read_api.py +0 -0
  111. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/operator_identity.py +0 -0
  112. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/planner/__init__.py +0 -0
  113. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/planner/action_derivation.py +0 -0
  114. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/planner/codex_cli_session.py +0 -0
  115. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/planner/durable_derivation.py +0 -0
  116. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/planner/engine.py +0 -0
  117. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/planner/openai_responses.py +0 -0
  118. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/planner/provider_adapter.py +0 -0
  119. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/planning/__init__.py +0 -0
  120. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/planning/loader.py +0 -0
  121. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/planning/registry.py +0 -0
  122. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/portfolio_intelligence.py +0 -0
  123. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/programme_authorization.py +0 -0
  124. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/prompts/__init__.py +0 -0
  125. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/prompts/codex_cli.py +0 -0
  126. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/prompts/generator.py +0 -0
  127. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/proposals/__init__.py +0 -0
  128. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/proposals/generator.py +0 -0
  129. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/provider_context.py +0 -0
  130. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/provider_security.py +0 -0
  131. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/qualification/__init__.py +0 -0
  132. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/qualification/bootstrap_sequence.py +0 -0
  133. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/qualification/criterion_completion.py +0 -0
  134. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/qualification/generation_one.py +0 -0
  135. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/qualification/installed_smoke.py +0 -0
  136. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/recommendations/__init__.py +0 -0
  137. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/recommendations/engine.py +0 -0
  138. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/repository_truth.py +0 -0
  139. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/review/__init__.py +0 -0
  140. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/review/engine.py +0 -0
  141. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/runtime/__init__.py +0 -0
  142. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/runtime/bootstrap.py +0 -0
  143. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/runtime/data_root.py +0 -0
  144. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/runtime/database.py +0 -0
  145. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/runtime/dynamic_mission.py +0 -0
  146. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/runtime/evidence.py +0 -0
  147. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/runtime/execution_context.py +0 -0
  148. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/runtime/health.py +0 -0
  149. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/runtime/mission_controller.py +0 -0
  150. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/runtime/operational_reset.py +0 -0
  151. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/runtime/runner.py +0 -0
  152. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/runtime/service.py +0 -0
  153. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/scheduler/__init__.py +0 -0
  154. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/scheduler/adapter.py +0 -0
  155. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/scheduler/ep_http_adapter.py +0 -0
  156. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/scheduler/ep_v12.py +0 -0
  157. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/scheduler/scheduler.py +0 -0
  158. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/capability-registry-1.0.schema.json +0 -0
  159. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/capability.schema.json +0 -0
  160. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/common.schema.json +0 -0
  161. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/decision-evidence-1.0.schema.json +0 -0
  162. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/engineering-goal-0.5.schema.json +0 -0
  163. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/engineering-increment-proposal-0.5.schema.json +0 -0
  164. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/engineering-mode.schema.json +0 -0
  165. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/engineering-plan-0.5.schema.json +0 -0
  166. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/engineering-platform-peer-configuration-1.0.schema.json +0 -0
  167. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/engineering-platform-peer-configuration-1.1.schema.json +0 -0
  168. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/engineering-prompt-artifact-0.7.schema.json +0 -0
  169. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/engineering-proposal-0.6.schema.json +0 -0
  170. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/evidence-reference-0.5.schema.json +0 -0
  171. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/foundation-document.schema.json +0 -0
  172. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/governance-profile.schema.json +0 -0
  173. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/knowledge-source-0.4.schema.json +0 -0
  174. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/knowledge-source.schema.json +0 -0
  175. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/mission-candidate-1.0.schema.json +0 -0
  176. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/mission-candidate-1.1.schema.json +0 -0
  177. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/phase-completion-1.0.schema.json +0 -0
  178. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/planning-document-0.5.schema.json +0 -0
  179. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/repository-catalog.schema.json +0 -0
  180. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/repository.schema.json +0 -0
  181. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/solution-template-1.0.schema.json +0 -0
  182. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/schemas/workspace.schema.json +0 -0
  183. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/secure_store.py +0 -0
  184. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/server_runtime.py +0 -0
  185. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/solutions/__init__.py +0 -0
  186. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/solutions/catalogue.py +0 -0
  187. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/solutions/generator.py +0 -0
  188. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/state/__init__.py +0 -0
  189. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/state/mission_state.py +0 -0
  190. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge/workspace/__init__.py +0 -0
  191. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge_autonomy.egg-info/dependency_links.txt +0 -0
  192. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge_autonomy.egg-info/entry_points.txt +0 -0
  193. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/forge_autonomy.egg-info/top_level.txt +0 -0
  194. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/pyproject.toml +0 -0
  195. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/capability-registry-1.0.schema.json +0 -0
  196. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/capability.schema.json +0 -0
  197. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/common.schema.json +0 -0
  198. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/decision-evidence-1.0.schema.json +0 -0
  199. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/engineering-goal-0.5.schema.json +0 -0
  200. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/engineering-increment-proposal-0.5.schema.json +0 -0
  201. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/engineering-mode.schema.json +0 -0
  202. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/engineering-plan-0.5.schema.json +0 -0
  203. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/engineering-prompt-artifact-0.7.schema.json +0 -0
  204. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/engineering-proposal-0.6.schema.json +0 -0
  205. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/evidence-reference-0.5.schema.json +0 -0
  206. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/foundation-document.schema.json +0 -0
  207. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/governance-profile.schema.json +0 -0
  208. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/knowledge-source-0.4.schema.json +0 -0
  209. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/knowledge-source.schema.json +0 -0
  210. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/mission-candidate-1.0.schema.json +0 -0
  211. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/mission-candidate-1.1.schema.json +0 -0
  212. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/phase-completion-1.0.schema.json +0 -0
  213. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/planning-document-0.5.schema.json +0 -0
  214. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/repository-catalog.schema.json +0 -0
  215. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/repository.schema.json +0 -0
  216. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/solution-template-1.0.schema.json +0 -0
  217. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/schemas/workspace.schema.json +0 -0
  218. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/setup.cfg +0 -0
  219. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_action_derivation.py +0 -0
  220. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_action_derivation_canary_closure.py +0 -0
  221. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_action_derivation_evidence.py +0 -0
  222. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_action_derivation_qualification.py +0 -0
  223. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_adaptive_action_sizing_design.py +0 -0
  224. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_agent_policy.py +0 -0
  225. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_aggregate_health_roadmap.py +0 -0
  226. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_ai_architect_provider.py +0 -0
  227. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_ai_architect_session.py +0 -0
  228. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_ai_development_projection.py +0 -0
  229. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_ai_provider_registry.py +0 -0
  230. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_architecture_reasoning.py +0 -0
  231. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_architecture_review_engine.py +0 -0
  232. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_architecture_workspace.py +0 -0
  233. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_autonomous_orchestrator.py +0 -0
  234. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_bootstrap_adapter.py +0 -0
  235. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_bootstrap_mission_canary.py +0 -0
  236. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_bootstrap_mission_runner.py +0 -0
  237. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_bootstrap_mission_scheduler.py +0 -0
  238. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_bootstrap_sequence_qualification.py +0 -0
  239. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_business_governance_ingress.py +0 -0
  240. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_business_workspace.py +0 -0
  241. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_canonical_data_root.py +0 -0
  242. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_capability_delegation.py +0 -0
  243. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_codex_cli_runtime_prompt.py +0 -0
  244. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_codex_cli_session.py +0 -0
  245. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_constitutional_validation.py +0 -0
  246. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_criterion_assessment_contract.py +0 -0
  247. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_criterion_ep_http_boundary.py +0 -0
  248. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_criterion_planning_context.py +0 -0
  249. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_decision_evidence.py +0 -0
  250. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_default_project_quality_policy.py +0 -0
  251. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_distribution_identity.py +0 -0
  252. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_durable_action_derivation.py +0 -0
  253. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_dynamic_mission_capability.py +0 -0
  254. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_engineering_action_architecture.py +0 -0
  255. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_engineering_intent_authoring.py +0 -0
  256. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_engineering_intent_lifecycle.py +0 -0
  257. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_engineering_mission.py +0 -0
  258. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_engineering_planning.py +0 -0
  259. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_engineering_prompt_artifacts.py +0 -0
  260. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_engineering_proposals.py +0 -0
  261. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_ep_http_adapter.py +0 -0
  262. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_ep_simulator.py +0 -0
  263. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_ep_simulator_rolling_planning.py +0 -0
  264. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_execution_context.py +0 -0
  265. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_execution_host_configuration.py +0 -0
  266. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_execution_host_contract.py +0 -0
  267. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_execution_loop.py +0 -0
  268. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_existing_release_reconciliation.py +0 -0
  269. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_forge_console_admin_parity_contract.py +0 -0
  270. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_forge_console_configuration_contract.py +0 -0
  271. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_forge_console_hosting_contract.py +0 -0
  272. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_forge_operations_console_roadmap.py +0 -0
  273. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_forge_outer_loop_ci_roadmap.py +0 -0
  274. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_forge_v1_bootstrap_scheduler_contract.py +0 -0
  275. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_forge_v1_implementation_dag.py +0 -0
  276. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_foundation_loader.py +0 -0
  277. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_foundation_models.py +0 -0
  278. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_generation_one_bootstrap_qualification.py +0 -0
  279. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_generation_two_execution.py +0 -0
  280. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_generation_two_mission_intake.py +0 -0
  281. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_governance_authority.py +0 -0
  282. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_governed_coverage.py +0 -0
  283. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_historical_engineering_intent.py +0 -0
  284. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_host_control_completion.py +0 -0
  285. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_inner_loop_ci_contract.py +0 -0
  286. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_installed_dynamic_mission_runtime.py +0 -0
  287. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_installed_forge_update.py +0 -0
  288. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_installed_lifecycle.py +0 -0
  289. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_integration_coordination.py +0 -0
  290. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_knowledge_consumption.py +0 -0
  291. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_live_project_roadmap_contract.py +0 -0
  292. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_mission_completion_evaluator.py +0 -0
  293. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_mission_controller.py +0 -0
  294. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_mission_dispatcher.py +0 -0
  295. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_mission_planner.py +0 -0
  296. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_mission_recommendation_engine.py +0 -0
  297. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_mission_recommendation_lifecycle.py +0 -0
  298. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_mission_runtime_projection.py +0 -0
  299. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_mission_runtime_scheduler.py +0 -0
  300. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_mission_state_store.py +0 -0
  301. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_mission_status.py +0 -0
  302. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_openai_action_derivation.py +0 -0
  303. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_operational_reset.py +0 -0
  304. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_operations_health.py +0 -0
  305. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_operations_read_api.py +0 -0
  306. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_operator_identity.py +0 -0
  307. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_phase_completion.py +0 -0
  308. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_platform_contract_documents.py +0 -0
  309. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_portfolio_intelligence_operation.py +0 -0
  310. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_producer_contract.py +0 -0
  311. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_product_version_operations.py +0 -0
  312. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_production_release_workflow.py +0 -0
  313. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_productization_reconciliation.py +0 -0
  314. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_programme_authorization.py +0 -0
  315. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_provider_context.py +0 -0
  316. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_provider_security.py +0 -0
  317. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_pypi_distribution_readback.py +0 -0
  318. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_recovered_partial_completion.py +0 -0
  319. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_release_operation.py +0 -0
  320. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_repository_truth.py +0 -0
  321. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_role_aware_conversations_contract.py +0 -0
  322. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_runtime_bootstrap.py +0 -0
  323. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_runtime_database.py +0 -0
  324. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_runtime_evidence.py +0 -0
  325. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_runtime_health.py +0 -0
  326. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_runtime_instance_persistence.py +0 -0
  327. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_runtime_prompt_generation.py +0 -0
  328. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_runtime_service.py +0 -0
  329. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_runtime_status_projection.py +0 -0
  330. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_scheduler_dependency_boundary.py +0 -0
  331. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_schemas.py +0 -0
  332. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_secure_store.py +0 -0
  333. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_server_api_contract.py +0 -0
  334. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_server_runtime.py +0 -0
  335. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_solution_templates.py +0 -0
  336. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_substantive_mission_completion.py +0 -0
  337. {forge_autonomy-2.7.35 → forge_autonomy-2.7.36}/tests/test_terminal_continuation_replay.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forge-autonomy
3
- Version: 2.7.35
3
+ Version: 2.7.36
4
4
  Summary: Forge mission planning, producer contracts, and evidence interpretation runtime.
5
5
  Author: Forge maintainers
6
6
  License-Expression: Apache-2.0
@@ -141,6 +141,28 @@ def main(argv: list[str] | None = None) -> int:
141
141
  uninstall_status.add_argument("--operation-id", required=True)
142
142
  uninstall_status.add_argument("--instances-root", required=True)
143
143
  uninstall_status.add_argument("--instance-id", required=True)
144
+ for lifecycle_name, lifecycle_help in (
145
+ ("preserve", "preserve one exact quiescent Forge instance data root"),
146
+ ("purge", "permanently purge one exact Forge instance"),
147
+ ("restore", "validate one preserved Forge instance for same-identity restore"),
148
+ ):
149
+ lifecycle = server_commands.add_parser(lifecycle_name, help=lifecycle_help)
150
+ lifecycle.add_argument("--operation-id", required=True)
151
+ lifecycle.add_argument("--instances-root", required=True)
152
+ lifecycle.add_argument("--instance-id", required=True)
153
+ lifecycle.add_argument("--runtime-id", required=True)
154
+ lifecycle.add_argument("--installation-id", required=True)
155
+ lifecycle.add_argument("--installed-version", required=True)
156
+ lifecycle.add_argument("--installed-source", required=True)
157
+ lifecycle.add_argument("--installed-artifact-digest", required=True)
158
+ if lifecycle_name == "restore":
159
+ lifecycle.add_argument("--preserve-operation-id", required=True)
160
+ lifecycle_status_parser = server_commands.add_parser(
161
+ "lifecycle-status", help="read one preserve/purge/restore lifecycle operation"
162
+ )
163
+ lifecycle_status_parser.add_argument("--operation-id", required=True)
164
+ lifecycle_status_parser.add_argument("--instances-root", required=True)
165
+ lifecycle_status_parser.add_argument("--instance-id", required=True)
144
166
  reset = server_commands.add_parser("reset", help="operate the Forge-owned operational-history reset")
145
167
  reset_commands = reset.add_subparsers(dest="reset_command", required=True)
146
168
  reset_commands.add_parser("preview", help="inspect a read-only reset plan")
@@ -316,6 +338,51 @@ def main(argv: list[str] | None = None) -> int:
316
338
  return 0
317
339
  except (InstalledLifecycleError, OSError, PermissionError, ValueError) as error:
318
340
  return _failure("server uninstall-status", error)
341
+ if args.command == "server" and args.server_command in {"preserve", "purge", "restore"}:
342
+ if args.data_root is None:
343
+ return _failure(
344
+ "server " + args.server_command,
345
+ ValueError("--data-root is required for instance lifecycle mutation"),
346
+ )
347
+ try:
348
+ from .preserved_lifecycle import (
349
+ InstalledPreserveDispatcher,
350
+ InstalledPurgeDispatcher,
351
+ InstalledRestoreDispatcher,
352
+ InstanceLifecycleRequest,
353
+ RestoreRequest,
354
+ )
355
+ common = {
356
+ "operation_id": args.operation_id,
357
+ "instance_id": args.instance_id,
358
+ "runtime_id": args.runtime_id,
359
+ "installation_id": args.installation_id,
360
+ "installed_version": args.installed_version,
361
+ "installed_source": args.installed_source,
362
+ "installed_artifact_digest": args.installed_artifact_digest,
363
+ "data_root": args.data_root,
364
+ "instances_root": args.instances_root,
365
+ }
366
+ if args.server_command == "preserve":
367
+ result = InstalledPreserveDispatcher(InstanceLifecycleRequest(**common)).run()
368
+ elif args.server_command == "purge":
369
+ result = InstalledPurgeDispatcher(InstanceLifecycleRequest(**common)).run()
370
+ else:
371
+ result = InstalledRestoreDispatcher(RestoreRequest(
372
+ **common, preserve_operation_id=args.preserve_operation_id,
373
+ )).run()
374
+ print(json.dumps(result, sort_keys=True))
375
+ return 0
376
+ except (InstalledLifecycleError, OSError, sqlite3.Error, PermissionError, ValueError) as error:
377
+ return _failure("server " + args.server_command, error)
378
+ if args.command == "server" and args.server_command == "lifecycle-status":
379
+ try:
380
+ from .preserved_lifecycle import InstalledLifecycleError, lifecycle_status
381
+ result = lifecycle_status(args.instances_root, args.instance_id, args.operation_id)
382
+ print(json.dumps(result, sort_keys=True))
383
+ return 0
384
+ except (InstalledLifecycleError, OSError, PermissionError, ValueError) as error:
385
+ return _failure("server lifecycle-status", error)
319
386
  if args.command == "health":
320
387
  if args.data_root is None:
321
388
  return _failure("health snapshot", ValueError("--data-root is required for health snapshot"))
@@ -0,0 +1,458 @@
1
+ """Product-owned preserve/purge/restore semantics for Forge Server Runtime.
2
+
3
+ This contract extends, but never changes, the destructive
4
+ ``forge-server-runtime-lifecycle/v1`` uninstall boundary. Deployment-owned
5
+ service definitions and immutable runtime slots remain outside Forge's mutable
6
+ instance-data ownership.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import asdict, dataclass
11
+ from hashlib import sha256
12
+ import os
13
+ from pathlib import Path
14
+ import stat
15
+ from typing import Any, Callable, Mapping
16
+
17
+ from .installed_lifecycle import (
18
+ CONTROL_DIRECTORY,
19
+ InstalledLifecycleError,
20
+ InstalledUninstallDispatcher,
21
+ UninstallRequest,
22
+ _assert_identity,
23
+ _assert_no_symlink_components,
24
+ _assert_quiescent,
25
+ _atomic_json,
26
+ _digest_bytes,
27
+ _exclusive_locks,
28
+ _json_bytes,
29
+ _read_json,
30
+ _read_regular_bytes,
31
+ _runtime_snapshot,
32
+ _validate_identifier,
33
+ _DIGEST,
34
+ _SOURCE,
35
+ )
36
+
37
+
38
+ INSTANCE_LIFECYCLE_CONTRACT = "forge-server-instance-lifecycle/v1"
39
+ LIFECYCLE_DIRECTORY = "instance-lifecycle-v1"
40
+ PROVIDER_AUTH_PRESERVED = "PRESERVED_REQUIRES_REVERIFICATION"
41
+
42
+
43
+ def _content_tree_digest(root: Path) -> str:
44
+ """Bind every regular byte in one exact, link-free instance tree."""
45
+ _assert_no_symlink_components(root)
46
+ entries: list[dict[str, object]] = []
47
+ for directory, names, files in os.walk(root, topdown=True, followlinks=False):
48
+ parent = Path(directory)
49
+ for name in sorted(names):
50
+ path = parent / name
51
+ info = path.lstat()
52
+ if stat.S_ISLNK(info.st_mode) or not stat.S_ISDIR(info.st_mode):
53
+ raise InstalledLifecycleError(f"instance tree contains an unsafe directory entry: {path}")
54
+ entries.append({
55
+ "path": str(path.relative_to(root)),
56
+ "kind": "DIRECTORY",
57
+ "mode": stat.S_IMODE(info.st_mode),
58
+ })
59
+ for name in sorted(files):
60
+ path = parent / name
61
+ info = path.lstat()
62
+ if stat.S_ISLNK(info.st_mode) or not stat.S_ISREG(info.st_mode):
63
+ raise InstalledLifecycleError(f"instance tree contains an unsafe file entry: {path}")
64
+ payload = _read_regular_bytes(path)
65
+ entries.append({
66
+ "path": str(path.relative_to(root)),
67
+ "kind": "FILE",
68
+ "mode": stat.S_IMODE(info.st_mode),
69
+ "size": len(payload),
70
+ "digest": _digest_bytes(payload),
71
+ })
72
+ return _digest_bytes(_json_bytes(entries))
73
+
74
+
75
+ @dataclass(frozen=True)
76
+ class InstanceLifecycleRequest:
77
+ operation_id: str
78
+ instance_id: str
79
+ runtime_id: str
80
+ installation_id: str
81
+ installed_version: str
82
+ installed_source: str
83
+ installed_artifact_digest: str
84
+ data_root: str
85
+ instances_root: str
86
+
87
+ def validate(self) -> None:
88
+ for label, value in (
89
+ ("operation identity", self.operation_id),
90
+ ("instance identity", self.instance_id),
91
+ ("runtime identity", self.runtime_id),
92
+ ("installation identity", self.installation_id),
93
+ ):
94
+ _validate_identifier(label, value)
95
+ if self.instance_id != self.runtime_id:
96
+ raise InstalledLifecycleError("deployment instance does not bind the selected Forge runtime")
97
+ if _SOURCE.fullmatch(self.installed_source) is None or _DIGEST.fullmatch(self.installed_artifact_digest) is None:
98
+ raise InstalledLifecycleError("installed Forge artifact identity is invalid")
99
+ if not isinstance(self.installed_version, str) or not self.installed_version:
100
+ raise InstalledLifecycleError("installed Forge version is invalid")
101
+ data_root, instances_root = Path(self.data_root), Path(self.instances_root)
102
+ if not data_root.is_absolute() or not instances_root.is_absolute():
103
+ raise InstalledLifecycleError("lifecycle paths must be absolute")
104
+ _assert_no_symlink_components(instances_root)
105
+ _assert_no_symlink_components(data_root, allow_missing=True)
106
+ if data_root.parent != instances_root or data_root.name == CONTROL_DIRECTORY:
107
+ raise InstalledLifecycleError("data root is not one direct managed instance root")
108
+
109
+ @property
110
+ def digest(self) -> str:
111
+ return _digest_bytes(_json_bytes(asdict(self)))
112
+
113
+
114
+ @dataclass(frozen=True)
115
+ class RestoreRequest(InstanceLifecycleRequest):
116
+ preserve_operation_id: str
117
+
118
+ def validate(self) -> None:
119
+ super().validate()
120
+ _validate_identifier("preserve operation identity", self.preserve_operation_id)
121
+
122
+
123
+ class _LifecycleOperation:
124
+ operation: str
125
+
126
+ def __init__(
127
+ self,
128
+ request: InstanceLifecycleRequest,
129
+ *,
130
+ interrupt_after: str | None = None,
131
+ clock: Callable[[], str] | None = None,
132
+ ) -> None:
133
+ request.validate()
134
+ self.request = request
135
+ self.data_root = Path(request.data_root)
136
+ self.instances_root = Path(request.instances_root)
137
+ identity = sha256(request.instance_id.encode()).hexdigest()
138
+ self.control_root = self.instances_root / CONTROL_DIRECTORY / identity
139
+ self.lifecycle_root = self.control_root / LIFECYCLE_DIRECTORY
140
+ self.operation_root = self.lifecycle_root / "operations" / request.operation_id
141
+ self.state_path = self.operation_root / "state.json"
142
+ self.receipt_path = self.operation_root / "receipt.json"
143
+ self.purge_tombstone = self.lifecycle_root / "purged.json"
144
+ self.lifecycle_lock = self.control_root / "lifecycle.lock"
145
+ self.interrupt_after = interrupt_after
146
+ if clock is None:
147
+ from .installed_lifecycle import _now
148
+ clock = _now
149
+ self.clock = clock
150
+
151
+ def _interrupt(self, phase: str) -> None:
152
+ if self.interrupt_after == phase:
153
+ raise InterruptedError(f"interrupted after {phase}")
154
+
155
+ def _state(self) -> dict[str, Any]:
156
+ if self.state_path.exists():
157
+ state = _read_json(self.state_path)
158
+ if (
159
+ state.get("contract") != INSTANCE_LIFECYCLE_CONTRACT
160
+ or state.get("operation") != self.operation
161
+ or state.get("request") != asdict(self.request)
162
+ or state.get("request_digest") != self.request.digest
163
+ ):
164
+ raise InstalledLifecycleError("operation identity already belongs to another lifecycle request")
165
+ return state
166
+ state = {
167
+ "contract": INSTANCE_LIFECYCLE_CONTRACT,
168
+ "operation": self.operation,
169
+ "request": asdict(self.request),
170
+ "request_digest": self.request.digest,
171
+ "phase": "PREPARED",
172
+ "prepared_at": self.clock(),
173
+ }
174
+ _atomic_json(self.state_path, state)
175
+ return state
176
+
177
+ def _save(self, state: Mapping[str, Any], phase: str, **evidence: object) -> dict[str, Any]:
178
+ updated = {**state, **evidence, "phase": phase, "updated_at": self.clock()}
179
+ _atomic_json(self.state_path, updated)
180
+ return updated
181
+
182
+ def _write_receipt(self, state: Mapping[str, Any], payload: Mapping[str, object]) -> dict[str, Any]:
183
+ receipt = {
184
+ "contract": INSTANCE_LIFECYCLE_CONTRACT,
185
+ "operation": self.operation,
186
+ "operation_id": self.request.operation_id,
187
+ "instance_id": self.request.instance_id,
188
+ "runtime_id": self.request.runtime_id,
189
+ "installation_id": self.request.installation_id,
190
+ "request_digest": self.request.digest,
191
+ "selected_artifact": {
192
+ "version": self.request.installed_version,
193
+ "source_revision": self.request.installed_source,
194
+ "artifact_digest": self.request.installed_artifact_digest,
195
+ },
196
+ "state": "COMPLETE",
197
+ **payload,
198
+ "completed_at": self.clock(),
199
+ }
200
+ receipt["receipt_digest"] = _digest_bytes(_json_bytes(receipt))
201
+ if self.receipt_path.exists():
202
+ existing = _read_json(self.receipt_path)
203
+ unsigned = {key: value for key, value in existing.items() if key != "receipt_digest"}
204
+ if (
205
+ existing.get("contract") != INSTANCE_LIFECYCLE_CONTRACT
206
+ or existing.get("operation") != self.operation
207
+ or existing.get("request_digest") != self.request.digest
208
+ or existing.get("receipt_digest") != _digest_bytes(_json_bytes(unsigned))
209
+ ):
210
+ raise InstalledLifecycleError("terminal lifecycle receipt conflicts with existing evidence")
211
+ receipt = existing
212
+ else:
213
+ _atomic_json(self.receipt_path, receipt)
214
+ self._save(state, "COMPLETE", receipt_digest=receipt["receipt_digest"])
215
+ return receipt
216
+
217
+ def _terminal_receipt(self, state: Mapping[str, Any]) -> dict[str, Any]:
218
+ receipt = _read_json(self.receipt_path)
219
+ unsigned = {key: value for key, value in receipt.items() if key != "receipt_digest"}
220
+ if (
221
+ receipt.get("contract") != INSTANCE_LIFECYCLE_CONTRACT
222
+ or receipt.get("operation") != self.operation
223
+ or receipt.get("request_digest") != self.request.digest
224
+ or receipt.get("selected_artifact") != {
225
+ "version": self.request.installed_version,
226
+ "source_revision": self.request.installed_source,
227
+ "artifact_digest": self.request.installed_artifact_digest,
228
+ }
229
+ or receipt.get("state") != "COMPLETE"
230
+ or receipt.get("receipt_digest") != _digest_bytes(_json_bytes(unsigned))
231
+ or state.get("receipt_digest") != receipt.get("receipt_digest")
232
+ ):
233
+ raise InstalledLifecycleError("terminal lifecycle receipt is inconsistent")
234
+ return receipt
235
+
236
+ def _runtime_locks(self) -> tuple[Path, ...]:
237
+ return (
238
+ self.data_root / "locks" / "forge-server-runtime.lock",
239
+ self.data_root / "forge-mission-controller.lock",
240
+ self.data_root / "forge-runtime-mutation.lock",
241
+ self.data_root / "locks" / "runtime.lock",
242
+ )
243
+
244
+ def _verified_snapshot(self) -> tuple[dict[str, Any], str]:
245
+ if not self.data_root.is_dir():
246
+ raise InstalledLifecycleError("selected preserved instance data is unavailable")
247
+ with _exclusive_locks(self._runtime_locks()):
248
+ snapshot = _runtime_snapshot(self.data_root)
249
+ _assert_identity(
250
+ snapshot,
251
+ runtime_id=self.request.runtime_id,
252
+ installation_id=self.request.installation_id,
253
+ installed_version=self.request.installed_version,
254
+ )
255
+ _assert_quiescent(snapshot)
256
+ tree_digest = _content_tree_digest(self.data_root)
257
+ return snapshot, tree_digest
258
+
259
+
260
+ class InstalledPreserveDispatcher(_LifecycleOperation):
261
+ """Durably attest one quiescent instance as data-preserved."""
262
+
263
+ operation = "PRESERVE"
264
+
265
+ def run(self) -> dict[str, Any]:
266
+ self.lifecycle_root.mkdir(parents=True, exist_ok=True)
267
+ os.chmod(self.lifecycle_root, 0o700)
268
+ with _exclusive_locks((self.lifecycle_lock,)):
269
+ if self.purge_tombstone.exists():
270
+ raise InstalledLifecycleError("instance identity was permanently purged")
271
+ state = self._state()
272
+ if state.get("phase") == "COMPLETE":
273
+ receipt = self._terminal_receipt(state)
274
+ _snapshot, current = self._verified_snapshot()
275
+ if current != receipt.get("mutable_instance_data_digest"):
276
+ raise InstalledLifecycleError("preserved instance data changed after terminal preserve")
277
+ return receipt
278
+ snapshot, tree_digest = self._verified_snapshot()
279
+ state = self._save(
280
+ state,
281
+ "VERIFIED",
282
+ runtime_snapshot_digest=snapshot["digest"],
283
+ mutable_instance_data_digest=tree_digest,
284
+ )
285
+ self._interrupt("verified")
286
+ return self._write_receipt(state, {
287
+ "lifecycle_state": "UNINSTALLED_DATA_PRESERVED",
288
+ "instance_identity": "PRESERVED",
289
+ "mutable_instance_data": "PRESERVED",
290
+ "mutable_instance_data_digest": tree_digest,
291
+ "restorable": True,
292
+ "service_state": "REMOVED_OR_INACTIVE",
293
+ "service_definition": "DEPLOYMENT_OWNER",
294
+ "immutable_runtime_slots": "PRESERVED",
295
+ "provider_auth_state": PROVIDER_AUTH_PRESERVED,
296
+ })
297
+
298
+
299
+ class InstalledRestoreDispatcher(_LifecycleOperation):
300
+ """Validate preserved Forge data for same-identity deployment-owner restore."""
301
+
302
+ operation = "RESTORE"
303
+
304
+ @property
305
+ def restore_request(self) -> RestoreRequest:
306
+ if not isinstance(self.request, RestoreRequest):
307
+ raise InstalledLifecycleError("restore request type is invalid")
308
+ return self.request
309
+
310
+ def _preserve_receipt(self) -> dict[str, Any]:
311
+ preserve_root = self.lifecycle_root / "operations" / self.restore_request.preserve_operation_id
312
+ receipt = _read_json(preserve_root / "receipt.json")
313
+ unsigned = {key: value for key, value in receipt.items() if key != "receipt_digest"}
314
+ if (
315
+ receipt.get("contract") != INSTANCE_LIFECYCLE_CONTRACT
316
+ or receipt.get("operation") != "PRESERVE"
317
+ or receipt.get("instance_id") != self.request.instance_id
318
+ or receipt.get("runtime_id") != self.request.runtime_id
319
+ or receipt.get("installation_id") != self.request.installation_id
320
+ or receipt.get("selected_artifact") != {
321
+ "version": self.request.installed_version,
322
+ "source_revision": self.request.installed_source,
323
+ "artifact_digest": self.request.installed_artifact_digest,
324
+ }
325
+ or receipt.get("lifecycle_state") != "UNINSTALLED_DATA_PRESERVED"
326
+ or receipt.get("restorable") is not True
327
+ or receipt.get("receipt_digest") != _digest_bytes(_json_bytes(unsigned))
328
+ ):
329
+ raise InstalledLifecycleError("preserve evidence does not authorize same-identity restore")
330
+ return receipt
331
+
332
+ def run(self) -> dict[str, Any]:
333
+ self.lifecycle_root.mkdir(parents=True, exist_ok=True)
334
+ os.chmod(self.lifecycle_root, 0o700)
335
+ with _exclusive_locks((self.lifecycle_lock,)):
336
+ if self.purge_tombstone.exists():
337
+ raise InstalledLifecycleError("purged instance identity cannot be restored")
338
+ state = self._state()
339
+ if state.get("phase") == "COMPLETE":
340
+ return self._terminal_receipt(state)
341
+ preserve = self._preserve_receipt()
342
+ snapshot, tree_digest = self._verified_snapshot()
343
+ if tree_digest != preserve.get("mutable_instance_data_digest"):
344
+ raise InstalledLifecycleError("preserved instance data no longer matches restore evidence")
345
+ state = self._save(
346
+ state,
347
+ "VERIFIED",
348
+ runtime_snapshot_digest=snapshot["digest"],
349
+ preserve_operation_id=self.restore_request.preserve_operation_id,
350
+ preserve_receipt_digest=preserve["receipt_digest"],
351
+ mutable_instance_data_digest=tree_digest,
352
+ )
353
+ self._interrupt("verified")
354
+ return self._write_receipt(state, {
355
+ "lifecycle_state": "RESTORE_VALIDATED",
356
+ "instance_identity": "PRESERVED",
357
+ "mutable_instance_data": "PRESERVED",
358
+ "mutable_instance_data_digest": tree_digest,
359
+ "restored_from_preserve_operation": self.restore_request.preserve_operation_id,
360
+ "restored": True,
361
+ "service_state": "DEPLOYMENT_OWNER_REINSTALL_REQUIRED",
362
+ "service_definition": "DEPLOYMENT_OWNER",
363
+ "immutable_runtime_slots": "PRESERVED",
364
+ "provider_auth_state": PROVIDER_AUTH_PRESERVED,
365
+ "ready": False,
366
+ })
367
+
368
+
369
+ class InstalledPurgeDispatcher(_LifecycleOperation):
370
+ """Explicit permanent purge projection over the legacy destructive uninstall."""
371
+
372
+ operation = "PURGE"
373
+
374
+ def run(self) -> dict[str, Any]:
375
+ self.lifecycle_root.mkdir(parents=True, exist_ok=True)
376
+ os.chmod(self.lifecycle_root, 0o700)
377
+ with _exclusive_locks((self.lifecycle_lock,)):
378
+ state = self._state()
379
+ if state.get("phase") == "COMPLETE":
380
+ receipt = self._terminal_receipt(state)
381
+ if self.data_root.exists() or self.data_root.is_symlink():
382
+ raise InstalledLifecycleError("purged instance target was recreated")
383
+ return receipt
384
+
385
+ uninstall = InstalledUninstallDispatcher(UninstallRequest(
386
+ operation_id=self.request.operation_id,
387
+ instance_id=self.request.instance_id,
388
+ runtime_id=self.request.runtime_id,
389
+ installation_id=self.request.installation_id,
390
+ data_root=self.request.data_root,
391
+ instances_root=self.request.instances_root,
392
+ )).run()
393
+ self._interrupt("uninstalled")
394
+
395
+ with _exclusive_locks((self.lifecycle_lock,)):
396
+ state = self._state()
397
+ if self.data_root.exists() or self.data_root.is_symlink():
398
+ raise InstalledLifecycleError("destructive uninstall did not remove selected instance data")
399
+ tombstone = {
400
+ "contract": INSTANCE_LIFECYCLE_CONTRACT,
401
+ "instance_id": self.request.instance_id,
402
+ "lifecycle_state": "PURGED",
403
+ "restorable": False,
404
+ "purge_operation_id": self.request.operation_id,
405
+ "uninstall_receipt_digest": uninstall["receipt_digest"],
406
+ }
407
+ tombstone["tombstone_digest"] = _digest_bytes(_json_bytes(tombstone))
408
+ if self.purge_tombstone.exists():
409
+ if _read_json(self.purge_tombstone) != tombstone:
410
+ raise InstalledLifecycleError("instance purge tombstone conflicts with prior evidence")
411
+ else:
412
+ _atomic_json(self.purge_tombstone, tombstone)
413
+ return self._write_receipt(state, {
414
+ "lifecycle_state": "PURGED",
415
+ "instance_identity": "RETIRED",
416
+ "mutable_instance_data": "REMOVED",
417
+ "restorable": False,
418
+ "service_state": "DEPLOYMENT_OWNER_REMOVAL_REQUIRED_OR_COMPLETE",
419
+ "service_definition": "DEPLOYMENT_OWNER",
420
+ "immutable_runtime_slots": "PRESERVED",
421
+ "provider_auth_state": "REMOVED_WITH_INSTANCE_DATA",
422
+ "legacy_uninstall_receipt_digest": uninstall["receipt_digest"],
423
+ "purge_tombstone_digest": tombstone["tombstone_digest"],
424
+ })
425
+
426
+
427
+ def lifecycle_status(instances_root: str, instance_id: str, operation_id: str) -> dict[str, Any]:
428
+ """Read one exact product-owned preserve/purge/restore operation."""
429
+ _validate_identifier("instance identity", instance_id)
430
+ _validate_identifier("operation identity", operation_id)
431
+ root = Path(instances_root)
432
+ if not root.is_absolute():
433
+ raise InstalledLifecycleError("instances root must be absolute")
434
+ _assert_no_symlink_components(root)
435
+ identity = sha256(instance_id.encode()).hexdigest()
436
+ operation_root = root / CONTROL_DIRECTORY / identity / LIFECYCLE_DIRECTORY / "operations" / operation_id
437
+ state = _read_json(operation_root / "state.json")
438
+ result = {
439
+ "contract": state.get("contract"),
440
+ "operation": state.get("operation"),
441
+ "operation_id": operation_id,
442
+ "instance_id": instance_id,
443
+ "phase": state.get("phase"),
444
+ "request_digest": state.get("request_digest"),
445
+ "state": "IN_PROGRESS",
446
+ }
447
+ if state.get("phase") == "COMPLETE":
448
+ receipt = _read_json(operation_root / "receipt.json")
449
+ unsigned = {key: value for key, value in receipt.items() if key != "receipt_digest"}
450
+ if receipt.get("receipt_digest") != _digest_bytes(_json_bytes(unsigned)):
451
+ raise InstalledLifecycleError("terminal lifecycle receipt is inconsistent")
452
+ result.update({
453
+ "state": "COMPLETE",
454
+ "lifecycle_state": receipt.get("lifecycle_state"),
455
+ "receipt_digest": receipt.get("receipt_digest"),
456
+ "restorable": receipt.get("restorable"),
457
+ })
458
+ return result
@@ -27,6 +27,13 @@ from forge.installed_lifecycle import (
27
27
  UpdateAssessmentRequest,
28
28
  assess_update,
29
29
  )
30
+ from forge.preserved_lifecycle import (
31
+ InstalledPreserveDispatcher,
32
+ InstalledPurgeDispatcher,
33
+ InstalledRestoreDispatcher,
34
+ InstanceLifecycleRequest,
35
+ RestoreRequest,
36
+ )
30
37
  from forge.models import (
31
38
  ExecutionEvidenceOutcome,
32
39
  ExecutionRequest,
@@ -239,6 +246,9 @@ def run(*, candidate_wheel: Path | None = None, source_revision: str | None = No
239
246
  "update_assessment": "NOT_REQUESTED",
240
247
  "uninstall_dispatcher": "PASS",
241
248
  "uninstall_replay": "PASS",
249
+ "preserve": "NOT_REQUESTED",
250
+ "restore": "NOT_REQUESTED",
251
+ "purge": "NOT_REQUESTED",
242
252
  }
243
253
  if candidate_wheel is not None:
244
254
  if source_revision is None or len(source_revision) != 40:
@@ -254,6 +264,71 @@ def run(*, candidate_wheel: Path | None = None, source_revision: str | None = No
254
264
  if assessment.get("state") != "UP_TO_DATE" or assessment.get("mutating") is not False:
255
265
  raise RuntimeError("installed lifecycle update assessment did not bind the exact artifact")
256
266
  lifecycle["update_assessment"] = "PASS"
267
+
268
+ preserve_request = InstanceLifecycleRequest(
269
+ operation_id="forge-server-runtime-installed-preserve",
270
+ instance_id=identities[0],
271
+ runtime_id=identities[0],
272
+ installation_id=installation_ids[0],
273
+ installed_version=version,
274
+ installed_source=source_revision,
275
+ installed_artifact_digest=wheel_digest,
276
+ data_root=str(roots[0]),
277
+ instances_root=str(root.resolve()),
278
+ )
279
+ preserve = InstalledPreserveDispatcher(preserve_request).run()
280
+ if (
281
+ preserve.get("lifecycle_state") != "UNINSTALLED_DATA_PRESERVED"
282
+ or preserve.get("instance_identity") != "PRESERVED"
283
+ or preserve.get("mutable_instance_data") != "PRESERVED"
284
+ or preserve.get("restorable") is not True
285
+ or preserve.get("provider_auth_state") != "PRESERVED_REQUIRES_REVERIFICATION"
286
+ or not roots[0].is_dir()
287
+ ):
288
+ raise RuntimeError("installed lifecycle preserve semantics are unavailable")
289
+ lifecycle["preserve"] = "PASS"
290
+
291
+ restore_request = RestoreRequest(
292
+ operation_id="forge-server-runtime-installed-restore",
293
+ instance_id=identities[0],
294
+ runtime_id=identities[0],
295
+ installation_id=installation_ids[0],
296
+ installed_version=version,
297
+ installed_source=source_revision,
298
+ installed_artifact_digest=wheel_digest,
299
+ data_root=str(roots[0]),
300
+ instances_root=str(root.resolve()),
301
+ preserve_operation_id="forge-server-runtime-installed-preserve",
302
+ )
303
+ restored = InstalledRestoreDispatcher(restore_request).run()
304
+ if (
305
+ restored.get("lifecycle_state") != "RESTORE_VALIDATED"
306
+ or restored.get("instance_identity") != "PRESERVED"
307
+ or restored.get("provider_auth_state") != "PRESERVED_REQUIRES_REVERIFICATION"
308
+ or restored.get("ready") is not False
309
+ ):
310
+ raise RuntimeError("installed lifecycle restore semantics are unavailable")
311
+ lifecycle["restore"] = "PASS"
312
+
313
+ purge_request = InstanceLifecycleRequest(
314
+ operation_id="forge-server-runtime-installed-purge",
315
+ instance_id=identities[0],
316
+ runtime_id=identities[0],
317
+ installation_id=installation_ids[0],
318
+ installed_version=version,
319
+ installed_source=source_revision,
320
+ installed_artifact_digest=wheel_digest,
321
+ data_root=str(roots[0]),
322
+ instances_root=str(root.resolve()),
323
+ )
324
+ purged = InstalledPurgeDispatcher(purge_request).run()
325
+ if (
326
+ purged.get("lifecycle_state") != "PURGED"
327
+ or purged.get("restorable") is not False
328
+ or roots[0].exists()
329
+ ):
330
+ raise RuntimeError("installed lifecycle purge semantics are unavailable")
331
+ lifecycle["purge"] = "PASS"
257
332
  uninstall_request = UninstallRequest(
258
333
  operation_id="forge-server-runtime-installed-uninstall",
259
334
  instance_id=identities[1], runtime_id=identities[1], installation_id=installation_ids[1],
@@ -321,6 +396,9 @@ def run(*, candidate_wheel: Path | None = None, source_revision: str | None = No
321
396
  "lifecycle_update_assessment": lifecycle["update_assessment"],
322
397
  "lifecycle_uninstall_dispatcher": lifecycle["uninstall_dispatcher"],
323
398
  "lifecycle_uninstall_replay": lifecycle["uninstall_replay"],
399
+ "lifecycle_preserve": lifecycle["preserve"],
400
+ "lifecycle_restore": lifecycle["restore"],
401
+ "lifecycle_purge": lifecycle["purge"],
324
402
  }
325
403
 
326
404
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forge-autonomy
3
- Version: 2.7.35
3
+ Version: 2.7.36
4
4
  Summary: Forge mission planning, producer contracts, and evidence interpretation runtime.
5
5
  Author: Forge maintainers
6
6
  License-Expression: Apache-2.0
@@ -25,6 +25,7 @@ forge/mission_scheduler.py
25
25
  forge/operations_read_api.py
26
26
  forge/operator_identity.py
27
27
  forge/portfolio_intelligence.py
28
+ forge/preserved_lifecycle.py
28
29
  forge/programme_authorization.py
29
30
  forge/provider_context.py
30
31
  forge/provider_security.py
@@ -303,6 +304,7 @@ tests/test_operator_identity.py
303
304
  tests/test_phase_completion.py
304
305
  tests/test_platform_contract_documents.py
305
306
  tests/test_portfolio_intelligence_operation.py
307
+ tests/test_preserved_lifecycle.py
306
308
  tests/test_producer_contract.py
307
309
  tests/test_product_version_operations.py
308
310
  tests/test_production_release_workflow.py