loader

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Aug 23, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package loader reads claim YAML files off disk (from a project's configured claims_dir) into model.Claim values, and writes individual claims back to their source file. It is the one place in the engine that touches the filesystem for claim content; every other package works purely in memory against []model.Claim.

Index

Constants

This section is empty.

Variables

View Source
var ErrClaimFileChanged = errors.New("claim file changed on disk since it was loaded")

ErrClaimFileChanged is returned by SaveClaimIfUnchanged when the target claim file's on-disk content no longer matches the snapshot the caller captured (via CaptureClaimFileToken) before mutating it — i.e. some other writer changed the file underneath this load->mutate->save sequence. It is a distinct, matchable sentinel (errors.Is) so a caller such as a future HTTP server can map it to a 409 Conflict / "reload, this claim changed" response rather than a generic 500.

View Source
var ErrClaimNotRoundTrippable = errors.New("claim would not round-trip through YAML; refusing to write a store-bricking file")

ErrClaimNotRoundTrippable is returned by SaveClaim/SaveClaimIfUnchanged when the claim's marshaled YAML would not decode back into the same claim — i.e. writing it would leave a file the very next LoadClaims cannot parse, bricking the whole claims dir (every loader-backed command then fails to load anything). The canonical trigger is a comment/reply body whose leading whitespace drives yaml.v3 v3.0.1 to emit a block scalar it cannot re-parse (a leading newline, a leading blank line, or a leading whitespace-only line). The save path REFUSES such a write and returns this matchable sentinel instead of persisting the store-bricking file, so no writer — present or future — can brick the store, even if it skipped the higher-level body validation.

Functions

func CommentBodyRoundTrips added in v0.2.0

func CommentBodyRoundTrips(body string) bool

CommentBodyRoundTrips reports whether body can be stored as a comment (or reply) body and read back BYTE-EXACT through the very marshal + strict-decode the save-time guard (verifyRoundTrip) applies. It is the shared, round-trip- ACCURATE pre-check the comments input boundary (comments.validateBody) uses so it rejects EXACTLY the bodies the save-time guard would refuse — matching that guard BY CONSTRUCTION rather than by a hand-rolled leading-whitespace heuristic, which both MISSED store-bricking bodies (a first CONTENT line that itself begins with a tab or space indent, e.g. "\tcode\nmore" or " code\n more") and FALSE-REJECTED bodies that actually round-trip (" \n…", "\r\n…", a NBSP/NEL/VT/FF-led first line).

yaml.v3 v3.0.1 emits certain leading-whitespace bodies as a block scalar it then cannot re-parse (a bare leading newline, a leading blank/whitespace-only line) or re-parses lossily (a space-indented first content line, whose block indent indicator is stripped on read); persisting one bricks the whole claims dir on the next LoadClaims. A minimal claim carrying body as its single comment body is marshaled and strict-decoded here; the reply-body nesting round-trips identically (verified empirically against v3.0.1), so probing the thread-body position alone is faithful. Empty/whitespace-only bodies are not this function's concern (comments.validateBody rejects those as ErrEmptyBody first); it is called only on bodies that carry real content.

func FindByID

func FindByID(claims []model.Claim, id string) (model.Claim, bool)

FindByID returns the claim with the given id, if present.

func LoadClaims

func LoadClaims(dir string) ([]model.Claim, error)

LoadClaims recursively reads every *.yaml/*.yml file under dir, strictly decoding each into a model.Claim (unknown fields are a hard error, same discipline as internal/config). Each claim's SourcePath is set to the file it was loaded from. The result is sorted by SourcePath so callers get deterministic ordering regardless of directory-walk order.

A dir that does not exist is a hard error: claims_dir is required project configuration, not an optional feature.

func SaveClaim

func SaveClaim(c model.Claim) error

SaveClaim writes c back to its SourcePath as YAML. It is used by the lock/unlock/reaudit-apply flows, which are the only paths that mutate a claim's on-disk representation.

The write is atomic (temp file in the same directory, then rename) rather than a direct os.WriteFile. os.WriteFile truncates the destination before writing its new bytes, leaving a window where the file is empty or partially written; a concurrent LoadClaims (every "dossierx lock" invocation starts by loading the *entire* claims_dir, including files other in-flight processes are saving) can land its read inside that window and see a truncated file, failing YAML decode with a bare EOF. Writing to a sibling temp file and renaming it into place means any concurrent reader only ever observes the old complete file or the new complete file, never a partial one — os.Rename is atomic within a single filesystem/directory.

SaveClaim serves TWO modes, and the difference is not incidental: it is also the file-CREATE path (dossierx claim new, which refuses to run if the file already exists), so it cannot assume a document to mutate. An absent SourcePath means CREATE and emits a fresh document from the struct; an existing one means MUTATE and goes through renderClaim, which rewrites only the top-level keys whose value actually changed. Both modes end in the same verifyRoundTrip + atomicWriteFile.

func SaveClaimIfUnchanged added in v0.2.0

func SaveClaimIfUnchanged(c model.Claim, want ClaimFileToken) error

SaveClaimIfUnchanged is SaveClaim guarded by an optimistic-concurrency check: it writes c back to its SourcePath only if that file's current on-disk content still matches want (the token the caller captured at load time via CaptureClaimFileToken). If the file changed underneath, it writes nothing and returns ErrClaimFileChanged.

This is a best-effort backstop layered UNDER the project-wide claims sentinel (see cmd/dossierx's claimsSentinelPath), not a replacement for it. The sentinel serializes every cooperating claim-file writer; this check additionally catches an out-of-band edit (a text editor, or a future writer that forgets the sentinel) — the one class of change the sentinel alone cannot see. The re-read/compare/write is deliberately not a single atomic transaction (a change slipped in after the compare but before the rename would be missed), which is precisely why it BACKS the sentinel rather than standing in for it.

Types

type ClaimFileToken added in v0.2.0

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

ClaimFileToken is an opaque snapshot of a claim file's on-disk content at load time, handed back to SaveClaimIfUnchanged so it can refuse to overwrite a file that changed underneath the caller. It records the file's byte length and a content hash; a content hash (rather than only mtime+size) is used deliberately, so the check is robust to same-size edits and to coarse filesystem mtime granularity — a hash mismatch is exactly "the bytes differ", with no timestamp-resolution guesswork.

func CaptureClaimFileToken added in v0.2.0

func CaptureClaimFileToken(path string) (ClaimFileToken, error)

CaptureClaimFileToken snapshots path's current on-disk content for a later SaveClaimIfUnchanged optimistic-concurrency check. Call it right after loading the claim (inside the same claims-sentinel critical section) so the token reflects the bytes the caller is about to mutate. A file that cannot be read is an error, not an empty token: callers only ever snapshot a claim they just loaded, so an unreadable file there is a real failure, never the expected "fresh, absent store" case LoadStore/LoadFlagStore tolerate.

Jump to

Keyboard shortcuts

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