Skip to content

fix(runtime): gate autonomous participant episode initialization like the control plane - #1448

Open
doublewhy wants to merge 2 commits into
devfrom
1439-participant-initialize-gate
Open

doublewhy wants to merge 2 commits into
devfrom
1439-participant-initialize-gate

Conversation

@doublewhy

@doublewhy doublewhy commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The autonomous participant scheduler initialized participant episodes by calling participant_runtime.initialize(request, snapshot) directly from participant_scheduler_initialization._ensure_participant_episode. It passed the apply's live working snapshot and adopted whatever snapshot came back. On dev at 3512210, using the DSL-437 autonomous fixture:

  • an initialize that wrote snapshot.metadata had that write committed;
  • an initialize that returned a snapshot without the accepted entries left 9 of the 25 entries the honest run commits.

Both applies reported success. The control plane already gates the same backend method: initialize_participant_episode reaches it through _call_backend_apply with participant_effect_authority(request, snapshot).

The scheduler now invokes initialize through _call_backend_apply with the same participant effect authority. The arguments are detached, and the result may change only participant-owned state for the named participant. On dev an exception raised by initialize propagated out of RuntimeManager.apply(); it now fails the apply with a diagnostic. Unlike the control plane's call, it passes no information-state context resolver. The stub, libvirt and reference participant runtimes inherit BaseParticipantRuntime.initialize, which writes only participant episode results and history.

Requirement UIDs

  • ASR-532 (Runtime Backend Result Integrity). Its normative contract, specs/formal/runtime-contracts/backend-result-admission.md, applies to "direct-manager and authenticated control-plane backend invocation", requires "an isolated immediate predecessor", and says a backend "MUST NOT ... change unrelated resources, or mutate runtime-owned metadata or provenance". This PR adds the scheduler module and the new regression test to the ASR-532 record's Traceability.

Related Issues

Closes #1439

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/participant_scheduler_initialization.py: _ensure_participant_episode builds the ParticipantEpisodeInitializeRequest once and calls participant_runtime.initialize through _call_backend_apply, with realization=participant_effect_authority(request, snapshot) and the address runtime.participant-scheduler.<participant>.initialize. A refused result fails the participant execution phase with runtime.backend-contract-invalid and keeps the predecessor. An exception from initialize now also fails the phase and keeps the predecessor, instead of propagating out of RuntimeManager.apply(): TypeError and ValueError give runtime.backend-contract-invalid, and any other Exception gives runtime.backend-call-failed. The call passes no _BackendCallContext, so it has no information-state context resolver, and the code comment says so.
  • implementations/python/tests/test_issue_1439_participant_initialize_gate.py (new), on the DSL-437 autonomous fixture (_scenario_yaml, _autonomous_manifest, _NativeParticipantRuntime):
    • The honest runtime still applies successfully.
    • Parametrized cases: an initialize that writes its snapshot argument, and one that returns a snapshot without the accepted entries. Each makes the apply fail with runtime.backend-contract-invalid. The forged metadata is not committed, no episode result is committed, and the provisioned entries equal the honest run's.
  • docs/requirements/ASR-532/requirement.md: two Traceability lines, IMPLEMENTS → CODE_FILE for the scheduler module and TESTS → TEST for the new test module, and updated_at set to 2026-10-09.
  • 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-1439 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. None of the 27 replayed cases changed digest or outcome, 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_1439_participant_initialize_gate.py -q -p no:cacheprovider passed (3 passed). With -m integration it selects nothing (3 deselected), because all three tests use the default markers. With dev's participant_scheduler_initialization.py swapped in, the same command failed both parametrized cases with assert True is False (the apply succeeded) and passed the honest case (2 failed, 1 passed). Every case runs the changed call, so the new lines are covered. The new module has no case where initialize raises, so a probe on the same fixture checked that path. RuntimeError gave runtime.backend-call-failed, and ValueError and TypeError gave runtime.backend-contract-invalid. Each apply failed with the 16 provisioned entries kept and no episode result committed. With dev's module swapped in, all three exceptions escaped RuntimeManager.apply().

The 36 test modules that build autonomous policies, pass a participant runtime, use the participant scheduler or reuse the native participant runtime fixture passed: 962 passed. This command selects and runs them from the repository root:

uv run --project implementations/python --frozen --all-extras python -m pytest -q -p no:cacheprovider \
  $(grep -lE 'autonomous_execution|_autonomous_manifest|ParticipantScheduler|participant_scheduler|participant_runtime=|_NativeParticipantRuntime' implementations/python/tests/test_*.py)

They include the test_dsl_437_*, test_act_614_*, test_issue_898_* and test_issue_899_* modules, test_participant_concurrent_batch_reservations.py and test_issue_1204_targeted_effects.py. None of their tests is integration-marked: adding -m "integration and not docker" deselects all 962.

tools/check_requirement_governance.py --base-rev origin/dev --requirement-uid ASR-532 exited 1 before the record change, with traceability-missing-implements for the scheduler module and traceability-missing-tests for the new test. With the two new lines it exits 0, also with --require-governance.

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 three evidence test modules passed: 209 passed and 19 deselected with default markers, and 19 passed with -m integration. RAES_REQUIREMENT_UID=ASR-532 nox -s verify-fast-feedback -- --base-rev origin/dev passed every stage, including requirement governance; YAML syntax was skipped because no YAML file changed. It ran the four directly changed test modules (212 passed, 19 deselected). nox -s lint passed.

CI resolves no requirement UID from this branch name, and the branch has no requirement scope file, so CI skips the requirement governance stage.

Ground Control Checks

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

RAES_REQUIREMENT_UID=ASR-532 make policy passed every stage, including requirement governance for ASR-532. 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: FM not applicable. No semantic rule, state machine or contract changes; the scheduler now applies the control plane's backend call gate and participant effect authority to this backend method. 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.

… the control plane

The autonomous scheduler called participant_runtime.initialize with the apply's live working snapshot and adopted the returned snapshot unchecked, so an initialize could write runtime-owned metadata or drop accepted resource entries while the apply reported success. The control plane already invokes the same backend method through _call_backend_apply with participant_effect_authority.

_ensure_participant_episode now calls it through _call_backend_apply with the same participant effect authority, so the arguments are detached and the result may change only participant-owned state for the named participant. Unlike the control plane call, it passes no information-state context resolver.

ASR-532 now traces the scheduler module and the new regression test.
@doublewhy
doublewhy force-pushed the 1439-participant-initialize-gate branch from 8e966c6 to a173304 Compare October 9, 2026 13:26
@doublewhy
doublewhy marked this pull request as ready for review October 9, 2026 15:31

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