Skip to main content

mj_controller/hel_controller/
checkpoint.rs

1//! Checkpoint export, latching, verification, and archive bookkeeping.
2
3use std::collections::BTreeSet;
4use std::ffi::OsStr;
5use std::path::{Path, PathBuf};
6use std::time::{Duration, Instant};
7
8use anyhow::{Context, Result, bail, ensure};
9
10use crate::hel_session_manager::{
11    ManagedSessionHandle, ManagedSessionLease, SessionManagerControl, StandaloneSession,
12    new_command_id, worker_connect_needs_restart,
13};
14use crate::hel_worker_client::RelayRejected;
15use hel::hel_archive::{
16    BundleManifest, CanonicalSessionSnapshot, SessionManifest, TargetManifest,
17    verify_archive_streaming,
18};
19use hel::hel_checkpoint::{
20    CHECKPOINT_EXPORT_PROTOCOL_VERSION, CHECKPOINT_STAGING_PROTOCOL_VERSION, CapturedCheckpoint,
21    CheckpointCaptureSpec, CheckpointExportSpec, CheckpointPackSpec, CheckpointRepositoryCapture,
22    CheckpointRepositorySpec, CheckpointTransfer, canonical_session_contains_prompt,
23    capture_stdin_command, checkpoint_sha256, export_command, export_stdin_command,
24    pack_stdin_command,
25};
26use hel::hel_config::{HarnessKind, sessions_dir};
27use hel::hel_projection::canonical_session_from_materialized;
28use hel::hel_state::{
29    CheckpointMetadata, HelState, ManagedSessionSnapshot, SessionRecord, SessionState,
30};
31use hel::hel_targets::{
32    self, CommandExecutor, CommandOutput, CommandSpec, ProcessExecutor, ProvisionStage,
33    ProvisionStageGuard,
34};
35use hel::hel_worker::{RelayCommand, RelayCursor, RelayExecutionState};
36
37use super::backend::backend_locator;
38use super::readiness::wait_for_native_session_in_stage;
39use super::worker_restart::{InstalledWorkerRestart, RESTART_FOR_CHECKPOINT};
40use super::{
41    Controller, execute_checked, now, persist_session_record_transition_or_restore,
42    scp_command_spec, ssh_command_spec, target_kind, target_profile_home,
43};
44
45/// How long an idle relay may fail to admit a barrier before its worker is
46/// treated as wedged. Busy recovery checkpoints defer immediately. A close
47/// sends a non-steering turn cancellation and gives the worker this same
48/// bounded interval to settle before recovery restarts it.
49const CHECKPOINT_BARRIER_TIMEOUT: Duration = Duration::from_secs(30);
50/// A close gets a fresh cancellation grace period once the worker accepts the
51/// request. This keeps an expensive status sync from consuming the whole
52/// cancellation budget before the worker has had a chance to settle.
53const CHECKPOINT_CANCEL_TIMEOUT: Duration = Duration::from_secs(30);
54/// After a wedged ACP forces a worker restart, wait as long as native-session
55/// startup: session/load of a long kimi transcript can outlast 30s.
56const CHECKPOINT_BARRIER_TIMEOUT_AFTER_RESTART: Duration = Duration::from_secs(300);
57
58/// Remove checkpoint archives installed by a process that exited before its
59/// database transaction committed. Call this only while holding the
60/// machine-wide controller-store guard and before starting background work.
61pub fn reconcile_managed_checkpoint_archives() -> Result<usize> {
62    let mut state = HelState::load()?;
63    // Include operation-owned recovery copies even after a ready destination
64    // installs a newer ordinary checkpoint.
65    for operation in hel::hel_database::load_move_operations()? {
66        if operation.retains_checkpoint()
67            && let Some(checkpoint) = operation.checkpoint
68            && let Some(mut session) = state.sessions.get(&operation.selection.session_id).cloned()
69        {
70            session.checkpoint = Some(checkpoint);
71            state
72                .sessions
73                .insert(format!("move:{}", operation.operation_id), session);
74        }
75    }
76    reconcile_managed_checkpoint_archives_in(&sessions_dir(), &state)
77}
78
79fn reconcile_managed_checkpoint_archives_in(directory: &Path, state: &HelState) -> Result<usize> {
80    if !directory.exists() {
81        return Ok(0);
82    }
83    let referenced_names = state
84        .sessions
85        .values()
86        .filter_map(|session| session.checkpoint.as_ref())
87        .filter_map(|checkpoint| checkpoint.archive_path.file_name())
88        .map(ToOwned::to_owned)
89        .collect::<BTreeSet<_>>();
90    let mut removed = 0;
91    for entry in std::fs::read_dir(directory)
92        .with_context(|| format!("scan checkpoint directory {}", directory.display()))?
93    {
94        let entry = entry?;
95        let file_type = entry.file_type()?;
96        if !file_type.is_file()
97            || !is_managed_checkpoint_archive_name(&entry.file_name())
98            || referenced_names.contains(&entry.file_name())
99        {
100            continue;
101        }
102        std::fs::remove_file(entry.path()).with_context(|| {
103            format!(
104                "remove unreferenced managed checkpoint {}",
105                entry.path().display()
106            )
107        })?;
108        removed += 1;
109    }
110    Ok(removed)
111}
112
113fn is_managed_checkpoint_archive_name(name: &OsStr) -> bool {
114    let Some(stem) = name.to_str().and_then(|name| name.strip_suffix(".hel.zip")) else {
115        return false;
116    };
117    let Some((frontier_prefix, nonce)) = stem.rsplit_once("-archive-") else {
118        return false;
119    };
120    if nonce.len() != 32
121        || !nonce
122            .bytes()
123            .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase())
124    {
125        return false;
126    }
127    let Some((session_id, frontier)) = frontier_prefix.rsplit_once('-') else {
128        return false;
129    };
130    !session_id.is_empty()
131        && frontier.parse::<u64>().is_ok()
132        && hel::hel_config::validate_id("session", session_id).is_ok()
133}
134
135#[derive(Debug, Clone)]
136pub struct CheckpointArtifact {
137    pub metadata: CheckpointMetadata,
138    pub native_session_id: String,
139    /// Digest paired with `metadata.event_frontier` at the relay barrier.
140    pub event_frontier_digest: String,
141}
142
143/// The relay connection one lifecycle operation talks to.
144///
145/// A managed operation borrows the session actor's own connection instead of
146/// opening a competing one. Exclusivity is only needed while a checkpoint
147/// latches its projection at the barrier's ready cursor; `end_latch` hands the
148/// connection back so the dashboard keeps syncing and submitting while the
149/// archive exports and transfers.
150pub(super) enum ControllerRelayLease {
151    Managed {
152        handle: ManagedSessionHandle,
153        lease: Option<ManagedSessionLease>,
154    },
155    Standalone(StandaloneSession),
156}
157
158impl ControllerRelayLease {
159    /// The exclusively held connection. Only a latch phase, or an operation
160    /// that deliberately holds its lease to the end, may use this.
161    pub(super) fn connection_mut(&mut self) -> &mut StandaloneSession {
162        match self {
163            Self::Managed { lease, .. } => lease
164                .as_mut()
165                .expect("checkpoint latch has already returned its connection")
166                .connection_mut(),
167            Self::Standalone(connection) => connection,
168        }
169    }
170
171    async fn submit(&mut self, command_id: String, command: RelayCommand) -> Result<u64> {
172        match self {
173            Self::Managed {
174                lease: Some(lease), ..
175            } => lease.connection_mut().submit(command_id, command).await,
176            Self::Managed { handle, .. } => handle.submit(command_id, command).await,
177            Self::Standalone(connection) => connection.submit(command_id, command).await,
178        }
179    }
180
181    async fn sync_snapshot(&mut self) -> Result<ManagedSessionSnapshot> {
182        match self {
183            Self::Managed {
184                lease: Some(lease), ..
185            } => lease.connection_mut().sync().await,
186            Self::Managed { handle, .. } => {
187                handle.sync_now().await?;
188                handle
189                    .view()
190                    .snapshot
191                    .context("managed session has no snapshot")
192            }
193            Self::Standalone(connection) => connection.sync().await,
194        }
195    }
196
197    /// Swap the proxy after the worker process behind it was restarted.
198    fn replace_connection(&mut self, connection: StandaloneSession) {
199        match self {
200            Self::Managed {
201                lease: Some(lease), ..
202            } => lease.replace_connection(connection),
203            Self::Standalone(existing) => *existing = connection,
204            Self::Managed { lease: None, .. } => {
205                *self = Self::Standalone(connection);
206            }
207        }
208    }
209
210    /// Return the connection to its session actor now that the projection is
211    /// latched. Releasing keeps the connection alive, so the relay barrier it
212    /// opened stays open. Idempotent.
213    fn end_latch(&mut self) {
214        if let Self::Managed { lease, .. } = self
215            && let Some(lease) = lease.take()
216        {
217            lease.release();
218        }
219    }
220
221    /// Abandon a checkpoint barrier this controller can no longer complete.
222    ///
223    /// A relay barrier belongs to the connection that opened it and only a
224    /// disconnect cancels it (`cancel_checkpoint_barrier_on_disconnect`).
225    /// Completing it instead would advance the relay's recovery floor past
226    /// history that no verified checkpoint covers, so reclaim the connection
227    /// and drop it: the worker cancels the barrier and resumes dispatch.
228    async fn cancel_abandoned_barrier(&mut self) -> Result<()> {
229        let Self::Managed { handle, lease } = self else {
230            // A standalone connection is dropped with this value, which the
231            // worker sees as the same disconnect.
232            return Ok(());
233        };
234        match lease.take() {
235            Some(lease) => drop(lease),
236            None => drop(handle.lease_connection().await?),
237        }
238        Ok(())
239    }
240
241    pub(super) fn release(self) {
242        if let Self::Managed {
243            lease: Some(lease), ..
244        } = self
245        {
246            lease.release();
247        }
248    }
249}
250
251/// Whether a checkpoint keeps its exclusive connection after latching.
252#[derive(Debug, Clone, Copy, PartialEq, Eq)]
253pub(super) enum LatchExclusivity {
254    /// Ordinary and recovery checkpoints only need exclusivity to latch the
255    /// projection at the barrier's ready cursor. Everything after that runs
256    /// through the session actor, so prompts keep flowing while the archive
257    /// exports and transfers.
258    ReleaseAfterLatch,
259    /// Close seals the relay at the exact latched cursor, so nothing else may
260    /// reach the relay between the barrier and its Close command.
261    HoldThroughClose,
262}
263
264/// Whether a latched checkpoint must export a fresh archive.
265#[derive(Debug, Clone, Copy, PartialEq, Eq)]
266pub(super) enum CheckpointExportPolicy {
267    /// Always export, transfer, and install a new archive.
268    Always,
269    /// Keep the installed archive when the latched projection holds the same
270    /// session content. Relay bookkeeping (the checkpoint commands themselves)
271    /// always moves the event frontier, so only content can decide this.
272    ReuseUnchangedArchive,
273}
274
275/// How a latched checkpoint ends the barrier it opened.
276#[derive(Debug, Clone, Copy, PartialEq, Eq)]
277pub(super) enum CheckpointCompletion {
278    /// The barrier is still open. Completing it resumes ACP dispatch and
279    /// advances the relay's recovery floor in one durable step; abandoning it
280    /// cancels the barrier and leaves the floor alone.
281    HeldBarrier,
282    /// The worker already resumed dispatch when target capture finished. All that
283    /// is left for a durably installed archive is the recovery floor move.
284    ReleasedAfterCapture,
285}
286
287pub(super) struct LatchedCheckpoint {
288    pub(super) artifact: CheckpointArtifact,
289    pub(super) relay: ControllerRelayLease,
290    pub(super) barrier_command_id: String,
291    pub(super) cursor: RelayCursor,
292    pub(super) completion: CheckpointCompletion,
293}
294
295/// A latched checkpoint owns an open relay barrier, and that barrier freezes
296/// ACP dispatch until something ends it. Every path out of one must therefore
297/// either [`LatchedCheckpoint::complete`] it or [`LatchedCheckpoint::abandon`]
298/// it; both consume the value so a new exit cannot quietly skip the choice.
299/// Close is the exception: it holds its lease to the end, so dropping that
300/// lease is what ends its barrier.
301impl LatchedCheckpoint {
302    /// Let the relay release the history that this installed archive covers.
303    async fn complete(mut self) -> Result<()> {
304        let (prefix, command) = match self.completion {
305            CheckpointCompletion::HeldBarrier => (
306                "checkpoint-complete",
307                RelayCommand::CompleteCheckpoint {
308                    barrier_command_id: self.barrier_command_id.clone(),
309                },
310            ),
311            // The worker that accepted the early release also understands the
312            // floor move; they were added together.
313            CheckpointCompletion::ReleasedAfterCapture => (
314                "checkpoint-floor",
315                RelayCommand::AdvanceRecoveryFloor {
316                    through: self.cursor.clone(),
317                },
318            ),
319        };
320        let command_id = new_command_id(prefix)?;
321        self.relay.submit(command_id, command).await.map(|_| ())
322    }
323
324    /// Cancel the barrier of a checkpoint the caller could not install.
325    ///
326    /// The latch is already back with the session actor, whose connection can
327    /// stay healthy for the rest of the session, so nothing else would ever
328    /// end this barrier.
329    async fn abandon(mut self, session_id: &str) {
330        if self.completion == CheckpointCompletion::ReleasedAfterCapture {
331            // Dispatch resumed when target capture finished, so there is no barrier
332            // left to cancel, and the recovery floor must stay behind an
333            // archive that was never installed. Doing nothing is the exit.
334            return;
335        }
336        if let Err(error) = self.relay.cancel_abandoned_barrier().await {
337            tracing::warn!(
338                session_id,
339                "abandoned checkpoint could not cancel its relay barrier: {error:#}"
340            );
341        }
342    }
343}
344
345impl Controller {
346    pub(super) fn persist_checkpoint_transition_or_restore(
347        &mut self,
348        session_id: &str,
349        previous: &SessionRecord,
350        context: &'static str,
351    ) -> Result<()> {
352        persist_session_record_transition_or_restore(
353            &mut self.state,
354            session_id,
355            previous,
356            context,
357            &hel::hel_database::save_checkpointed_session,
358        )
359    }
360
361    pub(super) fn persist_failed_checkpoint_state_or_restore(
362        &mut self,
363        session_id: &str,
364        previous: &SessionRecord,
365        primary: anyhow::Error,
366    ) -> anyhow::Error {
367        match self.persist_session_state(session_id) {
368            Ok(()) => primary,
369            Err(error) => self.restore_prior_session_after_persistence_failure(
370                session_id,
371                previous,
372                primary.context(format!(
373                    "failed to persist the checkpoint rollback state: {error:#}"
374                )),
375            ),
376        }
377    }
378
379    /// Materialize and locally verify a complete session checkpoint while the
380    /// target remains live. A failed export or transfer leaves the previous
381    /// archive and target untouched.
382    pub async fn checkpoint_session(&mut self, session_id: &str) -> Result<CheckpointMetadata> {
383        self.checkpoint_session_controlled(session_id, &ProcessExecutor)
384            .await
385    }
386
387    pub async fn checkpoint_session_controlled(
388        &mut self,
389        session_id: &str,
390        executor: &(impl CommandExecutor + Sync),
391    ) -> Result<CheckpointMetadata> {
392        self.checkpoint_session_controlled_with_manager(session_id, executor, None)
393            .await
394    }
395
396    async fn checkpoint_session_controlled_with_manager(
397        &mut self,
398        session_id: &str,
399        executor: &(impl CommandExecutor + Sync),
400        manager: Option<&SessionManagerControl>,
401    ) -> Result<CheckpointMetadata> {
402        let previous = self
403            .state
404            .sessions
405            .get(session_id)
406            .with_context(|| format!("unknown session {session_id}"))?
407            .clone();
408        ensure!(
409            !matches!(
410                previous.state,
411                SessionState::Closing | SessionState::Destroying
412            ),
413            "session {session_id} is already closing; resume that close instead of starting an ordinary checkpoint"
414        );
415        let record = self.state.sessions.get_mut(session_id).unwrap();
416        record.state = SessionState::Checkpointing;
417        record.updated_at = now();
418        record.last_checkpoint_error = None;
419        self.persist_session_transition_or_restore(
420            session_id,
421            &previous,
422            "persist checkpointing state before creating a checkpoint",
423        )?;
424
425        match self
426            .checkpoint_session_latched(
427                session_id,
428                executor,
429                manager,
430                LatchExclusivity::ReleaseAfterLatch,
431                CheckpointExportPolicy::Always,
432            )
433            .await
434        {
435            Ok(latched) => {
436                let artifact = latched.artifact.clone();
437                if let Err(error) = hel::hel_test_hooks::reach_test_hook(
438                    "checkpoint_archive_before_database_publication",
439                ) {
440                    latched.abandon(session_id).await;
441                    return Err(remove_uninstalled_checkpoint(
442                        &artifact.metadata.archive_path,
443                        error,
444                    ));
445                }
446                {
447                    let record = self.state.sessions.get_mut(session_id).unwrap();
448                    record.state = SessionState::Running;
449                    record.native_session_id = Some(artifact.native_session_id.clone());
450                    record.checkpoint = Some(artifact.metadata.clone());
451                    record.updated_at = now();
452                    record.last_error = None;
453                    record.last_checkpoint_error = None;
454                }
455                let persist_started = Instant::now();
456                if let Err(error) = self.persist_checkpoint_transition_or_restore(
457                    session_id,
458                    &previous,
459                    "persist verified checkpoint before releasing relay history",
460                ) {
461                    latched.abandon(session_id).await;
462                    return Err(error);
463                }
464                tracing::info!(
465                    session_id,
466                    persist_ms = persist_started.elapsed().as_millis() as u64,
467                    "checkpoint metadata persisted"
468                );
469                prune_replaced_checkpoint(previous.checkpoint.as_ref(), &artifact.metadata);
470                release_projection_behind_checkpoint(session_id, &artifact.metadata);
471                if let Err(error) = latched.complete().await {
472                    // Only journal retention is at stake. A barrier that is
473                    // still open cannot dangle: the actor retries a failed
474                    // submission over a fresh connection, and the worker
475                    // cancels barriers whose submitting connection dropped.
476                    // The next checkpoint moves the recovery floor again.
477                    tracing::warn!(
478                        session_id,
479                        "verified checkpoint was saved, but the relay could not be told to release the history it covers: {error:#}"
480                    );
481                }
482                Ok(artifact.metadata)
483            }
484            Err(error) => {
485                // A deferred checkpoint says the agent was working, not that
486                // anything failed. Recording it would leave a warning on the
487                // session row until the next successful copy, so the caller is
488                // told and the row is left alone.
489                let deferred = checkpoint_was_deferred(&error);
490                if let Some(record) = self.state.sessions.get_mut(session_id) {
491                    record.state = if previous.state == SessionState::Checkpointing {
492                        SessionState::Running
493                    } else {
494                        previous.state
495                    };
496                    record.updated_at = now();
497                    if !deferred {
498                        record.last_checkpoint_error = Some(format!("{error:#}"));
499                    }
500                }
501                Err(self.persist_failed_checkpoint_state_or_restore(session_id, &previous, error))
502            }
503        }
504    }
505
506    /// Create, checksum, and durably install a recovery archive before
507    /// allowing the relay to garbage-collect through its event frontier.
508    pub async fn create_recovery_checkpoint_managed_controlled(
509        &self,
510        session_id: &str,
511        manager: &SessionManagerControl,
512        executor: &(impl CommandExecutor + Sync),
513    ) -> Result<CheckpointArtifact> {
514        self.create_recovery_checkpoint_with_manager(session_id, Some(manager), executor)
515            .await
516    }
517
518    async fn create_recovery_checkpoint_with_manager(
519        &self,
520        session_id: &str,
521        manager: Option<&SessionManagerControl>,
522        executor: &(impl CommandExecutor + Sync),
523    ) -> Result<CheckpointArtifact> {
524        let previous_checkpoint = self
525            .state
526            .sessions
527            .get(session_id)
528            .with_context(|| format!("unknown session {session_id}"))?
529            .checkpoint
530            .clone();
531        let latched = self
532            .checkpoint_session_latched_with_recovery_stage(
533                session_id,
534                executor,
535                manager,
536                LatchExclusivity::ReleaseAfterLatch,
537                CheckpointExportPolicy::Always,
538                true,
539            )
540            .await?;
541        let artifact = latched.artifact.clone();
542        let verification = {
543            let _verifying = ProvisionStageGuard::new(executor, ProvisionStage::Verifying);
544            verify_checkpoint_artifact(session_id, &artifact)
545        };
546        if let Err(error) = verification {
547            latched.abandon(session_id).await;
548            return Err(remove_uninstalled_checkpoint(
549                &artifact.metadata.archive_path,
550                error.context("final recovery checkpoint verification"),
551            ));
552        }
553        if let Err(error) =
554            hel::hel_test_hooks::reach_test_hook("checkpoint_archive_before_database_publication")
555        {
556            latched.abandon(session_id).await;
557            return Err(remove_uninstalled_checkpoint(
558                &artifact.metadata.archive_path,
559                error,
560            ));
561        }
562        let persist_started = Instant::now();
563        if let Err(error) = hel::hel_database::record_recovery_success(
564            session_id,
565            &artifact.native_session_id,
566            &artifact.metadata,
567        ) {
568            latched.abandon(session_id).await;
569            return Err(error
570                .context("persist verified recovery checkpoint before releasing relay history"));
571        }
572        tracing::info!(
573            session_id,
574            persist_ms = persist_started.elapsed().as_millis() as u64,
575            "recovery checkpoint metadata persisted"
576        );
577        if let Err(error) = latched.complete().await {
578            // Only journal retention is at stake. A barrier that is still open
579            // cannot dangle: the actor retries a failed submission over a fresh
580            // connection, and the worker cancels barriers whose submitting
581            // connection dropped. The next checkpoint moves the floor again.
582            tracing::warn!(
583                session_id,
584                "recovery checkpoint was saved, but the relay could not be told to release the history it covers: {error:#}"
585            );
586        }
587        prune_replaced_checkpoint(previous_checkpoint.as_ref(), &artifact.metadata);
588        release_projection_behind_checkpoint(session_id, &artifact.metadata);
589        Ok(artifact)
590    }
591
592    pub(super) async fn checkpoint_session_latched(
593        &self,
594        session_id: &str,
595        executor: &(impl CommandExecutor + Sync),
596        manager: Option<&SessionManagerControl>,
597        exclusivity: LatchExclusivity,
598        export_policy: CheckpointExportPolicy,
599    ) -> Result<LatchedCheckpoint> {
600        self.checkpoint_session_latched_with_recovery_stage(
601            session_id,
602            executor,
603            manager,
604            exclusivity,
605            export_policy,
606            exclusivity == LatchExclusivity::HoldThroughClose,
607        )
608        .await
609    }
610
611    async fn checkpoint_session_latched_with_recovery_stage(
612        &self,
613        session_id: &str,
614        executor: &(impl CommandExecutor + Sync),
615        manager: Option<&SessionManagerControl>,
616        exclusivity: LatchExclusivity,
617        export_policy: CheckpointExportPolicy,
618        recovery_copy: bool,
619    ) -> Result<LatchedCheckpoint> {
620        if let Some(operation) = hel::hel_database::load_move_operation(session_id)?
621            && operation.queue_admission_started
622            && !operation.queue_admission_finished
623        {
624            // Advancing the recovery floor can prune terminal command IDs.
625            // Keep them until a retained Move queue has been fully admitted.
626            bail!(
627                "move queue admission is incomplete; retry Move before checkpointing this destination"
628            );
629        }
630        let session = self
631            .state
632            .sessions
633            .get(session_id)
634            .with_context(|| format!("unknown session {session_id}"))?
635            .clone();
636        let locator = session
637            .target
638            .as_ref()
639            .context("session has no live target")?;
640        let backend = backend_locator(locator, &session, &self.config)?;
641        let profile = self
642            .config
643            .profiles
644            .get(&session.last_profile)
645            .context("session profile is missing")?;
646        let bundle = session
647            .project_directory
648            .is_none()
649            .then(|| self.config.bundles.get(&session.bundle_id))
650            .flatten();
651        let reconnect = hel_targets::reconnect_plan(&backend, session_id)?
652            .commands
653            .into_iter()
654            .next()
655            .context("reconnect plan is empty")?;
656        let worker_root = hel_targets::worker_root(&backend, session_id)?;
657        let harness_home = target_profile_home(&backend, session_id, profile);
658        let (workspace_root, primary_repository, repositories) =
659            if let Some(project_directory) = &session.project_directory {
660                let parent = project_directory
661                    .parent()
662                    .context("bare project directory has no parent")?;
663                let destination = project_directory
664                    .file_name()
665                    .context("bare project directory cannot be the filesystem root")?;
666                (
667                    parent.to_string_lossy().into_owned(),
668                    "project".to_owned(),
669                    vec![CheckpointRepositorySpec {
670                        id: "project".into(),
671                        relative_destination: PathBuf::from(destination),
672                        // Managed worktrees are retired on Stop, so their
673                        // dirty/untracked state must travel in the archive.
674                        // Their branch and objects remain in the owning Git
675                        // repository; no remote origin is required. Unmanaged
676                        // raw checkouts remain in place.
677                        capture: if session.managed_worktree.is_some() {
678                            CheckpointRepositoryCapture::DeltaFrom {
679                                base_commit: super::worktree::raw_checkout_position(
680                                    &session,
681                                    &self.config,
682                                    project_directory,
683                                    executor,
684                                )?
685                                .head_commit,
686                            }
687                        } else {
688                            CheckpointRepositoryCapture::MetadataOnly
689                        },
690                        origin_override: None,
691                    }],
692                )
693            } else {
694                let bundle = bundle.context("session bundle is missing")?;
695                let workspace_root = match &backend {
696                    hel_targets::TargetLocator::LocalPodman { .. }
697                    | hel_targets::TargetLocator::LocalDocker { .. }
698                    | hel_targets::TargetLocator::AppleContainer { .. }
699                    | hel_targets::TargetLocator::SshPodman { .. }
700                    | hel_targets::TargetLocator::SshDocker { .. } => "/workspace".to_string(),
701                    hel_targets::TargetLocator::AwsEc2 { workspace, .. }
702                    | hel_targets::TargetLocator::SshBare { workspace, .. } => workspace.clone(),
703                    hel_targets::TargetLocator::LocalBare { worker_root } => worker_root.clone(),
704                };
705                let repositories = bundle
706                    .repositories
707                    .iter()
708                    .map(|repository| CheckpointRepositorySpec {
709                        id: repository.id.clone(),
710                        relative_destination: repository.destination.clone(),
711                        capture: CheckpointRepositoryCapture::RemoteWorkspace,
712                        origin_override: None,
713                    })
714                    .collect();
715                (workspace_root, bundle.primary_repo.clone(), repositories)
716            };
717        let target_path = |path: &str| match &backend {
718            hel_targets::TargetLocator::AwsEc2 { .. }
719            | hel_targets::TargetLocator::SshBare { .. }
720                if !path.starts_with('/') =>
721            {
722                PathBuf::from(format!("~/{path}"))
723            }
724            _ => PathBuf::from(path),
725        };
726        let remote_spec = format!("{worker_root}/checkpoint-spec.json");
727        let remote_archive = format!("{worker_root}/checkpoint.hel.zip");
728        let remote_stage = format!(
729            "{worker_root}/checkpoint-stage-{}",
730            new_command_id("capture")?
731        );
732        let checkpointed_at = now();
733        let target_manifest = TargetManifest {
734            template_id: session.target_template_id.clone(),
735            target_kind: target_kind(&backend).into(),
736            details: Default::default(),
737        };
738        let bundle_manifest = BundleManifest {
739            id: session.bundle_id.clone(),
740            primary_repository,
741        };
742        let session_manifest = |native_session_id: &str| SessionManifest {
743            id: session.id.clone(),
744            title: session.title.clone(),
745            harness_kind: session.harness_kind,
746            profile_id: session.last_profile.clone(),
747            native_session_id: native_session_id.to_owned(),
748            created_at: session.created_at.clone(),
749            checkpointed_at: checkpointed_at.clone(),
750            hel_version: env!("CARGO_PKG_VERSION").into(),
751            relay_version: env!("CARGO_PKG_VERSION").into(),
752            adapter_version: "acp-v1".into(),
753        };
754        let releases_after_capture = exclusivity == LatchExclusivity::ReleaseAfterLatch;
755        if releases_after_capture
756            && let Some(native_session_id) = session.native_session_id.as_deref()
757        {
758            let prestage = CheckpointCaptureSpec {
759                protocol_version: CHECKPOINT_STAGING_PROTOCOL_VERSION,
760                session: session_manifest(native_session_id),
761                target: target_manifest.clone(),
762                bundle: bundle_manifest.clone(),
763                relay_root: target_path(&worker_root),
764                harness_home: target_path(&harness_home),
765                workspace_root: target_path(&workspace_root),
766                repositories: repositories.clone(),
767                allow_empty_native: false,
768                stage_path: target_path(&remote_stage),
769                refresh_existing: false,
770            };
771            let prestage_started = Instant::now();
772            let prestaged = {
773                let _recovery_copy = recovery_copy
774                    .then(|| ProvisionStageGuard::new(executor, ProvisionStage::RecoveryCopy));
775                run_checkpoint_staging_command(
776                    executor,
777                    &backend,
778                    session_id,
779                    &prestage,
780                    capture_stdin_command,
781                    "prestage target checkpoint",
782                )
783            };
784            match prestaged {
785                Ok(output) => match serde_json::from_slice::<CapturedCheckpoint>(&output.stdout) {
786                    Ok(captured) => tracing::info!(
787                        session_id,
788                        prestage_ms = prestage_started.elapsed().as_millis() as u64,
789                        native_bytes = captured.native_bytes,
790                        repository_bytes = captured.repository_bytes,
791                        reused_native = captured.reused_native,
792                        "checkpoint target state prestaged while ACP dispatch remained active"
793                    ),
794                    Err(error) => tracing::warn!(
795                        session_id,
796                        error = format!("{error:#}"),
797                        "checkpoint prestage returned an invalid result; barrier capture will replace it"
798                    ),
799                },
800                Err(error) => {
801                    if executor.cancellation_requested() {
802                        return Err(error.context("checkpoint prestage was cancelled"));
803                    }
804                    tracing::warn!(
805                        session_id,
806                        error = format!("{error:#}"),
807                        "checkpoint prestage failed; barrier capture will collect a fresh generation"
808                    );
809                }
810            }
811        }
812        let (mut relay, mut restarted_worker) = self
813            .open_checkpoint_relay(
814                session_id,
815                executor,
816                manager,
817                InstalledWorkerRestart {
818                    backend: &backend,
819                    worker_root: &worker_root,
820                    reconnect: &reconnect,
821                    launch: None,
822                    messages: &RESTART_FOR_CHECKPOINT,
823                },
824                exclusivity == LatchExclusivity::HoldThroughClose
825                    || session.harness_kind != HarnessKind::Kimi,
826            )
827            .await?;
828        let (barrier, barrier_command_id) = loop {
829            // Restored native identity is not current-process readiness.
830            // Startup gets its own cancellable budget; its timeout must not
831            // enter the wedged-checkpoint worker-restart path below.
832            wait_for_native_session_in_stage(
833                relay.connection_mut(),
834                executor,
835                hel_targets::ProvisionStage::Starting,
836            )
837            .await?;
838            if exclusivity == LatchExclusivity::HoldThroughClose {
839                let snapshot = relay.connection_mut().sync().await?;
840                if snapshot.operational.capacity_retry.is_some() {
841                    // An explicit stop/move cancels recovery before sealing the
842                    // checkpoint. Routine recovery copies preserve its deadline.
843                    relay
844                        .connection_mut()
845                        .submit(
846                            new_command_id("cancel-capacity-retry")?,
847                            RelayCommand::CancelTurn,
848                        )
849                        .await?;
850                }
851            }
852            if exclusivity == LatchExclusivity::ReleaseAfterLatch {
853                let snapshot = relay.connection_mut().sync().await?;
854                if snapshot.operational.execution == RelayExecutionState::Running {
855                    // A routine recovery copy must not open a barrier just to
856                    // abandon it as soon as it observes the active turn.
857                    relay.release();
858                    return Err(CheckpointDeferred::harness_busy().into());
859                }
860                if !snapshot
861                    .operational
862                    .safe_for_checkpoint(session.harness_kind)
863                {
864                    // Kimi's native task level is process-owned workspace
865                    // work. Unknown or active work must defer before the
866                    // barrier is submitted; close deliberately does not use
867                    // this path and may still interrupt/terminate it.
868                    relay.release();
869                    return Err(CheckpointDeferred::background_work().into());
870                }
871            }
872            let barrier_command_id = new_command_id("checkpoint")?;
873            let timeout = if restarted_worker {
874                CHECKPOINT_BARRIER_TIMEOUT_AFTER_RESTART
875            } else {
876                CHECKPOINT_BARRIER_TIMEOUT
877            };
878            let result = {
879                let connection = relay.connection_mut();
880                connection
881                    .submit(
882                        barrier_command_id.clone(),
883                        RelayCommand::BeginCheckpoint {
884                            reason: Some("controller archive checkpoint".into()),
885                        },
886                    )
887                    .await?;
888                wait_for_checkpoint_barrier(
889                    connection,
890                    session_id,
891                    &barrier_command_id,
892                    timeout,
893                    BarrierBusyPolicy::of(exclusivity),
894                    session.harness_kind,
895                )
896                .await
897            };
898            match result {
899                Ok(barrier) => break (barrier, barrier_command_id),
900                Err(error)
901                    if !restarted_worker && checkpoint_barrier_needs_worker_restart(&error) =>
902                {
903                    if exclusivity == LatchExclusivity::ReleaseAfterLatch
904                        && session.harness_kind == HarnessKind::Kimi
905                    {
906                        let safe_to_restart =
907                            relay.connection_mut().sync().await.is_ok_and(|snapshot| {
908                                snapshot.operational.safe_to_replace(HarnessKind::Kimi)
909                            });
910                        if !safe_to_restart {
911                            return Err(error.context(CheckpointDeferred::background_work()));
912                        }
913                    }
914                    tracing::warn!(
915                        session_id,
916                        "checkpoint requires a worker restart; restarting and retrying: {error:#}"
917                    );
918                    let connection = self
919                        .restart_worker_for_checkpoint(
920                            session_id,
921                            executor,
922                            &backend,
923                            &worker_root,
924                            &reconnect,
925                        )
926                        .await?;
927                    relay.replace_connection(connection);
928                    restarted_worker = true;
929                }
930                Err(error) => return Err(error),
931            }
932        };
933        let barrier_ready_at = Instant::now();
934        // Project memory is checkpoint state, not relay connection state.
935        // Reconcile it once while the checkpoint barrier keeps the harness
936        // idle. Ordinary attach and polling deliberately never touch it.
937        relay
938            .connection_mut()
939            .sync_project_memory()
940            .await
941            .context("synchronize project memory for checkpoint")?;
942        let cursor = barrier
943            .operational
944            .checkpoint_ready
945            .clone()
946            .context("relay reported a checkpoint barrier without its ready cursor")?;
947        let materialized = barrier.materialized;
948        let expected_ordinal = materialized.applied_event_ordinal;
949        let expected_digest = materialized.applied_event_digest.clone();
950        ensure!(
951            expected_ordinal == barrier.operational.latest_ordinal,
952            "checkpoint projection frontier {expected_ordinal} does not match relay frontier {}",
953            barrier.operational.latest_ordinal
954        );
955        ensure!(
956            expected_digest == barrier.operational.latest_digest,
957            "checkpoint projection digest does not match the relay frontier digest"
958        );
959        ensure_exact_checkpoint_cut(&cursor, expected_ordinal, &expected_digest)?;
960        let canonical_session = canonical_session_from_materialized(&materialized)?;
961        let native_session_id = barrier
962            .operational
963            .native_session_id
964            .or_else(|| session.native_session_id.clone())
965            .context("harness did not report its native session ID")?;
966
967        // The latch holds: this projection sits exactly at the barrier's ready
968        // cursor. Exporting and transferring the archive needs the barrier, not
969        // the connection, so hand it back and let the dashboard keep syncing
970        // and submitting while the slow phase runs.
971        if exclusivity == LatchExclusivity::ReleaseAfterLatch {
972            relay.end_latch();
973        }
974
975        // Reuse before exporting: verifying an installed archive costs far less
976        // than exporting and transferring an identical one. A reused archive's
977        // frontier trails the cursor its caller seals by the checkpoint's own
978        // bookkeeping events, and only by those; resume rolls the controller's
979        // projection back to the archived record.
980        if export_policy == CheckpointExportPolicy::ReuseUnchangedArchive
981            // Host worktree edits do not advance the relay frontier. Always
982            // recapture before retiring one, including archives written by
983            // older workers that only recorded its Git metadata.
984            && session.managed_worktree.is_none()
985            && let Some(artifact) = reusable_installed_checkpoint(
986                session_id,
987                session.checkpoint.as_ref(),
988                &native_session_id,
989                cursor.ordinal,
990                &canonical_session,
991            )
992        {
993            return Ok(LatchedCheckpoint {
994                artifact,
995                relay,
996                barrier_command_id,
997                cursor,
998                completion: CheckpointCompletion::HeldBarrier,
999            });
1000        }
1001
1002        // Close must keep ACP dispatch frozen until it seals the relay, so only
1003        // an ordinary checkpoint may hand dispatch back at the end of its
1004        // export. `completion` also records whether an error path still has a
1005        // barrier to cancel.
1006        let mut completion = CheckpointCompletion::HeldBarrier;
1007
1008        let exported: Result<CheckpointArtifact> = async {
1009            let spec = CheckpointExportSpec {
1010                protocol_version: CHECKPOINT_EXPORT_PROTOCOL_VERSION,
1011                session: session_manifest(&native_session_id),
1012                target: target_manifest,
1013                bundle: bundle_manifest,
1014                relay_root: target_path(&worker_root),
1015                harness_home: target_path(&harness_home),
1016                workspace_root: target_path(&workspace_root),
1017                repositories,
1018                canonical_session,
1019                output_path: target_path(&remote_archive),
1020            };
1021            // Only the single-shot export path measures itself here; the
1022            // capture/pack path already logs its own phases above.
1023            let mut export_ms: Option<u64> = None;
1024            let exported = if releases_after_capture {
1025                let capture_spec = CheckpointCaptureSpec {
1026                    protocol_version: CHECKPOINT_STAGING_PROTOCOL_VERSION,
1027                    session: spec.session.clone(),
1028                    target: spec.target.clone(),
1029                    bundle: spec.bundle.clone(),
1030                    relay_root: spec.relay_root.clone(),
1031                    harness_home: spec.harness_home.clone(),
1032                    workspace_root: spec.workspace_root.clone(),
1033                    repositories: spec.repositories.clone(),
1034                    allow_empty_native: !canonical_session_contains_prompt(&spec.canonical_session),
1035                    stage_path: target_path(&remote_stage),
1036                    refresh_existing: true,
1037                };
1038                let capture_started = Instant::now();
1039                let captured = {
1040                    let _recovery_copy = recovery_copy.then(|| {
1041                        ProvisionStageGuard::new(executor, ProvisionStage::RecoveryCopy)
1042                    });
1043                    run_checkpoint_staging_command(
1044                        executor,
1045                        &backend,
1046                        session_id,
1047                        &capture_spec,
1048                        capture_stdin_command,
1049                        "capture target checkpoint",
1050                    )?
1051                };
1052                let captured: CapturedCheckpoint = serde_json::from_slice(&captured.stdout)
1053                    .context("decode captured checkpoint result")?;
1054                tracing::info!(
1055                    session_id,
1056                    capture_ms = capture_started.elapsed().as_millis() as u64,
1057                    barrier_held_ms = barrier_ready_at.elapsed().as_millis() as u64,
1058                    native_bytes = captured.native_bytes,
1059                    repository_bytes = captured.repository_bytes,
1060                    reused_native = captured.reused_native,
1061                    "checkpoint target state captured; releasing ACP dispatch"
1062                );
1063                completion = release_checkpoint_after_capture(
1064                    &mut relay,
1065                    session_id,
1066                    &barrier_command_id,
1067                    &cursor,
1068                    session.harness_kind,
1069                )
1070                .await?;
1071                let pack_spec = CheckpointPackSpec {
1072                    protocol_version: CHECKPOINT_STAGING_PROTOCOL_VERSION,
1073                    relay_root: spec.relay_root.clone(),
1074                    stage_path: target_path(&remote_stage),
1075                    canonical_session: spec.canonical_session.clone(),
1076                    output_path: spec.output_path.clone(),
1077                };
1078                let pack_started = Instant::now();
1079                let output = {
1080                    let _recovery_copy = recovery_copy.then(|| {
1081                        ProvisionStageGuard::new(executor, ProvisionStage::RecoveryCopy)
1082                    });
1083                    run_checkpoint_staging_command(
1084                        executor,
1085                        &backend,
1086                        session_id,
1087                        &pack_spec,
1088                        pack_stdin_command,
1089                        "pack target checkpoint",
1090                    )?
1091                };
1092                tracing::info!(
1093                    session_id,
1094                    pack_ms = pack_started.elapsed().as_millis() as u64,
1095                    "checkpoint archive packaged after ACP dispatch resumed"
1096                );
1097                output
1098            } else {
1099                let export_started = Instant::now();
1100                let output = {
1101                    let _recovery_copy = recovery_copy.then(|| {
1102                        ProvisionStageGuard::new(executor, ProvisionStage::RecoveryCopy)
1103                    });
1104                    export_target_checkpoint(
1105                        executor,
1106                        &backend,
1107                        session_id,
1108                        &spec,
1109                        &remote_spec,
1110                    )?
1111                };
1112                export_ms = Some(export_started.elapsed().as_millis() as u64);
1113                output
1114            };
1115            let target_checkpoint: hel::hel_checkpoint::TargetCheckpoint =
1116                serde_json::from_slice(&exported.stdout)
1117                    .context("decode target checkpoint result")?;
1118            if let Some(export_ms) = export_ms {
1119                // A worker that predates the timings field reports nothing, so
1120                // the phase numbers read as zero; `timings_reported` says which.
1121                let timings = target_checkpoint.timings.unwrap_or_default();
1122                tracing::info!(
1123                    session_id,
1124                    export_ms,
1125                    timings_reported = target_checkpoint.timings.is_some(),
1126                    native_ms = timings.native_ms,
1127                    repositories_ms = timings.repositories_ms,
1128                    archive_ms = timings.archive_ms,
1129                    worker_total_ms = timings.total_ms,
1130                    "checkpoint archive exported on the target"
1131                );
1132            }
1133            if target_checkpoint.event_frontier != expected_ordinal {
1134                bail!(
1135                    "target checkpoint event frontier changed: expected {expected_ordinal}, found {}",
1136                    target_checkpoint.event_frontier
1137                );
1138            }
1139            if target_checkpoint.event_frontier_digest != expected_digest {
1140                bail!("target checkpoint event frontier digest changed");
1141            }
1142
1143            // Checkpoint archives are immutable once controller metadata points
1144            // at them. A repeated checkpoint may have the same event frontier,
1145            // so a frontier-only name could overwrite the last known-good
1146            // archive before the metadata swap commits.
1147            let archive_id = new_command_id("archive")?;
1148            let destination = sessions_dir().join(format!(
1149                "{session_id}-{}-{archive_id}.hel.zip",
1150                target_checkpoint.event_frontier
1151            ));
1152            let transfer = CheckpointTransfer {
1153                locator: &backend,
1154                session_id,
1155                remote_archive: &remote_archive,
1156                destination: &destination,
1157                expected_sha256: &target_checkpoint.sha256,
1158                expected_event_frontier: target_checkpoint.event_frontier,
1159                expected_event_frontier_digest: &target_checkpoint.event_frontier_digest,
1160            };
1161            let metadata = {
1162                let _verifying = ProvisionStageGuard::new(executor, ProvisionStage::Verifying);
1163                let transfer_started = Instant::now();
1164                let verified = transfer.execute(executor)?;
1165                tracing::info!(
1166                    session_id,
1167                    transfer_and_checksum_ms = transfer_started.elapsed().as_millis() as u64,
1168                    "checkpoint archive transferred and checksum-verified"
1169                );
1170                let installed_archive = verified.archive_path().to_path_buf();
1171                let validate_transferred = || -> Result<()> {
1172                    ensure!(
1173                        verified.sha256() == target_checkpoint.sha256,
1174                        "target and controller checkpoint checksums differ"
1175                    );
1176                    ensure!(
1177                        verified.event_frontier_digest() == expected_digest,
1178                        "verified checkpoint event frontier digest changed"
1179                    );
1180                    Ok(())
1181                };
1182                if let Err(error) = validate_transferred() {
1183                    return Err(remove_uninstalled_checkpoint(&installed_archive, error));
1184                }
1185                // A checkpoint that still holds its barrier proves workspace
1186                // consistency here instead. One that already released proved it
1187                // before releasing; the sha256 chain covers the transfer itself.
1188                if completion == CheckpointCompletion::HeldBarrier {
1189                    let revalidated = relay.sync_snapshot().await.and_then(|snapshot| {
1190                        if releases_after_capture {
1191                            validate_automatic_checkpoint_barrier_snapshot(
1192                                &snapshot,
1193                                &barrier_command_id,
1194                                &cursor,
1195                                session.harness_kind,
1196                            )
1197                        } else {
1198                            validate_checkpoint_barrier_snapshot(
1199                                &snapshot,
1200                                &barrier_command_id,
1201                                &cursor,
1202                            )
1203                        }
1204                    });
1205                    if let Err(error) = revalidated {
1206                        return Err(remove_uninstalled_checkpoint(
1207                            &installed_archive,
1208                            error.context(
1209                                "checkpoint barrier changed while transferring its archive",
1210                            ),
1211                        ));
1212                    }
1213                }
1214                if let Err(error) = transfer
1215                    .cleanup_plan(&verified)
1216                    .and_then(|plan| plan.execute(executor).map(|_| ()))
1217                {
1218                    return Err(remove_uninstalled_checkpoint(
1219                        &installed_archive,
1220                        error.context("clean target checkpoint staging"),
1221                    ));
1222                }
1223                CheckpointMetadata {
1224                    archive_path: verified.archive_path().to_path_buf(),
1225                    sha256: verified.sha256().to_string(),
1226                    created_at: checkpointed_at.clone(),
1227                    event_frontier: verified.event_frontier(),
1228                }
1229            };
1230            Ok(CheckpointArtifact {
1231                metadata,
1232                native_session_id,
1233                event_frontier_digest: expected_digest,
1234            })
1235        }
1236        .await;
1237
1238        let artifact = match exported {
1239            Ok(artifact) => artifact,
1240            Err(error) => {
1241                // The barrier freezes ACP dispatch until it ends. Nothing will
1242                // complete it now, and the connection that opened it is back
1243                // with the session actor, so cancel it instead of leaving the
1244                // harness frozen until that connection happens to drop. A
1245                // barrier released after the export is already gone.
1246                if completion == CheckpointCompletion::HeldBarrier
1247                    && let Err(cancel_error) = relay.cancel_abandoned_barrier().await
1248                {
1249                    tracing::warn!(
1250                        session_id,
1251                        "failed checkpoint could not cancel its relay barrier: {cancel_error:#}"
1252                    );
1253                }
1254                return Err(error);
1255            }
1256        };
1257        Ok(LatchedCheckpoint {
1258            artifact,
1259            relay,
1260            barrier_command_id,
1261            cursor,
1262            completion,
1263        })
1264    }
1265
1266    /// Reach the session worker for a checkpoint, restarting it when the proxy
1267    /// cannot complete hello. A previous Stop can leave the daemon dead; failing
1268    /// that first connect without a bounce never gets to the barrier retry.
1269    async fn open_checkpoint_relay(
1270        &self,
1271        session_id: &str,
1272        executor: &(impl CommandExecutor + Sync),
1273        manager: Option<&SessionManagerControl>,
1274        target: InstalledWorkerRestart<'_>,
1275        restart_if_unreachable: bool,
1276    ) -> Result<(ControllerRelayLease, bool)> {
1277        let project_memory = match self.project_memory_sync_target(session_id) {
1278            Ok(target) => Some(target),
1279            Err(error) => {
1280                tracing::warn!(
1281                    session_id,
1282                    error = format!("{error:#}"),
1283                    "project memory will not be synchronized during checkpoint reconnect"
1284                );
1285                None
1286            }
1287        };
1288        match connect_checkpoint_relay(
1289            session_id,
1290            manager,
1291            target.reconnect,
1292            project_memory.clone(),
1293        )
1294        .await
1295        {
1296            Ok(relay) => Ok((relay, false)),
1297            Err(error) if worker_connect_needs_restart(&error) && restart_if_unreachable => {
1298                tracing::warn!(
1299                    session_id,
1300                    "checkpoint could not reach the worker; restarting it: {error:#}"
1301                );
1302                let mut connection = self
1303                    .restart_worker_for_checkpoint(
1304                        session_id,
1305                        executor,
1306                        target.backend,
1307                        target.worker_root,
1308                        target.reconnect,
1309                    )
1310                    .await?;
1311                connection.set_project_memory_target(project_memory);
1312                let relay =
1313                    adopt_restarted_checkpoint_relay(session_id, manager, connection).await?;
1314                Ok((relay, true))
1315            }
1316            Err(error) if worker_connect_needs_restart(&error) => {
1317                Err(error.context(CheckpointDeferred::background_work()))
1318            }
1319            Err(error) => Err(error).context("connect to the session worker for checkpoint"),
1320        }
1321    }
1322
1323    /// Kill a worker whose ACP turn will not finish, install the current
1324    /// binary, and reconnect. Restart recovery interrupts the in-flight prompt
1325    /// so a later BeginCheckpoint can be admitted.
1326    async fn restart_worker_for_checkpoint(
1327        &self,
1328        session_id: &str,
1329        executor: &(impl CommandExecutor + Sync),
1330        backend: &hel_targets::TargetLocator,
1331        worker_root: &str,
1332        reconnect: &hel_targets::CommandSpec,
1333    ) -> Result<StandaloneSession> {
1334        self.restart_worker_with_installed_binary(
1335            session_id,
1336            executor,
1337            InstalledWorkerRestart {
1338                backend,
1339                worker_root,
1340                reconnect,
1341                launch: None,
1342                messages: &RESTART_FOR_CHECKPOINT,
1343            },
1344        )
1345        .await
1346    }
1347}
1348
1349async fn connect_checkpoint_relay(
1350    session_id: &str,
1351    manager: Option<&SessionManagerControl>,
1352    reconnect: &hel_targets::CommandSpec,
1353    project_memory: Option<crate::hel_session_manager::ProjectMemorySyncTarget>,
1354) -> Result<ControllerRelayLease> {
1355    if let Some(manager) = manager {
1356        let handle = manager
1357            .wait_for_session(session_id, Duration::from_secs(5))
1358            .await?;
1359        let mut lease = handle.lease_connection().await?;
1360        lease
1361            .connection_mut()
1362            .set_project_memory_target(project_memory);
1363        Ok(ControllerRelayLease::Managed {
1364            handle,
1365            lease: Some(lease),
1366        })
1367    } else {
1368        let target = crate::hel_session_manager::RelaySessionTarget {
1369            session_id: session_id.to_owned(),
1370            spec: reconnect.clone(),
1371            worker_recovery: None,
1372            project_memory,
1373        };
1374        Ok(ControllerRelayLease::Standalone(
1375            StandaloneSession::connect(&target).await?,
1376        ))
1377    }
1378}
1379
1380async fn adopt_restarted_checkpoint_relay(
1381    session_id: &str,
1382    manager: Option<&SessionManagerControl>,
1383    connection: StandaloneSession,
1384) -> Result<ControllerRelayLease> {
1385    let Some(manager) = manager else {
1386        return Ok(ControllerRelayLease::Standalone(connection));
1387    };
1388    let handle = manager
1389        .wait_for_session(session_id, Duration::from_secs(5))
1390        .await?;
1391    match handle.lease_connection().await {
1392        Ok(mut lease) => {
1393            lease.replace_connection(connection);
1394            Ok(ControllerRelayLease::Managed {
1395                handle,
1396                lease: Some(lease),
1397            })
1398        }
1399        Err(error) => {
1400            tracing::warn!(
1401                session_id,
1402                "session actor could not lease after worker restart; using the restarted proxy: {error:#}"
1403            );
1404            Ok(ControllerRelayLease::Standalone(connection))
1405        }
1406    }
1407}
1408
1409/// What waiting for a barrier does while the session is working.
1410#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1411enum BarrierBusyPolicy {
1412    /// Give up as soon as the session is seen working. A checkpoint that can
1413    /// run again later has nothing to gain from holding a barrier behind a
1414    /// prompt or a turn the harness started on its own: the wait would only
1415    /// end at the deadline, and the deadline means "wedged", which restarts
1416    /// the worker and kills the work in flight.
1417    DeferWhileRunning,
1418    /// Request non-steering cancellation and wait for the turn to settle.
1419    /// Close may interrupt work, but only an unresponsive or incompatible
1420    /// worker needs restart recovery.
1421    InterruptWhileRunning,
1422}
1423
1424impl BarrierBusyPolicy {
1425    fn of(exclusivity: LatchExclusivity) -> Self {
1426        match exclusivity {
1427            LatchExclusivity::ReleaseAfterLatch => Self::DeferWhileRunning,
1428            LatchExclusivity::HoldThroughClose => Self::InterruptWhileRunning,
1429        }
1430    }
1431}
1432
1433async fn wait_for_checkpoint_barrier(
1434    relay: &mut StandaloneSession,
1435    session_id: &str,
1436    command_id: &str,
1437    timeout: Duration,
1438    busy: BarrierBusyPolicy,
1439    harness: HarnessKind,
1440) -> Result<ManagedSessionSnapshot> {
1441    let deadline = tokio::time::Instant::now() + timeout;
1442    let mut cancel_submitted = false;
1443    let mut cancel_deadline = None;
1444    let mut cancel_started_at: Option<Instant> = None;
1445    loop {
1446        let snapshot = relay.sync().await?;
1447        if busy == BarrierBusyPolicy::DeferWhileRunning
1448            && !snapshot.operational.safe_for_checkpoint(harness)
1449        {
1450            // The native task level can change after the controller's initial
1451            // idle sync and before the queued BeginCheckpoint is processed.
1452            // Defer from the barrier wait rather than allowing its timeout to
1453            // classify the worker as wedged and restart it.
1454            return Err(CheckpointDeferred::background_work().into());
1455        }
1456        if checkpoint_barrier_is_ready(&snapshot, command_id) {
1457            if let Some(started_at) = cancel_started_at {
1458                tracing::info!(
1459                    session_id,
1460                    barrier_command_id = command_id,
1461                    cancellation_ms = started_at.elapsed().as_millis() as u64,
1462                    "active turn cancellation settled before checkpoint barrier"
1463                );
1464            }
1465            return Ok(snapshot);
1466        }
1467        if busy == BarrierBusyPolicy::InterruptWhileRunning
1468            && snapshot.operational.execution == RelayExecutionState::Running
1469            && !cancel_submitted
1470        {
1471            let cancel_turn = RelayCommand::CancelTurn;
1472            if relay.protocol_version() < cancel_turn.minimum_protocol() {
1473                return Err(CheckpointBarrierUnreachable::cancel_turn_unavailable(
1474                    command_id,
1475                    relay.protocol_version(),
1476                )
1477                .into());
1478            }
1479            let cancel_command_id = new_command_id("checkpoint-cancel-turn")?;
1480            match relay.submit(cancel_command_id, cancel_turn).await {
1481                Ok(_) => {
1482                    cancel_submitted = true;
1483                    cancel_started_at = Some(Instant::now());
1484                    cancel_deadline = Some(tokio::time::Instant::now() + CHECKPOINT_CANCEL_TIMEOUT);
1485                    tracing::info!(
1486                        session_id,
1487                        barrier_command_id = command_id,
1488                        "requested active turn cancellation before checkpoint barrier"
1489                    );
1490                }
1491                Err(error) if checkpoint_cancel_turn_needs_worker_restart(&error) => {
1492                    return Err(error.context(
1493                        CheckpointBarrierUnreachable::cancel_turn_unavailable(
1494                            command_id,
1495                            relay.protocol_version(),
1496                        ),
1497                    ));
1498                }
1499                Err(error) if worker_connect_needs_restart(&error) => {
1500                    return Err(error.context(
1501                        CheckpointBarrierUnreachable::cancel_turn_unreachable(command_id),
1502                    ));
1503                }
1504                Err(error) => {
1505                    // The turn can finish between the status sync and this
1506                    // submit. If the barrier won that race, continue from its
1507                    // durable ready state; otherwise preserve the rejection.
1508                    if let Ok(snapshot) = relay.sync().await
1509                        && checkpoint_barrier_is_ready(&snapshot, command_id)
1510                    {
1511                        tracing::info!(
1512                            session_id,
1513                            barrier_command_id = command_id,
1514                            "active turn settled while submitting checkpoint cancellation"
1515                        );
1516                        return Ok(snapshot);
1517                    }
1518                    return Err(error.context("cancel active ACP turn before checkpoint barrier"));
1519                }
1520            }
1521            continue;
1522        }
1523        let out_of_time = tokio::time::Instant::now() >= cancel_deadline.unwrap_or(deadline);
1524        if let Some(error) = checkpoint_barrier_wait_ended(
1525            &snapshot,
1526            command_id,
1527            busy,
1528            out_of_time,
1529            cancel_submitted,
1530        ) {
1531            return Err(error);
1532        }
1533        tokio::time::sleep(std::time::Duration::from_millis(100)).await;
1534    }
1535}
1536
1537/// Why one sync of a barrier that is not ready yet ends the wait, or `None` to
1538/// keep waiting.
1539///
1540/// The deadline means "wedged": it restarts the worker only after a close has
1541/// already requested cancellation and the turn still has not settled. A
1542/// checkpoint that can try again later defers as soon as it sees work.
1543fn checkpoint_barrier_wait_ended(
1544    snapshot: &ManagedSessionSnapshot,
1545    command_id: &str,
1546    busy: BarrierBusyPolicy,
1547    out_of_time: bool,
1548    cancel_submitted: bool,
1549) -> Option<anyhow::Error> {
1550    if snapshot.operational.execution == RelayExecutionState::Closed {
1551        return Some(CheckpointBarrierUnreachable::runtime_stopped().into());
1552    }
1553    if snapshot.operational.execution == RelayExecutionState::Running {
1554        return Some(match busy {
1555            BarrierBusyPolicy::DeferWhileRunning => CheckpointDeferred::harness_busy().into(),
1556            BarrierBusyPolicy::InterruptWhileRunning if out_of_time && cancel_submitted => {
1557                CheckpointBarrierUnreachable::cancel_timed_out(command_id).into()
1558            }
1559            BarrierBusyPolicy::InterruptWhileRunning => return None,
1560        });
1561    }
1562    out_of_time.then(|| CheckpointBarrierUnreachable::not_admitted(command_id).into())
1563}
1564
1565/// The ACP runtime never admitted a checkpoint barrier: it stopped first, or it
1566/// never reached the barrier before the deadline.
1567///
1568/// [`wait_for_checkpoint_barrier`] is the only producer, and the retry decision
1569/// downcasts for this marker rather than reading the message, so rewording a
1570/// diagnostic cannot silently disable the restart-and-retry path.
1571#[derive(Debug)]
1572struct CheckpointBarrierUnreachable(String);
1573
1574impl CheckpointBarrierUnreachable {
1575    fn runtime_stopped() -> Self {
1576        Self("ACP runtime stopped before reaching the checkpoint barrier".to_owned())
1577    }
1578
1579    fn not_admitted(command_id: &str) -> Self {
1580        Self(format!(
1581            "ACP relay did not reach checkpoint barrier {command_id}"
1582        ))
1583    }
1584
1585    fn cancel_timed_out(command_id: &str) -> Self {
1586        Self(format!(
1587            "active ACP turn did not settle after cancellation before checkpoint barrier {command_id}"
1588        ))
1589    }
1590
1591    fn cancel_turn_unavailable(command_id: &str, protocol_version: u32) -> Self {
1592        Self(format!(
1593            "worker protocol {protocol_version} cannot cancel the active ACP turn before checkpoint barrier {command_id} (requires protocol {})",
1594            RelayCommand::CancelTurn.minimum_protocol(),
1595        ))
1596    }
1597
1598    fn cancel_turn_unreachable(command_id: &str) -> Self {
1599        Self(format!(
1600            "worker transport became unavailable while cancelling the active ACP turn before checkpoint barrier {command_id}"
1601        ))
1602    }
1603}
1604
1605impl std::fmt::Display for CheckpointBarrierUnreachable {
1606    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1607        formatter.write_str(&self.0)
1608    }
1609}
1610
1611impl std::error::Error for CheckpointBarrierUnreachable {}
1612
1613fn checkpoint_barrier_needs_worker_restart(error: &anyhow::Error) -> bool {
1614    error
1615        .downcast_ref::<CheckpointBarrierUnreachable>()
1616        .is_some()
1617}
1618
1619/// A worker that reports an incompatible protocol for `CancelTurn` needs to be
1620/// replaced before the close can retry the checkpoint with cancellation
1621/// available. The negotiated protocol is checked before submission; this
1622/// handles a race with a worker-side protocol rejection as well.
1623fn checkpoint_cancel_turn_needs_worker_restart(error: &anyhow::Error) -> bool {
1624    error.chain().any(|cause| {
1625        let Some(rejected) = cause.downcast_ref::<RelayRejected>() else {
1626            return false;
1627        };
1628        rejected.0.code == hel::hel_worker::RelayErrorCode::IncompatibleProtocol
1629    })
1630}
1631
1632/// The session was working, so this checkpoint did not run. Nothing is wrong
1633/// with the session, the target, or the last archive.
1634///
1635/// A busy session is the normal state of a session someone is using, including
1636/// one working through a turn the harness started on its own after a
1637/// background command. Treating that as a checkpoint failure would restart the
1638/// worker, record a failure against the session, and back the next attempt off
1639/// for hours. Callers that can try again later defer instead; the same work is
1640/// copied at the next idle observation.
1641#[derive(Debug)]
1642pub struct CheckpointDeferred(String);
1643
1644impl CheckpointDeferred {
1645    pub(crate) fn harness_busy() -> Self {
1646        Self("the agent is working; try again when it is idle".to_owned())
1647    }
1648
1649    fn background_work() -> Self {
1650        Self(
1651            "Kimi background-agent state is unknown or still active; checkpoint deferred until it is synchronized and idle"
1652                .to_owned(),
1653        )
1654    }
1655
1656    fn frontier_moved() -> Self {
1657        Self(
1658            "the session moved past the checkpoint-ready cursor before the barrier latched, so this checkpoint was deferred"
1659                .to_owned(),
1660        )
1661    }
1662
1663    fn harness_turn_during_capture() -> Self {
1664        Self(
1665            "the agent started a turn of its own while target state was captured, so this checkpoint was deferred"
1666                .to_owned(),
1667        )
1668    }
1669}
1670
1671impl std::fmt::Display for CheckpointDeferred {
1672    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1673        formatter.write_str(&self.0)
1674    }
1675}
1676
1677impl std::error::Error for CheckpointDeferred {}
1678
1679/// Whether a failed checkpoint only means the session was busy.
1680///
1681/// The marker is carried by the error, not by its text, and callers wrap
1682/// checkpoint errors in context, so the whole chain is searched.
1683pub fn checkpoint_was_deferred(error: &anyhow::Error) -> bool {
1684    error
1685        .chain()
1686        .any(|cause| cause.downcast_ref::<CheckpointDeferred>().is_some())
1687}
1688
1689fn checkpoint_barrier_is_ready(snapshot: &ManagedSessionSnapshot, command_id: &str) -> bool {
1690    snapshot.operational.checkpoint_barrier.as_deref() == Some(command_id)
1691        && snapshot.operational.checkpoint_ready.is_some()
1692}
1693
1694/// The latched projection must sit exactly at the barrier's ready cursor.
1695///
1696/// The barrier was admitted, but the relay can record more events before the
1697/// controller latches - the harness spoke again in the gap. The archive would
1698/// not be an exact cut of the session, so the attempt is dropped and the next
1699/// idle observation copies the settled session instead. This is not a fault in
1700/// the session, the target, or the last archive.
1701fn ensure_exact_checkpoint_cut(
1702    cursor: &RelayCursor,
1703    expected_ordinal: u64,
1704    expected_digest: &str,
1705) -> Result<()> {
1706    if cursor.ordinal != expected_ordinal || cursor.digest != expected_digest {
1707        bail!(CheckpointDeferred::frontier_moved());
1708    }
1709    Ok(())
1710}
1711
1712/// Prove the barrier that latched an archive is still the same barrier, still
1713/// held at the same ready cursor.
1714///
1715/// The relay frontier may have moved past that cursor: an active ordinary
1716/// barrier still accepts and journals submissions, it only freezes ACP
1717/// dispatch. Nothing the harness could write reaches the workspace while
1718/// dispatch is frozen, so an advanced frontier does not invalidate the archive.
1719/// Requiring frontier equality here would fail every checkpoint that overlapped
1720/// a prompt.
1721///
1722/// A turn the harness starts on its own is the exception. The barrier freezes
1723/// Mjolnir's dispatch, not the harness, so a harness turn that opened after the
1724/// cursor was captured means the agent may have been writing to the workspace
1725/// while it was staged. That archive is abandoned rather than installed.
1726fn validate_checkpoint_barrier_snapshot(
1727    snapshot: &ManagedSessionSnapshot,
1728    command_id: &str,
1729    expected: &RelayCursor,
1730) -> Result<()> {
1731    ensure!(
1732        snapshot.operational.checkpoint_barrier.as_deref() == Some(command_id),
1733        "checkpoint barrier {command_id} is no longer active"
1734    );
1735    ensure!(
1736        snapshot.operational.checkpoint_ready.as_ref() == Some(expected),
1737        "checkpoint barrier {command_id} has a different ready cursor"
1738    );
1739    if snapshot
1740        .operational
1741        .last_harness_turn_started_ordinal
1742        .is_some_and(|ordinal| ordinal > expected.ordinal)
1743    {
1744        bail!(CheckpointDeferred::harness_turn_during_capture());
1745    }
1746    Ok(())
1747}
1748
1749/// Validate the barrier cut and prove that a routine checkpoint still has no
1750/// provider-owned Kimi work. Close checkpoints intentionally use the more
1751/// permissive validator because close is allowed to interrupt/terminate work.
1752fn validate_automatic_checkpoint_barrier_snapshot(
1753    snapshot: &ManagedSessionSnapshot,
1754    command_id: &str,
1755    expected: &RelayCursor,
1756    harness: HarnessKind,
1757) -> Result<()> {
1758    validate_checkpoint_barrier_snapshot(snapshot, command_id, expected)?;
1759    ensure!(
1760        snapshot.operational.safe_for_checkpoint(harness),
1761        CheckpointDeferred::background_work()
1762    );
1763    Ok(())
1764}
1765
1766fn remove_uninstalled_checkpoint(path: &Path, error: anyhow::Error) -> anyhow::Error {
1767    match std::fs::remove_file(path) {
1768        Ok(()) => error,
1769        Err(remove_error) if remove_error.kind() == std::io::ErrorKind::NotFound => error,
1770        Err(remove_error) => error.context(format!(
1771            "also failed to remove uninstalled checkpoint {}: {remove_error}",
1772            path.display()
1773        )),
1774    }
1775}
1776
1777pub(super) async fn wait_for_relay_closed(relay: &mut StandaloneSession) -> Result<()> {
1778    let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(30);
1779    loop {
1780        if relay.sync().await?.operational.execution == RelayExecutionState::Closed {
1781            return Ok(());
1782        }
1783        if tokio::time::Instant::now() >= deadline {
1784            bail!("ACP runtime did not close within 30 seconds");
1785        }
1786        tokio::time::sleep(std::time::Duration::from_millis(100)).await;
1787    }
1788}
1789
1790/// Hand ACP dispatch back as soon as target-owned state is sealed.
1791///
1792/// Proving the barrier first moves the workspace-consistency proof ahead of the
1793/// release: the same barrier still holding the same ready cursor means nothing
1794/// the harness could write reached the workspace while the stage was captured.
1795/// The recovery floor stays put, because nothing yet proves the archive reached
1796/// the controller's disk.
1797///
1798/// A worker that does not understand the release keeps its barrier, and the
1799/// caller falls back to ending it only after the archive is installed. That is
1800/// slower, not wrong, so it is not a checkpoint failure.
1801async fn release_checkpoint_after_capture(
1802    relay: &mut ControllerRelayLease,
1803    session_id: &str,
1804    barrier_command_id: &str,
1805    cursor: &RelayCursor,
1806    harness: HarnessKind,
1807) -> Result<CheckpointCompletion> {
1808    relay
1809        .sync_snapshot()
1810        .await
1811        .and_then(|snapshot| {
1812            validate_automatic_checkpoint_barrier_snapshot(
1813                &snapshot,
1814                barrier_command_id,
1815                cursor,
1816                harness,
1817            )
1818        })
1819        .context("checkpoint barrier changed while capturing target state")?;
1820    match relay
1821        .submit(
1822            new_command_id("checkpoint-release")?,
1823            RelayCommand::ReleaseCheckpoint {
1824                barrier_command_id: barrier_command_id.to_owned(),
1825            },
1826        )
1827        .await
1828    {
1829        Ok(_) => Ok(CheckpointCompletion::ReleasedAfterCapture),
1830        Err(error) => {
1831            tracing::debug!(
1832                session_id,
1833                "relay kept the checkpoint barrier through the transfer: {error:#}"
1834            );
1835            Ok(CheckpointCompletion::HeldBarrier)
1836        }
1837    }
1838}
1839
1840fn run_checkpoint_staging_command<T: serde::Serialize>(
1841    executor: &impl CommandExecutor,
1842    locator: &hel_targets::TargetLocator,
1843    session_id: &str,
1844    spec: &T,
1845    command: fn(&hel_targets::TargetLocator, &str) -> Result<CommandSpec>,
1846    operation: &str,
1847) -> Result<CommandOutput> {
1848    let body = serde_json::to_vec(spec).with_context(|| format!("serialize {operation} spec"))?;
1849    let mut replaced_worker = false;
1850    loop {
1851        let command = command(locator, session_id)?;
1852        let output = executor.execute_with_stdin(&command, &mut body.as_slice())?;
1853        if output.status == 0 {
1854            return Ok(output);
1855        }
1856        let failure = String::from_utf8_lossy(&output.stderr).into_owned();
1857        if staging_protocol_unsupported(&failure)
1858            && replace_stale_export_worker(
1859                executor,
1860                locator,
1861                session_id,
1862                None,
1863                &failure,
1864                &mut replaced_worker,
1865            )?
1866        {
1867            continue;
1868        }
1869        bail!(
1870            "{operation} failed with status {}: {failure}",
1871            output.status
1872        );
1873    }
1874}
1875
1876/// Run the target's checkpoint export with the spec streamed over stdin.
1877///
1878/// Streaming removes a whole `podman cp`/`scp` round trip from the window in
1879/// which the relay barrier keeps ACP dispatch frozen.
1880fn export_target_checkpoint(
1881    executor: &impl CommandExecutor,
1882    locator: &hel_targets::TargetLocator,
1883    session_id: &str,
1884    spec: &CheckpointExportSpec,
1885    remote_spec: &str,
1886) -> Result<CommandOutput> {
1887    export_target_checkpoint_with_worker(executor, locator, session_id, spec, remote_spec, None)
1888}
1889
1890fn export_target_checkpoint_with_worker(
1891    executor: &impl CommandExecutor,
1892    locator: &hel_targets::TargetLocator,
1893    session_id: &str,
1894    spec: &CheckpointExportSpec,
1895    remote_spec: &str,
1896    worker_binary: Option<&Path>,
1897) -> Result<CommandOutput> {
1898    let body = serde_json::to_vec(spec).context("serialize checkpoint export spec")?;
1899    let mut replaced_worker = false;
1900    loop {
1901        let streamed = export_stdin_command(locator, session_id)?;
1902        let output = executor.execute_with_stdin(&streamed, &mut body.as_slice())?;
1903        if output.status == 0 {
1904            return Ok(output);
1905        }
1906        let failure = String::from_utf8_lossy(&output.stderr).into_owned();
1907        if export_spec_stdin_unsupported(&failure) {
1908            tracing::debug!(
1909                session_id,
1910                "target worker predates streamed checkpoint specs; uploading the spec file instead"
1911            );
1912            let output = export_uploaded_spec(executor, locator, session_id, spec, remote_spec)?;
1913            if output.status == 0 {
1914                return Ok(output);
1915            }
1916            let failure = String::from_utf8_lossy(&output.stderr).into_owned();
1917            if replace_stale_export_worker(
1918                executor,
1919                locator,
1920                session_id,
1921                worker_binary,
1922                &failure,
1923                &mut replaced_worker,
1924            )? {
1925                continue;
1926            }
1927            bail!(
1928                "export target checkpoint failed with status {}: {failure}",
1929                output.status
1930            );
1931        }
1932        if replace_stale_export_worker(
1933            executor,
1934            locator,
1935            session_id,
1936            worker_binary,
1937            &failure,
1938            &mut replaced_worker,
1939        )? {
1940            continue;
1941        }
1942        bail!(
1943            "{} failed with status {}: {failure}",
1944            streamed.purpose,
1945            output.status
1946        );
1947    }
1948}
1949
1950fn export_uploaded_spec(
1951    executor: &impl CommandExecutor,
1952    locator: &hel_targets::TargetLocator,
1953    session_id: &str,
1954    spec: &CheckpointExportSpec,
1955    remote_spec: &str,
1956) -> Result<CommandOutput> {
1957    let staging = tempfile::tempdir().context("create checkpoint staging")?;
1958    let local_spec = staging.path().join("checkpoint-spec.json");
1959    spec.write(&local_spec)?;
1960    upload_checkpoint_spec(executor, locator, session_id, &local_spec, remote_spec)?;
1961    executor.execute(&export_command(locator, session_id, remote_spec)?)
1962}
1963
1964/// When the installed worker cannot execute this export protocol, replace its
1965/// `mj` with the controller's current binary and tell the caller to retry. The
1966/// live daemon keeps the previous inode; only the next `export-checkpoint`
1967/// process changes.
1968fn replace_stale_export_worker(
1969    executor: &impl CommandExecutor,
1970    locator: &hel_targets::TargetLocator,
1971    session_id: &str,
1972    worker_binary: Option<&Path>,
1973    failure: &str,
1974    replaced_worker: &mut bool,
1975) -> Result<bool> {
1976    if *replaced_worker || !staging_protocol_unsupported(failure) {
1977        return Ok(false);
1978    }
1979    tracing::debug!(
1980        session_id,
1981        "target worker does not support this checkpoint export protocol; replacing the installed Mjolnir binary and retrying"
1982    );
1983    let owned_binary;
1984    let binary = if let Some(path) = worker_binary {
1985        path
1986    } else {
1987        owned_binary = super::worker_binary::worker_binary_for(locator, executor)?;
1988        owned_binary.as_path()
1989    };
1990    super::worker_binary::replace_installed_worker_binary(executor, locator, session_id, binary)?;
1991    *replaced_worker = true;
1992    Ok(true)
1993}
1994
1995/// Whether an export failure says the target's worker cannot read its spec from
1996/// standard input.
1997///
1998/// A worker built before `--spec -` treats the dash as a file name, so it fails
1999/// while reading that file rather than while running the checkpoint. One built
2000/// before the flag existed at all fails in argument parsing. Every other
2001/// failure is a real checkpoint error and must surface.
2002fn export_spec_stdin_unsupported(failure: &str) -> bool {
2003    failure.contains("read checkpoint export spec -")
2004        || failure.contains("unexpected argument")
2005        || failure.contains("invalid value")
2006}
2007
2008/// Whether an export failure says the target's worker cannot deserialize this
2009/// spec. `CheckpointExportSpec` and its nested canonical snapshot use
2010/// `deny_unknown_fields`, so a controller that gained a field such as
2011/// `terminal_refs` cannot pause a session whose installed `mj` predates it.
2012fn export_spec_schema_unsupported(failure: &str) -> bool {
2013    failure.contains("parse checkpoint")
2014        && (failure.contains("unknown field") || failure.contains("unknown variant"))
2015}
2016
2017fn export_protocol_unsupported(failure: &str) -> bool {
2018    export_spec_schema_unsupported(failure)
2019        || failure.contains("unsupported checkpoint export protocol version")
2020}
2021
2022fn staging_protocol_unsupported(failure: &str) -> bool {
2023    export_protocol_unsupported(failure)
2024        || failure.contains("unsupported checkpoint staging protocol version")
2025        || failure.contains("unrecognized subcommand")
2026        || failure.contains("unexpected argument")
2027}
2028
2029pub(super) fn upload_checkpoint_spec(
2030    executor: &impl CommandExecutor,
2031    locator: &hel_targets::TargetLocator,
2032    session_id: &str,
2033    local: &Path,
2034    remote: &str,
2035) -> Result<()> {
2036    match locator {
2037        hel_targets::TargetLocator::LocalBare { .. } => {
2038            std::fs::copy(local, remote)
2039                .with_context(|| format!("copy checkpoint specification to {remote}"))?;
2040            Ok(())
2041        }
2042        hel_targets::TargetLocator::LocalPodman { container_id, .. } => execute_checked(
2043            executor,
2044            CommandSpec::new(
2045                "podman",
2046                [
2047                    "cp".into(),
2048                    local.to_string_lossy().into_owned(),
2049                    format!("{container_id}:{remote}"),
2050                ],
2051            )
2052            .purpose("upload checkpoint specification"),
2053        )
2054        .map(|_| ()),
2055        hel_targets::TargetLocator::LocalDocker { container_id } => execute_checked(
2056            executor,
2057            CommandSpec::new(
2058                "docker",
2059                [
2060                    "cp".into(),
2061                    local.to_string_lossy().into_owned(),
2062                    format!("{container_id}:{remote}"),
2063                ],
2064            )
2065            .purpose("upload checkpoint specification"),
2066        )
2067        .map(|_| ()),
2068        hel_targets::TargetLocator::AppleContainer { container_id } => execute_checked(
2069            executor,
2070            CommandSpec::new(
2071                "container",
2072                [
2073                    "cp".into(),
2074                    local.to_string_lossy().into_owned(),
2075                    format!("{container_id}:{remote}"),
2076                ],
2077            )
2078            .purpose("upload checkpoint specification"),
2079        )
2080        .map(|_| ()),
2081        hel_targets::TargetLocator::AwsEc2 { ssh, .. }
2082        | hel_targets::TargetLocator::SshBare { ssh, .. } => execute_checked(
2083            executor,
2084            scp_command_spec(ssh, local, remote, false).purpose("upload checkpoint specification"),
2085        )
2086        .map(|_| ()),
2087        hel_targets::TargetLocator::SshPodman {
2088            ssh, container_id, ..
2089        }
2090        | hel_targets::TargetLocator::SshDocker { ssh, container_id } => {
2091            let engine = match locator {
2092                hel_targets::TargetLocator::SshPodman { .. } => "podman",
2093                hel_targets::TargetLocator::SshDocker { .. } => "docker",
2094                _ => unreachable!("matched remote container target"),
2095            };
2096            let staging = format!(".local/share/hel/uploads/{session_id}-checkpoint.json");
2097            execute_checked(
2098                executor,
2099                ssh_command_spec(ssh, ["mkdir", "-p", ".local/share/hel/uploads"])
2100                    .purpose("create remote checkpoint staging"),
2101            )?;
2102            execute_checked(
2103                executor,
2104                scp_command_spec(ssh, local, &staging, false)
2105                    .purpose("upload remote container checkpoint specification"),
2106            )?;
2107            execute_checked(
2108                executor,
2109                ssh_command_spec(
2110                    ssh,
2111                    [engine, "cp", &staging, &format!("{container_id}:{remote}")],
2112                )
2113                .purpose("install remote container checkpoint specification"),
2114            )?;
2115            execute_checked(
2116                executor,
2117                ssh_command_spec(ssh, ["rm", "-f", "--", &staging])
2118                    .purpose("remove remote checkpoint staging"),
2119            )?;
2120            Ok(())
2121        }
2122    }?;
2123    Ok(())
2124}
2125
2126/// The artifact a latched checkpoint may keep instead of exporting a new one,
2127/// or `None` when a full export has to run.
2128///
2129/// Every relay command is journalled, checkpoint plumbing included, so the
2130/// event frontier always moves between two checkpoints. Session content is
2131/// what decides whether the installed archive still represents the session.
2132/// Every reason to decline is reported; none of them fails the checkpoint.
2133fn reusable_installed_checkpoint(
2134    session_id: &str,
2135    installed: Option<&CheckpointMetadata>,
2136    native_session_id: &str,
2137    latched_ordinal: u64,
2138    latched_session: &CanonicalSessionSnapshot,
2139) -> Option<CheckpointArtifact> {
2140    let installed = installed?;
2141    if installed.event_frontier > latched_ordinal {
2142        tracing::warn!(
2143            session_id,
2144            installed_frontier = installed.event_frontier,
2145            latched_ordinal,
2146            "installed checkpoint is ahead of the latched cursor; exporting a fresh archive"
2147        );
2148        return None;
2149    }
2150    let verified = match verify_archive_streaming(&installed.archive_path) {
2151        Ok(verified) => verified,
2152        Err(error) => {
2153            tracing::warn!(
2154                session_id,
2155                path = %installed.archive_path.display(),
2156                "installed checkpoint could not be verified for reuse: {error:#}"
2157            );
2158            return None;
2159        }
2160    };
2161    if verified.archive_sha256 != installed.sha256
2162        || verified.manifest.session.id != session_id
2163        || verified.canonical_session.event_frontier != installed.event_frontier
2164    {
2165        tracing::warn!(
2166            session_id,
2167            path = %installed.archive_path.display(),
2168            "installed checkpoint no longer matches its controller metadata; exporting a fresh archive"
2169        );
2170        return None;
2171    }
2172    if !verified.canonical_session.content_matches(latched_session) {
2173        tracing::info!(
2174            session_id,
2175            archive_frontier = verified.canonical_session.event_frontier,
2176            latched_ordinal,
2177            "session content changed since the installed checkpoint; exporting a fresh archive"
2178        );
2179        return None;
2180    }
2181    tracing::info!(
2182        session_id,
2183        archive_frontier = verified.canonical_session.event_frontier,
2184        latched_ordinal,
2185        "reusing the installed checkpoint archive; only relay bookkeeping moved"
2186    );
2187    Some(CheckpointArtifact {
2188        metadata: installed.clone(),
2189        native_session_id: native_session_id.to_owned(),
2190        event_frontier_digest: verified.canonical_session.event_frontier_digest,
2191    })
2192}
2193
2194pub(super) fn verify_installed_checkpoint_gate(
2195    session_id: &str,
2196    checkpoint: &CheckpointMetadata,
2197) -> Result<()> {
2198    let sha256 = checkpoint_sha256(&checkpoint.archive_path).with_context(|| {
2199        format!(
2200            "hash installed checkpoint {} before target cleanup",
2201            checkpoint.archive_path.display()
2202        )
2203    })?;
2204    ensure!(
2205        sha256 == checkpoint.sha256,
2206        "refusing target cleanup for session {session_id}: installed checkpoint SHA changed"
2207    );
2208    Ok(())
2209}
2210
2211fn verify_checkpoint_artifact(session_id: &str, artifact: &CheckpointArtifact) -> Result<()> {
2212    let sha256 = checkpoint_sha256(&artifact.metadata.archive_path).with_context(|| {
2213        format!(
2214            "hash completed checkpoint {}",
2215            artifact.metadata.archive_path.display()
2216        )
2217    })?;
2218    ensure!(
2219        sha256 == artifact.metadata.sha256,
2220        "completed checkpoint SHA changed before persistence for session {session_id}"
2221    );
2222    Ok(())
2223}
2224
2225/// Release the projection history the new checkpoint covers.
2226///
2227/// The checkpoint archive holds the complete transcript up to its frontier, so
2228/// the tool output stored below that frontier is a second copy of something
2229/// already durable. Reclaiming it is housekeeping: a checkpoint that is
2230/// verified and persisted stays good whether or not this succeeds, so a
2231/// failure is logged rather than returned.
2232pub(super) fn release_projection_behind_checkpoint(session_id: &str, current: &CheckpointMetadata) {
2233    match hel::hel_database::compact_materialized_transcript_through(
2234        session_id,
2235        current.event_frontier,
2236    ) {
2237        Ok(retention) if retention.items == 0 => {}
2238        Ok(retention) => tracing::info!(
2239            session_id,
2240            items = retention.items,
2241            bytes = retention.bytes,
2242            remaining = retention.remaining,
2243            event_frontier = current.event_frontier,
2244            "released projection history the checkpoint covers"
2245        ),
2246        Err(error) => tracing::warn!(
2247            session_id,
2248            "checkpoint was saved, but the projection history it covers could not be released: {error:#}"
2249        ),
2250    }
2251}
2252
2253pub(super) fn prune_replaced_checkpoint(
2254    previous: Option<&CheckpointMetadata>,
2255    current: &CheckpointMetadata,
2256) {
2257    let Some(previous) = previous.filter(|old| old.archive_path != current.archive_path) else {
2258        return;
2259    };
2260    match hel::hel_database::move_checkpoint_is_retained(&previous.archive_path) {
2261        Ok(true) => return,
2262        Ok(false) => {}
2263        Err(error) => {
2264            tracing::warn!(%error, "could not check move retention; keeping superseded checkpoint");
2265            return;
2266        }
2267    }
2268    if let Err(error) = std::fs::remove_file(&previous.archive_path)
2269        && error.kind() != std::io::ErrorKind::NotFound
2270    {
2271        tracing::warn!(
2272            path = %previous.archive_path.display(),
2273            "could not remove superseded recovery copy: {error}"
2274        );
2275    }
2276}
2277
2278#[cfg(test)]
2279mod tests {
2280    use std::cell::{Cell, RefCell};
2281    use std::collections::BTreeMap;
2282    use std::fs::OpenOptions;
2283    use std::path::{Path, PathBuf};
2284    #[cfg(unix)]
2285    use std::process::Command;
2286    #[cfg(unix)]
2287    use std::time::Duration;
2288
2289    #[cfg(unix)]
2290    use agent_client_protocol::schema::v1::{ContentBlock, TextContent};
2291    use anyhow::Result;
2292
2293    #[cfg(unix)]
2294    use crate::hel_controller::now;
2295    use crate::hel_controller::restore_session_after_persistence_failure;
2296    use crate::hel_controller::test_support::{
2297        checkpoint_test_session, write_checkpoint_gate_archive,
2298    };
2299    #[cfg(unix)]
2300    use crate::hel_session_manager::{ManagedSessionHandle, new_command_id};
2301    use crate::hel_worker_client::RelayTransportDead;
2302    use hel::hel_archive::{
2303        BundleManifest, CanonicalTranscriptBody, CanonicalTranscriptItem, TargetManifest,
2304    };
2305    use hel::hel_checkpoint::CheckpointExportSpec;
2306    #[cfg(unix)]
2307    use hel::hel_config::{
2308        HarnessProfile, HelConfig, ProjectBundle, ProjectRepository, TargetTemplate,
2309    };
2310    use hel::hel_projection::canonical_session_from_materialized;
2311    #[cfg(unix)]
2312    use hel::hel_state::TargetLocator;
2313    use hel::hel_state::{
2314        CheckpointMetadata, HelState, ManagedSessionSnapshot, MaterializedSession, SessionState,
2315    };
2316    #[cfg(unix)]
2317    use hel::hel_targets::ProvisionStage;
2318    use hel::hel_targets::{self, CommandExecutor, CommandOutput, CommandSpec};
2319    #[cfg(unix)]
2320    use hel::hel_worker::RelayCommandOutcome;
2321    use hel::hel_worker::{RelayCommand, RelayCursor, RelayExecutionState};
2322
2323    use super::*;
2324
2325    #[test]
2326    fn startup_reconciliation_only_removes_unreferenced_controller_checkpoints() {
2327        let directory = tempfile::tempdir().unwrap();
2328        let session_id = "1123456789abcdef0123456789abcdef";
2329        let referenced_name =
2330            format!("{session_id}-7-archive-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.hel.zip");
2331        let orphan_name =
2332            format!("{session_id}-8-archive-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.hel.zip");
2333        let imported_name = format!("{session_id}.hel.zip");
2334        for name in [
2335            &referenced_name,
2336            &orphan_name,
2337            &imported_name,
2338            "notes.hel.zip",
2339        ] {
2340            std::fs::write(directory.path().join(name), b"test").unwrap();
2341        }
2342        let mut state = HelState::default();
2343        let mut session = checkpoint_test_session(session_id);
2344        session.checkpoint = Some(CheckpointMetadata {
2345            archive_path: directory.path().join(&referenced_name),
2346            sha256: "c".repeat(64),
2347            created_at: "2026-08-12T00:00:00Z".into(),
2348            event_frontier: 7,
2349        });
2350        state.sessions.insert(session_id.into(), session);
2351
2352        assert_eq!(
2353            reconcile_managed_checkpoint_archives_in(directory.path(), &state).unwrap(),
2354            1
2355        );
2356        assert!(directory.path().join(referenced_name).exists());
2357        assert!(!directory.path().join(orphan_name).exists());
2358        assert!(directory.path().join(imported_name).exists());
2359        assert!(directory.path().join("notes.hel.zip").exists());
2360    }
2361    #[test]
2362    fn recovery_artifact_final_verification_checks_the_archive_digest() {
2363        let directory = tempfile::tempdir().unwrap();
2364        let session_id = "1123456789abcdef0123456789abcdef";
2365        let metadata = write_checkpoint_gate_archive(directory.path(), session_id, 7);
2366        let mut artifact = CheckpointArtifact {
2367            metadata,
2368            native_session_id: "native-session".into(),
2369            event_frontier_digest: "a".repeat(64),
2370        };
2371
2372        verify_checkpoint_artifact(session_id, &artifact).unwrap();
2373        artifact.metadata.sha256 = "b".repeat(64);
2374        assert!(
2375            verify_checkpoint_artifact(session_id, &artifact)
2376                .unwrap_err()
2377                .to_string()
2378                .contains("checkpoint SHA changed")
2379        );
2380    }
2381    /// A snapshot of a session whose checkpoint barrier is open but not yet
2382    /// ready, projected exactly at `cursor`.
2383    fn checkpoint_barrier_snapshot(cursor: &RelayCursor) -> ManagedSessionSnapshot {
2384        let mut materialized = MaterializedSession::empty("session-1");
2385        materialized.applied_event_ordinal = cursor.ordinal;
2386        materialized.applied_event_digest = cursor.digest.clone();
2387        ManagedSessionSnapshot {
2388            window: hel::hel_state::ProjectionWindow::of(&materialized),
2389            materialized,
2390            latest_credential_sync_signal: None,
2391            worker_build: None,
2392            operational: hel::hel_worker::RelayOperationalState {
2393                capacity_retry: None,
2394                activity_turn_started_at_ms: None,
2395                acp_ready: None,
2396                store_id: None,
2397                idle_since_ms: None,
2398                session_id: "session-1".into(),
2399                execution: RelayExecutionState::Idle,
2400                latest_ordinal: cursor.ordinal,
2401                latest_digest: cursor.digest.clone(),
2402                acknowledged_through: cursor.ordinal,
2403                acknowledged_digest: cursor.digest.clone(),
2404                recovery_floor_ordinal: 0,
2405                recovery_floor_digest: hel::hel_worker::RELAY_EVENT_GENESIS_DIGEST.into(),
2406                native_session_id: Some("native-session".into()),
2407                agent_capabilities: None,
2408                agent_info: None,
2409                steering_supported: None,
2410                config_options: Vec::new(),
2411                modes: None,
2412                available_commands: Vec::new(),
2413                config: BTreeMap::new(),
2414                active_prompt: None,
2415                queued_prompts: Vec::new(),
2416                active_user_shells: Vec::new(),
2417                active_agent_terminals: Vec::new(),
2418                checkpoint_barrier: Some("checkpoint-1".into()),
2419                checkpoint_ready: None,
2420                last_acp_activity_at_ms: None,
2421                current_step_started_at_ms: None,
2422                foreground_tool_started_at_ms: None,
2423                harness_turn: None,
2424                last_harness_turn_started_ordinal: None,
2425                background_commands: Vec::new(),
2426                background_work_known: None,
2427            },
2428        }
2429    }
2430    #[test]
2431    fn checkpoint_barrier_is_not_reached_until_its_ready_cursor_is_projected() {
2432        let cursor = RelayCursor {
2433            ordinal: 7,
2434            digest: "a".repeat(64),
2435        };
2436        let mut snapshot = checkpoint_barrier_snapshot(&cursor);
2437
2438        assert!(!checkpoint_barrier_is_ready(&snapshot, "checkpoint-1"));
2439        snapshot.operational.checkpoint_ready = Some(cursor.clone());
2440        assert!(checkpoint_barrier_is_ready(&snapshot, "checkpoint-1"));
2441        validate_checkpoint_barrier_snapshot(&snapshot, "checkpoint-1", &cursor).unwrap();
2442    }
2443    #[test]
2444    fn checkpoint_revalidation_accepts_a_frontier_that_moved_past_the_ready_cursor() {
2445        let cursor = RelayCursor {
2446            ordinal: 7,
2447            digest: "a".repeat(64),
2448        };
2449        let mut snapshot = checkpoint_barrier_snapshot(&cursor);
2450        snapshot.operational.checkpoint_ready = Some(cursor.clone());
2451
2452        // An open ordinary barrier keeps accepting and journalling commands; it
2453        // only freezes dispatch. The archive still matches the sealed
2454        // workspace, so a frontier past the ready cursor stays valid.
2455        snapshot.operational.latest_ordinal = cursor.ordinal + 2;
2456        snapshot.operational.latest_digest = "b".repeat(64);
2457        snapshot.materialized.applied_event_ordinal = cursor.ordinal + 2;
2458        snapshot.materialized.applied_event_digest = "b".repeat(64);
2459        validate_checkpoint_barrier_snapshot(&snapshot, "checkpoint-1", &cursor).unwrap();
2460
2461        // Losing the barrier, or reaching a different cut, still invalidates it.
2462        snapshot.operational.checkpoint_ready = Some(RelayCursor {
2463            ordinal: cursor.ordinal + 1,
2464            digest: "c".repeat(64),
2465        });
2466        assert!(validate_checkpoint_barrier_snapshot(&snapshot, "checkpoint-1", &cursor).is_err());
2467        snapshot.operational.checkpoint_ready = Some(cursor.clone());
2468        snapshot.operational.checkpoint_barrier = None;
2469        assert!(validate_checkpoint_barrier_snapshot(&snapshot, "checkpoint-1", &cursor).is_err());
2470    }
2471
2472    #[test]
2473    fn routine_kimi_checkpoint_defers_when_background_liveness_is_not_safe() {
2474        let cursor = RelayCursor {
2475            ordinal: 7,
2476            digest: "a".repeat(64),
2477        };
2478        let mut snapshot = checkpoint_barrier_snapshot(&cursor);
2479        snapshot.operational.checkpoint_ready = Some(cursor.clone());
2480
2481        for (known, has_task, label) in [
2482            (Some(false), false, "tracker reported a failure"),
2483            (Some(true), true, "a native task is still active"),
2484            (None, false, "an older worker omitted the tracker field"),
2485        ] {
2486            snapshot.operational.background_work_known = known;
2487            snapshot.operational.background_commands = has_task
2488                .then(|| hel::hel_worker::BackgroundCommand {
2489                    id: "kimi:agent-1".into(),
2490                    started_at_ms: 1,
2491                    command: "background agent".into(),
2492                    can_stop: false,
2493                })
2494                .into_iter()
2495                .collect();
2496            let error = validate_automatic_checkpoint_barrier_snapshot(
2497                &snapshot,
2498                "checkpoint-1",
2499                &cursor,
2500                HarnessKind::Kimi,
2501            )
2502            .expect_err(label);
2503            assert!(checkpoint_was_deferred(&error), "{label}: {error:#}");
2504            assert!(!checkpoint_barrier_needs_worker_restart(&error));
2505        }
2506
2507        // A non-Kimi harness keeps the historical behavior even if the
2508        // optional Kimi field happens to be absent or a provider-level list is
2509        // present in a compatibility snapshot.
2510        snapshot.operational.background_work_known = None;
2511        snapshot.operational.background_commands = vec![hel::hel_worker::BackgroundCommand {
2512            id: "legacy-task".into(),
2513            started_at_ms: 1,
2514            command: "legacy background work".into(),
2515            can_stop: false,
2516        }];
2517        validate_automatic_checkpoint_barrier_snapshot(
2518            &snapshot,
2519            "checkpoint-1",
2520            &cursor,
2521            HarnessKind::Codex,
2522        )
2523        .expect("non-Kimi checkpoint compatibility");
2524    }
2525    /// Target-side answer of a successful export.
2526    fn exported_checkpoint_json() -> Vec<u8> {
2527        serde_json::to_vec(&hel::hel_checkpoint::TargetCheckpoint {
2528            path: PathBuf::from("/var/lib/hel/workers/session/checkpoint.hel.zip"),
2529            sha256: "c".repeat(64),
2530            event_frontier: 7,
2531            event_frontier_digest: "d".repeat(64),
2532            timings: None,
2533        })
2534        .unwrap()
2535    }
2536    fn export_spec_fixture() -> CheckpointExportSpec {
2537        CheckpointExportSpec {
2538            protocol_version: CHECKPOINT_EXPORT_PROTOCOL_VERSION,
2539            session: hel::hel_archive::SessionManifest {
2540                id: LATCH_RELAY_SESSION.into(),
2541                title: "streamed spec".into(),
2542                harness_kind: hel::hel_config::HarnessKind::Codex,
2543                profile_id: "codex".into(),
2544                native_session_id: "native-session".into(),
2545                created_at: "2026-08-12T00:00:00Z".into(),
2546                checkpointed_at: "2026-08-16T00:00:00Z".into(),
2547                hel_version: "test".into(),
2548                relay_version: "test".into(),
2549                adapter_version: "acp-v1".into(),
2550            },
2551            target: TargetManifest {
2552                template_id: "podman".into(),
2553                target_kind: "local-podman".into(),
2554                details: BTreeMap::new(),
2555            },
2556            bundle: BundleManifest {
2557                id: "project".into(),
2558                primary_repository: "app".into(),
2559            },
2560            relay_root: PathBuf::from("/var/lib/hel/workers/session"),
2561            harness_home: PathBuf::from("/var/lib/hel/profiles/codex"),
2562            workspace_root: PathBuf::from("/workspace"),
2563            repositories: Vec::new(),
2564            canonical_session: canonical_session_from_materialized(&MaterializedSession::empty(
2565                LATCH_RELAY_SESSION.to_owned(),
2566            ))
2567            .unwrap(),
2568            output_path: PathBuf::from("/var/lib/hel/workers/session/checkpoint.hel.zip"),
2569        }
2570    }
2571    /// Answers the streamed export with a scripted status, and every other
2572    /// command as a success.
2573    struct ExportExecutor {
2574        streamed_status: i32,
2575        streamed_stderr: String,
2576        retry_stdin_after_failure: bool,
2577        stdin_calls: Cell<usize>,
2578        purposes: RefCell<Vec<String>>,
2579        streamed_spec: RefCell<Vec<u8>>,
2580    }
2581    impl ExportExecutor {
2582        fn new(streamed_status: i32, streamed_stderr: &str) -> Self {
2583            Self {
2584                streamed_status,
2585                streamed_stderr: streamed_stderr.to_owned(),
2586                retry_stdin_after_failure: false,
2587                stdin_calls: Cell::new(0),
2588                purposes: RefCell::new(Vec::new()),
2589                streamed_spec: RefCell::new(Vec::new()),
2590            }
2591        }
2592
2593        fn retry_stdin_after_failure(mut self) -> Self {
2594            self.retry_stdin_after_failure = true;
2595            self
2596        }
2597    }
2598    impl CommandExecutor for ExportExecutor {
2599        fn execute(&self, command: &CommandSpec) -> Result<CommandOutput> {
2600            self.purposes.borrow_mut().push(command.purpose.clone());
2601            Ok(CommandOutput {
2602                status: 0,
2603                stdout: exported_checkpoint_json(),
2604                stderr: Vec::new(),
2605            })
2606        }
2607
2608        fn execute_with_stdin(
2609            &self,
2610            command: &CommandSpec,
2611            input: &mut (dyn std::io::Read + Send),
2612        ) -> Result<CommandOutput> {
2613            self.purposes.borrow_mut().push(command.purpose.clone());
2614            let mut spec = Vec::new();
2615            input.read_to_end(&mut spec)?;
2616            *self.streamed_spec.borrow_mut() = spec;
2617            let attempt = self.stdin_calls.get();
2618            self.stdin_calls.set(attempt + 1);
2619            let failed =
2620                self.streamed_status != 0 && (attempt == 0 || !self.retry_stdin_after_failure);
2621            Ok(CommandOutput {
2622                status: if failed { self.streamed_status } else { 0 },
2623                stdout: if failed {
2624                    Vec::new()
2625                } else {
2626                    exported_checkpoint_json()
2627                },
2628                stderr: if failed {
2629                    self.streamed_stderr.clone().into_bytes()
2630                } else {
2631                    Vec::new()
2632                },
2633            })
2634        }
2635    }
2636    #[test]
2637    fn docker_checkpoint_fallback_upload_uses_docker_cp() {
2638        struct RecordingExecutor {
2639            commands: RefCell<Vec<CommandSpec>>,
2640        }
2641        impl CommandExecutor for RecordingExecutor {
2642            fn execute(&self, command: &CommandSpec) -> Result<CommandOutput> {
2643                self.commands.borrow_mut().push(command.clone());
2644                Ok(CommandOutput {
2645                    status: 0,
2646                    stdout: Vec::new(),
2647                    stderr: Vec::new(),
2648                })
2649            }
2650        }
2651
2652        let executor = RecordingExecutor {
2653            commands: RefCell::new(Vec::new()),
2654        };
2655        let locator = hel_targets::TargetLocator::LocalDocker {
2656            container_id: "hel-session-12345678".to_owned(),
2657        };
2658        upload_checkpoint_spec(
2659            &executor,
2660            &locator,
2661            LATCH_RELAY_SESSION,
2662            Path::new("checkpoint-spec.json"),
2663            "/var/lib/hel/workers/session/checkpoint-spec.json",
2664        )
2665        .unwrap();
2666
2667        let commands = executor.commands.borrow();
2668        assert_eq!(commands.len(), 1);
2669        assert_eq!(commands[0].program, "docker");
2670        assert_eq!(
2671            commands[0].args,
2672            [
2673                "cp",
2674                "checkpoint-spec.json",
2675                "hel-session-12345678:/var/lib/hel/workers/session/checkpoint-spec.json"
2676            ]
2677        );
2678        assert_eq!(commands[0].purpose, "upload checkpoint specification");
2679    }
2680    #[test]
2681    fn checkpoint_export_streams_its_spec_instead_of_uploading_it() {
2682        let locator = hel_targets::TargetLocator::LocalPodman {
2683            container_id: hel_targets::resource_name(LATCH_RELAY_SESSION).unwrap(),
2684            workspace_storage: Default::default(),
2685        };
2686        let spec = export_spec_fixture();
2687        let executor = ExportExecutor::new(0, "");
2688
2689        let output = export_target_checkpoint(
2690            &executor,
2691            &locator,
2692            LATCH_RELAY_SESSION,
2693            &spec,
2694            "/var/lib/hel/workers/session/checkpoint-spec.json",
2695        )
2696        .unwrap();
2697
2698        assert_eq!(output.stdout, exported_checkpoint_json());
2699        assert_eq!(
2700            serde_json::from_slice::<CheckpointExportSpec>(&executor.streamed_spec.borrow())
2701                .unwrap(),
2702            spec
2703        );
2704        assert_eq!(
2705            executor.purposes.into_inner(),
2706            vec!["export target checkpoint".to_owned()]
2707        );
2708    }
2709    /// A worker copied into the target before `--spec -` existed reads the dash
2710    /// as a file name. The checkpoint has to keep working on it.
2711    #[test]
2712    fn an_export_that_cannot_read_stdin_falls_back_to_uploading_the_spec() {
2713        let locator = hel_targets::TargetLocator::LocalPodman {
2714            container_id: hel_targets::resource_name(LATCH_RELAY_SESSION).unwrap(),
2715            workspace_storage: Default::default(),
2716        };
2717        let executor = ExportExecutor::new(
2718            1,
2719            "Error: read checkpoint export spec -\n\nCaused by:\n    \
2720                 No such file or directory (os error 2)\n",
2721        );
2722
2723        let output = export_target_checkpoint(
2724            &executor,
2725            &locator,
2726            LATCH_RELAY_SESSION,
2727            &export_spec_fixture(),
2728            "/var/lib/hel/workers/session/checkpoint-spec.json",
2729        )
2730        .unwrap();
2731
2732        assert_eq!(output.stdout, exported_checkpoint_json());
2733        assert_eq!(
2734            executor.purposes.into_inner(),
2735            vec![
2736                "export target checkpoint".to_owned(),
2737                "upload checkpoint specification".to_owned(),
2738                "export target checkpoint".to_owned(),
2739            ]
2740        );
2741    }
2742    #[test]
2743    fn a_failing_export_is_not_retried_as_an_old_worker() {
2744        let locator = hel_targets::TargetLocator::LocalPodman {
2745            container_id: hel_targets::resource_name(LATCH_RELAY_SESSION).unwrap(),
2746            workspace_storage: Default::default(),
2747        };
2748        let executor = ExportExecutor::new(1, "Error: repository 'app' is missing\n");
2749
2750        let error = export_target_checkpoint(
2751            &executor,
2752            &locator,
2753            LATCH_RELAY_SESSION,
2754            &export_spec_fixture(),
2755            "/var/lib/hel/workers/session/checkpoint-spec.json",
2756        )
2757        .unwrap_err();
2758
2759        assert!(
2760            format!("{error:#}").contains("repository 'app' is missing"),
2761            "{error:#}"
2762        );
2763        assert_eq!(
2764            executor.purposes.into_inner(),
2765            vec!["export target checkpoint".to_owned()]
2766        );
2767    }
2768    /// The explicit export protocol field makes every older worker reject the
2769    /// current spec before it can apply obsolete path or collection behavior.
2770    #[test]
2771    fn a_legacy_export_worker_is_replaced_before_it_runs_obsolete_behavior() {
2772        let locator = hel_targets::TargetLocator::LocalPodman {
2773            container_id: hel_targets::resource_name(LATCH_RELAY_SESSION).unwrap(),
2774            workspace_storage: Default::default(),
2775        };
2776        let spec = export_spec_fixture();
2777        let executor = ExportExecutor::new(
2778            1,
2779            "Error: parse checkpoint export spec from standard input\n\nCaused by:\n    \
2780                 unknown field `protocol_version`, expected `session` at line 1 column 20\n",
2781        )
2782        .retry_stdin_after_failure();
2783        let worker_binary = Path::new("/hel-test-worker");
2784
2785        let output = export_target_checkpoint_with_worker(
2786            &executor,
2787            &locator,
2788            LATCH_RELAY_SESSION,
2789            &spec,
2790            "/var/lib/hel/workers/session/checkpoint-spec.json",
2791            Some(worker_binary),
2792        )
2793        .unwrap();
2794
2795        assert_eq!(output.stdout, exported_checkpoint_json());
2796        assert_eq!(
2797            serde_json::from_slice::<CheckpointExportSpec>(&executor.streamed_spec.borrow())
2798                .unwrap(),
2799            spec
2800        );
2801        assert_eq!(
2802            executor.purposes.into_inner(),
2803            vec![
2804                "export target checkpoint".to_owned(),
2805                "stage replacement Mjolnir worker".to_owned(),
2806                "assign replacement worker to the worker user".to_owned(),
2807                "replace installed Mjolnir worker".to_owned(),
2808                "make replaced Mjolnir worker executable".to_owned(),
2809                "export target checkpoint".to_owned(),
2810            ]
2811        );
2812    }
2813    #[test]
2814    fn a_schema_mismatch_after_uploading_the_spec_still_replaces_the_worker_binary() {
2815        let locator = hel_targets::TargetLocator::LocalPodman {
2816            container_id: hel_targets::resource_name(LATCH_RELAY_SESSION).unwrap(),
2817            workspace_storage: Default::default(),
2818        };
2819        struct FileThenRefreshExecutor {
2820            purposes: RefCell<Vec<String>>,
2821            file_export_calls: Cell<usize>,
2822        }
2823        impl CommandExecutor for FileThenRefreshExecutor {
2824            fn execute(&self, command: &CommandSpec) -> Result<CommandOutput> {
2825                self.purposes.borrow_mut().push(command.purpose.clone());
2826                if command.purpose == "export target checkpoint" {
2827                    let attempt = self.file_export_calls.get();
2828                    self.file_export_calls.set(attempt + 1);
2829                    if attempt == 0 {
2830                        return Ok(CommandOutput {
2831                            status: 1,
2832                            stdout: Vec::new(),
2833                            stderr: b"Error: parse checkpoint export spec /spec.json\n\nCaused by:\n    unknown variant `terminal_output`, expected one of `user`, `agent`, `thought`, `tool`, `plan`, `system`\n".to_vec(),
2834                        });
2835                    }
2836                }
2837                Ok(CommandOutput {
2838                    status: 0,
2839                    stdout: exported_checkpoint_json(),
2840                    stderr: Vec::new(),
2841                })
2842            }
2843
2844            fn execute_with_stdin(
2845                &self,
2846                command: &CommandSpec,
2847                input: &mut (dyn std::io::Read + Send),
2848            ) -> Result<CommandOutput> {
2849                self.purposes.borrow_mut().push(command.purpose.clone());
2850                let mut discarded = Vec::new();
2851                input.read_to_end(&mut discarded)?;
2852                let stdin_calls = self
2853                    .purposes
2854                    .borrow()
2855                    .iter()
2856                    .filter(|purpose| *purpose == "export target checkpoint")
2857                    .count();
2858                if stdin_calls == 1 {
2859                    return Ok(CommandOutput {
2860                        status: 1,
2861                        stdout: Vec::new(),
2862                        stderr: b"Error: read checkpoint export spec -\n\nCaused by:\n    No such file or directory (os error 2)\n".to_vec(),
2863                    });
2864                }
2865                Ok(CommandOutput {
2866                    status: 0,
2867                    stdout: exported_checkpoint_json(),
2868                    stderr: Vec::new(),
2869                })
2870            }
2871        }
2872
2873        let executor = FileThenRefreshExecutor {
2874            purposes: RefCell::new(Vec::new()),
2875            file_export_calls: Cell::new(0),
2876        };
2877        let output = export_target_checkpoint_with_worker(
2878            &executor,
2879            &locator,
2880            LATCH_RELAY_SESSION,
2881            &export_spec_fixture(),
2882            "/var/lib/hel/workers/session/checkpoint-spec.json",
2883            Some(Path::new("/hel-test-worker")),
2884        )
2885        .unwrap();
2886
2887        assert_eq!(output.stdout, exported_checkpoint_json());
2888        assert_eq!(
2889            executor.purposes.into_inner(),
2890            vec![
2891                "export target checkpoint".to_owned(),
2892                "upload checkpoint specification".to_owned(),
2893                "export target checkpoint".to_owned(),
2894                "stage replacement Mjolnir worker".to_owned(),
2895                "assign replacement worker to the worker user".to_owned(),
2896                "replace installed Mjolnir worker".to_owned(),
2897                "make replaced Mjolnir worker executable".to_owned(),
2898                "export target checkpoint".to_owned(),
2899            ]
2900        );
2901    }
2902    /// A session that is working is busy, not wedged. A copy that can run
2903    /// again later leaves at once instead of waiting out the deadline, which
2904    /// would restart the worker and kill the turn in flight.
2905    #[test]
2906    fn a_working_session_defers_but_close_waits_for_cancellation_before_recovery() {
2907        let cursor = RelayCursor {
2908            ordinal: 7,
2909            digest: "a".repeat(64),
2910        };
2911        let mut snapshot = checkpoint_barrier_snapshot(&cursor);
2912        snapshot.operational.execution = RelayExecutionState::Running;
2913
2914        let deferred = checkpoint_barrier_wait_ended(
2915            &snapshot,
2916            "checkpoint-1",
2917            BarrierBusyPolicy::DeferWhileRunning,
2918            false,
2919            false,
2920        )
2921        .expect("a working session ends the wait at once");
2922        assert!(checkpoint_was_deferred(&deferred), "{deferred:#}");
2923        assert!(
2924            !checkpoint_barrier_needs_worker_restart(&deferred),
2925            "a deferred copy must never restart the worker: {deferred:#}"
2926        );
2927        assert_eq!(
2928            BarrierBusyPolicy::of(LatchExclusivity::HoldThroughClose),
2929            BarrierBusyPolicy::InterruptWhileRunning
2930        );
2931
2932        // Stop requests a non-steering cancellation and waits for the real
2933        // turn boundary instead of selecting restart recovery immediately.
2934        assert!(
2935            checkpoint_barrier_wait_ended(
2936                &snapshot,
2937                "checkpoint-1",
2938                BarrierBusyPolicy::InterruptWhileRunning,
2939                false,
2940                false,
2941            )
2942            .is_none()
2943        );
2944        let interrupted = checkpoint_barrier_wait_ended(
2945            &snapshot,
2946            "checkpoint-1",
2947            BarrierBusyPolicy::InterruptWhileRunning,
2948            true,
2949            true,
2950        )
2951        .expect("an unresponsive cancellation ends the wait at the deadline");
2952        assert!(
2953            checkpoint_barrier_needs_worker_restart(&interrupted),
2954            "{interrupted:#}"
2955        );
2956        assert!(!checkpoint_was_deferred(&interrupted), "{interrupted:#}");
2957
2958        // An idle session that never admits the barrier is the real wedge,
2959        // whatever the policy.
2960        snapshot.operational.execution = RelayExecutionState::Idle;
2961        let wedged = checkpoint_barrier_wait_ended(
2962            &snapshot,
2963            "checkpoint-1",
2964            BarrierBusyPolicy::DeferWhileRunning,
2965            true,
2966            false,
2967        )
2968        .expect("the deadline ends the wait");
2969        assert!(
2970            checkpoint_barrier_needs_worker_restart(&wedged),
2971            "{wedged:#}"
2972        );
2973        assert!(!checkpoint_was_deferred(&wedged), "{wedged:#}");
2974    }
2975
2976    /// The relay moved on before the controller latched, so the archive would
2977    /// not be an exact cut. That is a deferral, not a failed checkpoint.
2978    #[test]
2979    fn a_frontier_that_moved_before_the_latch_defers_the_checkpoint() {
2980        let cursor = RelayCursor {
2981            ordinal: 220,
2982            digest: "a".repeat(64),
2983        };
2984        ensure_exact_checkpoint_cut(&cursor, cursor.ordinal, &cursor.digest)
2985            .expect("a projection latched at the ready cursor is an exact cut");
2986
2987        for (ordinal, digest) in [(223, "a".repeat(64)), (220, "b".repeat(64))] {
2988            let error = ensure_exact_checkpoint_cut(&cursor, ordinal, &digest)
2989                .expect_err("a projection past the ready cursor is not an exact cut");
2990            assert!(checkpoint_was_deferred(&error), "{error:#}");
2991            assert!(
2992                !checkpoint_barrier_needs_worker_restart(&error),
2993                "{error:#}"
2994            );
2995        }
2996    }
2997
2998    /// The barrier freezes Mjolnir's dispatch, not the harness. A turn the harness
2999    /// started on its own after the cursor was captured may have written to
3000    /// the workspace while it was staged, so that archive is abandoned.
3001    #[test]
3002    fn a_harness_turn_started_during_capture_abandons_the_archive() {
3003        let cursor = RelayCursor {
3004            ordinal: 220,
3005            digest: "a".repeat(64),
3006        };
3007        let mut snapshot = checkpoint_barrier_snapshot(&cursor);
3008        snapshot.operational.checkpoint_ready = Some(cursor.clone());
3009
3010        snapshot.operational.last_harness_turn_started_ordinal = Some(cursor.ordinal);
3011        validate_checkpoint_barrier_snapshot(&snapshot, "checkpoint-1", &cursor)
3012            .expect("a turn that started at or before the cursor is covered by the archive");
3013
3014        snapshot.operational.last_harness_turn_started_ordinal = Some(cursor.ordinal + 1);
3015        let error = validate_checkpoint_barrier_snapshot(&snapshot, "checkpoint-1", &cursor)
3016            .expect_err("a turn that started after the cursor invalidates the capture");
3017        assert!(checkpoint_was_deferred(&error), "{error:#}");
3018    }
3019
3020    #[test]
3021    fn a_stuck_checkpoint_barrier_is_retried_by_restarting_the_worker() {
3022        // Both ways the wait can end without a barrier, each wrapped the way
3023        // the checkpoint path wraps them, and each still asking for the retry.
3024        for failure in [
3025            CheckpointBarrierUnreachable::not_admitted(
3026                "checkpoint-976f6746887c5ccd93b9d8bbe120ef06",
3027            ),
3028            CheckpointBarrierUnreachable::runtime_stopped(),
3029        ] {
3030            let error = anyhow::Error::new(failure).context("latch a session checkpoint");
3031            assert!(checkpoint_barrier_needs_worker_restart(&error), "{error:#}");
3032        }
3033        assert!(!checkpoint_barrier_needs_worker_restart(&anyhow::anyhow!(
3034            "export target checkpoint failed with status 1"
3035        )));
3036        // The decision reads the type, not the text, so the old wording alone
3037        // no longer restarts a worker and rewording one cannot stop it either.
3038        assert!(!checkpoint_barrier_needs_worker_restart(&anyhow::anyhow!(
3039            "ACP relay did not reach checkpoint barrier checkpoint-1"
3040        )));
3041    }
3042
3043    #[test]
3044    fn an_incompatible_cancel_turn_requests_worker_recovery() {
3045        let error = anyhow::Error::new(RelayRejected(hel::hel_worker::RelayProtocolError {
3046            code: hel::hel_worker::RelayErrorCode::IncompatibleProtocol,
3047            message: "request uses protocol 6".into(),
3048            retryable: false,
3049            detail: None,
3050        }))
3051        .context("cancel active ACP turn before checkpoint barrier");
3052        assert!(
3053            checkpoint_cancel_turn_needs_worker_restart(&error),
3054            "{error:#}"
3055        );
3056        assert!(checkpoint_barrier_needs_worker_restart(&error.context(
3057            CheckpointBarrierUnreachable::cancel_turn_unavailable("checkpoint-1", 6,)
3058        )));
3059    }
3060    #[test]
3061    fn a_dead_worker_hello_failure_is_retried_by_restarting_the_worker() {
3062        let dead = anyhow::Error::new(RelayTransportDead::new("the proxy is gone"))
3063            .context("connect to the session worker for checkpoint");
3064        assert!(worker_connect_needs_restart(&dead), "{dead:#}");
3065        assert!(!worker_connect_needs_restart(&anyhow::anyhow!(
3066            "unknown session"
3067        )));
3068    }
3069    #[cfg(unix)]
3070    #[tokio::test]
3071    async fn checkpoint_restart_stop_failure_names_mjolnir() {
3072        struct FailingStop;
3073
3074        impl CommandExecutor for FailingStop {
3075            fn execute(&self, _command: &CommandSpec) -> Result<CommandOutput> {
3076                Ok(CommandOutput {
3077                    status: 1,
3078                    stdout: Vec::new(),
3079                    stderr: b"permission denied".to_vec(),
3080                })
3081            }
3082        }
3083
3084        let session_id = "0123456789abcdef0123456789abcdef";
3085        let worker_root = format!("/tmp/mjolnir-checkpoint-test/{session_id}");
3086        let backend = hel_targets::TargetLocator::LocalBare {
3087            worker_root: worker_root.clone(),
3088        };
3089        let controller = Controller {
3090            config: HelConfig::default(),
3091            state: HelState::default(),
3092        };
3093        let reconnect = CommandSpec::new("unused", std::iter::empty::<&str>());
3094
3095        let result = controller
3096            .restart_worker_for_checkpoint(
3097                session_id,
3098                &FailingStop,
3099                &backend,
3100                &worker_root,
3101                &reconnect,
3102            )
3103            .await;
3104        let error = match result {
3105            Ok(_) => panic!("a failed worker stop unexpectedly restarted the checkpoint worker"),
3106            Err(error) => error,
3107        };
3108        let detail = format!("{error:#}");
3109        assert!(
3110            detail.starts_with("stop wedged Mjolnir worker before retrying checkpoint"),
3111            "{detail}"
3112        );
3113        assert!(detail.contains("permission denied"), "{detail}");
3114    }
3115    #[test]
3116    fn export_spec_schema_mismatch_is_detected_from_the_parse_error() {
3117        assert!(export_spec_schema_unsupported(
3118            "Error: parse checkpoint export spec from standard input\n\nCaused by:\n    \
3119                 unknown field `terminal_refs`, expected `call` at line 1 column 7276552\n"
3120        ));
3121        assert!(export_spec_schema_unsupported(
3122            "Error: parse checkpoint export spec /spec.json\n\nCaused by:\n    \
3123                 unknown variant `terminal_output`, expected one of `user`, `agent`\n"
3124        ));
3125        assert!(!export_spec_schema_unsupported(
3126            "Error: repository 'app' is missing\n"
3127        ));
3128        assert!(!export_spec_schema_unsupported(
3129            "Error: parse checkpoint export spec from standard input\n\nCaused by:\n    \
3130                 missing field `relay_root`\n"
3131        ));
3132        assert!(export_protocol_unsupported(
3133            "Error: unsupported checkpoint export protocol version 3; worker supports 2\n"
3134        ));
3135    }
3136    const LATCH_RELAY_ROOT: &str = "MJ_TEST_LATCH_RELAY_ROOT";
3137    const LATCH_RELAY_STARTS: &str = "MJ_TEST_LATCH_RELAY_STARTS";
3138    const LATCH_RELAY_REJECT_RELEASE: &str = "MJ_TEST_LATCH_REJECT_RELEASE";
3139    #[cfg(unix)]
3140    const LATCH_RELAY_RUNNING: &str = "MJ_TEST_LATCH_RELAY_RUNNING";
3141    #[cfg(unix)]
3142    const LATCH_TEST_CHILD: &str = "MJ_TEST_LATCH_CHILD";
3143    #[cfg(unix)]
3144    const ABANDON_TEST_CHILD: &str = "MJ_TEST_ABANDON_LATCH_CHILD";
3145    #[cfg(unix)]
3146    const RELEASE_TEST_CHILD: &str = "MJ_TEST_RELEASE_LATCH_CHILD";
3147    #[cfg(unix)]
3148    const LEGACY_RELEASE_TEST_CHILD: &str = "MJ_TEST_LEGACY_RELEASE_LATCH_CHILD";
3149    #[cfg(unix)]
3150    const REUSE_TEST_CHILD: &str = "MJ_TEST_REUSE_LATCH_CHILD";
3151    const LATCH_RELAY_STARTUP_DELAY_MS: &str = "MJ_TEST_LATCH_STARTUP_DELAY_MS";
3152    const LATCH_RELAY_SESSION: &str = "018f9dd2-a3b4-7c8d-9000-0123456789ab";
3153    /// Whether the scripted relay understands the early checkpoint release.
3154    #[cfg(unix)]
3155    #[derive(Clone, Copy, PartialEq, Eq)]
3156    enum ReleaseSupport {
3157        Supported,
3158        /// Answer a release exactly as a worker that predates the command does:
3159        /// its `RelayCommand` cannot deserialize the variant at all.
3160        Rejected,
3161    }
3162    /// Relay server half of the checkpoint latch test.
3163    ///
3164    /// A durable relay only reports a checkpoint barrier ready once a dispatch
3165    /// driver claims it, so this also runs the one step the worker runtime
3166    /// performs for a barrier. It does nothing unless a parent test points it
3167    /// at a relay journal root.
3168    #[test]
3169    fn latch_relay_child_serves_stdio() {
3170        let Some(root) = std::env::var_os(LATCH_RELAY_ROOT) else {
3171            return;
3172        };
3173        // With `--nocapture` libtest writes `test <name> ... ` without a
3174        // trailing newline before the body runs. End that line first so it
3175        // cannot glue itself onto the first protocol frame.
3176        println!();
3177        // Record this start so a parent test can tell a reconnect from a reused
3178        // connection.
3179        if let Some(starts) = std::env::var_os(LATCH_RELAY_STARTS) {
3180            use std::io::Write;
3181            let mut log = OpenOptions::new()
3182                .create(true)
3183                .append(true)
3184                .open(starts)
3185                .expect("open the relay start log");
3186            writeln!(log, "{}", std::process::id()).expect("record this relay start");
3187        }
3188        let mut relay =
3189            hel::hel_worker::DurableRelay::open(Path::new(&root), LATCH_RELAY_SESSION, "1.0.0")
3190                .expect("open the test relay journal");
3191        if relay.operational_state().native_session_id.is_none() {
3192            relay
3193                .record_observation(hel::hel_worker::RelayObservation::SessionOpened {
3194                    native_session_id: "native-session".into(),
3195                    resumed: true,
3196                })
3197                .unwrap();
3198        }
3199        let ready_at = Instant::now()
3200            + Duration::from_millis(
3201                std::env::var(LATCH_RELAY_STARTUP_DELAY_MS)
3202                    .ok()
3203                    .map(|value| value.parse::<u64>().unwrap())
3204                    .unwrap_or(0),
3205            );
3206        let reject_release = std::env::var_os(LATCH_RELAY_REJECT_RELEASE).is_some();
3207        #[cfg(unix)]
3208        let running = std::env::var_os(LATCH_RELAY_RUNNING).is_some();
3209        #[cfg(unix)]
3210        if running && relay.operational_state().active_prompt.is_none() {
3211            let response = relay.handle(hel::hel_worker::RelayRequestEnvelope {
3212                request_id: "seed-running-request".into(),
3213                protocol_version: hel::hel_worker::RELAY_PROTOCOL_VERSION,
3214                request: hel::hel_worker::RelayRequest::Submit {
3215                    command_id: "seed-running-prompt".into(),
3216                    command: RelayCommand::Prompt {
3217                        prompt: vec![ContentBlock::Text(TextContent::new("running"))],
3218                    },
3219                },
3220            });
3221            assert!(matches!(
3222                response.body,
3223                hel::hel_worker::RelayResponseBody::Ok {
3224                    payload: hel::hel_worker::RelayResponsePayload::Accepted { .. }
3225                }
3226            ));
3227            let claimed = relay
3228                .claim_pending_commands(true)
3229                .expect("seed the running prompt");
3230            assert_eq!(claimed.len(), 1);
3231            assert_eq!(claimed[0].command_id, "seed-running-prompt");
3232        }
3233        let mut reader = std::io::stdin().lock();
3234        let mut writer = std::io::stdout().lock();
3235        let mut configured = false;
3236        while let Some(request) =
3237            hel::hel_worker::read_relay_frame(&mut reader).expect("read a relay request")
3238        {
3239            if !configured && Instant::now() >= ready_at {
3240                relay
3241                    .record_observation(hel::hel_worker::RelayObservation::SessionConfigured {
3242                        config_options: Vec::new(),
3243                    })
3244                    .unwrap();
3245                configured = true;
3246            }
3247            if matches!(
3248                &request.request,
3249                hel::hel_worker::RelayRequest::Submit {
3250                    command: RelayCommand::BeginCheckpoint { .. },
3251                    ..
3252                }
3253            ) {
3254                assert!(
3255                    relay.operational_state().native_session_is_ready(),
3256                    "checkpoint submitted before current ACP startup finished"
3257                );
3258            }
3259            let response = if reject_release && requests_checkpoint_release(&request) {
3260                unparseable_request_response(&request)
3261            } else {
3262                relay.handle(request)
3263            };
3264            hel::hel_worker::write_relay_frame(&mut writer, &response)
3265                .expect("answer a relay request");
3266            for claimed in relay
3267                .claim_pending_commands(true)
3268                .expect("claim relay commands")
3269            {
3270                match claimed.command {
3271                    RelayCommand::BeginCheckpoint { .. } => {
3272                        relay
3273                            .record_checkpoint_ready(&claimed.command_id)
3274                            .expect("report the checkpoint barrier ready");
3275                    }
3276                    #[cfg(unix)]
3277                    RelayCommand::CancelTurn => {
3278                        let prompt_id = relay
3279                            .operational_state()
3280                            .active_prompt
3281                            .as_ref()
3282                            .map(|prompt| prompt.command_id.clone())
3283                            .expect("a prompt to cancel");
3284                        relay
3285                            .record_command_completed(
3286                                &claimed.command_id,
3287                                RelayCommandOutcome::Cancelled,
3288                            )
3289                            .expect("complete the cancellation");
3290                        relay
3291                            .record_command_completed(
3292                                &prompt_id,
3293                                RelayCommandOutcome::Prompt {
3294                                    stop_reason: "cancelled".into(),
3295                                },
3296                            )
3297                            .expect("complete the cancelled prompt");
3298                    }
3299                    _ => {}
3300                }
3301            }
3302        }
3303    }
3304    fn requests_checkpoint_release(request: &hel::hel_worker::RelayRequestEnvelope) -> bool {
3305        matches!(
3306            &request.request,
3307            hel::hel_worker::RelayRequest::Submit {
3308                command: RelayCommand::ReleaseCheckpoint { .. },
3309                ..
3310            }
3311        )
3312    }
3313    /// The answer a worker gives for a frame its own protocol cannot decode.
3314    /// An older `RelayCommand` has no `release_checkpoint` variant, and the
3315    /// enum denies unknown ones, so the request never reaches its relay.
3316    fn unparseable_request_response(
3317        request: &hel::hel_worker::RelayRequestEnvelope,
3318    ) -> hel::hel_worker::RelayResponseEnvelope {
3319        hel::hel_worker::RelayResponseEnvelope {
3320            request_id: request.request_id.clone(),
3321            protocol_version: request.protocol_version,
3322            body: hel::hel_worker::RelayResponseBody::Error {
3323                error: hel::hel_worker::RelayProtocolError {
3324                    code: hel::hel_worker::RelayErrorCode::InvalidRequest,
3325                    message: "unknown variant `release_checkpoint`".into(),
3326                    retryable: false,
3327                    detail: None,
3328                },
3329            },
3330        }
3331    }
3332    /// A relay target served by this test binary over stdio. Each start of the
3333    /// server appends to `starts`, if given.
3334    #[cfg(unix)]
3335    fn latch_relay_target(
3336        relay_root: &Path,
3337        starts: Option<&Path>,
3338        release: ReleaseSupport,
3339        running: bool,
3340    ) -> crate::hel_session_manager::RelaySessionTarget {
3341        // `RelayClient` parses every stdout line as JSON, so libtest's own
3342        // progress lines are dropped before they reach the protocol reader.
3343        let script = format!(
3344            "\"$0\" --exact {}::latch_relay_child_serves_stdio --nocapture | \
3345                 grep --line-buffered '^{{'",
3346            module_path!()
3347                .strip_prefix("mj_controller::")
3348                .unwrap_or(module_path!())
3349        );
3350        let mut spec = CommandSpec::new(
3351            "sh",
3352            [
3353                "-c".to_owned(),
3354                script,
3355                std::env::current_exe()
3356                    .unwrap()
3357                    .to_string_lossy()
3358                    .into_owned(),
3359            ],
3360        )
3361        .purpose("test latch relay");
3362        spec.env.insert(
3363            LATCH_RELAY_ROOT.to_owned(),
3364            relay_root.to_string_lossy().into_owned(),
3365        );
3366        if let Some(starts) = starts {
3367            spec.env.insert(
3368                LATCH_RELAY_STARTS.to_owned(),
3369                starts.to_string_lossy().into_owned(),
3370            );
3371        }
3372        if release == ReleaseSupport::Rejected {
3373            spec.env
3374                .insert(LATCH_RELAY_REJECT_RELEASE.to_owned(), "1".to_owned());
3375        }
3376        if running {
3377            spec.env
3378                .insert(LATCH_RELAY_RUNNING.to_owned(), "1".to_owned());
3379        }
3380        crate::hel_session_manager::RelaySessionTarget {
3381            session_id: LATCH_RELAY_SESSION.to_owned(),
3382            spec,
3383            worker_recovery: None,
3384            project_memory: None,
3385        }
3386    }
3387    /// Start a session manager against a live relay and latch a checkpoint on
3388    /// it, exactly as [`Controller::checkpoint_session_latched`] does.
3389    #[cfg(unix)]
3390    async fn latch_a_live_checkpoint(
3391        relay_root: &Path,
3392        starts: Option<&Path>,
3393        release: ReleaseSupport,
3394        running: bool,
3395    ) -> (
3396        crate::hel_session_manager::SessionManagerChannels,
3397        ManagedSessionHandle,
3398        ControllerRelayLease,
3399        String,
3400        RelayCursor,
3401    ) {
3402        // The projection refuses events for sessions the controller does not
3403        // know, so register the one the relay journals for.
3404        hel::hel_database::save_session(&checkpoint_test_session(LATCH_RELAY_SESSION)).unwrap();
3405        let channels = crate::hel_session_manager::spawn_session_manager().unwrap();
3406        channels
3407            .targets
3408            .send(vec![latch_relay_target(
3409                relay_root, starts, release, running,
3410            )])
3411            .unwrap();
3412        let handle = channels
3413            .control
3414            .wait_for_session(LATCH_RELAY_SESSION, Duration::from_secs(10))
3415            .await
3416            .unwrap();
3417
3418        let lease = handle.lease_connection().await.unwrap();
3419        let mut relay = ControllerRelayLease::Managed {
3420            handle: handle.clone(),
3421            lease: Some(lease),
3422        };
3423        let barrier_command_id = new_command_id("checkpoint").unwrap();
3424        let connection = relay.connection_mut();
3425        connection
3426            .submit(
3427                barrier_command_id.clone(),
3428                RelayCommand::BeginCheckpoint { reason: None },
3429            )
3430            .await
3431            .unwrap();
3432        let barrier = wait_for_checkpoint_barrier(
3433            connection,
3434            LATCH_RELAY_SESSION,
3435            &barrier_command_id,
3436            CHECKPOINT_BARRIER_TIMEOUT,
3437            BarrierBusyPolicy::InterruptWhileRunning,
3438            HarnessKind::Codex,
3439        )
3440        .await
3441        .unwrap();
3442        assert_eq!(
3443            barrier.materialized.applied_event_ordinal,
3444            barrier.operational.latest_ordinal
3445        );
3446        let cursor = barrier.operational.checkpoint_ready.clone().unwrap();
3447        (channels, handle, relay, barrier_command_id, cursor)
3448    }
3449
3450    /// A close checkpoint cancels an active prompt and waits for the prompt's
3451    /// terminal event before admitting its barrier. The scripted worker clears
3452    /// the prompt only when it receives `CancelTurn`, so a single-start log
3453    /// proves that a responsive cancellation did not take restart recovery.
3454    #[cfg(unix)]
3455    #[tokio::test]
3456    async fn a_close_checkpoint_cancels_a_running_turn_without_restarting_the_worker() {
3457        // MJ_DATA_DIR is process-global, so keep the database-backed relay
3458        // actor isolated from unrelated tests.
3459        if std::env::var_os(LATCH_TEST_CHILD).is_none() {
3460            let directory = tempfile::tempdir().unwrap();
3461            let test_name = format!(
3462                "{}::a_close_checkpoint_cancels_a_running_turn_without_restarting_the_worker",
3463                module_path!()
3464                    .strip_prefix("mj_controller::")
3465                    .unwrap_or(module_path!())
3466            );
3467            let output = Command::new(std::env::current_exe().unwrap())
3468                .args(["--exact", &test_name, "--nocapture"])
3469                .env(LATCH_TEST_CHILD, "1")
3470                .env("MJ_DATA_DIR", directory.path())
3471                .output()
3472                .unwrap();
3473            assert!(
3474                output.status.success(),
3475                "isolated cancellation checkpoint test failed\nstdout:\n{}\nstderr:\n{}",
3476                String::from_utf8_lossy(&output.stdout),
3477                String::from_utf8_lossy(&output.stderr)
3478            );
3479            return;
3480        }
3481        let _writer = hel::hel_database::install_isolated_test_writer();
3482        let relay_root = tempfile::tempdir().unwrap();
3483        let start_log_directory = tempfile::tempdir().unwrap();
3484        let start_log = start_log_directory.path().join("relay-starts");
3485        let (_channels, _handle, mut relay, _barrier_command_id, _cursor) =
3486            latch_a_live_checkpoint(
3487                relay_root.path(),
3488                Some(&start_log),
3489                ReleaseSupport::Supported,
3490                true,
3491            )
3492            .await;
3493        let snapshot = relay.sync_snapshot().await.unwrap();
3494        assert_eq!(
3495            snapshot.operational.execution,
3496            RelayExecutionState::Idle,
3497            "the close wait returned before the cancelled turn became idle"
3498        );
3499        assert!(
3500            snapshot.operational.active_prompt.is_none(),
3501            "the close wait returned before the cancelled prompt settled"
3502        );
3503        assert_eq!(
3504            relay_starts(&start_log),
3505            1,
3506            "responsive cancellation restarted worker"
3507        );
3508    }
3509    /// The session actor absorbs a returned connection on its own task, so the
3510    /// first command after a latch ends may still be refused.
3511    #[cfg(unix)]
3512    async fn wait_until_the_actor_serves_again(handle: &ManagedSessionHandle) {
3513        for attempt in 0.. {
3514            if handle.sync_now().await.is_ok() {
3515                return;
3516            }
3517            assert!(attempt < 200, "the actor never took its connection back");
3518            tokio::time::sleep(std::time::Duration::from_millis(10)).await;
3519        }
3520    }
3521    /// Ending the latch is the whole point of the split checkpoint: the actor
3522    /// serves the dashboard again while the archive is still being exported,
3523    /// and the events it accepts do not invalidate the latched archive.
3524    #[cfg(unix)]
3525    #[tokio::test]
3526    async fn ending_the_checkpoint_latch_returns_the_connection_to_its_actor() {
3527        // MJ_DATA_DIR is process-global, so run the database-backed half in an
3528        // exact child test instead of racing unrelated tests in this process.
3529        if std::env::var_os(LATCH_TEST_CHILD).is_none() {
3530            let directory = tempfile::tempdir().unwrap();
3531            let test_name = format!(
3532                "{}::ending_the_checkpoint_latch_returns_the_connection_to_its_actor",
3533                module_path!()
3534                    .strip_prefix("mj_controller::")
3535                    .unwrap_or(module_path!())
3536            );
3537            let output = Command::new(std::env::current_exe().unwrap())
3538                .args(["--exact", &test_name, "--nocapture"])
3539                .env(LATCH_TEST_CHILD, "1")
3540                .env("MJ_DATA_DIR", directory.path())
3541                .output()
3542                .unwrap();
3543            assert!(
3544                output.status.success(),
3545                "isolated checkpoint latch test failed\nstdout:\n{}\nstderr:\n{}",
3546                String::from_utf8_lossy(&output.stdout),
3547                String::from_utf8_lossy(&output.stderr)
3548            );
3549            return;
3550        }
3551        // Alone in this child process, so it installs the one writer.
3552        let _writer = hel::hel_database::install_isolated_test_writer();
3553
3554        // A connection that never comes back would hang the suite instead of
3555        // failing it, so turn a stall into a hard error.
3556        std::thread::spawn(|| {
3557            std::thread::sleep(std::time::Duration::from_secs(120));
3558            eprintln!("the checkpoint latch never returned its connection");
3559            std::process::exit(101);
3560        });
3561
3562        let relay_root = tempfile::tempdir().unwrap();
3563        let (_channels, handle, mut relay, barrier_command_id, cursor) =
3564            latch_a_live_checkpoint(relay_root.path(), None, ReleaseSupport::Supported, false)
3565                .await;
3566
3567        // Latch phase: the projection must be read at the exact ready cursor,
3568        // so the actor cannot reach the relay at all.
3569        assert!(
3570            handle.sync_now().await.is_err(),
3571            "a latched projection must not be advanced by its own actor"
3572        );
3573
3574        relay.end_latch();
3575        wait_until_the_actor_serves_again(&handle).await;
3576
3577        // Slow phase, before anything else reaches the relay: the controller
3578        // reads its barrier back through the actor, which must report what the
3579        // latch already applied.
3580        let latched = relay.sync_snapshot().await.unwrap();
3581        validate_checkpoint_barrier_snapshot(&latched, &barrier_command_id, &cursor).unwrap();
3582
3583        // A prompt accepted while the archive transfers moves the frontier past
3584        // the ready cursor. The barrier still seals the same workspace.
3585        let prompt_ordinal = relay
3586            .submit(
3587                new_command_id("prompt").unwrap(),
3588                RelayCommand::Prompt {
3589                    prompt: vec![ContentBlock::Text(TextContent::new("hello"))],
3590                },
3591            )
3592            .await
3593            .unwrap();
3594        assert!(prompt_ordinal > cursor.ordinal);
3595        let snapshot = relay.sync_snapshot().await.unwrap();
3596        assert!(snapshot.operational.latest_ordinal > cursor.ordinal);
3597        validate_checkpoint_barrier_snapshot(&snapshot, &barrier_command_id, &cursor).unwrap();
3598
3599        latched_checkpoint(
3600            relay,
3601            barrier_command_id,
3602            cursor,
3603            CheckpointCompletion::HeldBarrier,
3604        )
3605        .complete()
3606        .await
3607        .unwrap();
3608        handle.sync_now().await.unwrap();
3609        assert_eq!(
3610            handle
3611                .view()
3612                .snapshot
3613                .expect("the actor published the completed barrier")
3614                .operational
3615                .checkpoint_barrier,
3616            None
3617        );
3618    }
3619    /// The archive is complete once the export returns, so the harness stops
3620    /// waiting there: the barrier ends, ACP dispatch resumes, and only the
3621    /// recovery floor waits for the installed archive.
3622    #[cfg(unix)]
3623    #[tokio::test]
3624    async fn releasing_a_checkpoint_after_capture_defers_only_the_recovery_floor() {
3625        // MJ_DATA_DIR is process-global, so run the database-backed half in an
3626        // exact child test instead of racing unrelated tests in this process.
3627        if std::env::var_os(RELEASE_TEST_CHILD).is_none() {
3628            let directory = tempfile::tempdir().unwrap();
3629            let test_name = format!(
3630                "{}::releasing_a_checkpoint_after_capture_defers_only_the_recovery_floor",
3631                module_path!()
3632                    .strip_prefix("mj_controller::")
3633                    .unwrap_or(module_path!())
3634            );
3635            let output = Command::new(std::env::current_exe().unwrap())
3636                .args(["--exact", &test_name, "--nocapture"])
3637                .env(RELEASE_TEST_CHILD, "1")
3638                .env("MJ_DATA_DIR", directory.path())
3639                .output()
3640                .unwrap();
3641            assert!(
3642                output.status.success(),
3643                "isolated checkpoint release test failed\nstdout:\n{}\nstderr:\n{}",
3644                String::from_utf8_lossy(&output.stdout),
3645                String::from_utf8_lossy(&output.stderr)
3646            );
3647            return;
3648        }
3649        // Alone in this child process, so it installs the one writer.
3650        let _writer = hel::hel_database::install_isolated_test_writer();
3651
3652        // A barrier that never releases would hang the suite instead of failing
3653        // it, so turn a stall into a hard error.
3654        std::thread::spawn(|| {
3655            std::thread::sleep(std::time::Duration::from_secs(120));
3656            eprintln!("the captured checkpoint never released its barrier");
3657            std::process::exit(101);
3658        });
3659
3660        let relay_root = tempfile::tempdir().unwrap();
3661        let (_channels, handle, mut relay, barrier_command_id, cursor) =
3662            latch_a_live_checkpoint(relay_root.path(), None, ReleaseSupport::Supported, false)
3663                .await;
3664        relay.end_latch();
3665        wait_until_the_actor_serves_again(&handle).await;
3666
3667        // Target state capture has just finished. Releasing proves the barrier first and
3668        // then hands ACP dispatch back.
3669        let completion = release_checkpoint_after_capture(
3670            &mut relay,
3671            LATCH_RELAY_SESSION,
3672            &barrier_command_id,
3673            &cursor,
3674            HarnessKind::Codex,
3675        )
3676        .await
3677        .unwrap();
3678        assert_eq!(completion, CheckpointCompletion::ReleasedAfterCapture);
3679        let released = relay.sync_snapshot().await.unwrap();
3680        assert_eq!(released.operational.checkpoint_barrier, None);
3681        assert_eq!(released.operational.checkpoint_ready, None);
3682        assert_eq!(
3683            released.operational.recovery_floor_ordinal, 0,
3684            "an exported archive that is not installed may not release journal history"
3685        );
3686
3687        // The transfer is still running, and the harness is already working
3688        // again: a prompt submitted now reaches ACP dispatch.
3689        relay
3690            .submit(
3691                new_command_id("prompt").unwrap(),
3692                RelayCommand::Prompt {
3693                    prompt: vec![ContentBlock::Text(TextContent::new("during transfer"))],
3694                },
3695            )
3696            .await
3697            .unwrap();
3698        let mut dispatched = None;
3699        for attempt in 0.. {
3700            let snapshot = relay.sync_snapshot().await.unwrap();
3701            if let Some(active) = snapshot.operational.active_prompt {
3702                dispatched = Some(active);
3703                break;
3704            }
3705            assert!(attempt < 200, "a released barrier still froze ACP dispatch");
3706            tokio::time::sleep(std::time::Duration::from_millis(10)).await;
3707        }
3708        assert!(dispatched.is_some());
3709
3710        // The archive is installed, so the relay may finally forget the history
3711        // it covers.
3712        latched_checkpoint(
3713            relay,
3714            barrier_command_id,
3715            cursor.clone(),
3716            CheckpointCompletion::ReleasedAfterCapture,
3717        )
3718        .complete()
3719        .await
3720        .unwrap();
3721        handle.sync_now().await.unwrap();
3722        let installed = handle
3723            .view()
3724            .snapshot
3725            .expect("the actor published the advanced recovery floor");
3726        assert_eq!(installed.operational.recovery_floor_ordinal, cursor.ordinal);
3727        assert_eq!(installed.operational.recovery_floor_digest, cursor.digest);
3728    }
3729    /// A target still running a worker that predates the early release keeps
3730    /// its barrier through the transfer and ends it the way it always did.
3731    #[cfg(unix)]
3732    #[tokio::test]
3733    async fn a_worker_that_rejects_the_release_keeps_its_barrier_through_the_transfer() {
3734        // MJ_DATA_DIR is process-global, so run the database-backed half in an
3735        // exact child test instead of racing unrelated tests in this process.
3736        if std::env::var_os(LEGACY_RELEASE_TEST_CHILD).is_none() {
3737            let directory = tempfile::tempdir().unwrap();
3738            let test_name = format!(
3739                "{}::a_worker_that_rejects_the_release_keeps_its_barrier_through_the_transfer",
3740                module_path!()
3741                    .strip_prefix("mj_controller::")
3742                    .unwrap_or(module_path!())
3743            );
3744            let output = Command::new(std::env::current_exe().unwrap())
3745                .args(["--exact", &test_name, "--nocapture"])
3746                .env(LEGACY_RELEASE_TEST_CHILD, "1")
3747                .env("MJ_DATA_DIR", directory.path())
3748                .output()
3749                .unwrap();
3750            assert!(
3751                output.status.success(),
3752                "isolated legacy checkpoint release test failed\nstdout:\n{}\nstderr:\n{}",
3753                String::from_utf8_lossy(&output.stdout),
3754                String::from_utf8_lossy(&output.stderr)
3755            );
3756            return;
3757        }
3758        // Alone in this child process, so it installs the one writer.
3759        let _writer = hel::hel_database::install_isolated_test_writer();
3760
3761        // A rejected release that lost its barrier would hang the suite instead
3762        // of failing it, so turn a stall into a hard error.
3763        std::thread::spawn(|| {
3764            std::thread::sleep(std::time::Duration::from_secs(120));
3765            eprintln!("the rejected release never finished its checkpoint");
3766            std::process::exit(101);
3767        });
3768
3769        let relay_root = tempfile::tempdir().unwrap();
3770        let start_log = tempfile::tempdir().unwrap();
3771        let start_log = start_log.path().join("relay-starts");
3772        let (_channels, handle, mut relay, barrier_command_id, cursor) = latch_a_live_checkpoint(
3773            relay_root.path(),
3774            Some(&start_log),
3775            ReleaseSupport::Rejected,
3776            false,
3777        )
3778        .await;
3779        relay.end_latch();
3780        wait_until_the_actor_serves_again(&handle).await;
3781
3782        let completion = release_checkpoint_after_capture(
3783            &mut relay,
3784            LATCH_RELAY_SESSION,
3785            &barrier_command_id,
3786            &cursor,
3787            HarnessKind::Codex,
3788        )
3789        .await
3790        .unwrap();
3791        assert_eq!(completion, CheckpointCompletion::HeldBarrier);
3792        // A refused command is a completed round trip, so the connection that
3793        // owns the barrier must survive it.
3794        assert_eq!(relay_starts(&start_log), 1);
3795
3796        // Today's ordering carries on: the barrier holds through the transfer,
3797        // the post-transfer revalidation still has something to prove, and the
3798        // completion both resumes dispatch and advances the recovery floor.
3799        let transferring = relay.sync_snapshot().await.unwrap();
3800        validate_checkpoint_barrier_snapshot(&transferring, &barrier_command_id, &cursor).unwrap();
3801        latched_checkpoint(relay, barrier_command_id, cursor.clone(), completion)
3802            .complete()
3803            .await
3804            .unwrap();
3805        handle.sync_now().await.unwrap();
3806        let completed = handle
3807            .view()
3808            .snapshot
3809            .expect("the actor published the completed barrier");
3810        assert_eq!(completed.operational.checkpoint_barrier, None);
3811        assert_eq!(completed.operational.recovery_floor_ordinal, cursor.ordinal);
3812    }
3813    /// A caller that cannot install a latched archive has to cancel its
3814    /// barrier. The latch is already back with the session actor, so the only
3815    /// thing that ends the barrier is dropping the connection that opened it:
3816    /// the worker cancels barriers whose connection disappears.
3817    #[cfg(unix)]
3818    #[tokio::test]
3819    async fn abandoning_a_latched_checkpoint_drops_the_connection_that_opened_its_barrier() {
3820        // MJ_DATA_DIR is process-global, so run the database-backed half in an
3821        // exact child test instead of racing unrelated tests in this process.
3822        if std::env::var_os(ABANDON_TEST_CHILD).is_none() {
3823            let directory = tempfile::tempdir().unwrap();
3824            let test_name = format!(
3825                "{}::abandoning_a_latched_checkpoint_drops_the_connection_that_opened_its_barrier",
3826                module_path!()
3827                    .strip_prefix("mj_controller::")
3828                    .unwrap_or(module_path!())
3829            );
3830            let output = Command::new(std::env::current_exe().unwrap())
3831                .args(["--exact", &test_name, "--nocapture"])
3832                .env(ABANDON_TEST_CHILD, "1")
3833                .env("MJ_DATA_DIR", directory.path())
3834                .output()
3835                .unwrap();
3836            assert!(
3837                output.status.success(),
3838                "isolated abandoned checkpoint test failed\nstdout:\n{}\nstderr:\n{}",
3839                String::from_utf8_lossy(&output.stdout),
3840                String::from_utf8_lossy(&output.stderr)
3841            );
3842            return;
3843        }
3844        // Alone in this child process, so it installs the one writer.
3845        let _writer = hel::hel_database::install_isolated_test_writer();
3846
3847        // An abandoned barrier that never releases its connection would hang
3848        // the suite instead of failing it, so turn a stall into a hard error.
3849        std::thread::spawn(|| {
3850            std::thread::sleep(std::time::Duration::from_secs(120));
3851            eprintln!("an abandoned checkpoint never released its relay connection");
3852            std::process::exit(101);
3853        });
3854
3855        let relay_root = tempfile::tempdir().unwrap();
3856        let start_log = tempfile::tempdir().unwrap();
3857        let start_log = start_log.path().join("relay-starts");
3858        let (_channels, handle, mut relay, barrier_command_id, cursor) = latch_a_live_checkpoint(
3859            relay_root.path(),
3860            Some(&start_log),
3861            ReleaseSupport::Supported,
3862            false,
3863        )
3864        .await;
3865        relay.end_latch();
3866        wait_until_the_actor_serves_again(&handle).await;
3867        assert_eq!(relay_starts(&start_log), 1);
3868
3869        latched_checkpoint(
3870            relay,
3871            barrier_command_id,
3872            cursor,
3873            CheckpointCompletion::HeldBarrier,
3874        )
3875        .abandon(LATCH_RELAY_SESSION)
3876        .await;
3877
3878        // The actor serves again, which proves the reclaimed lease was not
3879        // leaked, and it is talking to a new relay process, which proves the
3880        // connection that opened the barrier was dropped rather than handed
3881        // back alive.
3882        wait_until_the_actor_serves_again(&handle).await;
3883        assert_eq!(relay_starts(&start_log), 2);
3884    }
3885    /// The close policy, end to end against a live relay: a latch that finds
3886    /// its own content already archived issues no export or transfer command
3887    /// and keeps the installed archive, while the next latch after real
3888    /// session content goes back through the full export.
3889    #[cfg(unix)]
3890    #[tokio::test]
3891    async fn a_close_latch_reuses_an_unchanged_archive_and_exports_after_new_content() {
3892        // MJ_DATA_DIR is process-global, so run the database-backed half in an
3893        // exact child test instead of racing unrelated tests in this process.
3894        if std::env::var_os(REUSE_TEST_CHILD).is_none() {
3895            let directory = tempfile::tempdir().unwrap();
3896            let test_name = format!(
3897                "{}::a_close_latch_reuses_an_unchanged_archive_and_exports_after_new_content",
3898                module_path!()
3899                    .strip_prefix("mj_controller::")
3900                    .unwrap_or(module_path!())
3901            );
3902            let output = Command::new(std::env::current_exe().unwrap())
3903                .args(["--exact", &test_name, "--nocapture"])
3904                .env(REUSE_TEST_CHILD, "1")
3905                // Longer than the normal checkpoint barrier deadline. The
3906                // controller must wait for startup rather than restart it.
3907                .env(LATCH_RELAY_STARTUP_DELAY_MS, "31000")
3908                .env("MJ_DATA_DIR", directory.path())
3909                .output()
3910                .unwrap();
3911            assert!(
3912                output.status.success(),
3913                "isolated checkpoint reuse test failed\nstdout:\n{}\nstderr:\n{}",
3914                String::from_utf8_lossy(&output.stdout),
3915                String::from_utf8_lossy(&output.stderr)
3916            );
3917            return;
3918        }
3919        // Alone in this child process, so it installs the one writer.
3920        let _writer = hel::hel_database::install_isolated_test_writer();
3921
3922        // A latch that never returns would hang the suite instead of failing
3923        // it, so turn a stall into a hard error.
3924        std::thread::spawn(|| {
3925            std::thread::sleep(std::time::Duration::from_secs(120));
3926            eprintln!("the reuse checkpoint never finished its latch");
3927            std::process::exit(101);
3928        });
3929
3930        #[derive(Default)]
3931        struct RecordingExecutor {
3932            purposes: std::sync::Mutex<Vec<String>>,
3933            active_stages: std::sync::Mutex<Vec<ProvisionStage>>,
3934            stage_events: std::sync::Mutex<Vec<(ProvisionStage, bool)>>,
3935            observed_stages: std::sync::Mutex<Vec<(String, Vec<ProvisionStage>)>>,
3936        }
3937
3938        impl RecordingExecutor {
3939            fn refused(&self, command: &CommandSpec) -> Result<CommandOutput> {
3940                self.purposes.lock().unwrap().push(command.purpose.clone());
3941                self.observed_stages.lock().unwrap().push((
3942                    command.purpose.clone(),
3943                    self.active_stages.lock().unwrap().clone(),
3944                ));
3945                Ok(CommandOutput {
3946                    status: 1,
3947                    stdout: Vec::new(),
3948                    stderr: b"no target is provisioned for this test".to_vec(),
3949                })
3950            }
3951
3952            fn purposes(&self) -> Vec<String> {
3953                self.purposes.lock().unwrap().clone()
3954            }
3955
3956            fn observed_stages(&self) -> Vec<(String, Vec<ProvisionStage>)> {
3957                self.observed_stages.lock().unwrap().clone()
3958            }
3959
3960            fn stage_events(&self) -> Vec<(ProvisionStage, bool)> {
3961                self.stage_events.lock().unwrap().clone()
3962            }
3963        }
3964
3965        impl CommandExecutor for RecordingExecutor {
3966            fn execute(&self, command: &CommandSpec) -> Result<CommandOutput> {
3967                self.refused(command)
3968            }
3969
3970            fn execute_with_stdin(
3971                &self,
3972                command: &CommandSpec,
3973                _input: &mut (dyn std::io::Read + Send),
3974            ) -> Result<CommandOutput> {
3975                self.refused(command)
3976            }
3977
3978            fn stage_started(&self, stage: ProvisionStage) {
3979                self.active_stages.lock().unwrap().push(stage);
3980                self.stage_events.lock().unwrap().push((stage, true));
3981            }
3982
3983            fn stage_finished(&self, stage: ProvisionStage) {
3984                let mut active = self.active_stages.lock().unwrap();
3985                let position = active
3986                    .iter()
3987                    .position(|active_stage| *active_stage == stage)
3988                    .expect("stage finished without a matching start");
3989                active.remove(position);
3990                self.stage_events.lock().unwrap().push((stage, false));
3991            }
3992        }
3993
3994        let data_directory = PathBuf::from(std::env::var_os("MJ_DATA_DIR").unwrap());
3995        let relay_root = data_directory.join("relay");
3996        let profile_home = data_directory.join("profile");
3997        let archive_directory = data_directory.join("archives");
3998        for directory in [&relay_root, &profile_home, &archive_directory] {
3999            std::fs::create_dir_all(directory).unwrap();
4000        }
4001        // The archive covers the fake runtime's SessionOpened and
4002        // SessionConfigured events, before any checkpoint bookkeeping.
4003        let checkpoint = write_checkpoint_gate_archive(&archive_directory, LATCH_RELAY_SESSION, 2);
4004
4005        let mut session = checkpoint_test_session(LATCH_RELAY_SESSION);
4006        session.target_template_id = "local".into();
4007        session.target = Some(TargetLocator::LocalBare {
4008            worker_root: data_directory.join("workers").join(LATCH_RELAY_SESSION),
4009        });
4010        session.checkpoint = Some(checkpoint.clone());
4011        hel::hel_database::save_session(&session).unwrap();
4012
4013        let mut config = HelConfig::default();
4014        config.profiles.insert(
4015            "codex".into(),
4016            HarnessProfile {
4017                enabled: true,
4018                kind: hel::hel_config::HarnessKind::Codex,
4019                home: profile_home,
4020                environment: BTreeMap::new(),
4021                context_window_bytes: None,
4022            },
4023        );
4024        config
4025            .targets
4026            .insert("local".into(), TargetTemplate::LocalBare);
4027        config.bundles.insert(
4028            "project".into(),
4029            ProjectBundle {
4030                primary_repo: "project".into(),
4031                repositories: vec![ProjectRepository {
4032                    id: "project".into(),
4033                    github: Some("example/project".into()),
4034                    local: None,
4035                    destination: "project".into(),
4036                    git_ref: None,
4037                }],
4038            },
4039        );
4040        let controller = Controller {
4041            config,
4042            state: HelState {
4043                sessions: BTreeMap::from([(LATCH_RELAY_SESSION.into(), session)]),
4044                ..HelState::default()
4045            },
4046        };
4047
4048        let channels = crate::hel_session_manager::spawn_session_manager().unwrap();
4049        channels
4050            .targets
4051            .send(vec![latch_relay_target(
4052                &relay_root,
4053                None,
4054                ReleaseSupport::Supported,
4055                false,
4056            )])
4057            .unwrap();
4058        let handle = channels
4059            .control
4060            .wait_for_session(LATCH_RELAY_SESSION, Duration::from_secs(10))
4061            .await
4062            .unwrap();
4063
4064        let executor = RecordingExecutor::default();
4065        let latched = controller
4066            .checkpoint_session_latched(
4067                LATCH_RELAY_SESSION,
4068                &executor,
4069                Some(&channels.control),
4070                LatchExclusivity::HoldThroughClose,
4071                CheckpointExportPolicy::ReuseUnchangedArchive,
4072            )
4073            .await
4074            .unwrap();
4075
4076        assert!(
4077            executor.purposes().is_empty(),
4078            "an unchanged session exported an archive anyway: {:?}",
4079            executor.purposes()
4080        );
4081        assert_eq!(latched.artifact.metadata, checkpoint);
4082        assert!(checkpoint.archive_path.exists());
4083
4084        // The cursor close seals is ahead of the reused archive by this
4085        // checkpoint's own bookkeeping.
4086        assert!(latched.cursor.ordinal > checkpoint.event_frontier);
4087        let cursor = latched.cursor.clone();
4088        latched.complete().await.unwrap();
4089        wait_until_the_actor_serves_again(&handle).await;
4090
4091        // An ordinary recovery copy during a turn must defer before it
4092        // journals BeginCheckpoint, so no disconnect-cancellation message is
4093        // produced for a routine busy observation.
4094        handle
4095            .submit(
4096                new_command_id("busy-prompt").unwrap(),
4097                RelayCommand::Prompt {
4098                    prompt: vec![ContentBlock::Text(TextContent::new("keep working"))],
4099                },
4100            )
4101            .await
4102            .unwrap();
4103        let mut connection = handle.lease_connection().await.unwrap();
4104        let before = connection.connection_mut().sync().await.unwrap();
4105        assert_eq!(before.operational.execution, RelayExecutionState::Running);
4106        connection.release();
4107        let deferred = controller
4108            .checkpoint_session_latched(
4109                LATCH_RELAY_SESSION,
4110                &executor,
4111                Some(&channels.control),
4112                LatchExclusivity::ReleaseAfterLatch,
4113                CheckpointExportPolicy::ReuseUnchangedArchive,
4114            )
4115            .await;
4116        assert!(
4117            matches!(deferred, Err(ref error) if error.downcast_ref::<CheckpointDeferred>().is_some())
4118        );
4119        wait_until_the_actor_serves_again(&handle).await;
4120        let mut connection = handle.lease_connection().await.unwrap();
4121        let after = connection.connection_mut().sync().await.unwrap();
4122        assert_eq!(after.operational.execution, RelayExecutionState::Running);
4123        assert!(after.operational.checkpoint_barrier.is_none());
4124        let journal =
4125            std::fs::read_to_string(relay_root.join("relay-journal/active.jsonl")).unwrap();
4126        for line in journal.lines() {
4127            let event: hel::hel_worker::RelayEvent = serde_json::from_str(line).unwrap();
4128            if event.ordinal > before.operational.latest_ordinal {
4129                assert!(
4130                    !matches!(
4131                        event.observation,
4132                        hel::hel_worker::RelayObservation::CommandQueued {
4133                            command: RelayCommand::BeginCheckpoint { .. },
4134                            ..
4135                        } | hel::hel_worker::RelayObservation::CommandInterrupted {
4136                            command: hel::hel_worker::RelayCommandKind::BeginCheckpoint,
4137                            ..
4138                        }
4139                    ),
4140                    "busy deferral journaled checkpoint activity: {event:?}"
4141                );
4142            }
4143        }
4144        connection.release();
4145        handle
4146            .submit(
4147                new_command_id("finish-busy-prompt").unwrap(),
4148                RelayCommand::CancelTurn,
4149            )
4150            .await
4151            .unwrap();
4152        handle.sync_now().await.unwrap();
4153
4154        // Real session content, and the same policy has to export again.
4155        handle
4156            .submit(
4157                new_command_id("resume-notice").unwrap(),
4158                RelayCommand::RecordNotice {
4159                    text: "the session changed".into(),
4160                },
4161            )
4162            .await
4163            .unwrap();
4164        for attempt in 0.. {
4165            handle.sync_now().await.unwrap();
4166            let materialized = handle.view().snapshot.map(|snapshot| snapshot.materialized);
4167            if materialized.is_some_and(|materialized| {
4168                materialized.applied_event_ordinal > cursor.ordinal
4169                    && !materialized.transcript.is_empty()
4170            }) {
4171                break;
4172            }
4173            assert!(attempt < 200, "the notice never reached the projection");
4174            tokio::time::sleep(std::time::Duration::from_millis(10)).await;
4175        }
4176
4177        let changed = controller
4178            .checkpoint_session_latched(
4179                LATCH_RELAY_SESSION,
4180                &executor,
4181                Some(&channels.control),
4182                LatchExclusivity::HoldThroughClose,
4183                CheckpointExportPolicy::ReuseUnchangedArchive,
4184            )
4185            .await;
4186        let Err(error) = changed else {
4187            panic!("a changed session reused its installed archive");
4188        };
4189
4190        assert!(
4191            executor
4192                .purposes()
4193                .contains(&"export target checkpoint".to_owned()),
4194            "a changed session skipped its export: {:?}",
4195            executor.purposes()
4196        );
4197        assert!(
4198            format!("{error:#}").contains("no target is provisioned for this test"),
4199            "{error:#}"
4200        );
4201        assert!(
4202            executor.observed_stages().iter().any(|(purpose, stages)| {
4203                purpose == "export target checkpoint"
4204                    && stages.contains(&ProvisionStage::RecoveryCopy)
4205            }),
4206            "close checkpoint export did not run inside RecoveryCopy: {:?}",
4207            executor.observed_stages()
4208        );
4209        assert_eq!(
4210            executor
4211                .stage_events()
4212                .into_iter()
4213                .filter(|(stage, _)| *stage == ProvisionStage::RecoveryCopy)
4214                .collect::<Vec<_>>(),
4215            vec![
4216                (ProvisionStage::RecoveryCopy, true),
4217                (ProvisionStage::RecoveryCopy, false)
4218            ]
4219        );
4220        assert!(executor.active_stages.lock().unwrap().is_empty());
4221        assert!(checkpoint.archive_path.exists());
4222    }
4223    #[cfg(unix)]
4224    fn relay_starts(path: &Path) -> usize {
4225        std::fs::read_to_string(path)
4226            .unwrap_or_default()
4227            .lines()
4228            .count()
4229    }
4230    /// A latched checkpoint carrying a placeholder artifact. These tests
4231    /// exercise its relay barrier, not the archive it names.
4232    #[cfg(unix)]
4233    fn latched_checkpoint(
4234        relay: ControllerRelayLease,
4235        barrier_command_id: String,
4236        cursor: RelayCursor,
4237        completion: CheckpointCompletion,
4238    ) -> LatchedCheckpoint {
4239        LatchedCheckpoint {
4240            artifact: CheckpointArtifact {
4241                metadata: CheckpointMetadata {
4242                    archive_path: PathBuf::from("checkpoint.hel.zip"),
4243                    sha256: "a".repeat(64),
4244                    created_at: now(),
4245                    event_frontier: cursor.ordinal,
4246                },
4247                native_session_id: "native-session".into(),
4248                event_frontier_digest: cursor.digest.clone(),
4249            },
4250            relay,
4251            barrier_command_id,
4252            cursor,
4253            completion,
4254        }
4255    }
4256    #[test]
4257    fn checkpoint_persistence_rollback_restores_memory_and_reports_both_failures() {
4258        let session_id = "0123456789abcdef0123456789abcdef";
4259        let previous = checkpoint_test_session(session_id);
4260        let mut changed = previous.clone();
4261        changed.state = SessionState::Closing;
4262        changed.last_checkpoint_error = Some("partially installed checkpoint".into());
4263        let mut state = HelState::default();
4264        state.sessions.insert(session_id.into(), changed);
4265
4266        let error = restore_session_after_persistence_failure(
4267            &mut state,
4268            session_id,
4269            &previous,
4270            anyhow::anyhow!("verified checkpoint persistence failed"),
4271            |record| {
4272                assert_eq!(record, &previous);
4273                Err(anyhow::anyhow!("rollback database write failed"))
4274            },
4275        );
4276
4277        assert_eq!(state.sessions.get(session_id), Some(&previous));
4278        let detail = format!("{error:#}");
4279        assert!(detail.contains("verified checkpoint persistence failed"));
4280        assert!(detail.contains("rollback database write failed"));
4281    }
4282    #[test]
4283    fn installed_checkpoint_gate_reopens_and_checks_sha() {
4284        let directory = tempfile::tempdir().unwrap();
4285        let session_id = "0123456789abcdef0123456789abcdef";
4286        let checkpoint = write_checkpoint_gate_archive(directory.path(), session_id, 7);
4287        verify_installed_checkpoint_gate(session_id, &checkpoint).unwrap();
4288
4289        let mut wrong_sha = checkpoint.clone();
4290        wrong_sha.sha256 = "b".repeat(64);
4291        assert!(
4292            verify_installed_checkpoint_gate(session_id, &wrong_sha)
4293                .unwrap_err()
4294                .to_string()
4295                .contains("SHA changed")
4296        );
4297        std::fs::write(
4298            &checkpoint.archive_path,
4299            b"changed after first verification",
4300        )
4301        .unwrap();
4302        assert!(
4303            format!(
4304                "{:#}",
4305                verify_installed_checkpoint_gate(session_id, &checkpoint).unwrap_err()
4306            )
4307            .contains("installed checkpoint SHA changed")
4308        );
4309    }
4310    #[test]
4311    fn an_installed_archive_is_reused_when_only_relay_bookkeeping_moved() {
4312        let directory = tempfile::tempdir().unwrap();
4313        let session_id = "0123456789abcdef0123456789abcdef";
4314        let checkpoint = write_checkpoint_gate_archive(directory.path(), session_id, 7);
4315        let archived = verify_archive_streaming(&checkpoint.archive_path)
4316            .unwrap()
4317            .canonical_session;
4318
4319        // What a checkpoint taken seconds later latches on an idle session:
4320        // the frontier and the activity watermark moved, the content did not.
4321        let mut latched = archived.clone();
4322        latched.event_frontier += 6;
4323        latched.event_frontier_digest = "b".repeat(64);
4324        latched.session.last_activity_at_ms = Some(9_999);
4325
4326        let artifact = reusable_installed_checkpoint(
4327            session_id,
4328            Some(&checkpoint),
4329            "native-session",
4330            latched.event_frontier,
4331            &latched,
4332        )
4333        .expect("an unchanged session reuses its installed archive");
4334
4335        assert_eq!(artifact.metadata, checkpoint);
4336        assert_eq!(artifact.native_session_id, "native-session");
4337        assert_eq!(
4338            artifact.event_frontier_digest,
4339            archived.event_frontier_digest
4340        );
4341        // The reused archive is still the gate close destroys through.
4342        verify_checkpoint_artifact(session_id, &artifact).unwrap();
4343        verify_installed_checkpoint_gate(session_id, &artifact.metadata).unwrap();
4344    }
4345    #[test]
4346    fn archive_reuse_falls_back_to_a_full_export_for_anything_but_bookkeeping() {
4347        let directory = tempfile::tempdir().unwrap();
4348        let session_id = "0123456789abcdef0123456789abcdef";
4349        let checkpoint = write_checkpoint_gate_archive(directory.path(), session_id, 7);
4350        let archived = verify_archive_streaming(&checkpoint.archive_path)
4351            .unwrap()
4352            .canonical_session;
4353        let mut latched = archived.clone();
4354        latched.event_frontier += 6;
4355        let reuse = |installed: Option<&CheckpointMetadata>,
4356                     ordinal: u64,
4357                     session: &CanonicalSessionSnapshot| {
4358            reusable_installed_checkpoint(session_id, installed, "native-session", ordinal, session)
4359        };
4360
4361        assert!(reuse(None, latched.event_frontier, &latched).is_none());
4362
4363        let mut with_new_content = latched.clone();
4364        with_new_content.transcript.push(CanonicalTranscriptItem {
4365            stable_id: "system:notice:notice-1".into(),
4366            position: latched.event_frontier,
4367            latest_content_event_ordinal: None,
4368            created_at_ms: 2_000,
4369            last_changed_at_ms: 2_000,
4370            body: CanonicalTranscriptBody::System {
4371                text: "resumed".into(),
4372            },
4373        });
4374        assert!(reuse(Some(&checkpoint), latched.event_frontier, &with_new_content).is_none());
4375
4376        // An archive the latch has not reached yet cannot describe the session.
4377        assert!(reuse(Some(&checkpoint), checkpoint.event_frontier - 1, &latched).is_none());
4378
4379        let mut wrong_sha = checkpoint.clone();
4380        wrong_sha.sha256 = "b".repeat(64);
4381        assert!(reuse(Some(&wrong_sha), latched.event_frontier, &latched).is_none());
4382
4383        let another_session =
4384            write_checkpoint_gate_archive(directory.path(), "1123456789abcdef0123456789abcdef", 7);
4385        assert!(reuse(Some(&another_session), latched.event_frontier, &latched).is_none());
4386
4387        std::fs::write(&checkpoint.archive_path, b"not an archive any more").unwrap();
4388        assert!(reuse(Some(&checkpoint), latched.event_frontier, &latched).is_none());
4389    }
4390}