globalstore

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package globalstore implements the user-level, content-addressed skill store under the gskill home (~/.gskill/store). Objects are immutable content directories keyed by their canonical integrity.HashDir hash, with a schema-versioned metadata record alongside. Admission stages content under an object lock and promotes it with an atomic rename; verification fully re-hashes content before any activation and quarantines corruption fail-closed. Deletion happens only through the conservative mark-and-sweep garbage collector, never as a side effect of a project operation.

Index

Constants

This section is empty.

Variables

View Source
var ErrObjectNotFound = errors.New("store object not found")

ErrObjectNotFound reports a store lookup for a key with no admitted object.

View Source
var ErrSchemaVersion = errors.New("unsupported metadata schema version")

ErrSchemaVersion reports a metadata record written by a different gskill generation. The object itself may be perfectly healthy — callers must treat this as "cannot read", never as corruption.

Functions

func WriteMetadata

func WriteMetadata(path string, meta Metadata) error

WriteMetadata writes meta atomically and deterministically with owner-only permissions.

Types

type FetchFunc

type FetchFunc func(source, commit, dest string) error

FetchFunc downloads the exact commit of a source into dest. The installer wires this to the git runner; tests substitute stubs.

type FindingKind

type FindingKind string

FindingKind classifies one store-scan problem.

const (
	FindingCorrupted       FindingKind = "corrupted"
	FindingMalformed       FindingKind = "malformed"
	FindingInvalidMetadata FindingKind = "invalid-metadata"
	FindingUnsafePerms     FindingKind = "unsafe-permissions"
	FindingStrayStaging    FindingKind = "stray-staging"
)

Store-scan finding kinds (FR-022).

type GCCandidate

type GCCandidate struct {
	Key       string
	SizeBytes int64
	// Skill and Version come from the first recorded origin (display only).
	Skill    string
	Version  string
	LastUsed time.Time
}

GCCandidate is one object eligible for deletion.

type GCOptions

type GCOptions struct {
	// GracePeriod is the minimum object age (from max(createdAt, lastUsedAt))
	// before an unreferenced object becomes deletable. Zero uses 30 days.
	GracePeriod time.Duration
	// Apply performs deletions; false (the default) is a dry run.
	Apply bool
	// MarkRefs returns the content keys referenced by live projects —
	// lockfiles, state files, and active links, typically fed by the advisory
	// registry. It is re-invoked under the GC lock before any deletion so a
	// stale pre-lock scan can never justify a delete. A nil MarkRefs marks
	// nothing (degraded mode: only pins, locks, and the grace period protect).
	MarkRefs func() map[string]bool
}

GCOptions configures a garbage-collection run (FR-024/025).

type GCReport

type GCReport struct {
	Candidates       []GCCandidate
	ReclaimableBytes int64
	// Deleted lists the keys actually removed (apply mode only).
	Deleted []string
	// Skipped lists keys that became protected between marking and deletion
	// (gc-conflict: lock held, re-marked, or re-pinned) — reported, never an
	// error (error contract).
	Skipped []string
	// Degraded reports that no reference marking was available.
	Degraded bool
}

GCReport summarizes a GC run.

type Locker

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

Locker acquires the gskill lock taxonomy under <home>/locks: per-object admission locks, per-project mutation locks, the GC lock, and the registry lock. A contended lock waits up to the configured timeout, emitting a visible waiting notice, then fails with the lock-failure exit code.

func NewLocker

func NewLocker(h *home.Home, timeout time.Duration, notice io.Writer) *Locker

NewLocker returns a Locker with the given acquisition timeout. notice receives the waiting message (nil silences it); commands pass stderr.

func NewLockerWithNotice

func NewLockerWithNotice(h *home.Home, timeout time.Duration, notice io.Writer, noticeAfter time.Duration) *Locker

NewLockerWithNotice is NewLocker with a custom notice threshold (tests use a short one).

func (*Locker) GCLockPath

func (l *Locker) GCLockPath() string

GCLockPath returns the lock file guarding a whole GC apply run.

func (*Locker) LockGC

func (l *Locker) LockGC(ctx context.Context) (*fsutil.Lock, error)

LockGC takes the exclusive garbage-collection lock.

func (*Locker) LockObject

func (l *Locker) LockObject(ctx context.Context, key string) (*fsutil.Lock, error)

LockObject takes the exclusive lock for one store object.

func (*Locker) LockRegistry

func (l *Locker) LockRegistry(ctx context.Context) (*fsutil.Lock, error)

LockRegistry takes the exclusive project-registry lock.

func (*Locker) ObjectLockPath

func (l *Locker) ObjectLockPath(key string) string

ObjectLockPath returns the lock file guarding one store object.

func (*Locker) RegistryLockPath

func (l *Locker) RegistryLockPath() string

RegistryLockPath returns the lock file guarding project-registry writes.

type Metadata

type Metadata struct {
	SchemaVersion int       `json:"schemaVersion"`
	ContentHash   string    `json:"contentHash"`
	SizeBytes     int64     `json:"sizeBytes"`
	CreatedAt     time.Time `json:"createdAt"`
	LastUsedAt    time.Time `json:"lastUsedAt,omitzero"`
	Origins       []Origin  `json:"origins"`
}

Metadata is the descriptive, mutable-under-lock record beside an object's immutable content. It never affects object identity.

func ReadMetadata

func ReadMetadata(path string) (Metadata, error)

ReadMetadata loads and validates a metadata record, rejecting unknown schema versions with a clear error (forward compatibility, Constitution II).

type Object

type Object struct {
	// Key is the canonical content key ("sha256:<hex>").
	Key string
	// Path is the object directory.
	Path string
	// ContentPath is the immutable content directory.
	ContentPath string
	// Metadata is the object's descriptive record.
	Metadata Metadata
}

Object is an admitted store object.

type Origin

type Origin struct {
	SourceType string `json:"sourceType,omitempty"`
	Source     string `json:"source,omitempty"`
	SkillPath  string `json:"skillPath,omitempty"`
	Version    string `json:"version,omitempty"`
	Ref        string `json:"ref,omitempty"`
	Commit     string `json:"commit,omitempty"`
}

Origin records one known source of an object's content. Origins are descriptive only; identical content from different sources shares one object. Credentials and tokens are never recorded.

func MergeOrigins

func MergeOrigins(existing []Origin, extra ...Origin) []Origin

MergeOrigins returns existing with extra merged in, sorted by source (then skill path, then commit) and de-duplicated. The inputs are not mutated.

type ScanFinding

type ScanFinding struct {
	Kind FindingKind
	// Key is the affected object's content key (empty for stray staging).
	Key string
	// Detail is a human-readable description.
	Detail string
	// Expected and Actual carry the hash pair for corruption findings.
	Expected string
	Actual   string
	// UsedBy lists the known projects referencing the object.
	UsedBy []string
	// Path is the offending filesystem path.
	Path string
}

ScanFinding is one problem the store scan found.

type ScanOptions

type ScanOptions struct {
	// UsedBy resolves the projects known to reference an object key; nil
	// leaves UsedBy empty (the scan stays registry-independent).
	UsedBy func(key string) []string
	// StrayAge is the minimum age of a tmp/ entry to report as abandoned
	// staging; zero uses one hour.
	StrayAge time.Duration
}

ScanOptions configures VerifyStore.

type ScanReport

type ScanReport struct {
	Checked  int
	Healthy  int
	Findings []ScanFinding
}

ScanReport summarizes a store-wide verification (FR-022).

type Store

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

Store is the global content-addressed store rooted in a gskill home.

func New

func New(h *home.Home) *Store

New returns a Store over h. The home layout must already be ensured.

func (*Store) Admit

func (s *Store) Admit(ctx context.Context, expectedKey, srcDir string, origin Origin) (reused bool, err error)

Admit stores srcDir's content under expectedKey, recording origin. It is the only way content enters the store (FR-006):

  1. take the object lock and re-check existence (a concurrent admitter may have won);
  2. stage into an owner-only temp dir under <home>/tmp;
  3. validate the content (path traversal, escaping symlinks) — fetched content is never executed;
  4. re-hash the staged copy and verify it equals expectedKey, failing closed on mismatch with nothing admitted;
  5. write metadata, then promote content with an atomic same-filesystem rename.

An object that already exists is reused: its metadata gains origin and the call reports reused=true.

func (*Store) ContentPath

func (s *Store) ContentPath(key string) string

ContentPath returns the immutable content directory for a content key.

func (*Store) GC

func (s *Store) GC(ctx context.Context, opts GCOptions) (GCReport, error)

GC runs conservative mark-and-sweep over the store (FR-024/025). An object is a candidate only when it is unreferenced, unpinned, metadata-valid, safely owned, and older than the grace period. Dry runs only report; apply mode takes the global GC lock, re-marks, then re-checks each candidate under its object lock immediately before deletion (FR-031).

func (*Store) Has

func (s *Store) Has(key string) bool

Has reports whether an admitted object exists for key. Presence means the content directory exists; integrity is established separately by VerifyObject.

func (*Store) Home

func (s *Store) Home() *home.Home

Home returns the store's home.

func (*Store) ListKeys

func (s *Store) ListKeys() ([]string, error)

ListKeys returns the content keys of every object directory in the store, in no particular order. Malformed layouts are included (their key is the directory-derived name) so verification can report them.

func (*Store) MetadataPath

func (s *Store) MetadataPath(key string) string

MetadataPath returns the metadata file for a content key.

func (*Store) ObjectPath

func (s *Store) ObjectPath(key string) string

ObjectPath returns the object directory for a content key, whether or not it exists.

func (*Store) Open

func (s *Store) Open(key string) (*Object, error)

Open loads the object for key, returning ErrObjectNotFound when it is not admitted and a metadata error when its record is unreadable or from an unsupported schema.

func (*Store) Pin

func (s *Store) Pin(key string) error

Pin exempts the object for key from garbage collection (FR-026). Pinning an unknown object is an error.

func (*Store) Pinned

func (s *Store) Pinned(key string) bool

Pinned reports whether key is pinned.

func (*Store) Pins

func (s *Store) Pins() ([]string, error)

Pins lists every pinned content key, sorted.

func (*Store) Quarantine

func (s *Store) Quarantine(key string) error

Quarantine moves the object for key out of the store into <home>/quarantine/<key>-<timestamp>/ so it can never be activated, while preserving the evidence for repair and inspection (FR-021).

func (*Store) RecordOrigin

func (s *Store) RecordOrigin(ctx context.Context, key string, origin Origin) error

RecordOrigin merges origin into an admitted object's metadata under the object lock. Content is never touched (FR-004).

func (*Store) Repair

func (s *Store) Repair(ctx context.Context, key string, fetch FetchFunc) error

Repair restores the object for key from its recorded origin (FR-023): it picks a commit-bearing origin, re-fetches exactly that commit into staging, verifies the staged content hashes to key, and atomically replaces the object. It fails — leaving the existing object untouched — when no origin records an exact commit or when the re-fetched content differs: an object is never silently replaced with different content.

func (*Store) Root

func (s *Store) Root() string

Root returns the store root directory (<home>/store).

func (*Store) SetLocker

func (s *Store) SetLocker(l *Locker)

SetLocker overrides the store's locker (timeout, notice writer). A nil locker resets to the default.

func (*Store) TouchLastUsed

func (s *Store) TouchLastUsed(ctx context.Context, key string) error

TouchLastUsed stamps the object's lastUsedAt under the object lock. It is best-effort bookkeeping for GC reporting; content is never touched.

func (*Store) Unpin

func (s *Store) Unpin(key string) error

Unpin removes the GC exemption for key. Unpinning a never-pinned object is a no-op.

func (*Store) VerifyObject

func (s *Store) VerifyObject(key string) error

VerifyObject establishes that the object for key is trustworthy: its metadata parses and matches, and a full re-hash of content/ equals key (clarification #2). On corruption the object is moved to quarantine and a fail-closed integrity error carrying expected and actual hashes is returned (FR-020/021). Missing objects report ErrObjectNotFound.

func (*Store) VerifyStore

func (s *Store) VerifyStore(opts ScanOptions) (ScanReport, error)

VerifyStore scans every object: full content re-hash, metadata validation, malformed-layout detection, unsafe permissions, and abandoned staging directories (FR-022). It reports; it never quarantines or deletes — those are activation's and repair's jobs.

Jump to

Keyboard shortcuts

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