notebook

package
v1.0.3 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package notebook composes workspaces, Git state, and storage into the safe pull and optimistic commit operations in architecture/pull.md, commit.md, conflicts.md, checkpoints.md, and guarantees.md. It is the only consumer of the storage protocol besides cleanup: Pull reads and validates the authoritative manifest and imports packs; Commit builds proposals, uploads immutable packs before their manifest CAS, and resolves contention, ambiguity, and recovery.

The package consumes narrow consumer-owned interfaces (Workspace and the storage.ObjectStore boundary). All CGo and libgit2 types stay inside internal/git2; the notebook speaks only the git seam.

Index

Constants

View Source
const (
	MaxRetryLimit          = 100
	MinCheckpointPacks     = 1
	MaxRetainedCheckpoints = 64
)

MaxRetryLimit, MinCheckpointPacks, and MaxRetainedCheckpoints are the documented operational ranges (architecture/config.md). The application package validates its flags against the same values, so the two range checks cannot drift.

View Source
const DefaultCheckpointPacks = 256

DefaultCheckpointPacks is the application-resolved checkpoint threshold: the active tail length that schedules one checkpoint effort. A fresh workspace downloads and imports the whole tail one pack at a time, so the threshold bounds the cost of a cold pull; 256 keeps that bounded while the checkpoint effort — one full-state export and upload — stays rare.

View Source
const DefaultRetainedCheckpoints = 1

DefaultRetainedCheckpoints is the application-resolved retention count: the number of previous checkpoint generations kept in addition to the active generation.

View Source
const DefaultRetryLimit = 8

DefaultRetryLimit is the application-resolved CAS retry bound.

View Source
const MaxMessageBytes = 16384

MaxMessageBytes is the notes_commit message byte bound (architecture/product-contract.md). The MCP schema advertises the same value.

View Source
const PathSetsNestRule = "longest match decides"

PathSetsNestRule is the terse form of the composition rule, carried by every surface that lists both sets: each set's own sentence is complete only against the other, because the sets may nest (architecture/product-contract.md, Read-only and writable paths).

View Source
const ReadOnlyListSeparator = ", "

ReadOnlyListSeparator joins read-only entries on every surface that lists them.

Variables

This section is empty.

Functions

func LoggerFrom

func LoggerFrom(ctx context.Context) *slog.Logger

LoggerFrom returns the logger attached by WithLogger, or a discard logger when none is attached. It never returns nil, so callers can log unconditionally.

func ValidateMessage added in v0.1.15

func ValidateMessage(message string) error

ValidateMessage applies the notes_commit message contract before any scan or S3 access: non-blank (not only Unicode white space), at most 16,384 bytes, valid UTF-8 without U+0000. The MCP decoder calls it too, so both surfaces classify a bad message alike.

func WithLogger

func WithLogger(ctx context.Context, logger *slog.Logger) context.Context

WithLogger attaches logger to ctx for notebook background-effort records (checkpoint and cleanup warnings). The MCP layer attaches the request-scoped logger carrying the mcpReqID attribute, so a warning from a best-effort checkpoint or cleanup stays correlated with the tool call that scheduled it.

Types

type Action added in v0.1.15

type Action string

Action is the caller's next step after a domain error.

const (
	ActionFixInput  Action = "FIX_INPUT"
	ActionEditFiles Action = "EDIT_FILES"
	ActionPull      Action = "PULL"
	ActionRetry     Action = "RETRY"
	ActionOperator  Action = "OPERATOR"
)

type BackoffWaiter

type BackoffWaiter interface {
	Wait(ctx context.Context, attempt int) error
}

BackoffWaiter waits between CAS retries. The notebook resolves the wait policy; tests inject a deterministic waiter so retry bounds are exact.

type Change added in v1.0.0

type Change struct {
	Path       string
	Kind       ChangeKind
	Insertions int
	Deletions  int
}

Change is one file whose visible content differs from the accepted state, with its line counts.

type ChangeKind added in v1.0.0

type ChangeKind string

ChangeKind classifies one local change against the accepted state.

const (
	ChangeAdded    ChangeKind = "added"
	ChangeModified ChangeKind = "modified"
	ChangeDeleted  ChangeKind = "deleted"
)

type Code

type Code string

Code is the stable error taxonomy of notebook operations. The MCP layer maps each code to the structured tool error; the text of an error can change, its code cannot.

const (
	// CodeInvalidRequest reports invalid tool input or a state the
	// operation refuses before any Git or S3 work: a blank commit
	// message, a commit without a managed pull, or invalid visible
	// content; or before any local mutation: a first pull into a
	// directory holding files the remote notebook does not.
	CodeInvalidRequest Code = "INVALID_REQUEST"
	// CodeContentConflict reports a three-tree merge conflict. L is
	// rewritten with the full materialized result and the exact conflicted
	// paths and marker ranges are part of the error.
	CodeContentConflict Code = "CONTENT_CONFLICT"
	// CodeStorageIntegrity reports stored state that failed validation:
	// a corrupt pack, a pack that contradicts its descriptor, a missing
	// object in the accepted history, or a cache that cannot be trusted.
	CodeStorageIntegrity Code = "STORAGE_INTEGRITY"
	// CodeStorageFailure reports an object-store operation that failed
	// without a known accepted result: a pack download or upload, a
	// manifest read, or a CAS whose acceptance cannot be proved.
	CodeStorageFailure Code = "STORAGE_FAILURE"
	// CodeRemoteBusy reports that the CAS lost the configured retry
	// bound. Visible files are preserved for another attempt.
	CodeRemoteBusy Code = "REMOTE_BUSY"
	// CodeRecoveryFailure reports an unexpected failure after local
	// mutation started. The generic recovery path ran; the error carries
	// the recovery report.
	CodeRecoveryFailure Code = "RECOVERY_FAILURE"
)

type Config

type Config struct {
	// Workspace is the managed visible path.
	Workspace Workspace
	// Store is the semantic object-store boundary.
	Store storage.ObjectStore
	// ReadOnlyPaths are the notebook-relative read-only entries
	// (architecture/product-contract.md, Read-only and writable paths); New
	// rejects an invalid one.
	ReadOnlyPaths []string
	// WritablePaths are the notebook-relative writable entries. A non-empty
	// set protects every unmatched path (architecture/product-contract.md,
	// Read-only and writable paths); New rejects an invalid entry and a
	// path named by both sets.
	WritablePaths []string
	// RetryLimit bounds CAS retries after the first attempt.
	RetryLimit int
	// CheckpointPacks triggers one checkpoint effort when the active tail
	// length reaches this count.
	CheckpointPacks int
	// RetainedCheckpoints keeps this many previous checkpoint generations
	// as cleanup roots in addition to the active generation.
	RetainedCheckpoints int
	// NewID produces protocol publication and checkpoint IDs.
	NewID func() (storage.UUID, error)
	// Now is the operation-attempt clock for commit timestamps.
	Now func() time.Time
	// Waiter sleeps between CAS retries; nil uses bounded full-jitter
	// exponential backoff. Tests inject a deterministic waiter.
	Waiter BackoffWaiter
	// Failpoints injects deterministic failures; nil disables injection.
	Failpoints *Failpoints
}

Config wires one notebook. RetryLimit is the number of CAS attempts after the first (the application resolves the default; the notebook validates the range 0..100, where 0 means a single attempt). CheckpointPacks is the active-tail length that triggers one checkpoint effort (the application resolves the default 256; the notebook requires at least 1, so an explicitly configured zero fails). RetainedCheckpoints is the number of previous checkpoint generations kept as cleanup roots (the application resolves the default 1; the notebook validates 0..64). NewID and Now default to storage.NewUUIDv7 and time.Now; tests inject deterministic sources.

type Error

type Error struct {
	Code     Code
	Reason   Reason
	Action   Action
	Message  string
	Files    []ErrorFile
	Recovery *RecoveryReport
	Cause    error
}

Error is a notebook domain error (architecture/product-contract.md). Recovery is set only for CodeRecoveryFailure. Cause keeps the underlying failure for diagnostics and errors.Is.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap exposes the cause so errors.Is can classify wrapped failures.

type ErrorFile added in v0.1.15

type ErrorFile struct {
	Path   string
	Reason FileReason
	Ranges []git.MarkerRange
}

ErrorFile names one conflicted or rejected path, its reason, and the one-based inclusive marker ranges inside it (architecture/conflicts.md).

type Failpoints

type Failpoints struct {
	// CAS fires after the manifest CAS accepted the proposal and before
	// the local acceptance begins. A failure leaves the remote accepted
	// and P/L unadvanced: the exact window the generic recovery path
	// repairs, with remote acceptance known to be yes.
	CAS func() error
}

Failpoints injects deterministic failures at the notebook orchestration boundaries. A nil hook means no injection. The workspace mutation boundaries keep their own failpoints; the notebook maps their errors to the generic recovery path.

type FileReason added in v0.1.15

type FileReason string

FileReason classifies one file entry of a domain error.

const (
	FileReasonTextConflict      FileReason = "TEXT_CONFLICT"
	FileReasonPathConflict      FileReason = "PATH_CONFLICT"
	FileReasonUnresolvedMarkers FileReason = "UNRESOLVED_MARKERS"
	FileReasonReadOnly          FileReason = "READ_ONLY"
	FileReasonInvalidContent    FileReason = "INVALID_CONTENT"
	// A first pull found the file in the directory but not in the notebook,
	// or with other bytes than the notebook's (ReasonDirectoryNotEmpty).
	FileReasonNotInNotebook       FileReason = "NOT_IN_NOTEBOOK"
	FileReasonDiffersFromNotebook FileReason = "DIFFERS_FROM_NOTEBOOK"
)

type History added in v1.0.0

type History struct {
	Entries []LogEntry
	// More reports that older publications exist beyond Entries, or were
	// dropped by a checkpoint.
	More bool
}

History is the recent accepted publications, newest first.

type LogEntry added in v1.0.0

type LogEntry struct {
	Message string
}

LogEntry is one accepted publication.

type Metrics

type Metrics struct {
	// TailCount is the active increment count of the last observed
	// authoritative manifest. Retained tails do not count.
	TailCount atomic.Uint64
	// TailBytes is the sum of the active increment pack sizes of the last
	// observed authoritative manifest.
	TailBytes atomic.Uint64

	// CheckpointRuns counts scheduled checkpoint efforts.
	CheckpointRuns atomic.Uint64
	// CheckpointFailures counts checkpoint efforts that ended without a
	// successful manifest replacement. The failure never changes an
	// already accepted commit result.
	CheckpointFailures atomic.Uint64
	// CheckpointCASAttempts counts manifest CAS attempts by checkpoint
	// workers.
	CheckpointCASAttempts atomic.Uint64
	// CheckpointSize is the byte size of the last successfully indexed
	// checkpoint pack.
	CheckpointSize atomic.Uint64
	// CheckpointDurationNanos is the wall duration of the last successful
	// checkpoint effort, from selection to manifest acceptance.
	CheckpointDurationNanos atomic.Int64

	// CleanupRuns counts cleanup runs after a successful checkpoint CAS.
	CleanupRuns atomic.Uint64
	// CleanupCandidates counts listed pack keys at or before the cleanup
	// cutoff.
	CleanupCandidates atomic.Uint64
	// CleanupDeleted counts pack keys deleted by cleanup.
	CleanupDeleted atomic.Uint64
	// CleanupErrors counts failed delete batches and aborted cleanup
	// runs. A failed batch may have deleted part of its keys; the
	// semantic boundary cannot name them, so the error is recorded at
	// batch granularity and retried on a later checkpoint cleanup.
	CleanupErrors atomic.Uint64
}

Metrics exposes the operational measurements in architecture/checkpoints.md and guarantees.md: the active tail shape, checkpoint efforts, and cleanup results. The notebook records every value; tests read them and a later MCP or observability layer can sample them. Values are monotonic counters or last-value gauges, safe for concurrent use.

type Notebook

type Notebook struct {
	// contains filtered or unexported fields
}

Notebook executes pull and commit against one workspace and one store. All methods are safe for concurrent use; each Pull and Commit holds the workspace operation lock from its first check to its result, so two operations on one visible path never interleave. Checkpoint scheduling is opportunistic and never determines commit success (architecture/checkpoints.md).

func New

func New(cfg Config) (*Notebook, error)

New validates the configuration and returns a ready notebook.

func (*Notebook) Commit

func (n *Notebook) Commit(ctx context.Context, message string) (Result, error)

Commit publishes the caller's changes and incorporates concurrent, non-conflicting changes (architecture/commit.md). It requires a non-blank message and a managed pull, validates every visible file and rejects complete conflict-marker blocks before any Git or S3 work, then merges the accepted baseline, L, and R. A clean result creates a commit whose parent is the observed R head (or none for the first publication), uploads one immutable pack before its manifest CAS, and rewrites L and P to the accepted state only after acceptance is proved. A CAS loss imports the new remote tail, merges again, and retries with a new publication ID, generation, key, commit, and pack up to the configured bound. A no-change result synchronizes L and P to R without any remote mutation.

The returned Result reports the accepted generation and the diffstat of the published increment: the observed remote parent tree versus the accepted merged tree. A no-op commit returns the remote generation with an empty stat. A conflict or any error returns the zero Result with the existing error.

func (*Notebook) Log added in v1.0.0

func (n *Notebook) Log(ctx context.Context, limit int) (History, error)

Log returns up to limit accepted publications, newest first, from the history this machine holds. It reads no remote state, so a directory that was never pulled has none.

func (*Notebook) Metrics

func (n *Notebook) Metrics() *Metrics

Metrics returns the operational measurements of the notebook.

func (*Notebook) Pull

func (n *Notebook) Pull(ctx context.Context) (Result, error)

Pull validates and ingests the visible directory, reads and validates the authoritative manifest, downloads only missing packs, imports the accepted state, and merges the accepted baseline, L, and R (architecture/pull.md). L is rewritten with the full merge result and R becomes the new baseline. A conflicting pull writes the markers and non-conflicting results to L, records R as the baseline, and returns the exact conflicted paths and ranges; it never reverts L.

The returned Result reports the accepted remote generation and the diffstat of the on-disk delta between the visible state the pull observed and the materialized result. A conflict or any error returns the zero Result with the existing error.

func (*Notebook) ReadOnlyPaths added in v0.1.15

func (n *Notebook) ReadOnlyPaths() []string

ReadOnlyPaths returns the normalized, sorted read-only entries; never nil.

func (*Notebook) Status added in v1.0.0

func (n *Notebook) Status(ctx context.Context) (Status, error)

Status compares the visible directory with the accepted state.

func (*Notebook) WritablePaths added in v0.1.19

func (n *Notebook) WritablePaths() []string

WritablePaths returns the normalized, sorted writable entries; never nil.

type Reason added in v0.1.15

type Reason string

Reason classifies a domain error one level below Code (architecture/product-contract.md, Reason and action tokens).

const (
	ReasonMalformedInput      Reason = "MALFORMED_INPUT"
	ReasonPathOutsideRoot     Reason = "PATH_OUTSIDE_ROOT"
	ReasonMessageBlank        Reason = "MESSAGE_BLANK"
	ReasonMessageTooLong      Reason = "MESSAGE_TOO_LONG"
	ReasonMessageInvalid      Reason = "MESSAGE_INVALID"
	ReasonPullRequired        Reason = "PULL_REQUIRED"
	ReasonDirectoryNotEmpty   Reason = "DIRECTORY_NOT_EMPTY"
	ReasonInvalidContent      Reason = "INVALID_CONTENT"
	ReasonReadOnlyPath        Reason = "READ_ONLY_PATH"
	ReasonIgnoredConflict     Reason = "IGNORED_CONFLICT"
	ReasonMergeConflict       Reason = "MERGE_CONFLICT"
	ReasonUnresolvedMarkers   Reason = "UNRESOLVED_MARKERS"
	ReasonRetriesExhausted    Reason = "RETRIES_EXHAUSTED"
	ReasonManifestRead        Reason = "MANIFEST_READ"
	ReasonPackDownload        Reason = "PACK_DOWNLOAD"
	ReasonPackUpload          Reason = "PACK_UPLOAD"
	ReasonPublicationUnproven Reason = "PUBLICATION_UNPROVEN"
	ReasonManifestWrite       Reason = "MANIFEST_WRITE"
	ReasonLocalState          Reason = "LOCAL_STATE"
	ReasonInternal            Reason = "INTERNAL"
	ReasonStorageFull         Reason = "STORAGE_FULL"
	ReasonRequestLimit        Reason = "REQUEST_LIMIT"
	ReasonRateLimited         Reason = "RATE_LIMITED"
	ReasonAccessDenied        Reason = "ACCESS_DENIED"
	ReasonObjectTooLarge      Reason = "OBJECT_TOO_LARGE"
	ReasonUpgradeRequired     Reason = "UPGRADE_REQUIRED"
	ReasonManifestInvalid     Reason = "MANIFEST_INVALID"
	ReasonPackInvalid         Reason = "PACK_INVALID"
	ReasonHistoryInvalid      Reason = "HISTORY_INVALID"
	ReasonEngineFailed        Reason = "ENGINE_FAILED"
	ReasonLocalMutationFailed Reason = "LOCAL_MUTATION_FAILED"
)

type RecoveryReport

type RecoveryReport struct {
	Stage          string
	RemoteAccepted RemoteAccepted
	Resynchronized bool
}

RecoveryReport describes one generic recovery run (architecture/guarantees.md): the failed stage, whether remote acceptance is known, and whether resynchronization from authoritative current succeeded.

type RemoteAccepted

type RemoteAccepted string

RemoteAccepted is the recovery report's statement about remote acceptance: the proposal landed, never landed, or cannot be proved.

const (
	RemoteAcceptedYes     RemoteAccepted = "yes"
	RemoteAcceptedNo      RemoteAccepted = "no"
	RemoteAcceptedUnknown RemoteAccepted = "unknown"
)

type Result added in v0.1.4

type Result struct {
	Generation uint64
	Stat       git.DiffStat
}

Result is the success summary of one pull or commit: the accepted remote generation after the operation and the per-file line-change diffstat of what the operation changed. Pull reports the delta between the visible state it observed and the materialized result; commit reports the increment the publication added over the observed remote parent tree, empty for a no-op synchronization. The zero Result is always paired with a non-nil error; consumers read Result only when err is nil.

type Status added in v1.0.0

type Status struct {
	// Generation is the accepted remote generation; zero before the first pull.
	Generation uint64
	// Pulled reports whether the directory has been pulled, which a commit requires.
	Pulled bool
	// RecoveryRequired reports that the next pull or commit resynchronizes
	// the directory first. Changes is empty then: the directory is not compared.
	RecoveryRequired bool
	// Changes lists the local changes sorted by path.
	Changes []Change
}

Status is the local view of a notebook: what the last accepted state is and what a commit would publish. It reads no remote state.

type Workspace

type Workspace interface {
	Hold(ctx context.Context) (context.Context, func(), error)
	Snapshot(ctx context.Context) (git.Snapshot, error)
	Baseline() workspace.Baseline
	Repo() git.Repository
	Accept(ctx context.Context, baseline workspace.Baseline) error
	Materialize(ctx context.Context, baseline workspace.Baseline, tree git.OID) error
	Recover(ctx context.Context, baseline workspace.Baseline) error
	RecoveryRequired() bool
	CacheDir() string
	Pulled() bool
	MarkPulled(ctx context.Context) error
}

Workspace is the notebook's narrow view of a managed visible path. The interface is consumer-owned and path-free: the notebook never touches the caller's path directly, only the workspace's locked operations. The real implementation is internal/workspace.Workspace; tests use the real workspace over a fake engine, so only the Git engine and the object store are ever faked.

Jump to

Keyboard shortcuts

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