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
- Variables
- func DenyTake(ctx context.Context, st *store.Store, initiator, as, sprint, id, idem string) (string, error)
- func IsValidEst(s string) bool
- func Key(sprint, id string) string
- func ParseTitle(title, defaultRepo string) (paths, deps []string)
- func ParseTitleEst(title string, errOut io.Writer) string
- func PathsIntersect(a, b string) bool
- func PayloadSHA(req PushRequest) string
- func RandomToken() (string, error)
- func ReviewID(repo string, pr int, head, friend string) string
- func TitleField(title, name string) (string, bool)
- func WithCost(evidence string, c Cost) string
- type BeatRequest
- type BeatStatus
- type BlockedError
- type CancelRequest
- type CancelStatus
- type Claim
- type Cost
- type DoneOutcome
- type DoneRequest
- type DoneStatus
- type Effects
- type Field
- type Kind
- type ListRequest
- type Overlap
- type PushRequest
- type PushResult
- type PushStatus
- type Reply
- type Row
- type TakeRequest
- type TypedLine
- type Width
Constants ¶
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.
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.
const ( TakeClaimed = "CLAIMED" // TakeBlocked is a task whose needs are not all closed (#2939). TakeBlocked = "BLOCKED" )
TakeStatus words returned by ns_task_take.
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.
const FunctionCancel = "ns_task_cancel"
FunctionCancel is registered by internal/nsprint/fn/lua/task_beat.lua.
const FunctionFriendDown = "ns_friend_down"
FunctionFriendDown is the function task_queue.lua registers.
const FunctionList = "ns_task_list"
FunctionList is the read-only FCALL behind ListStore (nova-tools #3263).
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 ¶
var ErrNoSprint = errors.New("no --sprint and sprint:order is empty")
ErrNoSprint is a verb run with no --sprint while sprint:order is empty.
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 ¶
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 ParseTitle ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
Types ¶
type BeatRequest ¶
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 ¶
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 ¶
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 ¶
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.
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).
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 ¶
ListRequest selects one friend's tasks. A state filter is optional; without one, only open, claimed, working and waiting tasks are returned.
type Overlap ¶
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.
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 TakeRequest ¶
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 ¶
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.