Purge stale actor_sessions at OODA daemon startup (self-heal cross-host/observe_only leases) - #5009
Merged
rysweet merged 1 commit intoJul 30, 2026
Conversation
Clear transient actor-session leases before daemon goal-cycle work while preserving durable typed-OODA ledger data and live-session scope enforcement. Keep the purge crate-internal and document its lifecycle boundary. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
📊 Coverage Summary
Coverage data from CI run. Test files matching |
Owner
Author
Crusty review + merge-ready evidenceCrusty review: PASS.
Merge-ready criteria:
Verdict: merge-ready. |
rysweet
deleted the
feat/issue-5005-harden-the-typed-ooda-daemon-so-stale-actorsession
branch
July 30, 2026 04:59
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On the deva3 host migration, every OODA goal cycle failed with
actor session is already bound to a different identity or authorization scope(
AuthorizationScopeViolation,src/typed_ooda/ledger.rs).Root cause: the migrated ledger (
typed-ooda/outcomes.sqlite3) carried 3,976 staleactor_sessionsrows from the previous host, all created inobserve_only=0mode andfuture-dated (so the existing
DELETE ... WHERE expires_at < nowsweep never cleared them).The new host runs
observe_only=1, andscope_keyincludesobserve_only— so every reusedper-goal
session_idcollided with a stale row and was rejected forever. This recurs on anyrestart / migration /
observe_onlychange; it is a latent bug, not a one-off.Fix
Purge the transient
actor_sessionstable once at authoritative daemon startup, before anygoal cycle runs. At boot no actor session can still be in flight, so the leases are safe to
clear; runtime scope enforcement is completely unchanged.
Ledger::purge_actor_sessions()—DELETE FROM actor_sessions(ledger.rs)purge_actor_sessions_on_startup()wired into the daemon init path, immediately aftercreate_dir_all(&state_root)and before the draining-flag clear / freshness gate (daemon/mod.rs)startup_purge_removes_stale_actor_session_but_preserves_live_scope_guardproves astale future-dated lease is removed and that a genuine in-process cross-scope collision
is still rejected (
AuthorizationScopeViolation) afterwards.docs/reference/actor-session-startup-purge.md.Closes #5005
Merge-ready evidence
Crusty-old-engineer review: PASS. Fix rests on the invariant "no actor_session is
meaningful across a restart"; verified orphaned engineer subprocesses are tracked separately
in
engineer_claims(fail-closed liveness gate), so purging authorization leases cannot causea double-actor. Fresh-install safe:
CapabilityHandler::open()runsschema::initialize()eagerly and
schema.rs:73createsactor_sessionswithCREATE TABLE IF NOT EXISTS, so theDELETEcan't fail on first boot. Fail-closed?posture is correct for a daemon that must notrun on a broken ledger.
QA-team: PASS (Rust-native harness). This is a Rust CLI repo (
Cargo.tomlat root), socargo testis the QA harness per the qa-team skill's repo-type detection. Ran on the PR branch:startup_purge_removes_stale_actor_session_but_preserves_live_scope_guard— passactor_sessiongroup (7 tests) andtyped_ooda::ledgergroup (19 tests) — all pass,including
changed_observe_only_on_reused_session_is_rejected(proves the scope guard that wasthe root of the migration bug still rejects live scope changes).
Quality-audit: PASS (3 cycles, final cycle clean).
no
todo!/unimplemented!/bail!/panic!/TODO markers; production paths use?+map_err(persistence)(fail-visible); the 8expect()are all test-only.pub(crate)fn is documented,both new functions are small (~5 and ~13 lines), errors surfaced not swallowed.
Checks/build: all required CI green for head;
mergeable=MERGEABLE,mergeStateStatus=CLEAN.Docs: updated — new
actor-session-startup-purge.mdplus related reference/how-to pages.Reviews/policies:
mainis not branch-protected; no required approvals outstanding; no unresolved threads.Linked issue:
Closes #5005.Scope: diff limited to
src/typed_ooda/ledger.rs,src/operator_commands_ooda/daemon/mod.rs, and docs — no unrelated changes.Verdict: MERGE_READY.