Documentation / guides
Restore and cleanup
Restore snapshots and safely clean Session-owned workspace state.
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:
- Reject a canceled, closing, or faulted session.
- Suspend new process admission.
- Acquire the exclusive
WorkspaceOperationCheckpointpermit. Existing read-only lifetimes may remain; writable lifetimes and mutations drain. - Check lease health, materialize and verify the target ref, and commit.
- Append
event.WorkspaceRestoredafter the filesystem commit. - 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.