task

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package task implements the nova-sprint task transitions that are one Redis Function call each: push is create-only (#2756 3.1, control 3) and take claims with an attempt and a fenced token (#2756 4.2, control 4). Neither verb writes state with separate HSET and XADD calls (spec 2.1 rule 2).

Index

Constants

View Source
const (
	CostUSDField       = "cost_usd"
	CostTokensInField  = "tokens_in"
	CostTokensOutField = "tokens_out"
	CostModelField     = "model"
	CostRouteField     = "route"
	// CostUnmetered opens the clause of a cost that was not measured.
	CostUnmetered = "cost: unmetered"
)

Evidence field names of a metered cost.

View Source
const (
	FunctionPush = "ns_task_push"
	FunctionTake = "ns_task_take"
	FunctionDone = "ns_task_done"
	// FunctionTakeDenied writes the one receipt of a take the CLI refused
	// because --as is not the initiator (#2929 rev 6). It touches no task key.
	FunctionTakeDenied = "ns_task_take_denied"
	// FunctionTakeView is the read-only half of a batched take (#3261): every
	// open sprint's queue for the friend and the task fields its rank needs.
	FunctionTakeView = "ns_task_take_view"
	// FunctionTakeN claims a ranked candidate list through ns_task_take's
	// guard until n are claimed, in one call (#3261).
	FunctionTakeN = "ns_task_take_n"
)

Function names registered by internal/nsprint/fn/lua/task_claim.lua and internal/nsprint/fn/lua/task_take.lua. The library is `nova_sprint` (fn.Library); the loader prepends its header.

View Source
const (
	TakeClaimed = "CLAIMED"
	// TakeBlocked is a task whose needs are not all closed (#2939).
	TakeBlocked = "BLOCKED"
)

TakeStatus words returned by ns_task_take.

View Source
const FunctionBeat = "ns_task_beat"

FunctionBeat is registered by internal/nsprint/fn/lua/task_beat.lua. The library is `nova_sprint` (fn.Library); the loader prepends its header.

View Source
const FunctionCancel = "ns_task_cancel"

FunctionCancel is registered by internal/nsprint/fn/lua/task_beat.lua.

View Source
const FunctionFriendDown = "ns_friend_down"

FunctionFriendDown is the function task_queue.lua registers.

View Source
const FunctionList = "ns_task_list"

FunctionList is the read-only FCALL behind ListStore (nova-tools #3263).

View Source
const FunctionLive = "ns_task_live"

FunctionLive is the read-only snapshot of live tasks registered by internal/nsprint/fn/lua/task.lua (nova-tools #3067).

Variables

View Source
var ErrNoSprint = errors.New("no --sprint and sprint:order is empty")

ErrNoSprint is a verb run with no --sprint while sprint:order is empty.

View Source
var ErrNotFriend = errors.New("want NOVA_FRIEND in friends")

ErrNotFriend is an initiator (or a take's --as) that is not a member of `friends` (#2929 rev 6). The CLI maps it to exit 2 `want NOVA_FRIEND in friends`; nothing is written and no receipt is made.

Functions

func DenyTake

func DenyTake(ctx context.Context, st *store.Store, initiator, as, sprint, id, idem string) (string, error)

DenyTake records a take the CLI refused because --as is not the initiator (#2929 rev 6): one pipeline reads the initiator's `friends` membership (and the default sprint when sprint is empty), then ns_task_take_denied writes exactly one receipt `kind=task take denied actor=<initiator> for=<as> reason=as-not-initiator`. The task is untouched. A non-member initiator returns ErrNotFriend with no receipt. It returns the sprint it wrote to.

func IsValidEst

func IsValidEst(s string) bool

IsValidEst reports whether s is a valid task estimate: a whole number of minutes 1..10080 (at most one week), matching ^[1-9][0-9]{0,4}$ as text.

func Key

func Key(sprint, id string) string

Key is the task hash task:<id>.

func ParseTitle

func ParseTitle(title, defaultRepo string) (paths, deps []string)

ParseTitle reads PATHS and DEPENDS-ON from a task title. Each path is repo-qualified as repo:path; a `repo:` token switches the repo for the tokens after it, and untagged tokens take defaultRepo. Parentheticals and the empty markers "-" and "none" are dropped.

func ParseTitleEst

func ParseTitleEst(title string, errOut io.Writer) string

ParseTitleEst extracts an estimate from a task title in the form `est: <v> min`. If found and in-domain, it returns the estimate string. If found but outside the domain 1..10080, it writes a warning to errOut and returns "". If not found, it returns "".

func PathsIntersect

func PathsIntersect(a, b string) bool

PathsIntersect is the check-cut.py match rule over repo-qualified paths: an empty repo matches any repo; equal paths, a directory (with or without a trailing slash, or /** or /*) over a path beneath it, and a glob over a path it matches all intersect.

func PayloadSHA

func PayloadSHA(req PushRequest) string

PayloadSHA is the create-only identity of a push payload. The writer column is the same for every caller, so a re-push with the same fields compares equal and a changed field does not (spec 2.1 rule 4, rule 7).

func RandomToken

func RandomToken() (string, error)

RandomToken returns the 128 random bits of a fence token as 32 lowercase hex characters. Redis Lua has no secure random, so Go supplies this half and the function composes <attempt>.<random> atomically (spec 2.1 rule 8).

func ReviewID

func ReviewID(repo string, pr int, head, friend string) string

ReviewID is the fixed identity of a review task, read-<repo>-<pr>-<head12>- <friend> (spec 2.1 rule 7). A new head is a new identity, never a reopened task.

func TitleField

func TitleField(title, name string) (string, bool)

TitleField returns the text of the `| NAME: ...` segment of a title. A segment ends only where the next `| NAME:` begins, so a DONE-WHEN that quotes `-run \x27A|B\x27` is not cut at its own pipes.

func WithCost

func WithCost(evidence string, c Cost) string

WithCost appends the cost clause to evidence, once: evidence that already carries a cost clause is returned unchanged so a repeated done stays identical (exit 0 CLOSED, never CONFLICT). Empty evidence stays empty: a cost is not evidence, and the done is refused NOEVIDENCE.

Types

type BeatRequest

type BeatRequest struct {
	Sprint string
	ID     string
	Token  string
	Actor  string
	Idem   string
}

BeatRequest is one task beat (spec 4.2). The process that started the child sends it, every 60 s, never the model (Johnny 2). The first beat of a claimed task is the start acknowledgement (spec 3.1).

type BeatStatus

type BeatStatus string

BeatStatus is the outcome of one beat.

const (
	// BeatWorking is the first beat: claimed -> working (start ack). The
	// identity stays in the friend's one working set (#3998): a beat moves
	// nothing but the task's beat_at.
	BeatWorking BeatStatus = "WORKING"
	// BeatBeating refreshes the lease and beat_at of a working task.
	BeatBeating BeatStatus = "BEAT"
	// BeatFenced is a missing or superseded token (exit 3).
	BeatFenced BeatStatus = "FENCED"
	// BeatNotFound is an unknown task id.
	BeatNotFound BeatStatus = "NOTFOUND"
	// BeatInvalid is a beat on a task that is not claimed or working.
	BeatInvalid BeatStatus = "INVALID"
)

func Beat

func Beat(ctx context.Context, st *store.Store, req BeatRequest) (BeatStatus, error)

Beat acknowledges the start of a claimed task or refreshes a working one. The token check, the index and global lease moves, Redis TIME and the one receipt are a single atomic Redis Function call. The token is checked first, so a beat from an attempt the reconciler already superseded refuses FENCED and never renews the new attempt (spec 2.1 rules 2 and 8).

func (BeatStatus) ExitCode

func (s BeatStatus) ExitCode() int

ExitCode maps a beat outcome to its CLI exit code (spec 2.1 rule 8: a superseded or missing token refuses with exit 3 FENCED).

type BlockedError

type BlockedError struct {
	Sprint string
	ID     string
	Needs  []string
}

BlockedError is a take refused because the task needs ids that are not closed yet (#2939). `task take --id` prints it as BLOCKED needs <ids> and exits 7; a take without --id passes over the task.

func (*BlockedError) Error

func (e *BlockedError) Error() string

type CancelRequest

type CancelRequest struct {
	Sprint string
	ID     string
	Token  string
	Reason string
	Actor  string
	Idem   string
}

CancelRequest is one task cancel (spec 4.2): the owner gives a claimed or working task back. Reason is recorded in the receipt and in the unresolved item for an external-effect task.

type CancelStatus

type CancelStatus string

CancelStatus is the outcome of one cancel.

const (
	// CancelOpen is claimed/working -> open for effects none or idempotent,
	// with the reservation freed (spec 3.1 row 8).
	CancelOpen CancelStatus = "OPEN"
	// CancelReconcile is claimed/working -> reconcile-required for effects
	// external: slot freed and an unresolved item written, nothing replayed
	// (spec 3.1 row 9).
	CancelReconcile CancelStatus = "RECONCILE"
	// CancelFenced is a missing or superseded token (exit 3).
	CancelFenced CancelStatus = "FENCED"
	// CancelNotFound is an unknown task id.
	CancelNotFound CancelStatus = "NOTFOUND"
	// CancelInvalid is a cancel of a task that is not claimed or working.
	CancelInvalid CancelStatus = "INVALID"
)

func Cancel

func Cancel(ctx context.Context, st *store.Store, req CancelRequest) (CancelStatus, error)

Cancel gives a claimed or working task back. The token check, the lease and index moves, the unresolved evidence for external effects, Redis TIME and the one receipt are a single atomic Redis Function call. The stored token is replaced with the fence marker before the reopen, so the old token's Done refuses with exit 3 (spec 2.1 rules 2 and 8).

func (CancelStatus) ExitCode

func (s CancelStatus) ExitCode() int

ExitCode maps a cancel outcome to its CLI exit code (spec 2.1 rule 8).

type Claim

type Claim struct {
	Sprint  string
	ID      string
	Attempt int
	Token   string
	// Kind, Ref and Title come from the claim reply itself, so the CLI's
	// TASK line costs no extra round trip (#2929).
	Kind  string
	Ref   string
	Title string
}

Claim is one successful claim. Token is the attempt's fence: every later call for that attempt must present it or refuse with exit 3 FENCED (spec 2.1 rule 8).

func Take

func Take(ctx context.Context, st *store.Store, req TakeRequest) (Claim, bool, error)

Take claims one task for a friend. The guard, the attempt increment, the token, the index move and the one receipt are a single atomic Redis Function call, so two concurrent takes of one task yield exactly one owner and one receipt (#2756 control 4). ok is false when the task is not open or not in this friend's queue.

func TakeAvailable

func TakeAvailable(ctx context.Context, st *store.Store, as, sprint, id string, limit int, actor, idem string) ([]Claim, error)

TakeAvailable claims assigned work in sprint order, with front items first within each sprint. A zero limit means all currently free friend slots. Every individual claim still happens in the guarded Redis Function. actor is recorded on each take receipt as given (the CLI passes the initiator); as is the receipt's `for`.

It costs two round trips however many sprints and claims (#3261; it was 4 + S + 2k): one pipeline reads the `friends` membership of as, its desired slots, its working set (#3998) and ns_task_take_view (every open sprint's queue for as and the task fields its rank needs); Go ranks in memory (deal.RankSnapshot); one ns_task_take_n call claims down the ranked list until the free slots fill. A non-member returns ErrNotFriend before any claim (#2929 rev 6). Only a candidate whose attempt moved between the two round trips (RETRY, a race) costs a further single take.

type Cost

type Cost struct {
	Metered   bool
	USDMicro  int64
	TokensIn  int64
	TokensOut int64
	Model     string
	Route     string
	// Reason says why an unmetered cost was not measured (for example
	// "plan-billed": a main-session read on a plan has no per-read $).
	Reason string
}

Cost is what one task cost the friend who did it, carried in the task's done evidence (nova-tools #3105, #2756 spec 4.1.1, control 55). A metered cost names dollars, tokens in and out, the model and the route. A cost that was not measured is `cost: unmetered <reason>`: an absence, never $0, so a fold that sums reads prints the unmetered count beside the dollars.

func ParseCost

func ParseCost(evidence string) (Cost, bool, error)

ParseCost reads the cost clause out of done evidence. found is false when the evidence names no cost at all: that is unmetered, and a caller that sums must never count it as $0. A cost_usd that is not a dollar amount, or a token count that is not a whole number, is an error, not a zero. `cost_usd=-` is an explicit absence and parses as unmetered.

func (Cost) Clause

func (c Cost) Clause() string

Clause is the cost as the evidence text a done carries. Empty model, route or reason print as the dash, so every field is always present.

type DoneOutcome

type DoneOutcome struct {
	Status DoneStatus
	Why    string
	Record []string
}

DoneOutcome is one done with its typed record: Why on REFUSED, Record the ingest reply after RECORD on a typed close.

func DoneTyped

func DoneTyped(ctx context.Context, st *store.Store, req DoneRequest) (DoneOutcome, error)

DoneTyped is Done with the typed record: a request with Typed set closes a review only when its line is recorded in the same call.

type DoneRequest

type DoneRequest struct {
	Sprint   string
	ID       string
	Token    string
	Evidence string
	Verdict  string
	Score    string
	Head     string
	Actor    string
	Idem     string
	// Cost, when set, is appended to Evidence as its cost clause (#3105).
	// A read done without one is unmetered in the fold, never $0.
	Cost *Cost
	// Typed, when set, is the review's typed DISPOSITION line as the shared
	// parser (internal/nsprint/disposition) read it from the posted comment
	// (task done --body-file, nova-tools #3092 rev 7). Its head, verdict and
	// score are the close's; ns_task_done records it through
	// ns_ingest_disposition's body in the same atomic call as the close.
	Typed *TypedLine
	// As closes without a token when As owns the claimed or working lease
	// (#3206 PR A, the friend-queue `done --as` shape).
	As string
}

DoneRequest is one task done (spec 4.2). A review task's Evidence must name a verdict, a score and a head equal to the task head.

type DoneStatus

type DoneStatus string

DoneStatus is the outcome of one done.

const (
	// DoneClosed is the transition claimed/working -> closed.
	DoneClosed DoneStatus = "DONE"
	// DoneRepeat is a repeated identical done of a closed task (exit 0).
	DoneRepeat DoneStatus = "CLOSED"
	// DoneFenced is a missing or mismatched token (exit 3).
	DoneFenced DoneStatus = "FENCED"
	// DoneConflict is different evidence on a terminal task (exit 4).
	DoneConflict DoneStatus = "CONFLICT"
	// DoneNoEvidence refuses a done with no evidence.
	DoneNoEvidence DoneStatus = "NOEVIDENCE"
	// DoneInvalid refuses a review done without verdict, score or head.
	DoneInvalid DoneStatus = "INVALID"
	// DoneRefused refuses a typed close: the line is not a DISPOSITION, its
	// who is not the task owner, or ns_ingest_disposition refused it (the
	// reason is DoneOutcome.Why). Nothing is written (exit 2).
	DoneRefused DoneStatus = "REFUSED"
)

func Done

func Done(ctx context.Context, st *store.Store, req DoneRequest) (DoneStatus, error)

Done closes a claimed or working task. The token check, the evidence check, the disposition write, the index move and the one receipt are atomic.

func (DoneStatus) ExitCode

func (s DoneStatus) ExitCode() int

ExitCode maps a done outcome to its CLI exit code (spec 4.2, 2.1 rule 8).

type Effects

type Effects string

Effects is a task's effect class (spec 4.2: none, idempotent, external).

const (
	EffectsNone       Effects = "none"
	EffectsIdempotent Effects = "idempotent"
	EffectsExternal   Effects = "external"
)

type Field

type Field struct {
	Value string
	Set   bool
}

Field is one HMGET cell: Set is false for a nil reply.

type Kind

type Kind string

Kind is a task kind (spec 4.2: read, review, harvest, fix, work).

const (
	KindRead    Kind = "read"
	KindReview  Kind = "review"
	KindHarvest Kind = "harvest"
	KindFix     Kind = "fix"
	KindWork    Kind = "work"
	// KindRebase and KindRecut are the rebalance kinds added by #3206 PR A
	// (only read, fix, rebase and recut move between friends).
	KindRebase Kind = "rebase"
	KindRecut  Kind = "recut"
	// KindMerge is a stream's merge card (nova-tools #4324): the reconciler's
	// land watch cuts one per stream with members in merging, to a friend
	// advertising the frontier type; its child lands the stream as one PR.
	KindMerge Kind = "merge"
)

type ListRequest

type ListRequest struct {
	Sprint string
	As     string
	State  string
}

ListRequest selects one friend's tasks. A state filter is optional; without one, only open, claimed, working and waiting tasks are returned.

type Overlap

type Overlap struct {
	ID        string
	With      string
	Path      string
	OtherPath string
}

Overlap names the live task a refused build push collides with: the pushed id, the other id (sprint/id when it lives in another sprint), and the two PATHS entries that intersect.

func (*Overlap) String

func (o *Overlap) String() string

String is the one refusal line; it always prints both ids.

type PushRequest

type PushRequest struct {
	Sprint     string
	ID         string
	Kind       Kind
	Title      string
	Effects    Effects
	Repo       string
	PR         int
	Head       string
	Ref        string
	To         string
	Front      bool
	Priority   int
	PayloadSHA string
	Actor      string
	Idem       string
	Est        string
	// Needs are the ids in Sprint that must be closed before this task can
	// be claimed (#2939). Empty needs leave PayloadSHA as it was.
	Needs []string
	// DependsOn is the task's DEPENDS-ON list (#3206 PR A, ruling #3516):
	// conditions joined by ';' ("none" or empty for none). While any is
	// unmet the task is waiting and in no ready queue.
	DependsOn string
	// Author is the read rule's author (rebalance never moves a read to it).
	Author string
	ErrOut io.Writer
	// Initiator is the seat running the verb ($NOVA_FRIEND). Only
	// deprecated/cmd/nova-sprint sets it: when set, PushChecked reads its `friends`
	// membership in its first round trip and returns ErrNotFriend on a 0,
	// before any write (#2929 rev 6). With Sprint empty, the same round trip
	// reads the default sprint (lowest-score member of sprint:order). A library
	// caller leaves it empty and keeps dev's behaviour.
	Initiator string
}

PushRequest is one create-only task push (spec 4.2). PayloadSHA is the create-only identity of the payload; when empty it is derived from the request, so an identical re-push is recognized as the same task.

type PushResult

type PushResult struct {
	Status  PushStatus
	Overlap *Overlap
	// Down is the friend:<to>:down marker value when Status is DOWN.
	Down string
	// Reason is the function's reason word on INVALID, when it gave one
	// (`unknown friend` for a --to not in friends).
	Reason string
	// Sprint is the sprint the push went to (the default when none was given).
	Sprint string
	// Waiting is the number of unmet DEPENDS-ON conditions of a CREATED task
	// that is waiting; 0 when it is ready.
	Waiting int
	// OnMet is a CREATED task whose DEPENDS-ON conditions all held at push.
	OnMet bool
}

PushResult is a push outcome with the colliding task when it is OVERLAP.

func PushChecked

func PushChecked(ctx context.Context, st *store.Store, req PushRequest) (PushResult, error)

PushChecked is Push with the build-task path lint (#3067) reported: a build task whose PATHS intersect a live build task's PATHS outside a DEPENDS-ON chain returns OVERLAP naming the other task, and nothing is written.

type PushStatus

type PushStatus string

PushStatus is the outcome of one push.

const (
	// PushCreated wrote the task hash, its queue place and one receipt.
	PushCreated PushStatus = "CREATED"
	// PushExists is the identical payload of a live task; nothing was written.
	PushExists PushStatus = "EXISTS"
	// PushClosed is the identical payload of a terminal task; nothing was
	// written and the task never reopens (spec 3.1 row 1, rowan-tools #145).
	PushClosed PushStatus = "CLOSED"
	// PushConflict is a different payload for an existing id (exit 4).
	PushConflict PushStatus = "CONFLICT"
	// PushInvalid is a review task without a repo, PR and head.
	PushInvalid PushStatus = "INVALID"
	// PushOverlap is a build task whose PATHS intersect a live build task's
	// PATHS outside a DEPENDS-ON chain; nothing was written (#3067, exit 5).
	PushOverlap PushStatus = "OVERLAP"
	// PushDown is a push to a friend whose friend:<to>:down marker is set;
	// nothing was written and no receipt was made (#2929 rev 5, exit 7).
	PushDown PushStatus = "DOWN"
)

func Push

func Push(ctx context.Context, st *store.Store, req PushRequest) (PushStatus, error)

Push issues one create-only push. The guard, the task hash, the queue move and the single receipt are one atomic Redis Function call.

func (PushStatus) ExitCode

func (s PushStatus) ExitCode() int

ExitCode maps a push outcome to its CLI exit code (spec 4.2).

type Reply

type Reply struct {
	Status string
	Args   []string
}

Reply is one function reply: the status word and the words after it.

func FriendDown

func FriendDown(ctx context.Context, st *store.Store, friend string, on bool, reason, actor, idem string) (Reply, error)

FriendDown sets (on) or clears friend:<f>:down, a HASH {reason, actor, at}. A down friend\x27s push and take are refused.

type Row

type Row struct {
	Sprint string
	ID     string
	State  string
}

func ListStore

func ListStore(ctx context.Context, st *store.Store, req ListRequest) ([]Row, error)

type TakeRequest

type TakeRequest struct {
	Sprint string
	ID     string
	As     string
	Actor  string
	Idem   string
}

TakeRequest is one task take (spec 4.2). As is the consumer/friend name.

type TypedLine

type TypedLine struct {
	Type        string // DISPOSITION (a REPAIR refuses a review close)
	Who         string
	Head        string // 40 hex
	Verdict     string // APPROVE | HOLD
	Score       string // 1-10
	Kind        string // explicit kind=, or empty
	KindDerived string // the classifier's kind for a HOLD with no kind=
	Scope       string
	Reason      string
	URL         string // the comment as posted
	CommentID   string
}

TypedLine is one parsed typed line carried by a DoneRequest. The parser lives in internal/nsprint/disposition, which imports this package, so the caller converts its Line into this shape.

type Width

type Width struct {
	Desired int
	Leased  int
	CI      int
	Free    int
}

Width is the slot accounting of one friend (spec 2.2, 4.2). Leased is ZCARD friend:<f>:cards:working, the one lease ledger (#3998): taken tasks, task cards and copies, global to the consumer, so it already sums over every open sprint; CI is the CI legs running on the friend's machine, its beat's ci (nova-tools#4293), each holding a slot; Free is desired minus CI minus leased.

func GetWidth

func GetWidth(ctx context.Context, st *store.Store, as string) (Width, error)

GetWidth reads a friend's desired slots and the ZCARD of its working set, and returns the accounting. It is read only: task width without n never writes, and with n it is the desired write of <f> <n> (one writer, the capacity function).

func WidthFrom

func WidthFrom(desired, leased, ci int) Width

WidthFrom is the pure accounting, so it can be tested without a store.

Jump to

Keyboard shortcuts

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