Skip to content

fix(runtime): refuse backend snapshot entries whose runtime domain is an untyped string - #1441

Open
doublewhy wants to merge 3 commits into
devfrom
1434-typed-runtime-domain
Open

doublewhy wants to merge 3 commits into
devfrom
1434-typed-runtime-domain

Conversation

@doublewhy

@doublewhy doublewhy commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

SnapshotEntry.domain is typed RuntimeDomain, a str enum, but backend snapshot admission never checked that type. The plan-ownership check in backend_entry_transitions._identity_violation compares entry.domain != domain, and "provisioning" == RuntimeDomain.PROVISIONING is true. On dev at cf8b430, a reference provisioner whose successful result named its changed entries' domains as plain strings was committed with success, by both RuntimeManager.apply and the control plane.

The control-plane submission path (control_plane_submission._submitted_operation_diagnostic) and the credential path (backend_account_credentials._closed_plan_entry) compare the domain by identity. After one such result, the planner-authorized teardown of provision.node.web was refused with runtime.plan-resource-incoherent, so the resource could not be removed until a durable reload re-typed it.

Backend snapshot admission now refuses an entry whose domain is not a RuntimeDomain member, next to the existing typed profile-binding check.

Because this changes source bound by the research evidence, both families get one fresh content-addressed capture, as docs/develop/research-captures.md requires since #1449. Retained outcomes, classifications and claim limits are unchanged, and formal replay shows no drift.

Requirement UIDs

  • ASR-532 (Runtime Backend Result Integrity). specs/formal/runtime-contracts/backend-result-admission.md says a submitted plan owns the resource's "runtime domain" and "A backend MUST NOT rewrite these". docs/requirements/ASR-532/requirement.md already lists raes_runtime/backend_snapshot_contracts.py as an IMPLEMENTS 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 requirement governance is not activated and no requirement record changes.

Related Issues

Closes #1434

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_snapshot_contracts.py: new _domain_violation, checked first in _entry_violation. It requires type(entry.domain) is RuntimeDomain and otherwise reports "Backend snapshot contains an untyped runtime domain.". The existing shape gate turns that into runtime.backend-contract-invalid and keeps the predecessor.
  • implementations/python/tests/test_issue_1434_typed_runtime_domain.py (new): a delegating wrapper around the reference provisioner whose first successful result names its changed entries' domains as strings. The tests compare exact (code, message) diagnostics.
    • RuntimeManager.apply refuses it with one diagnostic, runtime.backend-contract-invalid "Backend snapshot contains an untyped runtime domain.", and leaves the snapshot empty.
    • On the control plane, the string-domain plan fails with that diagnostic followed by runtime.control-plane.operation-failed, and the next planner-authorized plan succeeds without diagnostics. The typed control commits and then removes provision.node.web without diagnostics. Both end with only provision.network.lab, and with typed domains.
  • implementations/python/tests/test_issue_1200_mixed_runtime_constraints.py (_returned) and implementations/python/tests/test_issue_1067_resolved_realization_authority.py (_snapshot_from_plan): these helpers built backend results with domain="provisioning" and now use RuntimeDomain.PROVISIONING. Without this, 13 test_issue_1200 cases that expect acceptance fail on the new check, and test_backend_apply_uses_plan_authority_and_restores_baseline_on_closed_excess would pass for the wrong reason: its result is refused as an untyped domain, and with the excess mutation removed, the result was also refused as an untyped domain. With the typed helper, the excess result is refused with "Backend materialized closed realization concern 'runtime-environment'.", and with the excess mutation removed the result is accepted. The test now asserts that message as well as the code.
  • Research evidence: specification-coverage capture capture-621839feead974e36c1d1ca296447fc7ae844949feaea591aa92546102615050 (template: dev's newest capture capture-94a84282...) and formal-validation capture capture-30800f71c4ba3ceedeb9ad43db759b1d3e1a83f9df1a5e529b661b6b6f195c70 (baseline: dev's newest capture capture-2df4b6bd...), each with its capture-records receipt, recorded with python -m tools.research_capture record. Both bind implementation digest 2943bea1d38e at code commit 5d623a26. No numbered file, index, pin or evidence test changes. This branch's earlier numbered evidence (specification 69.0.0, formal 70.0.0, their index entries and revision pins) is dropped, and the two code commits are rebased onto dev at cf8b430 unchanged.

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_1434_typed_runtime_domain.py -q -p no:cacheprovider passed (3 passed). With backend_snapshot_contracts.py restored from dev, the same command failed 2 of 3. The manager case committed the result (assert True is False), and the control-plane case's second receipt was refused with runtime.plan-resource-incoherent ("Submitted plan disagrees with the admitted snapshot resource identity."). The typed control passed.

With the fix in place, reverting the _snapshot_from_plan helper to domain="provisioning" makes the closed-excess test fail on its message assertion, because the refusal becomes "Backend snapshot contains an untyped runtime domain.". Reverting the _returned helper instead makes 13 of the 46 test_issue_1200 cases fail.

The targeted modules from the issue's verification plan passed: test_issue_1434_typed_runtime_domain.py, test_issue_1067_resolved_realization_authority.py, test_issue_1200_mixed_runtime_constraints.py, test_issue_158_runtime_result_integrity.py and the four test_issue_1204_result_* modules (178 passed). With -m integration they select no tests (178 deselected).

tools/check_specification_coverage.py and tools/check_formal_semantic_validation.py passed under uv run --project implementations/python --frozen --all-extras (integrity and replay). The evidence modules test_specification_coverage.py, test_formal_semantic_validation.py, test_issue_989_versioned_evidence.py and test_research_capture.py passed with default markers (226 passed) and with -m integration (18 passed). nox -s contracts -- --base-rev origin/dev, the command of CI's "Run governed contract graph" step, passed every step. Formal replay of the retained corpus showed no drift and no pinned artifact changed.

nox -s verify-fast-feedback -- --base-rev origin/dev passed every stage except four skips: requirement governance (--skip-requirement), YAML syntax (no YAML files changed) and the two tooling lint stages (no tooling Python changed). It ran the three directly changed test modules (79 passed). nox -s lint passed. backend_snapshot_contracts.py is 288 lines.

CI at 5672244e: 28 checks passed and deploy was skipped (CI run 38357433381, Docs 38357432923, PR Body Policy 38357432962, PR Title Lint 38357432928). The SonarCloud quality gate passed with no failing conditions.

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). No pre-push Codex or test-quality review was run for this lane.

Traceability

Checklist

  • Code follows the project coding standards
  • FM level classified if semantic change: FM0: local type check in bounded snapshot shape admission; unit and regression tests are the required artifact. 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 behaviour changes.

@doublewhy
doublewhy force-pushed the 1434-typed-runtime-domain branch from 6e425be to 9a80447 Compare October 9, 2026 13:25
@doublewhy
doublewhy marked this pull request as ready for review October 9, 2026 15:28
… an untyped string

RuntimeDomain is a str enum, so a backend entry whose domain was the plain string "provisioning" passed the value comparison in the plan-ownership check and was committed. The control-plane submission and credential paths compare the domain by identity, so every later planner-authorized plan for that resource was refused as incoherent, and the resource could not be torn down.

Backend snapshot admission now requires a RuntimeDomain member, next to the typed profile-binding check. Two test helpers that built backend results with string domains now use RuntimeDomain.PROVISIONING, so the closed-excess test keeps failing for closed excess.
The issue #1434 regression module now asserts the exact backend diagnostic
on the manager and control-plane paths, and its SDL fixtures are literal
documents. The issue #1067 closed-excess test also asserts its refusal
message, so an untyped-domain refusal can no longer satisfy it.
@doublewhy
doublewhy force-pushed the 1434-typed-runtime-domain branch from 9a80447 to 5672244 Compare October 13, 2026 06: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