session

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 14, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

File batch.go adds the INTEGRATION NODE of the op spine (ADR-0004 §10.1/§10.3, SPEC §10): Session.ApplyBatch applies a HETEROGENEOUS []Op (Edit + Create + Delete + Move) as ONE all-or-nothing logical change a host (Hylla) maps to ONE graph mutation. This is the whole reason the tagged Op shape exists.

ALL-OR-NOTHING is the hard guarantee: if ANY op fails its anchor (region_hash/raw_hash drift, clobber, parse/lint floor) OR fails to apply, the ENTIRE batch is rejected and the filesystem is left EXACTLY as before — no op half-applied. "Atomic" here is WAL-backed all-or-nothing ON RECOVERY (there is no multi-file syscall): a crash mid-commit converges via Recover to fully-before or fully-after, never half.

It REUSES the existing per-op machinery rather than reimplementing anchors:

  • edits go through resolveEdits (region_hash gate) + the format/lint/parse floor (formatLintParse) + edit.SpliceEdits, exactly like Commit;
  • creates use the non-existence anchor + the same floor over the staged bytes;
  • deletes/moves use the raw_hash drift gate and capture Originals;
  • the union of all op paths (+ move dests) is locked in the deadlock-free SORTED order via lockPaths, held across BOTH phases.

The flow is four phases under the union lock: (A) VALIDATE every op's anchor up front, reading nothing-destructive and writing nothing — any failure rejects the whole batch having written NOTHING; (B) build ONE unified wal.Intent capturing every op's undo (edit Originals + resolved Edits, Creates, Deletes + Originals, Moves + Originals) and Append it durably; (C) APPLY every op — on any apply failure ROLL BACK every already-applied op from that unified intent and return the error, leaving the WAL as the Recover backstop; (D) clearWAL on full success. Because Recover already converges a unified intent (Originals restore, Creates unlink, Moves converge), a crashed batch needs NO batch-specific Recover change.

It is LIBRARY-ONLY: the host drives the batch, so there is no CLI verb (a batch is not ergonomic as flags). It is ADDITIVE — the existing []region.Edit Prepare/Commit and the single-op CreateFile/DeleteFile/MoveFile paths are unchanged.

File create.go adds the FILE-LIFECYCLE create leg to the session engine (ADR-0004, SPEC §10): creating a new file is first-class on the same engine philosophy as an edit — WAL-logged, atomic, reject-never-corrupt. Its anchor is NON-EXISTENCE: the target must not already exist (no clobber, no overwrite escape hatch in this slice), enforced race-safely under the per-file lock with O_EXCL create semantics. The staged bytes must clear the same format/lint/parse floor edits clear, and the create is WAL-logged so a crash unlinks the half-created file (create's undo is unlink). It is ADDITIVE: the existing []region.Edit Prepare/Commit/Rollback path is unchanged.

File delete.go adds the FILE-LIFECYCLE delete leg to the session engine (ADR-0004, SPEC §10). Delete is the DESTRUCTIVE op, so safety is paramount: it is WAL-logged + reject-never-corrupt on the same engine philosophy as edits and create. Its anchor is the expected raw_hash drift gate — the live file must still hash to op.ExpectedRawHash or the delete HARD-REJECTS, never discarding bytes the caller did not see (mirroring the FileAnchor drift gate the edit path uses). Its undo is a content RESTORE: the FULL prior bytes are captured in a durable WAL record BEFORE the unlink, so a crash, rollback, or Recover can restore the deleted file. This WAL-original-bytes-FIRST, unlink-LAST ordering is the inverse of create's O_EXCL-before-WAL ordering: for delete the recoverable window is the one between the durable undo record and the destructive unlink. It is ADDITIVE — the existing edit and create paths are unchanged.

File move.go adds the FILE-LIFECYCLE move leg to the session engine (ADR-0004, SPEC §10). A move is an anchored-DELETE(source) + anchored-CREATE(destination) as ONE atomic-on-recovery unit. The MVP is RELOCATE ONLY: the source bytes are preserved at the destination unchanged (no LSP import-fixup in this slice).

It composes the create and delete anchors:

  • SOURCE anchor = the expected raw_hash drift gate (like delete): the live source must still hash to op.ExpectedRawHash or the move HARD-REJECTS, never relocating bytes the caller did not see. A missing source rejects with ErrNotFound.
  • DESTINATION anchor = NON-EXISTENCE (like create): the destination must NOT already exist or the move HARD-REJECTS, never clobbering it. This is enforced race-safely with an O_EXCL claim (openExclusive), NOT os.Rename — os.Rename CLOBBERS an existing destination on POSIX, so it cannot be the guard.

Safety priorities, in order: (1) NEVER lose the source bytes; (2) NEVER clobber an existing destination; (3) atomic-on-recovery via the WAL; (4) no orphan partial destination on crash. Both per-file locks are taken in a DETERMINISTIC SORTED order (lockPaths) so a move A->B racing a move B->A can never deadlock. It is ADDITIVE — the existing edit, create, and delete paths are unchanged.

Package session implements Båge's FILE-LEG two-phase, region-anchored, concurrency-safe edit protocol (SPEC §8, ADR-0003).

Prepare is OPTIMISTIC: it holds no lock, reads each live file, resolves every region-anchored edit against those bytes (rejecting a Conflict/Ambiguous with a typed error), preview-splices, formats, lints, and reparses to prove the result is valid, then durably records a wal.Intent. Nothing is written to a source file during Prepare — its sole on-disk effect is the WAL record.

Commit is the ATOMIC, lossless point. Per file, UNDER A PER-FILE LOCK, it RE-READS the live bytes and RE-RESOLVES every edit (resolve-under-lock, so a concurrent commit that benignly shifted a region is picked up and the edit lands at the current offset, never the stale one), splices, atomic-writes, reparses, and computes a region.EditResult. A region whose region_hash no longer matches any live node is a *ConflictError and that file is not written. Same-file commits serialize on one lock; cross-file commits take different locks and run in parallel.

The cross-store graph+file coordinator lives in HYLLA per ADR-0001; this package owns only the FILE leg.

Index

Constants

This section is empty.

Variables

View Source
var ErrConflict = errors.New("session: edit conflict")

ErrConflict is the sentinel wrapped by every ConflictError, so callers can match a region conflict with errors.Is without inspecting the path.

View Source
var ErrExists = errors.New("session: target already exists")

ErrExists is the sentinel returned when CreateFile is asked to create a path that already exists. The non-existence anchor is absolute in this slice: Båge never clobbers existing content, so callers can match the reject with errors.Is(err, ErrExists).

View Source
var ErrNotFound = errors.New("session: target does not exist")

ErrNotFound is the sentinel returned when DeleteFile is asked to delete a path that does not exist: there is nothing to delete, distinct from a drift reject. Callers can match it with errors.Is(err, ErrNotFound).

Functions

This section is empty.

Types

type BatchResult added in v0.2.0

type BatchResult struct {
	// Kind tags which op this result describes (OpCreate/OpDelete/OpMove/OpEdit).
	Kind OpKind
	// Edit holds the per-edit results for an OpEdit (one EditResult per region edit
	// in the op). Nil for other kinds.
	Edit []region.EditResult
	// Create holds the whole-file result for an OpCreate. Zero value for other kinds.
	Create region.EditResult
	// Delete holds the removed-path signal for an OpDelete. Zero value otherwise.
	Delete DeleteResult
	// Move holds the relocation signal for an OpMove. Zero value otherwise.
	Move MoveResult
}

BatchResult is the per-op outcome a host (Hylla) reads after a successful ApplyBatch, in input order, so it can map each op to its graph effect. Exactly one of the inner result fields is populated, selected by Kind: Edit for the edits applied to one file, Create for a created (or move-destination) whole-file result, Delete for a removed file, Move for a relocation.

type ConflictError

type ConflictError struct {
	// Path is the file whose region could not be resolved.
	Path string
	// Reason is the resolve status that triggered the conflict ("conflict" or
	// "ambiguous"), or a wrapped resolver error message.
	Reason string
	// contains filtered or unexported fields
}

ConflictError reports that a region-anchored edit could not be resolved against the live file: the target's region_hash no longer matches any live node (a concurrent edit changed the same region), or it matches more than one (ambiguous twins). Either case is a hard reject — Båge never guesses and never misapplies (SPEC §8.3, §8.4, ADR-0003). The offending file is left untouched.

func (*ConflictError) Error

func (e *ConflictError) Error() string

Error implements error with a path-and-reason-qualified message.

func (*ConflictError) Kind added in v0.3.0

func (e *ConflictError) Kind() Kind

Kind classifies the conflict so KindOf can distinguish a raw_hash drift reject (KindDrift) from a region resolve conflict (KindConflict). It prefers the explicitly set kind; when zero it falls back to the Reason heuristic so construction sites that predate the field stay correctly classified.

func (*ConflictError) Unwrap

func (e *ConflictError) Unwrap() error

Unwrap returns ErrConflict so errors.Is(err, ErrConflict) matches a ConflictError.

type DeleteResult added in v0.2.0

type DeleteResult struct {
	// Path is the file that was deleted.
	Path string `json:"path" toon:"path"`
	// RawHash is the raw content hash the live bytes matched (the satisfied
	// drift anchor), confirming WHICH content was removed.
	RawHash string `json:"raw_hash" toon:"raw_hash"`
}

DeleteResult is the small success signal a host (Hylla) reads after a delete to close that file's node versions: the deleted path and the confirmed raw_hash the live bytes matched at unlink time.

func (DeleteResult) RenderText added in v0.4.0

func (r DeleteResult) RenderText(w io.Writer) error

RenderText writes the human-readable delete confirmation line — "deleted <path> raw=<h>" — the FormatText path render.Emit type-asserts to (DeleteResult implements render.TextRenderable without importing pkg/render, avoiding an import cycle). It is byte-identical to the line the delete CLI printed before --format wiring.

type ErrorEnvelope added in v0.3.0

type ErrorEnvelope struct {
	// Kind is the stable classification from KindOf.
	Kind Kind `json:"kind"`
	// Path is the file the failure concerns, omitted from JSON when empty.
	Path string `json:"path,omitempty"`
	// Message is the underlying error's text.
	Message string `json:"message"`
}

ErrorEnvelope is the machine- and human-renderable projection of a session error: its stable Kind, the offending path (when known), and the underlying error message. Hosts marshal it to JSON for Hylla or render it as a single diagnostic line for the CLI via RenderText.

func Envelope added in v0.3.0

func Envelope(err error) ErrorEnvelope

Envelope projects err into an ErrorEnvelope: it classifies err via KindOf, records err.Error() as the message, and lifts the Path from a wrapped *ConflictError when one is present in the chain.

func (ErrorEnvelope) RenderText added in v0.3.0

func (e ErrorEnvelope) RenderText(w io.Writer) error

RenderText writes the envelope as a single "bage: <kind>: <message>" line to w. It implements the render.TextRenderable contract by method shape alone, so this package imports only io and fmt — never pkg/render.

type Kind added in v0.3.0

type Kind string

Kind is a stable, machine-readable classification of a session error, used by hosts (CLI exit codes, Hylla) to react to a failure without inspecting wrapped error chains or message text.

const (
	// KindConflict marks a region-anchored edit that could not be resolved against
	// the live file (concurrent change or ambiguous twins); see ErrConflict.
	KindConflict Kind = "conflict"
	// KindDrift marks a raw_hash drift reject: the live bytes no longer match the
	// expected anchor the caller saw.
	KindDrift Kind = "drift"
	// KindExists marks a create rejected because the target path already exists;
	// see ErrExists.
	KindExists Kind = "exists"
	// KindNotFound marks an op rejected because the target path does not exist;
	// see ErrNotFound.
	KindNotFound Kind = "not-found"
	// KindUsage marks a caller/usage error (bad arguments or invalid request).
	KindUsage Kind = "usage"
	// KindIO marks the default I/O or otherwise unclassified failure.
	KindIO Kind = "io"
)

func KindOf added in v0.3.0

func KindOf(err error) Kind

KindOf classifies err into a Kind. It returns the empty Kind for a nil error, the error's own Kind if any error in the chain implements Kinded, then matches the known sentinels (ErrConflict, ErrExists, ErrNotFound) and the OS not-exist error (os.ErrNotExist, e.g. opening a missing file) via errors.Is, and otherwise defaults to KindIO.

type Kinded added in v0.3.0

type Kinded interface {
	Kind() Kind
}

Kinded is implemented by errors that classify themselves. KindOf prefers an errors.As-discoverable Kinded value over the sentinel switch.

type MoveResult added in v0.2.0

type MoveResult struct {
	// From is the source path that was removed by the move.
	From string `json:"from" toon:"from"`
	// Dest is the whole-file EditResult for the destination (the relocated bytes),
	// shaped like a create result so a host can ingest the destination's nodes.
	Dest region.EditResult `json:"dest" toon:"dest"`
}

MoveResult is the signal a host (Hylla) reads after a move so it can re-identify the moved file's nodes: the destination create-result (whole-file EditResult over the relocated bytes) plus the source path that was removed.

func (MoveResult) RenderText added in v0.4.0

func (r MoveResult) RenderText(w io.Writer) error

RenderText writes the human-readable move confirmation line — "moved <from> -> <dest> raw=<h>" — where the destination path and raw_hash come from the destination EditResult. It is the FormatText path render.Emit type-asserts to (MoveResult implements render.TextRenderable without importing pkg/render, avoiding an import cycle) and is byte-identical to the line the move CLI printed before --format wiring.

type Op added in v0.2.0

type Op struct {
	// Kind tags which lifecycle operation this is.
	Kind OpKind
	// Path is the target file path. For OpMove it is the SOURCE path being moved.
	Path string
	// To is the destination path for OpMove: the source bytes are relocated here
	// unchanged. Its anchor is non-existence (To must NOT already exist, no
	// clobber). Empty for OpCreate/OpDelete.
	To string
	// Content is the full bytes of the new file (OpCreate).
	Content string
	// Lang optionally forces the language for the parse floor; LangUnknown (the
	// zero value) means auto-detect from Path.
	Lang parser.Lang
	// ExpectedRawHash is the drift gate for OpDelete and the SOURCE of OpMove: the
	// live file must still hash (hashing.RawHash) to this value or the op
	// HARD-REJECTS, never discarding (delete) or relocating (move) bytes the caller
	// did not see. Empty for OpCreate.
	ExpectedRawHash string
	// Edit carries the region-anchored edit for OpEdit: the content-anchored region
	// (gated by its region_hash) and the replacement text. It is nil for every other
	// op kind. Path mirrors Edit.Region.Path so the batch can lock and key the op by
	// path uniformly. (ADR-0004 §10.1.)
	Edit *region.Edit
}

Op is a tagged file-lifecycle operation (ADR-0004). It currently models Create; the tagged shape lets later slices add Delete/Move without changing the edit (region.Edit) path. For OpCreate, Path is the file to create and Content is its full bytes; Lang optionally forces the parse-floor language, otherwise the session auto-detects it from Path (mirroring langFor).

type OpKind added in v0.2.0

type OpKind int

OpKind tags the kind of a file-lifecycle Op. It is shaped to extend to delete and move in later slices; this slice implements only OpCreate.

const (
	// OpCreate is a create-from-non-existence op: bring a new file into being
	// with the given content, rejecting if the path already exists.
	OpCreate OpKind = iota
	// OpDelete is a delete op: unlink an existing file, gated by the expected
	// raw_hash drift anchor, capturing the prior bytes for restore (ADR-0004).
	OpDelete
	// OpMove is a relocate op: anchored-DELETE(Path) + anchored-CREATE(To) as one
	// atomic-on-recovery unit, preserving the source bytes unchanged at the
	// destination (ADR-0004). The source is gated by the raw_hash drift anchor
	// (like delete); the destination is gated by non-existence (like create).
	OpMove
	// OpEdit is a region-anchored edit op: replace the bytes of a content-anchored
	// region with new text, gated by the region's region_hash (the same drift gate
	// the []region.Edit Prepare/Commit path uses). It lets an edit be carried as an
	// Op so a heterogeneous []Op batch (Session.ApplyBatch) can mix edits with
	// create/delete/move as ONE all-or-nothing change (ADR-0004 §10.1). The edit
	// payload lives in Op.Edit; the other op kinds leave it nil.
	OpEdit
)

func (OpKind) String added in v0.2.0

func (k OpKind) String() string

String returns a stable lowercase label for the op kind.

type Plan

type Plan struct {
	// Intent is the WAL intent recorded by Prepare (already persisted): originals
	// for restore-on-failure plus the expected per-file hashes.
	Intent wal.Intent
	// Edits are the region-anchored edits this plan will apply, in input order.
	Edits []region.Edit
	// Anchors maps each file path to the per-file drift gate it was prepared
	// against (SPEC §8.1).
	Anchors map[string]region.FileAnchor
}

Plan is the result of a successful Prepare. It records the region edits and the per-file anchors so Commit can re-validate under lock — Prepare is optimistic, Commit is the atomic point (SPEC §8).

type Session

type Session struct {
	// Parser reparses live and staged bytes (drift relocation + parse assertion).
	Parser parser.ParserPort
	// Hasher computes the raw and normalized hashes recorded into the WAL intent
	// and the post-edit hashes returned in each EditResult.
	Hasher hashing.Hasher
	// Formatter, when non-nil, rewrites staged bytes before linting/parsing.
	Formatter format.Formatter
	// Linter, when non-nil, validates staged bytes; a lint failure blocks Prepare.
	Linter format.Linter
	// Lang, when set (non-LangUnknown), forces that language for every file;
	// when LangUnknown (the zero value) each file's language is auto-detected
	// from its path via parser.LangForPath. It is an optional per-session
	// override, letting an agent IDE edit a mixed-language tree without a single
	// session-wide language.
	Lang parser.Lang
	// WALDir is the directory holding this session's write-ahead log.
	WALDir string
	// contains filtered or unexported fields
}

Session is the configured FILE-LEG edit engine. Formatter and Linter may be nil to skip the corresponding step; Parser, Hasher, and WALDir are required. Lang is an OPTIONAL per-session override (see the field). A single Session is safe for concurrent Prepare/Commit calls: the per-file lock map serializes writers to the same file while letting different files proceed in parallel (SPEC §8.3).

func (*Session) ApplyBatch added in v0.2.0

func (s *Session) ApplyBatch(ctx context.Context, ops []Op) ([]BatchResult, error)

ApplyBatch applies a heterogeneous []Op as ONE all-or-nothing change (ADR-0004 §10.1). It locks the UNION of every op's path (plus each move's destination) in the deadlock-free SORTED order, then runs four phases under that lock: VALIDATE every anchor (writing nothing), append ONE unified wal.Intent capturing every op's undo, APPLY every op (rolling back from that intent on any apply failure), and clear the WAL on full success. On any reject NOTHING is half-applied and the filesystem is left exactly as before. It returns one BatchResult per op in input order. An empty batch, an unknown op kind, or an OpEdit with a nil Edit is a hard reject.

func (*Session) Commit

func (s *Session) Commit(plan *Plan) ([]region.EditResult, error)

Commit is the atomic, lossless point. Per file, under that file's lock, it RE-READS the live bytes, RE-RESOLVES every edit against the current content (resolve-under-lock — this picks up concurrent commits, so a benignly shifted region lands at its current offset and a same-region conflict is rejected), splices the resolved ranges, atomic-writes, reparses, and computes an EditResult. A *ConflictError on any file aborts the Commit: every file already written in this Commit is restored from its original, and the WAL is preserved for Recover. On full success the WAL is cleared. Different files take different locks, so cross-file commits run in parallel; same-file commits serialize.

func (*Session) CreateFile added in v0.2.0

func (s *Session) CreateFile(ctx context.Context, op Op) (region.EditResult, error)

CreateFile creates a new file from op, returning a whole-file EditResult a host can ingest. It rejects unless op.Kind is OpCreate.

Under the target's per-file lock it: (1) runs the format/lint/parse floor over the staged bytes (a failure rejects, NOTHING is written); (2) mkdir -p's the parent directory; (3) creates the file with O_CREATE|O_EXCL so a pre-existing path (or a concurrent create that won the race) HARD-REJECTS with ErrExists and never clobbers — this EXCLUSIVELY claims the path and PROVES non-existence BEFORE anything durable names it; (4) only now WAL-logs the create (Creates names the now-guaranteed-mine path so a crash or rollback unlinks it); (5) writes + fsyncs the bytes; (6) clears the WAL. Doing the O_EXCL open before the WAL append is the clobber-via-recovery fix: a Creates record can only ever name a file THIS op actually brought into being, so Recover's unlink can never delete pre-existing content. On any post-open failure the partial file is removed AND the WAL is cleared. The O_EXCL create under the lock is the non-existence anchor: the existence check and the create are one atomic kernel step, so two concurrent creates cannot both win and nothing is overwritten.

func (*Session) DeleteFile added in v0.2.0

func (s *Session) DeleteFile(_ context.Context, op Op) (DeleteResult, error)

DeleteFile deletes op.Path, returning a DeleteResult a host can ingest to close that file's node versions. It rejects unless op.Kind is OpDelete.

Under the target's per-file lock it: (1) reads the live bytes — a missing path HARD-REJECTS with ErrNotFound (nothing to delete) and NOTHING is unlinked; (2) gates the delete on the raw_hash drift anchor — if the live bytes no longer hash to op.ExpectedRawHash a concurrent change the caller did not see occurred, so the delete HARD-REJECTS with a *ConflictError and NOTHING is unlinked (Båge never discards bytes the caller did not see); (3) durably WAL-logs the delete with the FULL prior bytes captured in Originals BEFORE any destructive effect, so a crash between the record and the unlink is recoverable (Recover restores the bytes); (4) only now unlinks the file; (5) clears the WAL. The WAL-with-original-bytes-FIRST, unlink-LAST ordering is the inverse of create's O_EXCL-before-WAL ordering: a crash in the recoverable window leaves a durable record whose Originals restore the deleted file. On any reject NOTHING is unlinked.

func (*Session) MoveFile added in v0.2.0

func (s *Session) MoveFile(_ context.Context, op Op) (MoveResult, error)

MoveFile relocates op.Path to op.To, preserving the source bytes unchanged, and returns a MoveResult a host can ingest. It rejects unless op.Kind is OpMove.

Under BOTH per-file locks (source and destination) acquired in a DETERMINISTIC SORTED order so concurrent moves can never deadlock, it: (1) reads the live source — a missing source HARD-REJECTS with ErrNotFound and NOTHING moves; (2) gates the source on the raw_hash drift anchor — a live mismatch HARD-REJECTS with a *ConflictError and NOTHING moves (never relocate bytes the caller did not see); (3) mkdir -p's the destination's parent then O_EXCL-creates the destination and writes the source bytes + fsync — the O_EXCL claim PROVES the destination did not exist (ErrExists if it did, no clobber) and durably lands the relocated bytes; (4) durably WAL-logs the move (a Moves record plus Originals[src]=bytes) so a crash between here and the unlink converges to fully-moved with the source bytes as the backstop; (5) removes the source; (6) clears the WAL. On any reject NOTHING moves: the source is intact and no destination is left behind.

Ordering note: the destination is claimed+written BEFORE the WAL append (so a rejected destination can never log a Moves record that Recover would act on), yet the source is NOT unlinked until AFTER the WAL append — so the recoverable window (durable Moves record + Originals, destination already written, source still present) always has the source bytes safe, satisfying priority (1).

func (*Session) Prepare

func (s *Session) Prepare(ctx context.Context, edits []region.Edit, anchors []region.FileAnchor) (*Plan, error)

Prepare optimistically stages every region-anchored edit and records one WAL intent. It holds no lock. For each file it reads the live bytes, resolves every edit via region.Resolve (a Conflict/Ambiguous resolve yields a *ConflictError and nothing is committed), preview-splices the resolved ranges, runs the Formatter then the Linter (a lint failure rejects), and reparses to assert the result still parses (a parse failure rejects). The originals and expected hashes are recorded into a wal.Intent persisted via wal.Append. On success it returns a Plan; the only on-disk effect is the WAL record — no source file is written.

func (*Session) Recover

func (s *Session) Recover(_ context.Context, dir string) error

Recover is the restart path: it replays the WAL in dir and, for each intent, restores every affected file from Intent.Originals via atomicwrite.Write, converging the files back to their pre-Prepare state (files-as-truth, SPEC §1.2 converge-on-crash). It then clears the WAL. A crash between Prepare and Commit leaves a WAL record whose Originals undo the half-done edit; a crash after Commit cleared the WAL leaves nothing to replay.

The same Originals loop is the delete undo: a DELETE intent (ADR-0004) records every deleted path's FULL prior bytes in Originals BEFORE the unlink, so a crash in the window between that durable record and the unlink is recovered by writing those bytes back — restoring the deleted file. A delete that already landed (file gone, bytes captured) is re-created with its original content; a delete that never reached the unlink leaves the file present and the restore is a harmless rewrite of identical bytes. (Intent.Deletes marks the lifecycle op; the restore mechanism is Originals, shared with the edit-undo path.)

A UNIFIED BATCH intent (Intent.Batch true, Session.ApplyBatch) converges the WHOLE batch BACKWARD to fully-before: its edits/deletes/creates undo as above and its MOVES also undo (the source is restored in place from Originals[From] and the destination is removed), so a crash mid-batch can never leave the half-applied state where the move completed forward while the other ops rolled back (ADR-0004 §10.1). A SINGLE-OP MoveFile intent (Batch false) keeps the forward-converge semantics described above.

func (*Session) Rollback

func (s *Session) Rollback(plan *Plan) error

Rollback abandons a prepared Plan. Because Prepare writes nothing live (only the WAL record), rollback discards the staged edits and clears the WAL; the source files are left untouched (SPEC §1.2 restore-on-handled-failure).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL