Documentation
¶
Overview ¶
Package cairn is the store behind nova-cairn: session records holding a caller's exact words, each with a clock stamp, a stable identifier and a source pointer, and a bounded index and coverage count read back from them.
It does nothing else. There is no seal, no consume, no delete, no grading, no consolidation and no liveness inference: those are separate choices a caller never gets by accident. The store is plain files under a directory the caller names, and a write is synced to disk before success is reported, with no Redis and no remote: local persistence is reported apart from remote publication, and neither implies the other.
A store holds a session in one of two shapes, and both are read as they stand; a store is never converted to suit the tool.
The nested shape is the one open creates:
sessions/<session>.md the readable record; its header is convention only entries/<session>/<id>.json one file per entry, the source of truth log.jsonl append-only event log: the open records and the appends
The flat shape is one markdown file per session directly under the store, <store>/<session>.md, kept by hand or by another tool. The file is the record: an append lands a dated `## <stamp> — <entry>` section at its end, and no entries/ directory, log or index appears beside it. The nested record wins when a store holds both for one session.
Each entry file is written atomically through pkg/atomicfile (an exclusive temporary file beside the target, an explicit mode, fsync, then rename), so a retry after an interrupted append finishes the pointer line without duplicating the entry and without touching other writers' files. A stale temporary file is never indexed.
Index ¶
- Constants
- Variables
- func EntryText(store, session, id string) (string, error)
- func Open(store, session, source string, now time.Time, publish string) error
- func ValidID(s string) bool
- func ValidPublish(p string) bool
- type AppendResult
- type ConflictError
- type IndexRow
- type Ledger
- type NotFoundError
- type OpenRecord
- type ReceiptInfo
Constants ¶
const ( PublishNever = "never" PublishManual = "manual" PublishDeferred = "deferred" PublishImmediate = "immediate" // PublishUnknown is what a flat record reports: its format stores no policy. PublishUnknown = "unknown" )
Publish policies name who publishes a checkpoint and when. This package implements no transport: the tool prints published=false for every append and receipt, and the policy travels with the entry, so a later explicit act can carry it.
const IDRule = "an id names exactly one file or directory of that name inside the store, on every platform: " +
"nonempty, at most 128 bytes, no whitespace or control characters, none of / \\ : * ? \" < > |, " +
"not only dots, no `..`, no trailing dot, and not a Windows device name (CON, PRN, AUX, NUL, COM1-9, LPT1-9, with or without an extension)"
IDRule is what ValidID asks of a session or entry id, for a refusal to quote.
Variables ¶
var Policies = []string{PublishNever, PublishManual, PublishDeferred, PublishImmediate}
Policies is the publication policies a caller may choose, in the order help names them.
Functions ¶
func EntryText ¶
EntryText returns the stored words of a nested entry byte for byte. A flat record returns the section body in the form its reader indexes.
func Open ¶
Open starts one session record, or re-opens it: a re-open naming the recorded policy (and the recorded source, when it names one) changes nothing, and one naming another is a conflict. Concurrent records coexist: opening a second session never touches the first.
tla/CairnStore.tla Open
func ValidID ¶
ValidID keeps an id one token on every output line and exactly one file or directory of that name inside the store: an id becomes a path component (sessions/<id>.md, <store>/<id>.md, entries/<id>/, entries/<s>/<id>.json), so every id that a path would read as something else is refused. "." would make entries/./ the entries directory itself, so an append lands where no index looks; ".." and any id holding it climb out; an id of only dots is one of those; a trailing dot or blank is stripped by Windows, folding "s." into "s"; a separator splits the id into two components; a device name or a Windows-forbidden character names no file at all there.
func ValidPublish ¶
ValidPublish reports whether p is one of Policies.
Types ¶
type AppendResult ¶
type AppendResult struct {
Stamp time.Time // the stamp actually stored, including on a duplicate retry
Persisted 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 package guarantees from what it does not: Persisted is true once the words are fsync-durable on this machine (false only for a plan, PlanAppend, of words not yet stored); the tool prints the remote fact, published=false, apart from it.
func Append ¶
func Append(store, session, id, text, source string, now time.Time, publish string) (AppendResult, error)
Append stores the caller's exact words under a stable entry id with a clock stamp and a source pointer. A retry of the same request succeeds with Duplicate=true and no second entry; the same id with other words is a conflict, never an overwrite. Offline use succeeds: the result carries Persisted=true and the tool prints published=false, because local durability never waits for a remote.
An empty source carries the session's, and an empty publish the session's recorded policy: open named where the record points back to and who publishes it, and an append that names neither came from the same place under the same policy. Both are read before any write, so a log that cannot be read refuses with nothing written.
func PlanAppend ¶
func PlanAppend(store, session, id, text, source string, now time.Time, publish string) (AppendResult, error)
PlanAppend is Append with nothing written: every check Append makes, the same error, and the result the write would report, with Persisted false unless the words are already stored (a duplicate).
type ConflictError ¶
type ConflictError struct{ Msg, Remedy string }
ConflictError is a write that must not silently win: the same entry id carrying different words, or a re-open naming another policy or source. It is the caller's failure (exit 1), not a crash. Remedy is the command to run next.
func (*ConflictError) Error ¶
func (e *ConflictError) Error() string
type IndexRow ¶
IndexRow is one row of the entry index.
func Index ¶
Index builds the entry index from the stored entries: no words are recopied, entries are linked by session and entry id, and a stale *.tmp from an interrupted append is never a row. The sessions the store holds, from sessions/<id>.md and a flat <store>/<id>.md, come back from the same walk, so index prints and counts every session, an empty one included, without reading the store a second time. session "" indexes every record; a named session is the only one returned. It returns the rows, the session ids and the total.
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, Remedy string }
NotFoundError is a use before open, or a read of an entry nothing stored. Remedy is the command to run next.
func (*NotFoundError) Error ¶
func (e *NotFoundError) Error() string
type OpenRecord ¶
OpenRecord is what a session's open recorded in log.jsonl: its source, its publication policy and the opened stamp. Found is false when the log holds no open record for the session (a flat store, or no log at all). Opened is zero when the record names no stamp. Index prints the policy and the stamp on the session line (docs/SPEC-CAIRN.md, the index verb).
func PlanOpen ¶
PlanOpen is Open with nothing written: every check Open makes and the same error, so a dry run refuses what the real run would. It returns the open record that would stand after the open (Found false for a flat record, which records none).
func ReadOpen ¶
func ReadOpen(store, session string) (OpenRecord, error)
ReadOpen reads the session's open record from log.jsonl. The session file's header is convention only and is never parsed, so the log is where the pointer and the policy are read from.
Corrupt provenance never reads as none. An absent log answers Found=false with no error, as does a log with no open record for the session. 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 "none" for either would let an append file source=- over a pointer open recorded.
type ReceiptInfo ¶
type ReceiptInfo struct {
Session string
ID string
Stamp time.Time
Source string
Bytes int
Policy string
Persisted bool
}
ReceiptInfo is what receipt and index read back: the stamp, the pointer, the size, and the persisted fact the append reported; the tool prints published=false beside it.
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 fact repeated and published=false printed beside it, so a reader never has to infer the remote from the local.