transition

package
v0.22.1 Latest Latest
Warning

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

Go to latest
Published: Sep 14, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package transition is the single owner of the gadak transition write (CLI, including `gadak close`, and REST). Both surfaces call Apply so identifier resolution, required screen fields, resolution lookup, field-alias remap, category-token idempotency, and the origin write cannot exist on one path and not the other.

Refused errors carry catalogue data only. The CLI formats flag names; REST maps them to HTTP 400. This package does not name CLI flags.

Mirror refresh is not here: it is the write-through tail shared by every write verb (CLI mutate / server mutate), not a property of this verb.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DuplicateDestinations added in v0.22.0

func DuplicateDestinations(list []jira.Transition) [][]jira.Transition

DuplicateDestinations reports the groups of transitions whose destination status shares a display name and a category — the shape that makes a category token fold (GDK-1356), and the shape a reader staring at two identical rows in `gadak transition KEY` needs named rather than inferred. Groups are in payload order and always hold two or more members; a workflow with no duplicates returns nothing.

func FormatDuplicateDestinations added in v0.22.0

func FormatDuplicateDestinations(list []jira.Transition) string

FormatDuplicateDestinations renders DuplicateDestinations as one line per group, or "" when there are none.

func FormatNamedIDs

func FormatNamedIDs(list []jira.NamedID) string

FormatNamedIDs joins a catalog's names (ids where the name is empty) with ", " — the "available: …" list in resolution errors. Single owner; the `gadak agent` / `gadak edit` surfaces import it (GDK-619).

func FormatTransition added in v0.22.0

func FormatTransition(t jira.Transition) string

func IsRefused

func IsRefused(err error) bool

IsRefused reports whether err is a caller-side refusal (bad identifier, missing required field, unknown resolution). Origin errors are not.

func JoinTransitions added in v0.22.0

func JoinTransitions(list []jira.Transition) string

func MirrorStatusUse added in v0.22.0

func MirrorStatusUse(ctx context.Context, db *store.DB, key string) func(string) int

MirrorStatusUse answers "how many issues does this project actually hold in that status", from the mirror, which is local and free. It is the tiebreak between two destination statuses that display the same name in the same category (GDK-1356): the gdk workspace carries an In Progress the 2026-09-01 cutover left behind with zero issues in it, beside the In Progress the board shows. nil when there is no mirror or no project key to scope by — the pick then falls back to payload order.

It lives in this package, not next to the CLI that first passed it (GDK-1521), because all three write surfaces need the identical answer: `gadak transition`, the REST transition write, and `gadak claim`'s Cloud fallback. internal/store does not import internal/transition, so the dependency closes without a cycle.

func PickTransition added in v0.22.0

func PickTransition(key, want string, list []jira.Transition) (string, error)

PickTransition resolves want with no mirror and no current-status read. See PickTransitionWith for the resolution order.

func PickTransitionWith added in v0.22.0

func PickTransitionWith(key, want string, list []jira.Transition, opt PickOptions) (string, error)

PickTransitionWith resolves want against the issue's available transitions. Order: transition id, target status id, transition name / target status name, then category tokens (new|inprogress|done). Two *distinguishable* landings in the same category refuse rather than picking the first; two landings the reader cannot tell apart — same destination status name, same category — are one landing and fold (GDK-1356). A token that is one transition's id and a different transition's to.id is also refused.

func Preview

func Preview(ctx context.Context, o Origin, key, target string, statusUse func(statusID string) int) (id string, changed bool, err error)

Preview answers what Apply would do without writing: the resolved transition id when one would fire (changed=true), or the category no-op (changed=false, empty id). It exists so a dry-run cannot drift from the real write — both run resolveTransition. A pick miss that is not a no-op returns the pick error. statusUse is Request.StatusUse: a dry run that skipped it could name a different transition id than the write, which is the one thing a dry run must not do.

func ReachableCategories added in v0.22.0

func ReachableCategories(list []jira.Transition) []string

func StatusCategoryToken added in v0.22.0

func StatusCategoryToken(s string) (string, bool)

StatusCategoryToken accepts only the three values data-model.md documents. Category and jql.mapStatusCategory both fold aliases (todo, indeterminate) onto those values; applying either to the user token would reopen the localization trap this command is closing. Apply's category no-op uses this same function so a token PickTransition would refuse cannot no-op.

Types

type AmbiguousTransitionError added in v0.22.0

type AmbiguousTransitionError struct {
	Key        string
	Want       string
	Candidates []jira.Transition
	// Folded are the candidates a Candidate stands in for: same destination
	// name, same category, so naming one of them could not have told the two
	// readings apart (GDK-1356). Empty unless a category token folded. They
	// are still reachable by transition id or target status id, and the
	// message says so — a reader who saw them in `gadak transition KEY` must
	// not conclude gadak stopped seeing them.
	Folded []jira.Transition
}

AmbiguousTransitionError is the refusal when want lands on more than one transition (GDK-1174: a board with two in-progress statuses makes every bare `gadak claim` land here). The message is unchanged from the old fmt.Errorf; the type exists so a caller that owns a disambiguation flag can name its recourse — this package still never names CLI flags.

func (*AmbiguousTransitionError) Error added in v0.22.0

func (e *AmbiguousTransitionError) Error() string

type Origin

type Origin interface {
	Transitions(ctx context.Context, key string) ([]jira.Transition, error)
	Transition(ctx context.Context, key, transitionID string, fields map[string]any, comment json.RawMessage) error
}

Origin is the origin verbs Apply needs. origin.Writer satisfies it.

type PickOptions added in v0.22.0

type PickOptions struct {
	// CurrentStatusID is the issue's status id at resolution time. Inside a
	// group of same-named destinations it rules that member out: "put me in
	// In Progress" cannot mean the In Progress the issue already sits in.
	// A lone candidate is never ruled out this way — dropping it would let
	// a category token mean some *other* status of that category, which is a
	// worse answer than the self-loop the workflow actually offers.
	CurrentStatusID string

	// StatusUse reports how many issues the local mirror holds in statusID.
	// It breaks the tie inside a folded group only: of two statuses that
	// display the same name in the same category, the one the project is
	// actually using is the one the board shows and the reader means. It is
	// consulted at most once per member of a group larger than one, so the
	// common path never calls it. nil leaves payload order deciding.
	StatusUse func(statusID string) int
}

PickOptions carries the two facts the transitions payload does not hold. Both are optional: the zero value is name folding on payload order, which is what a caller with no mirror and no status read gets.

type Refused

type Refused struct {
	Msg string
	// Err is the refusal this was built from, when one exists — kept so a
	// caller can still match the concrete kind (AmbiguousTransitionError
	// under gadak claim, GDK-1174) through the type adapters map on.
	Err error
}

Refused is a caller-side refusal: the origin was not written. Adapters map this to a 400 / CLI message; origin errors pass through unchanged.

func (*Refused) Error

func (e *Refused) Error() string

func (*Refused) Unwrap added in v0.19.1

func (e *Refused) Unwrap() error

type Request

type Request struct {
	Key        string
	Target     string
	Resolution string
	Fields     map[string]any
	Comment    string
	// StatusUse is an optional read of the local mirror: how many issues sit
	// in statusID right now. It is consulted only to break a tie between two
	// destination statuses that display the same name in the same category
	// (GDK-1356) — never to decide what Target means. nil is fine, and is
	// what a caller without a mirror handle passes.
	StatusUse func(statusID string) int
}

Request is one transition write. Target is whatever PickTransition accepts: transition id, target status id, transition/status name, or a status category token (new|inprogress|done). Fields are already parsed (CLI --field, REST JSON); aliases are remapped here. Resolution is a name or id; a name is resolved from the transition's allowedValues, else the origin's resolution catalog.

type RequiredFieldsError

type RequiredFieldsError struct {
	Key             string
	TransitionName  string
	FormattedFields []string
}

RequiredFieldsError is a Refused whose screen lists required fields the request did not supply. CLI appends flag names; REST uses Error as-is.

func (*RequiredFieldsError) Error

func (e *RequiredFieldsError) Error() string

type Result

type Result struct {
	Changed bool `json:"changed"`
}

Result is a successful Apply. Changed is false when Target was a category token and the origin already reports that category: no POST (and no comment). A named or id miss is still an error, not a no-op.

func Apply

func Apply(ctx context.Context, o Origin, cfg *config.Config, req Request) (Result, error)

Apply lists the issue's transitions, picks one, assembles fields, and calls the origin. A category-token target (new|inprogress|done) that the origin already reports is a no-op success (Changed=false). It does not refresh the mirror — that tail is mutate.

Jump to

Keyboard shortcuts

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