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
- func LoggerFrom(ctx context.Context) *slog.Logger
- func ValidateMessage(message string) error
- func WithLogger(ctx context.Context, logger *slog.Logger) context.Context
- type Action
- type BackoffWaiter
- type Change
- type ChangeKind
- type Code
- type Config
- type Error
- type ErrorFile
- type Failpoints
- type FileReason
- type History
- type LogEntry
- type Metrics
- type Notebook
- func (n *Notebook) Commit(ctx context.Context, message string) (Result, error)
- func (n *Notebook) Log(ctx context.Context, limit int) (History, error)
- func (n *Notebook) Metrics() *Metrics
- func (n *Notebook) Pull(ctx context.Context) (Result, error)
- func (n *Notebook) ReadOnlyPaths() []string
- func (n *Notebook) Status(ctx context.Context) (Status, error)
- func (n *Notebook) WritablePaths() []string
- type Reason
- type RecoveryReport
- type RemoteAccepted
- type Result
- type Status
- type Workspace
Constants ¶
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.
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.
const DefaultRetainedCheckpoints = 1
DefaultRetainedCheckpoints is the application-resolved retention count: the number of previous checkpoint generations kept in addition to the active generation.
const DefaultRetryLimit = 8
DefaultRetryLimit is the application-resolved CAS retry bound.
const MaxMessageBytes = 16384
MaxMessageBytes is the notes_commit message byte bound (architecture/product-contract.md). The MCP schema advertises the same value.
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).
const ReadOnlyListSeparator = ", "
ReadOnlyListSeparator joins read-only entries on every surface that lists them.
Variables ¶
This section is empty.
Functions ¶
func LoggerFrom ¶
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
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 ¶
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.
type BackoffWaiter ¶
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.
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 (*Notebook) Commit ¶
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
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) Pull ¶
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
ReadOnlyPaths returns the normalized, sorted read-only entries; never nil.
func (*Notebook) Status ¶ added in v1.0.0
Status compares the visible directory with the accepted state.
func (*Notebook) WritablePaths ¶ added in v0.1.19
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
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.