worklang

package
v0.16.0-dev.c839379e Latest Latest
Warning

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

Go to latest
Published: Sep 19, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package worklang is the bounded reader for a `.work` plan (docs/SPEC-WORKLANG.md).

A plan is data the kernel reads, never a program it evaluates. The reader is the restricted-Lisp grammar the rest of this repo already uses for work files: a sequence of s-expressions made only of lists, keywords, strings, integers and bare symbols, with every evaluation form refused at the boundary and three bounds enforced as the reader runs -- bytes, nesting depth and node count. Nothing read here is ever evaluated: there is no eval, and a dispatch macro is a refusal with the byte offset that owes it, not a form the reader will run.

Every refusal is a *Refusal carrying exit code 2, so the command that opens a plan can print one line and stop, never a partial parse.

Index

Constants

View Source
const CardFile = "card"

CardFile is the name of the file one card is written to inside its directory.

Variables

This section is empty.

Functions

func ExpandDir

func ExpandDir(out string, cards []Card) (int, error)

ExpandDir writes one card directory per node under out, and returns the number of cards it wrote. A card already present is left byte-identical, so a re-expansion after one fact changes appends only the new card and mints no id: the directory name is the node's own stable id.

func FindCycles

func FindCycles(order []string, needs map[string][]string) [][]string

FindCycles names every `:needs` cycle in a graph, each once. refuseCycle stops at the first because a plan with one is refused whole; a checker prints them all, and both walk this one function so the two can never find different cycles.

The walk is the same three-colour depth-first search: grey is the path being walked and a grey node reached again closes a cycle. The cycle is normalised to start at its smallest member so that the same loop entered by two of its members is reported once.

func KnownKinds

func KnownKinds() []string

KnownKinds returns the kind names the plan grammar admits, in spec order.

func KnownOutcomes

func KnownOutcomes() []string

KnownOutcomes returns the attempt outcomes the grammar admits.

func KnownStates

func KnownStates() []string

KnownStates returns the unit states the grammar admits, in spec order.

func ParseStamp

func ParseStamp(s string) (time.Time, error)

ParseStamp reads any of those spellings as UTC and refuses anything else naming what it accepts, rather than falling back to a clock.

func RenderCard

func RenderCard(c Card) []byte

RenderCard renders one card deterministically: the same Card always produces the same bytes, so a re-expansion is byte-identical.

func Requests

func Requests(units []Unit) []jobs.Request

Requests builds the admission requests for units in written order, so a pass over a set is deterministic in the order the author wrote it.

Types

type Affinity

type Affinity struct {
	Bench string
	Route string
	Model string
}

Affinity names which bench kind may pull the card and which route serves it. Model is the route's model class when the plan pins it; an empty Model means the class is a projection from facts this slice does not read.

type Attempt

type Attempt struct {
	N        int64
	Rung     string
	Owner    string
	Started  string
	Outcome  string
	HasProof bool
	Proof    Form
}

Attempt is one record of one try at a unit: which rung ran it, who owned it, when it started, how it ended, and whether it proved termination.

type Blocker

type Blocker struct {
	Need     string
	State    string
	Resolver string
}

Blocker is the exact reason a node cannot proceed: the first unmet need, its state, and the join that resolves it.

type Budget

type Budget struct {
	Minutes int64
	Tokens  int64
	Floor   string
}

Budget is one card's wall: its minutes, its token ceiling and the lowest model class admitted.

type Card

type Card struct {
	Node     string
	Kind     string
	Repo     string
	Base     string
	Needs    []string
	Blocks   []string
	Ready    bool
	Inputs   []string
	Result   string
	Branch   string
	Green    []string
	Budget   Budget
	Affinity Affinity
}

Card is one expanded node: everything the bench needs, and nothing the plan did not write. Its fields are the spec's output contract, budget and affinity, plus the needs/blocks edges the graph derived.

func ExpandPlan

func ExpandPlan(plan *Plan) ([]Card, error)

ExpandPlan validates a hand-written plan, builds the needs/blocks graph and returns one Card per node in plan order. It refuses an absent need or a cycle (through the job graph's rules 2 and 3), an unadmitted top-level form, a duplicate branch name, and a budget below its floor -- always before a card is written.

type Collection

type Collection struct {
	Name           string
	Under          string
	MembersUnknown bool
}

Collection is one output collection: named before the run, its members known only after it.

type Counts

type Counts struct {
	Units   int
	Ready   int
	Blocked int
	Done    int
	Owned   int
}

Counts is the one summary line's arithmetic: units = ready + blocked + done. owned is the units that name an owner, the ones the machinery routes to a friend rather than cutting as a card.

type Criterion

type Criterion struct {
	ID        string
	Kind      string
	Subject   string
	Predicate string
}

Criterion is one acceptance criterion, in the schema the work spec fixes.

type Finding

type Finding struct {
	Rule string
	Unit string
	// Key and Value are the rule's own field -- need=<id>, owner=<name>,
	// cycle=<a->b->a> -- kept apart so the printing site escapes the value on
	// its own and a unit id with a space can never break the line.
	Key    string
	Value  string
	Remedy string
	Offset int
}

Finding is one thing wrong with the CONTENT of a work set: the rule that caught it, the unit that owes it, and the fields the line prints. Rule is the word the SET line carries, so a caller greps one word rather than a sentence.

func (Finding) Detail

func (f Finding) Detail() string

Detail is the rule's field as one `name=value` token, for a caller that wants the line's middle rather than its two halves.

type Form

type Form struct {
	Kind   Kind
	Offset int
	List   []Form
	Value  string // keyword name (no colon) or decoded string
	Int    int64
}

Form is one restricted s-expression. Offset is the form's first byte, so a refusal can name where in the file it happened.

func Read

func Read(file string, data []byte, limits Limits) (Form, error)

Read reads exactly one restricted form from data, holding it to limits and refusing anything else. The file name is carried into every refusal.

func (Form) IsKeyword

func (f Form) IsKeyword(name string) bool

IsKeyword reports whether f is the keyword named name (without the colon).

func (Form) Text

func (f Form) Text() string

Text returns the decoded string of a String form, or "".

type Graph

type Graph struct {
	// contains filtered or unexported fields
}

Graph is the closed needs/blocks graph a plan declares. order is seed order, the order every node is admitted in; needs is the forward edge and blocks the inverse, both derived from the same declarations so the graph never holds one without the other.

func (*Graph) Blocks

func (g *Graph) Blocks(id string) []string

Blocks returns the reverse edges of id in seed order. It is the inverse of Needs, written by the same insert.

func (*Graph) Edges

func (g *Graph) Edges() int

Edges returns the number of forward needs edges.

func (*Graph) Len

func (g *Graph) Len() int

Len is the number of nodes. Edges is the number of needs edges.

func (*Graph) Needs

func (g *Graph) Needs(id string) []string

Needs returns the forward edges of id in seed order: the nodes it cannot begin before.

func (*Graph) NeedsBroken

func (g *Graph) NeedsBroken(id string) bool

NeedsBroken reports whether a reverted need has left id flagged needs-broken.

func (*Graph) Order

func (g *Graph) Order() []string

Order returns every node id in seed order: admission, not readiness.

func (*Graph) Ready

func (g *Graph) Ready(id string) (bool, *Blocker)

Ready reports whether id may proceed, and when it may not, the exact blocker and its resolver. A node is ready only when every need is terminal accepted and no reverted need has left it needs-broken. A node already terminal accepted has nothing left to begin.

func (*Graph) ReadySet

func (g *Graph) ReadySet() []string

ReadySet is the mechanical ready set: the nodes with no unmet need, in seed order. The graph's own nodes are all admitted; only the ready ones are pullable.

func (*Graph) Revert

func (g *Graph) Revert(id string)

Revert records that id was reverted after landing, flagging each dependent needs-broken rather than leaving it silently ready.

func (*Graph) Settle

func (g *Graph) Settle(id string)

Settle records that id merged and went green: terminal accepted.

type Kind

type Kind int

Kind is the type of one restricted form.

const (
	// List is a parenthesized form, possibly empty.
	List Kind = iota
	// Keyword is a `:name` token, stored without the leading colon.
	Keyword
	// String is a double-quoted string, stored decoded.
	String
	// Integer is a decimal integer.
	Integer
	// Symbol is a bare token such as go-fix or false, stored verbatim. The
	// plan language uses symbols for values that are not text (kind names,
	// booleans), and they are data: nothing here is ever evaluated.
	Symbol
)

type Limits

type Limits struct {
	MaxBytes int
	MaxDepth int
	MaxNodes int
}

Limits is the three bounds every plan read is held to. A zero value is not usable; build one with DefaultLimits or set all three. The names are the flag names the caller exposes: --max-bytes, --max-depth, --max-nodes.

func DefaultLimits

func DefaultLimits() Limits

DefaultLimits is the shape the kernel's intake limits already carry (lisp/nova-work/src/control.lisp): 64 KiB, depth 64, 4096 nodes.

type Node

type Node struct {
	Fields  map[string]Form
	Unknown map[string]Form
}

Node is one `:node` of a plan: its known Fields and the unknown keys beside them, preserved so a later slice can read what this one ignores.

func (Node) ID

func (n Node) ID() string

ID is the node's :id string, or "" when it carries none.

func (Node) Kind

func (n Node) Kind() string

Kind is the node's :kind name, or "" when it carries none.

type Options

type Options struct {
	// Minds is the set of known owners, folded to lower case. Nil means no --minds
	// was given and no owner is checked.
	Minds map[string]bool
	// MindsFile is what Minds was read from, named in the finding so the remedy
	// says which file to add the row to.
	MindsFile string
	// Lanes is the set of known lane names. Nil means no --lanes was given and no
	// lane is checked.
	Lanes     map[string]bool
	LanesFile string
	// Done names units that are done regardless of what the file says, for a
	// caller that holds the settled facts the document does not.
	Done map[string]bool
}

Options is what Check needs from outside the language. Every one of them is optional, and an absent one turns its rule OFF rather than inventing a default: there is no default minds file, no default lanes file and no discovery.

type Plan

type Plan struct {
	File    string
	Version int64
	Goal    *Form
	Nodes   []Node
	Unknown []Form
}

Plan is the reader's view of a `.work` file. Unknown forms at the top level are preserved and ignored.

func ParsePlan

func ParsePlan(file string, data []byte, limits Limits) (*Plan, error)

ParsePlan reads a plan and validates the fields the reader owns: every `:kind` must be a known kind, refused naming the field otherwise. An unknown key beside it is preserved in Node.Unknown and otherwise ignored.

func (*Plan) Graph

func (p *Plan) Graph() (*Graph, error)

Graph closes the plan's nodes into a needs/blocks graph. It refuses a duplicate or empty id, an absent need naming the field and the id, and a `:needs` cycle by validator rule 3, before returning: a refused graph is never published.

type Refusal

type Refusal struct {
	File   string
	Reason string
}

Refusal is a plan this reader would not read. It is always exit 2: the tool could not run, and the input is refused whole rather than truncated.

func (*Refusal) Error

func (r *Refusal) Error() string

func (*Refusal) ExitCode

func (r *Refusal) ExitCode() int

ExitCode is 2 for every refusal, the code docs/nova-lessons.md fixes for a plan that could not be read.

type Status

type Status struct {
	Merged   bool
	Green    bool
	Reverted bool
}

Status is the settled facts the journal holds for one node. A node is terminal accepted when it has merged and gone green and has not been reverted.

func (Status) State

func (s Status) State() string

State names a status for a row: open, merged, reverted, or accepted. A merged node that is not yet green is the blocker nova-pulse harvest settles.

type Tool

type Tool struct {
	Verb string
	At   string
	Key  string
}

Tool is one verb a unit needs installed, at a version, with an optional semantic key: when the key moves the unit's outputs are invalid even though the version did not change.

type Unit

type Unit struct {
	ID      string
	Offset  int
	Fields  map[string]Form
	Unknown map[string]Form
	// contains filtered or unexported fields
}

Unit is one (unit "<id>" ...) form: its stable id, its known Fields and the unknown keys beside them, preserved so a later slice reads what this one ignores.

func (Unit) Acceptance

func (u Unit) Acceptance() []Criterion

Acceptance returns the unit's acceptance criteria in A14's schema: the evidence that closes the unit. A criterion written as a bare sentence is not one of these -- it is in AcceptanceText -- because a sentence names no predicate a tool can read.

func (Unit) AcceptanceText

func (u Unit) AcceptanceText() []string

AcceptanceText is the unit's acceptance in the older spelling: the sentences a person wrote, which is what the sets on the bench carry and what an ask note renders. A criterion written as a (:id ...) form contributes its subject, so one caller reads both spellings and there is no second reader of the key.

func (Unit) Attempts

func (u Unit) Attempts() []Attempt

Attempts returns the unit's attempt records in source order.

func (Unit) Branch

func (u Unit) Branch() string

Branch is the :branch a unit's owner replies on. It is read HERE with every other key because there is ONE reader of this form: a key only the ask side read would be the second reader of the work set growing back.

func (Unit) Collections

func (u Unit) Collections() []Collection

Collections returns the unit's output collections.

func (Unit) Deadline

func (u Unit) Deadline() string

Deadline is the :deadline text EXACTLY as written. It is parsed by the caller, because an unreadable deadline is a finding about one unit and never a refusal of the whole set.

func (Unit) Done

func (u Unit) Done() bool

Done reports the :done key, read as true for the handful of spellings a person writes for yes. Anything else is false: a :done nobody can read is not a unit this tool will call finished.

func (Unit) Keys

func (u Unit) Keys() []string

Keys returns every key the unit's form carried, known or not, in written order. It is what lets a later slice read what this one ignores without a second reader over the same bytes.

func (Unit) Lane

func (u Unit) Lane() string

Lane returns the unit's lane -- the resource of capacity 1 over the area of the tree queue/control/lanes.tsv names -- from :resources first and the plain :lane key second, so the set as written today keeps working.

func (Unit) Needs

func (u Unit) Needs() []string

Needs returns the ids this unit waits on, in source order.

func (Unit) Owner

func (u Unit) Owner() string

Owner returns the mind that owns the unit: a friend, a child rung, a swarm, or "all". One spelling, so `nova-work ask` and the pull worker read the same form.

func (Unit) Request

func (u Unit) Request() jobs.Request

Request is the unit's admission request: the vector it consumes and the paths it writes. The lane comes from :resources first and the plain :lane key second -- the reader already reconciles the two (A6) -- and it enters the vector as a NAMED dimension, because `lane:docs` and `lane:merge` do not contend and one opaque "lane" count could not tell them apart.

func (Unit) Resources

func (u Unit) Resources() map[string]int64

Resources returns the unit's resource vector as counts. A lane is capacity 1 under the key "lane"; a named scarce class is "class:<name>"; every other entry keeps its own name. Admission against these counts is the kernel's, not this reader's.

func (Unit) State

func (u Unit) State() string

State returns the unit's state, "open" when it carries none. `uncertain` is a state of its own.

func (Unit) Status

func (u Unit) Status() string

Status is the :status value as written -- "closed", :review -- one of the three ways a unit says it has finished. The other two are :done and the caller's own list.

func (Unit) Title

func (u Unit) Title() string

Title is the unit's display text. The id is never it: A2 keeps the two apart so a rename is a new id carrying :was, not a re-pointed one.

func (Unit) Tools

func (u Unit) Tools() []Tool

Tools returns the verbs the unit needs installed, with their versions and optional semantic keys.

func (Unit) Warm

func (u Unit) Warm() WarmState

Warm returns the retained/active split of the unit's warm state.

func (Unit) Writes

func (u Unit) Writes() []string

Writes returns the repo-relative paths the unit edits, in source order.

type WarmState

type WarmState struct {
	Retained []Form
	Active   []Form
}

WarmState is the retained/active split. Retained entries are held between units (a kept worktree, a loaded compiler); active entries are what the unit is using while it runs. They are accounted apart so warmth is never charged as running capacity.

type WorkSet

type WorkSet struct {
	File string
	ID   string
	// Title is the set's :title as written, kept beside Fields because it is
	// the one field of the set a caller prints rather than walks.
	Title   string
	Fields  map[string]Form
	Unknown map[string]Form
	Units   []Unit
	// contains filtered or unexported fields
}

WorkSet is the reader's view of one (work-set ...) file.

func ParseWorkSet

func ParseWorkSet(file string, data []byte, limits Limits) (*WorkSet, error)

ParseWorkSet reads a work set and validates the shapes this reader owns: the top form, every unit's id, and the shape of each amendment key. A refused set is refused whole at exit 2, never half-read. This is A2's door: a unit with no id, an empty id, or an id another unit already carries is refused here.

func ParseWorkSetTolerant

func ParseWorkSetTolerant(file string, data []byte, limits Limits) (*WorkSet, error)

ParseWorkSetTolerant reads the same form but carries a broken IDENTITY through instead of refusing it: a member of :units that is not a unit, an empty id and a repeated id all come back as units at their own byte, for Check to report as findings in one pass. Everything else -- a file that cannot be read, a :units that is not a list, an amendment key whose shape is wrong -- is still a refusal, because those are not findings about one unit.

The two doors exist because the two callers want different answers. A kernel loading a set wants A2 enforced before anything is scheduled; a checker wants every defect of the file in one pass, and a reader that stopped at the first would cost its caller a round trip per defect.

func (*WorkSet) Blocked

func (w *WorkSet) Blocked(extra map[string]bool) map[string]string

Blocked is the complement of Ready among the open units: each one with the first need that is holding it. It is the same walk, so the two can never disagree.

func (*WorkSet) Check

func (w *WorkSet) Check(opts Options) ([]Finding, Counts)

Check validates one work set whole and returns EVERY finding, in the order the rules run and the units are written. It never stops at the first: a checker that did would cost the caller one round trip per defect.

The rules:

  1. every unit has an id, and no two units share one
  2. every :needs names a unit of this set
  3. the :needs edges are acyclic
  4. every :owner is a mind --minds names (off without --minds)
  5. every :lane is a lane --lanes names (off without --lanes)
  6. every :deadline is an instant, in either RFC3339 spelling

A unit is done when it says so (`:done`, or a `:status` of closed, done, landed or merged) or when Options.Done names it. It is ready when it is not done and every need is done; blocked when it is not done and some need is not.

func (*WorkSet) DoneSet

func (w *WorkSet) DoneSet(extra map[string]bool) map[string]bool

DoneSet is every unit this run counts as finished: what the document says and what the caller's --done adds. It is the only input to readiness, so the ready set is mechanical -- a function of the language and the settled facts, never of anyone's judgment.

func (*WorkSet) Ready

func (w *WorkSet) Ready(extra map[string]bool) []Unit

Ready is the mechanical ready set: the units that are not done and whose every need is done, in written order. It is what a coordinator asks the language for -- "what can be pulled right now" -- and it is derived, never maintained by hand.

func (*WorkSet) Unit

func (w *WorkSet) Unit(id string) (Unit, bool)

Unit returns the unit with this id.

func (*WorkSet) WithoutAcceptance

func (w *WorkSet) WithoutAcceptance() []string

WithoutAcceptance returns the ids of the units that name no evidence. It is the report rule A14 owes: a unit with no acceptance names no finish line, and the real set of 2026-09-17 has not one.

Jump to

Keyboard shortcuts

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