gate

package
v0.4.3 Latest Latest
Warning

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

Go to latest
Published: Oct 13, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package gate holds the rules about when a ticket may move.

These are pure functions over already-loaded state, deliberately separated from the store. Two reasons: they can be unit-tested without touching a filesystem, and there is exactly one file to change when a rule changes — the CLI and the TUI both call in here rather than each carrying their own copy, which is the only way "enforced identically by both interfaces" is true rather than merely intended.

Index

Constants

View Source
const (
	CodeMissingField    = "missing_field"
	CodeBlocked         = "blocked"
	CodeUnknownLane     = "unknown_lane"
	CodeNoSuchLane      = "no_such_lane"
	CodeMissingOutcome  = "missing_outcome"
	CodeNeedsSignal     = "needs_nonmodel_signal"
	CodeNeedsQuestion   = "needs_question"
	CodeNeedsReason     = "needs_blocked_reason"
	CodeNeedsCommits    = "needs_commits"
	CodeSelfBlock       = "self_block"
	CodeMissingProduces = "missing_lane_output"
	CodePlanIncomplete  = "plan_incomplete"
	CodeNeedsHuman      = "needs_human"
	CodeStepNotChosen   = "step_not_chosen"
	CodeNotOwner        = "not_owner"
)

Violation codes. Agents branch on these, so they are stable strings rather than prose that might be reworded.

Variables

PromotionFields are required before a ticket may leave the backlog.

The point of the gate is that capture should be frictionless while execution should not be: anything can be thrown into the backlog, but nothing starts without a goal, a definition of done, the context it came from, and a human who owns the outcome. Acquiring those is the price of admission to a run.

Functions

func Actionable

func Actionable(env Env, t *ticket.Ticket) bool

Actionable reports whether a ticket could be picked up right now.

func DeclaredBy added in v0.1.1

func DeclaredBy(set *lane.Set) map[string][]string

DeclaredBy answers which installed lanes declare each producible field, in board order. It is what a renderer labels a FILLED value with, so it must not assert more than the frontmatter can back: the declaration is the only provenance the file carries — which lane actually wrote the latest value is the History's job once the schema grows one. That is why EVERY declarer is returned rather than the first: on a board where critique and review both produce review-summary, labelling a review-written value "(critique)" is a lie on the one screen where a person accepts work, while "(critique/review)" is true in every configuration.

func InputMissing added in v0.3.7

func InputMissing(l *lane.Lane, t *ticket.Ticket) []string

InputMissing names the inputs a lane declares that this ticket has not filled and that no earlier lane could be skipped for — fields in ticket.SuppliedFields are left out, because the ticket carries those from creation and their absence is reported as a promotion field instead. What remains is what an earlier lane was meant to produce: a ticket created straight into in-progress has no plan, and this is what says so.

func OutputOwed added in v0.1.1

func OutputOwed(l *lane.Lane, t *ticket.Ticket) []string

OutputOwed lists the fields lane l declares it produces that ticket t has not filled in yet — in other words, what the lane still owes before the ticket may advance past it.

This is the same computation that refuses a forward move above, exported because it answers a second question the board needs: whether the lane has been run on this ticket at all. Nine tickets once sat in a critique lane uncritiqued and looked exactly like the two that had been through it, because nothing but the empty field said so.

A lane the ticket has opted out of owes nothing, for the same reason the move is not refused: the step does not apply to this ticket.

func OwedBy added in v0.1.1

func OwedBy(set *lane.Set, t *ticket.Ticket) map[string]string

OwedBy answers, for every field an installed lane declares it produces and this ticket has not filled in, which lane still owes it. It is the display side of OutputOwed: a renderer showing a field's value has to decide what to put there when the value is empty, and "nothing" is the wrong answer — a ticket that reached review without its outcome fields then looks exactly like one that was worked, which is the state a reviewer most needs to see.

Lanes are walked in board order and the first producer of a field wins, the same precedence checkContracts uses when it names a producer, so a field two lanes declare is attributed to the one that should have written it first. A lane the ticket has opted out of owes nothing, because OutputOwed says so.

Some declared fields live in the body as a checklist rather than as one frontmatter value — plan is the one shipped lane contract that does — and they have no label-and-value row for a caller to stand a debt in. They are reported here all the same, because the question "which lane owes this" has the same answer either way; a renderer showing rows is expected to skip them and let the empty checklist say it, rather than print a second, worse version of the same fact.

func Ready

func Ready(t *ticket.Ticket) bool

Ready reports whether a ticket satisfies the promotion gate, without reference to any particular target lane.

Types

type Env

type Env struct {
	Lanes *lane.Set
	// All is every ticket, used to evaluate blockers. Only id and status are read.
	All []*ticket.Ticket

	// DeriveCommits works out which commits carry a ticket, by asking git,
	// when the ticket records none of its own. It is injected rather than
	// this package importing core/gitrepo directly, so gate stays the pure,
	// filesystem-free package its doc comment promises and its tests can keep
	// building Env without a git repository on disk. nil means no derivation
	// is on offer, and the requires-commits refusal below stands exactly as
	// it did before this existed.
	DeriveCommits func(t *ticket.Ticket) []string

	// Satisfied answers whether a ticket that is not in All has nevertheless
	// been finished and filed away — in the logbook, or in the archive in a
	// terminal lane. It is injected for the same reason DeriveCommits is:
	// finding that out means reading directories, and this package stays
	// free of the filesystem.
	//
	// Without it, a dependency that completed and was filed read as "does not
	// exist in this store" and blocked the ticket waiting on it forever —
	// finishing the blocker was the thing that broke the board. nil keeps the
	// old behaviour, which is right for a caller that has no store.
	Satisfied func(id string) bool
}

Env is the surrounding state a decision needs.

type Request

type Request struct {
	To string

	// Question accompanies a move into a lane that requires one.
	Question string

	// Reason accompanies a move into a lane that will not take a ticket without
	// knowing what it is waiting on.
	Reason string

	// Actor is who is performing the move.
	Actor string

	// ActorAliases are the other names that mean the same person as Actor: a
	// git user.email as well as a user.name, a work address as well as a
	// personal one. A person is not one string, and the ownership rail below
	// compares strings — without this, your own tickets read as someone
	// else's and every move needs --force, which trains the override rather
	// than protecting anything. Callers fill it from identity.Aliases; empty
	// means "Actor is the only name I go by".
	ActorAliases []string

	// Interactive marks a move a person made by hand, in the board. It is the
	// only distinction available that an agent cannot simply assert: an agent
	// drives the CLI, and cannot press a key in someone's terminal. Lanes that
	// require a human to release a ticket rely on it.
	Interactive bool

	// NewAssignee accompanies a move that also hands the ticket to someone
	// else. A hand-over is always allowed, regardless of who currently owns
	// the ticket — otherwise an absent owner could freeze it forever.
	NewAssignee string
}

Request describes a proposed move.

type Violation

type Violation struct {
	Code    string `json:"code"`
	Field   string `json:"field,omitempty"`
	Message string `json:"message"`
}

Violation is one reason a move was refused. Field is set when the fix is to supply a value, so an agent can act on it without parsing the message.

func (Violation) String

func (v Violation) String() string

type Violations

type Violations []Violation

Violations is an ordered set of refusals.

func CheckAdvance

func CheckAdvance(env Env, t *ticket.Ticket, req Request) Violations

CheckAdvance decides whether t may move to req.To.

func (Violations) Err

func (vs Violations) Err() error

func (Violations) Fields

func (vs Violations) Fields() []string

Fields lists the field names that need filling in.

Jump to

Keyboard shortcuts

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