config

package
v1.2.11 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 36 Imported by: 0

Documentation

Overview

Package config is nova-config's library: the permanent, non-ephemeral configuration of the fleet, kept in Postgres (schema `config`) and applied into Redis so Redis is always a rebuildable copy of the configuration.

Every kind of configuration is one Kind descriptor: its name, its table, its fields with their types and validators, and the two Redis functions that write and remove one row. The CLI's add, remove, set, list, show and history verbs, the SQL, the history rows and the apply diff are all generated from the descriptor, so a new kind is one descriptor, one migration and one Redis writer (docs/SPEC-CONFIG.md, "Adding a kind").

Index

Constants

View Source
const (
	EnvPG          = "NOVA_PG_DSN"
	EnvPGPassEnv   = "NOVA_PG_PASSWORD_ENV"
	DefaultPassEnv = "NOVA_PG_PASSWORD"
)

The environment that names the config store: the DSN, the variable that holds its password, and the variable read when that one is not named. Every tool that reads nova-config (nova-config itself, nova-sprint fleet sync) resolves the store's address with ResolveDSN, so there is one set of address rules (docs/nova-config/README.md, "Connecting").

View Source
const (
	MaxArgs      = 64
	MaxArgvBytes = 4096
)

The bounds of a TypeArgv value: a command is at most MaxArgs words and MaxArgvBytes bytes in its canonical spelling, refused before any write.

View Source
const (
	KindMachine = "machine"
	KindFleet   = "fleet"
	KindFriend  = "friend"
	KindSprint  = "sprint"
	KindLoop    = "loop"
	KindRoute   = "route"
	KindTier    = "tier"
)

The kinds of this cut (docs/SPEC-CONFIG.md, "The kinds of this cut").

View Source
const (
	FriendModeBatch   = "batch"
	FriendModeOneShot = "one-shot"
	DefaultFriendMode = FriendModeOneShot
)

The delivery mode, the default a row without one has, and the retired word. Every friend takes one card per lane; FriendModeBatch is kept only to read a row written before the retirement.

View Source
const (
	FieldDecideBounce = "decide_bounce"
	FieldDecideReview = "decide_review"
)

The sprint row's two bars on a decide read's p(defect) (pkg/decide, Bars; docs/SPEC-SPRINT.md section 6, the decide read): apply writes them to SprintKey(field), which the sprint's routes read takes.

View Source
const (
	FieldDecideAttemptNoResult    = "decide_attempt_no_result"
	FieldDecideAttemptNothingToDo = "decide_attempt_nothing_to_do"
	FieldDecideGrade              = "decide_grade"
)

The sprint row's bars of nova-decide's layer 2 (pkg/decide, attempt.go and grade.go; docs/SPEC-SPRINT.md sections 2 and 5): a failed finish whose attempt decision is no-result at or above decide_attempt_no_result ends as a take with no result, one whose decision is nothing-to-do at or above decide_attempt_nothing_to_do is failed work of that class (no other class has a bar), and a card graded pro at or above decide_grade starts on pro. All three are empty by default (the coordinator, 2026-10-03: nothing routes on a decision until a review round labels cards independently): the decisions are asked, recorded and shown, and route nothing.

View Source
const (
	FieldDecideGateFlaky       = "decide_gate_flaky"
	FieldDecideGatePreexisting = "decide_gate_preexisting"
)

The sprint row's two bars on a failed gate's decisions (pkg/decide, GateBars; docs/SPEC-SPRINT.md section 5, the gate verdict): a failure flaky at or above the first is rerun once, one pre-existing at or above the second is reported pre-existing. Both are empty by default: every gate decision is recorded and shown, and nothing is rerun or reclassified until the owner sets a bar. Apply writes them to SprintKey(field), which the sprint's routes read takes.

View Source
const (
	ProviderOpenRouter = "openrouter"
	ProviderOpenCode   = "opencode"
)

ProviderOpenRouter and ProviderOpenCode are the provider words of the routes whose prices a published list gives.

View Source
const (
	DeclKey     = "config:decl"
	FriendsKey  = "friends"
	MachinesKey = "machines"
	CapLogKey   = "cap:log"
	LoopsKey    = "loops"
	RoutesKey   = "routes"
	TiersKey    = "tiers"
)

The Redis keys apply writes, per kind. A friend's keys are the ones the nova-sprint verbs wrote by hand until now, through the same Redis Functions (pkg/nsprint/fn/lua: capacity.lua's ns_capacity_desired for slots and tiers, friend_roles.lua's ns_friend_roles for roles), and her width, delivery mode, config_dir, token_cap and the optional streams and kinds work restriction, plain fields of friend:<f>:desired no function touches. Her logins and wake path are what she would just know: her own presence writes them, apply never touches friends:login or friend:<f>:wakepath. A machine's ceiling goes through ns_capacity_machine; its registry row has no writer in the library, so it is the hash machine:<m> and the set `machines`, both nova-config's own. The fleet row is two plain keys of nova-config's own, fleet:store and fleet:coordinator, each a machine's name; the sprint row is sprint:coordinator, a friend's name (each absent when the row names none).

A loop's row is the hash loop:<name> with every field, the derived log path, rev and at, and its name in the set `loops`: nova-config's own keys, which the plays read to render one unit per row. A route's row is the hash route:<name> with every field, rev and at, and its name in the set `routes`, which the deal reads (hashKinds); a tier's row is the hash tier:<name> and its name in the set `tiers`, read by the deal beside the routes.

config:decl is the stamp: rev:<kind> is the Postgres revision last applied and at:<kind> the server time it was written (the shape of friends:decl in friend_declare.lua).

View Source
const (
	SelfEnv      = "env"      // NOVA_MACHINE
	SelfTailnet  = "tailnet"  // the tailnet's own name for this host
	SelfHostname = "hostname" // the first label of the hostname
)

How SelfName learnt the name.

View Source
const (
	OpAdd    = "add"
	OpSet    = "set"
	OpRemove = "remove"
)

The three operations a history row records.

View Source
const ConnectTimeout = 10 * time.Second

ConnectTimeout bounds the connection check of OpenPG when the context carries no deadline of its own.

View Source
const CoordinatorRole = "coordinator"

CoordinatorRole is the Redis role ns_friend_roles and the deal read (friend:<f>:roles), derived at apply from the sprint row.

View Source
const DefaultFriendTokenCap int64 = 6_000_000

DefaultFriendTokenCap is a friend's per-card token cap when her row names none: input, cached input, output and reasoning summed, 6000000, the stopgap's cap. 0 on the row is no cap, and is not this default.

View Source
const DefaultFriendWidth = 8

DefaultFriendWidth is a friend's width when her row names none: the jobs she works at once (the owner, 2026-10-02: "6/1 seems a bit wrong -- need to setup width for friends? Start at 8 for each?"). Migration 0018 fills every row there before it with this; nova-sprint friend sync reads a row without the field as this.

View Source
const EnvMachine = "NOVA_MACHINE"

EnvMachine names the machine row a process runs on, an explicit override (an empty value counts as unset).

View Source
const FieldAnswerRulesOff = "answer_rules_off"

FieldAnswerRulesOff is the sprint row's off switch of the tick's rule answers (docs/SPEC-SPRINT.md section 8, answered by rule): a list of AnswerRules, each a rule the machine does not answer a judgment by while it is listed. Apply writes it to SprintKey(FieldAnswerRulesOff), which the sprint's routes read takes.

View Source
const FieldDecideBriefBar = "decide_brief_bar"

FieldDecideBriefBar is the sprint row's bar on a brief decision's p(converges) (pkg/decide, BriefBar; docs/SPEC-NOVA-DECIDE.md section 14): nova-sprint add refuses a card under it, and empty asks and reports only.

View Source
const FieldDecideJudgment = "decide_judgment_bar"

FieldDecideJudgment is the sprint row's bar on a judgment decision's probability (pkg/decide, Choose; docs/SPEC-SPRINT.md section 8, answered by nova-decide): nova-sprint answer applies the verb it chose at or above it. Apply writes it to SprintKey(FieldDecideJudgment), which the sprint's routes read takes.

View Source
const FieldDecideScoreBar = "decide_score_bar"

FieldDecideScoreBar is the sprint row's bar on a landed diff's score (pkg/decide, Top; docs/SPEC-SPRINT.md section 7, the landed score): a batch whose cards' top class meets it raises one "landed work scored low" judgment listing them.

View Source
const ListNoteRunes = 60

ListNoteRunes is how many characters of a Cut field (a note) a list line prints before "..." marks the cut.

View Source
const LoopRunExitHeld = 3

LoopRunExitHeld is loop run's exit when another copy holds the loop's lock: the bash wrapper's 3, so a unit's restart policy reads it the same.

View Source
const MaxFixtureBytes = 1 << 20

MaxFixtureBytes bounds a fixture file, read whole before it is parsed.

View Source
const MaxStoreFileBytes = 16 << 20

MaxStoreFileBytes bounds a store file, read whole when it is opened.

View Source
const OpHeld = "held"

OpHeld is the held write a publish reports instead of moving the sprint seat (docs/SPEC-CONFIG.md, "sprint"): its Name is the one line. SaidLine prints that name verbatim. nova-config apply prints through OpLine, which wraps the name; OpLine is outside this change.

View Source
const OpenRouterModelsURL = "https://openrouter.ai/api/v1/models"

OpenRouterModelsURL is OpenRouter's public models endpoint: every model's pricing in USD per token, read with no key.

View Source
const PriceJumpFactor = 2

PriceJumpFactor is how far a price may move between two reads and still be set by the refresh: a list price above twice or below half of the row's is a judgment, set by hand after a reader has looked, never silently.

View Source
const PriceStalePercent = 10

PriceStalePercent is how far a row's price may differ from the list and still read as current: past it the row is stale.

View Source
const SayWhy = "say why: --note '<the measured reason>'"

SayWhy is the remedy of a disabled route with no reason: a disabled route carries its reason (the owner, 2026-10-02: the choices on route and width "should be saved somewhere permanent with notes").

Variables

View Source
var (
	ErrCeiling = errors.New("ceiling")
	ErrActor   = errors.New("actor")
	ErrInUse   = errors.New("in use")
)

The refusals the runtime's functions answer with.

View Source
var (
	ErrExists     = errors.New("exists")
	ErrNotFound   = errors.New("not found")
	ErrNoRef      = errors.New("names no row")
	ErrReferenced = errors.New("is named by")
	ErrInvalid    = errors.New("invalid")
)

The refusals a store returns; the CLI turns each into one line with its remedy.

View Source
var AnswerRules = []string{"base-gate", "bound", "brief-defect", "conflict", "failed", "friend-take", "hold-need", "late", "read-broken", "read-late"}

AnswerRules is every rule the sprint answers a mechanical judgment by (internal/sprint, RuleNames, which a test holds equal): work came back failed, a card at its bound, a work card past its deadline, a stream stopped on a conflict in a file no ledger owns, the same finding twice (a brief defect), the lander's base tree gate retried, a friend's card she has not started taken back, failed work that HOLDs for a card not landed waiting for it, a reader's finding reworked as the fix, and a late read asked of another reader.

View Source
var ErrLoopNotRunnable = errors.New("loop not runnable")

ErrLoopNotRunnable marks a loop row loop run refuses: disabled, with secrets, or with no command.

View Source
var ErrNoTailnet = errors.New("no tailnet on this host")

ErrNoTailnet says no tailnet is present on this host.

View Source
var FriendModes = []string{FriendModeOneShot}

FriendModes are how a friend's daemon (nova-friend run) hands her work: one-shot only. One-shot is width lanes, each its own session of her, handed one card per turn and waiting for that card's RESULT.md before the next (docs/SPEC-FRIEND.md, one-shot lanes; the owner, 2026-10-04: "so [she] can still be wide, it's just 8 [of her]"). Batch, where one session took many cards in one turn (deaf for its length, context filling), is retired: nova-config refuses the word, and a stored row that still says it reads as one-shot (FriendMode).

View Source
var FriendRoles = []string{"builder", "may-hold", "reader"}

FriendRoles are the roles someone decides for a friend. The coordinator role is not one: who coordinates is the sprint row's one field, and apply derives the Redis role from it (Kind.Derive below), so ns_friend_roles still sees exactly one coordinator.

View Source
var Kinds = []*Kind{
	{
		Name:  KindMachine,
		Table: "machines",
		Doc:   "a machine of the fleet, named by its tailnet host: the login, the seat, its ceiling, its runners, the sprint member's width on it, and whether it is a TLC record machine",
		Fields: []Field{
			{Name: "user", Type: TypeText, Required: true, Help: "the login the plays and seals use on it (ssh <user>@<name>)"},
			{Name: "seat", Type: TypeText, Required: true, Help: "its nova-secrets seat: the identity it opens secrets as, one <seat>.yaml in the store"},
			{Name: "slots", Type: TypeInt, Required: true, Help: "the machine ceiling apply writes to machine:<m>:ceiling, which the friends' desired slots must fit under; not the sprint's width"},
			{Name: "runners", Type: TypeInt, Help: "how many CI runners it hosts; 0 (the default) hosts none"},
			{Name: "width", Type: TypeInt, Nullable: true, Clear: "default", Help: "the most work cards the sprint's member on it runs at once, what nova-sprint fleet sync sets; set apart from --slots, never derived from it; unset (the default, or --width default) is half the machine's cores as its beat reports them, which fleet sync resolves; 0 is no member, dealt no work"},
			{Name: "tla", Type: TypeBool, Help: "a TLC record machine: the tools play installs the pinned TLC jar on it and tlacheck run --bench any picks among them; false (the default) is none"},
			noteField("why the machine is as it is: a hold, a rest, the load that was measured"),
		},
	},
	{
		Name:      KindFleet,
		Table:     "fleet",
		Singleton: true,
		Doc:       "the one row of fleet-wide facts: the store and coordinator machines, Redis port, explicit password-free Postgres URI, the bus store's address and the loops log directory",
		Fields: []Field{
			{Name: "store", Type: TypeRef, Ref: KindMachine, Help: "the machine that runs Redis (a machine row), or empty"},
			{Name: "coordinator", Type: TypeRef, Ref: KindMachine, Help: "the machine the coordinator's loops run on (a machine row), or empty"},
			{Name: "redis_port", Type: TypeInt, Nullable: true, Help: "the explicit TCP port Redis listens on, from 1 through 65535; unset until declared"},
			{Name: "pg_dsn", Type: TypeText, Help: "the explicit password-free postgres:// URI the configuration store uses; empty until set"},
			{Name: "bus", Type: TypeText, Help: "the bus store's Redis address, host:port, what nova-bus reads from the applied fleet:bus when NOVA_BUS_REDIS is unset; empty until set"},
			{Name: "loops_dir", Type: TypeText, Help: "the directory where loop logs are written; non-empty, seeded to ~/nova-bench/loops"},
		},
		Check: checkFleet,
	},
	{

		Name:  KindFriend,
		Table: "friends",
		Doc:   "an AI friend: her slots, which tiers she can do, her roles, and her width, the jobs she works at once, her delivery mode, the config directory her claude lanes run with, the per-card token cap her one-shot lanes hold a card at, and the optional streams and kinds restrictions on the work she may be dealt",
		Fields: []Field{
			{Name: "slots", Type: TypeInt, Required: true, Help: "her desired slots, under the ceiling of the machine her beat reports; no machine's width"},
			{Name: "tiers", Type: TypeList, Enum: Tiers, Required: true, Help: "which tiers she can do: comma list of " + strings.Join(Tiers, ", ")},
			{Name: "roles", Type: TypeList, Enum: FriendRoles, Help: "comma list of " + strings.Join(FriendRoles, ", ") + " (who coordinates is the sprint row's)"},
			{Name: "width", Type: TypeInt, Default: strconv.Itoa(DefaultFriendWidth), Help: "the jobs she works at once, the width nova-sprint friend sync sets on her friends row; at least 1, " + strconv.Itoa(DefaultFriendWidth) + " by default"},
			{Name: "mode", Type: TypeEnum, Enum: FriendModes, Default: DefaultFriendMode, Help: "how her daemon hands her work: one-shot (width lanes, each its own session, handed one card per turn); the retired batch (one session, many cards in one turn) is refused"},
			{Name: "config_dir", Type: TypeText, Nullable: true, Help: "the absolute directory a claude one-shot lane runs with as CLAUDE_CONFIG_DIR, her account's login and settings; unset (the default, or --config_dir '') for any other harness; nova-friend run refuses a claude friend in one-shot mode without it"},
			{Name: "token_cap", Type: TypeInt, Default: strconv.FormatInt(DefaultFriendTokenCap, 10), Help: "tokens one card may spend (input, cached input, output and reasoning summed) before a one-shot lane stops its own run and holds the card; " + strconv.FormatInt(DefaultFriendTokenCap, 10) + " by default, and 0 is no cap"},
			{Name: "streams", Type: TypeText, Help: "optional comma-separated glob patterns over stream names this friend may be dealt work on; empty means any stream"},
			{Name: "kinds", Type: TypeNames, Help: "optional comma-separated card KIND values this friend may be dealt; empty means any kind"},
		},
		Check: checkFriend,
		ApplyOrder: func(r Row) int {
			if hasWord(r.Fields["roles"], CoordinatorRole) {
				return 0
			}
			return 1
		},
		Derive: deriveCoordinator,
	},
	{
		Name:      KindSprint,
		Table:     "sprint",
		Singleton: true,
		Doc:       "the one row of sprint-global facts: which friend coordinates and the decide_* bars, each a probability in [0,1]; nova-config sprint set -h says what each bar decides",
		Fields: []Field{
			{Name: "coordinator", Type: TypeRef, Ref: KindFriend, Help: "the friend who holds the coordinator role (a friend row), or empty; set it to hand over"},
			{Name: FieldDecideBounce, Type: TypeDecimal, Default: "0.5", Help: "the decide read's bounce bar: a flash card whose first read gives p(defect) at or above it is bounced with the read's finding; a probability, at least --decide_review; 0.5 (the default); empty, with --decide_review empty, turns the decide read off"},
			{Name: FieldDecideReview, Type: TypeDecimal, Default: "0.3", Help: "the decide read's review bar: below it the card lands with no model read, and from it up to --decide_bounce it goes to a strings read; a probability; 0.3 (the default)"},
			{Name: FieldDecideScoreBar, Type: TypeDecimal, Help: "the landed score's bar: land scores every landed diff (nova-decide's score decision), and a batch whose cards' top class has a p at or above it raises one landed work scored low judgment listing them; a probability; empty (the default) records the scores and raises none; 0.7 is the starting point once a review round labels cards independently"},
			{Name: FieldDecideAttemptNoResult, Type: TypeDecimal, Help: "the attempt decision's no-result bar: a failed take nova-decide classes no-result at or above it ends as a take with no result (redealt, never failed work), whatever its reason line's prefix, but never a provider failure, a staging refusal or a launch refused; a probability; empty (the default) routes nothing on the decision, which is still asked, recorded and shown; 0.7 is the starting point the calibration of 2026-10-03 supports (class=no-result AUC 0.883; docs/SPEC-NOVA-DECIDE.md section 10)"},
			{Name: FieldDecideAttemptNothingToDo, Type: TypeDecimal, Help: "the attempt decision's nothing-to-do bar: a failed take nova-decide classes nothing-to-do at or above it is failed work of the class `decided nothing-to-do`, whatever its reason line says, but never a provider failure, a staging refusal or a launch refused; a probability; empty (the default) routes nothing on the decision, which is still asked, recorded and shown (class=nothing-to-do AUC 0.618 in the calibration of 2026-10-03; docs/SPEC-NOVA-DECIDE.md section 10)"},
			{Name: FieldDecideGrade, Type: TypeDecimal, Help: "the grade decision's bar: a card graded pro at or above it starts on pro instead of flash; a probability; empty (the default) keeps the grade a hint on the card; 0.7 is the starting point the calibration of 2026-10-03 supports (docs/SPEC-NOVA-DECIDE.md section 11)"},
			{Name: FieldDecideGateFlaky, Type: TypeDecimal, Default: "", Help: "the gate decision's flaky bar: a failing test of a red gate (a work card's, the lander's batch) whose p(flaky) is at or above it is rerun once before the take or the batch is reported red; a probability, summing above 1 with --decide_gate_preexisting when both are set; empty (the default) reruns nothing, and every gate decision is still recorded and shown; 0.8 is the starting point the calibration of 2026-10-03 reads (docs/SPEC-NOVA-DECIDE.md section 12)"},
			{Name: FieldDecideGatePreexisting, Type: TypeDecimal, Default: "", Help: "the gate decision's pre-existing bar: a work card's failing test whose p(pre-existing) is at or above it is reported `pre-existing: <test>`, the base's or the member's and never the card's; a probability; empty (the default) reclassifies nothing; 0.8 is the starting point, though at 0.8 24 of the calibration's 39 flaky failures would have been reported pre-existing"},
			{Name: FieldDecideJudgment, Type: TypeDecimal, Help: "the judgment bar: nova-sprint answer applies the verb the judgment decision chose when its probability is at or above it, and lists it for the coordinator below it; a probability; empty (the default) applies nothing: every decision is recorded and what a bar would apply is listed; 0.8 is a starting point measured on 100 of the coordinator's own judgments (docs/SPEC-NOVA-DECIDE.md section 13), not an independent calibration"},
			{Name: FieldDecideBriefBar, Type: TypeDecimal, Help: "the brief bar: nova-sprint add asks the brief decision of each card and refuses a card whose p(converges) is under it, naming the questions it failed; a probability; empty (the default) asks and reports only. The decision is uncalibrated (AUC 0.600 on 234 review labels, docs/SPEC-NOVA-DECIDE.md section 14): leave it empty until calibrate on the brief record's own outcomes supports a bar"},
			{Name: FieldAnswerRulesOff, Type: TypeList, Enum: AnswerRules, Help: "the rules the machine does not answer judgments by: comma list of " + strings.Join(AnswerRules, ", ") + "; empty (the default) answers by every rule: failed and no-result work redealt then raised a tier, a card at its bound raised a tier (heavy to a friend), a late card waited once or returned and redealt, a conflict in a file no ledger owns returned, redone on the tip and resumed, the same finding twice marked a brief defect, the base tree gate retried before a stream stops, a friend's card she has not started past its bound taken back and dealt again, failed work whose report HOLDs for a card not landed waiting for it, a reader's first finding reworked as the fix, and a late read asked of another reader once an attempt (docs/SPEC-SPRINT.md section 8, answered by rule)"},
		},
		Check: checkSprint,
	},
	{

		Name:  KindLoop,
		Table: "loops",
		Doc:   "a supervised loop on one machine: its command, the seat and secret names it opens, and how it runs (every n seconds or kept alive); a nova-swarm member's width, a reader's too, is its machine row's, never the argv's",
		Fields: []Field{
			{Name: "machine", Type: TypeRef, Ref: KindMachine, Required: true, Help: "the machine it runs on (a machine row)"},
			{Name: "argv", Type: TypeArgv, Required: true, Help: `the command as a JSON array of strings, the program first: '["/path/prog","--flag","v"]'; never a secret, which goes by name in --keys`},
			{Name: "seat", Type: TypeText, Help: "the nova-secrets seat on that machine it opens its secrets from, or empty when it needs none"},
			{Name: "keys", Type: TypeKeys, Help: "comma list of the names of the secrets it needs from the seat (API_KEY,...), never a value; empty when none"},
			{Name: "every", Type: TypeInt, Help: "seconds between runs of a periodic loop; 0 (the default) when it is kept alive"},
			{Name: "keepalive", Type: TypeBool, Help: "true for a long-running unit restarted when it exits; false (the default) when it runs --every n"},
			{Name: "enabled", Type: TypeBool, Default: "true", Help: "false writes the unit and does not start it; true (the default) runs it"},
		},
		Check:  checkLoop,
		Derive: deriveLoopLog,
	},
	{

		Name:  KindRoute,
		Table: "routes",
		Doc:   "a route of a model tier: the provider and model a card of that tier runs on, its token budget and deadline; the tier's array orders its routes; frontier cards are never dealt from routes, they escalate to the coordinator",
		Fields: []Field{
			{Name: "tier", Type: TypeEnum, Enum: RouteTiers, Required: true, Help: "the tier it serves: one of " + strings.Join(RouteTiers, ", ") + " (frontier cards are never drawn from routes, they escalate to the coordinator)"},
			{Name: "provider", Type: TypeText, Required: true, Help: "the provider word of the model id <provider>/<model> the harness is launched with: one word, no slash"},
			{Name: "model", Type: TypeText, Required: true, Help: "the model name after the provider, which may hold slashes (x-ai/grok-4); no blank"},
			{Name: "harness", Type: TypeEnum, Enum: harness.Kinds, Default: harness.OpenCode, Help: "the harness a card on this route runs under: opencode (the default: the providers table launches it with the provider's key) or a headless program of the machine's own subscription login, " + strings.Join(harness.Headless, ", ") + " (the heavy tier), whose --provider is " + harness.ProviderPrefix + "<harness>, one word per harness so one login's failure rests only its own routes"},
			{Name: "tokens", Type: TypeInt, Help: "the token budget per card; 0 (the default) is unmetered and the deadline is the only stop"},
			{Name: "usd", Type: TypeDecimal, Help: "the dollar budget per card, a decimal like 0.50: the harness's reported cost at which the card is stopped, beside the token budget; empty (the default) is none"},
			{Name: "deadline", Type: TypeInt, Required: true, Help: "the seconds a card on this route may run, above 0"},
			{Name: "enabled", Type: TypeBool, Default: "true", Help: "false takes it out of the deal and needs --note, the measured reason (a disabled route carries its reason); true (the default) keeps it in and needs none"},
			{Name: "first", Type: TypeBool, Default: "false", Help: "true deals this route before the others of its tier (the walk from the tier's index prefers it); false (the default) leaves the walk as it is"},

			{Name: cardcost.FieldInput, Type: TypeDecimal, Help: "USD per million uncached input tokens, a decimal like 0.30; empty (the default) when not known"},
			{Name: cardcost.FieldCacheRead, Type: TypeDecimal, Help: "USD per million cached input tokens read"},
			{Name: cardcost.FieldCacheWrite, Type: TypeDecimal, Help: "USD per million tokens written to the cache"},
			{Name: cardcost.FieldOutput, Type: TypeDecimal, Help: "USD per million output tokens"},
			{Name: cardcost.FieldReasoningAsOutput, Type: TypeBool, Default: "true", Help: "true (the default) bills reasoning tokens at the output price; false when the provider does not bill them apart"},
			{Name: cardcost.FieldLongContext, Type: TypeInt, Help: "the prompt size in tokens above which a request is priced at the long prices; 0 (the default) is none"},
			{Name: cardcost.FieldInputLong, Type: TypeDecimal, Help: "USD per million input tokens of a request above --" + cardcost.FieldLongContext},
			{Name: cardcost.FieldOutputLong, Type: TypeDecimal, Help: "USD per million output tokens of a request above --" + cardcost.FieldLongContext},
			{Name: cardcost.FieldRequest, Type: TypeDecimal, Help: "USD per request, on top of the tokens; empty when there is no fee"},
			{Name: cardcost.FieldBilling, Type: TypeEnum, Enum: cardcost.Billings, Default: cardcost.BillingMetered, Help: "how it is paid: metered (the default: per token) or plan (a subscription, so the predicted cost is the metered price of the same tokens)"},
			{Name: cardcost.FieldGateway, Type: TypeDecimal, Help: "the percent a gateway adds on top of the prices, a decimal like 5.5; empty when none"},
			{Name: cardcost.FieldSource, Type: TypeText, Help: "where the prices were read, free text (a URL)"},
			{Name: cardcost.FieldAsOf, Type: TypeText, Help: "the date the prices were read, YYYY-MM-DD"},
			noteField("why the route is as it is: the measured reason it is disabled, for one (ok out of total, the deadline it ran to)"),
		},
		Check:        checkRoute,
		CheckChanges: checkRouteChanges,
	},
	{

		Name:  KindTier,
		Table: "tiers",
		Doc:   "a model tier's route array: the deal takes routes[index mod len] for each card of the tier, a route named twice taking two turns; one row each for " + strings.Join(RouteTiers, " and ") + ", created by migrate",
		Fields: []Field{
			{Name: "routes", Type: TypeSeq, Ref: KindRoute, Help: "the ordered comma list of the tier's routes, a name repeated for more turns; each an enabled route of the tier; empty takes the tier's enabled routes in name order"},
		},
		Seed:  RouteTiers,
		Check: checkTier,
	},
}

Kinds is the registry, in apply order: machines first, the fleet row next (it names machines, and a friend's desired slots are charged to the fleet's coordinator machine when her beat names none), friends, the sprint row (it names a friend), loops (each names a machine), routes (each names no row), and tiers last (each names routes).

A machine's record is exactly the declared facts something reads, one reader each, and nothing invented. Its name is the tailnet host: `ssh <name>` reaches it, so there is no address field ("All fleet machines must be on the tailnet. This is a hard requirement."). Measured facts (os, arch, cores, memory) are never typed: they come live from the machine's own beat (Beat, docs/SPEC-CONFIG.md, "Declared and measured").

View Source
var NamePattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)

NamePattern is the shape of a row key: lower-case, digits and dashes, the registry key friends and machines already use in Redis.

View Source
var NovaTools = []string{
	"nova-bus", "nova-cairn", "nova-check", "nova-ci", "nova-config", "nova-decide",
	"nova-doctor", "nova-friend", "nova-fuse", "nova-local", "nova-memory", "nova-redis",
	"nova-sandbox", "nova-secrets", "nova-self-talk", "nova-swarm", "nova-table",
	"nova-tokens", "nova-up", "nova-update", "nova-version",
}

NovaTools are the programs of this repository (cmd/): the only words of an argv the check runs. A wrapper (nova-loop, a shell) is never run to ask it. TestNovaToolsIsEveryCommand holds the list to the directories of cmd/: a new command is added here, or that test is red.

View Source
var PriceLists = []PriceList{
	{Provider: ProviderOpenRouter, URL: OpenRouterModelsURL},
	{Provider: ProviderOpenCode, URL: OpenRouterModelsURL, Assumed: ProviderOpenRouter},
}

PriceLists are the providers with a list to refresh from, in order. OpenCode publishes none, so its rows are priced from OpenRouter's and keep that note until it does.

View Source
var RouteTiers = []string{"flash", "pro", "heavy"}

RouteTiers are the tiers a route serves: Tiers less frontier, whose cards are never drawn from routes and escalate to the coordinator.

View Source
var SprintTools = []string{"nova-card", "nova-sprint", "nova-work"}

SprintTools are the programs the same release ships from nova-sprint's own release (the split, v1.2.3: nova-update release build --sprint-release), not from cmd/. A loop that runs one is asked like any other.

View Source
var Tiers = []string{"flash", "frontier", "heavy", "pro"}

Tiers are the model tiers a friend can do, capacity.lua's filter_ok list (docs/SPEC-CONFIG.md, "friend"); TestTiersMatchCapacityFilter holds the Go and Lua lists equal.

Functions

func Argv

func Argv(canonical string) []string

Argv decodes a canonical TypeArgv value ("" is none).

func BeatKey

func BeatKey(m string) string

BeatKey is the machine's own heartbeat, written by the bench that runs on it (nova-sprint bench beat): host, at (unix ms), load1, ncpu, cpu today; os, arch and memory_gb when the beat carries them. nova-config reads it, never writes it.

func CheckLoopVerb

func CheckLoopVerb(ctx context.Context, r Row, probe VerbProbe) error

CheckLoopVerb is the refusal add and set make of an enabled loop row whose verb is gone, nil when none is. A disabled row passes: its unit is not started, and setting --enabled false is a way out. The kind's Check runs no program; nova-config asks this with HelpProbe beside it.

func Conflict

func Conflict(kind string, redis, wanted int64) error

Conflict is the refusal Stamp returns when the stamp moved.

func DeadLoops

func DeadLoops(ctx context.Context, rows []Row, probe VerbProbe) []string

DeadLoops is a line for each loop row whose verb is gone, in the rows' order: what status says, each with the remove that ends it.

func FleetKey

func FleetKey(field string) string

FleetKey is the plain key one fleet field is written to: fleet:store, fleet:coordinator.

func FriendBeatKey

func FriendBeatKey(f string) string

FriendBeatKey is a friend's own heartbeat (nova-friend): its host field is the machine she runs on now, what her desired slots are charged to.

func FriendMode

func FriendMode(r Row) string

FriendMode is a friend row's delivery mode: one-shot when the row names it, when the row has no mode, and when the row still names the retired batch (docs/SPEC-FRIEND.md, one-shot lanes).

func FriendTokenCap

func FriendTokenCap(r Row) int64

FriendTokenCap is a friend row's per-card token cap: its token_cap field, DefaultFriendTokenCap when the row has none or the field is not a non-negative integer. 0 is no cap.

func FriendWidth

func FriendWidth(r Row) int

FriendWidth is a friend row's width: its width field, DefaultFriendWidth when the row has none.

func HelpProbe

func HelpProbe(ctx context.Context, program, verb string) (bool, error)

HelpProbe asks program `help <verb>`: exit 0 is a verb it has; exit 2 with a refusal naming verbs (every nova tool's unknown-verb refusal) is a verb it has not; anything else, or a program not installed here, is an error.

func HistoryLine

func HistoryLine(c Change) string

HistoryLine is one change: `HISTORY id=<n> kind=<k> name=<n> op=<add|set|remove> actor=<a> at=<rfc3339>` then, for a set, each changed field as `<field>=<before>><after>`; for an add every field's value; for a remove every field's last value. A write that recorded a reason names it as `reason=<why>` after at=, before the fields, and one that recorded none prints no reason at all.

func Idem

func Idem(kind string, rev int64) string

Idem is the idempotency marker every apply write carries into cap:log: the kind and the revision it applied.

func IsConflict

func IsConflict(err error) bool

IsConflict reports whether err is the compare-and-set refusal.

func KindLine

func KindLine(k *Kind) string

KindLine is one line of `nova-config kinds`: `CONFIG KIND name=<k> table=config.<t> fields=<f,g,...> required=<f,...> rows=many|one` (one: a singleton kind, whose row the migration creates).

func KindNames

func KindNames() []string

KindNames lists the kinds in apply order.

func ListLine

func ListLine(k *Kind, row Row) string

ListLine is RowLine for a list: a Cut field longer than ListNoteRunes characters is cut there and ends in "...", so a row is one short line; show and --json print it whole.

func LiveLine

func LiveLine(b *Beat) string

LiveLine is the measured facts a machine's list and show lines carry after the declared fields when Redis is at hand (docs/SPEC-CONFIG.md, "Declared and measured"): ` os=<v> arch=<v> cores=<n> memory_gb=<n> beat=<rfc3339>`, each `-` when the beat does not carry it, and `beat=none` alone when the machine has no beat. Nothing here is stored or typed: it is what the machine reported last.

func LoopKey

func LoopKey(name string) string

LoopKey is a loop's hash: its fields, log, rev and at.

func LoopLockPath

func LoopLockPath(runDir, name string) string

LoopLockPath is the loop's lock: <runDir>/<name>.lock.

func LoopLog

func LoopLog(loopsDir, name string) string

LoopLog is where a loop's unit writes its output on its machine, derived from the fleet's loops_dir and the loop's name, and never typed: <loops_dir>/<name>.log. apply writes it into the loop's Redis hash beside the row's fields.

func LoopMetrics

func LoopMetrics(name string, starts int, at time.Time) string

LoopMetrics is the node_exporter textfile of one start: the starts counter and the time of the last start, each labelled with the loop's name.

func LoopMetricsPath

func LoopMetricsPath(dir, name string) string

LoopMetricsPath is the loop's node_exporter textfile in dir.

func LoopRunArgv

func LoopRunArgv(r Row) ([]string, error)

LoopRunArgv is the command loop run runs for a loop row, or why it refuses the row (ErrLoopNotRunnable). A disabled row is not started, as its unit is not. A row with keys opens its secrets through the nova-secrets exec the plays wrap around its argv, which the row does not carry: the unit's own command after `--` runs it.

func LoopStartsPath

func LoopStartsPath(runDir, name string) string

LoopStartsPath is the loop's start count: <runDir>/<name>.starts, written only under the lock.

func MachineCeilingKey

func MachineCeilingKey(m string) string

MachineCeilingKey is what ns_capacity_machine writes.

func MachineKey

func MachineKey(m string) string

MachineKey is the machine's registry hash.

func MigrateAs

func MigrateAs(dsn, role string) string

MigrateAs is dsn with its user replaced by role and no password in it: the --pg a run of migrate as that role takes, printed for a person to run. A URL DSN keeps its host, port, database and query; a keyword DSN has its user= token replaced, or appended when it has none.

func NextLoopStarts

func NextLoopStarts(prev []byte) int

NextLoopStarts is the start count after prev, the text of the starts file: one more than the number it holds, 1 when it is absent or holds no count.

func OpLine

func OpLine(word string, kind string, op Op) string

OpLine is one line of an apply or a check: `APPLY ADD kind=<k> name=<n>`, `APPLY SET kind=<k> name=<n> changed=<f,g>`, `APPLY REMOVE kind=<k> name=<n>`; the first word is CHECK when nothing is written.

func ParseOpenRouterList

func ParseOpenRouterList(b []byte) (map[string]ListPrice, error)

ParseOpenRouterList reads OpenRouter's models endpoint: {"data":[{"id", "pricing":{"prompt","completion","input_cache_read","input_cache_write", "request"}}]}, each price a decimal string in USD per token (per request for the fee). A model whose price is not a non-negative decimal (a router's -1) is left out; a list with no model at all is an error, so an empty answer never reads as every route missing.

func PasswordEnvFor

func PasswordEnvFor(role string) string

PasswordEnvFor is the variable a nova role's password is conventionally kept under by the fleet's seats: nova_config -> NOVA_PG_CONFIG_PASSWORD (docs/FRIENDS.md, the friend sync loop row), any other role NOVA_PG_<ROLE>_PASSWORD, the role upper-cased with its nova_ prefix cut.

func PlanLine

func PlanLine(c Change) string

PlanLine is the change a dry run would record (Plan): `CONFIG DRY-RUN op=<add|set|remove> kind=<k> name=<n> actor=<a> wrote=nothing` then the fields as HistoryLine prints them, and the reason it would record.

func PriceJump

func PriceJump(have, list string) bool

PriceJump says a price moved past PriceJumpFactor either way from have to list. A row with no price (have "") is never a jump: its first price is the list's. A price of 0 becoming one above 0 is.

func PriceListProviders

func PriceListProviders() string

PriceListProviders names the providers with a list, for a refusal.

func PriceStale

func PriceStale(have, list string) bool

PriceStale says a row's price differs from the list by more than PriceStalePercent of the list's: the row is a number nobody re-read. A price on one side only is stale; on neither, not.

func ReasonFrom

func ReasonFrom(ctx context.Context) string

ReasonFrom is the reason WithReason put on ctx, "" when none.

func Redact

func Redact(dsn string) string

Redact returns the DSN with any password replaced, for a line.

func Refused

func Refused(err error) bool

Refused reports whether err is a store refusal (exit 1), as opposed to a store that could not answer (exit 2).

func ResolveDSN

func ResolveDSN(flagValue string, getenv func(string) string) (string, error)

ResolveDSN is the Postgres DSN a tool dials: the flag, else NOVA_PG_DSN; a flag that carries a password is refused in every spelling the pgconn parser accepts (it would be on the command line, where a ps reads it); a DSN without one takes it from the variable NOVA_PG_PASSWORD_ENV names, NOVA_PG_PASSWORD when unset, and a named variable that is empty is refused with its name (the shape pkg/nsprint/redisauth keeps for Redis).

func RouteKey

func RouteKey(name string) string

RouteKey is a route's hash: its fields, name, rev and at. The deal reads every route of the set RoutesKey (internal/sprint/store, the routes read).

func RowLine

func RowLine(k *Kind, row Row) string

RowLine is a row: `FRIEND name=<n> <field>=<v> ...`, every field of the kind in declaration order, every value whole.

func SaidLine

func SaidLine(word, kind string, op Op) string

SaidLine is the line a caller prints for one op. An OpHeld name is the whole line, said verbatim, so a publish's held seat is one line (docs/SPEC-CONFIG.md, "sprint") and not wrapped. Every other op is OpLine. nova-config apply prints every op through it.

func SelfName

func SelfName(ctx context.Context, src SelfSource) (name, how string, err error)

SelfName is this machine's name as the config keys it, and how it was learnt: NOVA_MACHINE when set (lower-cased, and refused when it is no valid machine name), else the first label of the host's name on the tailnet when a tailnet is present and running, else the first label of the hostname. Names are lower-case. It refuses when none of the three yields a name. It never reads the inventory: whether the name is a machine row is the caller's check (machine self --check).

func ShowLine

func ShowLine(k *Kind, row Row) string

ShowLine is RowLine with the row's stamps.

func SoleOwner

func SoleOwner(o Ownership) (owner string, ok bool)

SoleOwner is the one other role that could run migrate as the catalog stands: it owns schema config and every table in it, and it is not o.Role. ok is false when no such role exists (tables of mixed owners, or the role itself owns everything). On 2026-10-08 the seat ran migrate as nova_admin against a store nova_config owned whole, and the remedy printed was six ALTER OWNER statements, which were the wrong fix for that fleet: the right one was to run migrate as nova_config, the owner.

func SprintKey

func SprintKey(field string) string

SprintKey is the plain key one sprint field is written to: sprint:coordinator.

func TailscaleStatus

func TailscaleStatus(ctx context.Context) ([]byte, error)

TailscaleStatus runs `tailscale status --json --peers=false` when the program is installed, bounded to 5 s, and ErrNoTailnet when it is not: it shells out only where there is a tailnet to ask.

func TierKey

func TierKey(name string) string

TierKey is a tier's hash: its route array (routes), name, rev and at. The deal reads its routes field with the routes (internal/sprint/store, the routes read).

func ValidateFleetEndpoints

func ValidateFleetEndpoints(fleet View) error

ValidateFleetEndpoints refuses incomplete fleet endpoints before apply writes or inventory rewrites loop argv (docs/SPEC-CONFIG.md, "Apply").

func ValidateName

func ValidateName(name string) error

ValidateName checks a row key.

func Value

func Value(s string) string

Value renders one field value.

func WholeOwner

func WholeOwner(o Ownership) (owner string, ok bool)

WholeOwner is the one role that owns schema config and every table in it, whichever role is connected: the owner the seat play migrates as, and the check that the store is owned whole before any window opens. ok is false when the schema does not exist yet or the tables have more than one owner (mixed ownership: migrate as any role refuses, and the remedy is one ALTER OWNER per table, run by a person).

func WithReason

func WithReason(ctx context.Context, reason string) context.Context

WithReason returns ctx carrying reason as the reason of the writes made with it; "" is no reason.

Types

type AnsibleGroup

type AnsibleGroup struct {
	Hosts []string       `json:"hosts"`
	Vars  map[string]any `json:"vars,omitempty"`
}

AnsibleGroup is one group in an Ansible JSON inventory.

type AnsibleInventory

type AnsibleInventory struct {
	Meta          AnsibleMeta  `json:"_meta"`
	All           AnsibleGroup `json:"all"`
	Benches       AnsibleGroup `json:"benches"`
	Coordinator   AnsibleGroup `json:"coordinator"`
	Store         AnsibleGroup `json:"store"`
	StoreDeployer AnsibleGroup `json:"store_deployer"`
	Runners       AnsibleGroup `json:"runners"`
	TLA           AnsibleGroup `json:"tla"`
}

AnsibleInventory is the standard Ansible dynamic inventory JSON representation. store_deployer is the coordinator machine: it holds the seat that loads the function library and the ACL onto the store (fleet/tools.yml, fleet/redis.yml). tla is the machines whose row says tla=true, the TLC record machines the tools play's tla play installs the pinned jar on.

func BuildInventory

func BuildInventory(snap *Snapshot, localHost string) (*AnsibleInventory, error)

BuildInventory builds the inventory from one snapshot of the applied state. When localHost matches a machine name, that host's variables include ansible_connection=local so the machine running the command reaches itself without ssh. A loop whose view does not parse, or names a machine with no row, is an error naming it: the plays never guess at a unit.

func (*AnsibleInventory) Has

func (inv *AnsibleInventory) Has(name string) bool

Has reports whether a machine row has exactly this name.

func (*AnsibleInventory) HostJSON

func (inv *AnsibleInventory) HostJSON(name string) ([]byte, error)

HostJSON serializes the variables of one host into indented JSON format. A name the inventory does not hold is an *UnknownHostError, never an empty object.

func (*AnsibleInventory) JSON

func (inv *AnsibleInventory) JSON() ([]byte, error)

JSON serializes the inventory into indented JSON format.

type AnsibleMeta

type AnsibleMeta struct {
	Hostvars map[string]map[string]any `json:"hostvars"`
}

AnsibleMeta holds host-specific variables in an Ansible JSON inventory.

type Applier

type Applier interface {
	// Read returns Redis's rows of the kind by name, and the revision of the
	// kind stamped in config:decl (0 when none). It writes nothing, so
	// --check can call it against a store whose function library is
	// missing.
	Read(ctx context.Context, kind string) (map[string]View, int64, error)
	// Prepare is called once before the first write: it installs the Redis
	// Function library when the store has none (fn.LoadMissing).
	Prepare(ctx context.Context) error
	// Write adds (prev nil) or updates one row through the runtime's own
	// functions. A refusal (CEILING, an actor without the role) is a
	// *RefusedError.
	Write(ctx context.Context, kind string, row Row, prev View, actor, idem string) error
	// Remove removes one row's keys. A row the runtime still holds (a
	// machine with consumers) is a *RefusedError naming them, and
	// nothing is written.
	Remove(ctx context.Context, kind, name, actor, idem string) error
	// Stamp records rev as the kind's applied revision, compare-and-set
	// against prev: a store whose stamp moved past prev is ErrConflict.
	Stamp(ctx context.Context, kind string, prev, rev int64) error
}

Applier is the Redis side of apply. RedisApplier is the one over go-redis; the unit tests hand in a fake.

type Beat

type Beat struct {
	OS, Arch, Cores, MemoryGB, At string
}

Beat is what a machine reported last, read from BeatKey: never stored in Postgres or typed configuration because measured facts come from the live beat. Cores is the beat's ncpu; OS, Arch and MemoryGB are "" until the beat carries them; At is the beat's time as RFC 3339 UTC.

type BeatReader

type BeatReader interface {
	Beats(ctx context.Context, names []string) (map[string]*Beat, error)
}

BeatReader reads the beats of named machines; nil for one with no beat.

type Change

type Change struct {
	ID     int64
	Kind   string
	Name   string
	Op     string // OpAdd, OpSet or OpRemove
	Before map[string]string
	After  map[string]string
	Actor  string
	// Reason is why the write was made, the --reason its verb gave ("" when
	// none): the issue's "every config change recording --reason in its
	// history" (nova-tools#5101). It is metadata of the write, not a row
	// field, so it is kept apart from Before and After: PostgreSQL carries
	// it in the history row's own JSON (historyReasonKey, pg.go), Mem in this
	// field, and History hands it back whole either way.
	Reason string
	At     string // RFC 3339 UTC
}

Change is one history row.

func PlanWrite

func PlanWrite(ctx context.Context, st Store, op, kind string, row Row, changes map[string]string) (Change, error)

PlanWrite is the history row a write would add, worked out from the store as it stands and written nowhere: the dry run of Insert (OpAdd, row), Update (OpSet, row.Name and changes) and Delete (OpRemove, row.Name), with the refusals both stores make before they write (a name taken or missing, the kind's Check, a ref naming no row, a row another names or the migration made), from the same checks. The change has no id and no instant: nothing was recorded.

type Field

type Field struct {
	Name string
	Type Type
	// Required is true when add refuses a row without it. set never requires
	// a field: it updates the ones named.
	Required bool
	// Enum is the word list of a TypeEnum or TypeList field.
	Enum []string
	// Ref is the kind a TypeRef or TypeSeq field names.
	Ref string
	// Help is the flag's help line, one sentence.
	Help string
	// Default is the canonical value add stores for a field it is not
	// given. "" means the type's zero: 0 for an int, false for a bool, the
	// empty value for the rest, or an unset value when Nullable is true.
	Default string
	// Nullable leaves an omitted field unset, represented as SQL NULL.
	Nullable bool
	// Clear, on a Nullable field, is the word a set takes to clear the field
	// back to unset ("default" for a machine's width).
	Clear string
	// Cut is a free-text field the list verbs print cut to ListNoteRunes
	// characters (ListLine), so one row stays one short line; show, --json and
	// the history keep it whole.
	Cut bool
}

Field is one column of a kind: the flag `--<Name>` on add and set, the column of the same name (quoted) in Postgres, and the key on every typed line.

func (Field) Canonical

func (f Field) Canonical(raw string) (string, error)

Canonical validates one field's raw value and returns its canonical text: an int as digits, an enum as its word, a list deduplicated and sorted, a wake path in its one spelling. The error names the field and what it wants.

type FileStore

type FileStore struct {
	*Mem
	// contains filtered or unexported fields
}

FileStore is the Store kept in one local JSON file: the rows and the history of a Mem, read when it is opened and written whole after every write. It is for trying the tool with no database (--file <path>): the same kinds, refusals, history and revisions as PostgreSQL, because it is the same strict Mem the tests hold to the store contract. It is never the fleet's store: apply and inventory read Redis, and the runtime tools read PostgreSQL. One process writes it at a time.

Libraries considered: natefinch/atomic (not a module of this repository); the write is os.CreateTemp in the file's directory and os.Rename, so a reader never sees half a file.

func OpenFile

func OpenFile(path string) (*FileStore, error)

OpenFile opens the store file at path. A file that is not there is an empty store, as a database migrate has not run on: every read and write refuses until Migrate writes the file.

func (*FileStore) Applied

func (f *FileStore) Applied(context.Context) ([]int, error)

Applied is the ledger: every migration once the file is there (a file is read up to this binary's schema), none before.

func (*FileStore) Close

func (f *FileStore) Close() error

Close writes nothing: every write saved the file as it landed.

func (*FileStore) Counts

func (f *FileStore) Counts(ctx context.Context) (map[string]int, error)

func (*FileStore) Delete

func (f *FileStore) Delete(ctx context.Context, kind, name, actor string) (int64, error)

func (*FileStore) Get

func (f *FileStore) Get(ctx context.Context, kind, name string) (Row, bool, error)

func (*FileStore) History

func (f *FileStore) History(ctx context.Context, kind, name string) ([]Change, error)

func (*FileStore) Insert

func (f *FileStore) Insert(ctx context.Context, kind string, row Row, actor string) (int64, error)

func (*FileStore) List

func (f *FileStore) List(ctx context.Context, kind string) ([]Row, error)

func (*FileStore) Migrate

func (f *FileStore) Migrate(ctx context.Context) (from, to int, applied []int, err error)

Migrate makes the file when it is not there (from 0, every migration applied) and otherwise rewrites it at this binary's schema, applying none.

func (*FileStore) Ownership

func (f *FileStore) Ownership(context.Context) (Ownership, error)

Ownership is the zero Ownership: a file has no roles and no owners, so migrate's preflight has nothing to refuse.

func (*FileStore) Path

func (f *FileStore) Path() string

Path is the file the store is kept in.

func (*FileStore) Rev

func (f *FileStore) Rev(ctx context.Context, kind string) (int64, error)

func (*FileStore) Sessions

func (f *FileStore) Sessions(context.Context) ([]Session, error)

Sessions is none: a file has no other backends.

func (*FileStore) Update

func (f *FileStore) Update(ctx context.Context, kind, name string, changes map[string]string, actor string) (Row, int64, error)

func (*FileStore) Version

func (f *FileStore) Version(context.Context) (int, error)

Version is the schema the file is at: 0 before migrate made it, else this binary's (a file is read up to it when opened).

type Gap

type Gap struct {
	Table string
	Owner string
}

Gap is one thing the role lacks to apply a migration: a table of schema config another role owns, or, with Table "", the right to create tables in the schema (Owner is then the schema's owner).

func Gaps

func Gaps(o Ownership) []Gap

Gaps is every gap the role has on schema config: each table another role owns, in table order, then a missing CREATE on the schema. No schema yet (a fresh database, where the first migration makes it) has none.

func MigrateGaps

func MigrateGaps(o Ownership, pending []Migration) []Gap

MigrateGaps is migrate's preflight, the decision before anything is applied: the role that runs migrate must own every table in schema config and be able to create tables in it, because migrations alter and fill tables, which only their owner may do, and some make tables. Nothing pending has no gaps, whatever the owners; otherwise the gaps are Gaps. The SQL is never read: the rule covers every migration.

func (Gap) Remedy

func (g Gap) Remedy(role string) string

Remedy is the one statement a role with the owner's rights runs, once, to close the gap: printed, never executed (migrate changes no ownership). Catalog identifiers are quoted (docs/SPEC-CONFIG.md, "The schema").

type InventoryLoop

type InventoryLoop struct {
	Name      string   `json:"name"`
	Argv      []string `json:"argv"`
	Seat      string   `json:"seat"`
	Keys      []string `json:"keys"`
	Every     int      `json:"every"`
	Keepalive bool     `json:"keepalive"`
	Enabled   bool     `json:"enabled"`
	Log       string   `json:"log"`
}

InventoryLoop is one loop record as a host variable: the loop kind's fields, typed (docs/FLEET.md, "Loops"). Log is the view's log, which the loop kind derives from the fleet's loops_dir and the name (<loops_dir>/<name>.log) and never takes typed.

type Kind

type Kind struct {
	// Name is the kind's word in the grammar (`nova-config <kind> ...`), the
	// singular; Table is its table under schema config.
	Name  string
	Table string
	// Fields are the columns after the row key `name`, in the order every
	// line prints them and every migration declares them.
	Fields []Field
	// Doc is the one sentence `nova-config kinds` prints about the kind.
	Doc string
	// Singleton is a kind of exactly one row, named as the kind is (the
	// fleet has exactly one coordinator at a time). Its migration
	// creates the row, so the grammar has no add,
	// remove or list and its set, show and history take no name
	// (docs/SPEC-CONFIG.md, "Singleton kinds").
	Singleton bool
	// Seed are the rows the kind's migration creates, every field at its
	// default (the tiers), so set takes them on a new store. nil is none.
	Seed []string
	// Derive, when set, is run by Apply on the kind's rows before they are
	// planned: a value another kind's row decides (the sprint's coordinator
	// as a friend's Redis role) is added here, so Redis holds it and the
	// stored row does not. nil derives nothing.
	Derive func(ctx context.Context, st Store, rows []Row) ([]Row, error)
	// ApplyOrder sorts the rows of this kind for apply: a row with a lower
	// number is written first. nil keeps name order. Friends put the
	// coordinator first so ns_friend_roles' bootstrap has one.
	ApplyOrder func(r Row) int
	// Check, when set, is the rule across a row's fields that no one field
	// can say (a loop runs every n seconds or is kept alive, never both).
	// add runs it on the new row before any store is opened; every store
	// runs it on the row a set would leave, and refuses the set with
	// ErrInvalid. nil checks nothing. add also runs it when some other
	// field is already refused, so one refusal names every problem; a
	// field that failed its own validation is then absent from r.Fields,
	// and a rule that needs it is skipped (the field's own refusal says
	// what is wrong).
	Check func(r Row) error
	// CheckChanges, when set, is the rule on what a set names, which the row it
	// leaves cannot tell (a route set --enabled false names its reason in the
	// same set). Changes runs it on the canonical values before any store is
	// opened. nil checks nothing.
	CheckChanges func(changes map[string]string) error
}

Kind is one kind of configuration. See the package comment.

func Lookup

func Lookup(name string) (*Kind, bool)

Lookup finds a kind by name.

func (*Kind) Changes

func (k *Kind) Changes(raw map[string]string) (map[string]string, error)

Changes validates the named fields of a set and returns their canonical values. Every problem is reported at once.

func (*Kind) Field

func (k *Kind) Field(name string) (Field, bool)

Field finds a field by name.

func (*Kind) FieldNames

func (k *Kind) FieldNames() []string

FieldNames lists the fields in declaration order.

func (*Kind) NewRow

func (k *Kind) NewRow(name string, raw map[string]string) (Row, error)

NewRow builds a canonical row of the kind from raw flag values: every field named in raw is validated, every required field must be present, and every problem is reported in one error so a first run is refused once (docs/ONBOARDING.md point 2). Fields not named are "" (an int, 0).

func (*Kind) Sorted

func (k *Kind) Sorted(rows []Row) []Row

Sorted returns the rows in apply order: Kind.ApplyOrder first, then name.

type ListPrice

type ListPrice struct {
	Input, CacheRead, CacheWrite, Output, Request string
}

ListPrice is one model's prices as a list publishes them, each a canonical decimal in the route's units (USD per million tokens; the request fee USD per request), "" when the list carries none.

type LoopCall

type LoopCall struct{ Program, Verb string }

LoopCall is one nova program an argv runs and the verb it runs it with.

func DeadLoopVerb

func DeadLoopVerb(ctx context.Context, argv []string, probe VerbProbe) (LoopCall, bool)

DeadLoopVerb is the first call of argv whose verb probe says is gone, ok false when none is. A call the probe cannot answer is skipped: the binary a loop runs may be installed only on the loop's machine.

func LoopCalls

func LoopCalls(argv []string) []LoopCall

LoopCalls is every nova program in argv followed by a verb word, in order: the program under each wrapper (nova-secrets exec ... -- nova-sprint where) as well as the wrapper's own. help and version are every tool's.

type MachineWidth

type MachineWidth struct {
	Machine string
	// Width is the row's width field; 0 when Default.
	Width int
	// Default says the row has no width: the default, resolved from the cores.
	Default bool
}

MachineWidth is one machine row's width.

func WidthOf

func WidthOf(ws []MachineWidth, name string) (w MachineWidth, found bool)

WidthOf is one machine's width from Widths; found is false when no machine row has the name.

func Widths

func Widths(ctx context.Context, st Store) ([]MachineWidth, error)

Widths is every machine row's width, in name order, read from the machine rows alone.

func (MachineWidth) Line

func (w MachineWidth) Line() string

Line is the width's one printed line.

func (MachineWidth) Member

func (w MachineWidth) Member() bool

Member says whether the machine is a member of the sprint's fleet: its width is above 0, or it has the default width.

type Mem

type Mem struct {
	Now func() time.Time
	// Catalog is what Ownership answers; a test sets the owners it needs.
	Catalog Ownership
	// Held is what Sessions answers: the other backends a test says hold the
	// database (none, as a quiet store).
	Held []Session
	// contains filtered or unexported fields
}

Mem is the in-memory Store the unit tests use. It is strict like PG: the same refusals, the same history, the same revision.

func NewMem

func NewMem() *Mem

NewMem returns an empty store: no rows of any kind but the singleton kinds' one row each, as a migrated Postgres has.

func (*Mem) Counts

func (m *Mem) Counts(_ context.Context) (map[string]int, error)

func (*Mem) Delete

func (m *Mem) Delete(ctx context.Context, kind, name string, actor string) (int64, error)

func (*Mem) Get

func (m *Mem) Get(_ context.Context, kind, name string) (Row, bool, error)

func (*Mem) History

func (m *Mem) History(_ context.Context, kind, name string) ([]Change, error)

func (*Mem) Insert

func (m *Mem) Insert(ctx context.Context, kind string, row Row, actor string) (int64, error)

func (*Mem) List

func (m *Mem) List(_ context.Context, kind string) ([]Row, error)

func (*Mem) Ownership

func (m *Mem) Ownership(context.Context) (Ownership, error)

func (*Mem) Rev

func (m *Mem) Rev(_ context.Context, kind string) (int64, error)

func (*Mem) Sessions

func (m *Mem) Sessions(context.Context) ([]Session, error)

Sessions is the other backends holding the database: Held, as a test set it.

func (*Mem) Update

func (m *Mem) Update(ctx context.Context, kind, name string, changes map[string]string, actor string) (Row, int64, error)

type Migration

type Migration struct {
	Version int
	Name    string
	SQL     string
}

Migration is one embedded migration.

func Migrations

func Migrations() ([]Migration, error)

Migrations lists the embedded migrations in version order. A file whose name does not begin with its number, or a number declared twice, is an error: the ledger keys on the number.

func Pending

func Pending(all []Migration, from int) []Migration

Pending is the migrations of all (in version order, as Migrations lists them) after version from.

type Op

type Op struct {
	Op      string   // OpAdd, OpSet, OpRemove or OpHeld
	Name    string   // the row's name, or the whole held line for OpHeld (SaidLine prints it verbatim)
	Changed []string // the fields that differ, for OpSet
	Row     Row      // the row to write, for OpAdd and OpSet
	Prev    View     // Redis's row, for OpSet and OpRemove
}

Op is one line of a plan.

func Plan

func Plan(k *Kind, rows []Row, views map[string]View) []Op

Plan diffs the kind's rows against Redis's views: an add for a row Redis lacks, a set for one that differs in any field, a remove for a name in Redis that Postgres has not. Adds and sets come in the kind's apply order, then removes by name, so a machine's ceiling is written before a friend on it is, and a friend is unregistered after everything else.

type Ownership

type Ownership struct {
	// Role is the connected role (current_user).
	Role string
	// SchemaOwner owns schema config; "" when the schema does not exist yet.
	SchemaOwner string
	// Create is whether Role may create tables in schema config.
	Create bool
	// Tables is each table of schema config and the role that owns it.
	Tables map[string]string
}

Ownership is what the catalog says about schema config for the role a store is connected as (Store.Ownership reads it in one query). migrate's preflight decides from it, before anything is applied, whether that role can apply the pending migrations (MigrateGaps).

type PG

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

PG is the Postgres Store. Open it with OpenPG; the DSN carries no password on a command line (cmd/nova-config resolves it from the environment).

func OpenPG

func OpenPG(ctx context.Context, dsn string) (*PG, error)

OpenPG opens the store and pings it once, so a wrong address or login is refused here rather than on the first verb. The ping is bounded by the context's deadline when it has one and by ConnectTimeout when it has none.

func (*PG) Applied

func (p *PG) Applied(ctx context.Context) ([]int, error)

Applied is the ledger: every version recorded in config.schema_migrations, in order, none before the first migrate. migrate --dry-run prints it, so a version missing below the greatest is seen rather than assumed.

func (*PG) Close

func (p *PG) Close() error

Close closes the pool.

func (*PG) Counts

func (p *PG) Counts(ctx context.Context) (map[string]int, error)

func (*PG) Delete

func (p *PG) Delete(ctx context.Context, kind, name string, actor string) (int64, error)

func (*PG) Get

func (p *PG) Get(ctx context.Context, kind, name string) (Row, bool, error)

func (*PG) History

func (p *PG) History(ctx context.Context, kind, name string) ([]Change, error)

func (*PG) Insert

func (p *PG) Insert(ctx context.Context, kind string, row Row, actor string) (int64, error)

func (*PG) List

func (p *PG) List(ctx context.Context, kind string) ([]Row, error)

func (*PG) Migrate

func (p *PG) Migrate(ctx context.Context) (from, to int, applied []int, err error)

Migrate applies every embedded migration the ledger lacks, each in its own transaction with its ledger row, and returns the version before, the version after and the versions applied. Running it twice applies nothing the second time.

func (*PG) Ownership

func (p *PG) Ownership(ctx context.Context) (Ownership, error)

Ownership reads schema config's owner, whether the connected role may create in it, and each table's owner, in one catalog query; the schema's absence is an empty SchemaOwner and no tables.

func (*PG) Rev

func (p *PG) Rev(ctx context.Context, kind string) (int64, error)

func (*PG) Sessions

func (p *PG) Sessions(ctx context.Context) ([]Session, error)

Sessions is every other backend of this database connected as a nova role (usename nova_*), as pg_stat_activity shows it to any role: the role, application_name and pid are visible to every connected role; the query and client columns are not, so they are not read. The connection's own backend is left out.

func (*PG) Update

func (p *PG) Update(ctx context.Context, kind, name string, changes map[string]string, actor string) (Row, int64, error)

func (*PG) Version

func (p *PG) Version(ctx context.Context) (int, error)

Version is the greatest applied migration, 0 before the first migrate (the ledger table itself does not exist yet).

type PriceJudgment

type PriceJudgment struct {
	Field, Have, List string
}

PriceJudgment is a price the refresh would not set, or a price the row still holds stale: it moved past PriceJumpFactor since the row's last read, or it differs from the list by more than PriceStalePercent.

type PriceList

type PriceList struct {
	Provider string // the route rows' provider word
	URL      string
	Assumed  string // "" when the list is the provider's own; else whose list stands in
}

PriceList is where a provider's route prices are read: the list's URL, and whether the list is the provider's own or assumed from another's.

func PriceListOf

func PriceListOf(provider string) (PriceList, bool)

PriceListOf is the list a provider's routes are refreshed from; ok is false when it has none.

type PriceRefresh

type PriceRefresh struct {
	Route     string
	Provider  string
	Model     string
	ListID    string            // the model's id on the list; "" when missing
	Assumed   string            // whose list stands in for the provider's own
	Changes   map[string]string // the fields the write sets, price_as_of and price_source among them; nil when none
	Moved     []string          // the price fields whose value changes, in listFields order
	Judgments []PriceJudgment
	Stale     []PriceJudgment // the fields whose row differed from the list by more than PriceStalePercent, in listFields order
	Missing   bool            // the model is not on the list: nothing is set
}

PriceRefresh is what a refresh does to one route: the fields it sets, the prices it refuses as judgments, the prices it names stale, or why the list says nothing of it.

func PlanPriceRefresh

func PlanPriceRefresh(routes []Row, list map[string]ListPrice, provider, source, today string) []PriceRefresh

PlanPriceRefresh is the refresh of the enabled routes of the given providers ("" every provider with a list) against a list read from source on today (YYYY-MM-DD), in name order. A price the list carries is set unless it moved past PriceJumpFactor (a judgment, left as it is); a field the list does not carry is left as it is. A route the refresh reaches gets price_as_of today and price_source the list's URL, unless no price is set from the list (a missing model; every price a judgment) or the row already says both. A field whose stored price differs from the list by more than PriceStalePercent is named stale, whether or not the refresh sets it.

type RedisApplier

type RedisApplier struct {
	Client *redis.Client
	// Now is the stamp's clock (time.Now when nil).
	Now func() time.Time
	// contains filtered or unexported fields
}

RedisApplier is the Applier over a live store.

func (*RedisApplier) Beats

func (a *RedisApplier) Beats(ctx context.Context, names []string) (map[string]*Beat, error)

Beats reads every named machine's beat in one pipeline.

func (*RedisApplier) PrefetchFriends

func (a *RedisApplier) PrefetchFriends(ctx context.Context, names []string) error

PrefetchFriends pipelines the per-friend beat reads and fleet coordinator lookup across all friends being applied (redis.go:255, 262).

func (*RedisApplier) Prepare

func (a *RedisApplier) Prepare(ctx context.Context) error

Prepare installs the nova_sprint function library when the store has none (fn.LoadMissing: never replaces a deployed one). It runs at most once per process (RedisApplier).

func (*RedisApplier) Read

func (a *RedisApplier) Read(ctx context.Context, kind string) (map[string]View, int64, error)

func (*RedisApplier) Remove

func (a *RedisApplier) Remove(ctx context.Context, kind, name, actor, idem string) error

func (*RedisApplier) Snapshot

func (a *RedisApplier) Snapshot(ctx context.Context) (*Snapshot, error)

Snapshot reads the applied state the inventory is built from in two round trips, whatever the fleet's size: the names (the machines and loops sets), every declared fleet field and config:decl first, then every machine's hash, ceiling and beat and every loop's hash in one pipeline. It writes nothing.

func (*RedisApplier) Stamp

func (a *RedisApplier) Stamp(ctx context.Context, kind string, prev, rev int64) error

Stamp is the compare-and-set of friend_declare.lua in a WATCH/MULTI: the stamp is written only while it still reads prev.

func (*RedisApplier) Write

func (a *RedisApplier) Write(ctx context.Context, kind string, row Row, prev View, actor, idem string) error

type RefusedError

type RefusedError struct {
	Err    error
	Detail string
}

RefusedError is a store refusal with the words for the line: Err is one of the sentinels above, Detail says which row or field.

func RedisRefusal

func RedisRefusal(format string, args ...any) *RefusedError

RedisRefusal is a *RefusedError with a formatted detail.

func (*RefusedError) Error

func (e *RefusedError) Error() string

func (*RefusedError) Unwrap

func (e *RefusedError) Unwrap() error

type Result

type Result struct {
	Kind                string
	Rev                 int64 // Postgres's revision of the kind
	Add, Set, Remove    int
	Ops                 []Op
	Check               bool // --check: nothing was written
	RedisRev            int64
	PreparedForWritesOK bool
}

Result is one kind's apply.

func Apply

func Apply(ctx context.Context, st Store, ap Applier, kind, actor string, check bool, report func(Op)) (Result, error)

Apply applies one kind from the store into Redis (docs/SPEC-CONFIG.md, "Apply"): read Postgres and its revision, read Redis and its stamp, refuse CONFLICT when Redis is ahead, plan, and unless check is set write every op in order and stamp the revision. Every op is reported to report before it is written, so a refusal part way names what was written before it. A publish never moves the sprint seat (docs/SPEC-CONFIG.md, "sprint"): a sprint:coordinator the live store disagrees with is held, every other sprint field is written and one OpHeld line is said. The seat moves by nova-sprint's seat verb, or by ApplyMovingSeat (nova-config apply --move-seat).

func ApplyMovingSeat

func ApplyMovingSeat(ctx context.Context, st Store, ap Applier, kind, actor string, check bool, report func(Op)) (Result, error)

ApplyMovingSeat is Apply with the seat move named (docs/SPEC-CONFIG.md, "sprint"): it writes a sprint:coordinator the live store disagrees with. nova-config apply --move-seat calls it.

type Row

type Row struct {
	Name   string
	Fields map[string]string
	// CreatedAt and UpdatedAt are RFC 3339 UTC, "" for a row a store has not
	// stamped (a row built from flags).
	CreatedAt, UpdatedAt string
}

Row is one row of a kind: its name and every field as canonical text (ints as digits, lists sorted and comma joined, "" for an empty value). Values are text end to end so the descriptor, not the row, knows the type.

func (Row) Clone

func (r Row) Clone() Row

Clone copies a row.

func (Row) Int

func (r Row) Int(field string) int

Int reads an int field of a canonical row (0 when absent).

type SelfSource

type SelfSource struct {
	Getenv   func(string) string
	Hostname func() (string, error)
	// Tailscale returns `tailscale status --json --peers=false`, or
	// ErrNoTailnet when the program is not installed. nil is the same as
	// ErrNoTailnet.
	Tailscale func(ctx context.Context) ([]byte, error)
}

SelfSource is what SelfName reads, each a seam a test replaces.

type Session

type Session struct {
	Role        string
	Application string
	PID         int
}

Session is another backend holding the store's database, as the catalog (pg_stat_activity) shows it to the connected role: the role, the client's application name when it set one, and the backend's pid. migrate --window refuses while any nova role but its own session holds the database: the window of the seat play (fleet/tools.yml) has the old server and member stopped, and a migration that is not an addition the old build tolerates must never run under one.

func (Session) String

func (s Session) String() string

String is the session as a refusal names it.

type Snapshot

type Snapshot struct {
	// Machines are the machine views by name: user, seat, slots, runners.
	Machines map[string]View
	// Fleet is the fleet row's view: store, coordinator, redis_port, pg_dsn, bus.
	Fleet View
	// Loops are the loop views by name, nil when the loop kind was never
	// applied (no rev:loop in config:decl).
	Loops map[string]View
	// Beats are the machines' measured facts; a machine with no beat is
	// absent.
	Beats map[string]*Beat
	// Revs are config:decl's revisions by kind, 0 when a kind was never
	// applied.
	Revs map[string]int64
}

Snapshot is the applied state the inventory is built from: one read of the Redis view (RedisApplier.Snapshot), or a fixture file (LoadFixture).

func LoadFixture

func LoadFixture(path string) (*Snapshot, error)

LoadFixture reads a fixture file into the snapshot the Redis view would give for the same rows: the loop fields in their canonical text (argv compact JSON, keys a sorted comma list, booleans true or false).

type Store

type Store interface {
	// Get reads one row; found is false when there is none.
	Get(ctx context.Context, kind, name string) (row Row, found bool, err error)
	// List reads every row of a kind, by name.
	List(ctx context.Context, kind string) ([]Row, error)
	// Insert adds a row. A row of that name is ErrExists; a ref field naming
	// no row of its kind is ErrNoRef. It returns the history id.
	Insert(ctx context.Context, kind string, row Row, actor string) (int64, error)
	// Update changes the named fields of a row (ErrNotFound when there is
	// none) and returns the row after and the history id.
	Update(ctx context.Context, kind, name string, changes map[string]string, actor string) (Row, int64, error)
	// Delete removes a row (ErrNotFound when there is none; ErrReferenced
	// when a row of another kind names it) and returns the history id.
	Delete(ctx context.Context, kind, name string, actor string) (int64, error)
	// History reads the change rows of one row, oldest first.
	History(ctx context.Context, kind, name string) ([]Change, error)
	// Rev is the kind's revision: the greatest history id of the kind, 0
	// when it has none. apply stamps it into Redis.
	Rev(ctx context.Context, kind string) (int64, error)
	// Counts is the row count per kind that has many (a singleton kind is
	// always one row and is left out).
	Counts(ctx context.Context) (map[string]int, error)
	// Ownership reads from the catalog who owns schema config and each of
	// its tables, and the role connected: migrate's preflight (MigrateGaps).
	// A store with no roles (the file) answers the zero Ownership.
	Ownership(ctx context.Context) (Ownership, error)
}

Store is the permanent store: Postgres in production (PG), Mem in the unit tests. Every write is one transaction that changes the row AND appends its history row (kind, name, op, before, after, actor, at); there is no write without a record (docs/SPEC-CONFIG.md, "History").

type Type

type Type string

Type is a field's type. It decides the SQL column, the flag's parsing and the validator.

const (
	// TypeText is free text, stored as text, escaped on the typed line.
	TypeText Type = "text"
	// TypeInt is a non-negative integer, stored as integer.
	TypeInt Type = "int"
	// TypeEnum is one word from Field.Enum, stored as text.
	TypeEnum Type = "enum"
	// TypeList is a comma list of words from Field.Enum, deduplicated and
	// sorted, stored as text ("" is the empty list).
	TypeList Type = "list"
	// TypeNames is a comma list of names (Field.Pattern each), deduplicated
	// and sorted, stored as text.
	TypeNames Type = "names"
	// TypeRef is the name of a row of another kind (Field.Ref), stored as
	// text with a foreign key.
	TypeRef Type = "ref"
	// TypeBool is true or false, stored as boolean.
	TypeBool Type = "bool"
	// TypeKeys is a comma list of environment variable names (letters,
	// digits and underscores, not starting with a digit), deduplicated and
	// sorted, stored as text: the names of secrets, never their values.
	TypeKeys Type = "keys"
	// TypeArgv is a command as a JSON array of strings, the program first,
	// stored as text in its compact JSON spelling.
	TypeArgv Type = "argv"
	// TypeSeq is a comma list of row names of another kind (Field.Ref) in the
	// order given, a name kept as often as it is given, stored as text ("" is
	// the empty list): a tier's route array.
	TypeSeq Type = "seq"
	// TypeDecimal is a non-negative decimal number (digits, one point, no sign
	// or exponent), stored as text in its one spelling (cardcost.Canonical), ""
	// when not set: a price, never a float.
	TypeDecimal Type = "decimal"
)

type UnknownHostError

type UnknownHostError struct {
	Name  string
	Known []string
}

UnknownHostError is HostJSON's refusal: no machine row has the name. Known is every machine name the inventory holds, sorted.

func (*UnknownHostError) Error

func (e *UnknownHostError) Error() string

type VerbProbe

type VerbProbe func(ctx context.Context, program, verb string) (gone bool, err error)

VerbProbe says whether program no longer has verb: gone is true only when the program answered that it has no such verb; err is a probe that could not answer (the program is not installed here), which judges nothing.

type View

type View map[string]string

View is one row as Redis holds it: the kind's fields in the same canonical text as a Row, read back from the keys the runtime tools read. The diff is Row.Fields against View, field by field, so Redis is always a copy of Postgres and never the other way round (docs/SPEC-CONFIG.md, "Apply").

Jump to

Keyboard shortcuts

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