Documentation
¶
Overview ¶
Package cairn is the mechanical half of nova-cairn, the optional checkpoint tool from nova-tools #248.
It opens session records, appends the friend's exact words with a real clock stamp, stable identifiers and source pointers, and builds a bounded index and coverage ledger over them — and nothing else. There is no seal, no consume, no delete, no grading, no consolidation and no liveness inference here: those are separate explicit choices the caller never gets by accident. The store is plain files under a caller-named directory, so a note is durable (fsync) before success is reported, independently of Redis and of any remote: local persistence is reported separately from remote publication, and neither implies replicated durability.
Layout under the store directory:
sessions/<session>.md the readable record; header by convention only entries/<session>/<id>.json one file per entry, the source of truth log.jsonl append-only event log feeding the ledger
A store that keeps ONE MARKDOWN FILE PER SESSION directly under it -- <session>.md, the shape a friend appending by hand already has -- is read as it stands. `open` on such a record is a no-op and `append` lands a dated `## <stamp> — <entry>` section at the end of the file, with no entries/ directory, no log and no index appearing beside it. The tool adapts to the store; the store is never converted to suit the tool.
Each entry file is written atomically via internal/atomicfile (exclusive temporary file beside target, explicit mode, fsync to media, atomic rename), so a retry after an interrupted append finishes the pointer without duplicating the entry and without touching other writers' files. Stale temporary files are never indexed.
Index ¶
- Constants
- func EntryText(store, session, id string) (string, error)
- func Open(store, session, source string, now time.Time, publish string) error
- func SessionSource(store, session string) (string, error)
- type AppendResult
- type ConflictError
- type IndexRow
- type Ledger
- type NotFoundError
- type ReceiptInfo
Constants ¶
const ( PublishNever = "never" PublishManual = "manual" PublishDeferred = "deferred" PublishImmediate = "immediate" )
Publish policies name who publishes a checkpoint and when. This slice implements no transport: every append reports Published=false and the policy travels with the entry, so a later explicit act can carry it.
Variables ¶
This section is empty.
Functions ¶
func EntryText ¶
EntryText returns the friend's words byte-for-byte: the store never rewrites, grades or consolidates what was appended.
func Open ¶
Open starts (or re-starts, idempotently) one session record. Concurrent records coexist: opening a second session never touches the first.
func SessionSource ¶
SessionSource reads back the --source the session was opened with, from the open record in log.jsonl. The session file's header is convention only and is never parsed, so the log is where the pointer is read from.
CORRUPT PROVENANCE NEVER READS AS NONE. An absent log is a store with no open records in it (a bench store, or one opened before the log carried a source) and answers "" with no error, as does an open record with no source key. A log that exists and cannot be read is an error, and so is a line that may be this session's open record and does not decode as one: a line naming the session and the open event that is not valid JSON, or whose fields are not strings. Answering "" for either would let an append file source=- over a pointer that open recorded.
Types ¶
type AppendResult ¶
type AppendResult struct {
Persisted bool
Published bool
Policy string
// Source is the pointer the entry carries: its own --source, or the
// session's when the append named none.
Source string
Duplicate bool
}
AppendResult separates what this slice guarantees from what it does not: Persisted is true once the note is fsync-durable on this machine; Published names the remote, which this slice never touches.
func Append ¶
func Append(store, session, id, text, source string, now time.Time, publish string) (AppendResult, error)
Append stores the friend's exact prose under a stable entry id with a real clock stamp and source pointers. A retry of the same request succeeds with Duplicate=true and no second entry; the same id with different prose is a conflict, never an overwrite. Offline use succeeds: the result carries persisted=true with published=false, because local durability never waited for the remote.
type ConflictError ¶
type ConflictError struct{ Msg string }
ConflictError is a retry that must not silently win: the same entry id carrying different prose. It is the caller's failure (exit 1), not a crash.
func (*ConflictError) Error ¶
func (e *ConflictError) Error() string
type IndexRow ¶
IndexRow is one mechanical row of the section/entry index.
func Index ¶
Index builds the bounded section/entry index mechanically from the stored entries: no narrative is recopied, work events are linked by session/entry pointers, and a stale *.tmp from an interrupted append is never a row. session "" indexes every record; max <= 0 lifts the ceiling and returns everything with no MORE standing for the rest.
type Ledger ¶
Ledger is the coverage count, derived from the store rather than remembered: how many session records exist and how many entries they hold.
type NotFoundError ¶
type NotFoundError struct{ Msg string }
NotFoundError is a use-before-open or a receipt for nothing stored.
func (*NotFoundError) Error ¶
func (e *NotFoundError) Error() string
type ReceiptInfo ¶
type ReceiptInfo struct {
Session string
ID string
Stamp time.Time
Source string
Bytes int
Policy string
Persisted bool
Published bool
}
ReceiptInfo is what receipt and index read back: the stamp, the pointer, the size, and the same persisted/published split the append reported.
func Receipt ¶
func Receipt(store, session, id string) (ReceiptInfo, error)
Receipt names what was preserved for one entry: its stamp, pointers and size, with the persisted/published split repeated so a reader never has to infer the remote from the local.