K21a (regenerate, in between): the generated Go and TypeScript follow the definitions so far

The conditions developer reads kinds, attempt-changed, wait-held and wait-ended from it. K21 regenerates last. The client
meshconditionsclient does not build against the findings state's new layout until the question on it is answered.

Builds: design 47 G §341, §523, §580, §541, §499

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 20:31:55 +01:00
parent 9243f8c44f
commit 74b6162807
25 changed files with 1335 additions and 162 deletions
+9
View File
@@ -278,8 +278,17 @@ type ListInput struct {
Module string `json:"module,omitempty"`
}
// ListAnswerCaps is the sum of the bus's caps against its limits, in MiB; an absent limit is unlimited (design 47 G 547, 661).
type ListAnswerCaps struct {
AccountLimitMiB int64 `json:"account-limit-mib,omitempty"`
ServerLimitMiB int64 `json:"server-limit-mib,omitempty"`
SumMiB int64 `json:"sum-mib"`
}
// ListAnswer is what list answers.
type ListAnswer struct {
// the sum of the bus's caps against its limits, in MiB; an absent limit is unlimited (design 47 G 547, 661)
Caps *ListAnswerCaps `json:"caps,omitempty"`
// every entry
Entries []Held `json:"entries"`
}
+4
View File
@@ -45,6 +45,10 @@ func TestFixtures(t *testing.T) {
decode: func(b []byte) (any, error) { var v ListAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "list answers the binding of an entry the bus holds", subject: "", given: "{\"entries\":[{\"binding\":{\"history\":\"mesh-knowledge_ledger\",\"indexes\":{\"by-kind\":\"mesh-knowledge_ledger_index-by-kind\"},\"local\":\"ledger\",\"objects\":\"mesh-knowledge_ledger_state-objects\",\"snapshot\":\"mesh.ledger.mesh-knowledge.ledger.snapshot\",\"state\":\"mesh-knowledge_ledger_state\",\"storage\":\"ledger\",\"subject\":\"mesh.ledger.mesh-knowledge.ledger.history\",\"template\":\"ledger\",\"types\":[{\"indexes\":[{\"name\":\"by-kind\",\"on\":[\"kind\"]}],\"name\":\"decision\",\"operations\":[{\"effect\":\"set-fields\",\"fields\":[\"title\",\"kind\"],\"name\":\"set\"}],\"schema\":{\"kind\":{\"type\":\"string\"},\"title\":{\"type\":\"string\"}}}]},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"settings\":{\"max-mib\":64},\"storage-type\":\"ledger\",\"template\":\"ledger\"}]}", wire: "{\"entries\":[{\"binding\":{\"history\":\"mesh-knowledge_ledger\",\"indexes\":{\"by-kind\":\"mesh-knowledge_ledger_index-by-kind\"},\"local\":\"ledger\",\"objects\":\"mesh-knowledge_ledger_state-objects\",\"snapshot\":\"mesh.ledger.mesh-knowledge.ledger.snapshot\",\"state\":\"mesh-knowledge_ledger_state\",\"storage\":\"ledger\",\"subject\":\"mesh.ledger.mesh-knowledge.ledger.history\",\"template\":\"ledger\",\"types\":[{\"indexes\":[{\"name\":\"by-kind\",\"on\":[\"kind\"]}],\"name\":\"decision\",\"operations\":[{\"effect\":\"set-fields\",\"fields\":[\"title\",\"kind\"],\"name\":\"set\"}],\"schema\":{\"kind\":{\"type\":\"string\"},\"title\":{\"type\":\"string\"}}}]},\"bus-name\":\"mesh-knowledge_ledger\",\"fill\":{\"bytes\":2048,\"cap\":67108864,\"count\":10},\"local-name\":\"ledger\",\"module\":\"mesh-knowledge\",\"settings\":{\"max-mib\":64},\"storage-type\":\"ledger\",\"template\":\"ledger\"}]}",
decode: func(b []byte) (any, error) { var v ListAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "list answers the sum of the caps and both limits (design 47 G 661)", subject: "", given: "{\"caps\":{\"account-limit-mib\":1024,\"server-limit-mib\":2048,\"sum-mib\":512},\"entries\":[]}", wire: "{\"caps\":{\"account-limit-mib\":1024,\"server-limit-mib\":2048,\"sum-mib\":512},\"entries\":[]}",
decode: func(b []byte) (any, error) { var v ListAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "list answers a sum with no limit, which is unlimited", subject: "", given: "{\"caps\":{\"sum-mib\":64},\"entries\":[]}", wire: "{\"caps\":{\"sum-mib\":64},\"entries\":[]}",
decode: func(b []byte) (any, error) { var v ListAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
}
for _, c := range cases {
v, err := c.decode([]byte(c.given))
+391
View File
@@ -0,0 +1,391 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
// Package meshrecovery is the contract of the seat "mesh-recovery", defined whole (seats/mesh-recovery/seat.json): Takes, sets aside and restores a module's kept data on the live bus, each act in the hand-act log (design 47 G 554). Its verbs answer at once and the result is the event.
package meshrecovery
import "time"
// Seat is the seat's name.
const Seat = "mesh-recovery"
const (
VerbSnapshotKept = "snapshot-kept"
VerbRestoreKept = "restore-kept"
VerbKeptPoints = "kept-points"
VerbRestoreAside = "restore-aside"
VerbCancel = "cancel"
)
const (
EventEnded = "ended"
EventRestoreStarted = "restore-started"
EventRestored = "restored"
EventSetAside = "set-aside"
EventAsideDiscarded = "aside-discarded"
EventPointRemoved = "point-removed"
EventExpiryEnded = "expiry-ended"
)
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbSnapshotKept, VerbRestoreKept, VerbKeptPoints, VerbRestoreAside, VerbCancel}
}
// Events is every event published on the seat's subject: a new copy each call.
func Events() []string {
return []string{EventEnded, EventRestoreStarted, EventRestored, EventSetAside, EventAsideDiscarded, EventPointRemoved, EventExpiryEnded}
}
// Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects.
const (
Family = "mesh-recovery.events"
FamilyMaxAgeDays = 7
)
// StreamEnded is the stream ended is kept in.
const StreamEnded = "SEAT_mesh-recovery_ended"
// SubjectEnded is where ended is published, on mesh.seat.mesh-recovery.event.ended.<module>.<kept>.
func SubjectEnded(module, kept string) string {
return "mesh.seat.mesh-recovery.event.ended." + module + "." + kept
}
// SubjectEndedAll hears every ended.
const SubjectEndedAll = "mesh.seat.mesh-recovery.event.ended.*.*"
// StreamRestoreStarted is the stream restore-started is kept in.
const StreamRestoreStarted = "SEAT_mesh-recovery_restore-started"
// SubjectRestoreStarted is where restore-started is published, on mesh.seat.mesh-recovery.event.restore-started.<module>.<kept>.
func SubjectRestoreStarted(module, kept string) string {
return "mesh.seat.mesh-recovery.event.restore-started." + module + "." + kept
}
// SubjectRestoreStartedAll hears every restore-started.
const SubjectRestoreStartedAll = "mesh.seat.mesh-recovery.event.restore-started.*.*"
// StreamRestored is the stream restored is kept in.
const StreamRestored = "SEAT_mesh-recovery_restored"
// SubjectRestored is where restored is published, on mesh.seat.mesh-recovery.event.restored.<module>.<kept>.
func SubjectRestored(module, kept string) string {
return "mesh.seat.mesh-recovery.event.restored." + module + "." + kept
}
// SubjectRestoredAll hears every restored.
const SubjectRestoredAll = "mesh.seat.mesh-recovery.event.restored.*.*"
// StreamSetAside is the stream set-aside is kept in.
const StreamSetAside = "SEAT_mesh-recovery_set-aside"
// SubjectSetAside is where set-aside is published, on mesh.seat.mesh-recovery.event.set-aside.<module>.<kept>.
func SubjectSetAside(module, kept string) string {
return "mesh.seat.mesh-recovery.event.set-aside." + module + "." + kept
}
// SubjectSetAsideAll hears every set-aside.
const SubjectSetAsideAll = "mesh.seat.mesh-recovery.event.set-aside.*.*"
// StreamAsideDiscarded is the stream aside-discarded is kept in.
const StreamAsideDiscarded = "SEAT_mesh-recovery_aside-discarded"
// SubjectAsideDiscarded is where aside-discarded is published, on mesh.seat.mesh-recovery.event.aside-discarded.<module>.<kept>.
func SubjectAsideDiscarded(module, kept string) string {
return "mesh.seat.mesh-recovery.event.aside-discarded." + module + "." + kept
}
// SubjectAsideDiscardedAll hears every aside-discarded.
const SubjectAsideDiscardedAll = "mesh.seat.mesh-recovery.event.aside-discarded.*.*"
// StreamPointRemoved is the stream point-removed is kept in.
const StreamPointRemoved = "SEAT_mesh-recovery_point-removed"
// SubjectPointRemoved is where point-removed is published, on mesh.seat.mesh-recovery.event.point-removed.<module>.<kept>.
func SubjectPointRemoved(module, kept string) string {
return "mesh.seat.mesh-recovery.event.point-removed." + module + "." + kept
}
// SubjectPointRemovedAll hears every point-removed.
const SubjectPointRemovedAll = "mesh.seat.mesh-recovery.event.point-removed.*.*"
// StreamExpiryEnded is the stream expiry-ended is kept in.
const StreamExpiryEnded = "SEAT_mesh-recovery_expiry-ended"
// SubjectExpiryEnded is where expiry-ended is published.
const SubjectExpiryEnded = "mesh.seat.mesh-recovery.event.expiry-ended"
// Aside is the live data set aside before a restore: a directory under aside/ in the recovery module's directory, nothing of it on the bus (design 47 G 516).
type Aside struct {
// the directory's name
ID string `json:"id"`
// the bus name it was live under
Name string `json:"name"`
// its bytes on disk
Size int64 `json:"size"`
}
// CatchUp is the catch-up of the newer manifest: a tool and its arguments, never text (design 47 G 451, 516, 631).
type CatchUp struct {
Arguments map[string]any `json:"arguments,omitempty"`
Tool string `json:"tool"`
}
// Consumer is one durable consumer of the kept data and the sequence it had acknowledged.
type Consumer struct {
Name string `json:"name"`
Sequence int64 `json:"sequence"`
Stream string `json:"stream"`
}
// Digest is the digest of one object of a point's bytes.
type Digest struct {
Digest string `json:"digest"`
Name string `json:"name"`
}
// Point is a restore point: the kept data whole and its consumers' positions, in the recovery module's points directory (design 47 G 516, 560).
type Point struct {
// when it was taken, RFC 3339
At time.Time `json:"at"`
CatchUp *CatchUp `json:"catch-up,omitempty"`
Consumers []Consumer `json:"consumers,omitempty"`
// the digest of each object's bytes, checked before a restore (design 47 G 567)
Digests []Digest `json:"digests,omitempty"`
// the fingerprint it was taken before
Fingerprint string `json:"fingerprint"`
// its form, the number the holder raises when it changes how it writes it (0 when undeclared)
Form int64 `json:"form"`
// the kept data, its local name in the module's manifest
Kept string `json:"kept"`
// the module
Module string `json:"module"`
// its bytes
Size int64 `json:"size"`
// the point's name, also its directory's
Snapshot string `json:"snapshot"`
}
// Running is the act that runs, since when and what it waits on (design 47 G 517, 554).
type Running struct {
Act string `json:"act"`
Since time.Time `json:"since"`
Step string `json:"step,omitempty"`
// what it waits on, when it waits
WaitsOn string `json:"waits-on,omitempty"`
}
// SnapshotKeptInput is what snapshot-kept takes.
type SnapshotKeptInput struct {
CatchUp *CatchUp `json:"catch-up,omitempty"`
// the fingerprint the restore point is taken before, or reverted from
Fingerprint string `json:"fingerprint"`
// its form, the number the holder raises when it changes how it writes it (0 when undeclared)
Form int64 `json:"form"`
// the kept data, its local name in the module's manifest
Kept string `json:"kept"`
// the module
Module string `json:"module"`
// why, which the hand-act log keeps
Why string `json:"why"`
}
// SnapshotKeptAnswer is what snapshot-kept answers.
type SnapshotKeptAnswer struct {
// accepted: the act's id, which the ending event carries
Accepted string `json:"accepted,omitempty"`
// busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event
Busy string `json:"busy,omitempty"`
// why it was not taken; set alone
Error string `json:"error,omitempty"`
}
// RestoreKeptInput is what restore-kept takes.
type RestoreKeptInput struct {
// the fingerprint the restore point is taken before, or reverted from
Fingerprint string `json:"fingerprint"`
// its form, the number the holder raises when it changes how it writes it (0 when undeclared)
Form int64 `json:"form"`
// ask the backup holder for the points directory when the point is missing there (design 47 G 516)
FromBackup bool `json:"from-backup,omitempty"`
// the kept data, its local name in the module's manifest
Kept string `json:"kept"`
// the module
Module string `json:"module"`
// the restore point's name
Snapshot string `json:"snapshot,omitempty"`
// why, which the hand-act log keeps
Why string `json:"why"`
}
// RestoreKeptAnswer is what restore-kept answers.
type RestoreKeptAnswer struct {
// accepted: the act's id, which the ending event carries
Accepted string `json:"accepted,omitempty"`
// busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event
Busy string `json:"busy,omitempty"`
// why it was not taken; set alone
Error string `json:"error,omitempty"`
}
// KeptPointsInput is what kept-points takes.
type KeptPointsInput struct {
// one kept data (optional)
Kept string `json:"kept,omitempty"`
// one module (optional)
Module string `json:"module,omitempty"`
}
// KeptPointsAnswer is what kept-points answers.
type KeptPointsAnswer struct {
Asides []Aside `json:"asides"`
Points []Point `json:"points"`
Running *Running `json:"running,omitempty"`
}
// RestoreAsideInput is what restore-aside takes.
type RestoreAsideInput struct {
// the aside's id
ID string `json:"id"`
// the kept data, its local name in the module's manifest
Kept string `json:"kept"`
// the module
Module string `json:"module"`
// why, which the hand-act log keeps
Why string `json:"why"`
}
// RestoreAsideAnswer is what restore-aside answers.
type RestoreAsideAnswer struct {
// accepted: the act's id, which the ending event carries
Accepted string `json:"accepted,omitempty"`
// busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event
Busy string `json:"busy,omitempty"`
// why it was not taken; set alone
Error string `json:"error,omitempty"`
}
// CancelInput is what cancel takes.
type CancelInput struct {
// the act's id
Act string `json:"act"`
// why, which the hand-act log keeps
Why string `json:"why"`
}
// CancelAnswer is what cancel answers.
type CancelAnswer struct {
// the act ended cancelled
Cancelled bool `json:"cancelled,omitempty"`
// why not, naming the step an act has begun; set alone
Error string `json:"error,omitempty"`
}
// EndedEvent is the body of the event ended.
type EndedEvent struct {
// the id the act was answered accepted with, or a hand act's own id (design 47 G 560)
Act string `json:"act"`
// when it was said, RFC 3339
At time.Time `json:"at"`
CatchUp *CatchUp `json:"catch-up,omitempty"`
Consumers []Consumer `json:"consumers,omitempty"`
Digests []Digest `json:"digests,omitempty"`
// when failed
Error string `json:"error,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
Form int64 `json:"form,omitempty"`
Kept string `json:"kept"`
Module string `json:"module"`
// how the act ended
Outcome string `json:"outcome"`
Size int64 `json:"size,omitempty"`
Snapshot string `json:"snapshot,omitempty"`
}
// The values of EndedEvent's outcome.
const (
EndedEventOutcomeDone = "done"
EndedEventOutcomeFailed = "failed"
)
// RestoreStartedEvent is the body of the event restore-started.
type RestoreStartedEvent struct {
// the id the act was answered accepted with, or a hand act's own id (design 47 G 560)
Act string `json:"act"`
// when it was said, RFC 3339
At time.Time `json:"at"`
Fingerprint string `json:"fingerprint,omitempty"`
Form int64 `json:"form,omitempty"`
Kept string `json:"kept"`
Module string `json:"module"`
Snapshot string `json:"snapshot,omitempty"`
}
// RestoredEvent is the body of the event restored.
type RestoredEvent struct {
// the id the act was answered accepted with, or a hand act's own id (design 47 G 560)
Act string `json:"act"`
Aside *Aside `json:"aside,omitempty"`
// when it was said, RFC 3339
At time.Time `json:"at"`
Error string `json:"error,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
Form int64 `json:"form,omitempty"`
Kept string `json:"kept"`
// the point is not in the recovery module's directory
Missing bool `json:"missing,omitempty"`
Module string `json:"module"`
// how the act ended
Outcome string `json:"outcome"`
Snapshot string `json:"snapshot,omitempty"`
}
// The values of RestoredEvent's outcome.
const (
RestoredEventOutcomeDone = "done"
RestoredEventOutcomeFailed = "failed"
)
// SetAsideEvent is the body of the event set-aside.
type SetAsideEvent struct {
// the id the act was answered accepted with, or a hand act's own id (design 47 G 560)
Act string `json:"act"`
// when it was said, RFC 3339
At time.Time `json:"at"`
ID string `json:"id"`
Name string `json:"name"`
Size int64 `json:"size"`
}
// AsideDiscardedEvent is the body of the event aside-discarded.
type AsideDiscardedEvent struct {
// the id the act was answered accepted with, or a hand act's own id (design 47 G 560)
Act string `json:"act"`
// when it was said, RFC 3339
At time.Time `json:"at"`
ID string `json:"id"`
Name string `json:"name"`
Size int64 `json:"size"`
}
// PointRemovedEvent is the body of the event point-removed.
type PointRemovedEvent struct {
// the id the act was answered accepted with, or a hand act's own id (design 47 G 560)
Act string `json:"act"`
// when it was said, RFC 3339
At time.Time `json:"at"`
Fingerprint string `json:"fingerprint,omitempty"`
Form int64 `json:"form,omitempty"`
Kept string `json:"kept"`
Module string `json:"module"`
Snapshot string `json:"snapshot,omitempty"`
}
// ExpiryEndedEvent is the body of the event expiry-ended.
type ExpiryEndedEvent struct {
// the id the act was answered accepted with, or a hand act's own id (design 47 G 560)
Act string `json:"act"`
// when it was said, RFC 3339
At time.Time `json:"at"`
// the number of points the run removed, 0 included
Removed int64 `json:"removed"`
}
+80
View File
@@ -0,0 +1,80 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
package meshrecovery
import (
"encoding/json"
"reflect"
"regexp"
"testing"
)
// TestFixtures reads every fixture of seats/mesh-recovery/fixtures: each decodes into its type, a field the type does not
// know is dropped (ADR 0608 §6), and it reads back as its wire says; each subject named is the contract's.
func TestFixtures(t *testing.T) {
cases := []struct {
name, subject, given, wire string
decode func([]byte) (any, error)
subjectIs *regexp.Regexp
}{
{name: "ended is said", subject: "mesh.seat.mesh-recovery.event.ended.mesh-knowledge.ledger", given: "{\"act\":\"a-1\",\"at\":\"2026-11-11T10:00:00Z\",\"catch-up\":{\"tool\":\"knowledge_catch_up\"},\"consumers\":[{\"name\":\"c1\",\"sequence\":42,\"stream\":\"KV_x\"}],\"digests\":[{\"digest\":\"sha256:00\",\"name\":\"KV_x\"}],\"fingerprint\":\"0123456789ab\",\"form\":2,\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"outcome\":\"done\",\"size\":2048,\"snapshot\":\"2-0123456789ab\"}", wire: "{\"act\":\"a-1\",\"at\":\"2026-11-11T10:00:00Z\",\"catch-up\":{\"tool\":\"knowledge_catch_up\"},\"consumers\":[{\"name\":\"c1\",\"sequence\":42,\"stream\":\"KV_x\"}],\"digests\":[{\"digest\":\"sha256:00\",\"name\":\"KV_x\"}],\"fingerprint\":\"0123456789ab\",\"form\":2,\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"outcome\":\"done\",\"size\":2048,\"snapshot\":\"2-0123456789ab\"}",
decode: func(b []byte) (any, error) { var v EndedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-recovery\\.event\\.ended\\.[^.*> ]+\\.[^.*> ]+$")},
{name: "ended is said", subject: "mesh.seat.mesh-recovery.event.ended.mesh-knowledge.ledger", given: "{\"act\":\"a-2\",\"at\":\"2026-11-11T10:00:00Z\",\"error\":\"stopped\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"outcome\":\"failed\"}", wire: "{\"act\":\"a-2\",\"at\":\"2026-11-11T10:00:00Z\",\"error\":\"stopped\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"outcome\":\"failed\"}",
decode: func(b []byte) (any, error) { var v EndedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-recovery\\.event\\.ended\\.[^.*> ]+\\.[^.*> ]+$")},
{name: "restore-started is said", subject: "mesh.seat.mesh-recovery.event.restore-started.mesh-knowledge.ledger", given: "{\"act\":\"a-3\",\"at\":\"2026-11-11T10:00:00Z\",\"fingerprint\":\"0123456789ab\",\"form\":2,\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"snapshot\":\"2-0123456789ab\"}", wire: "{\"act\":\"a-3\",\"at\":\"2026-11-11T10:00:00Z\",\"fingerprint\":\"0123456789ab\",\"form\":2,\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"snapshot\":\"2-0123456789ab\"}",
decode: func(b []byte) (any, error) { var v RestoreStartedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-recovery\\.event\\.restore-started\\.[^.*> ]+\\.[^.*> ]+$")},
{name: "restored is said", subject: "mesh.seat.mesh-recovery.event.restored.mesh-knowledge.ledger", given: "{\"act\":\"a-3\",\"at\":\"2026-11-11T10:00:00Z\",\"error\":\"restore point missing\",\"kept\":\"ledger\",\"missing\":true,\"module\":\"mesh-knowledge\",\"outcome\":\"failed\"}", wire: "{\"act\":\"a-3\",\"at\":\"2026-11-11T10:00:00Z\",\"error\":\"restore point missing\",\"kept\":\"ledger\",\"missing\":true,\"module\":\"mesh-knowledge\",\"outcome\":\"failed\"}",
decode: func(b []byte) (any, error) { var v RestoredEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-recovery\\.event\\.restored\\.[^.*> ]+\\.[^.*> ]+$")},
{name: "set-aside is said", subject: "mesh.seat.mesh-recovery.event.set-aside.mesh-knowledge.ledger", given: "{\"act\":\"a-4\",\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"20261111T121200Z\",\"name\":\"mesh-knowledge_ledger\",\"size\":4096}", wire: "{\"act\":\"a-4\",\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"20261111T121200Z\",\"name\":\"mesh-knowledge_ledger\",\"size\":4096}",
decode: func(b []byte) (any, error) { var v SetAsideEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-recovery\\.event\\.set-aside\\.[^.*> ]+\\.[^.*> ]+$")},
{name: "aside-discarded is said", subject: "mesh.seat.mesh-recovery.event.aside-discarded.mesh-knowledge.ledger", given: "{\"act\":\"a-5\",\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"20261111T121200Z\",\"name\":\"mesh-knowledge_ledger\",\"size\":4096}", wire: "{\"act\":\"a-5\",\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"20261111T121200Z\",\"name\":\"mesh-knowledge_ledger\",\"size\":4096}",
decode: func(b []byte) (any, error) { var v AsideDiscardedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-recovery\\.event\\.aside-discarded\\.[^.*> ]+\\.[^.*> ]+$")},
{name: "point-removed is said", subject: "mesh.seat.mesh-recovery.event.point-removed.mesh-knowledge.ledger", given: "{\"act\":\"a-6\",\"at\":\"2026-11-11T10:00:00Z\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"snapshot\":\"1-aaaaaaaaaaaa\"}", wire: "{\"act\":\"a-6\",\"at\":\"2026-11-11T10:00:00Z\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"snapshot\":\"1-aaaaaaaaaaaa\"}",
decode: func(b []byte) (any, error) { var v PointRemovedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-recovery\\.event\\.point-removed\\.[^.*> ]+\\.[^.*> ]+$")},
{name: "expiry-ended says 0 when it removed nothing", subject: "mesh.seat.mesh-recovery.event.expiry-ended", given: "{\"act\":\"a-7\",\"at\":\"2026-11-11T10:00:00Z\",\"removed\":0}", wire: "{\"act\":\"a-7\",\"at\":\"2026-11-11T10:00:00Z\",\"removed\":0}",
decode: func(b []byte) (any, error) { var v ExpiryEndedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-recovery\\.event\\.expiry-ended$")},
{name: "snapshot-kept takes the point of a kept data before its form changes", subject: "", given: "{\"catch-up\":{\"arguments\":{\"since\":\"0\"},\"tool\":\"knowledge_catch_up\"},\"fingerprint\":\"0123456789ab\",\"form\":2,\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"form change of mesh-knowledge's ledger to 0123456789ab\"}", wire: "{\"catch-up\":{\"arguments\":{\"since\":\"0\"},\"tool\":\"knowledge_catch_up\"},\"fingerprint\":\"0123456789ab\",\"form\":2,\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"form change of mesh-knowledge's ledger to 0123456789ab\"}",
decode: func(b []byte) (any, error) { var v SnapshotKeptInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "snapshot-kept answers accepted with the act", subject: "", given: "{\"accepted\":\"a-20261111t100000\"}", wire: "{\"accepted\":\"a-20261111t100000\"}",
decode: func(b []byte) (any, error) { var v SnapshotKeptAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "restore-kept answers busy with the act that runs", subject: "", given: "{\"busy\":\"a-20261111t100000\"}", wire: "{\"busy\":\"a-20261111t100000\"}",
decode: func(b []byte) (any, error) { var v RestoreKeptAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "restore-kept takes a point and asks the backup for its directory", subject: "", given: "{\"fingerprint\":\"0123456789ab\",\"form\":2,\"from-backup\":true,\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"snapshot\":\"2-0123456789ab\",\"why\":\"revert of mesh-knowledge's ledger from 0123456789ab\"}", wire: "{\"fingerprint\":\"0123456789ab\",\"form\":2,\"from-backup\":true,\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"snapshot\":\"2-0123456789ab\",\"why\":\"revert of mesh-knowledge's ledger from 0123456789ab\"}",
decode: func(b []byte) (any, error) { var v RestoreKeptInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "restore-kept answers an error naming why", subject: "", given: "{\"error\":\"aside-only is the operator's hand tool alone\"}", wire: "{\"error\":\"aside-only is the operator's hand tool alone\"}",
decode: func(b []byte) (any, error) { var v RestoreKeptAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "kept-points lists a point, an aside and the act that runs", subject: "", given: "{\"asides\":[{\"id\":\"20261111T121200Z\",\"name\":\"mesh-knowledge_ledger\",\"size\":4096}],\"points\":[{\"at\":\"2026-11-11T10:00:00Z\",\"catch-up\":{\"tool\":\"knowledge_catch_up\"},\"consumers\":[{\"name\":\"c1\",\"sequence\":42,\"stream\":\"KV_x\"}],\"digests\":[{\"digest\":\"sha256:00\",\"name\":\"KV_x\"}],\"fingerprint\":\"0123456789ab\",\"form\":2,\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"size\":2048,\"snapshot\":\"2-0123456789ab\"}],\"running\":{\"act\":\"a-1\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"waiting on the dump\",\"waits-on\":\"item-ended of the bus item\"}}", wire: "{\"asides\":[{\"id\":\"20261111T121200Z\",\"name\":\"mesh-knowledge_ledger\",\"size\":4096}],\"points\":[{\"at\":\"2026-11-11T10:00:00Z\",\"catch-up\":{\"tool\":\"knowledge_catch_up\"},\"consumers\":[{\"name\":\"c1\",\"sequence\":42,\"stream\":\"KV_x\"}],\"digests\":[{\"digest\":\"sha256:00\",\"name\":\"KV_x\"}],\"fingerprint\":\"0123456789ab\",\"form\":2,\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"size\":2048,\"snapshot\":\"2-0123456789ab\"}],\"running\":{\"act\":\"a-1\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"waiting on the dump\",\"waits-on\":\"item-ended of the bus item\"}}",
decode: func(b []byte) (any, error) { var v KeptPointsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "restore-aside names the aside", subject: "", given: "{\"id\":\"20261111T121200Z\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"an aside left at removed\"}", wire: "{\"id\":\"20261111T121200Z\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"an aside left at removed\"}",
decode: func(b []byte) (any, error) { var v RestoreAsideInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "cancel names the act", subject: "", given: "{\"act\":\"a-1\",\"why\":\"the dump waits too long\"}", wire: "{\"act\":\"a-1\",\"why\":\"the dump waits too long\"}",
decode: func(b []byte) (any, error) { var v CancelInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "cancel answers an error naming the step an act has begun", subject: "", given: "{\"error\":\"the act has begun: step restoring\"}", wire: "{\"error\":\"the act has begun: step restoring\"}",
decode: func(b []byte) (any, error) { var v CancelAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
}
for _, c := range cases {
v, err := c.decode([]byte(c.given))
if err != nil {
t.Errorf("%s: does not decode: %v", c.name, err)
continue
}
back, err := json.Marshal(v)
if err != nil {
t.Errorf("%s: does not encode: %v", c.name, err)
continue
}
var got, want any
if err := json.Unmarshal(back, &got); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal([]byte(c.wire), &want); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("%s: reads back as %s, want %s", c.name, back, c.wire)
}
if c.subjectIs != nil && !c.subjectIs.MatchString(c.subject) {
t.Errorf("%s: subject %q is not the contract's %s", c.name, c.subject, c.subjectIs)
}
}
}
+3
View File
@@ -0,0 +1,3 @@
module git.novox.be/novox/mesh-contracts/go/seats/meshrecovery
go 1.26.0
+111 -25
View File
@@ -28,6 +28,8 @@ const (
VerbManifestReads = "manifest-reads"
VerbSeatList = "seat-list"
VerbSeatListAgain = "seat-list-again"
VerbFirstViews = "first-views"
VerbFirstViewAgain = "first-view-again"
VerbProcessBuilds = "process-builds"
)
@@ -38,17 +40,18 @@ const (
EventManifestReadsChanged = "manifest-reads-changed"
EventComposed = "composed"
EventRunning = "running"
EventCapabilitiesChanged = "capabilities-changed"
EventWaitHeld = "wait-held"
EventWaitEnded = "wait-ended"
)
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbHandOver, VerbAccessAct, VerbJudgeDirectories, VerbSetuidSearch, VerbRootFree, VerbActs, VerbPackages, VerbComposition, VerbCompose, VerbComposeCandidate, VerbManifestReads, VerbSeatList, VerbSeatListAgain, VerbProcessBuilds}
return []string{VerbHandOver, VerbAccessAct, VerbJudgeDirectories, VerbSetuidSearch, VerbRootFree, VerbActs, VerbPackages, VerbComposition, VerbCompose, VerbComposeCandidate, VerbManifestReads, VerbSeatList, VerbSeatListAgain, VerbFirstViews, VerbFirstViewAgain, VerbProcessBuilds}
}
// Events is every event published on the seat's subject: a new copy each call.
func Events() []string {
return []string{EventAsked, EventDone, EventRefused, EventManifestReadsChanged, EventComposed, EventRunning, EventCapabilitiesChanged}
return []string{EventAsked, EventDone, EventRefused, EventManifestReadsChanged, EventComposed, EventRunning, EventWaitHeld, EventWaitEnded}
}
// Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects.
@@ -88,13 +91,27 @@ func SubjectRunning(node, module string) string {
// SubjectRunningAll hears every running.
const SubjectRunningAll = "mesh.seat.node-engine.event.running.*.*"
// SubjectCapabilitiesChanged is where capabilities-changed is published, on mesh.seat.node-engine.event.capabilities-changed.<node>.
func SubjectCapabilitiesChanged(node string) string {
return "mesh.seat.node-engine.event.capabilities-changed." + node
// StreamWaitHeld is the stream wait-held is kept in.
const StreamWaitHeld = "SEAT_node-engine_wait-held"
// SubjectWaitHeld is where wait-held is published, on mesh.seat.node-engine.event.wait-held.<node>.<module>.
func SubjectWaitHeld(node, module string) string {
return "mesh.seat.node-engine.event.wait-held." + node + "." + module
}
// SubjectCapabilitiesChangedAll hears every capabilities-changed.
const SubjectCapabilitiesChangedAll = "mesh.seat.node-engine.event.capabilities-changed.*"
// SubjectWaitHeldAll hears every wait-held.
const SubjectWaitHeldAll = "mesh.seat.node-engine.event.wait-held.*.*"
// StreamWaitEnded is the stream wait-ended is kept in.
const StreamWaitEnded = "SEAT_node-engine_wait-ended"
// SubjectWaitEnded is where wait-ended is published, on mesh.seat.node-engine.event.wait-ended.<node>.<module>.
func SubjectWaitEnded(node, module string) string {
return "mesh.seat.node-engine.event.wait-ended." + node + "." + module
}
// SubjectWaitEndedAll hears every wait-ended.
const SubjectWaitEndedAll = "mesh.seat.node-engine.event.wait-ended.*.*"
const (
StateHave = "have"
@@ -552,18 +569,6 @@ type CandidateRefusal struct {
Why string `json:"why"`
}
// CapabilitiesChanged is a node's capabilities changed, which the assigner places by.
type CapabilitiesChanged struct {
// when, RFC 3339
At time.Time `json:"at"`
// its capabilities now
Capabilities []string `json:"capabilities"`
// the node
Node string `json:"node"`
// the layout's version
V int64 `json:"v"`
}
// Composed is an entry composed on a node (design 47 *What each step says*).
type Composed struct {
// when, RFC 3339
@@ -885,19 +890,21 @@ type RootVerdict struct {
Why string `json:"why"`
}
// Running is a module running healthy on a node at a build: what the node-engines of other nodes and the grants part decide a seat's pass from (ADR 0660 §2), kept last per subject with no age limit.
// Running is a module's build running on a node, and its health: what the node-engines of other nodes and the grants part decide a seat's pass from (ADR 0660 §2), and the assigner reads a provider's and a canary's health and a put's end from (design 47 G 520); said again on the same subject each time healthy changes and for every module the have holds at the engine's start, kept last per subject with no age limit.
type Running struct {
// when it was seen running healthy, RFC 3339
// when it was said, RFC 3339
At time.Time `json:"at"`
// the fingerprint of the build it runs; none with stopped
Fingerprint string `json:"fingerprint,omitempty"`
// the health of the build that runs, as the node-engine's have holds it from the module's declared check (ADR 0240); a running with none, said by a node-engine of before design 47 G, reads as healthy (design 47 G 520). SeatsToPassOn reads none of it: an unhealthy holder still holds its seat
Healthy *bool `json:"healthy,omitempty"`
// the digest of that build's manifest, the indexer's manifest of it, read by digest from the catalog half
Manifest string `json:"manifest,omitempty"`
// the module
Module string `json:"module"`
// the node
Node string `json:"node"`
// true when the module no longer runs on the node: no fingerprint or manifest then (design 47 G 365, 404); mesh-sdk's SeatsToPassOn counts such a module as not running there
// true when the have no longer holds the module on the node, never for an unhealthy build: no fingerprint or manifest then (design 47 G 365, 404, 520); mesh-sdk's SeatsToPassOn counts such a module as not running there
Stopped bool `json:"stopped,omitempty"`
// the layout's version
V int64 `json:"v"`
@@ -987,6 +994,37 @@ type UserListWritten struct {
Wanted string `json:"wanted"`
}
// Wait is a hold or wait a node-engine says, the fields of the seat mesh-wants' wait (design 47 G 498, 512, 629); in the have it also carries ask-at and act (design 47 G 560).
type Wait struct {
// in the have: the act the recovery module answered accepted with (design 47 G 560)
Act string `json:"act,omitempty"`
// in the have: the time of the first ask of the recovery module for this wait (design 47 G 560)
AskAt *time.Time `json:"ask-at,omitempty"`
// a duration such as 30m
Bound string `json:"bound,omitempty"`
// the time the bound counts from (design 47 G 266, 269, 498)
ClockFrom *time.Time `json:"clock-from,omitempty"`
// the node waited on: the build node for a kept build; the subject's <node> is the node that says it
Node string `json:"node,omitempty"`
// what it waits on
On string `json:"on,omitempty"`
// the seat holding the wait: node-engine
Owner string `json:"owner,omitempty"`
// why it holds, such as restore point missing, entry-missing, parts-differ, node away or build no longer kept (design 47 G 541, 499)
Reason string `json:"reason,omitempty"`
Since time.Time `json:"since"`
// compose: a hold of the composition (a restore point missing, an entry missing, parts that differ, a recovery that waits); kept-build: waiting for a kept build (design 47 G 499, 541)
Step string `json:"step"`
// the wait said in words
Words string `json:"words,omitempty"`
}
// The values of Wait's step.
const (
WaitStepCompose = "compose"
WaitStepKeptBuild = "kept-build"
)
// HandOverInput is what hand-over takes.
type HandOverInput struct {
// the directory's absolute path, as the engine states it
@@ -1324,6 +1362,50 @@ type SeatListAgainInput struct {
// SeatListAgainAnswer is what seat-list-again answers.
type SeatListAgainAnswer = SeatList
// FirstViewsInput is what first-views takes.
type FirstViewsInput struct {
}
// FirstViewsAnswer is what first-views answers.
type FirstViewsAnswer struct {
// one status per owner and state followed, as mesh-sdk's views runtime keeps it
FirstViews []map[string]any `json:"first-views"`
}
// FirstViewAgainInput is what first-view-again takes.
type FirstViewAgainInput struct {
// the owner module, as first-views names it
Owner string `json:"owner"`
// the owner's state, as first-views names it
State string `json:"state"`
// why it is asked again, in plain words
Why string `json:"why"`
}
// FirstViewAgainAnswerAsked is the hand ask written.
type FirstViewAgainAnswerAsked struct {
// when it was asked, RFC 3339
At time.Time `json:"at"`
// the caller the runtime proved
By string `json:"by"`
// the owner module
Owner string `json:"owner"`
// the owner's state
State string `json:"state"`
// the layout's version
V int64 `json:"v"`
// why it was asked again
Why string `json:"why"`
}
// FirstViewAgainAnswer is what first-view-again answers.
type FirstViewAgainAnswer struct {
// the hand ask written
Asked FirstViewAgainAnswerAsked `json:"asked"`
// the verb that reads the status, first-views
Read string `json:"read"`
}
// ProcessBuildsInput is what process-builds takes.
type ProcessBuildsInput struct {
// only this module's processes
@@ -1351,8 +1433,11 @@ type ComposedEvent = Composed
// RunningEvent is the body of the event running.
type RunningEvent = Running
// CapabilitiesChangedEvent is the body of the event capabilities-changed.
type CapabilitiesChangedEvent = CapabilitiesChanged
// WaitHeldEvent is the body of the event wait-held.
type WaitHeldEvent = Wait
// WaitEndedEvent is the body of the event wait-ended.
type WaitEndedEvent = Wait
// HaveByNodeModuleValue is one value of the state have under the key <node>.<module>.
type HaveByNodeModuleValue struct {
@@ -1380,6 +1465,7 @@ type HaveByNodeModuleValue struct {
SealedAt map[string]string `json:"sealed-at,omitempty"`
UserList *UserListWritten `json:"user-list,omitempty"`
V int64 `json:"v"`
Wait *Wait `json:"wait,omitempty"`
// in phase waiting-yes, the digest of the composition that waits
Waits string `json:"waits,omitempty"`
}
+12 -6
View File
@@ -39,14 +39,16 @@ func TestFixtures(t *testing.T) {
decode: func(b []byte) (any, error) { var v ComposedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.composed\\.[^.*> ]+$")},
{name: "running is said, its node and module in its subject", subject: "mesh.seat.node-engine.event.running.ace.nats", given: "{\"at\":\"2026-11-09T20:01:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"ace\",\"v\":1}", wire: "{\"at\":\"2026-11-09T20:01:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"ace\",\"v\":1}",
decode: func(b []byte) (any, error) { var v RunningEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.running\\.[^.*> ]+\\.[^.*> ]+$")},
{name: "running says the build that runs is healthy", subject: "mesh.seat.node-engine.event.running.ace.nats", given: "{\"at\":\"2026-11-11T09:00:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"healthy\":true,\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"ace\",\"v\":1}", wire: "{\"at\":\"2026-11-11T09:00:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"healthy\":true,\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"ace\",\"v\":1}",
decode: func(b []byte) (any, error) { var v RunningEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.running\\.[^.*> ]+\\.[^.*> ]+$")},
{name: "running says the build that runs turned unhealthy, still running", subject: "mesh.seat.node-engine.event.running.ace.nats", given: "{\"at\":\"2026-11-11T09:05:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"healthy\":false,\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"ace\",\"v\":1}", wire: "{\"at\":\"2026-11-11T09:05:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"healthy\":false,\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"ace\",\"v\":1}",
decode: func(b []byte) (any, error) { var v RunningEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.running\\.[^.*> ]+\\.[^.*> ]+$")},
{name: "running says a module stopped on the node, with no fingerprint", subject: "mesh.seat.node-engine.event.running.ace.nats", given: "{\"at\":\"2026-11-11T09:00:00Z\",\"module\":\"nats\",\"node\":\"ace\",\"stopped\":true,\"v\":1}", wire: "{\"at\":\"2026-11-11T09:00:00Z\",\"module\":\"nats\",\"node\":\"ace\",\"stopped\":true,\"v\":1}",
decode: func(b []byte) (any, error) { var v RunningEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.running\\.[^.*> ]+\\.[^.*> ]+$")},
{name: "capabilities-changed is said", subject: "mesh.seat.node-engine.event.capabilities-changed.ace", given: "{\"at\":\"2026-11-09T20:02:00Z\",\"capabilities\":[\"build-go\",\"docker\"],\"node\":\"ace\",\"v\":1}", wire: "{\"at\":\"2026-11-09T20:02:00Z\",\"capabilities\":[\"build-go\",\"docker\"],\"node\":\"ace\",\"v\":1}",
decode: func(b []byte) (any, error) {
var v CapabilitiesChangedEvent
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.capabilities-changed\\.[^.*> ]+$")},
{name: "a kept build's wait is held", subject: "mesh.seat.node-engine.event.wait-held.ace.postgres", given: "{\"bound\":\"60m\",\"clock-from\":\"2026-11-11T10:00:00Z\",\"node\":\"ace\",\"on\":\"the kept build of postgres\",\"owner\":\"node-engine\",\"reason\":\"node away\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"kept-build\",\"words\":\"waiting for the kept build of postgres on ace\"}", wire: "{\"bound\":\"60m\",\"clock-from\":\"2026-11-11T10:00:00Z\",\"node\":\"ace\",\"on\":\"the kept build of postgres\",\"owner\":\"node-engine\",\"reason\":\"node away\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"kept-build\",\"words\":\"waiting for the kept build of postgres on ace\"}",
decode: func(b []byte) (any, error) { var v WaitHeldEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.wait-held\\.[^.*> ]+\\.[^.*> ]+$")},
{name: "a hold of the composition ends", subject: "mesh.seat.node-engine.event.wait-ended.ace.postgres", given: "{\"owner\":\"node-engine\",\"reason\":\"restore point missing\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"compose\"}", wire: "{\"owner\":\"node-engine\",\"reason\":\"restore point missing\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"compose\"}",
decode: func(b []byte) (any, error) { var v WaitEndedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.wait-ended\\.[^.*> ]+\\.[^.*> ]+$")},
{name: "the finding of a seat list unread, as the engine writes it", subject: "", given: "{\"at\":\"2026-11-05T14:00:00Z\",\"key\":\"machine.ace.seat-list-unread\",\"kind\":\"seat-list-unread\",\"observer\":\"mesh-host\",\"severity\":\"warning\",\"source\":\"node-engine.seat-list\",\"subject\":{\"id\":\"ace\",\"machine\":\"ace\",\"scope\":\"machine\"},\"summary\":\"the seat list has not been read for five minutes\",\"v\":1}", wire: "{\"at\":\"2026-11-05T14:00:00Z\",\"key\":\"machine.ace.seat-list-unread\",\"kind\":\"seat-list-unread\",\"observer\":\"mesh-host\",\"severity\":\"warning\",\"source\":\"node-engine.seat-list\",\"subject\":{\"id\":\"ace\",\"machine\":\"ace\",\"scope\":\"machine\"},\"summary\":\"the seat list has not been read for five minutes\",\"v\":1}",
decode: func(b []byte) (any, error) { var v FindingsByKeyValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a node's packages, present and absent (captured from ace, shortened)", subject: "", given: "{\"node\":\"ace\",\"packages\":[{\"absent\":false,\"modules\":[\"avahi\"],\"package\":\"avahi\"},{\"absent\":false,\"modules\":[\"mesh-build-go\",\"mesh-build-node\"],\"package\":\"bubblewrap\"},{\"absent\":true,\"modules\":[\"time-sync\"],\"package\":\"ntp\"}]}", wire: "{\"node\":\"ace\",\"packages\":[{\"absent\":false,\"modules\":[\"avahi\"],\"package\":\"avahi\"},{\"absent\":false,\"modules\":[\"mesh-build-go\",\"mesh-build-node\"],\"package\":\"bubblewrap\"},{\"absent\":true,\"modules\":[\"time-sync\"],\"package\":\"ntp\"}]}",
@@ -89,6 +91,8 @@ func TestFixtures(t *testing.T) {
decode: func(b []byte) (any, error) { var v DataByNodeModuleValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a node's health (typed from the engine's values)", subject: "", given: "{\"at\":\"2026-11-05T12:00:00Z\",\"contract\":3,\"network\":{\"parts\":[{\"part\":\"resolver\",\"since\":\"2026-11-05T10:00:00Z\",\"state\":\"running\"}],\"since\":\"2026-11-05T10:00:00Z\",\"state\":\"running\"},\"node\":\"ace\",\"peers\":[{\"addresses\":[\"198.51.100.1\"],\"key\":\"k\",\"seen\":true}],\"relogin\":[{\"account\":\"web\",\"reason\":\"password changed\",\"since\":\"2026-11-05T11:00:00Z\"}],\"units\":{\"failed\":[{\"result\":\"exit-code\",\"scope\":\"system\",\"since\":\"2026-11-05T11:00:00Z\",\"unit\":\"x.service\"}],\"state\":\"degraded\"},\"v\":1}", wire: "{\"at\":\"2026-11-05T12:00:00Z\",\"contract\":3,\"network\":{\"parts\":[{\"part\":\"resolver\",\"since\":\"2026-11-05T10:00:00Z\",\"state\":\"running\"}],\"since\":\"2026-11-05T10:00:00Z\",\"state\":\"running\"},\"node\":\"ace\",\"peers\":[{\"addresses\":[\"198.51.100.1\"],\"key\":\"k\",\"seen\":true}],\"relogin\":[{\"account\":\"web\",\"reason\":\"password changed\",\"since\":\"2026-11-05T11:00:00Z\"}],\"units\":{\"failed\":[{\"result\":\"exit-code\",\"scope\":\"system\",\"since\":\"2026-11-05T11:00:00Z\",\"unit\":\"x.service\"}],\"state\":\"degraded\"},\"v\":1}",
decode: func(b []byte) (any, error) { var v HealthByNodeValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a have that waits for the recovery module, with its wait, ask-at and act (design 47 G 560, 629)", subject: "", given: "{\"detail\":\"waiting for a restore point\",\"fp\":\"0123456789ab\",\"health\":\"waits\",\"phase\":\"held\",\"v\":1,\"wait\":{\"act\":\"a-20261111t100000\",\"ask-at\":\"2026-11-11T10:00:00Z\",\"bound\":\"90m\",\"clock-from\":\"2026-11-11T10:00:00Z\",\"on\":\"the recovery module's ended\",\"owner\":\"node-engine\",\"reason\":\"recovery wait\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"compose\",\"words\":\"waiting for the point of postgres's data\"}}", wire: "{\"detail\":\"waiting for a restore point\",\"fp\":\"0123456789ab\",\"health\":\"waits\",\"phase\":\"held\",\"v\":1,\"wait\":{\"act\":\"a-20261111t100000\",\"ask-at\":\"2026-11-11T10:00:00Z\",\"bound\":\"90m\",\"clock-from\":\"2026-11-11T10:00:00Z\",\"on\":\"the recovery module's ended\",\"owner\":\"node-engine\",\"reason\":\"recovery wait\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"compose\",\"words\":\"waiting for the point of postgres's data\"}}",
decode: func(b []byte) (any, error) { var v HaveByNodeModuleValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "manifest-reads answers what the engine reads and when it stated it", subject: "", given: "{\"manifest-reads\":[\"form:seat-reference\",\"word:series\"],\"node\":\"ace\",\"stated-at\":\"2026-11-04T10:00:00Z\",\"v\":1}", wire: "{\"manifest-reads\":[\"form:seat-reference\",\"word:series\"],\"node\":\"ace\",\"stated-at\":\"2026-11-04T10:00:00Z\",\"v\":1}",
decode: func(b []byte) (any, error) { var v ManifestReadsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "manifest-reads of an engine built before it said them is the baseline", subject: "", given: "{\"node\":\"g14\",\"stated-at\":\"2026-11-04T10:05:00Z\",\"v\":1}", wire: "{\"node\":\"g14\",\"stated-at\":\"2026-11-04T10:05:00Z\",\"v\":1}",
@@ -109,6 +113,8 @@ func TestFixtures(t *testing.T) {
decode: func(b []byte) (any, error) { var v ProcessBuildsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "process-builds answers an error", subject: "", given: "{\"error\":\"the service manager did not answer\",\"node\":\"ace\",\"processes\":[]}", wire: "{\"error\":\"the service manager did not answer\",\"node\":\"ace\",\"processes\":[]}",
decode: func(b []byte) (any, error) { var v ProcessBuildsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "first-view-again names the owner, its state and why", subject: "", given: "{\"owner\":\"mesh-delivery-indexer\",\"state\":\"manifests\",\"why\":\"the picture lags the indexer by hand\"}", wire: "{\"owner\":\"mesh-delivery-indexer\",\"state\":\"manifests\",\"why\":\"the picture lags the indexer by hand\"}",
decode: func(b []byte) (any, error) { var v FirstViewAgainInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
}
for _, c := range cases {
v, err := c.decode([]byte(c.given))
@@ -95,26 +95,36 @@ func DecodeConditionsByKeyValue(raw []byte) (ConditionsByKeyValue, error) {
return v, nil
}
// FindingsByKeyKey is the key of one value of the state findings under the form "<key>".
func FindingsByKeyKey(key string) string { return key }
// FindingsByScopeIDKindKey is the key of one value of the state findings under the form "<scope>.<id>.<kind>".
func FindingsByScopeIDKindKey(scope, id, kind string) string { return scope + "." + id + "." + kind }
// ParseFindingsByKeyKey reads the parts of a key of the form "<key>", in the order of the form; the last result is false for a key of another form.
func ParseFindingsByKeyKey(key string) (string, bool) {
// ParseFindingsByScopeIDKindKey reads the parts of a key of the form "<scope>.<id>.<kind>", in the order of the form; the last result is false for a key of another form.
func ParseFindingsByScopeIDKindKey(key string) (string, string, string, bool) {
rest := key
if rest == "" {
return "", false
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", "", false
}
v0 := rest
rest = after0
v2, after2, found2 := strings.Cut(rest, ".")
if !found2 || v2 == "" {
return "", "", "", false
}
rest = after2
if rest == "" {
return "", "", "", false
}
v4 := rest
rest = ""
if rest != "" {
return "", false
return "", "", "", false
}
return v0, true
return v0, v2, v4, true
}
// DecodeFindingsByKeyValue reads one value of the state findings under the form "<key>", refusing a field FindingsByKeyValue does not know and any text after the value.
func DecodeFindingsByKeyValue(raw []byte) (FindingsByKeyValue, error) {
var v FindingsByKeyValue
// DecodeFindingsByScopeIDKindValue reads one value of the state findings under the form "<scope>.<id>.<kind>", refusing a field FindingsByScopeIDKindValue does not know and any text after the value.
func DecodeFindingsByScopeIDKindValue(raw []byte) (FindingsByScopeIDKindValue, error) {
var v FindingsByScopeIDKindValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
@@ -290,7 +300,7 @@ type Finding struct {
Needs string `json:"needs,omitempty"`
// the module that wrote it
Observer string `json:"observer"`
// the module the finding is about, when not the observer
// who the finding is about, when not the observer: a session who@node (^[a-z0-9][a-z0-9._-]*@[a-z0-9][a-z0-9-]*$), a seat's name (^[a-z][a-z0-9-]*$) or a commit's author forge:<account> (^forge:[A-Za-z0-9._-]+$); another is refused (design 47 G 678)
Owner string `json:"owner,omitempty"`
// the key's last part when it is not the kind
Ref string `json:"ref,omitempty"`
@@ -308,7 +318,7 @@ type Finding struct {
Subject Subject `json:"subject"`
// one line
Summary string `json:"summary"`
// what it waits on: a person, a part or a call
// what it waits on: a session who@node (^[a-z0-9][a-z0-9._-]*@[a-z0-9][a-z0-9-]*$), a seat's name (^[a-z][a-z0-9-]*$) or a commit's author forge:<account> (^forge:[A-Za-z0-9._-]+$); another is refused (design 47 G 678)
WaitingOn string `json:"waiting-on,omitempty"`
}
@@ -344,6 +354,8 @@ type FindingsStated struct {
Asked string `json:"asked,omitempty"`
At time.Time `json:"at"`
Findings []Finding `json:"findings"`
// the finding kinds the stating bundle owns and states, the only kinds whose keys the statement deletes and whose conditions the keeper clears when it names none of them; none covers every kind, as a part with one bundle states (design 47 G 580)
Kinds []string `json:"kinds,omitempty"`
// the keys the observer cannot judge yet since its start, with their since and bound
Pending []FindingsStatedPendingItem `json:"pending"`
}
@@ -477,46 +489,8 @@ const (
ConditionsByKeyValueSeverityWarning = "warning"
)
// FindingsByKeyValue is one value of the state findings under the key <key>.
type FindingsByKeyValue struct {
Actions []Action `json:"actions,omitempty"`
At time.Time `json:"at"`
// newest first, at most 10
Evidence []Evidence `json:"evidence,omitempty"`
Explanation string `json:"explanation,omitempty"`
Headline string `json:"headline,omitempty"`
// scope.id.token
Key string `json:"key"`
Kind string `json:"kind"`
Needs string `json:"needs,omitempty"`
// the module that wrote it
Observer string `json:"observer"`
// the module the finding is about, when not the observer
Owner string `json:"owner,omitempty"`
// the key's last part when it is not the kind
Ref string `json:"ref,omitempty"`
Resolved string `json:"resolved,omitempty"`
// self, operator, agent or healer:<name>
Resolver string `json:"resolver,omitempty"`
// what of its own saw it last
Run string `json:"run,omitempty"`
Severity string `json:"severity"`
// the row, probe or event that saw it
Source string `json:"source"`
Subject Subject `json:"subject"`
// one line
Summary string `json:"summary"`
// layout version, 1
V int64 `json:"v"`
// what it waits on: a person, a part or a call
WaitingOn string `json:"waiting-on,omitempty"`
}
// The values of FindingsByKeyValue's severity.
const (
FindingsByKeyValueSeverityUrgent = "urgent"
FindingsByKeyValueSeverityWarning = "warning"
)
// FindingsByScopeIDKindValue is one value of the state findings under the key <scope>.<id>.<kind>.
type FindingsByScopeIDKindValue = Finding
// FindingsViewsByObserverNodeValueStated is the last findings-stated set.
type FindingsViewsByObserverNodeValueStated struct {
@@ -25,10 +25,30 @@ func TestFixtures(t *testing.T) {
decode: func(b []byte) (any, error) { var v ConditionsByKeyValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a condition with an empty evidence trail of headline (captured; empty fields left out)", subject: "", given: "{\"count\":1,\"evidence\":[{\"at\":\"2026-10-30T13:03:46.432309782Z\",\"said\":\"The backup of postgres failed on ace\"}],\"explanation\":\"Needs you: answer whether to back up again or go on without a backup. The backup before applying postgres at 978b5db8cb4e on ace failed: node-backup.now could not be asked: not asked: nothing on this machine answers mesh.seat.node-backup.tool.now.ace.\",\"headline\":\"The backup of postgres failed on ace\",\"key\":\"module.ace.postgres.978b5db8cb4ef746ce68d82cc616d1439a53266d335e60f368dc44c4cb25610f.backup\",\"kind\":\"backup\",\"last-observed\":\"2026-10-30T13:03:46.432309782Z\",\"needs\":\"answer whether to back up again or go on without a backup\",\"observations\":1,\"raised\":\"2026-10-31T08:19:57.069818178Z\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"assigner.backup\",\"subject\":{\"id\":\"ace.postgres.978b5db8cb4ef746ce68d82cc616d1439a53266d335e60f368dc44c4cb25610f\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"The backup of postgres failed on ace\"}", wire: "{\"count\":1,\"evidence\":[{\"at\":\"2026-10-30T13:03:46.432309782Z\",\"said\":\"The backup of postgres failed on ace\"}],\"explanation\":\"Needs you: answer whether to back up again or go on without a backup. The backup before applying postgres at 978b5db8cb4e on ace failed: node-backup.now could not be asked: not asked: nothing on this machine answers mesh.seat.node-backup.tool.now.ace.\",\"headline\":\"The backup of postgres failed on ace\",\"key\":\"module.ace.postgres.978b5db8cb4ef746ce68d82cc616d1439a53266d335e60f368dc44c4cb25610f.backup\",\"kind\":\"backup\",\"last-observed\":\"2026-10-30T13:03:46.432309782Z\",\"needs\":\"answer whether to back up again or go on without a backup\",\"observations\":1,\"raised\":\"2026-10-31T08:19:57.069818178Z\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"assigner.backup\",\"subject\":{\"id\":\"ace.postgres.978b5db8cb4ef746ce68d82cc616d1439a53266d335e60f368dc44c4cb25610f\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"The backup of postgres failed on ace\"}",
decode: func(b []byte) (any, error) { var v ConditionsByKeyValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a finding of the bus scope with its zero time (captured from the watchman; empty fields left out)", subject: "", given: "{\"at\":\"0001-01-01T00:00:00Z\",\"evidence\":[{\"at\":\"0001-01-01T00:00:00Z\",\"said\":\"1 held for EVENTS.controller (counted again every 5m0s while any is held, so one delivered again or dropped ends this within it)\"}],\"key\":\"bus.EVENTS.controller.max-deliveries\",\"kind\":\"max-deliveries\",\"needs\":\"deliver them again or drop them, from the mesh MCP server.\",\"observer\":\"mesh-delivery-watchman\",\"severity\":\"warning\",\"source\":\"S9\",\"subject\":{\"id\":\"EVENTS.controller\",\"scope\":\"bus\"},\"summary\":\"the controller's consumer on EVENTS gave up on a message; it is kept in DEAD_LETTERS until delivered again or dropped, with why\",\"v\":1}", wire: "{\"at\":\"0001-01-01T00:00:00Z\",\"evidence\":[{\"at\":\"0001-01-01T00:00:00Z\",\"said\":\"1 held for EVENTS.controller (counted again every 5m0s while any is held, so one delivered again or dropped ends this within it)\"}],\"key\":\"bus.EVENTS.controller.max-deliveries\",\"kind\":\"max-deliveries\",\"needs\":\"deliver them again or drop them, from the mesh MCP server.\",\"observer\":\"mesh-delivery-watchman\",\"severity\":\"warning\",\"source\":\"S9\",\"subject\":{\"id\":\"EVENTS.controller\",\"scope\":\"bus\"},\"summary\":\"the controller's consumer on EVENTS gave up on a message; it is kept in DEAD_LETTERS until delivered again or dropped, with why\",\"v\":1}",
decode: func(b []byte) (any, error) { var v FindingsByKeyValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a finding of the module scope with its machine (captured from the watchman; empty fields left out)", subject: "", given: "{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on ace uses mesh-build-node.cache as found: owned by 10005:10005, mode 700, as found\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator.\",\"headline\":\"mesh-build-node waits for a directory on ace\",\"key\":\"module.mesh-build-node.ace.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observer\":\"mesh-delivery-watchman\",\"resolved\":\"mesh-build-node's directory on ace is the mesh's\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on ace uses mesh-build-node.cache as found\",\"v\":1}", wire: "{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on ace uses mesh-build-node.cache as found: owned by 10005:10005, mode 700, as found\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator.\",\"headline\":\"mesh-build-node waits for a directory on ace\",\"key\":\"module.mesh-build-node.ace.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observer\":\"mesh-delivery-watchman\",\"resolved\":\"mesh-build-node's directory on ace is the mesh's\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on ace uses mesh-build-node.cache as found\",\"v\":1}",
decode: func(b []byte) (any, error) { var v FindingsByKeyValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a finding of the bus scope with its zero time (in the finding layout (design 47 G 660))", subject: "", given: "{\"at\":\"0001-01-01T00:00:00Z\",\"evidence\":[{\"at\":\"0001-01-01T00:00:00Z\",\"said\":\"1 held for EVENTS.controller (counted again every 5m0s while any is held, so one delivered again or dropped ends this within it)\"}],\"id\":\"EVENTS.controller\",\"kind\":\"max-deliveries\",\"needs\":\"deliver them again or drop them, from the mesh MCP server.\",\"observer\":\"mesh-delivery-watchman\",\"scope\":\"bus\",\"severity\":\"warning\",\"since\":\"0001-01-01T00:00:00Z\",\"source\":\"S9\",\"subject\":{\"id\":\"EVENTS.controller\",\"scope\":\"bus\"},\"summary\":\"the controller's consumer on EVENTS gave up on a message; it is kept in DEAD_LETTERS until delivered again or dropped, with why\"}", wire: "{\"at\":\"0001-01-01T00:00:00Z\",\"evidence\":[{\"at\":\"0001-01-01T00:00:00Z\",\"said\":\"1 held for EVENTS.controller (counted again every 5m0s while any is held, so one delivered again or dropped ends this within it)\"}],\"id\":\"EVENTS.controller\",\"kind\":\"max-deliveries\",\"needs\":\"deliver them again or drop them, from the mesh MCP server.\",\"observer\":\"mesh-delivery-watchman\",\"scope\":\"bus\",\"severity\":\"warning\",\"since\":\"0001-01-01T00:00:00Z\",\"source\":\"S9\",\"subject\":{\"id\":\"EVENTS.controller\",\"scope\":\"bus\"},\"summary\":\"the controller's consumer on EVENTS gave up on a message; it is kept in DEAD_LETTERS until delivered again or dropped, with why\"}",
decode: func(b []byte) (any, error) {
var v FindingsByScopeIDKindValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a finding of the module scope with its machine (in the finding layout (design 47 G 660))", subject: "", given: "{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on ace uses mesh-build-node.cache as found: owned by 10005:10005, mode 700, as found\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator.\",\"headline\":\"mesh-build-node waits for a directory on ace\",\"id\":\"mesh-build-node.ace\",\"kind\":\"directory-used-as-found\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observer\":\"mesh-delivery-watchman\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-10-26T20:19:41.474823121Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on ace uses mesh-build-node.cache as found\"}", wire: "{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on ace uses mesh-build-node.cache as found: owned by 10005:10005, mode 700, as found\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator.\",\"headline\":\"mesh-build-node waits for a directory on ace\",\"id\":\"mesh-build-node.ace\",\"kind\":\"directory-used-as-found\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observer\":\"mesh-delivery-watchman\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-10-26T20:19:41.474823121Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on ace uses mesh-build-node.cache as found\"}",
decode: func(b []byte) (any, error) {
var v FindingsByScopeIDKindValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a finding owned by a session and waiting on a seat (design 47 G 678)", subject: "", given: "{\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"mesh-host.ace\",\"kind\":\"directory-used-as-found\",\"observer\":\"mesh-delivery-watchman\",\"owner\":\"jochen@shanks\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-11-11T10:00:00Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"node-engine\"}", wire: "{\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"mesh-host.ace\",\"kind\":\"directory-used-as-found\",\"observer\":\"mesh-delivery-watchman\",\"owner\":\"jochen@shanks\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-11-11T10:00:00Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"node-engine\"}",
decode: func(b []byte) (any, error) {
var v FindingsByScopeIDKindValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a finding owned by a seat and waiting on a commit's author (design 47 G 678, 540)", subject: "", given: "{\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"mesh-host.ace\",\"kind\":\"directory-used-as-found\",\"observer\":\"mesh-delivery-watchman\",\"owner\":\"node-engine\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-11-11T10:00:00Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"forge:jochen\"}", wire: "{\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"mesh-host.ace\",\"kind\":\"directory-used-as-found\",\"observer\":\"mesh-delivery-watchman\",\"owner\":\"node-engine\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-11-11T10:00:00Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"forge:jochen\"}",
decode: func(b []byte) (any, error) {
var v FindingsByScopeIDKindValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
}
for _, c := range cases {
v, err := c.decode([]byte(c.given))
@@ -20,7 +20,8 @@ const (
)
const (
EventHealerActed = "healer-acted"
EventHealerActed = "healer-acted"
EventAttemptChanged = "attempt-changed"
)
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
@@ -30,7 +31,7 @@ func Verbs() []string {
// Events is every event published on the seat's subject: a new copy each call.
func Events() []string {
return []string{EventHealerActed}
return []string{EventHealerActed, EventAttemptChanged}
}
// Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects.
@@ -42,6 +43,17 @@ const (
// SubjectHealerActed is where healer-acted is published.
const SubjectHealerActed = "mesh.seat.mesh-healers.event.healer-acted"
// StreamAttemptChanged is the stream attempt-changed is kept in.
const StreamAttemptChanged = "SEAT_mesh-healers_attempt-changed"
// SubjectAttemptChanged is where attempt-changed is published, on mesh.seat.mesh-healers.event.attempt-changed.<condition>.<attempt>.
func SubjectAttemptChanged(condition, attempt string) string {
return "mesh.seat.mesh-healers.event.attempt-changed." + condition + "." + attempt
}
// SubjectAttemptChangedAll hears every attempt-changed.
const SubjectAttemptChangedAll = "mesh.seat.mesh-healers.event.attempt-changed.*.*"
const (
StateAttempts = "attempts"
StateWould = "would"
@@ -165,6 +177,32 @@ const (
HealerActedEventOutcomeEscalated = "escalated"
)
// AttemptChangedEvent is the body of the event attempt-changed.
type AttemptChangedEvent struct {
Act string `json:"act,omitempty"`
Begun *time.Time `json:"begun,omitempty"`
// what the budget counts by: the condition's key unless the healer says another
BudgetKey string `json:"budget-key,omitempty"`
Condition string `json:"condition,omitempty"`
Finished *time.Time `json:"finished,omitempty"`
Healer string `json:"healer,omitempty"`
Kind string `json:"kind,omitempty"`
Outcome string `json:"outcome,omitempty"`
// true when the healer ends the attempt
Removed bool `json:"removed,omitempty"`
Said string `json:"said,omitempty"`
// the end of that key's lifetime, which the keeper drops the attempt at on its own clock
Until *time.Time `json:"until,omitempty"`
}
// The values of AttemptChangedEvent's outcome.
const (
AttemptChangedEventOutcomeActing = "acting"
AttemptChangedEventOutcomeActed = "acted"
AttemptChangedEventOutcomeFailed = "failed"
AttemptChangedEventOutcomeEscalated = "escalated"
)
// AttemptsByAttemptValue is one value of the state attempts under the key <attempt>.
type AttemptsByAttemptValue struct {
Act string `json:"act"`
@@ -17,6 +17,10 @@ func TestFixtures(t *testing.T) {
decode func([]byte) (any, error)
subjectIs *regexp.Regexp
}{
{name: "an attempt begun, said with its end (design 47 G 665)", subject: "mesh.seat.mesh-healers.event.attempt-changed.c-1.a-1", given: "{\"act\":\"hand-over\",\"begun\":\"2026-11-11T10:00:00Z\",\"budget-key\":\"module.mesh-host.ace.directory-used-as-found\",\"condition\":\"module.mesh-host.ace.directory-used-as-found\",\"healer\":\"directory\",\"kind\":\"directory-used-as-found\",\"outcome\":\"acting\",\"until\":\"2026-11-11T12:00:00Z\"}", wire: "{\"act\":\"hand-over\",\"begun\":\"2026-11-11T10:00:00Z\",\"budget-key\":\"module.mesh-host.ace.directory-used-as-found\",\"condition\":\"module.mesh-host.ace.directory-used-as-found\",\"healer\":\"directory\",\"kind\":\"directory-used-as-found\",\"outcome\":\"acting\",\"until\":\"2026-11-11T12:00:00Z\"}",
decode: func(b []byte) (any, error) { var v AttemptChangedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-healers\\.event\\.attempt-changed\\.[^.*> ]+\\.[^.*> ]+$")},
{name: "an attempt ended by its healer", subject: "mesh.seat.mesh-healers.event.attempt-changed.c-1.a-1", given: "{\"condition\":\"module.mesh-host.ace.directory-used-as-found\",\"removed\":true}", wire: "{\"condition\":\"module.mesh-host.ace.directory-used-as-found\",\"removed\":true}",
decode: func(b []byte) (any, error) { var v AttemptChangedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-healers\\.event\\.attempt-changed\\.[^.*> ]+\\.[^.*> ]+$")},
{name: "an act a healer would have made, kept from the controller's time (captured)", subject: "", given: "{\"act\":\"assert the module's buckets, logs and streams again through the grants part (grants_assert)\",\"at\":\"2026-11-02T08:15:56.49507336Z\",\"condition\":\"bus.EVENTS.4fs9xzyDY7CD2Cf1wVFYDK_1.consumer-lost\",\"healer\":\"H3\",\"kind\":\"consumer-lost\",\"why\":\"the controller's lease names a controller (or none is written, so one may take it): every act is written as would\"}", wire: "{\"act\":\"assert the module's buckets, logs and streams again through the grants part (grants_assert)\",\"at\":\"2026-11-02T08:15:56.49507336Z\",\"condition\":\"bus.EVENTS.4fs9xzyDY7CD2Cf1wVFYDK_1.consumer-lost\",\"healer\":\"H3\",\"kind\":\"consumer-lost\",\"why\":\"the controller's lease names a controller (or none is written, so one may take it): every act is written as would\"}",
decode: func(b []byte) (any, error) { var v WouldByWouldValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
}
+10 -40
View File
@@ -41,11 +41,11 @@ export declare function conditionsByKeyKey(key: string): string;
/** The parts of a key of the form "<key>", or undefined for a key of another form. */
export declare function parseConditionsByKeyKey(_key: string): { key: string } | undefined;
/** The key of one value of the state findings under the form "<key>". */
export declare function findingsByKeyKey(key: string): string;
/** The key of one value of the state findings under the form "<scope>.<id>.<kind>". */
export declare function findingsByScopeIDKindKey(scope: string, id: string, kind: string): string;
/** The parts of a key of the form "<key>", or undefined for a key of another form. */
export declare function parseFindingsByKeyKey(_key: string): { key: string } | undefined;
/** The parts of a key of the form "<scope>.<id>.<kind>", or undefined for a key of another form. */
export declare function parseFindingsByScopeIDKindKey(_key: string): { scope: string; id: string; kind: string } | undefined;
/** The key of one value of the state findings-views under the form "<observer>.<node>". */
export declare function findingsViewsByObserverNodeKey(observer: string, node: string): string;
@@ -154,7 +154,7 @@ export interface Finding {
"needs"?: string;
/** the module that wrote it */
"observer": string;
/** the module the finding is about, when not the observer */
/** who the finding is about, when not the observer: a session who@node (^[a-z0-9][a-z0-9._-]*@[a-z0-9][a-z0-9-]*$), a seat's name (^[a-z][a-z0-9-]*$) or a commit's author forge:<account> (^forge:[A-Za-z0-9._-]+$); another is refused (design 47 G 678) */
"owner"?: string;
/** the key's last part when it is not the kind */
"ref"?: string;
@@ -172,7 +172,7 @@ export interface Finding {
"subject": Subject;
/** one line */
"summary": string;
/** what it waits on: a person, a part or a call */
/** what it waits on: a session who@node (^[a-z0-9][a-z0-9._-]*@[a-z0-9][a-z0-9-]*$), a seat's name (^[a-z][a-z0-9-]*$) or a commit's author forge:<account> (^forge:[A-Za-z0-9._-]+$); another is refused (design 47 G 678) */
"waiting-on"?: string;
}
@@ -200,6 +200,8 @@ export interface FindingsStated {
"asked"?: string;
"at": string;
"findings": Finding[];
/** the finding kinds the stating bundle owns and states, the only kinds whose keys the statement deletes and whose conditions the keeper clears when it names none of them; none covers every kind, as a part with one bundle states (design 47 G 580) */
"kinds"?: string[];
/** the keys the observer cannot judge yet since its start, with their since and bound */
"pending": FindingsStatedPendingItem[];
}
@@ -318,40 +320,8 @@ export interface ConditionsByKeyValue {
"tried"?: ConditionsByKeyValueTriedItem[];
}
/** is one value of the state findings under the key <key>. */
export interface FindingsByKeyValue {
"actions"?: Action[];
"at": string;
/** newest first, at most 10 */
"evidence"?: Evidence[];
"explanation"?: string;
"headline"?: string;
/** scope.id.token */
"key": string;
"kind": string;
"needs"?: string;
/** the module that wrote it */
"observer": string;
/** the module the finding is about, when not the observer */
"owner"?: string;
/** the key's last part when it is not the kind */
"ref"?: string;
"resolved"?: string;
/** self, operator, agent or healer:<name> */
"resolver"?: string;
/** what of its own saw it last */
"run"?: string;
"severity": "urgent" | "warning";
/** the row, probe or event that saw it */
"source": string;
"subject": Subject;
/** one line */
"summary": string;
/** layout version, 1 */
"v": number;
/** what it waits on: a person, a part or a call */
"waiting-on"?: string;
}
/** is one value of the state findings under the key <scope>.<id>.<kind>. */
export type FindingsByScopeIDKindValue = Finding;
/** the last findings-stated set */
export interface FindingsViewsByObserverNodeValueStated {
@@ -50,19 +50,27 @@ export function parseConditionsByKeyKey(_key) {
return { key };
}
/** The key of one value of the state findings under the form "<key>". */
export function findingsByKeyKey(key) {
return key;
/** The key of one value of the state findings under the form "<scope>.<id>.<kind>". */
export function findingsByScopeIDKindKey(scope, id, kind) {
return scope + "." + id + "." + kind;
}
/** The parts of a key of the form "<key>", or undefined for a key of another form. */
export function parseFindingsByKeyKey(_key) {
/** The parts of a key of the form "<scope>.<id>.<kind>", or undefined for a key of another form. */
export function parseFindingsByScopeIDKindKey(_key) {
let _rest = _key;
const _at0 = _rest.indexOf(".");
if (_at0 <= 0) return undefined;
const scope = _rest.slice(0, _at0);
_rest = _rest.slice(_at0 + 1);
const _at2 = _rest.indexOf(".");
if (_at2 <= 0) return undefined;
const id = _rest.slice(0, _at2);
_rest = _rest.slice(_at2 + 1);
if (_rest === "") return undefined;
const key = _rest;
const kind = _rest;
_rest = "";
if (_rest !== "") return undefined;
return { key };
return { scope, id, kind };
}
/** The key of one value of the state findings-views under the form "<observer>.<node>". */
+25
View File
@@ -12,12 +12,19 @@ export declare const verbs: {
export declare const events: {
readonly healerActed: "healer-acted";
readonly attemptChanged: "attempt-changed";
};
export declare const family: { readonly name: "mesh-healers.events"; readonly maxAgeDays: 7 };
export declare const healerActedSubject: "mesh.seat.mesh-healers.event.healer-acted";
export declare const attemptChangedStream: "SEAT_mesh-healers_attempt-changed";
export declare function attemptChangedSubject(condition: string, attempt: string): string;
export declare const attemptChangedSubjectAll: "mesh.seat.mesh-healers.event.attempt-changed.*.*";
export declare const states: {
readonly attempts: "attempts";
readonly would: "would";
@@ -83,6 +90,24 @@ export interface HealerActedEvent {
"show"?: string;
}
/** is the body of the event attempt-changed. */
export interface AttemptChangedEvent {
"act"?: string;
"begun"?: string;
/** what the budget counts by: the condition's key unless the healer says another */
"budget-key"?: string;
"condition"?: string;
"finished"?: string;
"healer"?: string;
"kind"?: string;
"outcome"?: "acting" | "acted" | "failed" | "escalated";
/** true when the healer ends the attempt */
"removed"?: boolean;
"said"?: string;
/** the end of that key's lifetime, which the keeper drops the attempt at on its own clock */
"until"?: string;
}
/** is one value of the state attempts under the key <attempt>. */
export interface AttemptsByAttemptValue {
"act": string;
@@ -12,12 +12,21 @@ export const verbs = {
export const events = {
healerActed: "healer-acted",
attemptChanged: "attempt-changed",
};
export const family = { name: "mesh-healers.events", maxAgeDays: 7 };
export const healerActedSubject = "mesh.seat.mesh-healers.event.healer-acted";
export const attemptChangedStream = "SEAT_mesh-healers_attempt-changed";
export function attemptChangedSubject(condition, attempt) {
return "mesh.seat.mesh-healers.event.attempt-changed." + condition + "." + attempt;
}
export const attemptChangedSubjectAll = "mesh.seat.mesh-healers.event.attempt-changed.*.*";
export const states = {
attempts: "attempts",
would: "would",
@@ -389,6 +389,8 @@ type HistoryAnswer = map[string]any
type NotifyInput struct {
// it is over
Clear bool `json:"clear,omitempty"`
// send it on every channel's send, as a break-glass act is announced, not on the operator's chosen channel alone (design 47 G 514)
EveryChannel bool `json:"every-channel,omitempty"`
// one or two plain sentences
Explanation string `json:"explanation,omitempty"`
// plain words
@@ -524,6 +526,8 @@ type AsksByAskerIDValue struct {
Asker string `json:"asker"`
// its asker has been told how it ended
AskerTold bool `json:"asker_told,omitempty"`
// when it ended, RFC 3339; present once it ended, a refused ask's refusal time (design 47 G 570, 638)
At string `json:"at,omitempty"`
// when it ends unanswered, RFC 3339
Expires string `json:"expires"`
// its headline
@@ -37,6 +37,10 @@ func TestFixtures(t *testing.T) {
decode: func(b []byte) (any, error) { var v AsksByAskerIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a fourth ask, the asker's oldest held back behind newer ones", subject: "", given: "{\"asker\":\"mesh-delivery-teller\",\"expires\":\"2026-11-11T10:00:00Z\",\"headline\":\"Check failed - merge into main\",\"held-since\":\"2026-11-10T10:30:00Z\",\"key\":\"mesh-delivery-teller.b-1\",\"opened\":\"2026-11-10T10:00:00Z\",\"state\":\"held-back\"}", wire: "{\"asker\":\"mesh-delivery-teller\",\"expires\":\"2026-11-11T10:00:00Z\",\"headline\":\"Check failed - merge into main\",\"held-since\":\"2026-11-10T10:30:00Z\",\"key\":\"mesh-delivery-teller.b-1\",\"opened\":\"2026-11-10T10:00:00Z\",\"state\":\"held-back\"}",
decode: func(b []byte) (any, error) { var v AsksByAskerIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "an ask the router refused at take-in, closed (design 47 G 570)", subject: "", given: "{\"answer\":\"refused: its expiry passes the bound of its highest option level\",\"asker\":\"nats\",\"asker_told\":true,\"at\":\"2026-11-11T10:00:01Z\",\"expires\":\"2026-11-12T10:00:00Z\",\"headline\":\"Recreate a bus entry\",\"key\":\"nats.r-1\",\"opened\":\"2026-11-11T10:00:00Z\",\"state\":\"refused\"}", wire: "{\"answer\":\"refused: its expiry passes the bound of its highest option level\",\"asker\":\"nats\",\"asker_told\":true,\"at\":\"2026-11-11T10:00:01Z\",\"expires\":\"2026-11-12T10:00:00Z\",\"headline\":\"Recreate a bus entry\",\"key\":\"nats.r-1\",\"opened\":\"2026-11-11T10:00:00Z\",\"state\":\"refused\"}",
decode: func(b []byte) (any, error) { var v AsksByAskerIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "notify asks the message sent on every channel", subject: "", given: "{\"every-channel\":true,\"key\":\"widens.mesh-delivery\",\"severity\":\"warning\",\"subject\":\"mesh-delivery\",\"summary\":\"a change widens what a module may do\"}", wire: "{\"every-channel\":true,\"key\":\"widens.mesh-delivery\",\"severity\":\"warning\",\"subject\":\"mesh-delivery\",\"summary\":\"a change widens what a module may do\"}",
decode: func(b []byte) (any, error) { var v NotifyInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
}
for _, c := range cases {
v, err := c.decode([]byte(c.given))
@@ -305,6 +305,8 @@ export type HistoryAnswer = Record<string, unknown>;
export interface NotifyInput {
/** it is over */
"clear"?: boolean;
/** send it on every channel's send, as a break-glass act is announced, not on the operator's chosen channel alone (design 47 G 514) */
"every-channel"?: boolean;
/** one or two plain sentences */
"explanation"?: string;
/** plain words */
@@ -424,6 +426,8 @@ export interface AsksByAskerIDValue {
"asker": string;
/** its asker has been told how it ended */
"asker_told"?: boolean;
/** when it ended, RFC 3339; present once it ended, a refused ask's refusal time (design 47 G 570, 638) */
"at"?: string;
/** when it ends unanswered, RFC 3339 */
"expires": string;
/** its headline */
+9
View File
@@ -183,8 +183,17 @@ export interface ListInput {
"module"?: string;
}
/** the sum of the bus's caps against its limits, in MiB; an absent limit is unlimited (design 47 G 547, 661) */
export interface ListAnswerCaps {
"account-limit-mib"?: number;
"server-limit-mib"?: number;
"sum-mib": number;
}
/** is what list answers. */
export interface ListAnswer {
/** the sum of the bus's caps against its limits, in MiB; an absent limit is unlimited (design 47 G 547, 661) */
"caps"?: ListAnswerCaps;
/** every entry */
"entries": Held[];
}
+330
View File
@@ -0,0 +1,330 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
// The contract of the seat "mesh-recovery", defined whole (seats/mesh-recovery/seat.json): Takes, sets aside and restores a module's kept data on the live bus, each act in the hand-act log (design 47 G 554). Its verbs answer at once and the result is the event.
export declare const seat: "mesh-recovery";
export declare const verbs: {
readonly snapshotKept: "snapshot-kept";
readonly restoreKept: "restore-kept";
readonly keptPoints: "kept-points";
readonly restoreAside: "restore-aside";
readonly cancel: "cancel";
};
export declare const events: {
readonly ended: "ended";
readonly restoreStarted: "restore-started";
readonly restored: "restored";
readonly setAside: "set-aside";
readonly asideDiscarded: "aside-discarded";
readonly pointRemoved: "point-removed";
readonly expiryEnded: "expiry-ended";
};
export declare const family: { readonly name: "mesh-recovery.events"; readonly maxAgeDays: 7 };
export declare const endedStream: "SEAT_mesh-recovery_ended";
export declare function endedSubject(module: string, kept: string): string;
export declare const endedSubjectAll: "mesh.seat.mesh-recovery.event.ended.*.*";
export declare const restoreStartedStream: "SEAT_mesh-recovery_restore-started";
export declare function restoreStartedSubject(module: string, kept: string): string;
export declare const restoreStartedSubjectAll: "mesh.seat.mesh-recovery.event.restore-started.*.*";
export declare const restoredStream: "SEAT_mesh-recovery_restored";
export declare function restoredSubject(module: string, kept: string): string;
export declare const restoredSubjectAll: "mesh.seat.mesh-recovery.event.restored.*.*";
export declare const setAsideStream: "SEAT_mesh-recovery_set-aside";
export declare function setAsideSubject(module: string, kept: string): string;
export declare const setAsideSubjectAll: "mesh.seat.mesh-recovery.event.set-aside.*.*";
export declare const asideDiscardedStream: "SEAT_mesh-recovery_aside-discarded";
export declare function asideDiscardedSubject(module: string, kept: string): string;
export declare const asideDiscardedSubjectAll: "mesh.seat.mesh-recovery.event.aside-discarded.*.*";
export declare const pointRemovedStream: "SEAT_mesh-recovery_point-removed";
export declare function pointRemovedSubject(module: string, kept: string): string;
export declare const pointRemovedSubjectAll: "mesh.seat.mesh-recovery.event.point-removed.*.*";
export declare const expiryEndedStream: "SEAT_mesh-recovery_expiry-ended";
export declare const expiryEndedSubject: "mesh.seat.mesh-recovery.event.expiry-ended";
/** the live data set aside before a restore: a directory under aside/ in the recovery module's directory, nothing of it on the bus (design 47 G 516) */
export interface Aside {
/** the directory's name */
"id": string;
/** the bus name it was live under */
"name": string;
/** its bytes on disk */
"size": number;
}
/** the catch-up of the newer manifest: a tool and its arguments, never text (design 47 G 451, 516, 631) */
export interface CatchUp {
"arguments"?: Record<string, unknown>;
"tool": string;
}
/** one durable consumer of the kept data and the sequence it had acknowledged */
export interface Consumer {
"name": string;
"sequence": number;
"stream": string;
}
/** the digest of one object of a point's bytes */
export interface Digest {
"digest": string;
"name": string;
}
/** a restore point: the kept data whole and its consumers' positions, in the recovery module's points directory (design 47 G 516, 560) */
export interface Point {
/** when it was taken, RFC 3339 */
"at": string;
"catch-up"?: CatchUp;
"consumers"?: Consumer[];
/** the digest of each object's bytes, checked before a restore (design 47 G 567) */
"digests"?: Digest[];
/** the fingerprint it was taken before */
"fingerprint": string;
/** its form, the number the holder raises when it changes how it writes it (0 when undeclared) */
"form": number;
/** the kept data, its local name in the module's manifest */
"kept": string;
/** the module */
"module": string;
/** its bytes */
"size": number;
/** the point's name, also its directory's */
"snapshot": string;
}
/** the act that runs, since when and what it waits on (design 47 G 517, 554) */
export interface Running {
"act": string;
"since": string;
"step"?: string;
/** what it waits on, when it waits */
"waits-on"?: string;
}
/** is what snapshot-kept takes. */
export interface SnapshotKeptInput {
"catch-up"?: CatchUp;
/** the fingerprint the restore point is taken before, or reverted from */
"fingerprint": string;
/** its form, the number the holder raises when it changes how it writes it (0 when undeclared) */
"form": number;
/** the kept data, its local name in the module's manifest */
"kept": string;
/** the module */
"module": string;
/** why, which the hand-act log keeps */
"why": string;
}
/** is what snapshot-kept answers. */
export interface SnapshotKeptAnswer {
/** accepted: the act's id, which the ending event carries */
"accepted"?: string;
/** busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event */
"busy"?: string;
/** why it was not taken; set alone */
"error"?: string;
}
/** is what restore-kept takes. */
export interface RestoreKeptInput {
/** the fingerprint the restore point is taken before, or reverted from */
"fingerprint": string;
/** its form, the number the holder raises when it changes how it writes it (0 when undeclared) */
"form": number;
/** ask the backup holder for the points directory when the point is missing there (design 47 G 516) */
"from-backup"?: boolean;
/** the kept data, its local name in the module's manifest */
"kept": string;
/** the module */
"module": string;
/** the restore point's name */
"snapshot"?: string;
/** why, which the hand-act log keeps */
"why": string;
}
/** is what restore-kept answers. */
export interface RestoreKeptAnswer {
/** accepted: the act's id, which the ending event carries */
"accepted"?: string;
/** busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event */
"busy"?: string;
/** why it was not taken; set alone */
"error"?: string;
}
/** is what kept-points takes. */
export interface KeptPointsInput {
/** one kept data (optional) */
"kept"?: string;
/** one module (optional) */
"module"?: string;
}
/** is what kept-points answers. */
export interface KeptPointsAnswer {
"asides": Aside[];
"points": Point[];
"running"?: Running;
}
/** is what restore-aside takes. */
export interface RestoreAsideInput {
/** the aside's id */
"id": string;
/** the kept data, its local name in the module's manifest */
"kept": string;
/** the module */
"module": string;
/** why, which the hand-act log keeps */
"why": string;
}
/** is what restore-aside answers. */
export interface RestoreAsideAnswer {
/** accepted: the act's id, which the ending event carries */
"accepted"?: string;
/** busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event */
"busy"?: string;
/** why it was not taken; set alone */
"error"?: string;
}
/** is what cancel takes. */
export interface CancelInput {
/** the act's id */
"act": string;
/** why, which the hand-act log keeps */
"why": string;
}
/** is what cancel answers. */
export interface CancelAnswer {
/** the act ended cancelled */
"cancelled"?: boolean;
/** why not, naming the step an act has begun; set alone */
"error"?: string;
}
/** is the body of the event ended. */
export interface EndedEvent {
/** the id the act was answered accepted with, or a hand act's own id (design 47 G 560) */
"act": string;
/** when it was said, RFC 3339 */
"at": string;
"catch-up"?: CatchUp;
"consumers"?: Consumer[];
"digests"?: Digest[];
/** when failed */
"error"?: string;
"fingerprint"?: string;
"form"?: number;
"kept": string;
"module": string;
/** how the act ended */
"outcome": "done" | "failed";
"size"?: number;
"snapshot"?: string;
}
/** is the body of the event restore-started. */
export interface RestoreStartedEvent {
/** the id the act was answered accepted with, or a hand act's own id (design 47 G 560) */
"act": string;
/** when it was said, RFC 3339 */
"at": string;
"fingerprint"?: string;
"form"?: number;
"kept": string;
"module": string;
"snapshot"?: string;
}
/** is the body of the event restored. */
export interface RestoredEvent {
/** the id the act was answered accepted with, or a hand act's own id (design 47 G 560) */
"act": string;
"aside"?: Aside;
/** when it was said, RFC 3339 */
"at": string;
"error"?: string;
"fingerprint"?: string;
"form"?: number;
"kept": string;
/** the point is not in the recovery module's directory */
"missing"?: boolean;
"module": string;
/** how the act ended */
"outcome": "done" | "failed";
"snapshot"?: string;
}
/** is the body of the event set-aside. */
export interface SetAsideEvent {
/** the id the act was answered accepted with, or a hand act's own id (design 47 G 560) */
"act": string;
/** when it was said, RFC 3339 */
"at": string;
"id": string;
"name": string;
"size": number;
}
/** is the body of the event aside-discarded. */
export interface AsideDiscardedEvent {
/** the id the act was answered accepted with, or a hand act's own id (design 47 G 560) */
"act": string;
/** when it was said, RFC 3339 */
"at": string;
"id": string;
"name": string;
"size": number;
}
/** is the body of the event point-removed. */
export interface PointRemovedEvent {
/** the id the act was answered accepted with, or a hand act's own id (design 47 G 560) */
"act": string;
/** when it was said, RFC 3339 */
"at": string;
"fingerprint"?: string;
"form"?: number;
"kept": string;
"module": string;
"snapshot"?: string;
}
/** is the body of the event expiry-ended. */
export interface ExpiryEndedEvent {
/** the id the act was answered accepted with, or a hand act's own id (design 47 G 560) */
"act": string;
/** when it was said, RFC 3339 */
"at": string;
/** the number of points the run removed, 0 included */
"removed": number;
}
+78
View File
@@ -0,0 +1,78 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
// The contract of the seat "mesh-recovery", defined whole (seats/mesh-recovery/seat.json): Takes, sets aside and restores a module's kept data on the live bus, each act in the hand-act log (design 47 G 554). Its verbs answer at once and the result is the event.
export const seat = "mesh-recovery";
export const verbs = {
snapshotKept: "snapshot-kept",
restoreKept: "restore-kept",
keptPoints: "kept-points",
restoreAside: "restore-aside",
cancel: "cancel",
};
export const events = {
ended: "ended",
restoreStarted: "restore-started",
restored: "restored",
setAside: "set-aside",
asideDiscarded: "aside-discarded",
pointRemoved: "point-removed",
expiryEnded: "expiry-ended",
};
export const family = { name: "mesh-recovery.events", maxAgeDays: 7 };
export const endedStream = "SEAT_mesh-recovery_ended";
export function endedSubject(module, kept) {
return "mesh.seat.mesh-recovery.event.ended." + module + "." + kept;
}
export const endedSubjectAll = "mesh.seat.mesh-recovery.event.ended.*.*";
export const restoreStartedStream = "SEAT_mesh-recovery_restore-started";
export function restoreStartedSubject(module, kept) {
return "mesh.seat.mesh-recovery.event.restore-started." + module + "." + kept;
}
export const restoreStartedSubjectAll = "mesh.seat.mesh-recovery.event.restore-started.*.*";
export const restoredStream = "SEAT_mesh-recovery_restored";
export function restoredSubject(module, kept) {
return "mesh.seat.mesh-recovery.event.restored." + module + "." + kept;
}
export const restoredSubjectAll = "mesh.seat.mesh-recovery.event.restored.*.*";
export const setAsideStream = "SEAT_mesh-recovery_set-aside";
export function setAsideSubject(module, kept) {
return "mesh.seat.mesh-recovery.event.set-aside." + module + "." + kept;
}
export const setAsideSubjectAll = "mesh.seat.mesh-recovery.event.set-aside.*.*";
export const asideDiscardedStream = "SEAT_mesh-recovery_aside-discarded";
export function asideDiscardedSubject(module, kept) {
return "mesh.seat.mesh-recovery.event.aside-discarded." + module + "." + kept;
}
export const asideDiscardedSubjectAll = "mesh.seat.mesh-recovery.event.aside-discarded.*.*";
export const pointRemovedStream = "SEAT_mesh-recovery_point-removed";
export function pointRemovedSubject(module, kept) {
return "mesh.seat.mesh-recovery.event.point-removed." + module + "." + kept;
}
export const pointRemovedSubjectAll = "mesh.seat.mesh-recovery.event.point-removed.*.*";
export const expiryEndedStream = "SEAT_mesh-recovery_expiry-ended";
export const expiryEndedSubject = "mesh.seat.mesh-recovery.event.expiry-ended";
+12
View File
@@ -0,0 +1,12 @@
{
"lockfileVersion": 3,
"name": "@novox/mesh-contracts-mesh-recovery",
"packages": {
"": {
"name": "@novox/mesh-contracts-mesh-recovery",
"version": "0.1.0"
}
},
"requires": true,
"version": "0.1.0"
}
+18
View File
@@ -0,0 +1,18 @@
{
"description": "The contract of the seat mesh-recovery, generated from its definition.",
"exports": {
".": {
"default": "./index.js",
"types": "./index.d.ts"
}
},
"files": [
"index.js",
"index.d.ts",
"parts"
],
"name": "@novox/mesh-contracts-mesh-recovery",
"type": "module",
"types": "./index.d.ts",
"version": "0.1.0"
}
+94 -20
View File
@@ -18,6 +18,8 @@ export declare const verbs: {
readonly manifestReads: "manifest-reads";
readonly seatList: "seat-list";
readonly seatListAgain: "seat-list-again";
readonly firstViews: "first-views";
readonly firstViewAgain: "first-view-again";
readonly processBuilds: "process-builds";
};
@@ -28,7 +30,8 @@ export declare const events: {
readonly manifestReadsChanged: "manifest-reads-changed";
readonly composed: "composed";
readonly running: "running";
readonly capabilitiesChanged: "capabilities-changed";
readonly waitHeld: "wait-held";
readonly waitEnded: "wait-ended";
};
export declare const family: { readonly name: "node-engine.events"; readonly maxAgeDays: 7 };
@@ -51,9 +54,17 @@ export declare function runningSubject(node: string, module: string): string;
export declare const runningSubjectAll: "mesh.seat.node-engine.event.running.*.*";
export declare function capabilitiesChangedSubject(node: string): string;
export declare const waitHeldStream: "SEAT_node-engine_wait-held";
export declare const capabilitiesChangedSubjectAll: "mesh.seat.node-engine.event.capabilities-changed.*";
export declare function waitHeldSubject(node: string, module: string): string;
export declare const waitHeldSubjectAll: "mesh.seat.node-engine.event.wait-held.*.*";
export declare const waitEndedStream: "SEAT_node-engine_wait-ended";
export declare function waitEndedSubject(node: string, module: string): string;
export declare const waitEndedSubjectAll: "mesh.seat.node-engine.event.wait-ended.*.*";
export declare const states: {
readonly have: "have";
@@ -267,18 +278,6 @@ export interface CandidateRefusal {
"why": string;
}
/** a node's capabilities changed, which the assigner places by */
export interface CapabilitiesChanged {
/** when, RFC 3339 */
"at": string;
/** its capabilities now */
"capabilities": string[];
/** the node */
"node": string;
/** the layout's version */
"v": number;
}
/** an entry composed on a node (design 47 *What each step says*) */
export interface Composed {
/** when, RFC 3339 */
@@ -575,19 +574,21 @@ export interface RootVerdict {
"why": string;
}
/** a module running healthy on a node at a build: what the node-engines of other nodes and the grants part decide a seat's pass from (ADR 0660 §2), kept last per subject with no age limit */
/** a module's build running on a node, and its health: what the node-engines of other nodes and the grants part decide a seat's pass from (ADR 0660 §2), and the assigner reads a provider's and a canary's health and a put's end from (design 47 G 520); said again on the same subject each time healthy changes and for every module the have holds at the engine's start, kept last per subject with no age limit */
export interface Running {
/** when it was seen running healthy, RFC 3339 */
/** when it was said, RFC 3339 */
"at": string;
/** the fingerprint of the build it runs; none with stopped */
"fingerprint"?: string;
/** the health of the build that runs, as the node-engine's have holds it from the module's declared check (ADR 0240); a running with none, said by a node-engine of before design 47 G, reads as healthy (design 47 G 520). SeatsToPassOn reads none of it: an unhealthy holder still holds its seat */
"healthy"?: boolean | null;
/** the digest of that build's manifest, the indexer's manifest of it, read by digest from the catalog half */
"manifest"?: string;
/** the module */
"module": string;
/** the node */
"node": string;
/** true when the module no longer runs on the node: no fingerprint or manifest then (design 47 G 365, 404); mesh-sdk's SeatsToPassOn counts such a module as not running there */
/** true when the have no longer holds the module on the node, never for an unhealthy build: no fingerprint or manifest then (design 47 G 365, 404, 520); mesh-sdk's SeatsToPassOn counts such a module as not running there */
"stopped"?: boolean;
/** the layout's version */
"v": number;
@@ -669,6 +670,31 @@ export interface UserListWritten {
"wanted": string;
}
/** a hold or wait a node-engine says, the fields of the seat mesh-wants' wait (design 47 G 498, 512, 629); in the have it also carries ask-at and act (design 47 G 560) */
export interface Wait {
/** in the have: the act the recovery module answered accepted with (design 47 G 560) */
"act"?: string;
/** in the have: the time of the first ask of the recovery module for this wait (design 47 G 560) */
"ask-at"?: string;
/** a duration such as 30m */
"bound"?: string;
/** the time the bound counts from (design 47 G 266, 269, 498) */
"clock-from"?: string;
/** the node waited on: the build node for a kept build; the subject's <node> is the node that says it */
"node"?: string;
/** what it waits on */
"on"?: string;
/** the seat holding the wait: node-engine */
"owner"?: string;
/** why it holds, such as restore point missing, entry-missing, parts-differ, node away or build no longer kept (design 47 G 541, 499) */
"reason"?: string;
"since": string;
/** compose: a hold of the composition (a restore point missing, an entry missing, parts that differ, a recovery that waits); kept-build: waiting for a kept build (design 47 G 499, 541) */
"step": "compose" | "kept-build";
/** the wait said in words */
"words"?: string;
}
/** is what hand-over takes. */
export interface HandOverInput {
/** the directory's absolute path, as the engine states it */
@@ -991,6 +1017,50 @@ export interface SeatListAgainInput {
/** is what seat-list-again answers. */
export type SeatListAgainAnswer = SeatList;
/** is what first-views takes. */
export interface FirstViewsInput {
}
/** is what first-views answers. */
export interface FirstViewsAnswer {
/** one status per owner and state followed, as mesh-sdk's views runtime keeps it */
"first-views": Record<string, unknown>[];
}
/** is what first-view-again takes. */
export interface FirstViewAgainInput {
/** the owner module, as first-views names it */
"owner": string;
/** the owner's state, as first-views names it */
"state": string;
/** why it is asked again, in plain words */
"why": string;
}
/** the hand ask written */
export interface FirstViewAgainAnswerAsked {
/** when it was asked, RFC 3339 */
"at": string;
/** the caller the runtime proved */
"by": string;
/** the owner module */
"owner": string;
/** the owner's state */
"state": string;
/** the layout's version */
"v": number;
/** why it was asked again */
"why": string;
}
/** is what first-view-again answers. */
export interface FirstViewAgainAnswer {
/** the hand ask written */
"asked": FirstViewAgainAnswerAsked;
/** the verb that reads the status, first-views */
"read": string;
}
/** is what process-builds takes. */
export interface ProcessBuildsInput {
/** only this module's processes */
@@ -1018,8 +1088,11 @@ export type ComposedEvent = Composed;
/** is the body of the event running. */
export type RunningEvent = Running;
/** is the body of the event capabilities-changed. */
export type CapabilitiesChangedEvent = CapabilitiesChanged;
/** is the body of the event wait-held. */
export type WaitHeldEvent = Wait;
/** is the body of the event wait-ended. */
export type WaitEndedEvent = Wait;
/** is one value of the state have under the key <node>.<module>. */
export interface HaveByNodeModuleValue {
@@ -1047,6 +1120,7 @@ export interface HaveByNodeModuleValue {
"sealed-at"?: Record<string, string>;
"user-list"?: UserListWritten;
"v": number;
"wait"?: Wait;
/** in phase waiting-yes, the digest of the composition that waits */
"waits"?: string;
}
+17 -4
View File
@@ -18,6 +18,8 @@ export const verbs = {
manifestReads: "manifest-reads",
seatList: "seat-list",
seatListAgain: "seat-list-again",
firstViews: "first-views",
firstViewAgain: "first-view-again",
processBuilds: "process-builds",
};
@@ -28,7 +30,8 @@ export const events = {
manifestReadsChanged: "manifest-reads-changed",
composed: "composed",
running: "running",
capabilitiesChanged: "capabilities-changed",
waitHeld: "wait-held",
waitEnded: "wait-ended",
};
export const family = { name: "node-engine.events", maxAgeDays: 7 };
@@ -63,11 +66,21 @@ export function runningSubject(node, module) {
export const runningSubjectAll = "mesh.seat.node-engine.event.running.*.*";
export function capabilitiesChangedSubject(node) {
return "mesh.seat.node-engine.event.capabilities-changed." + node;
export const waitHeldStream = "SEAT_node-engine_wait-held";
export function waitHeldSubject(node, module) {
return "mesh.seat.node-engine.event.wait-held." + node + "." + module;
}
export const capabilitiesChangedSubjectAll = "mesh.seat.node-engine.event.capabilities-changed.*";
export const waitHeldSubjectAll = "mesh.seat.node-engine.event.wait-held.*.*";
export const waitEndedStream = "SEAT_node-engine_wait-ended";
export function waitEndedSubject(node, module) {
return "mesh.seat.node-engine.event.wait-ended." + node + "." + module;
}
export const waitEndedSubjectAll = "mesh.seat.node-engine.event.wait-ended.*.*";
export const states = {
have: "have",