Documentation
¶
Overview ¶
Package itemstate provides the canonical single-owner state store for per-issue tracking in the Fabrik engine.
This package is the foundation of the reactive cache architecture described in docs/cache-refactor/02-design.md. It replaces the 25+ fragmented in-memory state structures spread across engine/ and boardcache/ with a single Store that holds one ItemState per (repo, issueNumber) pair.
The core contract:
- All state mutations flow through Store.Apply — no bypassing writes.
- All reads flow through Store.Get, which returns an immutable Snapshot.
- Downstream components react to changes via Observer subscriptions.
Design rationale is in adrs/036-reactive-cache-single-owner.md. Phase-by-phase migration strategy is in docs/cache-refactor/02-design.md §5.
This package (Phase 3-A) is a pure addition. It is not yet wired into the engine or boardcache; that happens in Phase 3-B onward.
Index ¶
- Variables
- type BaseBranchWarnRecorded
- type BoardReconciled
- type CIFixCycleIncremented
- type CIFixNoOpRecorded
- type CIMergePendingCleared
- type CIMergePendingStarted
- type Change
- type ChangeFlags
- type CheckRunCompleted
- type CommentBreakerInvocationRecorded
- type CommentBreakerReset
- type CommentBreakerState
- type CommentProcessed
- type CooldownRecorded
- type DeepFetchFailed
- type DeepFetchInvalidated
- type EngineCyclesCleared
- type EnginePaused
- type EngineUnpaused
- type EnqueueCycleIncremented
- type FallbackFetcher
- type InvocationRecorded
- type IssueAssigneesUpdated
- type IssueClosed
- type IssueCommentCreated
- type IssueEdited
- type IssueLabeled
- type IssueOpened
- type IssueReopened
- type IssueUnlabeled
- type ItemDeepFetched
- type ItemIDRegistered
- type ItemState
- type LabelAppliedAtRecorded
- type LinkageHealAttempted
- type LinkedPRState
- type LocalCommentAdded
- type LocalLabelAdded
- type LocalLabelRemoved
- type LocalLockAcquired
- type LocalLockReleased
- type LocalStatusUpdated
- type LockState
- type Logger
- type Mutation
- type Observer
- type ObserverFunc
- type PRChecksObserved
- type PRCreationFailedRecorded
- type PRDetailsUpdated
- type PREnqueueRecorded
- type PRHeadSHAUpdated
- type PRReviewCommentCreated
- type PRReviewRequestRemoved
- type PRReviewRequested
- type PRReviewSubmitted
- type ProbeBoardItemUpdated
- type ProjectV2ItemEdited
- type RebaseCycleIncremented
- type ReviewCycleIncremented
- type ReviewThreadCommentAdded
- type SelfWriteObserved
- type ShallowBoardItemUpdated
- type Snapshot
- func (s Snapshot) Attempts(stageName string) int
- func (s Snapshot) CIFixCycles(stageName string) int
- func (s Snapshot) CommentBreakerInvocationsAt() []time.Time
- func (s Snapshot) CommentBreakerLastAuthor() string
- func (s Snapshot) CommentProcessed(commentID string) time.Time
- func (s Snapshot) CooldownAt(reason string) time.Time
- func (s Snapshot) EnqueueCycles(stageName string) int
- func (s Snapshot) HasActiveCooldown(now time.Time) bool
- func (s Snapshot) HasExpiredCooldown(now time.Time) bool
- func (s Snapshot) IsClosed() bool
- func (s Snapshot) IsTerminal() bool
- func (s Snapshot) LabelAppliedAt(label string) time.Time
- func (s Snapshot) Labels() []string
- func (s Snapshot) LastAttemptAt(stageName string) time.Time
- func (s Snapshot) LastCIFixNoOpSHA() string
- func (s Snapshot) LastEnqueuedSHA() string
- func (s Snapshot) LastTurnsCapped(stageName string) bool
- func (s Snapshot) LastTurnsUsed(stageName string) int
- func (s Snapshot) LinkageHealAttempted(stageName, prSHA string) bool
- func (s Snapshot) LinkedPR() *LinkedPRState
- func (s Snapshot) Lock() *LockState
- func (s Snapshot) Number() int
- func (s Snapshot) PRCreationFailed(stageName string) bool
- func (s Snapshot) PausedByEngine(stageName string) bool
- func (s Snapshot) RebaseCycles(stageName string) int
- func (s Snapshot) Repo() string
- func (s Snapshot) ReviewCycles(stageName string) int
- func (s Snapshot) StallHintPending(stageName string) bool
- func (s Snapshot) State() ItemState
- func (s Snapshot) Status() string
- func (s Snapshot) ValidateCompletedSHA() string
- func (s Snapshot) Worker() *WorkerHandle
- type StageAttempted
- type StageLastAttemptCleared
- type StageRetryCleared
- type StageRetryIncremented
- type StageState
- type StageTurnUsageRecorded
- type StallHintArmed
- type StallHintConsumed
- type Store
- func (s *Store) All() []Snapshot
- func (s *Store) Apply(m Mutation) (Snapshot, []Change, error)
- func (s *Store) CheckRunsBySHA(sha string) []gh.CheckRun
- func (s *Store) EnterRepoWorker(repoKey string)
- func (s *Store) ExitRepoWorker(repoKey string)
- func (s *Store) Get(repo string, number int) (Snapshot, error)
- func (s *Store) GetByPRKey(repo string, prNum int) (string, bool)
- func (s *Store) HasInFlightWorker() bool
- func (s *Store) HasItems() bool
- func (s *Store) Remove(repo string, number int)
- func (s *Store) RemoveByItemID(itemID string) (repo string, number int, ok bool)
- func (s *Store) RepoWorkerActive(repoKey string) bool
- func (s *Store) Reset(items []gh.ProjectItem)
- func (s *Store) Subscribe(o Observer) func()
- type StoreOption
- type TerminalFlagSet
- type TokenUsage
- type ValidateCompletedAtSHA
- type ValidateCompletedAtSHACleared
- type WorkerEntered
- type WorkerExited
- type WorkerHandle
- type WorkerHeartbeat
- type WorkerPIDSet
Constants ¶
This section is empty.
Variables ¶
var ErrNotFound = errors.New("itemstate: item not found")
ErrNotFound is returned by Store.Get when an item does not exist in the cache and the FallbackFetcher also cannot locate it.
Functions ¶
This section is empty.
Types ¶
type BaseBranchWarnRecorded ¶
BaseBranchWarnRecorded marks a base-branch override as having been warned about.
type BoardReconciled ¶
type BoardReconciled struct {
Items []gh.ProjectItem
}
BoardReconciled is submitted by the periodic poll loop after a full board fetch. The Store computes per-item deltas and applies them as focused mutations. itemKey returns "" because this mutation affects all items, not a single one.
type CIFixCycleIncremented ¶
CIFixCycleIncremented increments the CI-fix cycle counter for a stage.
type CIFixNoOpRecorded ¶ added in v0.0.71
CIFixNoOpRecorded sets LinkedPRState.LastCIFixNoOpSHA to the head SHA at which a CI-fix reinvoke (engine.dispatchCIFixReinvoke) completed without pushing a new commit (#958 leg 2). While the head SHA stays at this value, handleMergeAndCIGates skips further CI-fix dispatch/cycle-increment for it — CIWaitTimeout remains the backstop if CI never resolves on this SHA.
type CIMergePendingCleared ¶
CIMergePendingCleared records that CI-gate merge polling has ended for the item's linked PR. Zeros LinkedPRState.CIMergePendingSince. Replaces delete(engine.ciMergePendingSince, iKey).
type CIMergePendingStarted ¶
CIMergePendingStarted records that CI-gate merge polling has begun for the item's linked PR. Sets LinkedPRState.CIMergePendingSince to At. Replaces engine.ciMergePendingSince[iKey] = time.Now().
type Change ¶
type Change struct {
// Repo is "owner/repo" identifying the item.
Repo string
// Number is the issue number.
Number int
// Fields is a bitmask of ChangeFlags indicating which field groups changed.
Fields ChangeFlags
}
Change describes what fields a mutation altered. Delivered to every Observer after a successful Store.Apply.
type ChangeFlags ¶
type ChangeFlags uint32
ChangeFlags is a bitmask describing which logical field groups of an ItemState were altered by a mutation. Observers use this to cheaply filter whether a Change is relevant to them without inspecting the full Snapshot.
const ( // StatusChanged indicates the project-board Status column changed. StatusChanged ChangeFlags = 1 << iota // LabelsChanged indicates the Labels slice changed. LabelsChanged // LockChanged indicates Lock state was acquired, released, or modified. LockChanged // StageStateChanged indicates StageState (attempts, cycles, pauses) changed. StageStateChanged // WorkerChanged indicates any Worker-handle field changed (set, cleared, heartbeat, PID). WorkerChanged // WorkerLifecycleChanged is a sub-flag emitted only by WorkerEntered and WorkerExited. // It is the flag that drives wakeChFlags / mayNeedWork so heartbeats and PID-sets // do not cause spurious deep-fetch cycles for in-flight items. WorkerLifecycleChanged // CooldownChanged indicates CooldownAt map entries were added or removed. CooldownChanged // LinkedPRChanged indicates LinkedPR state (including check runs) changed. LinkedPRChanged // CommentsChanged indicates Comments or PR thread comments changed. CommentsChanged // AssigneesChanged indicates the Assignees slice changed. AssigneesChanged // TitleBodyChanged indicates Title, Body, URL, or Author changed. TitleBodyChanged // StateChanged indicates the open/closed State or IsClosed changed. StateChanged // BlockedByChanged indicates the BlockedBy dependency list changed. BlockedByChanged // DeepFetchChanged indicates LastDeepFetchAt or LastDeepFetchFailureAt changed. DeepFetchChanged // InvocationChanged indicates LastInvocationCompleted, LastInvocationBlocked, // or LastTokenUsage changed. InvocationChanged // BaseBranchChanged indicates BaseBranchWarned map changed. BaseBranchChanged // ItemRemoved indicates the item was removed from the board during a Reset. // This flag is distinct from StateChanged (issue open/closed) and is emitted // only by Store.Reset for items present in the old map but absent from the // new items slice. ItemRemoved // CheckRunChanged indicates a check run was written to the pre-linkage // pendingCheckRuns buffer (SHA not yet linked to any item). Not in wakeChFlags: // the CI gate is catch-up-driven, not wake-driven. The flag exists for // observability (e.g. future TUI consumers) without forcing a poll wake. CheckRunChanged // TerminalChanged indicates the Terminal flag was explicitly set or cleared via // TerminalFlagSet. Store-internal clears (when status changes) piggyback on // StatusChanged and do not emit TerminalChanged. TerminalChanged // CommentBreakerChanged indicates ItemState.CommentBreaker (invocation // timestamps or last author) changed. Informational only — not in // wakeChFlags/cycleSetFlags, same treatment as InvocationChanged. CommentBreakerChanged // PRStateChanged is a narrower sub-flag of LinkedPRChanged, set only by // PRDetailsUpdated (LinkedPR.Title/State/Merged/Draft — a genuine PR-level // state transition such as merged, closed, or draft<->ready). Distinct from // LinkedPRChanged so consumers that only care about the PR's own state (not // review activity, check runs, or thread comments, which also set // LinkedPRChanged) can filter precisely. PRStateChanged // SelfWriteBaselineChanged indicates LastSeenSourceUpdatedAt was advanced by // a SelfWriteObserved mutation (#1090). Pure bookkeeping for the probe // staleness check — intentionally excluded from wakeChFlags/cycleSetFlags // (engine/observers.go) so it never wakes the poll loop or bypasses the // dispatch cooldown. SelfWriteBaselineChanged // LabelAppliedAtChanged indicates ItemState.LabelAppliedAt was written by a // LabelAppliedAtRecorded mutation (#1314). Informational only — like // SelfWriteBaselineChanged, intentionally excluded from wakeChFlags/ // cycleSetFlags so it never wakes the poll loop or bypasses the dispatch // cooldown. LabelAppliedAtChanged )
type CheckRunCompleted ¶
CheckRunCompleted is emitted when a CI check run reaches a terminal state. The SHA field is used to route the run to the correct LinkedPRState.
type CommentBreakerInvocationRecorded ¶ added in v0.0.76
type CommentBreakerInvocationRecorded struct {
Repo string
Number int
At time.Time
Author string
Cutoff time.Time
}
CommentBreakerInvocationRecorded appends a timestamp (and records the triggering comment's author) to ItemState.CommentBreaker for the runaway-loop circuit breaker (#1089). Applied once per comment-processing invocation that actually reaches Claude. Cutoff, when non-zero, additionally prunes any InvocationsAt entries older than Cutoff before appending — the caller (engine) computes it from its own window setting so an issue that receives invocations sparser than the window doesn't grow InvocationsAt without bound; a zero Cutoff performs no pruning (mirrors zero-means-default elsewhere, but here zero means "caller opted out of pruning").
type CommentBreakerReset ¶ added in v0.0.76
CommentBreakerReset clears ItemState.CommentBreaker.InvocationsAt (and LastAuthor) after forward progress is observed: a stage:*:complete transition, a new commit on the branch, a PR state change, an issue-body update, or a manual human unpause (#1089).
type CommentBreakerState ¶ added in v0.0.76
type CommentBreakerState struct {
// InvocationsAt holds the timestamp of each recorded comment-processing
// invocation since the last reset.
InvocationsAt []time.Time
// LastAuthor is the author of the comment that triggered the most recent
// recorded invocation. Surfaced in the trip comment so the operator knows
// who/what to look at.
LastAuthor string
}
CommentBreakerState tracks comment-processing invocation timestamps used by the engine's circuit breaker to detect a non-advancing comment-processing loop. The engine (not this package) owns the threshold/window business logic and prunes-and-counts InvocationsAt on every read — mirroring the existing mergeTrainTrials runaway-guard precedent (ADR-059 D8).
type CommentProcessed ¶
CommentProcessed records that a comment has been processed by the engine. Prevents reprocessing the same comment on subsequent polls or restarts (backed by rocket reactions on GitHub for cross-restart durability).
type CooldownRecorded ¶
CooldownRecorded sets a cooldown expiry for a given reason key.
type DeepFetchFailed ¶
DeepFetchFailed records that a deep-fetch attempt for this item failed.
type DeepFetchInvalidated ¶
DeepFetchInvalidated clears LastDeepFetchAt so the next FetchItemDetails call re-fetches deep fields from GitHub. Used by delta handlers that detect stale deep state (e.g., new PR linkage discovered, new review thread comment added).
type EngineCyclesCleared ¶
EngineCyclesCleared zeroes ReviewCycles, CIFixCycles, RebaseCycles, and EnqueueCycles for a stage. Called by clearFailedStage on unpause/success to prevent stale counters from triggering premature max-cycle pauses on the next run.
type EnginePaused ¶
EnginePaused records that the engine has paused work on a stage due to repeated failures. (The design doc listed this as "EngineEnginePaused" — typo.)
type EngineUnpaused ¶
EngineUnpaused clears PausedByEngine for a stage, used when a user comment triggers an unpause or when clearFailedStage resets engine-managed pause state.
type EnqueueCycleIncremented ¶ added in v0.0.71
EnqueueCycleIncremented increments the merge-queue re-enqueue cycle counter for a stage (ADR-058 D4 FR-3). Applied on each fresh enqueue trip in the convergence monitor; bounds a queue-thrash loop independently of the rebase/CI-fix caps.
type FallbackFetcher ¶
type FallbackFetcher interface {
FetchItem(repo string, number int) (gh.ProjectItem, error)
}
FallbackFetcher is called by Store.Get on cache miss to populate the item from GitHub. The actual implementation (wrapping gh.Client) is wired in Phase 3-B.
type InvocationRecorded ¶
type InvocationRecorded struct {
Repo string
Number int
Completed bool
Blocked bool
// Errored is true when the Claude process exited non-zero AND the exit was not
// classified as a turn-limit hit (see TurnLimited). Recorded independently of
// Completed — the completion marker is authoritative for whether the stage
// completed; Errored only records that the process didn't exit cleanly for a
// genuine fault.
Errored bool
// TurnLimited is true when the Claude invocation exited because it exhausted
// its configured turn budget (CLI subtype error_max_turns), as opposed to a
// genuine failure. A turn-limited run is incomplete but resumable, not an
// error — see claudeTurnLimitError in engine/claude.go and ADR-1178.
TurnLimited bool
Usage TokenUsage
// IsComment is true when the invocation processed a user comment rather than
// running a stage. Stored in ItemState.LastInvocationIsComment so that the
// InvocationObserver can forward the correct flag to the TUI.
IsComment bool
// Duration is the wall-clock time from invocation start to completion.
// Zero when not set (e.g., comment-processing paths that don't track start time).
Duration time.Duration
}
InvocationRecorded captures the outcome of a completed Claude invocation for TUI display.
type IssueAssigneesUpdated ¶
IssueAssigneesUpdated is emitted when the assignee list changes (assigned or unassigned). Assignees is the full post-mutation list from the webhook payload.
type IssueClosed ¶
IssueClosed is emitted when an issue is closed.
type IssueCommentCreated ¶
IssueCommentCreated is emitted when a new comment is added to an issue.
type IssueEdited ¶
IssueEdited is emitted when an issue's title or body is changed.
type IssueLabeled ¶
IssueLabeled is emitted when a label is added to an issue.
type IssueOpened ¶
type IssueOpened struct {
Item gh.ProjectItem
}
IssueOpened is emitted when a new issue is created or first observed. Item is a gh.ProjectItem because there is no separate gh.Issue type; ProjectItem is the full per-item representation used throughout Fabrik.
type IssueReopened ¶
IssueReopened is emitted when a previously closed issue is reopened.
type IssueUnlabeled ¶
IssueUnlabeled is emitted when a label is removed from an issue.
type ItemDeepFetched ¶
type ItemDeepFetched struct {
Repo string
Number int
FreshState gh.ProjectItem
}
ItemDeepFetched is applied after a single-item deep fetch from the GitHub API.
type ItemIDRegistered ¶
ItemIDRegistered sets the project-item node ID for an existing Store entry that was added without one (e.g., via issues.opened before projects_v2_item.created). This is triggered by the Layer 1 fallback GraphQL lookup, not by a Fabrik-initiated GitHub mutation. Returns ChangeFlags(0) — the itemIDToKey reverse index is updated via reflect.DeepEqual detection in applySingleItem regardless of the returned flags. Dispatch is triggered only by the subsequent UpdateItemStatus call, not by this.
type ItemState ¶
type ItemState struct {
// Repo is "owner/repo".
Repo string
// Number is the issue number.
Number int
// ID is the GitHub content node ID (e.g. "I_kwDO..." for issues, "PR_kwDO..." for PRs).
// Used by github.Client.FetchItemDetails for its GraphQL node lookup.
ID string
// ItemID is the GitHub Project item node ID (empty if not on the board).
ItemID string
Title string
Body string
URL string
Author string
Assignees []string
// State is "open" or "closed".
State string
IsClosed bool
IsPR bool
Labels []string
// Status is the project board column ("Specify", "Implement", etc.).
Status string
// UpdatedAt is max(issue.updatedAt, projectItem.updatedAt, linkedPR.updatedAt).
UpdatedAt time.Time
// BlockedBy holds issues that must be closed before this one can advance.
BlockedBy []gh.Dependency
// Comments holds all comments on this issue.
Comments []gh.Comment
// LinkedPR is the state of the closing PR; nil if none.
LinkedPR *LinkedPRState
// Lock is nil when unlocked; non-nil when this instance or another holds a lock.
Lock *LockState
// StageState holds per-stage attempt and cycle counters.
StageState StageState
// CooldownAt maps reason → expiry time (e.g. "retry", "review-blocked", "ci-await").
CooldownAt map[string]time.Time
// LabelAppliedAt maps label name → the time the engine itself most recently
// applied that label to this issue (record-at-write, #1314). Deliberately a
// separate map from CooldownAt, not a repurposing of it: CooldownAt's
// HasExpiredCooldown treats any non-zero, past timestamp as "wake this item"
// (engine/poll.go), but an applied-at timestamp is always in the past the
// instant it's recorded — aliasing the two would make every recorded label
// application look like a permanently expired cooldown. Populated only for
// labels the engine writes exclusively through applyLabelAdd or an explicit
// recordLabelAppliedAtNow call (engine/mutate.go); a label this map has no
// entry for simply falls back to the live FetchLabelAppliedAt REST fetch.
LabelAppliedAt map[string]time.Time
// Worker is present when a worker is in-flight for this item.
Worker *WorkerHandle
// Terminal is set by the engine after a deep-fetch confirms the item satisfies
// the terminal predicate (cleanup-stage status + stage:<Name>:complete label + no
// transient lifecycle labels). While set, the poll loop skips deep-fetch entirely
// for this item. Cleared automatically when the item's status changes.
Terminal bool
// LastDeepFetchAt is the time of the last successful deep fetch for this item.
// A zero value means no deep fetch has occurred. Replaces boardcache.deepFetched[key].
LastDeepFetchAt time.Time
// LastSeenSourceUpdatedAt is the value of pi.UpdatedAt observed at the moment
// of the last successful deep fetch. The cache compares this against the fresh
// pi.UpdatedAt from each board read to decide whether the cached deep fields
// are still authoritative; a later board updatedAt forces a deep re-fetch.
// This is the "GraphQL fetching is primary; updatedAt makes the staleness check
// cheap" contract — webhooks remain an optimization that keeps the cache fresh
// between polls but never replace the polling refresh path.
//
// Has a second writer besides ItemDeepFetched: SelfWriteObserved (#1090)
// advances it to time.Now() at each of Fabrik's own self-write call sites
// (label add/remove, comment post, issue body edit, board status move) so a
// self-caused GitHub updatedAt bump isn't mistaken for external staleness on
// the next probe cycle. Both writers share the same monotonic guard in
// applyToItem, and DeepFetchInvalidated's zero-reset always wins against a
// stale SelfWriteObserved racing behind it.
LastSeenSourceUpdatedAt time.Time
// LastDeepFetchFailureAt is the time the most recent deep fetch attempt failed.
// Replaces engine.deepFetchFailureTime[iKey].
LastDeepFetchFailureAt time.Time
// LastInvocationCompleted records whether the most recent Claude invocation
// emitted FABRIK_STAGE_COMPLETE. Replaces engine.lastCompleted[iKey].
LastInvocationCompleted bool
// LastInvocationBlocked records whether the most recent Claude invocation
// emitted FABRIK_BLOCKED_ON_INPUT. Replaces engine.lastBlocked[iKey].
LastInvocationBlocked bool
// LastInvocationIsComment is true when the most recent invocation processed a
// user comment rather than running a full stage. Mirrors InvocationRecorded.IsComment.
LastInvocationIsComment bool
// LastInvocationDuration is the wall-clock time of the most recent Claude invocation.
// Zero when not recorded (comment-processing paths that don't track start time).
LastInvocationDuration time.Duration
// LastInvocationErrored records whether the most recent Claude invocation exited
// with a non-zero status (process error, timeout kill, etc.) AND was not classified
// as a turn-limit exit (see LastInvocationTurnLimited) — i.e. a genuine fault. This
// is recorded independently of LastInvocationCompleted: a stage can complete
// (FABRIK_STAGE_COMPLETE emitted) even when the process exits non-zero — e.g. a
// timeout kill after the stage finished. The error is surfaced as
// JobCompletedEvent.Success=false in history.
LastInvocationErrored bool
// LastInvocationTurnLimited records whether the most recent Claude invocation
// exited because it exhausted its configured turn budget (CLI subtype
// error_max_turns), as opposed to a genuine failure. A turn-limited invocation
// is incomplete but resumable — surfaced as JobCompletedEvent.TurnLimited in
// history, rendered distinctly from a genuine error. See ADR-1178.
LastInvocationTurnLimited bool
// LastTokenUsage holds token consumption from the most recent Claude invocation.
// Replaces engine.lastUsage[iKey].
LastTokenUsage TokenUsage
// BaseBranchWarned tracks which base-branch overrides have already produced a
// "branch not found" warning comment. Replaces engine.baseBranchWarnedSet.
BaseBranchWarned map[string]bool
// CommentBreaker tracks comment-processing invocations for the runaway-loop
// circuit breaker (#1089). Scoped to the item as a whole, not per-stage — the
// breaker must survive a legitimate stage transition mid-window.
CommentBreaker CommentBreakerState
}
ItemState is the canonical per-item state. All mutations flow through Store.Apply; all reads flow through Store.Get or change subscriptions.
Field grouping by lifecycle:
- Identity: never changes after first Apply
- GitHub state: mirrors GitHub's view of issue, project, and linked PR
- Engine state: fabrik's local control state (locks, retries, cycle counts, cooldowns)
- TUI state: mirrors last-invocation outcomes for display
type LabelAppliedAtRecorded ¶ added in v0.0.77
LabelAppliedAtRecorded records the time the engine itself applied Label to this issue (record-at-write, #1314). Always overwrites any prior entry for the same label — a genuine re-application (applied → removed → re-applied) must yield the latest timestamp, not the first. Callers are expected to only record here on a genuine new application (guarded by a "not already present" check at the write site), never as a defensive idempotent no-op.
type LinkageHealAttempted ¶ added in v0.0.69
LinkageHealAttempted records that the engine attempted to auto-heal missing PR↔issue linkage for the given stage. Keyed by stage name → PR head SHA. In-memory only — does not survive restart. A force-push (new SHA) clears the guard naturally by virtue of the SHA not matching the stored value.
type LinkedPRState ¶
type LinkedPRState struct {
Number int
// Title, State ("open"/"closed"), Merged, and Draft mirror gh.PRDetails fields
// that were previously stored only in CacheImpl.linkedPRs. Populated by PRDetailsUpdated.
Title string
State string
Merged bool
Draft bool
// Mergeable is nil when unknown; true/false once GitHub resolves mergeability.
Mergeable *bool
MergeableState string // "clean", "unstable", "blocked", etc.
HeadSHA string
Reviews []gh.PRReview
ReviewRequests []gh.ReviewRequest
// ThreadComments holds unresolved review-thread comments.
ThreadComments []gh.Comment
ResolvedThreadCount int
CheckRuns []gh.CheckRun
// IsMergeQueueEnabled is true when the repository has the merge queue feature
// enabled. Populated from GraphQL via FetchItemDetails; zero until first deep fetch.
IsMergeQueueEnabled bool
// IsInMergeQueue is true when the PR is currently in the merge queue.
IsInMergeQueue bool
// MergeQueueEntry holds queue position and state when the PR is enqueued.
// Nil when not in queue; set to nil again after dequeueing.
MergeQueueEntry *gh.MergeQueueEntry
// HasHadChecks records whether this PR has ever had CI check runs reported.
// Replaces engine.prHasHadChecks[iKey].
HasHadChecks bool
// CIMergePendingSince records when the engine began waiting for the merge
// to complete after CI passed. Zero if not currently pending.
// Replaces engine.ciMergePendingSince[iKey].
CIMergePendingSince time.Time
// ValidateCompletedSHA records the HEAD SHA of the linked PR at the moment
// stage:Validate:complete was last applied. The SHA-invalidation scan in
// engine/poll.go compares this against the current HeadSHA to detect force-pushes
// or external commits after Validate finished. Empty string means "not recorded"
// (pre-feature or no Validate completion in this session).
ValidateCompletedSHA string
// LastHeadSHAUpdate records when the linked PR's HeadSHA was last observed to
// change via a PRHeadSHAUpdated mutation. Zero means the SHA has never changed
// (cold start or post-restart). Used by the post-push dwell guard in checkCIGate
// to block gate-clearance during the brief window after a force-push when GitHub
// has not yet computed mergeability or started CI for the new SHA.
LastHeadSHAUpdate time.Time
// LastEnqueuedSHA records the PR head SHA at the moment the engine last enqueued
// (or re-enqueued) this PR into GitHub's native merge queue (ADR-058 D4 FR-3).
// The convergence monitor uses it to distinguish a genuine post-resolution
// re-enqueue (head SHA changed since the last enqueue → enqueue fresh) from the
// brief post-enqueue consistency window where GitHub has not yet reflected
// isInMergeQueue=true (same SHA → suppress a spurious re-enqueue). Empty until the
// first enqueue.
LastEnqueuedSHA string
// LastCIFixNoOpSHA records the head SHA for which the most recent CI-fix
// reinvoke (engine.dispatchCIFixReinvoke) observed no new commit pushed
// (HEAD unchanged before/after processComments). While the head SHA
// stays at this value, handleMergeAndCIGates skips further CI-fix
// dispatch/cycle-increment for it — a repeated no-op reinvoke burns
// nothing further; CIWaitTimeout remains the backstop if CI never
// resolves. Cleared implicitly once HeadSHA advances past it. Empty
// means "no no-op recorded for the current SHA."
LastCIFixNoOpSHA string
}
LinkedPRState holds the state of the closing pull request for an issue.
type LocalCommentAdded ¶
LocalCommentAdded is applied after fabrik posts a comment on an issue.
type LocalLabelAdded ¶
LocalLabelAdded is applied after fabrik adds a label to an issue.
type LocalLabelRemoved ¶
LocalLabelRemoved is applied after fabrik removes a label from an issue.
type LocalLockAcquired ¶
type LocalLockAcquired struct {
Repo string
Number int
User string
Worker *WorkerHandle
AcquiredAt time.Time // caller-provided time; enables idempotent/no-op detection
}
LocalLockAcquired is applied after fabrik adds the fabrik:locked:<user> label.
type LocalLockReleased ¶
LocalLockReleased is applied after fabrik removes the fabrik:locked:<user> label.
type LocalStatusUpdated ¶
LocalStatusUpdated is applied after fabrik calls UpdateProjectItemStatus.
type LockState ¶
type LockState struct {
// HolderUser is the user identity from the fabrik:locked:<user> label.
HolderUser string
// HeldByThis is true if HolderUser matches this engine instance's user and Worker != nil.
HeldByThis bool
AcquiredAt time.Time
}
LockState describes who holds the fabrik:locked:<user> label on this issue.
type Logger ¶
Logger is an optional logging function for Store internals. Accepts a printf-style format string and arguments. The default (nil) produces no output.
type Mutation ¶
type Mutation interface {
// contains filtered or unexported methods
}
Mutation is a discriminated union of every possible state change. Every code path that wants to update ItemState expresses it as a Mutation and calls Store.Apply. There is no other write path.
type Observer ¶
Observer is implemented by any component that wants to react to ItemState changes.
OnChange is called by Store.Apply after every successful, non-no-op mutation. It is called outside the Store's write lock, so it is safe for observers to call Store.Get or Store.Apply from within OnChange without deadlocking.
Ordering note: when two goroutines call Apply concurrently, observers may see their changes in a different order than the goroutines submitted them. The Store does not guarantee total ordering of concurrent mutations; each Apply is atomic, but relative ordering across concurrent callers is undefined.
type ObserverFunc ¶
ObserverFunc adapts a plain function to the Observer interface.
func (ObserverFunc) OnChange ¶
func (f ObserverFunc) OnChange(change Change, snapshot Snapshot)
OnChange implements Observer.
type PRChecksObserved ¶
PRChecksObserved records that the linked PR has had at least one CI check run returned by FetchCheckRuns (REST path). Sets LinkedPRState.HasHadChecks = true. Replaces engine.prHasHadChecks[iKey] = true.
type PRCreationFailedRecorded ¶
PRCreationFailedRecorded records that Claude completed a stage but the draft PR could not be created. In-memory only — does not survive restart. Cleared by StageRetryCleared when the PR is eventually created or the stage succeeds.
type PRDetailsUpdated ¶
type PRDetailsUpdated struct {
Repo string
Number int // issue number
PRNumber int
Title string
State string
Merged bool
Draft bool
}
PRDetailsUpdated sets LinkedPR.Title, State, Merged, and Draft for the given item. These four fields were previously stored only in CacheImpl.linkedPRs; this mutation moves them into the Store so PR-detail changes are observable via LinkedPRChanged. PRNumber also ensures the prToKey reverse index is current.
type PREnqueueRecorded ¶ added in v0.0.71
PREnqueueRecorded sets LinkedPRState.LastEnqueuedSHA to the head SHA at which the engine last enqueued (or re-enqueued) the PR into GitHub's merge queue (ADR-058 D4 FR-3). Lets the convergence monitor distinguish a genuine post-resolution re-enqueue (SHA changed) from the post-enqueue consistency window (SHA unchanged).
type PRHeadSHAUpdated ¶
type PRHeadSHAUpdated struct {
Repo string
Number int // issue number
LinkedPRNum int // PR number to set (0 = leave unchanged)
SHA string
}
PRHeadSHAUpdated sets LinkedPR.HeadSHA for the given item and triggers the Store's shaToKey index to be updated. Used by delta handlers when a push event updates the PR's head commit. LinkedPRNum, if non-zero, also sets LinkedPR.Number (used in auto-heal paths where the PR↔issue linkage is being established for the first time).
type PRReviewCommentCreated ¶
PRReviewCommentCreated is emitted when a new inline review comment is added.
type PRReviewRequestRemoved ¶
PRReviewRequestRemoved is emitted when one reviewer is removed from the linked PR.
type PRReviewRequested ¶
type PRReviewRequested struct {
Repo string
Number int // issue number
Reviewers []gh.ReviewRequest
}
PRReviewRequested is emitted when reviewers are added to the linked PR. Reviewers is the full post-mutation list from the webhook payload.
type PRReviewSubmitted ¶
PRReviewSubmitted is emitted when a reviewer submits a review on the linked PR.
type ProbeBoardItemUpdated ¶
type ProbeBoardItemUpdated struct {
Repo string
Number int
Item gh.BoardProbeItem
}
ProbeBoardItemUpdated updates only the probe-visible fields of an existing item from a ProbeProjectBoard result. Unlike ShallowBoardItemUpdated, it does NOT touch Labels (the probe query fetches no labels). Updates ID, ItemID, State, IsClosed, IsPR, Status, and UpdatedAt only.
type ProjectV2ItemEdited ¶
type ProjectV2ItemEdited struct {
// ItemID is the GitHub Project item node ID (matches ItemState.ItemID).
ItemID string
NewStatus string
}
ProjectV2ItemEdited is emitted when the project board status field is changed.
type RebaseCycleIncremented ¶
RebaseCycleIncremented increments the rebase cycle counter for a stage.
type ReviewCycleIncremented ¶
ReviewCycleIncremented increments the review cycle counter for a stage.
type ReviewThreadCommentAdded ¶
ReviewThreadCommentAdded appends an inline review-thread comment to an item's LinkedPR.ThreadComments, idempotent by NodeID. Uses the ISSUE number (not the PR number) as the routing key. Used by boardcache delta handlers.
type SelfWriteObserved ¶ added in v0.0.76
SelfWriteObserved advances an item's probe staleness baseline (LastSeenSourceUpdatedAt) to the current wall-clock time, without performing a deep fetch and without touching any other field. Applied at every call site where Fabrik performs a self-write (label add/remove, comment post, issue body edit, board status move) that is known to bump the item's real GitHub updatedAt but whose cache write-through already reflects the resulting state (#1090). Store.applyToItem enforces monotonicity: the baseline only ever advances, never moves backward relative to a prior deep-fetch or self-write, so a concurrent DeepFetchInvalidated (which zeroes the baseline) can never be "un-done" by a stale SelfWriteObserved.
type ShallowBoardItemUpdated ¶
type ShallowBoardItemUpdated struct {
Repo string
Number int
Item gh.ProjectItem
}
ShallowBoardItemUpdated updates only the shallow fields of an existing item during a Reconcile pass. Unlike ItemDeepFetched or IssueOpened, it does NOT overwrite deep fields (Comments, Body, Assignees, BlockedBy, LinkedPRReviews, etc.). Used exclusively by CacheImpl.Reconcile.
WARNING: do not use with probe data — the Labels field will be set to empty, wiping the cached label set. Use ProbeBoardItemUpdated for probe-loop updates.
type Snapshot ¶
type Snapshot struct {
// contains filtered or unexported fields
}
Snapshot is an immutable copy of an ItemState returned by Store.Get and Store.Apply. Because it wraps a value (not a pointer), callers can hold it as long as needed without blocking writes and without risk of concurrent mutation.
All slice and map fields are deep-copied when a Snapshot is constructed, so mutations to the Store's internal state do not bleed into held Snapshots.
func (Snapshot) CIFixCycles ¶
CIFixCycles returns the CI-fix re-invocation cycle count for a given stage.
func (Snapshot) CommentBreakerInvocationsAt ¶ added in v0.0.76
CommentBreakerInvocationsAt returns a copy of the recorded comment-processing invocation timestamps since the last reset (#1089).
func (Snapshot) CommentBreakerLastAuthor ¶ added in v0.0.76
CommentBreakerLastAuthor returns the author of the comment that triggered the most recent recorded comment-breaker invocation, or "" if none.
func (Snapshot) CommentProcessed ¶
CommentProcessed returns the timestamp when a comment was last processed, or zero if it has not been seen.
func (Snapshot) CooldownAt ¶
CooldownAt returns the expiry time for a given cooldown reason, or zero if none.
func (Snapshot) EnqueueCycles ¶ added in v0.0.71
EnqueueCycles returns the merge-queue re-enqueue cycle count for a given stage.
func (Snapshot) HasActiveCooldown ¶
HasActiveCooldown reports whether any CooldownAt entry has not yet expired relative to now. Reads directly from the snapshot's already-copied map — no additional allocation.
func (Snapshot) HasExpiredCooldown ¶
HasExpiredCooldown reports whether any CooldownAt entry is non-zero and has already expired relative to now. Reads directly from the snapshot's already-copied map — no additional allocation.
func (Snapshot) IsTerminal ¶
IsTerminal reports whether the terminal flag is set for this item. When true, the poll loop skips deep-fetch as long as status remains in a cleanup stage.
func (Snapshot) LabelAppliedAt ¶ added in v0.0.77
LabelAppliedAt returns the time the engine itself most recently applied the given label to this issue, or zero if no record-at-write has been made for it (cold cache — the caller should fall back to a live fetch). See ItemState.LabelAppliedAt's doc comment for why this is a distinct map from CooldownAt.
func (Snapshot) LastAttemptAt ¶
LastAttemptAt returns the last invocation timestamp for a given stage, or zero.
func (Snapshot) LastCIFixNoOpSHA ¶ added in v0.0.71
LastCIFixNoOpSHA returns the PR head SHA for which the most recent CI-fix reinvoke completed without pushing a new commit, or "" if not recorded (LinkedPR is nil or no no-op has occurred). Used by handleMergeAndCIGates to skip a further CI-fix dispatch/cycle-increment for the same SHA (#958).
func (Snapshot) LastEnqueuedSHA ¶ added in v0.0.71
LastEnqueuedSHA returns the PR head SHA recorded at the last merge-queue enqueue, or "" if not recorded (LinkedPR is nil or no enqueue has occurred).
func (Snapshot) LastTurnsCapped ¶ added in v0.0.76
LastTurnsCapped reports whether the most recent incomplete invocation of a stage hit its turn cap without completing (#1146).
func (Snapshot) LastTurnsUsed ¶ added in v0.0.76
LastTurnsUsed returns the TurnsUsed recorded for the most recent incomplete invocation of a stage, or zero if none has been recorded (#1146).
func (Snapshot) LinkageHealAttempted ¶ added in v0.0.69
LinkageHealAttempted returns true when a linkage auto-heal has already been attempted for the given stage and PR head SHA. Returns false if either is unknown.
func (Snapshot) LinkedPR ¶
func (s Snapshot) LinkedPR() *LinkedPRState
LinkedPR returns the LinkedPRState, or nil if no closing PR exists.
func (Snapshot) PRCreationFailed ¶
PRCreationFailed reports whether Claude completed a stage but the draft PR could not be created. In-memory only — does not survive restart.
func (Snapshot) PausedByEngine ¶
PausedByEngine reports whether the engine has paused this stage due to repeated failures. This flag is in-memory only and does not survive restart.
func (Snapshot) RebaseCycles ¶
RebaseCycles returns the rebase re-invocation cycle count for a given stage.
func (Snapshot) ReviewCycles ¶
ReviewCycles returns the review re-invocation cycle count for a given stage.
func (Snapshot) StallHintPending ¶ added in v0.0.76
StallHintPending reports whether a stall was detected for a stage and its next invocation should receive a corrective hint (#1146).
func (Snapshot) State ¶
State returns a deep copy of the underlying ItemState value. Mutating the returned value — including its slice and map fields — does not affect the Snapshot or the Store.
func (Snapshot) ValidateCompletedSHA ¶ added in v0.0.69
ValidateCompletedSHA returns the HEAD SHA recorded when stage:Validate:complete was last applied, or "" if not recorded (LinkedPR is nil or field not set).
func (Snapshot) Worker ¶
func (s Snapshot) Worker() *WorkerHandle
Worker returns the active WorkerHandle, or nil if no worker is in-flight.
type StageAttempted ¶
StageAttempted records the start of a new Claude invocation for a stage.
type StageLastAttemptCleared ¶
StageLastAttemptCleared zeroes LastAttemptAt for a stage so it re-runs promptly. Used by unblockAwaitingInput to reset the dispatch cooldown after user input.
type StageRetryCleared ¶
StageRetryCleared resets the attempt counter for a stage.
type StageRetryIncremented ¶
StageRetryIncremented increments the attempt counter for a stage.
type StageState ¶
type StageState struct {
// Attempts counts how many times Claude was invoked for each stage.
// Replaces engine.retryCount[stageKey].
Attempts map[string]int
// LastAttemptAt records the last invocation timestamp per stage.
// Replaces engine.processedSet[stageKey] for retry-suppression.
LastAttemptAt map[string]time.Time
// PausedByEngine records engine-initiated pauses (vs user-initiated).
// Replaces engine.pausedDueToRetries[stageKey].
PausedByEngine map[string]bool
// PRCreationFailed records that Claude completed but the draft PR could not
// be created. In-memory only — does not survive restart. When set, the next
// retry first attempts PR creation before re-invoking Claude.
PRCreationFailed map[string]bool
// ReviewCycles counts how many review iterations each stage has completed.
// Replaces engine.reviewCycleCount[stageKey].
ReviewCycles map[string]int
// CIFixCycles counts how many CI-fix iterations each stage has completed.
// Replaces engine.ciFixCycleCount[stageKey].
CIFixCycles map[string]int
// RebaseCycles counts how many rebase iterations each stage has completed.
// Replaces engine.rebaseCycleCount[stageKey].
RebaseCycles map[string]int
// EnqueueCycles counts how many times the engine has re-enqueued the linked PR
// into GitHub's native merge queue after an ejection (ADR-058 D4 FR-3). Bounds a
// queue-thrash loop (enqueue→eject→re-enqueue→eject) independently of the
// RebaseCycles/CIFixCycles that the conflict/CI sub-paths increment.
EnqueueCycles map[string]int
// ProcessedComments maps comment ID to the time Fabrik finished processing it.
ProcessedComments map[string]time.Time
// LinkageHealAttempted maps stage name to the PR head SHA for which a linkage
// auto-heal was attempted. In-memory only — does not survive restart. Keyed by
// stage name so force-push (new SHA) clears the guard naturally.
LinkageHealAttempted map[string]string
// LastTurnsUsed records the most recently completed invocation's TurnsUsed for
// each stage. In-memory only — does not survive restart. Used by the stall
// detector (#1146) to compare consecutive incomplete attempts.
LastTurnsUsed map[string]int
// LastTurnsCapped records whether the most recently completed invocation for a
// stage was turn-capped (TurnsUsed >= MaxTurns without completing). Overwritten
// on every invocation, which makes stall detection self-limiting to a single
// corrective hint per episode (#1146): the declining attempt that triggers
// detection is itself uncapped, so it immediately clears the precondition for
// the next comparison.
LastTurnsCapped map[string]bool
// StallHintPending marks a stage for which a stall was detected and a
// corrective hint should be injected into the next invocation's prompt. Cleared
// (consumed) as soon as that invocation is dispatched. In-memory only — does
// not survive restart (#1146).
StallHintPending map[string]bool
}
StageState holds per-stage attempt counters and cycle counts. Keys are stage names (e.g. "Implement", "Review").
type StageTurnUsageRecorded ¶ added in v0.0.76
type StageTurnUsageRecorded struct {
Repo string
Number int
StageName string
TurnsUsed int
Capped bool
}
StageTurnUsageRecorded records the TurnsUsed/capped status of the invocation that just finalized as incomplete for a stage (#1146). Applied once per incomplete invocation, always overwriting the prior value — this is what makes stall detection self-limiting: the very attempt that triggers detection (a decline) is itself uncapped, so it clears the precondition for a subsequent re-arm.
type StallHintArmed ¶ added in v0.0.76
StallHintArmed records that a stall was detected for a stage (a turn-capped attempt followed by an incomplete attempt using strictly fewer turns) and the next invocation of that stage should receive a corrective hint (#1146).
type StallHintConsumed ¶ added in v0.0.76
StallHintConsumed clears a pending stall hint once it has been injected into an invocation's prompt (#1146), so the hint fires exactly once per detected episode.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the single owner of all per-item state. All mutations flow through Apply; all reads flow through Get or Observer subscriptions.
Concurrency model:
- mu guards items, shaToKey, itemIDToKey, and pendingCheckRuns.
- observerMu guards the observers slice independently to avoid holding mu while calling observer callbacks (which may themselves call Apply or Get).
- Apply holds mu for the write, captures the observer slice under observerMu, then calls observers after releasing both locks.
func NewStore ¶
func NewStore(fallback FallbackFetcher, opts ...StoreOption) *Store
NewStore creates a new Store. fallback may be nil, in which case cache misses return ErrNotFound instead of triggering a live fetch.
func (*Store) All ¶
All returns an immutable snapshot of every item currently in the Store. The returned slice is a point-in-time snapshot; subsequent mutations do not affect it. Safe to call concurrently with Apply.
func (*Store) Apply ¶
Apply mutates state. Every state change flows through here.
Returns the updated Snapshot for the affected item, a list of Changes (zero or one for single-item mutations; multiple for BoardReconciled), and any error.
If the mutation results in no field change (no-op), no Changes are returned and no Observers are notified (invariant I6).
For BoardReconciled, the returned Snapshot is zero-valued; each affected item produces one Change in the returned slice.
func (*Store) CheckRunsBySHA ¶
CheckRunsBySHA returns all check runs known for a commit SHA, combining:
- runs buffered in pendingCheckRuns (pre-linkage, SHA not yet linked to any item)
- runs in the linked item's LinkedPR.CheckRuns (post-linkage)
Returns a deep copy; callers may mutate the result without affecting Store state. Returns nil if no runs are known for the SHA.
func (*Store) EnterRepoWorker ¶ added in v0.0.76
EnterRepoWorker marks repoKey ("owner/repo") as having an active repo-scoped worker (currently: a merge-train worker spanning a batch of items). Idempotent.
func (*Store) ExitRepoWorker ¶ added in v0.0.76
ExitRepoWorker clears the repo-scoped worker marker for repoKey. Safe to call even if no marker is set (no-op).
func (*Store) Get ¶
Get returns an immutable snapshot of the current ItemState for the given item.
On cache miss, Get calls FallbackFetcher.FetchItem (if set), applies the result as an IssueOpened mutation, and returns the new snapshot (invariant I9).
Returns ErrNotFound if:
- the item is not in the cache AND
- the fallback is nil OR the fallback also returns an error.
func (*Store) GetByPRKey ¶
GetByPRKey returns the item key for the issue that is closed by the given PR. Returns ("", false) if no item in the Store has a LinkedPR.Number matching prNum in the given repo. The returned key can be parsed with parseKey to extract repo and number for a subsequent Get call.
func (*Store) HasInFlightWorker ¶ added in v0.0.76
HasInFlightWorker reports whether any worker — per-item (WorkerEntered on a (Repo, Number)) or repo-scoped (EnterRepoWorker on a repo) — is currently in flight. This is the single authoritative answer to "is a worker running" consumed by the auto-upgrade idle guard: neither registry alone is sufficient, since a merge-train worker registers only the latter.
func (*Store) HasItems ¶
HasItems reports whether the Store contains at least one item. O(1) and non-allocating. Safe to call concurrently with Apply.
func (*Store) Remove ¶
Remove deletes the item identified by (repo, number) from the Store and updates the shaToKey, itemIDToKey, and prToKey indexes accordingly. No-op when the item is not present.
func (*Store) RemoveByItemID ¶
RemoveByItemID removes the item identified by its project ItemID (board-side ID). Updates shaToKey, itemIDToKey, and prToKey indexes accordingly. No-op and ok=false if the ItemID is not in the index.
func (*Store) RepoWorkerActive ¶ added in v0.0.76
RepoWorkerActive reports whether a repo-scoped worker is currently marked in-flight for repoKey ("owner/repo").
func (*Store) Reset ¶
func (s *Store) Reset(items []gh.ProjectItem)
Reset atomically replaces all Store state with the items in the given slice. Existing items, indexes, and deep-fetch state are cleared. This is used by Bootstrap to ensure a clean slate (unlike Reconcile, which preserves deep state). Observers are notified outside the write lock, following the same pattern as Apply.
type StoreOption ¶
type StoreOption func(*storeOptions)
StoreOption is a functional option for NewStore.
func WithLogger ¶
func WithLogger(l Logger) StoreOption
WithLogger sets a diagnostic logger on the Store. The default is no-op.
type TerminalFlagSet ¶
TerminalFlagSet sets or clears the Terminal flag on an item. When Terminal is true, the poll loop skips deep-fetch for this item as long as its status remains in a cleanup (Done) stage. When Terminal is false, normal deep-fetch evaluation resumes.
type TokenUsage ¶
type TokenUsage struct {
InputTokens int
OutputTokens int
CacheCreationTokens int
CacheReadTokens int
CostUSD float64
TurnsUsed int
MaxTurns int
}
TokenUsage records Claude API token counts for a single invocation. Fields mirror engine.TokenUsage to enable zero-cost assignment in Phase 3-E.
type ValidateCompletedAtSHA ¶ added in v0.0.69
ValidateCompletedAtSHA records the HEAD SHA of the linked PR at the moment stage:Validate:complete was applied. The SHA-invalidation scan uses this to detect force-pushes or external commits after Validate finished.
type ValidateCompletedAtSHACleared ¶ added in v0.0.69
ValidateCompletedAtSHACleared zeroes LinkedPRState.ValidateCompletedSHA after the SHA-invalidation scan fires and removes the stale completion labels.
type WorkerEntered ¶
WorkerEntered sets a non-nil Worker placeholder immediately before the goroutine is launched. This makes snap.Worker() != nil an effective dispatch guard from the instant the goroutine is started, closing the window between goroutine launch and LocalLockAcquired (which fires only after the GitHub lock label is acquired). LocalLockAcquired overwrites the placeholder with full details.
type WorkerExited ¶
WorkerExited clears the Worker handle when a Claude invocation finishes.
type WorkerHandle ¶
type WorkerHandle struct {
PID int
StageName string
StartedAt time.Time
// LastSignAt is updated by worker heartbeats; a stale heartbeat implies the worker died.
LastSignAt time.Time
}
WorkerHandle identifies an in-flight Claude invocation for this item.
type WorkerHeartbeat ¶
WorkerHeartbeat updates the LastSignAt timestamp for the active worker.
type WorkerPIDSet ¶
WorkerPIDSet records the Claude subprocess PID once cmd.Start() returns. No-op if Worker is nil (e.g., WorkerExited arrived first).