Read the release's new manifest words and seat names, with no change of meaning, so the running parts accept design 47's manifests (issue 774) #132
@@ -0,0 +1,49 @@
|
||||
package catalogue
|
||||
|
||||
// **The artifact sweep, out of the controller** (novox/hq ADR 0571 clause 2, issue 1955): what the artifact store keeps
|
||||
// and lets go of, the seat `mesh-artifact-sweep` (named for its scope, ADR 0121 §1; the operator, 2026-10-29), held
|
||||
// once for the mesh by artifact-store-tools, serves what the controller served of its store (artifacts, held, mirrors, images, collect) and says `artifacts-gone`. Its keep set is the store's own listing joined
|
||||
// with every want's fingerprint and each module's five newest builds from the builder's log (ADR 0526 §1); it lets go
|
||||
// of nothing until the controller's lease names a holder that is no controller.
|
||||
|
||||
// ArtifactSweepSeat is the seat artifact-store-tools holds. Not the seat mesh-artifact-store, the store itself held by
|
||||
// distribution, and not the provision artifact-store it delivers.
|
||||
const ArtifactSweepSeat = "mesh-artifact-sweep"
|
||||
|
||||
// artifactSweepVerbs are the seat's verbs, with the controller's arguments so a caller changes only the seat it asks.
|
||||
func artifactSweepVerbs() []Verb {
|
||||
return []Verb{
|
||||
{Name: "artifacts", Description: "Every artifact the mesh made that lives in its artifact store, each with its " +
|
||||
"repository, digest, kind (image or archive) and state: kept, and why (a want names it, or one of the five " +
|
||||
"newest builds of its module in the builder's log), eligible (made, kept for no reason, not yet let go), or " +
|
||||
"unrecorded (in the store and named by nothing the mesh made: never removed). Reads only.",
|
||||
Input: schema(map[string]string{
|
||||
"repository": "only this repository's references, as <module>/<artifact> (the counts stay whole)",
|
||||
"collected": "\"true\": list the collected references too (counted always)",
|
||||
}, nil, "collected")},
|
||||
{Name: "held", Description: "Whether the artifact store holds each reference given, asked with one HEAD each: " +
|
||||
"held, gone (the store answered 404) or unknown with why. The builder asks it before it reuses a done key. " +
|
||||
"Reads only.",
|
||||
Input: listed(schema(map[string]string{
|
||||
"references": "the artifacts' references, artifact-store://<module>/<artifact>/blobs/sha256:<hex>: a list, " +
|
||||
"or one text separated by commas",
|
||||
}, []string{"references"}), "references")},
|
||||
{Name: "collect", Description: "The artifact store's sweep, now: what is eligible is let go of. Without confirm a " +
|
||||
"dry run: what would be let go, nothing held or deleted. With confirm and why: every kept archive held first, " +
|
||||
"then each eligible artifact let go of, oldest first, an index with the platform manifests no kept index " +
|
||||
"names, each deletion recorded in the hand-act log. Bounded by most (500 by default, at most 5000) and 45 " +
|
||||
"seconds; what is left is said. Never touches a digest the mesh did not make.",
|
||||
Input: schema(map[string]string{
|
||||
"confirm": "\"true\": let go of what is eligible (needs why); without it nothing is done",
|
||||
"why": "with confirm: why, required and recorded",
|
||||
"most": "let go of at most this many (500 by default, at most 5000)",
|
||||
}, nil, "confirm")},
|
||||
{Name: "mirrors", Description: "The bases the mesh copied into its artifact store, each kept (a kept build stood on " +
|
||||
"it), eligible (only builds no longer kept stood on it) or unrecorded (no build the builder's log holds stood on " +
|
||||
"it: never removed), by what the builder's log says each build stood on. Records nothing by hand.",
|
||||
Input: schema(map[string]string{}, nil)},
|
||||
{Name: "images", Description: "Every container image one machine's want and have name, each with the resources " +
|
||||
"naming it: what the machine keeps when it prunes its images. Reads only.",
|
||||
Input: schema(map[string]string{"node": "the machine's name"}, []string{"node"})},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package catalogue
|
||||
|
||||
// **The control records, out of the controller** (novox/hq ADR 0565 clause 1, issue 1953): two seats at mesh scope,
|
||||
// each held once, and the node seat's verb `packages`.
|
||||
//
|
||||
// - mesh-wants, held by the assigner (mesh-delivery-assigner): a person's upgrade policy and the bus step (clauses 7,
|
||||
// 8, 9);
|
||||
// - mesh-hand-acts, held by the module mesh-hand-acts: the hand-act log and the warrants recorded (clause 10).
|
||||
//
|
||||
// Their events are their holders' to say on the seat; their layouts are go/handacts' and go/delivery's.
|
||||
|
||||
// The two seats.
|
||||
const (
|
||||
WantsSeat = "mesh-wants"
|
||||
HandActsSeat = "mesh-hand-acts"
|
||||
)
|
||||
|
||||
// controlRecordsDecision is the record that made the two seats.
|
||||
const controlRecordsDecision = "novox/hq ADR 0565"
|
||||
|
||||
// wantsVerbs are the assigner's verbs on mesh-wants (ADR 0565 clauses 7, 8, 9, 15).
|
||||
func wantsVerbs() []Verb {
|
||||
return []Verb{
|
||||
{Name: "upgrade", Description: "Whether each module's new builds wait for a person: its policy (roll, together or record) " +
|
||||
"and where it comes from (a person, the module, the bus, its irreplaceable data, the default); the bus is never " +
|
||||
"rolled. With module, that one; with module, policy and why, a person's choice for it, recorded as a hand act; " +
|
||||
"with policy default, the choice taken back. With compare, every module whose policy differs from the one the " +
|
||||
"controller writes beside its compositions.",
|
||||
Input: schema(map[string]string{
|
||||
"module": "one module",
|
||||
"policy": "with module: roll, together, record, or default to take a person's choice back",
|
||||
"why": "with policy: why, required and kept",
|
||||
"compare": "\"true\": every module whose policy at the assigner differs from the controller's",
|
||||
}, nil, "compare")},
|
||||
{Name: "bus", Description: "What a bus upgrade would do: the fingerprint of the bus module each node runs against the " +
|
||||
"indexer's newest, and the last bus steps from the hand-act log and the assigner's puts of the bus. Read-only: " +
|
||||
"the step itself is the put on the operator's warrant.",
|
||||
Input: schema(map[string]string{}, nil)},
|
||||
}
|
||||
}
|
||||
|
||||
// handActsVerbs are mesh-hand-acts' verbs (ADR 0565 clause 10), as the controller's took them.
|
||||
func handActsVerbs() []Verb {
|
||||
return []Verb{
|
||||
{Name: "hand-act", Description: "Record one act done by hand: what, why, its cause in a word or a condition's kind " +
|
||||
"(the word a second act for the same reason uses) and the condition it addresses. Who did it is the caller the " +
|
||||
"runtime proves, never said by the caller. Said as act-recorded.",
|
||||
Input: schema(map[string]string{
|
||||
"what": "what was done, in a line",
|
||||
"why": "why it had to be done by hand",
|
||||
"cause": "the cause in a word, or a condition's kind",
|
||||
"condition": "the key of the condition it addressed, if any (optional)",
|
||||
"verb": "the act's verb in the log: a repair when absent, `hand-act drill` for a drill, or a recorder's own",
|
||||
}, []string{"what", "why"})},
|
||||
{Name: "hand-acts", Description: "What was done by hand lately, over this log and the controller's: who, why and the " +
|
||||
"cause of each, and the causes recorded twice within a fortnight (a healer is wanted).",
|
||||
Input: schema(map[string]string{"days": "how many days back (default 14)", "cause": "only this cause",
|
||||
"who": "only acts whose who contains this", "warrants": "\"true\": every warrant recorded"}, nil, "warrants")},
|
||||
{Name: "warranted", Description: "Record what a module did on the operator's warrant: who chose, through which channel, " +
|
||||
"with which proofs and which answer read from the router's own record of that module's ask, never from the " +
|
||||
"caller; once per ask. Refused for an ask still open, ended without a choice, or not that module's.",
|
||||
Input: schema(map[string]string{"asker": "the module that asked", "ask": "its ask's id"}, []string{"asker", "ask"})},
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -45,6 +46,31 @@ func TestADeclaredStoreParsesAndPassesTheCheck(t *testing.T) {
|
||||
if it, _ := m.DataItem("dumps"); it.BackedUp() {
|
||||
t.Fatal("a rebuildable item that says none is backed up")
|
||||
}
|
||||
// Issue 774's new words first: an offer's credential form and an artifact's bus are accepted and change nothing: the
|
||||
// manifest that says them composes byte-equal to the one that does not, and no form still means a pair.
|
||||
plain := strings.Replace(declaredStore, `"resources":[`, `"build":{"artifacts":[{"name":"tools","kind":"bundle","language":"go","system":"arch","from":"cmd/x","binary":"x","loads":["x"]}]},"resources":[`, 1)
|
||||
for _, form := range []string{"pair", "none"} {
|
||||
worded := strings.Replace(plain, `"scope":"mesh"}]`, `"scope":"mesh","credential":{"form":"`+form+`"}}]`, 1)
|
||||
worded = strings.Replace(worded, `"loads":["x"]}`, `"loads":["x"],"bus":{"state":["acts"],"removes":["engine-acts"]}}`, 1)
|
||||
a, b := mustParse(t, plain), mustParse(t, worded)
|
||||
if b.Provides[0].Credential != nil || b.CredentialFormOf("postgres-database") != a.CredentialFormOf("postgres-database") ||
|
||||
a.CredentialFormOf("postgres-database") != CredentialFormPair || len(b.Build.Artifacts[0].Bus) == 0 {
|
||||
t.Fatalf("form %s: offer %+v, form %q against %q", form, b.Provides[0], b.CredentialFormOf("postgres-database"),
|
||||
a.CredentialFormOf("postgres-database"))
|
||||
}
|
||||
b.Build.Artifacts[0].Bus = nil
|
||||
ra, erra := (Resolution{Node: "anchor", Modules: []Manifest{a}}).Compose(Rendering{})
|
||||
rb, errb := (Resolution{Node: "anchor", Modules: []Manifest{b}}).Compose(Rendering{})
|
||||
ja, _ := json.Marshal(ra.Resources)
|
||||
jb, _ := json.Marshal(rb.Resources)
|
||||
if erra != nil || errb != nil || string(ja) != string(jb) || len(ra.Resources) == 0 {
|
||||
t.Fatalf("form %s composes %s against %s (%v, %v)", form, jb, ja, errb, erra)
|
||||
}
|
||||
}
|
||||
if _, err := ParseManifest([]byte(strings.Replace(declaredStore, `"scope":"mesh"}]`, `"scope":"mesh","credential":{"form":"shared"}}]`, 1))); err == nil ||
|
||||
!strings.Contains(err.Error(), `the form "shared", not "pair" or "none"`) {
|
||||
t.Fatalf("a form the release does not know: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Every rule of the section itself, refused at parse in the words of the module.
|
||||
|
||||
@@ -43,6 +43,12 @@ func EngineVerbs() []Verb {
|
||||
"each hand-over, access act and setuid search, open, done or refused, with who asked, the ask and the " +
|
||||
"operator's warrant. Read-only.",
|
||||
Input: schema(map[string]string{"limit": "at most this many, newest", "id": "one act, by its id"}, nil)},
|
||||
// The packages this node's compositions declare (novox/hq ADR 0565 §6, §15): optional until every engine serves it.
|
||||
{Name: "packages", Optional: true, Description: "The packages this machine's compositions declare, present or absent, " +
|
||||
"each with the modules that declare it, as the engine keeps them in its key of `packages`; until the controller " +
|
||||
"hands its lease away, beside the controller's mirror for this machine and their difference. With write, the " +
|
||||
"key written again from what the engine composed.",
|
||||
Input: schema(map[string]string{"write": "\"true\": write this machine's key again from what the engine composed"}, nil, "write")},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package catalogue
|
||||
|
||||
// **The mesh's health, out of the controller** (novox/hq ADR 0564 clause 1, issue 1952): four seats at mesh scope, each
|
||||
// held once, each holder a process on its own bus credential that writes only its own state.
|
||||
//
|
||||
// - mesh-conditions, held by the module mesh-conditions (the condition keeper): the one writer of the open conditions,
|
||||
// raised from the observers' findings and cleared when they go (clauses 2, 3, 9, 13);
|
||||
// - mesh-watchdogs, held by the watchman (mesh-delivery-watchman): the signals as keys that expire, its stuck keys and
|
||||
// presence as findings, and the dead letters from the lease write (clauses 4, 5);
|
||||
// - mesh-self-check, held by the module mesh-self-check: the probes, every five minutes (clause 6);
|
||||
// - mesh-healers, held by the module mesh-healers: the healers, acting on the keeper's events (clauses 7, 8).
|
||||
//
|
||||
// Their events keep the controller's names and bodies (go/health), so a reader changes only the seat it hears.
|
||||
|
||||
// The four health seats.
|
||||
const (
|
||||
ConditionsSeat = "mesh-conditions"
|
||||
WatchdogsSeat = "mesh-watchdogs"
|
||||
SelfCheckSeat = "mesh-self-check"
|
||||
HealersSeat = "mesh-healers"
|
||||
)
|
||||
|
||||
// healthDecision is the record that made the four seats.
|
||||
const healthDecision = "novox/hq ADR 0564"
|
||||
|
||||
// conditionsVerbs are the condition keeper's verbs (ADR 0564 clauses 3, 9, 12).
|
||||
func conditionsVerbs() []Verb {
|
||||
return []Verb{
|
||||
{Name: "conditions", Description: "What is wrong with the mesh now: every open condition, urgent first, then oldest, " +
|
||||
"raised from the observers' findings (the watchman, the self-check, the healers, the assigner) and cleared when " +
|
||||
"the finding goes, never by hand. Given a key, that one whole with its evidence; with history, every transition " +
|
||||
"lately, the controller's before the take-over included.",
|
||||
Input: schema(map[string]string{
|
||||
"key": "a condition's key: that one whole; with history, only its transitions",
|
||||
"scope": "only this scope: machine, plan, call, build, merge, provider, seat, bus, core, probe, mesh, delivery or module",
|
||||
"severity": "only urgent, or only warning",
|
||||
"machine": "only those about this machine",
|
||||
"history": "\"true\": every raising, change, silence and clearing lately, oldest first",
|
||||
"days": "with history: how many days back (default 7, at most 90)",
|
||||
}, nil, "history")},
|
||||
{Name: "silence", Description: "Stop one condition's messages for a while, a hand act with why: it stays open and in " +
|
||||
"status, and the silence is kept as its transition in history.",
|
||||
Input: schema(map[string]string{
|
||||
"key": "the condition's key",
|
||||
"for": "how long: 30m, 4h, 2d; at most 7d",
|
||||
"why": "why, required and recorded",
|
||||
"cause": "the cause in a word (the condition's kind when absent)",
|
||||
}, []string{"key", "for", "why"})},
|
||||
{Name: "compare", Description: "The keeper's open conditions against the controller's: every key open in one and not " +
|
||||
"the other, or open in both with another severity, each with its reason; the kinds that end with the controller " +
|
||||
"and the keys held while their module is delivered listed apart and not counted. The take-over waits for it empty.",
|
||||
Input: schema(map[string]string{}, nil)},
|
||||
{Name: "take-over", Description: "Ask the operator's Approve to switch who the router, the grants part and " +
|
||||
"mesh-watcher hear from the controller to the keeper; refused while compare shows a difference. On the warrant " +
|
||||
"the keeper writes its take-over key.",
|
||||
Input: schema(map[string]string{"why": "why now, shown to the operator"}, []string{"why"})},
|
||||
{Name: "hand-back", Description: "Switch the readers back to the controller's conditions, until the controller's " +
|
||||
"lease is handed away; refused after it.",
|
||||
Input: schema(map[string]string{"why": "why, kept with the switch"}, []string{"why"})},
|
||||
{Name: "resync", Description: "Read every observer's findings again and raise and clear what differs from the open " +
|
||||
"conditions: the recovery of a keeper that missed a change.",
|
||||
Input: schema(map[string]string{}, nil)},
|
||||
}
|
||||
}
|
||||
|
||||
// watchdogsVerbs are the watchman's verbs (ADR 0564 clauses 4, 5, 12).
|
||||
func watchdogsVerbs() []Verb {
|
||||
return []Verb{
|
||||
{Name: "signals", Description: "Every watchdog of the signals table the watchman keeps, each a key that expires unless " +
|
||||
"its signal renews it: its row, what renews it, its bound, the age of its newest signal, and the finding its " +
|
||||
"expiry wrote.",
|
||||
Input: schema(map[string]string{"row": "one row, e.g. S1"}, nil)},
|
||||
{Name: "stuck", Description: "Every want, build or step the watchman says stopped moving past its bound, with since " +
|
||||
"when and the words of its finding.",
|
||||
Input: schema(map[string]string{"machine": "only those on this machine"}, nil)},
|
||||
{Name: "presence", Description: "Every machine and tool runner the watchman sees connected, with its last word, and " +
|
||||
"those it says silent; a machine that said it sleeps is shown as sleeping, not silent.",
|
||||
Input: schema(map[string]string{"machine": "one machine"}, nil)},
|
||||
{Name: "dead-letters", Description: "Every message a consumer on the bus gave up on, kept in DEAD_LETTERS: whose " +
|
||||
"consumer, the subject, how often it was handed over and when it was given up, newest first. With id: that one " +
|
||||
"whole. With deliver or drop, a hand act with why, taken only once the controller's lease is handed away; " +
|
||||
"before, refused naming the controller's verb.",
|
||||
Input: schema(map[string]string{
|
||||
"id": "a dead letter's id, as the list gives it: that one whole, with its message",
|
||||
"consumer": "a consumer's name, or <stream>.<consumer>: only what that one gave up on",
|
||||
"limit": "how many to list, newest first (default 50); only when listing",
|
||||
"deliver": "a dead letter's id: deliver it again to the consumer that gave it up (needs why)",
|
||||
"drop": "a dead letter's id: drop it for good (needs why)",
|
||||
"why": "with deliver or drop: why, required and recorded",
|
||||
"cause": "with deliver or drop: the cause in a word (dead-letter when absent)",
|
||||
}, nil)},
|
||||
{Name: "resync", Description: "Rebuild presence from the bus's connections, and the pulses and findings from the keys " +
|
||||
"the watchman watches, and take every dead-letter notice waiting: the recovery of a watchman that missed a change.",
|
||||
Input: schema(map[string]string{}, nil)},
|
||||
}
|
||||
}
|
||||
|
||||
// selfCheckVerbs are the self-check's verbs (ADR 0564 clause 6).
|
||||
func selfCheckVerbs() []Verb {
|
||||
return []Verb{
|
||||
{Name: "doctor", Description: "The self-check: the last run's verdict at once, each probe passed, failed, deferred " +
|
||||
"(its facts not on the bus yet, named) or could not run, and how long ago. With run, a run now; with probe, one " +
|
||||
"probe's verdict whole. Runs every five minutes on its own; each failure is a finding.",
|
||||
Input: schema(map[string]string{
|
||||
"run": "\"true\": run every probe now and answer the verdict",
|
||||
"probe": "one probe's id (D6): its verdict alone, with every finding",
|
||||
}, nil, "run")},
|
||||
{Name: "probes", Description: "The registry: what each probe asserts, where it reads its facts, and the finding it writes.",
|
||||
Input: schema(map[string]string{}, nil)},
|
||||
}
|
||||
}
|
||||
|
||||
// healersVerbs are the healers' verbs (ADR 0564 clauses 7, 8).
|
||||
func healersVerbs() []Verb {
|
||||
return []Verb{
|
||||
{Name: "healers", Description: "The healers: each registered response to one kind of condition, its repair, its " +
|
||||
"budget and what happens when it is spent; every act taken lately with its outcome, the acts it would have " +
|
||||
"taken while the controller's lease names a controller, and the mesh-wide brake.",
|
||||
Input: schema(map[string]string{"days": "how many days of acts back (default 7)"}, nil)},
|
||||
{Name: "heal", Description: "Take one healer's act for one open condition now, within its budget and the brake.",
|
||||
Input: schema(map[string]string{
|
||||
"key": "the condition's key",
|
||||
"why": "why now, kept with the act",
|
||||
}, []string{"key", "why"})},
|
||||
{Name: "drill", Description: "Record a drill: something broken on purpose to see the mesh raise and clear it, with " +
|
||||
"what it tests. A drill is never a repair: no healer is wanted for it however often it is run.",
|
||||
Input: schema(map[string]string{
|
||||
"what": "what was done on purpose, in a line",
|
||||
"why": "what the drill tests",
|
||||
"condition": "the key of the condition the drill is meant to raise, if any (optional)",
|
||||
}, []string{"what", "why"})},
|
||||
}
|
||||
}
|
||||
+20
-5
@@ -27,8 +27,15 @@ type LogDeclaration struct {
|
||||
// MaxMiB is the log's cap in MiB; zero is LogDefaultMiB. When full, the log refuses new entries
|
||||
// and never drops old ones.
|
||||
MaxMiB int `json:"max-mib,omitempty"`
|
||||
// MaxAgeDays is how long an entry is kept, in days; zero is as long as the log (novox/hq ADR 0297). The one way an
|
||||
// entry leaves a log: the server ages it out, never a delete or a purge (novox/hq ADR 0565 §10: the hand-act log
|
||||
// keeps every act 400 days; issue 1953 S-age).
|
||||
MaxAgeDays int `json:"max-age-days,omitempty"`
|
||||
}
|
||||
|
||||
// LogMostAgeDays is the longest a log may keep an entry by age: past it, a log keeps its entries as long as it is.
|
||||
const LogMostAgeDays = 3650
|
||||
|
||||
// Cap is the log's cap in MiB, the default where none is said.
|
||||
func (l LogDeclaration) Cap() int {
|
||||
if l.MaxMiB == 0 {
|
||||
@@ -37,20 +44,21 @@ func (l LogDeclaration) Cap() int {
|
||||
return l.MaxMiB
|
||||
}
|
||||
|
||||
// UnmarshalJSON reads a log as its bare name, or as {name, max-mib}.
|
||||
// UnmarshalJSON reads a log as its bare name, or as {name, max-mib, max-age-days}.
|
||||
func (l *LogDeclaration) UnmarshalJSON(raw []byte) error {
|
||||
trimmed := bytes.TrimSpace(raw)
|
||||
if len(trimmed) > 0 && trimmed[0] == '"' {
|
||||
return json.Unmarshal(trimmed, &l.Name)
|
||||
}
|
||||
var full struct {
|
||||
Name string `json:"name"`
|
||||
MaxMiB *int `json:"max-mib"`
|
||||
Name string `json:"name"`
|
||||
MaxMiB *int `json:"max-mib"`
|
||||
MaxAgeDays *int `json:"max-age-days"`
|
||||
}
|
||||
dec := json.NewDecoder(bytes.NewReader(trimmed))
|
||||
dec.DisallowUnknownFields()
|
||||
if err := dec.Decode(&full); err != nil {
|
||||
return fmt.Errorf("a log is either a name or {name, max-mib}: %w", typedUnknown(err))
|
||||
return fmt.Errorf("a log is either a name or {name, max-mib, max-age-days}: %w", typedUnknown(err))
|
||||
}
|
||||
l.Name = full.Name
|
||||
l.MaxMiB = 0
|
||||
@@ -61,12 +69,19 @@ func (l *LogDeclaration) UnmarshalJSON(raw []byte) error {
|
||||
}
|
||||
l.MaxMiB = *full.MaxMiB
|
||||
}
|
||||
l.MaxAgeDays = 0
|
||||
if full.MaxAgeDays != nil {
|
||||
if *full.MaxAgeDays < 1 || *full.MaxAgeDays > LogMostAgeDays {
|
||||
return fmt.Errorf("log %q: max-age-days is between 1 and %d, not %d", full.Name, LogMostAgeDays, *full.MaxAgeDays)
|
||||
}
|
||||
l.MaxAgeDays = *full.MaxAgeDays
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON writes back the short form when there is nothing else to say.
|
||||
func (l LogDeclaration) MarshalJSON() ([]byte, error) {
|
||||
if l.MaxMiB == 0 {
|
||||
if l.MaxMiB == 0 && l.MaxAgeDays == 0 {
|
||||
return json.Marshal(l.Name)
|
||||
}
|
||||
type plain LogDeclaration
|
||||
|
||||
@@ -24,6 +24,11 @@ func TestAManifestMaySayWhatLogsItKeeps(t *testing.T) {
|
||||
if string(out) != `["changes",{"name":"moves","max-mib":2048}]` {
|
||||
t.Fatalf("written back as %s", out)
|
||||
}
|
||||
// Issue 774's new words first: max-age-days is accepted and written back; nothing reads it yet.
|
||||
aged, err := ParseManifest([]byte(`{"module":"a","version":"1","logs":[{"name":"acts","max-age-days":400}]}`))
|
||||
if out, _ := json.Marshal(aged.Logs); err != nil || aged.Logs[0].MaxAgeDays != 400 || string(out) != `[{"name":"acts","max-age-days":400}]` {
|
||||
t.Fatalf("max-age-days read as %+v, written %s: %v", aged.Logs, out, err)
|
||||
}
|
||||
for _, edge := range []string{`{"name":"a","max-mib":1}`, `{"name":"a","max-mib":8192}`} {
|
||||
if _, err := ParseManifest([]byte(`{"module":"a","version":"1","logs":[` + edge + `]}`)); err != nil {
|
||||
t.Errorf("%s is inside the caps and was refused: %v", edge, err)
|
||||
@@ -42,7 +47,8 @@ func TestALogIsNamedLocallyAndCapped(t *testing.T) {
|
||||
{`{"module":"a","version":"1","logs":[{"name":"c","max-mib":8193}]}`, `between 1 and 8192 MiB`},
|
||||
{`{"module":"a","version":"1","logs":[{"name":"c","max-mib":-1}]}`, `between 1 and 8192 MiB`},
|
||||
{`{"module":"a","version":"1","logs":[{"name":"c","max-mib":0}]}`, `max-mib is between 1 and 8192, not 0`},
|
||||
{`{"module":"a","version":"1","logs":[{"name":"c","stream":"LOG_x"}]}`, `{name, max-mib}`},
|
||||
{`{"module":"a","version":"1","logs":[{"name":"c","stream":"LOG_x"}]}`, `{name, max-mib, max-age-days}`},
|
||||
{`{"module":"a","version":"1","logs":[{"name":"c","max-age-days":0}]}`, `max-age-days is between 1 and 3650`},
|
||||
{`{"module":"a.b","version":"1","logs":["c"]}`, `no dot`},
|
||||
} {
|
||||
_, err := ParseManifest([]byte(c.manifest))
|
||||
|
||||
@@ -287,8 +287,18 @@ type OfferCredential struct {
|
||||
Own string `json:"own,omitempty"`
|
||||
IssuedBy string `json:"issued-by,omitempty"`
|
||||
Tool string `json:"tool,omitempty"`
|
||||
// Form is the release's word for an offer that names no own secret and no issuer (novox/hq ADR 0570; issue 774's
|
||||
// new words first): CredentialFormPair or CredentialFormNone. Accepted and read by nothing yet: an offer that says
|
||||
// only its form is parsed as one that says no credential, which is a pair, as before.
|
||||
Form string `json:"form,omitempty"`
|
||||
}
|
||||
|
||||
// The forms an offer's credential may say by itself (OfferCredential.Form).
|
||||
const (
|
||||
CredentialFormWordPair = "pair"
|
||||
CredentialFormWordNone = "none"
|
||||
)
|
||||
|
||||
// IssuedBySoftware says a provision's credential is issued by its provider's software (novox/hq ADR 0305).
|
||||
const IssuedBySoftware = "software"
|
||||
|
||||
@@ -1149,6 +1159,10 @@ type Artifact struct {
|
||||
// success, and was invisible to the launcher (novox/hq 04-ISSUES/142).
|
||||
Binary string `json:"binary,omitempty"`
|
||||
|
||||
// Bus is the release's word for a tools bundle's own bus account (novox/hq ADR 0564 Limits 4; issue 774's new words
|
||||
// first): accepted as it is written and read by nothing yet.
|
||||
Bus json.RawMessage `json:"bus,omitempty"`
|
||||
|
||||
// URL is where an archive's upstream file is fetched from, for an archive that is a release
|
||||
// somebody else published rather than a directory of this repository (novox/hq ADR 0316 §2, §6).
|
||||
// With it come SHA256 and Version, and From stays empty.
|
||||
@@ -1926,7 +1940,7 @@ func parseManifest(raw []byte, predefined PredefinedList) (Manifest, error) {
|
||||
"%s says its instances are %q; the one word is %q, for a module that is the same on every machine",
|
||||
m.Module, m.Instances, InstancesInterchangeable))
|
||||
}
|
||||
for _, offer := range m.Provides {
|
||||
for i, offer := range m.Provides {
|
||||
p := offer.Name
|
||||
if !name.MatchString(p) {
|
||||
problems = append(problems, fmt.Sprintf("%q is not a usable name to provide", p))
|
||||
@@ -1979,6 +1993,15 @@ func parseManifest(raw []byte, predefined PredefinedList) (Manifest, error) {
|
||||
} else if offer.Credential != nil {
|
||||
own, declared := m.OwnSecrets[offer.Credential.Own]
|
||||
switch {
|
||||
case offer.Credential.Own == "" && offer.Credential.IssuedBy == "" && offer.Credential.Tool == "" &&
|
||||
offer.Credential.Form != "":
|
||||
// The form alone (issue 774's new words first): pair or none, accepted and meaning what no credential
|
||||
// meant before, a pair (the offer is read as saying none).
|
||||
if f := offer.Credential.Form; f != CredentialFormWordPair && f != CredentialFormWordNone {
|
||||
problems = append(problems, fmt.Sprintf("%s provides %q with the form %q, not %q or %q", m.Module, p, f,
|
||||
CredentialFormWordPair, CredentialFormWordNone))
|
||||
}
|
||||
m.Provides[i].Credential = nil
|
||||
case offer.Credential.Own == "":
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s provides %q with a credential that names no own secret", m.Module, p))
|
||||
|
||||
@@ -39,7 +39,10 @@ func placementVerbs() []Verb {
|
||||
"history, one node's or one module's changes in order: a revision the state keeps whole, an older one as the " +
|
||||
"proposal's changes from its placement-changed event.",
|
||||
Input: schema(map[string]string{"node": "one node", "module": "one module: the nodes it is placed on",
|
||||
"history": "\"true\": the changes in order, from the stream and the state's revisions"}, nil, "history")},
|
||||
"history": "\"true\": the changes in order, from the stream and the state's revisions",
|
||||
// ADR 0571 clause 4: who holds which seat on which node, from the state seat-holders.
|
||||
"seats": "\"true\": who holds each seat, and on which node (the state seat-holders); with node, that node's"},
|
||||
nil, "history", "seats")},
|
||||
{Name: PlacementVerbProposals, Optional: true, Description: "Every open proposal of placement: its changes, " +
|
||||
"the revision of each key it was laid over, its candidates, every node's answer, the differences computed and " +
|
||||
"answered, its verdict and its ask. Narrowed to one by id.",
|
||||
|
||||
+27
-1
@@ -245,13 +245,25 @@ var defaultSeats = append([]Seat{
|
||||
// The owner of every config value (novox/hq ADR 0348 §9, design 57 K3): its holder alone writes the buckets
|
||||
// mesh-config_values, _resolved, _predefined and _proposals, and serves the one verb `config` (§1, §10).
|
||||
// Optional until the mesh-config that runs serves it (design 33 §7).
|
||||
{Name: "mesh-config", Scope: ScopeMesh, Decision: "novox/hq ADR 0348",
|
||||
{Name: "mesh-config", Scope: ScopeMesh, Decision: "novox/hq ADR 0348, ADR 0565",
|
||||
Serves: []Verb{
|
||||
{Name: "config", Optional: true, Description: "Set, clear, list or read the history of config values: " +
|
||||
"mesh config or node config for a predefined key, or a module's own value on one node, each set " +
|
||||
"with why, validated, and naming each module on each node whose value moves. A trusted value only " +
|
||||
"from the controller's terminal.",
|
||||
Input: configInput()},
|
||||
// The switch of config to its own sources (novox/hq ADR 0565 §4): optional until every mesh-config serves it.
|
||||
{Name: "compare", Optional: true, Description: "Every difference between config's own sources and the " +
|
||||
"controller's records, each with its reason: a module's declared config against the controller's mirror, " +
|
||||
"the indexer's predefined list against the mirror's, and each open pull request's config candidate against " +
|
||||
"what the controller told. The take-over waits for none.",
|
||||
Input: schema(map[string]string{}, nil)},
|
||||
{Name: "take-over", Optional: true, Description: "Ask the operator's Approve to switch config to its own " +
|
||||
"sources (the indexer); refused while compare shows a difference.",
|
||||
Input: schema(map[string]string{"why": "why, in a sentence"}, []string{"why"})},
|
||||
{Name: "hand-back", Optional: true, Description: "Switch config back to the controller's mirrors, until the " +
|
||||
"controller hands its lease away; refused after it.",
|
||||
Input: schema(map[string]string{"why": "why, in a sentence"}, []string{"why"})},
|
||||
}},
|
||||
// The provisioner's server (novox/hq ADR 0349 clause 3, design 57 P4): its holder keeps the registry of which
|
||||
// domain answers which kind of placeholder, and the buckets mesh-provisioner_needs, _applied and _collect,
|
||||
@@ -280,6 +292,20 @@ var defaultSeats = append([]Seat{
|
||||
{Name: PlacementSeat, Scope: ScopeMesh, Serves: placementVerbs(), Decision: "novox/hq ADR 0567"},
|
||||
// The delivery's checker (novox/hq ADR 0547 §10, issue 774): the try is its verb, not the controller's.
|
||||
{Name: CheckerSeat, Scope: ScopeMesh, Serves: checkerVerbs(), Decision: "novox/hq ADR 0547"},
|
||||
// The mesh's health, out of the controller (novox/hq ADR 0564 clause 1, health_seats.go): the condition keeper, the
|
||||
// watchman's watchdogs, the self-check and the healers.
|
||||
{Name: ConditionsSeat, Scope: ScopeMesh, Decision: healthDecision,
|
||||
Emits: []string{"condition-raised", "condition-changed", "condition-cleared"}, Serves: conditionsVerbs()},
|
||||
{Name: WatchdogsSeat, Scope: ScopeMesh, Decision: healthDecision, Serves: watchdogsVerbs()},
|
||||
{Name: SelfCheckSeat, Scope: ScopeMesh, Decision: healthDecision, Emits: []string{"checked"}, Serves: selfCheckVerbs()},
|
||||
{Name: HealersSeat, Scope: ScopeMesh, Decision: healthDecision, Emits: []string{"healer-acted"}, Serves: healersVerbs()},
|
||||
// The control records, out of the controller (novox/hq ADR 0565 clause 1, control_records_seats.go): a person's
|
||||
// policy and the bus step at the assigner, and the hand-act log.
|
||||
{Name: WantsSeat, Scope: ScopeMesh, Decision: controlRecordsDecision, Emits: []string{"policy-changed"}, Serves: wantsVerbs()},
|
||||
{Name: HandActsSeat, Scope: ScopeMesh, Decision: controlRecordsDecision, Emits: []string{"act-recorded"}, Serves: handActsVerbs()},
|
||||
// The artifact sweep (novox/hq ADR 0571 clause 2, artifact_sweep_seat.go).
|
||||
{Name: ArtifactSweepSeat, Scope: ScopeMesh, Decision: "novox/hq ADR 0571", Emits: []string{"artifacts-gone"},
|
||||
Serves: artifactSweepVerbs()},
|
||||
// Deferred renames (novox/hq ADR 0121): these deliver a provision, so renaming them is a
|
||||
// delivering-seat migration with a mesh-wide cascade if a holder stops resolving mid-flight.
|
||||
// They keep their names until that migration is done deliberately, apart from the node-* pass.
|
||||
|
||||
@@ -64,6 +64,27 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) {
|
||||
if strings.Join(checkerVerbs, ",") != "try,try-read,try-end" {
|
||||
t.Errorf("the checker seat serves %v", checkerVerbs)
|
||||
}
|
||||
// The four health seats (novox/hq ADR 0564 clause 1): at mesh scope, each held once, each naming ADR 0564, with the
|
||||
// verbs and events the record names; the events keep the controller's names (clause 3).
|
||||
for _, c := range []struct{ seat, verbs, emits string }{
|
||||
{ConditionsSeat, "conditions,silence,compare,take-over,hand-back,resync", "condition-raised,condition-changed,condition-cleared"},
|
||||
{WatchdogsSeat, "signals,stuck,presence,dead-letters,resync", ""},
|
||||
{SelfCheckSeat, "doctor,probes", "checked"},
|
||||
{HealersSeat, "healers,heal,drill", "healer-acted"},
|
||||
// And mesh-config's switch (ADR 0565 §4), beside its config.
|
||||
{"mesh-config", "config,compare,take-over,hand-back", ""},
|
||||
// And ADR 0565 clause 1's two.
|
||||
{WantsSeat, "upgrade,bus", "policy-changed"},
|
||||
{HandActsSeat, "hand-act,hand-acts,warranted", "act-recorded"},
|
||||
// ADR 0571 clause 2: the artifact store's sweep.
|
||||
{ArtifactSweepSeat, "artifacts,held,collect,mirrors,images", "artifacts-gone"},
|
||||
} {
|
||||
s, ok := SeatNamed(c.seat)
|
||||
if !ok || s.Scope != ScopeMesh || s.Replicated || s.Delivers != "" || !strings.Contains(s.Decision, "ADR 05") ||
|
||||
strings.Join(VerbNames(s.Serves), ",") != c.verbs || strings.Join(s.Emits, ",") != c.emits {
|
||||
t.Errorf("the health seat %s is %v, %+v", c.seat, ok, s)
|
||||
}
|
||||
}
|
||||
// The node seat node-engine is the node's engine's (novox/hq ADR 0563 §1): at node scope, delivering nothing, its six
|
||||
// verbs and three events, held by no assignment, and a module's claim of it refused.
|
||||
if engine, ok := SeatNamed(EngineSeat); !ok || engine.Scope != ScopeNode || engine.Delivers != "" || !engine.HeldByTheEngine {
|
||||
@@ -74,10 +95,10 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) {
|
||||
verbs = append(verbs, v.Name)
|
||||
}
|
||||
// acts reads one act by its id too (ADR 0563 §9: the seat's tools are its command line).
|
||||
if props, _ := engine.Serves[len(engine.Serves)-1].Input["properties"].(map[string]any); props["id"] == nil {
|
||||
t.Errorf("acts takes no id: %v", engine.Serves[len(engine.Serves)-1].Input)
|
||||
if props, _ := engine.Serves[5].Input["properties"].(map[string]any); props["id"] == nil {
|
||||
t.Errorf("acts takes no id: %v", engine.Serves[5].Input)
|
||||
}
|
||||
if strings.Join(verbs, ",") != "hand-over,access-act,judge-directories,setuid-search,root-free,acts" ||
|
||||
if strings.Join(verbs, ",") != "hand-over,access-act,judge-directories,setuid-search,root-free,acts,packages" ||
|
||||
strings.Join(engine.Emits, ",") != "asked,done,refused" {
|
||||
t.Errorf("node-engine serves %v and emits %v", verbs, engine.Emits)
|
||||
}
|
||||
@@ -99,9 +120,11 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) {
|
||||
// node-container-runtime (ADR 0207); nineteen with node-environment and node-login-shell (ADR 0203,
|
||||
// ADR 0204); seventeen with node-build-agent (ADR 0190). One fewer once the retired
|
||||
// mesh-build-machine row goes, when no registered manifest claims it.
|
||||
// Forty-eight with mesh-delivery-checker (novox/hq ADR 0547 §10, issue 774); forty-nine with node-engine (novox/hq ADR 0563 §1).
|
||||
if len(Seats()) != 49 {
|
||||
t.Errorf("the mesh defines %d seats rather than 49; the set is closed, so a change here is "+
|
||||
// Forty-eight with mesh-delivery-checker (novox/hq ADR 0547 §10, issue 774); forty-nine with node-engine (novox/hq ADR 0563 §1);
|
||||
// fifty-three with the four health seats (novox/hq ADR 0564 clause 1); fifty-five with mesh-wants and mesh-hand-acts
|
||||
// (novox/hq ADR 0565 clause 1); fifty-six with mesh-artifact-sweep (novox/hq ADR 0571 clause 2).
|
||||
if len(Seats()) != 56 {
|
||||
t.Errorf("the mesh defines %d seats rather than 56; the set is closed, so a change here is "+
|
||||
"a decision (novox/hq ADR 0110): %s", len(Seats()), seatNames())
|
||||
}
|
||||
}
|
||||
@@ -127,7 +150,7 @@ func TestTheUplinkIsANodeSeatThatDeliversNothing(t *testing.T) {
|
||||
}
|
||||
|
||||
func claimed(claims string) []byte {
|
||||
return []byte(`{"module":"thing","version":"1","provides":[{"name":"npm-package-registry","scope":"mesh"}],"claims":` + claims + `}`)
|
||||
return []byte(`{"module":"thing","version":"1","provides":[{"name":"npm-package-registry","scope":"mesh","credential":{"form":"none"}}],"claims":` + claims + `}`)
|
||||
}
|
||||
|
||||
// **The refusal moved, it did not go** (novox/hq ADR 0118, superseding 0110). A module may now
|
||||
|
||||
Reference in New Issue
Block a user