Documentation
¶
Overview ¶
Package hooks provides a hook event dispatch system for the pasture protocol.
Hook events are fired at key lifecycle transitions (phase changes, slice starts/completions, epoch boundaries, review cycles) and error conditions (slice failures, constraint violations, connection loss). Session events track Claude Code agent session start/end for observability.
The Manager dispatches events concurrently to all registered handlers. Dispatch is non-blocking: each handler runs in its own goroutine under a context deadline. Slow or failing handlers do not block the caller.
Package hooks — singleton.go previously held the module-level hooksMgr singleton, InitHooksManager, GetManager, and DispatchHook.
These have been removed as part of the Activity struct DI refactor (Revision 3). Hook dispatch now flows through Activities.DispatchHook, which receives the hooks.Manager via constructor injection (Activities.HooksMgr field).
This file is intentionally empty except for the package declaration.
Index ¶
Constants ¶
const DefaultDispatchTimeout = 5 * time.Second
DefaultDispatchTimeout is the maximum time a single handler invocation may run before it is abandoned. Chosen to be long enough for non-trivial I/O (file write, HTTP webhook) but short enough to prevent indefinite blocking.
const GitCommitDataKey = "sha"
GitCommitDataKey is the conventional key in HookPayload.Data that carries the git commit SHA. The GitRecorder hook handler reads this key when a hook payload is dispatched to it; payloads missing or with a non-string value are skipped (the hook may be unrelated to git).
Variables ¶
var AllHookEvents = []HookEvent{ HookPhaseTransition, HookVoteRecorded, HookSliceStarted, HookSliceCompleted, HookEpochStarted, HookEpochCompleted, HookReviewCycle, HookSliceFailed, HookConstraintViolation, HookConnectionLost, HookSessionStarted, HookSessionEnded, }
AllHookEvents is the ordered slice of all valid HookEvent values. Useful for registration completeness checks and tests.
Functions ¶
This section is empty.
Types ¶
type GitRecorder ¶
type GitRecorder struct {
// contains filtered or unexported fields
}
GitRecorder is a HookHandler that records git commit events to the unified pasture.db audit trail.
Construct via NewGitRecorder; the zero value is unusable (the embedded TaskTracker / *sql.DB would be nil).
Concurrency: GitRecorder is safe for concurrent calls to Handle and RecordCommit because both operations delegate to tasks.RecordGitEvent, which is itself safe for concurrent use (the underlying *sql.DB and TaskTracker are both goroutine-safe).
func NewGitRecorder ¶
func NewGitRecorder( tracker protocol.TaskTracker, auditDB *sql.DB, opts ...GitRecorderOption, ) (*GitRecorder, error)
NewGitRecorder constructs a GitRecorder bound to the supplied TaskTracker and auxiliary auditDB handle (the same handle returned by tasks.OpenAuditDBForFreeFloating). Both arguments MUST be non-nil; the constructor returns an actionable *pasterrors.StructuredError otherwise so the caller catches the wiring mistake at startup, not at first dispatch.
The auditDB handle is used only for the post-write SELECT MAX(id) lookup inside tasks.RecordGitEvent — see free_floating.go for the rationale.
By default, the recorder subscribes to HookSliceCompleted (see the package doc comment); pass WithSubscribedEvents to override.
func RegisterDefaultRecorders ¶
func RegisterDefaultRecorders( mgr *Manager, tracker protocol.TaskTracker, auditDB *sql.DB, ) (*GitRecorder, error)
RegisterDefaultRecorders registers the in-tree free-floating event recorders (currently: GitRecorder) with the supplied hooks.Manager. Returns the constructed GitRecorder so the caller can also call its RecordCommit method directly (e.g., from a Claude Code Stop-hook bridge S7+ will introduce).
This helper exists so cmd/pastured/main.go can wire the recorder with a single line of glue, keeping the daemon's main.go change minimal — the goal is to avoid stepping on S7's parallel modifications to the same file (per SLICE-9 coordination plan with aura-plugins-9ye50).
Both `tracker` and `auditDB` MUST be non-nil; this function returns the underlying *pasterrors.StructuredError surface from NewGitRecorder when either is nil so the caller's startup fails fast with an actionable error.
When the audit backend is the in-memory implementation (no SQLite file), callers should NOT invoke this helper — there is no auxiliary *sql.DB to pass, and free-floating event recording requires a durable store.
func (*GitRecorder) Events ¶
func (g *GitRecorder) Events() []HookEvent
Events returns the set of HookEvent values this recorder subscribes to. Implements the HookHandler interface.
func (*GitRecorder) Handle ¶
func (g *GitRecorder) Handle(ctx context.Context, payload HookPayload) error
Handle is called by hooks.Manager when one of the subscribed HookEvents fires. The recorder looks for a string SHA at payload.Data[GitCommitDataKey]; if present and non-empty, it records a free-floating git commit event via tasks.RecordGitEvent. If the key is absent / not a string / empty, Handle returns nil (the hook fired for a non-git reason, which is normal).
Implements the HookHandler interface.
func (*GitRecorder) RecordCommit ¶
func (g *GitRecorder) RecordCommit( ctx context.Context, sha string, payload map[string]any, ) (int64, error)
RecordCommit records a free-floating git commit event directly, bypassing the hooks.Manager dispatch path. This is the entry point production wiring (cmd/pastured + the Claude Code Stop-hook bridge S7 will introduce) calls when a commit completes.
sha MUST be the non-empty git commit SHA (becomes the ContextGit edge's context_id). payload SHOULD include "sha" for symmetry; it is NOT enforced.
Returns the int64 audit_events.id so callers can attach further contexts (Scenario 7 multi-context attachment — e.g., a post-epoch commit citing epoch X also gets ContextEpoch).
Errors are *pasterrors.StructuredError; see tasks.RecordGitEvent for the full error contract.
type GitRecorderOption ¶
type GitRecorderOption func(*GitRecorder)
GitRecorderOption is a functional option for NewGitRecorder.
func WithSubscribedEvents ¶
func WithSubscribedEvents(events ...HookEvent) GitRecorderOption
WithSubscribedEvents replaces the default subscription set. Used by S7+ when the Claude Code hook → pasture mapping introduces a new HookEvent (e.g. HookGitCommit) — until that lands, callers stick with the default.
type HookEvent ¶
type HookEvent string
HookEvent is a typed identifier for a pasture hook event. Values are lowercase snake_case strings suitable for JSON serialization and external webhook payloads.
const ( // HookPhaseTransition fires whenever the epoch transitions between phases. HookPhaseTransition HookEvent = "phase_transition" // HookVoteRecorded fires when a reviewer submits a vote. HookVoteRecorded HookEvent = "vote_recorded" // HookSliceStarted fires when a worker slice begins execution. HookSliceStarted HookEvent = "slice_started" // HookSliceCompleted fires when a worker slice finishes successfully. HookSliceCompleted HookEvent = "slice_completed" // HookEpochStarted fires when a new epoch workflow is created. HookEpochStarted HookEvent = "epoch_started" // HookEpochCompleted fires when an epoch reaches the "complete" terminal phase. HookEpochCompleted HookEvent = "epoch_completed" // HookReviewCycle fires when a review round begins. HookReviewCycle HookEvent = "review_cycle" // HookSliceFailed fires when a worker slice activity fails or panics. HookSliceFailed HookEvent = "slice_failed" // HookConstraintViolation fires when a phase-advance constraint check fails. HookConstraintViolation HookEvent = "constraint_violation" // HookConnectionLost fires when the connection to the Temporal server is lost. HookConnectionLost HookEvent = "connection_lost" // HookSessionStarted fires when a Claude Code agent session is registered. HookSessionStarted HookEvent = "session_started" // HookSessionEnded fires when a Claude Code agent session ends. HookSessionEnded HookEvent = "session_ended" )
type HookHandler ¶
type HookHandler interface {
// Handle processes a hook payload. Must respect ctx cancellation.
Handle(ctx context.Context, payload HookPayload) error
// Events returns the set of HookEvent values this handler subscribes to.
Events() []HookEvent
}
HookHandler is the interface that hook consumers must implement.
Events returns the set of HookEvent values this handler is interested in. The Manager only dispatches payloads whose Event is in the returned set.
Handle is called with the payload and a context that carries the dispatch deadline set on the Manager (see WithDispatchTimeout; default is DefaultDispatchTimeout). Implementations should respect ctx.Done() and return promptly when the context is cancelled.
type HookPayload ¶
type HookPayload struct {
Event HookEvent `json:"event"`
EpochId string `json:"epochId"`
Phase protocol.PhaseId `json:"phase,omitempty"`
Data map[string]any `json:"data,omitempty"`
}
HookPayload carries the data emitted with every hook event.
Phase is optional (zero-value "" for non-phase events such as HookSessionStarted/HookSessionEnded). Data holds arbitrary event-specific key/value pairs.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager routes HookPayloads to registered HookHandlers.
Handlers are registered per-event at startup via Register. At runtime, Dispatch fans out to all handlers subscribed to the payload's event. Each handler runs in its own goroutine under a fresh context bounded by the Manager's dispatchTimeout (default: DefaultDispatchTimeout). Errors from individual handlers are collected and returned as a combined error, but do not prevent other handlers from running.
Manager is safe for concurrent use from multiple goroutines after all Register calls complete (typically at startup before any Dispatch calls).
func NewManager ¶
func NewManager(opts ...ManagerOption) *Manager
NewManager creates an empty Manager with no registered handlers. Callers may pass functional options to override defaults (e.g. WithDispatchTimeout).
func (*Manager) Dispatch ¶
func (m *Manager) Dispatch(ctx context.Context, payload HookPayload) error
Dispatch sends payload to all handlers registered for payload.Event.
Each handler is invoked in its own goroutine under a context derived from ctx, with a hard deadline of DefaultDispatchTimeout. Dispatch blocks until ALL handler goroutines have returned (or timed out), then returns a combined error if any handler failed.
"Non-blocking" here means: Dispatch does not block the CALLER indefinitely — handlers run with bounded timeouts. The caller must still await Dispatch's return to learn of errors. To fire-and-forget, wrap Dispatch in a goroutine.
func (*Manager) Register ¶
func (m *Manager) Register(h HookHandler)
Register subscribes h to all events returned by h.Events(). Registration is additive — calling Register multiple times for the same handler appends it each time. Register is not safe to call concurrently with Dispatch; register all handlers before starting dispatch.
type ManagerOption ¶
type ManagerOption func(*Manager)
ManagerOption is a functional option for configuring a Manager at construction.
func WithDispatchTimeout ¶
func WithDispatchTimeout(d time.Duration) ManagerOption
WithDispatchTimeout sets the per-handler deadline used by Dispatch. If d is zero or negative, DefaultDispatchTimeout is used instead.