Skip to documentation
Documentation navigation

Documentation navigation

Documentation / guides

Restore and cleanup

Restore snapshots and safely clean Session-owned workspace state.

developer

SessionController.RestoreWorkspace is an idle-time control operation. It does not overwrite a live tree in place without coordination: it suspends new process admission, drains writable permits, materializes into a protected staging location, commits a safe swap or deterministic reconcile, and only then records the new durable pointer.

Restore contract

import (
	"context"
	"errors"

	"github.com/looprig/harness/pkg/session"
	"github.com/looprig/harness/pkg/workspacestore"
)

type SessionController interface {
	session.Session
	CheckpointWorkspace(context.Context) (workspacestore.Ref, error)
	RestoreWorkspace(context.Context, workspacestore.Ref) error
	Shutdown(context.Context) error
}

if err := controller.RestoreWorkspace(ctx, ref); err != nil {
	var materializeErr *workspacestore.MaterializeError
	if errors.As(err, &materializeErr) {
		// The public store error identifies the ref and destination cause.
	}
	return err
}

Consumers should match the public errors exposed by pkg/session and inspect wrapped causes with errors.As. An unconfigured session returns *session.WorkspaceNotConfiguredError. A canceled context, closing session, faulted session, failed permit, unhealthy lease, failed swap, or failed durable append is fail-closed.

Admission and commit

The sequence is intentionally ordered:

  1. Reject a canceled, closing, or faulted session.
  2. Suspend new process admission.
  3. Acquire the exclusive WorkspaceOperationCheckpoint permit. Existing read-only lifetimes may remain; writable lifetimes and mutations drain.
  4. Check lease health, materialize and verify the target ref, and commit.
  5. Append event.WorkspaceRestored after the filesystem commit.
  6. Resume process admission on every return path.

If the event append fails after the tree changed, the session faults because the live tree and durable pointer would otherwise disagree. If a fixed-root rollback itself fails, WorkspaceRestoreRollbackFailed is escalated to a session fault.

Placement-specific restore

Per-session roots use a verified whole-root swap:

materialize ref -> sibling .<id>.staging
rename live root -> sibling .<id>.backup
rename staging -> live root
remove backup (or restore it if the second rename fails)

Exclusive and shared fixed roots are never renamed or recursively wiped. They materialize into session-unique sibling scratch directories, build a regular-file manifest, replace changed files, and delete files absent from the ref in sorted order. Every touched file has a rollback copy. Symlinked components are refused so a restore cannot write outside the root. Empty directories and non-regular entries not represented by the archive are not pruned in the fixed-root path.

Cleanup and events

%%{init: {"theme":"dark"}}%%
stateDiagram-v2
    [*] --> Open
    Open --> Restoring: RestoreWorkspace(ref)
    Restoring --> Open: commit + WorkspaceRestored
    Restoring --> Faulted: rollback failure or append failure
    Open --> ShuttingDown: Shutdown
    ShuttingDown --> Closed: stop writers, resources, hub
    Closed --> [*]: release root lease then session lease

WorkspaceRestored carries only the Ref; the prepared private payload is not part of the public event stream. On a fresh session, WithSeedSnapshot(ref) materializes before loop construction and journals a first WorkspaceCheckpointed{Trigger: SnapshotTriggerSeed}. A seed is rejected for shared placement, a non-empty root, or a ref that cannot materialize.

The implementation is internal/sessionruntime/workspace_restore.go, internal/sessionruntime/checkpoint.go, and pkg/rig/session_options.go. End-to-end seed and rewind behavior is proved by pkg/rig/workspace_integration_test.go and internal/sessionruntime/restore_workspace_test.go.

Source and proof

← back to documentation