hookpins

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 hookpins records TOFU content pins for imported hooks at ~/.gridctl/pins/hooks/global.json. A pin is written by the import that installs the hook. A later byte change made any other way is pin drift until a human approves it. gridctl never executes a hook.

Index

Constants

View Source
const (
	// FileVersion is the hook pin file version this binary writes and the
	// highest version it accepts.
	FileVersion = "1"

	StatusPinned = "pinned"
	StatusDrift  = "drift"

	SourceLocal = "local"
	SourceGit   = "git"
)

Variables

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

Sentinel errors. Load's ErrCorrupt and ErrNewerVersion follow the skill pin contract: a corrupt file is reported and not rewritten; ErrNewerVersion must never be papered over.

Functions

This section is empty.

Types

type ApproveRequest

type ApproveRequest struct {
	ExpectedHash     string
	Reason           string
	Evidence         []string
	ApprovedBy       string
	ApprovedBySource string
	// Findings are advisory results for the content being approved. When
	// Scanned is true, Findings is the current scan, including an empty
	// list that clears findings recorded on an earlier pin. When Scanned
	// is false and Findings is empty, the stored findings are kept.
	// A non-empty effective list requires Reason.
	Findings []pins.Finding
	Scanned  bool
}

ApproveRequest is one explicit hook approval.

type HookDiff

type HookDiff struct {
	Name          string         `json:"name"`
	OldHash       string         `json:"old_definition_hash"`
	NewHash       string         `json:"new_definition_hash"`
	OldDocument   string         `json:"old_document,omitempty"`
	NewDocument   string         `json:"new_document,omitempty"`
	AddedFiles    []string       `json:"added_files,omitempty"`
	RemovedFiles  []string       `json:"removed_files,omitempty"`
	ModifiedFiles []string       `json:"modified_files,omitempty"`
	Findings      []pins.Finding `json:"findings,omitempty"`
}

HookDiff describes how a hook moved since its pin.

func (*HookDiff) DocumentChanged

func (d *HookDiff) DocumentChanged() bool

DocumentChanged reports whether hook.json moved.

type HookPin

type HookPin struct {
	// DefinitionHash is the SHA-256 of hook.json bytes.
	DefinitionHash string `json:"definition_hash"`
	// Files are per-file digests over every installed file except
	// .origin.json, dot-prefixed entries, .tmp files, symlinks, and
	// irregular files. Sorted by path.
	Files []skillpins.FileDigest `json:"files,omitempty"`
	// Document is the hook.json text as pinned, kept so drift review can
	// show a diff.
	Document       string               `json:"document,omitempty"`
	Source         string               `json:"source,omitempty"`
	Origin         *skillpins.OriginRef `json:"origin,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"`
	CompositeHash  string               `json:"composite_hash,omitempty"`
}

HookPin is the pin record for one hook. Approvals is rebuilt from the approvals log on every load. CompositeHash is recomputed from the digests.

type PinFile

type PinFile struct {
	Version string              `json:"version"`
	Hooks   map[string]*HookPin `json:"hooks"`
}

PinFile is the top-level JSON structure stored at ~/.gridctl/pins/hooks/global.json. Hook pins are not stack-scoped. The subdirectory keeps this file disjoint from the tool pin file of a stack named hooks.

type PinRequest

type PinRequest struct {
	Name     string
	Dir      string
	Findings []pins.Finding
	Origin   *skillpins.OriginRef
	Source   string
}

PinRequest is the content an accepted import pins.

type Store

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

Store manages TOFU content pins for hooks. Writes hold a file lock and are atomic. The store is not stack-scoped.

func New

func New() *Store

New creates a Store for ~/.gridctl/pins/hooks/global.json. The path is resolved on first use so construction stays error-free.

func NewWithPath

func NewWithPath(dir string) *Store

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

func (*Store) Approve

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

Approve re-pins the hook's current bytes and appends an approval entry. The log is appended inside the file lock before the pin file is rewritten.

func (*Store) CurrentCompositeHash

func (s *Store) CurrentCompositeHash(name, registryDir string) (string, error)

CurrentCompositeHash is the approval fingerprint of the hook's current bytes.

func (*Store) Diff

func (s *Store) Diff(name, registryDir string) (*VerifyResult, error)

Diff is Verify's diff. A clean pin returns a diff with equal hashes and status pinned so callers can still print the composite hash.

func (*Store) Get

func (s *Store) Get(name string) (*HookPin, bool)

Get returns a copy of one pin. The bool is false when the name is not pinned. Load must have succeeded.

func (*Store) Load

func (s *Store) Load() error

Load reads the pin file. A missing file starts empty. A corrupt file resets to empty and wraps ErrCorrupt. A newer version resets to empty and wraps ErrNewerVersion with the skill-pin wording shape.

func (*Store) Pin

func (s *Store) Pin(ctx context.Context, req PinRequest) error

Pin records the bytes currently installed in req.Dir. An accepted import calls this, including when the bytes changed. It does not append an approval entry.

func (*Store) Pins

func (s *Store) Pins() map[string]*HookPin

Pins returns a copy of every pin, keyed by name.

func (*Store) Reset

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

Reset deletes the pin record. Approval history is left in the log.

func (*Store) Sync

func (s *Store) Sync(ctx context.Context, registryDir string) (*SyncResult, error)

Sync recomputes digests for pinned hooks and marks drift. It does not replace pinned bytes. A missing hook directory is reported and kept.

func (*Store) Verify

func (s *Store) Verify(name, registryDir string) (*VerifyResult, error)

Verify builds the diff for one hook without writing. CompositeHash is the fingerprint of the current bytes, the value approve --expect binds to.

type SyncResult

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

SyncResult summarizes one verify pass over pinned hooks. Sync does not re-pin changed bytes; that happens only on an accepted import or approve.

type VerifyResult

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

VerifyResult is the outcome of verifying one hook against its pin.

Jump to

Keyboard shortcuts

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