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
- func CanonicalOutcome(s string) (string, bool)
- func ExpandDir(out string, cards []Card) (int, error)
- func ExpandDirWithSink(out string, cards []Card, sink ExpandSink) (int, error)
- func FindCycles(order []string, needs map[string][]string) [][]string
- func KnownKinds() []string
- func KnownOutcomes() []string
- func KnownStates() []string
- func OutcomeSpellings() []string
- func ParseStamp(s string) (time.Time, error)
- func RenderCard(c Card) []byte
- func Requests(units []Unit) []jobs.Request
- func StateAfter(outcome string) string
- type Affinity
- type Attempt
- type Blocker
- type Branch
- type Budget
- type Card
- type Collection
- type Counts
- type Criterion
- type CutEvent
- type DeriveEvent
- type ExpandSink
- type Facts
- type Finding
- type Form
- type Graph
- func (g *Graph) Blocks(id string) []string
- func (g *Graph) Edges() int
- func (g *Graph) Len() int
- func (g *Graph) Needs(id string) []string
- func (g *Graph) NeedsBroken(id string) bool
- func (g *Graph) Order() []string
- func (g *Graph) Ready(id string) (bool, *Blocker)
- func (g *Graph) ReadySet() []string
- func (g *Graph) Revert(id string)
- func (g *Graph) Settle(id string)
- type Issue
- type Kind
- type Limits
- type NewAttempt
- type Node
- type Options
- type Plan
- type Proof
- type Recorded
- type Refusal
- type Status
- type Tool
- type Unit
- func (u Unit) Acceptance() []Criterion
- func (u Unit) AcceptanceText() []string
- func (u Unit) Attempts() []Attempt
- func (u Unit) Branch() string
- func (u Unit) Bytes(data []byte) []byte
- func (u Unit) Collections() []Collection
- func (u Unit) Deadline() string
- func (u Unit) Done() bool
- func (u Unit) Keys() []string
- func (u Unit) Lane() string
- func (u Unit) Needs() []string
- func (u Unit) Owner() string
- func (u Unit) PR() string
- func (u Unit) Request() jobs.Request
- func (u Unit) Resources() map[string]int64
- func (u Unit) State() string
- func (u Unit) Status() string
- func (u Unit) Title() string
- func (u Unit) Tools() []Tool
- func (u Unit) Warm() WarmState
- func (u Unit) Writes() []string
- type WarmState
- type WorkSet
- func (w *WorkSet) Blocked(extra map[string]bool) map[string]string
- func (w *WorkSet) Check(opts Options) ([]Finding, Counts)
- func (w *WorkSet) Decided(opts Options) map[string]bool
- func (w *WorkSet) DoneSet(extra map[string]bool) map[string]bool
- func (w *WorkSet) LoadCheck() error
- func (w *WorkSet) Ready(extra map[string]bool) []Unit
- func (w *WorkSet) ReadyFrom(done map[string]bool) []Unit
- func (w *WorkSet) Unit(id string) (Unit, bool)
- func (w *WorkSet) WithoutAcceptance() []string
Constants ¶
const CardFile = "card"
CardFile is the name of the file one card is written to inside its directory.
const StateLive = "live"
StateLive is the state a taken unit is in while its attempt runs.
Variables ¶
This section is empty.
Functions ¶
func CanonicalOutcome ¶
CanonicalOutcome maps a caller's spelling onto the grammar's outcome. The second result is false for a word that is neither.
func ExpandDir ¶
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 ExpandDirWithSink ¶
func ExpandDirWithSink(out string, cards []Card, sink ExpandSink) (int, error)
ExpandDirWithSink is ExpandDir with an optional event sink. If sink is not nil, one cut event is emitted per card actually written (existing cards are skipped and emit no event).
func FindCycles ¶
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 OutcomeSpellings ¶
func OutcomeSpellings() []string
OutcomeSpellings is every word --outcome accepts, in a stable order, for the refusal that names them.
func ParseStamp ¶
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 ¶
RenderCard renders one card deterministically: the same Card always produces the same bytes, so a re-expansion is byte-identical.
func Requests ¶
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.
func StateAfter ¶
StateAfter is the state an outcome leaves the unit in: A4's machine in one function, so the CLI and any later caller cannot disagree about it.
Types ¶
type Affinity ¶
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 {
// Offset and End are the record's own byte range, so a verb that CLOSES an
// open attempt rewrites that record and nothing else.
Offset int
End int
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 ¶
Blocker is the exact reason a node cannot proceed: the first unmet need, its state, and the join that resolves it.
type Branch ¶
Branch is one fact the (:fold :over (:branches ...)) sweep reads: the name of the sibling branch, the base it branched off, and whether it merged and went green. The fold is green-only by default -- a non-green sibling is excluded by the `:over` selector before it reaches the inputs list, so the caller does not check green here.
type Budget ¶
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 ¶
ExpandPlan validates a hand-written plan, expands any (:derive) or (:fold) the plan carries into the same Card pass as the hand-written (:node)s, 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), a `:derive` or `:fold` with no Facts source, an unknown `:from` or `:over` selector key, a duplicate branch name, and a budget below its floor -- always before a card is written.
The variadic Facts parameter is additive: callers without a fact source pass none, and a plan that never names `:derive` or `:fold` is unchanged; callers that do sweep projects pass `ExpandPlan(plan, &worklang.Facts{...})`. `:facts` remains unadmitted -- the rule form is a later slice, not this one.
func ExpandPlanWithSink ¶
func ExpandPlanWithSink(plan *Plan, sink ExpandSink, facts ...*Facts) ([]Card, error)
ExpandPlanWithSink is ExpandPlan with an optional event sink. If sink is not nil, one derive event is emitted per card in plan order, derived and folded cards included.
type Collection ¶
type Collection struct {
Name string
Under string
MembersUnknown bool
Revision string
Members []string
}
Collection is one output collection: named before the run, its members known only after it.
func (*Collection) BindMembers ¶
func (c *Collection) BindMembers(revision string, members []string)
BindMembers records the unit's revision and the member paths at harvest time, clearing the unknown-before-run flag.
type Counts ¶
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 CutEvent ¶
CutEvent is one cut event from the expander: a card cut, carrying its card id, pool candidate, template and route.
type DeriveEvent ¶
DeriveEvent is one derive event from the expander: a node derived from the graph, carrying its node id, parent and rule.
type ExpandSink ¶
type ExpandSink interface {
Derive(DeriveEvent)
Cut(CutEvent)
}
ExpandSink receives derive and cut events from the expander. A nil sink discards all events.
type Facts ¶
Facts is the closed fact source the expander reads: pinned issues and pinned branches. The plan only carries the *shape* of a selector; the data the selector filters lives here, passed in by the caller. A nil Facts means a hand-written plan only, and a plan naming `:derive` or `:fold` in that case is a refusal naming the missing source, never a silent empty sweep.
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.
type Form ¶
type Form struct {
Kind Kind
Offset int
End 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 and End the byte just past its last, so a refusal can name where in the file it happened -- and a WRITER can replace exactly the bytes one form occupies and leave every other byte of the file alone.
func Read ¶
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) Bytes ¶
Bytes returns the form's own bytes out of the source it was read from. It is the writer's half of Offset/End: the one place a caller turns a form back into the text the author wrote, rather than re-rendering it and losing the spacing, the order and the comments beside it.
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 ¶
Blocks returns the reverse edges of id in seed order. It is the inverse of Needs, written by the same insert.
func (*Graph) Needs ¶
Needs returns the forward edges of id in seed order: the nodes it cannot begin before.
func (*Graph) NeedsBroken ¶
NeedsBroken reports whether a reverted need has left id flagged needs-broken.
func (*Graph) Ready ¶
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 ¶
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.
type Issue ¶
type Issue struct {
Number int64
Slug string
URL string
Repo string
Label string
State string
HasPR bool
PRNumber int64
}
Issue is one fact the (:derive :from (:issues ...)) sweep reads: the stable identifiers the kernel already uses, the slug the branch rule substitutes, the URL the issue input names, and the :pr fact -- bool for presence, number for the PR it points at -- that decides ink-or-out. An issue missing any of these is refused by the caller before it reaches the sweep, so the sweep never picks half-formed ones.
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 ¶
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 NewAttempt ¶
type NewAttempt struct {
Rung string
Owner string
Started string
Outcome string
Proof Proof
Usage string
PR int
}
NewAttempt is one attempt to record: who ran it, when it started, how it ended, the proof that it ended, and the two optional pointers a run leaves behind -- the usage TSV it spent into and the PR it produced.
type Node ¶
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.
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
// Evidence is the verdict of a unit's evaluated :acceptance (set check
// --evaluate, #2664). A unit it names is done exactly when the value is true
// -- the criteria replace what :done and :status say -- and a unit it does
// not name keeps the document's own word. Done still adds on top.
Evidence 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 ¶
Plan is the reader's view of a `.work` file. Unknown forms at the top level are preserved and ignored.
type Proof ¶
Proof is the termination proof A3 requires of every outcome but `uncertain`: what kind of evidence it is and the evidence itself. A3's own examples are an exit, a merge, a reaped pid and a fencing token; what a caller has at hand is a path, a sha or a url, and the kind is READ off the value rather than asked for twice.
type Recorded ¶
type Recorded struct {
N int64
Closed bool
State string
// Rung, Owner and Started are the record AS WRITTEN. They are not always
// what the caller passed: closing an open attempt keeps the rung, the owner
// and the instant the try actually began, because that is when it began.
Rung string
Owner string
Started string
Outcome string
Bytes []byte
}
Recorded is what one edit did: the record as written, whether it closed an attempt that was already open rather than appending a new one, and the state the unit now carries.
func Record ¶
func Record(file string, data []byte, unitID string, a NewAttempt, limits Limits) ([]byte, Recorded, error)
Record appends -- or closes -- one attempt on one unit of a work set, editing the file's bytes in place.
It CLOSES rather than appends when the unit's last attempt is still open: an `:outcome :uncertain` with no `:proof`, taken by this same owner. An open attempt taken by a DIFFERENT owner is refused, never appended beside: its state and reservation stand until its own owner closes it. A try that started and then ended is ONE try (A3: "an attempt is a record, not a counter"), so the record that was opened by `next --take` is the record that is finished here, keeping its `:n`, its `:rung` and its `:started`. Any other last attempt is a new try and a new record.
Every refusal is made BEFORE a byte is written, so a file is never left half edited: an outcome that is not one of A3's, an outcome other than `uncertain` with no proof, a unit the set does not hold, a unit already in a terminal state and an open attempt owned by another mind are all refused with the document untouched.
func Take ¶
func Take(file string, data []byte, unitID string, a NewAttempt, limits Limits) ([]byte, Recorded, error)
Take opens an attempt: the record `next --take` writes when a mind picks a unit up. It is an ordinary attempt with the only outcome an unfinished try can honestly carry -- `uncertain`, which owes no proof precisely because nothing has terminated -- and it moves the unit to `live`, so the lane and the writes it holds are charged to it from this instant.
type Refusal ¶
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.
type Status ¶
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.
type Tool ¶
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 and End are the unit form's own byte range in the file it was read
// from. They are what lets a writer edit ONE unit in place and leave every
// other byte of the document exactly as its author wrote it.
Offset int
End 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 ¶
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 ¶
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) Branch ¶
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) Bytes ¶
Bytes returns the unit's own bytes out of the document it was read from: the form exactly as its author wrote it, spacing and comments included. It is what makes "every unit but the edited one is byte-identical" a reading rather than an assertion.
func (Unit) Collections ¶
func (u Unit) Collections() []Collection
Collections returns the unit's output collections.
func (Unit) Deadline ¶
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 ¶
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 ¶
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 ¶
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) Owner ¶
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) PR ¶
PR is the :pr a unit's work lands as, as written -- `1412`, `#1412` or `"1412"`. It is read HERE with Branch and Lane, for the reason Branch names: there is ONE reader of this form, and a key only the token ledger read would be the second reader of the work set growing back. An Integer form carries no Value, so the number is rendered from Int.
func (Unit) 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 ¶
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 ¶
State returns the unit's state, "open" when it carries none. `uncertain` is a state of its own.
func (Unit) Status ¶
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 ¶
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 ¶
Tools returns the verbs the unit needs installed, with their versions and optional semantic keys.
type WarmState ¶
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 ¶
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 ¶
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 ¶
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 ¶
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:
- every unit has an id, and no two units share one
- every :needs names a unit of this set
- the :needs edges are acyclic
- every :owner is a mind --minds names (off without --minds)
- every :lane is a lane --lanes names (off without --lanes)
- 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) Decided ¶
Decided is DoneSet with the evaluated evidence beside it: a unit Evidence names is done when its criteria hold, whatever its :status says; every other unit is done by its own word; opts.Done adds to both.
func (*WorkSet) DoneSet ¶
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) LoadCheck ¶
LoadCheck refuses the work set at load time when any unit carries no :acceptance. This is the kernel's door (behaviour 40): a unit naming no evidence names no finish line, so whether it is done is a judgement rather than evidence. The caller uses the file and limits that produced this set so the refusal carries the right name.
func (*WorkSet) Ready ¶
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) ReadyFrom ¶
ReadyFrom is Ready over a done set the caller already decided -- Decided's, when the done set came from evidence rather than from the document's words.
func (*WorkSet) WithoutAcceptance ¶
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.