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