forge-autonomy 2.7.22__tar.gz → 2.7.24__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {forge_autonomy-2.7.22/forge_autonomy.egg-info → forge_autonomy-2.7.24}/PKG-INFO +12 -1
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/README.md +11 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/__main__.py +2 -1
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/runtime/operational_reset.py +108 -9
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24/forge_autonomy.egg-info}/PKG-INFO +12 -1
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge_autonomy.egg-info/SOURCES.txt +2 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/product-version.json +1 -1
- forge_autonomy-2.7.24/tests/test_existing_release_reconciliation.py +357 -0
- forge_autonomy-2.7.24/tests/test_installed_forge_update.py +694 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_operational_reset.py +150 -1
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_production_release_workflow.py +111 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/MANIFEST.in +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/_version.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/action_derivation_canary_closure.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/action_derivation_evidence.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/action_derivation_qualification.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/action_derivation_reattempt.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/ai_architect/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/ai_architect/provider_registry.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/architecture/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/architecture/advisor.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/architecture/presentation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/architecture/workspace.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/autonomous_orchestrator.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/business/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/business/advisor.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/business/ingress.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/business/presentation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/business/workspace.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/canary.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/capabilities/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/capabilities/delegation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/completion/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/completion/assessor.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/completion/mission.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/constitutional/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/constitutional/assessor.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/core/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/core/json_store.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/decision_evidence/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/decision_evidence/repository.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/dispatcher/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/dispatcher/dispatcher.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/execution/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/execution/loop.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/execution/presentation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/execution_host_configuration.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/foundation/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/foundation/loader.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/generation_two.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/generation_two_execution.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/governance/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/governance/execution_policy.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/governance/profiles.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/governance_authority.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/intake.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/integration/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/integration/coordinator.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/integration/repository.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/knowledge/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/knowledge/consumer.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/knowledge/registry.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/lifecycle/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/lifecycle/store.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/mission_amendment.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/mission_scheduler.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/action.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/action_derivation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/agent_policy.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/ai_architect_provider.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/ai_architect_session.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/ai_provider_registry.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/architecture_mission.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/architecture_reasoning.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/architecture_review.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/codex_runtime_prompt.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/constitutional_validation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/decision_evidence.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/execution_host.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/foundation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/historical_intent.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/integration.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/intent.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/intent_authoring.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/mission.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/mission_candidate.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/mission_completion.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/mission_planner.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/mission_recommendation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/phase_completion.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/planning.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/producer.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/prompt_artifact.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/proposal.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/runtime_prompt.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/models/solution_template.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/operator_identity.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/planner/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/planner/action_derivation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/planner/codex_cli_session.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/planner/durable_derivation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/planner/engine.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/planner/openai_responses.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/planner/provider_adapter.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/planning/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/planning/loader.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/planning/registry.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/portfolio_intelligence.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/programme_authorization.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/prompts/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/prompts/codex_cli.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/prompts/generator.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/proposals/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/proposals/generator.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/provider_security.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/qualification/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/qualification/bootstrap_sequence.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/qualification/generation_one.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/qualification/installed_smoke.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/recommendations/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/recommendations/engine.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/repository_truth.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/review/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/review/engine.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/runtime/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/runtime/bootstrap.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/runtime/data_root.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/runtime/database.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/runtime/dynamic_mission.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/runtime/evidence.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/runtime/execution_context.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/runtime/health.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/runtime/runner.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/runtime/service.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/scheduler/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/scheduler/adapter.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/scheduler/ep_http_adapter.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/scheduler/ep_v12.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/scheduler/scheduler.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/capability-registry-1.0.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/capability.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/common.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/decision-evidence-1.0.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/engineering-goal-0.5.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/engineering-increment-proposal-0.5.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/engineering-mode.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/engineering-plan-0.5.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/engineering-platform-peer-configuration-1.0.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/engineering-platform-peer-configuration-1.1.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/engineering-prompt-artifact-0.7.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/engineering-proposal-0.6.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/evidence-reference-0.5.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/foundation-document.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/governance-profile.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/knowledge-source-0.4.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/knowledge-source.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/mission-candidate-1.0.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/mission-candidate-1.1.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/phase-completion-1.0.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/planning-document-0.5.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/repository-catalog.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/repository.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/solution-template-1.0.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/schemas/workspace.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/secure_store.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/solutions/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/solutions/catalogue.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/solutions/generator.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/state/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/state/mission_state.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge/workspace/__init__.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge_autonomy.egg-info/dependency_links.txt +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge_autonomy.egg-info/entry_points.txt +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/forge_autonomy.egg-info/top_level.txt +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/pyproject.toml +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/capability-registry-1.0.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/capability.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/common.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/decision-evidence-1.0.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/engineering-goal-0.5.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/engineering-increment-proposal-0.5.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/engineering-mode.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/engineering-plan-0.5.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/engineering-prompt-artifact-0.7.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/engineering-proposal-0.6.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/evidence-reference-0.5.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/foundation-document.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/governance-profile.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/knowledge-source-0.4.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/knowledge-source.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/mission-candidate-1.0.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/mission-candidate-1.1.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/phase-completion-1.0.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/planning-document-0.5.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/repository-catalog.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/repository.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/solution-template-1.0.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/schemas/workspace.schema.json +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/setup.cfg +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_action_derivation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_action_derivation_canary_closure.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_action_derivation_evidence.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_action_derivation_qualification.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_adaptive_action_sizing_design.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_agent_policy.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_aggregate_health_roadmap.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_ai_architect_provider.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_ai_architect_session.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_ai_development_projection.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_ai_provider_registry.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_architecture_reasoning.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_architecture_review_engine.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_architecture_workspace.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_autonomous_orchestrator.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_bootstrap_adapter.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_bootstrap_mission_canary.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_bootstrap_mission_runner.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_bootstrap_mission_scheduler.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_bootstrap_sequence_qualification.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_business_governance_ingress.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_business_workspace.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_canonical_data_root.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_capability_delegation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_codex_cli_runtime_prompt.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_codex_cli_session.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_constitutional_validation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_decision_evidence.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_default_project_quality_policy.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_distribution_identity.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_durable_action_derivation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_dynamic_mission_capability.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_engineering_action_architecture.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_engineering_intent_authoring.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_engineering_intent_lifecycle.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_engineering_mission.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_engineering_planning.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_engineering_prompt_artifacts.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_engineering_proposals.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_ep_http_adapter.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_execution_context.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_execution_host_configuration.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_execution_host_contract.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_execution_loop.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_forge_console_admin_parity_contract.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_forge_console_configuration_contract.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_forge_console_hosting_contract.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_forge_operations_console_roadmap.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_forge_outer_loop_ci_roadmap.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_forge_v1_bootstrap_scheduler_contract.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_forge_v1_implementation_dag.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_foundation_loader.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_foundation_models.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_generation_one_bootstrap_qualification.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_generation_two_execution.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_generation_two_mission_intake.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_governance_authority.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_governed_coverage.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_historical_engineering_intent.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_inner_loop_ci_contract.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_installed_dynamic_mission_runtime.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_integration_coordination.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_knowledge_consumption.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_live_project_roadmap_contract.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_mission_completion_evaluator.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_mission_dispatcher.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_mission_planner.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_mission_recommendation_engine.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_mission_recommendation_lifecycle.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_mission_runtime_projection.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_mission_runtime_scheduler.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_mission_state_store.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_openai_action_derivation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_operator_identity.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_phase_completion.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_platform_contract_documents.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_portfolio_intelligence_operation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_producer_contract.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_product_version_operations.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_productization_reconciliation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_programme_authorization.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_provider_security.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_pypi_distribution_readback.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_release_operation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_repository_truth.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_role_aware_conversations_contract.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_runtime_bootstrap.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_runtime_database.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_runtime_evidence.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_runtime_health.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_runtime_instance_persistence.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_runtime_prompt_generation.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_runtime_service.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_runtime_status_projection.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_scheduler_dependency_boundary.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_schemas.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_secure_store.py +0 -0
- {forge_autonomy-2.7.22 → forge_autonomy-2.7.24}/tests/test_solution_templates.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: forge-autonomy
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.24
|
|
4
4
|
Summary: Forge mission planning, producer contracts, and evidence interpretation runtime.
|
|
5
5
|
Author: Forge maintainers
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -123,6 +123,17 @@ The exact instance-ID comparison is consistency evidence, not a newly invented
|
|
|
123
123
|
cryptographic peer identity; preflight reports the latter as `NOT_ASSERTED`.
|
|
124
124
|
`CONFIGURED` is therefore never presented as `LIVE_READY`.
|
|
125
125
|
|
|
126
|
+
## Bounded installed maintenance
|
|
127
|
+
|
|
128
|
+
The product-owned external controller for the selected Forge 2.7.21 to 2.7.22
|
|
129
|
+
installation transition is documented in the
|
|
130
|
+
[installed update runbook](docs/operations/FORGE_INSTALLED_UPDATE_RUNBOOK.md) and
|
|
131
|
+
[sanitized Forge 2.7.22 release/installation completion record](docs/operations/FORGE_2_7_22_RELEASE_INSTALLATION_COMPLETION.md).
|
|
132
|
+
It stages an exact qualified wheel, backs up and migrates the selected runtime,
|
|
133
|
+
and atomically activates a versioned slot. It is not packaged as a Forge
|
|
134
|
+
Runtime command and does not replace Forge Platform's normal installer/update
|
|
135
|
+
composition boundary.
|
|
136
|
+
|
|
126
137
|
## Operational-history reset
|
|
127
138
|
|
|
128
139
|
Forge schema 38 provides a bounded, product-owned maintenance service under
|
|
@@ -106,6 +106,17 @@ The exact instance-ID comparison is consistency evidence, not a newly invented
|
|
|
106
106
|
cryptographic peer identity; preflight reports the latter as `NOT_ASSERTED`.
|
|
107
107
|
`CONFIGURED` is therefore never presented as `LIVE_READY`.
|
|
108
108
|
|
|
109
|
+
## Bounded installed maintenance
|
|
110
|
+
|
|
111
|
+
The product-owned external controller for the selected Forge 2.7.21 to 2.7.22
|
|
112
|
+
installation transition is documented in the
|
|
113
|
+
[installed update runbook](docs/operations/FORGE_INSTALLED_UPDATE_RUNBOOK.md) and
|
|
114
|
+
[sanitized Forge 2.7.22 release/installation completion record](docs/operations/FORGE_2_7_22_RELEASE_INSTALLATION_COMPLETION.md).
|
|
115
|
+
It stages an exact qualified wheel, backs up and migrates the selected runtime,
|
|
116
|
+
and atomically activates a versioned slot. It is not packaged as a Forge
|
|
117
|
+
Runtime command and does not replace Forge Platform's normal installer/update
|
|
118
|
+
composition boundary.
|
|
119
|
+
|
|
109
120
|
## Operational-history reset
|
|
110
121
|
|
|
111
122
|
Forge schema 38 provides a bounded, product-owned maintenance service under
|
|
@@ -101,6 +101,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
101
101
|
prepare_reset.add_argument("--plan-digest", required=True)
|
|
102
102
|
prepare_reset.add_argument("--acknowledge-operational-fk", action="append", default=[])
|
|
103
103
|
for name, help_text in (
|
|
104
|
+
("revalidate", "revalidate the exact prepared operation without changing it"),
|
|
104
105
|
("apply", "apply the authorized destructive reset"),
|
|
105
106
|
("verify", "verify reset integrity and preserved bindings"),
|
|
106
107
|
):
|
|
@@ -179,7 +180,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
179
180
|
expected_plan_digest=args.plan_digest,
|
|
180
181
|
acknowledge_operational_fk=args.acknowledge_operational_fk,
|
|
181
182
|
)
|
|
182
|
-
elif args.reset_command in {"apply", "verify"}:
|
|
183
|
+
elif args.reset_command in {"revalidate", "apply", "verify"}:
|
|
183
184
|
result = getattr(service, args.reset_command)(
|
|
184
185
|
operation_id=args.operation_id, plan_digest=args.plan_digest,
|
|
185
186
|
request_digest=args.request_digest, backup_digest=args.backup_digest,
|
|
@@ -31,7 +31,8 @@ from .service import RuntimeServiceLock
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
RESET_PROFILE = "forge-operational-history-v1"
|
|
34
|
-
RESET_POLICY_VERSION = "
|
|
34
|
+
RESET_POLICY_VERSION = "2"
|
|
35
|
+
RESET_PLAN_CONTRACT = "forge-operational-reset-plan-1.1"
|
|
35
36
|
_OPERATION_ID = re.compile(r"[a-zA-Z0-9][a-zA-Z0-9._-]{7,127}\Z")
|
|
36
37
|
_DIGEST = re.compile(r"sha256:[0-9a-f]{64}\Z")
|
|
37
38
|
|
|
@@ -511,10 +512,18 @@ class ForgeOperationalResetService:
|
|
|
511
512
|
).fetchone()
|
|
512
513
|
if state is not None:
|
|
513
514
|
generation, active_operation = int(state[0]), state[1]
|
|
515
|
+
database_stat = self.database_path.stat()
|
|
516
|
+
database_binding = {
|
|
517
|
+
"path": str(self.database_path.resolve()),
|
|
518
|
+
"device": int(database_stat.st_dev),
|
|
519
|
+
"inode": int(database_stat.st_ino),
|
|
520
|
+
}
|
|
514
521
|
target = {
|
|
515
522
|
"product": "forge", "runtime_id": runtime_id,
|
|
516
523
|
"data_root": str(self.data_root), "database": str(self.database_path),
|
|
517
|
-
"database_binding_digest": _digest(
|
|
524
|
+
"database_binding_digest": _digest(database_binding),
|
|
525
|
+
"database_device": database_binding["device"],
|
|
526
|
+
"database_inode": database_binding["inode"],
|
|
518
527
|
"marker_digest": _file_digest(self.marker_path), "schema_version": schema,
|
|
519
528
|
"dataset_generation": generation,
|
|
520
529
|
}
|
|
@@ -580,8 +589,12 @@ class ForgeOperationalResetService:
|
|
|
580
589
|
and active_permits == 0
|
|
581
590
|
)
|
|
582
591
|
source_revision = self._source_revision()
|
|
592
|
+
semantic_classifications = {
|
|
593
|
+
category: entries for category, entries in classifications.items()
|
|
594
|
+
if category != "MAINTENANCE_AUDIT"
|
|
595
|
+
}
|
|
583
596
|
plan_core = {
|
|
584
|
-
"contract":
|
|
597
|
+
"contract": RESET_PLAN_CONTRACT, "profile": RESET_PROFILE,
|
|
585
598
|
"policy_version": RESET_POLICY_VERSION, "product_version": canonical_version(),
|
|
586
599
|
"source_revision": source_revision,
|
|
587
600
|
"source_revision_kind": (
|
|
@@ -590,7 +603,8 @@ class ForgeOperationalResetService:
|
|
|
590
603
|
),
|
|
591
604
|
"implementation_digest": self._implementation_digest(),
|
|
592
605
|
"target": target,
|
|
593
|
-
"relevant_revision": relevant_revision,
|
|
606
|
+
"relevant_revision": relevant_revision,
|
|
607
|
+
"classifications": semantic_classifications,
|
|
594
608
|
"preserved": preserved, "effect_set": effect_set,
|
|
595
609
|
"effect_set_digest": _digest(effect_set), "foreign_key_issues": foreign_keys,
|
|
596
610
|
"required_fk_acknowledgements": [item["issue_id"] for item in operational_fk],
|
|
@@ -601,7 +615,7 @@ class ForgeOperationalResetService:
|
|
|
601
615
|
"preserved_external_digest": _digest(preserved_external),
|
|
602
616
|
"unknown_external": unknown_external,
|
|
603
617
|
"unknown_tables": unknown_tables, "missing_tables": missing_tables,
|
|
604
|
-
"
|
|
618
|
+
"no_op": no_op,
|
|
605
619
|
"namespace": {
|
|
606
620
|
"repository_document": "missions/MISSION-0003.md",
|
|
607
621
|
"repository_document_kind": "HISTORICAL_REPOSITORY_DOCUMENT",
|
|
@@ -610,7 +624,17 @@ class ForgeOperationalResetService:
|
|
|
610
624
|
},
|
|
611
625
|
}
|
|
612
626
|
return {
|
|
613
|
-
**plan_core,
|
|
627
|
+
**plan_core,
|
|
628
|
+
# Availability findings remain visible and fail closed, but are
|
|
629
|
+
# not part of the approved semantic effect plan. In particular,
|
|
630
|
+
# the owning prepare transition necessarily makes maintenance
|
|
631
|
+
# active without redefining the reset that was authorized.
|
|
632
|
+
"blockers": blockers,
|
|
633
|
+
"plan_digest": _digest(plan_core),
|
|
634
|
+
"maintenance_observation": {
|
|
635
|
+
"active_operation_id": active_operation,
|
|
636
|
+
"tables": classifications["MAINTENANCE_AUDIT"],
|
|
637
|
+
},
|
|
614
638
|
"external_control_entries": external_controls,
|
|
615
639
|
}
|
|
616
640
|
|
|
@@ -999,8 +1023,79 @@ class ForgeOperationalResetService:
|
|
|
999
1023
|
raise OperationalResetError("source revision changed after authorization")
|
|
1000
1024
|
if row["implementation_digest"] != self._implementation_digest():
|
|
1001
1025
|
raise OperationalResetError("maintenance service revision changed after authorization")
|
|
1026
|
+
actor_reference, authority_digest = self._operator_authority(connection)
|
|
1027
|
+
if (
|
|
1028
|
+
row["actor_reference"] != actor_reference
|
|
1029
|
+
or row["authority_digest"] != authority_digest
|
|
1030
|
+
):
|
|
1031
|
+
raise OperationalResetError("operator or reset authority changed after authorization")
|
|
1002
1032
|
return row
|
|
1003
1033
|
|
|
1034
|
+
def _pre_apply_revalidation(
|
|
1035
|
+
self, connection: sqlite3.Connection, operation: sqlite3.Row,
|
|
1036
|
+
) -> dict[str, Any]:
|
|
1037
|
+
"""Re-prove one prepared operation without redefining its approved plan."""
|
|
1038
|
+
if operation["state"] != "BACKUP_VERIFIED":
|
|
1039
|
+
raise OperationalResetError("operation is not in the prepared revalidation phase")
|
|
1040
|
+
operation_id = str(operation["operation_id"])
|
|
1041
|
+
document = json.loads(operation["document"])
|
|
1042
|
+
plan = document["plan"]
|
|
1043
|
+
current = self._inventory(connection)
|
|
1044
|
+
expected_maintenance = [{
|
|
1045
|
+
"code": "MAINTENANCE_ALREADY_ACTIVE", "operation_id": operation_id,
|
|
1046
|
+
}]
|
|
1047
|
+
if current["blockers"] != expected_maintenance:
|
|
1048
|
+
raise OperationalResetError("prepared operation has new or missing blocking findings")
|
|
1049
|
+
if current["target"] != plan["target"]:
|
|
1050
|
+
raise OperationalResetError("prepared operation target or database binding changed")
|
|
1051
|
+
if current["relevant_revision"] != operation["relevant_revision"]:
|
|
1052
|
+
raise OperationalResetError("meaningful source data changed after the approved preview")
|
|
1053
|
+
for key in (
|
|
1054
|
+
"effect_set_digest", "external_inventory_digest", "preserved_external_digest",
|
|
1055
|
+
"required_fk_acknowledgements", "foreign_key_issues", "policy_version",
|
|
1056
|
+
"source_revision", "implementation_digest",
|
|
1057
|
+
):
|
|
1058
|
+
if current[key] != plan[key]:
|
|
1059
|
+
raise OperationalResetError(f"prepared operation binding changed: {key}")
|
|
1060
|
+
if int(current["target"]["dataset_generation"]) != int(operation["generation_before"]):
|
|
1061
|
+
raise OperationalResetError("dataset generation changed after authorization")
|
|
1062
|
+
evidence = {
|
|
1063
|
+
"operation_id": operation_id,
|
|
1064
|
+
"state": str(operation["state"]),
|
|
1065
|
+
"writer_fence_owner": operation_id,
|
|
1066
|
+
"actor_reference": str(operation["actor_reference"]),
|
|
1067
|
+
"authority_digest": str(operation["authority_digest"]),
|
|
1068
|
+
"target_binding_digest": str(plan["target"]["database_binding_digest"]),
|
|
1069
|
+
"plan_digest": str(operation["plan_digest"]),
|
|
1070
|
+
"request_digest": str(operation["request_digest"]),
|
|
1071
|
+
"effect_set_digest": str(plan["effect_set_digest"]),
|
|
1072
|
+
"relevant_revision_digest": str(operation["relevant_revision"]),
|
|
1073
|
+
"backup_digest": str(operation["backup_digest"]),
|
|
1074
|
+
"dataset_generation": int(operation["generation_before"]),
|
|
1075
|
+
"source_revision": str(operation["source_revision"]),
|
|
1076
|
+
"implementation_digest": str(operation["implementation_digest"]),
|
|
1077
|
+
}
|
|
1078
|
+
evidence["revalidation_digest"] = _digest(evidence)
|
|
1079
|
+
return evidence
|
|
1080
|
+
|
|
1081
|
+
def revalidate(
|
|
1082
|
+
self, *, operation_id: str, plan_digest: str, request_digest: str,
|
|
1083
|
+
backup_digest: str,
|
|
1084
|
+
) -> dict[str, Any]:
|
|
1085
|
+
"""Read-only proof for the already prepared, exact owning operation."""
|
|
1086
|
+
with self._lock.acquire():
|
|
1087
|
+
with self._connect(read_only=True) as connection:
|
|
1088
|
+
row = self._bound_operation(
|
|
1089
|
+
connection, operation_id, plan_digest=plan_digest,
|
|
1090
|
+
request_digest=request_digest, backup_digest=backup_digest,
|
|
1091
|
+
)
|
|
1092
|
+
evidence = self._pre_apply_revalidation(connection, row)
|
|
1093
|
+
self._verify_operation_backup(row)
|
|
1094
|
+
receipt = self._operation_receipt(operation_id)
|
|
1095
|
+
receipt["revalidation"] = evidence
|
|
1096
|
+
receipt["execution_allowed"] = True
|
|
1097
|
+
return receipt
|
|
1098
|
+
|
|
1004
1099
|
@staticmethod
|
|
1005
1100
|
def _tombstone_values(connection: sqlite3.Connection) -> list[tuple[str, str]]:
|
|
1006
1101
|
queries: tuple[tuple[str, str], ...] = (
|
|
@@ -1036,6 +1131,7 @@ class ForgeOperationalResetService:
|
|
|
1036
1131
|
# and every DELETE. A stale BACKUP_VERIFIED label can never
|
|
1037
1132
|
# authorize a purge after recovery bytes were changed.
|
|
1038
1133
|
self._verify_operation_backup(row)
|
|
1134
|
+
self._pre_apply_revalidation(connection, row)
|
|
1039
1135
|
current = self._relevant_revision(connection)
|
|
1040
1136
|
if current != row["relevant_revision"]:
|
|
1041
1137
|
raise OperationalResetError("meaningful source data changed after the approved preview")
|
|
@@ -1487,6 +1583,11 @@ class ForgeOperationalResetService:
|
|
|
1487
1583
|
state = str(payload.get("state") or payload.get("status") or "UNKNOWN")
|
|
1488
1584
|
backup_digest = payload.get("backup_digest")
|
|
1489
1585
|
backup_reference = payload.get("backup_reference")
|
|
1586
|
+
observed_generation = payload.get("generation_after")
|
|
1587
|
+
if observed_generation is None:
|
|
1588
|
+
observed_generation = payload.get("dataset_generation")
|
|
1589
|
+
if observed_generation is None:
|
|
1590
|
+
observed_generation = target.get("dataset_generation")
|
|
1490
1591
|
return {
|
|
1491
1592
|
"contract_version": "operational-reset-v1",
|
|
1492
1593
|
"product": "forge",
|
|
@@ -1503,9 +1604,7 @@ class ForgeOperationalResetService:
|
|
|
1503
1604
|
"schema_version": target.get("schema_version", payload.get("schema_version")),
|
|
1504
1605
|
},
|
|
1505
1606
|
"profile": payload.get("profile", RESET_PROFILE),
|
|
1506
|
-
"dataset_generation":
|
|
1507
|
-
"dataset_generation", payload.get("generation_after", payload.get("dataset_generation"))
|
|
1508
|
-
),
|
|
1607
|
+
"dataset_generation": observed_generation,
|
|
1509
1608
|
"plan_digest": payload.get("plan_digest"),
|
|
1510
1609
|
"relevant_revision_digest": (
|
|
1511
1610
|
None if plan is None else plan.get("relevant_revision")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: forge-autonomy
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.24
|
|
4
4
|
Summary: Forge mission planning, producer contracts, and evidence interpretation runtime.
|
|
5
5
|
Author: Forge maintainers
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -123,6 +123,17 @@ The exact instance-ID comparison is consistency evidence, not a newly invented
|
|
|
123
123
|
cryptographic peer identity; preflight reports the latter as `NOT_ASSERTED`.
|
|
124
124
|
`CONFIGURED` is therefore never presented as `LIVE_READY`.
|
|
125
125
|
|
|
126
|
+
## Bounded installed maintenance
|
|
127
|
+
|
|
128
|
+
The product-owned external controller for the selected Forge 2.7.21 to 2.7.22
|
|
129
|
+
installation transition is documented in the
|
|
130
|
+
[installed update runbook](docs/operations/FORGE_INSTALLED_UPDATE_RUNBOOK.md) and
|
|
131
|
+
[sanitized Forge 2.7.22 release/installation completion record](docs/operations/FORGE_2_7_22_RELEASE_INSTALLATION_COMPLETION.md).
|
|
132
|
+
It stages an exact qualified wheel, backs up and migrates the selected runtime,
|
|
133
|
+
and atomically activates a versioned slot. It is not packaged as a Forge
|
|
134
|
+
Runtime command and does not replace Forge Platform's normal installer/update
|
|
135
|
+
composition boundary.
|
|
136
|
+
|
|
126
137
|
## Operational-history reset
|
|
127
138
|
|
|
128
139
|
Forge schema 38 provides a bounded, product-owned maintenance service under
|
|
@@ -237,6 +237,7 @@ tests/test_execution_context.py
|
|
|
237
237
|
tests/test_execution_host_configuration.py
|
|
238
238
|
tests/test_execution_host_contract.py
|
|
239
239
|
tests/test_execution_loop.py
|
|
240
|
+
tests/test_existing_release_reconciliation.py
|
|
240
241
|
tests/test_forge_console_admin_parity_contract.py
|
|
241
242
|
tests/test_forge_console_configuration_contract.py
|
|
242
243
|
tests/test_forge_console_hosting_contract.py
|
|
@@ -254,6 +255,7 @@ tests/test_governed_coverage.py
|
|
|
254
255
|
tests/test_historical_engineering_intent.py
|
|
255
256
|
tests/test_inner_loop_ci_contract.py
|
|
256
257
|
tests/test_installed_dynamic_mission_runtime.py
|
|
258
|
+
tests/test_installed_forge_update.py
|
|
257
259
|
tests/test_integration_coordination.py
|
|
258
260
|
tests/test_knowledge_consumption.py
|
|
259
261
|
tests/test_live_project_roadmap_contract.py
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"""Behavioral coverage for terminal reconciliation of an existing Forge release."""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
from hashlib import sha256
|
|
5
|
+
import io
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import sys
|
|
9
|
+
import tarfile
|
|
10
|
+
import tempfile
|
|
11
|
+
import unittest
|
|
12
|
+
import zipfile
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
SCRIPT = Path(__file__).parents[1] / "scripts" / "reconcile_existing_release.py"
|
|
16
|
+
SPEC = importlib.util.spec_from_file_location("forge_existing_release_reconciliation", SCRIPT)
|
|
17
|
+
assert SPEC and SPEC.loader
|
|
18
|
+
reconcile = importlib.util.module_from_spec(SPEC)
|
|
19
|
+
sys.modules[SPEC.name] = reconcile
|
|
20
|
+
SPEC.loader.exec_module(reconcile)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ExistingReleaseReconciliationTests(unittest.TestCase):
|
|
24
|
+
version = "2.7.22"
|
|
25
|
+
source = "a" * 40
|
|
26
|
+
original_run_id = "35254063298"
|
|
27
|
+
controller = "b" * 40
|
|
28
|
+
reconciliation_run = "40000000000"
|
|
29
|
+
|
|
30
|
+
def setUp(self) -> None:
|
|
31
|
+
self.temporary = tempfile.TemporaryDirectory()
|
|
32
|
+
self.addCleanup(self.temporary.cleanup)
|
|
33
|
+
self.root = Path(self.temporary.name)
|
|
34
|
+
self.wheel = self.root / f"forge_autonomy-{self.version}-py3-none-any.whl"
|
|
35
|
+
self.sdist = self.root / f"forge_autonomy-{self.version}.tar.gz"
|
|
36
|
+
metadata = f"Metadata-Version: 2.4\nName: forge-autonomy\nVersion: {self.version}\n\n"
|
|
37
|
+
with zipfile.ZipFile(self.wheel, "w") as archive:
|
|
38
|
+
archive.writestr(f"forge_autonomy-{self.version}.dist-info/METADATA", metadata)
|
|
39
|
+
with tarfile.open(self.sdist, "w:gz") as archive:
|
|
40
|
+
data = metadata.encode()
|
|
41
|
+
member = tarfile.TarInfo(f"forge_autonomy-{self.version}/PKG-INFO")
|
|
42
|
+
member.size = len(data)
|
|
43
|
+
archive.addfile(member, io.BytesIO(data))
|
|
44
|
+
self.wheel_digest = sha256(self.wheel.read_bytes()).hexdigest()
|
|
45
|
+
self.sdist_digest = sha256(self.sdist.read_bytes()).hexdigest()
|
|
46
|
+
self.hashes = self.root / "SHA256SUMS"
|
|
47
|
+
self.hashes.write_text(
|
|
48
|
+
f"{self.wheel_digest} dist/{self.wheel.name}\n"
|
|
49
|
+
f"{self.sdist_digest} dist/{self.sdist.name}\n",
|
|
50
|
+
encoding="utf-8",
|
|
51
|
+
)
|
|
52
|
+
self.operation = reconcile.ReleaseOperation.create(
|
|
53
|
+
operation_id=f"forge-release-{self.version}-{self.source}",
|
|
54
|
+
version=self.version,
|
|
55
|
+
policy_revision="forge-bootstrap-release-cadence-v2",
|
|
56
|
+
source_revision=self.source,
|
|
57
|
+
artifacts={
|
|
58
|
+
"wheel": "sha256:" + self.wheel_digest,
|
|
59
|
+
"sdist": "sha256:" + self.sdist_digest,
|
|
60
|
+
},
|
|
61
|
+
).transition("QUALIFIED", evidence={
|
|
62
|
+
"exact_main_sha": self.source,
|
|
63
|
+
"qualification": "forge-production-distribution",
|
|
64
|
+
"artifact_digests": {
|
|
65
|
+
f"dist/{self.wheel.name}": "sha256:" + self.wheel_digest,
|
|
66
|
+
f"dist/{self.sdist.name}": "sha256:" + self.sdist_digest,
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
self.qualified_bytes = self.operation_bytes(self.operation)
|
|
70
|
+
self.qualified_name = f"forge-release-qualified-{self.version}-{self.source}.json"
|
|
71
|
+
self.assets = self.root / "assets"
|
|
72
|
+
self.assets.mkdir()
|
|
73
|
+
(self.assets / self.qualified_name).write_bytes(self.qualified_bytes)
|
|
74
|
+
self.release = self.release_document([self.qualified_name])
|
|
75
|
+
self.run = {
|
|
76
|
+
"databaseId": int(self.original_run_id),
|
|
77
|
+
"status": "completed",
|
|
78
|
+
"conclusion": "failure",
|
|
79
|
+
"headSha": self.source,
|
|
80
|
+
"event": "workflow_dispatch",
|
|
81
|
+
"workflowName": "Forge production release",
|
|
82
|
+
"jobs": [
|
|
83
|
+
{"name": "build-and-qualify", "conclusion": "success"},
|
|
84
|
+
{"name": "publish-pypi", "conclusion": "success"},
|
|
85
|
+
{"name": "registry-readback-and-published-evidence", "conclusion": "failure"},
|
|
86
|
+
{"name": "record-release-complete", "conclusion": "skipped"},
|
|
87
|
+
],
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def operation_bytes(operation: object) -> bytes:
|
|
92
|
+
return (json.dumps(reconcile.asdict(operation), sort_keys=True, separators=(",", ":")) + "\n").encode()
|
|
93
|
+
|
|
94
|
+
def release_document(self, names: list[str], *, draft: bool = True, database_id: int = 123) -> dict[str, object]:
|
|
95
|
+
assets = []
|
|
96
|
+
for name in names:
|
|
97
|
+
content = (self.assets / name).read_bytes()
|
|
98
|
+
assets.append({
|
|
99
|
+
"name": name,
|
|
100
|
+
"state": "uploaded",
|
|
101
|
+
"size": len(content),
|
|
102
|
+
"digest": "sha256:" + sha256(content).hexdigest(),
|
|
103
|
+
})
|
|
104
|
+
return {
|
|
105
|
+
"tagName": f"forge-v{self.version}",
|
|
106
|
+
"targetCommitish": self.source,
|
|
107
|
+
"databaseId": database_id,
|
|
108
|
+
"id": "RE_kwDOReleaseNode",
|
|
109
|
+
"apiUrl": "https://api.github.com/repos/example/forge/releases/123",
|
|
110
|
+
"isDraft": draft,
|
|
111
|
+
"isPrerelease": False,
|
|
112
|
+
"isImmutable": False,
|
|
113
|
+
"assets": assets,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def release_identity(self) -> dict[str, object]:
|
|
118
|
+
return reconcile.validate_release_inventory(
|
|
119
|
+
self.release, version=self.version, source_revision=self.source,
|
|
120
|
+
)["release"]
|
|
121
|
+
|
|
122
|
+
def validate(self, **changes: object) -> dict[str, object]:
|
|
123
|
+
values = {
|
|
124
|
+
"run_document": self.run,
|
|
125
|
+
"qualified_action": self.qualified_bytes,
|
|
126
|
+
"qualified_release": self.qualified_bytes,
|
|
127
|
+
"hashes": self.hashes,
|
|
128
|
+
"wheel": self.wheel,
|
|
129
|
+
"sdist": self.sdist,
|
|
130
|
+
"version": self.version,
|
|
131
|
+
"source_revision": self.source,
|
|
132
|
+
"original_run_id": self.original_run_id,
|
|
133
|
+
"controller_source": self.controller,
|
|
134
|
+
"reconciliation_run_id": self.reconciliation_run,
|
|
135
|
+
"release_document": self.release,
|
|
136
|
+
}
|
|
137
|
+
values.update(changes)
|
|
138
|
+
return reconcile.validate_reconciliation(**values)
|
|
139
|
+
|
|
140
|
+
def published_operation(self) -> object:
|
|
141
|
+
return self.operation.transition("PUBLISHED", evidence=self.validate())
|
|
142
|
+
|
|
143
|
+
def test_exact_historical_release_and_fresh_registry_bytes_are_accepted(self) -> None:
|
|
144
|
+
evidence = self.validate()
|
|
145
|
+
self.assertEqual(evidence["readback"], "PASS")
|
|
146
|
+
self.assertEqual(evidence["product_source_revision"], self.source)
|
|
147
|
+
self.assertEqual(evidence["release_controller_source"], self.controller)
|
|
148
|
+
self.assertEqual(evidence["github_release"], self.release_identity)
|
|
149
|
+
|
|
150
|
+
def test_wrong_artifact_or_unexpected_original_failure_is_rejected(self) -> None:
|
|
151
|
+
self.wheel.write_bytes(b"different registry bytes")
|
|
152
|
+
with self.assertRaisesRegex(reconcile.ExistingReleaseReconciliationError, "registry artifacts"):
|
|
153
|
+
self.validate()
|
|
154
|
+
changed_run = {**self.run, "conclusion": "success"}
|
|
155
|
+
with self.assertRaisesRegex(reconcile.ExistingReleaseReconciliationError, "conclusion"):
|
|
156
|
+
self.validate(run_document=changed_run)
|
|
157
|
+
|
|
158
|
+
def test_changed_release_receipt_and_release_object_are_rejected(self) -> None:
|
|
159
|
+
with self.assertRaisesRegex(reconcile.ExistingReleaseReconciliationError, "differs"):
|
|
160
|
+
self.validate(qualified_release=self.qualified_bytes + b" ")
|
|
161
|
+
changed = self.release_document([self.qualified_name], database_id=124)
|
|
162
|
+
with self.assertRaisesRegex(reconcile.ExistingReleaseReconciliationError, "identity changed"):
|
|
163
|
+
reconcile.validate_same_release(
|
|
164
|
+
self.release, changed, version=self.version, source_revision=self.source,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def test_inventory_rejects_unknown_duplicate_and_incomplete_lineage(self) -> None:
|
|
168
|
+
unknown = {**self.release, "assets": [*self.release["assets"], {
|
|
169
|
+
"name": "forge_autonomy-2.7.22-py3-none-any.whl", "state": "uploaded",
|
|
170
|
+
"size": 1, "digest": "sha256:" + "0" * 64,
|
|
171
|
+
}]}
|
|
172
|
+
with self.assertRaisesRegex(reconcile.ExistingReleaseReconciliationError, "unexpected asset"):
|
|
173
|
+
reconcile.validate_release_inventory(unknown, version=self.version, source_revision=self.source)
|
|
174
|
+
duplicate = {**self.release, "assets": [*self.release["assets"], self.release["assets"][0]]}
|
|
175
|
+
with self.assertRaisesRegex(reconcile.ExistingReleaseReconciliationError, "duplicate"):
|
|
176
|
+
reconcile.validate_release_inventory(duplicate, version=self.version, source_revision=self.source)
|
|
177
|
+
complete = f"forge-release-complete-{self.version}-{self.source}.json"
|
|
178
|
+
(self.assets / complete).write_text("{}", encoding="utf-8")
|
|
179
|
+
incomplete = self.release_document([self.qualified_name, complete])
|
|
180
|
+
with self.assertRaisesRegex(reconcile.ExistingReleaseReconciliationError, "published predecessor"):
|
|
181
|
+
reconcile.validate_release_inventory(incomplete, version=self.version, source_revision=self.source)
|
|
182
|
+
prerelease = {**self.release, "isPrerelease": True}
|
|
183
|
+
with self.assertRaisesRegex(reconcile.ExistingReleaseReconciliationError, "must not be a prerelease"):
|
|
184
|
+
reconcile.validate_release_inventory(prerelease, version=self.version, source_revision=self.source)
|
|
185
|
+
|
|
186
|
+
def test_release_asset_bytes_and_symlinks_are_fail_closed(self) -> None:
|
|
187
|
+
self.assertEqual(
|
|
188
|
+
reconcile.validate_release_assets(
|
|
189
|
+
self.release, self.assets, version=self.version, source_revision=self.source,
|
|
190
|
+
)["assets"],
|
|
191
|
+
[self.qualified_name],
|
|
192
|
+
)
|
|
193
|
+
(self.assets / self.qualified_name).write_bytes(b"changed")
|
|
194
|
+
with self.assertRaisesRegex(reconcile.ExistingReleaseReconciliationError, "differs"):
|
|
195
|
+
reconcile.validate_release_assets(
|
|
196
|
+
self.release, self.assets, version=self.version, source_revision=self.source,
|
|
197
|
+
)
|
|
198
|
+
(self.assets / self.qualified_name).unlink()
|
|
199
|
+
(self.assets / self.qualified_name).symlink_to(self.hashes)
|
|
200
|
+
with self.assertRaisesRegex(reconcile.ExistingReleaseReconciliationError, "unsafe"):
|
|
201
|
+
reconcile.validate_release_assets(
|
|
202
|
+
self.release, self.assets, version=self.version, source_revision=self.source,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def test_followup_lineage_is_stable_across_resume_controller_runs(self) -> None:
|
|
206
|
+
published = self.published_operation()
|
|
207
|
+
published_path = self.root / "published.json"
|
|
208
|
+
published_path.write_bytes(self.operation_bytes(published))
|
|
209
|
+
complete_evidence = reconcile.completion_evidence(
|
|
210
|
+
version=self.version, original_run_id=self.original_run_id,
|
|
211
|
+
controller_source="c" * 40, reconciliation_run_id="50000000000",
|
|
212
|
+
source_revision=self.source, release_identity=self.release_identity,
|
|
213
|
+
)
|
|
214
|
+
complete = published.transition("RELEASE_COMPLETE", evidence=complete_evidence)
|
|
215
|
+
complete_path = self.root / "complete.json"
|
|
216
|
+
complete_path.write_bytes(self.operation_bytes(complete))
|
|
217
|
+
result = reconcile.validate_followup_receipts(
|
|
218
|
+
published_path=published_path, pending_path=None, complete_path=complete_path,
|
|
219
|
+
original_run_id=self.original_run_id, release_identity=self.release_identity,
|
|
220
|
+
)
|
|
221
|
+
self.assertEqual(result["state"], "RELEASE_COMPLETE")
|
|
222
|
+
self.assertEqual(result["controller_runs"], [
|
|
223
|
+
{"phase": "publication", "run_id": self.reconciliation_run,
|
|
224
|
+
"controller_source": self.controller},
|
|
225
|
+
{"phase": "completion", "run_id": "50000000000",
|
|
226
|
+
"controller_source": "c" * 40},
|
|
227
|
+
])
|
|
228
|
+
self.assertEqual(
|
|
229
|
+
reconcile.validate_followup_receipts(
|
|
230
|
+
published_path=published_path, pending_path=None, complete_path=complete_path,
|
|
231
|
+
original_run_id=self.original_run_id, release_identity=self.release_identity,
|
|
232
|
+
), result,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def test_cleanup_pending_is_durable_and_may_advance_to_complete(self) -> None:
|
|
236
|
+
published = self.published_operation()
|
|
237
|
+
published_path = self.root / "published.json"
|
|
238
|
+
published_path.write_bytes(self.operation_bytes(published))
|
|
239
|
+
pending_evidence = reconcile.cleanup_pending_evidence(
|
|
240
|
+
original_run_id=self.original_run_id, controller_source="c" * 40,
|
|
241
|
+
reconciliation_run_id="50000000000", source_revision=self.source,
|
|
242
|
+
release_identity=self.release_identity, failed_targets=["published-assets"],
|
|
243
|
+
)
|
|
244
|
+
pending = published.transition("CLEANUP_PENDING", evidence=pending_evidence)
|
|
245
|
+
pending_path = self.root / "pending.json"
|
|
246
|
+
pending_path.write_bytes(self.operation_bytes(pending))
|
|
247
|
+
result = reconcile.validate_followup_receipts(
|
|
248
|
+
published_path=published_path, pending_path=pending_path, complete_path=None,
|
|
249
|
+
original_run_id=self.original_run_id, release_identity=self.release_identity,
|
|
250
|
+
)
|
|
251
|
+
self.assertEqual(result["state"], "CLEANUP_PENDING")
|
|
252
|
+
complete = pending.transition("RELEASE_COMPLETE", evidence=reconcile.completion_evidence(
|
|
253
|
+
version=self.version, original_run_id=self.original_run_id,
|
|
254
|
+
controller_source="d" * 40, reconciliation_run_id="60000000000",
|
|
255
|
+
source_revision=self.source, release_identity=self.release_identity,
|
|
256
|
+
))
|
|
257
|
+
complete_path = self.root / "complete.json"
|
|
258
|
+
complete_path.write_bytes(self.operation_bytes(complete))
|
|
259
|
+
result = reconcile.validate_followup_receipts(
|
|
260
|
+
published_path=published_path, pending_path=pending_path, complete_path=complete_path,
|
|
261
|
+
original_run_id=self.original_run_id, release_identity=self.release_identity,
|
|
262
|
+
)
|
|
263
|
+
self.assertEqual(result["state"], "RELEASE_COMPLETE")
|
|
264
|
+
|
|
265
|
+
def test_new_publication_and_controller_run_bind_exact_current_provenance(self) -> None:
|
|
266
|
+
qualified = self.root / "qualified.json"
|
|
267
|
+
qualified.write_bytes(self.qualified_bytes)
|
|
268
|
+
publication = self.root / "publication.json"
|
|
269
|
+
publication.write_text(json.dumps(self.validate()), encoding="utf-8")
|
|
270
|
+
result = reconcile.validate_new_publication_evidence(
|
|
271
|
+
qualified_path=qualified, publication_path=publication,
|
|
272
|
+
original_run_id=self.original_run_id, release_identity=self.release_identity,
|
|
273
|
+
controller_source=self.controller, reconciliation_run_id=self.reconciliation_run,
|
|
274
|
+
)
|
|
275
|
+
self.assertEqual(result["controller_runs"][0]["run_id"], self.reconciliation_run)
|
|
276
|
+
with self.assertRaisesRegex(reconcile.ExistingReleaseReconciliationError, "this controller run"):
|
|
277
|
+
reconcile.validate_new_publication_evidence(
|
|
278
|
+
qualified_path=qualified, publication_path=publication,
|
|
279
|
+
original_run_id=self.original_run_id, release_identity=self.release_identity,
|
|
280
|
+
controller_source="f" * 40, reconciliation_run_id=self.reconciliation_run,
|
|
281
|
+
)
|
|
282
|
+
run = {
|
|
283
|
+
"databaseId": int(self.reconciliation_run), "headSha": self.controller,
|
|
284
|
+
"event": "workflow_dispatch", "workflowName": "Forge existing release reconciliation",
|
|
285
|
+
"status": "completed", "conclusion": "success",
|
|
286
|
+
}
|
|
287
|
+
self.assertEqual(
|
|
288
|
+
reconcile.validate_controller_run(
|
|
289
|
+
run, run_id=self.reconciliation_run, controller_source=self.controller,
|
|
290
|
+
)["controller_source"], self.controller,
|
|
291
|
+
)
|
|
292
|
+
with self.assertRaisesRegex(reconcile.ExistingReleaseReconciliationError, "headSha"):
|
|
293
|
+
reconcile.validate_controller_run(
|
|
294
|
+
run, run_id=self.reconciliation_run, controller_source="f" * 40,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
def test_followup_receipt_hydration_is_monotonic_and_idempotent(self) -> None:
|
|
298
|
+
evidence_root = self.root / "evidence"
|
|
299
|
+
store = reconcile.ReleaseOperationStore(evidence_root)
|
|
300
|
+
store.acquire(self.operation.operation_id)
|
|
301
|
+
try:
|
|
302
|
+
store.save(self.operation)
|
|
303
|
+
finally:
|
|
304
|
+
store.release(self.operation.operation_id)
|
|
305
|
+
published = self.published_operation()
|
|
306
|
+
receipt = self.root / "published.json"
|
|
307
|
+
receipt.write_bytes(self.operation_bytes(published))
|
|
308
|
+
self.assertEqual(
|
|
309
|
+
reconcile.hydrate_followup_receipt(evidence_root, self.operation.operation_id, receipt), published,
|
|
310
|
+
)
|
|
311
|
+
self.assertEqual(
|
|
312
|
+
reconcile.hydrate_followup_receipt(evidence_root, self.operation.operation_id, receipt), published,
|
|
313
|
+
)
|
|
314
|
+
receipt.write_bytes(self.qualified_bytes)
|
|
315
|
+
with self.assertRaisesRegex(reconcile.ExistingReleaseReconciliationError, "invalid operation or state"):
|
|
316
|
+
reconcile.hydrate_followup_receipt(evidence_root, self.operation.operation_id, receipt)
|
|
317
|
+
|
|
318
|
+
def test_workflow_separates_artifact_execution_from_release_mutation(self) -> None:
|
|
319
|
+
workflow = Path(".github/workflows/forge-existing-release-reconcile.yml").read_text(encoding="utf-8")
|
|
320
|
+
validation = workflow.split(" validate-existing-release:", 1)[1].split(
|
|
321
|
+
" smoke-existing-release:", 1,
|
|
322
|
+
)[0]
|
|
323
|
+
smoke = workflow.split(" smoke-existing-release:", 1)[1].split(
|
|
324
|
+
" reconcile-existing-release:", 1,
|
|
325
|
+
)[0]
|
|
326
|
+
mutation = workflow.split(" reconcile-existing-release:", 1)[1]
|
|
327
|
+
self.assertIn("persist-credentials: false", workflow)
|
|
328
|
+
self.assertIn("contents: read", validation)
|
|
329
|
+
self.assertNotIn("pip install", validation)
|
|
330
|
+
self.assertNotIn("installed_smoke", validation)
|
|
331
|
+
self.assertNotIn("gh release edit", validation)
|
|
332
|
+
self.assertIn("forge-registry-wheel", smoke)
|
|
333
|
+
self.assertIn("env -i HOME=", smoke)
|
|
334
|
+
self.assertIn("--no-index --no-deps", smoke)
|
|
335
|
+
self.assertNotIn("upload-artifact", smoke)
|
|
336
|
+
self.assertNotIn("publication.json", smoke)
|
|
337
|
+
self.assertIn("contents: write", mutation)
|
|
338
|
+
self.assertNotIn("pip install", mutation)
|
|
339
|
+
self.assertNotIn("registry-readback", mutation)
|
|
340
|
+
self.assertNotIn("forge-release-smoke-", mutation)
|
|
341
|
+
self.assertIn("validate-publication", mutation)
|
|
342
|
+
self.assertIn("validate-controller-run", mutation)
|
|
343
|
+
self.assertIn("verify_controller_runs", mutation)
|
|
344
|
+
self.assertIn("same-release", mutation)
|
|
345
|
+
self.assertIn("validate-assets", mutation)
|
|
346
|
+
self.assertIn("$tag^{commit}", mutation)
|
|
347
|
+
self.assertIn("--mark-cleanup-pending", mutation)
|
|
348
|
+
self.assertIn("--wheel-digest", mutation)
|
|
349
|
+
self.assertIn("--complete", mutation)
|
|
350
|
+
self.assertNotIn("gh-action-pypi-publish", workflow)
|
|
351
|
+
self.assertNotIn("twine upload", workflow)
|
|
352
|
+
self.assertNotIn("python3 -m build", workflow)
|
|
353
|
+
self.assertNotIn("git tag", workflow)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
if __name__ == "__main__":
|
|
357
|
+
unittest.main()
|