Skip to content

fix(runtime): refuse provisioning results that bind an envelope the plan never selected - #1456

Open
doublewhy wants to merge 2 commits into
devfrom
1450-realization-envelope-binding
Open

doublewhy wants to merge 2 commits into
devfrom
1450-realization-envelope-binding

Conversation

@doublewhy

@doublewhy doublewhy commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

When a backend call carried a provisioning plan, nothing compared the returned snapshot's realization_envelope with the envelope identity the plan selected. backend_effect_transitions._runtime_owned_violation refuses realization-carrier changes only for calls without a provisioning plan. Compute-node plans were refused later, through compute-substrate evaluation ("Backend returned no bound substrate selection"). On dev at 3512210, a reference provisioner that returned a forged configuration_digest for a network-only plan had it committed with success, and the accepted snapshot named an envelope the run never selected.

When the submitted provisioning plan names an envelope, the returned envelope must now equal that identity or the accepted predecessor's. Otherwise the result is refused at runtime.snapshot.realization-envelope. The rule also covers RuntimeManager.destroy(), whose delete plan names the predecessor's envelope.

Requirement UIDs

  • ASR-532 (Runtime Backend Result Integrity). Its statement requires the runtime to "reject or sanitize results that violate RAE-owned portable contracts, plan authority, runtime-domain ownership, or snapshot-transition invariants" and says "Rejections shall preserve the trusted predecessor state". Its normative contract, specs/formal/runtime-contracts/backend-result-admission.md, says "Backend results are proposals, not authority over accepted portable state" and "Non-resource snapshot carriers retain their specialized domain owners". SNAPSHOT_VALUE_OWNERS assigns realization_envelope to the realization owner. The Carry resolved realization posture through the backend-facing plan #1067 preflight merged with feat: carry resolved realization authority through plans #1081 says to resolve the backend envelope "by its digest-checked identity". ASR-532's record already lists backend_effect_transitions.py as an implementing code file. As in feat: carry resolved realization authority through plans #1081, the branch name carries no UID and there is no requirement scope file, so CI resolves no requirement UID and skips requirement governance; no requirement record changes.

Related Issues

Closes #1450

ADR Impact

  • None. ADR-105 names specs/formal/runtime-contracts/backend-result-admission.md as a normative contract; this change follows it and changes no ADR text.

Changes

  • implementations/python/packages/raes_runtime/backend_effect_transitions.py: _runtime_owned_violation gains one check. If the call's provisioning plan names a realization envelope, the returned envelope must equal it or the predecessor's under snapshot_values_equal. Otherwise the result is refused with runtime.backend-contract-invalid at runtime.snapshot.realization-envelope, and the predecessor is kept.
    • Calls without a provisioning plan keep the existing rule that realization carriers do not change.
    • A plan that names no envelope is unaffected. The stub and reference provisioners bind their own declared identity for such hand-built plans.
    • RuntimeManager.destroy() names the predecessor's envelope in its delete plan (manager_destroy.py), so a teardown result must return that envelope. An honest apply followed by destroy still succeeds. A teardown whose predecessor names an envelope other than the provisioner's own is now refused and keeps the predecessor's envelope and entries; on dev the reference provisioner's result was admitted and replaced the predecessor's envelope with its own. When such a teardown is refused, the reference provisioner has already deleted the resources through its driver, but the runtime keeps the predecessor's entries (ASR-532's result-admission contract provides no infrastructure rollback), and later destroy() calls are refused the same way until a plan naming the provisioner's envelope is applied. The libvirt provisioner already refuses a plan whose envelope differs from its configured one.
  • implementations/python/tests/test_issue_1450_realization_envelope_binding.py (new): a delegating wrapper around the reference provisioner whose successful results carry a forged configuration_digest.
    • RuntimeManager.apply refuses it for a network-only and a compute-node plan, with the refusal at runtime.snapshot.realization-envelope, and leaves no envelope or entries.
    • The control-plane submission of the network-only plan fails with the refusal at /runtime.snapshot.realization-envelope, the portable form of that address. It keeps the predecessor's envelope and entries, both from an empty snapshot and from the snapshot an honest apply committed.
    • RuntimeManager.destroy() refuses a forged teardown envelope, and a teardown whose predecessor names another envelope. Both keep the predecessor's envelope and entries.
    • An honest apply still binds the plan's selected envelope.
  • Research evidence republished, because tools/research_evidence.py implementation_digest() hashes every package .py:
    • Specification-coverage release 69.0.0 (execution-snapshot-v69.json, analysis-v69.json, issue-1450 bundle) replays the retained matrix against this branch's source.
    • Formal-validation release 70.0.0 (execution-snapshot-v70.json, analysis-v70.json, retest-v70.json) replays the retained formal cases with baseline 69.0.0. No replay digest changed and no deviation is recorded.
    • Revision pins are advanced in tools/check_specification_coverage.py, tools/formal_semantic_validation/, and the three evidence test modules. Both research indexes record the new releases.

Test Plan

  • Unit tests pass
  • Integration tests pass if applicable
  • Full completion suite required in CI before merge
  • No coverage regression

uv run --project implementations/python --frozen --all-extras python -m pytest implementations/python/tests/test_issue_1450_realization_envelope_binding.py -q -p no:cacheprovider passed (7 passed). With -m integration it selects nothing (7 deselected), because the module has no integration-marked tests. With backend_effect_transitions.py replaced by its dev content, the same command failed 6 of 7:

  • the network-only manager case, both control-plane cases and both destroy cases were admitted (success true, or SUCCEEDED);
  • the compute-node case was refused for the substrate reason at provision.node.web, not at runtime.snapshot.realization-envelope.

The honest case passed.

The 157 test modules that mention raes_runtime, selected with grep -rl raes_runtime --include='test_*.py' implementations/python/tests and including the new one, passed: 3476 passed, 2 skipped, 47 deselected. With -m integration, 43 passed. These modules include the existing reference-backend apply-then-destroy tests in test_issue_1204_reference_profiles.py and test_issue_1207_description_lifecycle.py.

The 83 test modules that mention the reference, libvirt or stub backend packages or raes_conformance but not raes_runtime, selected with grep -rlE 'raes_reference_backend|raes_backend_libvirt|raes_backend_stubs|raes_conformance' --include='test_*.py' implementations/python/tests | xargs grep -L raes_runtime, passed: 1935 passed, 25 deselected. With -m integration, 16 passed, 2 skipped and 7 errored. The errors are the seven installed-wheel cases in test_corpus_packaging.py, whose wheel install fails on this machine ("Python closure client execution failure"); CI's integration lane runs them.

After the republish, tools/check_specification_coverage.py and tools/check_formal_semantic_validation.py passed under uv run --project implementations/python --frozen --all-extras. The four directly changed test modules passed with -m integration (19 passed, 216 deselected). nox -s verify-fast-feedback -- --base-rev origin/dev passed every stage except two skips: requirement governance (--skip-requirement) and YAML syntax (no YAML files changed). It ran the four directly changed test modules (216 passed, 19 deselected).

At this head, 2828315, all 32 PR checks pass and the Docs workflow's deploy job (run 37941145536) is skipped. CI run 37941146135 (21 jobs, all green) includes canonical / integration, which ran every integration-marked test: 226 passed and 2 skipped (the two real-libvirt cases), including the seven test_corpus_packaging.py installed-wheel cases that error locally. In canonical / checks, both contracts evidence steps passed (standardized specification coverage, formal semantic-validation evidence), and requirement governance was skipped because no requirement UID resolves for the branch. SonarCloud's quality gate is OK for this head: 100% coverage on new code (7 new lines to cover), 0 new issues and 0.0% duplication.

Ground Control Checks

  • Repository policy command passes
  • Pre-push code review and test-quality review completed, or not run for this lane

make policy passed (requirement governance skipped by --skip-requirement). A code and test-quality review of the previous head, 7ec1d75, was completed; this revision applies its findings. No Codex review was run.

Traceability

Checklist

  • Code follows the project coding standards
  • FM level classified if semantic change: FM1. One new fail-closed result-admission invariant: when the submitted provisioning plan names an envelope, the returned envelope equals that identity or the accepted predecessor's. ASR-505 lists result contracts under FM3 and ADR-105 is classified FM3, but this change is one fail-closed equality check on an existing realization carrier: it adds no state, transition or lifecycle, and a refused result takes ASR-532's existing rejection path, which keeps the trusted predecessor. The invariant is stated here and in fix(runtime): refuse provisioning results that bind an envelope the plan never selected #1450 and covered by the regression module. No contract, schema or spec text changes. No executable or runtime adoption is claimed.
  • Published contract schemas regenerated if models changed: no model or schema changed.
  • PR title is a Conventional Commit (release-please derives the version and CHANGELOG.md from it)
  • Architectural docs updated if applicable: not applicable; no documented behavior changes.

…lan never selected

With a provisioning plan present, nothing compared the returned realization envelope with the plan's selected identity. Compute-node plans were refused later through compute-substrate evaluation, but a network-only plan committed a forged envelope identity with success.

When the submitted plan names an envelope, the returned envelope must now equal that identity or the accepted predecessor's; otherwise the result is refused at runtime.snapshot.realization-envelope.
Specification-coverage release 69.0.0 and formal-validation release 70.0.0 bind the current implementation digest; outcomes and claim limits are unchanged.
@doublewhy
doublewhy force-pushed the 1450-realization-envelope-binding branch from 7ec1d75 to 2828315 Compare October 9, 2026 14:01
@doublewhy
doublewhy marked this pull request as ready for review October 9, 2026 15:34

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant