artifacts

package
v0.10.8 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package artifacts centralizes filesystem mechanics shared by approach artifact stores. Domain stores still own their JSON schema, validation, and listing semantics.

Index

Constants

View Source
const (
	DirPerm  os.FileMode = 0o700
	FilePerm os.FileMode = 0o600
)

Variables

This section is empty.

Functions

func AcquireFileLock

func AcquireFileLock(path, label string, timeout time.Duration) (func(), error)

AcquireFileLock acquires an exclusive advisory lock with a bounded retry window. The lock parent directory remains the caller's responsibility.

func AcquireFileLockNoFollow added in v0.10.0

func AcquireFileLockNoFollow(path, label string, timeout time.Duration) (func(), error)

AcquireFileLockNoFollow opens a lock file without following its final path component and requires a regular file before acquiring the advisory lock.

func AllocateTimestampedID

func AllocateTimestampedID(opts IDOptions) (string, error)

AllocateTimestampedID returns a timestamp+slug ID that does not already have a record directory in the configured collection.

func CollectionDir

func CollectionDir(root, collection string) string

CollectionDir returns root/<collection>.

func DefaultRoot

func DefaultRoot() (string, error)

DefaultRoot returns the shared approach artifact root used by sessions, plans, and flows.

A development build defaults to its own root (`approach-dev`) so it cannot silently migrate the database a released build owns. Only the *default* changes: an explicit --state-root, the APPROACH_*_STATE_ROOT variables, and [sessions].root are untouched, so Flow-launched agents — which always receive an explicit root — are unaffected.

The blast radius is wider than Flows. artifacts.DefaultRoot backs the session store, the plan store, and the flow store alike, so a development build moves its session history and plan list along with its Flow list. That is the correct outcome — development state should be isolated as a unit rather than split across two roots — but it is a user-visible change, not just "an empty Flow list on first run".

func EnsureCollection

func EnsureCollection(root, collection string) error

EnsureCollection creates and secures root/<collection>.

func EnsureRecordDir

func EnsureRecordDir(root, collection, id string) (string, error)

EnsureRecordDir creates and secures root/<collection>/<id>.

func IsSafeID

func IsSafeID(id string) bool

IsSafeID reports whether id can be used as one artifact path segment.

func NormalizePhaseID

func NormalizePhaseID(id string) string

NormalizePhaseID canonicalizes a phase identifier so superficially different spellings of the same logical phase (case or surrounding whitespace) compare equal and upsert in place instead of duplicating rows.

func RecordDir

func RecordDir(root, collection, id string) string

RecordDir returns root/<collection>/<id>.

func ReleaseDefaultRoot added in v0.10.5

func ReleaseDefaultRoot() (string, error)

ReleaseDefaultRoot is the root a released build defaults to, whatever this build is. It exists so the migration guard can test for "this development build is about to migrate the release-owned database" by exact path equality rather than by a broader rule that would also fire in every temp directory.

func RequireAbsoluteRoot

func RequireAbsoluteRoot(root, storeName string) (string, error)

RequireAbsoluteRoot returns the same root when it is absolute.

func ResolveCanonicalRoot added in v0.10.5

func ResolveCanonicalRoot(root, label string) (string, error)

ResolveCanonicalRoot resolves root through symlinks and returns the canonical path without creating, chmod'ing, or asserting anything about it.

It exists for read-only opens. SecureCanonicalRoot's two chmods succeed on a user-owned 0755 or 0500 directory, so calling it from a reader would silently tighten the root to 0700 — repairing the very directory state a diagnostic is supposed to report, and giving `approach serve` a different effect on the same directory depending on how its root was spelled. A reader reports the mode it found; only a writer or a migrator repairs it.

func SecureCanonicalRoot added in v0.10.5

func SecureCanonicalRoot(root, label string) (string, error)

SecureCanonicalRoot creates root when absent, forces it to 0700, resolves it through symlinks, and returns the canonical path only when the resolved directory is genuinely owner-only. label names the root in every error so a caller's diagnostics read the way its own messages always did.

Every consumer that stores executable or authoritative state under an approach root shares this check: the flow database is written through it, and the pinned launch binary is executed out of it.

func Slug

func Slug(text, fallback string) string

Slug lowercases text, keeps [a-z0-9-], collapses separator runs, trims boundary dashes, caps length, and falls back when nothing usable remains.

func StageReplace added in v0.10.6

func StageReplace(path string) (commit, rollback func() error, err error)

StageReplace snapshots path so a replacement can be published without buffering the previous contents or leaving the canonical name empty. The replacement must be a new inode (WriteFileAtomic); an in-place truncate would mutate a hardlinked backup. commit deletes the backup; rollback restores it by renaming over the canonical path, or removes path when there was no previous file.

Backups are for in-process rollback only. A crash after WriteFileAtomic publishes the replacement has the same torn window as WriteFileAtomic alone: metadata remains the publication marker. A leftover sibling named path+".prev" is removed the next time StageReplace runs.

func TimestampedIDCandidates added in v0.10.0

func TimestampedIDCandidates(opts IDOptions) []string

TimestampedIDCandidates returns the exact candidate sequence used by AllocateTimestampedID. Storage backends use it to preserve file-allocation naming and exhaustion behavior while checking uniqueness in their own authority.

func WriteFileAtomic

func WriteFileAtomic(path string, data []byte) error

WriteFileAtomic replaces path with data using a temporary sibling file.

func WriteFileAtomicFromReader

func WriteFileAtomicFromReader(path string, input io.Reader) error

WriteFileAtomicFromReader streams input to path using a temporary sibling file, then renames it into place with restrictive permissions.

func WriteFileAtomicFunc

func WriteFileAtomicFunc(path string, write func(io.Writer) error) error

WriteFileAtomicFunc lets callers generate file contents into an atomic temporary file without buffering the whole artifact in memory.

Types

type IDOptions

type IDOptions struct {
	Root         string
	Collection   string
	Title        string
	FallbackSlug string
	Kind         string
	Now          time.Time
	MaxAttempts  int
}

IDOptions configures timestamped artifact ID allocation.

Jump to

Keyboard shortcuts

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