prwatch

package
v0.179.1 Latest Latest
Warning

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

Go to latest
Published: Oct 2, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package prwatch is the daemon's watcher for herdr-session-transport (spec/plans/herdr-session-transport.md, Task 6): it discovers pull requests worth watching only through worktrees.ListRegisteredPullRequestBindings — the first reader of the binding `wb pr create` already records via worktrees.RecordClaimPullRequestBinding — and evaluates each one's current state through prsnapshot.Observe, the same renamed-required-check-aware logic `wb wait pr` and `wb ci wait` already use. It never scans GitHub for pull requests WB has no recorded binding for, and it never reimplements check-state interpretation.

Each tick takes exactly one observation per registered binding — never a poll loop, never a foreground wait — and a Watcher remembers the previous tick's classification and head per binding so a checks verdict becomes Terminal only once two consecutive ticks agree: the caller's own outer cadence supplies the confirming reread `wb ci wait` would otherwise take inside one bounded call. A merged or closed pull request needs no such confirmation — GitHub's own state is immediately authoritative — and is Terminal on its first observation.

This package produces Outcomes only; it does not decide what to do with one. Resolving the task's current session and transport identity, the at-most-once delivery-intent coalescing, and the record-only/advisory/ submit decision belong to herdr-session-transport's Task 7, which consumes the Outcomes this package produces.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Kind

type Kind string

Kind classifies one evaluated Outcome so a caller keys its at-most-once delivery intent (task, pull request, head, Kind — herdr-session-transport Plan Task 7) on a closed set of values, never on parsed Reason text.

const (
	// KindChecksPassed is every observed GitHub check green and the target's
	// required-check policy fully satisfied (prsnapshot.Snapshot.Green),
	// confirmed by two consecutive identical ticks.
	KindChecksPassed Kind = "checks-passed"
	// KindChecksFailed is at least one failed or cancelled GitHub check,
	// confirmed by two consecutive identical ticks.
	KindChecksFailed Kind = "checks-failed"
	// KindChecksPending is an open pull request whose checks have not yet
	// reached a verdict: something is still running, or a required check has
	// not registered at all (prsnapshot.Snapshot.Blocked) — the
	// renamed-workflow trap. Never Terminal.
	KindChecksPending Kind = "checks-pending"
	// KindMerged is GitHub's own merged fact. Terminal on the first
	// observation: nothing about a merge needs a confirming reread.
	KindMerged Kind = "merged"
	// KindClosed is a pull request GitHub reports closed without merging.
	// Terminal on the first observation, for the same reason as KindMerged.
	KindClosed Kind = "closed"
	// KindHeadDrift is an open pull request whose head SHA changed since the
	// Watcher's previous tick for this binding. It overrides whatever the
	// checks verdict for the new head would otherwise have been: a streak
	// toward Terminal must not silently carry over past a new push, and the
	// two-observation count starts over from this tick's real classification.
	// Never Terminal.
	KindHeadDrift Kind = "head-drift"
	// KindUnavailable is an observation that failed for an operational
	// reason (a GitHub read error, transient or not) rather than describing
	// pull-request state at all. Never Terminal; the next tick simply tries
	// again. It never counts as a break in an in-progress checks streak —
	// the Watcher's memory of the last real observation is left untouched.
	KindUnavailable Kind = "unavailable"
)

type Outcome

type Outcome struct {
	Task        string
	ClaimID     string
	Repository  string
	PullRequest int
	URL         string
	// Head is the pull request's exact head SHA observed at this tick — part
	// of Task 7's at-most-once delivery-intent key, alongside Task,
	// PullRequest, and Kind. Empty when Kind is KindUnavailable.
	Head string
	// Target is the pull request's exact base branch observed at this tick.
	Target string
	Kind   Kind
	// Terminal is true exactly when this Outcome is ready to act on. See the
	// Kind constants for which values can ever be Terminal.
	Terminal bool
	Reason   string
	// Checks, Failed, Failures, and Blocked are copied from the snapshot
	// unchanged, so Task 8's facts-only template can name a required check
	// (or a count) without re-deriving it from GitHub.
	Checks      map[string]int
	Failed      []string
	Failures    []orchestrate.CIFailureDetail
	Blocked     []string
	EvaluatedAt time.Time
	// Snapshot is the observation this Outcome was classified from, whole, so
	// a caller that shows pull-request state (the cockpit fleet) reads
	// prsnapshot's own facts (State, Merged, Draft, Mergeable, Green) rather
	// than re-deriving any of them. It is the zero value, with Err set, for
	// KindUnavailable.
	Snapshot prsnapshot.Snapshot
}

Outcome is one evaluated registered pull request, at one tick.

type PollResult

type PollResult struct {
	Binding worktrees.RegisteredPullRequestBinding
	Outcome Outcome
	Err     error
}

PollResult pairs one registered binding with the Outcome its evaluation produced, or the error that evaluation hit. One unreadable or unreachable pull request never stops Tick from evaluating the rest. Err is reserved for a binding-level problem (a malformed binding); an ordinary GitHub read failure is reported as an Outcome with Kind KindUnavailable, never as Err.

type Watcher

type Watcher struct {

	// Now stamps Outcome.EvaluatedAt. Tests inject a fixed function so they
	// never depend on a real clock; Evaluate itself takes exactly one
	// observation and makes no timing decision of its own — there is no
	// timer to inject because there is nothing here that waits.
	Now func() time.Time
	// Observe takes one observation; nil means prsnapshot.Observe. A test
	// injects a fake so no unit test reaches GitHub.
	Observe func(ctx context.Context, repository, selector string) prsnapshot.Snapshot
	// Reader, when set, answers every GitHub read of an evaluation instead of
	// the real observer: a test counts and answers them without a process or the
	// network.
	Reader *githubobserver.Reader
	// contains filtered or unexported fields
}

Watcher is the daemon's own PR-outcome watcher. It remembers each registered binding's previous tick's classification and head so a checks verdict becomes Terminal only once two consecutive ticks agree, and so a head that changed between ticks is reported honestly as KindHeadDrift instead of silently restarting the confirmation count on an unconfirmed new observation. It holds no durable state across process restarts: a restarted daemon simply starts its two-observation count over, which is safe because starting over only delays a Terminal verdict by one tick, it never fabricates one.

func NewWatcher

func NewWatcher() *Watcher

NewWatcher returns a Watcher with no remembered ticks.

func (*Watcher) Evaluate

Evaluate takes exactly one observation of binding's pull request through prsnapshot.Observe and classifies it, comparing it against this Watcher's memory of binding's previous tick. It never polls, waits, or takes a second observation itself: the caller's own outer tick cadence supplies the second observation Terminal's two-observation rule needs for a checks verdict.

func (*Watcher) Forget added in v0.175.0

func (w *Watcher) Forget(binding worktrees.RegisteredPullRequestBinding)

Forget drops binding's remembered tick, if any.

func (*Watcher) Retain added in v0.175.0

func (w *Watcher) Retain(bindings []worktrees.RegisteredPullRequestBinding)

Retain drops the memory of every binding that is not in bindings, as Tick does for a binding it no longer lists. A caller that evaluates bindings itself, rather than through Tick, calls it with the bindings it still has.

func (*Watcher) Tick

func (w *Watcher) Tick(ctx context.Context, projectsRoot string) ([]PollResult, error)

Tick runs one evaluation pass — one observation per binding, no poll loop of its own — over every pull request WB has a recorded binding for: worktrees.ListRegisteredPullRequestBindings, never a fleet-wide GitHub scan. The caller decides the outer cadence; Tick itself runs exactly one pass and returns. Call Tick repeatedly on the same Watcher so its two-observation rule (see the Kind constants) can confirm a checks verdict across calls.

Jump to

Keyboard shortcuts

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