ctxpins

package
v1.0.0-rc.5 Latest Latest
Warning

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

Go to latest
Published: Nov 8, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package ctxpins implements TOFU content pins for global context: one record per rule fragment, or the single canonical AGENTS.md when fragments mode is off. A pin records a content digest; any later mismatch is "pin drift" that persists until a human approves (re-pins) or resets. The deterministic hash is the gate; poisoning findings attached to a pin are advisory decoration and never block projection.

The store is the stackless sibling of pkg/skillpins. It borrows that package's sentinel errors, lock, atomic save, and re-pin conventions, and reuses pkg/pins scanner types. It does not keep a Load-once copy of the pin file: every public method reloads under the file lock, so a CLI write is visible to the API server on its next call. Only hash and status are load-bearing. Document, source, pack, approved reason, findings, and the projected approvals list are derived; an older gridctl rewriting the file drops them without a file-version bump.

Hashes use the same canonical normalization as the projection lockfile (CRLF to LF, trailing newlines trimmed) with a c1: prefix. The lockfile stores sha256:-prefixed digests, so parity is the unprefixed digest. Pin drift is a fact about the source, distinct from client-side stale and drifted projection state.

Index

Constants

View Source
const (
	StatusPinned = "pinned"
	StatusDrift  = "drift"
)

Status values for FragmentPin, matching pkg/pins' vocabulary.

View Source
const (
	SourceLocal = "local"
	SourcePack  = "pack"
)

Source discriminants for FragmentPin.Source. Unknown values load fine and pass through untouched.

View Source
const CanonicalName = "AGENTS.md"

CanonicalName is the reserved record key for the single-file store.

View Source
const FileVersion = "1"

FileVersion is the context pin file version this binary writes and the highest version it accepts. Advertised by `gridctl info --json` as schemas.context_pins.

Variables

View Source
var (
	ErrCorrupt           = errors.New("corrupt context pin file")
	ErrNewerVersion      = errors.New("context pin file written by a newer gridctl")
	ErrNotPinned         = errors.New("fragment is not pinned")
	ErrNoDocument        = errors.New("fragment does not exist")
	ErrHashMismatch      = errors.New("fragment content changed since the reviewed diff")
	ErrReasonRequired    = errors.New("approving a fragment with unresolved findings requires a reason")
	ErrDigestUnavailable = errors.New("fragment content could not be hashed")
)

Sentinel errors. Load's ErrCorrupt and ErrNewerVersion follow the pkg/skillpins contract: a corrupt file is reported and not rewritten; ErrNewerVersion must never be papered over. The rest are decision errors so the CLI and API can map them without string matching.

Functions

func CompositeHash

func CompositeHash(hash string) string

CompositeHash folds one fragment hash into the approval-binding fingerprint shared with skill pins. --expect values are comparable across kinds because both call skillpins.CompositeHash.

func DeployedScanConfig

func DeployedScanConfig() (bool, []string)

DeployedScanConfig reads gateway.security.schema_pinning scan and scan_ignore from the single deployed stack. Zero stacks, more than one stack, or a stack that does not load all mean the defaults: scan on, no ignores. A nil Scan pointer is on. This never returns an error; stack resolution failure must not fail a ctx command.

func Hash

func Hash(raw []byte) string

Hash returns the pin digest of raw fragment bytes. Normalization matches the projection lockfile's canonical content hash (CRLF to LF, trailing newlines trimmed) so stale and pin drift agree on whether content changed.

func PinStatus

func PinStatus(pin *FragmentPin, doc Document) string

PinStatus reports pinned, drift, or unpinned for one document against an optional stored record. It does not write. An unreadable pinned document is drift.

Types

type ApproveRequest

type ApproveRequest struct {
	ExpectedHash     string
	Reason           string
	Evidence         []string
	ApprovedBy       string
	ApprovedBySource string
}

ApproveRequest is one explicit fragment approval. ExpectedHash, when non-empty, must match the current composite hash. Evidence is stored as returned by pinapprovals.ValidateReferences, not as the caller supplied it.

type Diff

type Diff struct {
	Name        string         `json:"name"`
	OldHash     string         `json:"old_hash"`
	NewHash     string         `json:"new_hash"`
	OldDocument string         `json:"old_document,omitempty"`
	NewDocument string         `json:"new_document,omitempty"`
	Findings    []pins.Finding `json:"findings"`
}

Diff describes how a fragment moved since its pin. Findings are advisory results for the new content, computed at verify time. Findings is never nil when Diff itself is non-nil.

func (*Diff) DocumentChanged

func (d *Diff) DocumentChanged() bool

DocumentChanged reports whether the pinned digest moved.

type DocSource

type DocSource struct {
	Docs []Document
}

DocSource is a Source backed by an already-loaded document list.

func (DocSource) GetDocument

func (s DocSource) GetDocument(ctx context.Context, name string) (Document, error)

GetDocument returns the named document. A missing name wraps ErrNoDocument. A document with ReadErr set is returned as-is so the store can fail closed instead of treating an unreadable file as absent.

func (DocSource) ListDocuments

func (s DocSource) ListDocuments(ctx context.Context) ([]Document, error)

ListDocuments returns the documents. Cancellation is honored before the copy is returned.

type Document

type Document struct {
	Name        string
	Description string
	Body        string
	Raw         []byte
	Pack        string
	ReadErr     error
}

Document is one fragment (or the canonical AGENTS.md) as the pin store sees it. Pack is filled by the CLI and API adapters from the skills lockfile; PinDocuments leaves it empty. ReadErr is a per-document read or parse failure. Callers must not turn that into a whole-list error: an unreadable fragment fails closed for itself and must not skip pinning for every other fragment.

func AnnotatePack

func AnnotatePack(docs []Document, lockPath string) []Document

AnnotatePack sets Document.Pack when the skills lockfile lists the fragment name under a pack source's Rules or RuleFiles. A lockfile read error leaves provenance empty and logs at DEBUG. The first pack name wins when more than one source lists the same fragment.

type FragmentPin

type FragmentPin struct {
	Hash           string               `json:"hash"`
	Document       string               `json:"document,omitempty"`
	Source         string               `json:"source,omitempty"`
	Pack           string               `json:"pack,omitempty"`
	ApprovedReason string               `json:"approved_reason,omitempty"`
	PinnedAt       time.Time            `json:"pinned_at"`
	LastVerifiedAt time.Time            `json:"last_verified_at"`
	Status         string               `json:"status"`
	Findings       []pins.Finding       `json:"findings,omitempty"`
	Approvals      []pinapprovals.Entry `json:"approvals,omitempty"`
}

FragmentPin holds the pin state for one fragment, or for AGENTS.md in single-file mode. Document, Source, Pack, ApprovedReason, Findings, and Approvals are derived. Approvals is rebuilt from the approvals log on every load. Only Hash and Status decide drift.

type PinFile

type PinFile struct {
	Version   string                  `json:"version"`
	Fragments map[string]*FragmentPin `json:"fragments"`
}

PinFile is the top-level JSON structure stored at ~/.gridctl/pins/context/global.json.

type Source

type Source interface {
	ListDocuments(ctx context.Context) ([]Document, error)
	GetDocument(ctx context.Context, name string) (Document, error)
}

Source is the read surface Sync, Verify, and Approve need. Defining it here keeps pkg/ctxpins free of pkg/contexts.

type Store

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

Store manages TOFU content pins for the global context. It holds only its path and scan config. Every public method reloads the pin file under state.WithLock, so two Store values on the same path never disagree.

func New

func New() *Store

New creates a Store for ~/.gridctl/pins/context/global.json. The path is resolved on first use so construction stays error-free; resolution fails only when no home directory is available.

func NewWithPath

func NewWithPath(dir string) *Store

NewWithPath creates a Store that keeps pins in dir/global.json. Intended for tests; it never resolves the real home.

func (*Store) Approve

func (s *Store) Approve(ctx context.Context, name string, src Source, req ApproveRequest) error

Approve re-pins the current content and appends an approval entry. A present fragment with no record is pinned by this approval, matching skill pins: the call is a review of current content, not a check that a record already exists. Absent fragments return ErrNoDocument from the source. The log is written inside the file lock before the pin file. A log error leaves the pin file unchanged. Reset does not touch the log.

func (*Store) Get

func (s *Store) Get(ctx context.Context, name string) (*FragmentPin, bool, error)

Get returns a deep copy of one pin. The bool is false when the name is not pinned. A corrupt or newer pin file is returned as an error and is not rewritten.

func (*Store) GetAll

func (s *Store) GetAll(ctx context.Context) (map[string]*FragmentPin, error)

GetAll returns a deep copy of every pin, keyed by fragment name. The map is empty when the file is absent. A corrupt or newer file is an error.

func (*Store) Rename

func (s *Store) Rename(ctx context.Context, from, to string) error

Rename moves a record from one fragment name to another when from exists and to does not. Any other condition is a no-op that returns nil. Used by the fragments-mode migration, which preserves the digest because the written bytes normalize to the same hash.

func (*Store) Reset

func (s *Store) Reset(ctx context.Context, name string) error

Reset deletes the pin record. The approvals log is left untouched, so a later re-pin still shows the prior entries.

func (*Store) SetScanConfig

func (s *Store) SetScanConfig(enabled bool, ignore []string)

SetScanConfig configures the advisory poisoning scanner. enabled toggles it; ignore suppresses findings by code. Scanning never affects the hash or status. Nil Scan on a deployed stack means on; callers pass that resolved bool.

func (*Store) Sync

func (s *Store) Sync(ctx context.Context, src Source) (*SyncResult, error)

Sync pins unseen documents, verifies the rest, flips status on change, and lists missing pinned names without pruning. It saves once, and only when something changed. A document with ReadErr set is drift when pinned and skipped when unpinned.

func (*Store) Verify

func (s *Store) Verify(ctx context.Context, name string, src Source) (*VerifyResult, error)

Verify compares one fragment with its pin and does not write. ErrDigestUnavailable is returned when the document cannot be read, before any not-found result. ErrNoDocument means the name does not exist. ErrNotPinned means the name has no record.

type Summary

type Summary struct {
	Drifted  []string
	Missing  []string
	Unpinned []string
}

Summary is the read-only pin picture computed from stored records and the current documents. Arrays are non-nil.

func Summarize

func Summarize(records map[string]*FragmentPin, docs []Document) Summary

Summarize compares stored pins with the current documents. It does not write. An unreadable pinned document is drift. An unreadable unpinned document is omitted: there is nothing trustworthy to call unpinned or missing. Slices are non-nil and sorted.

type SyncResult

type SyncResult struct {
	Pinned  []string
	Drifted []string
	Missing []string
}

SyncResult summarizes one TOFU/verify pass. Slices are sorted.

type VerifyResult

type VerifyResult struct {
	Name          string
	Status        string
	CompositeHash string
	Diff          *Diff
}

VerifyResult is the outcome of verifying one fragment against its pin. CompositeHash is the approval-binding fingerprint of the current content.

Jump to

Keyboard shortcuts

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