Skip to content

refactor(metadata): retire assign_session_sandbox (ADR 0123 C4) - #1592

Merged
nikhilunni merged 1 commit into
mainfrom
refactor/retire-assign-session-sandbox
Oct 6, 2026
Merged

nikhilunni merged 1 commit into
mainfrom
refactor/retire-assign-session-sandbox

Conversation

@nikhilunni

Copy link
Copy Markdown
Contributor

Summary

ADR 0123 C4. Retire the blind sandbox setter assign_session_sandbox from MetadataStore.

  • Every remaining sandbox binding write is fenced or guarded: transition_session_created, fenced_assign_sandbox, assign_session_sandbox_guarded, rebind_session_guarded, teleport_commit.
  • fenced_assign_sandbox now carries the cleanup the blind setter owned: strike reset on bind and unbind, live-manifest clear and chunk_generation bump on unbind, in one statement (PG) and one lock (sim).
  • Reconcile's strike-out clear is one CAS, including the absent-binding case (expected_current = Some(None)), so a binding that landed between the strike decision and the clear is never wiped.
  • Trait defaults for the guarded writes now unimplemented! instead of composing a read with the blind setter; both stores implement them.
  • assign_session_host stays for dead_host.rs only, and says so.
  • ~35 test fixtures move to fenced_assign_sandbox(.., 0, ..); the binding-writer inventory drops the retired rows.

Conformance (ADR 0098 D4)

  • binding_epoch_minted_by_binding_writes extended: the absent-binding CAS rejects a newly bound sandbox.
  • New fenced_binding_preserves_cleanup_and_strike_reset: a rejected clear keeps the pin and the streak; a bind resets strikes and mints one epoch; a clear removes the pin, bumps the generation, mints nothing.

Validation

  • cargo fmt --check, cargo clippy -D warnings
  • engram-sim conformance against sim and live PG: 160 passed
  • live-PG lane: 2680 passed
  • just check green

Stacked on #1591.

🤖 Generated with Claude Code

@engrams-agent

engrams-agent Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

👀 engrams is reviewing 46a04ef.

nikhilunni added a commit that referenced this pull request Oct 6, 2026
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01965DMBwLXzE9baCmj1Wp8Q

@engrams-agent engrams-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Engrams review

Verdict: 1 finding included in this summary.
Severity: Critical 0 · High 1 · Medium 0 · Low 0
Categories: 🗄️ Data Integrity & Integration: 1

View the full engrams review

Findings on the review page

deploy/migrations/0122_teleport_machine.sql:L42 — Migration 0122 settle ignores and discards live_disk_manifest, marking disk-recoverable sessions Dead

WHAT: The orphaned-evacuating settle in migration 0122 decides Idle vs. Dead from a recoverable memory snapshot alone and then NULLs live_disk_manifest_id/version/at — but the rest of the system treats a live disk manifest as an independent, valid recovery basis, so this both mis-classifies disk-only-recoverable sessions as Dead and strips the cold-boot fallback that resume relies on.

The settle mirrors the teleport "lost source" path per its comment, but diverges from it in two ways that cost recoverable sessions:

  1. Wrong Idle/Dead decision (lines 28-32). The CASE routes to 'dead' whenever no recoverable snapshot exists, even if the session has a live disk manifest. The system's honest-recoverability predicate is explicit that this is wrong: dead_host::recovery_target (dead_host.rs:504-512) returns Idle iff has_recoverable_snapshot || has_live_manifest — "the latter still resumes via the cold-boot path" — and routing such a session to Dead is exactly the "Dead that lies about resumability" the #777 / ADR 0098 Phase-3 "honest-Dead" work forbids. An Active session being live-migrated normally carries an ADR-0028 continuous-sync live_disk_manifest and may have no recent recoverable memory snapshot, so this hits the migration's own target population.

  2. Destroys the cold-boot fallback (lines 34-36). The code path this migration claims parity with (teleport::steps::settle_lost_source / fail_move, teleport.rs:1081, 571) settles via fenced_transition_session with BindingDisposition::Detach, whose UPDATE nulls only sandbox_id (postgres/src/lib.rs:3024-3045) and PRESERVES live_disk_manifest_*. The migration additionally nulls the manifest. resume_from_idle walks snapshots newest-first and, when no snapshot artifacts are present, falls back to resume_disk_only_cold_boot via session.live_disk_manifest (snapshot.rs:1066-1108). Nulling the manifest removes that last-resort rung even for sessions settled to Idle.

Net effect: a session that the dead-host/resume machinery would recover is instead left permanently unrecoverable (user must fork), with no manifest record remaining.

WHEN:

  1. Deploy applies migration 0122 while sessions are mid-move under the old evac scanner. A targeted session has a live disk manifest but no recoverable memory snapshot → the migration marks it dead and nulls its manifest → a disk-only-recoverable session is permanently lost (routine for live-migrating sessions at upgrade).
  2. A targeted session has a recoverable snapshot (→ idle) plus a live manifest; the migration nulls the manifest; later the snapshot's artifacts are found missing at /resume (the 89f7984d class the resume chain defends against) → resume cannot fall back to disk-only cold boot and flips the session Dead instead of recovering it.

Trigger likelihood: routine

@nikhilunni
nikhilunni force-pushed the refactor/retire-assign-session-sandbox branch from a1294b8 to 5c2762c Compare October 6, 2026 17:01
@nikhilunni

Copy link
Copy Markdown
Contributor Author

Addressed before this head: the 0122 settle now uses the dead-host predicate (Idle when a recoverable memory snapshot OR a live disk manifest exists, Dead only with nothing recoverable) and keeps live_disk_manifest_*; it drops only the binding, the way the dead-host orphan step does. The migration lives in #1591 (commit dd69420 on that branch); this PR inherits it. The teleport rollback's lost-source settle uses the same predicate (settle_target), with a sim + Postgres scenario: rollback_with_a_destroyed_source_keeps_a_disk_only_session_idle.

@nikhilunni
nikhilunni added this pull request to stack #1595 October 6, 2026 17:38
@nikhilunni
nikhilunni force-pushed the refactor/retire-assign-session-sandbox branch from 5c2762c to 254b997 Compare October 6, 2026 17:40
@nikhilunni
nikhilunni force-pushed the refactor/retire-assign-session-sandbox branch from 254b997 to c977109 Compare October 6, 2026 17:42
Base automatically changed from feat/teleport-machine to main October 6, 2026 17:44
Rebuilt onto main after the squash merge of #1591; content unchanged.

Remove the blind sandbox setter from MetadataStore, both stores, and all
mocks. Use the surviving fenced binding writes in test fixtures. Make
reconcile compare the exact current binding, including None. Keep
assign_session_host for dead-host cleanup. Preserve strike reset and
live-manifest cleanup in fenced assignment; extend conformance coverage.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01965DMBwLXzE9baCmj1Wp8Q
@nikhilunni
nikhilunni force-pushed the refactor/retire-assign-session-sandbox branch from c977109 to 46a04ef Compare October 6, 2026 17:44
@nikhilunni
nikhilunni merged commit 23c28c5 into main Oct 6, 2026
3 checks passed
@nikhilunni
nikhilunni deleted the refactor/retire-assign-session-sandbox branch October 6, 2026 17:44
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