R-4: SeatsToPassOn resolves each assignment's build itself, with one fallback

Builds: design 47 G §199, §404, §459

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 12:23:16 +01:00
parent 19fb1dce58
commit e0f9356bda
2 changed files with 139 additions and 21 deletions
+67 -6
View File
@@ -5,14 +5,36 @@ import (
"sort"
)
// An assignment as the passing of a seat reads it: a module on a machine, with the manifest of the build that machine
// runs, else the trunk's (novox/hq ADR 0660 §2).
// An assignment as the passing of a seat reads it: a module on a node, with its placement's watched branch (design 47 G
// §459). The build it runs is resolved by SeatsToPassOn from what the caller heard and its reader of entries.
type Assignment struct {
Node string
Module string
// Branch is the watched branch of the assignment's placement, whose newest entry of the module is taken where the node
// said no `running` for it (§199).
Branch string
}
// Heard is what the caller heard of a module on a node from the node-engine seat's `running`: a fingerprint, or Stopped
// (`stopped: true` and no fingerprint, §404). Nothing heard since its consumer began is no Heard at all.
type Heard struct {
Fingerprint string
Stopped bool
}
// EntryReader is the caller's view of the catalog half (design 47 G §459, §443): the entry's manifest by its
// fingerprint, and a module's entry on a watched branch's newest revision. ok is false where it answers no entry.
type EntryReader interface {
ByFingerprint(fingerprint string) (m Manifest, ok bool)
OnBranch(module, branch string) (m Manifest, ok bool)
}
// resolved is an assignment with the manifest of the build it runs. Unread is one whose entry the reader did not answer:
// it is still assigned, so its holdings stand, and it claims nothing.
type resolved struct {
Node string
Manifest Manifest
// Unread is an assignment whose manifest could not be read at all: it is still assigned, so its holdings stand,
// and it claims nothing.
Unread bool
Unread bool
}
// SeatPass is one seat whose holder on record has left it, and the only assignment that can take it.
@@ -33,7 +55,46 @@ type SeatPass struct {
// be read. Only mesh seats held once are read: a replicated seat's holders are added by an act (ADR 0223), a kinded
// bench is held by its claims, and a seat with several holders on record is a store that disagrees with the mesh.
// Nothing here records a pass (ADR 0660 §4).
func SeatsToPassOn(holdings []Held, assignments []Assignment) ([]SeatPass, []string) {
//
// The assignments' builds are resolved here, by one rule for both callers (design 47 G §199, §459, §404): the heard
// fingerprint's entry; with nothing heard, the module's entry on its placement's branch; a `stopped` module claims nothing
// on that node; and where the reader answers no entry, no seat that assignment could claim passes, the holder on record
// stays, and the answer says it, naming the node, the module and the fingerprint. The function reads no bus.
func SeatsToPassOn(holdings []Held, assignments []Assignment, heard func(node, module string) (Heard, bool),
entries EntryReader) ([]SeatPass, []string) {
var rs []resolved
var said []string
for _, a := range assignments {
h, ok := Heard{}, false
if heard != nil {
h, ok = heard(a.Node, a.Module)
}
if ok && h.Stopped {
rs = append(rs, resolved{Node: a.Node, Manifest: Manifest{Module: a.Module}})
continue
}
var m Manifest
var found bool
fp := "none heard, the newest on " + a.Branch
if ok {
fp = h.Fingerprint
m, found = entries.ByFingerprint(h.Fingerprint)
} else {
m, found = entries.OnBranch(a.Module, a.Branch)
}
if !found {
said = append(said, fmt.Sprintf("no entry is answered for %s on %s (%s): no seat it could claim passes and the holder on record stays",
a.Module, a.Node, fp))
rs = append(rs, resolved{Node: a.Node, Manifest: Manifest{Module: a.Module}, Unread: true})
continue
}
rs = append(rs, resolved{Node: a.Node, Manifest: m})
}
passes, more := seatsToPass(holdings, rs)
return passes, append(said, more...)
}
func seatsToPass(holdings []Held, assignments []resolved) ([]SeatPass, []string) {
bySeat := map[string][]Held{}
var order []string
for _, h := range holdings {
+72 -15
View File
@@ -1,6 +1,7 @@
package catalogue
import (
"fmt"
"strings"
"testing"
)
@@ -27,6 +28,51 @@ func recorded(node, module string) []Held {
return []Held{{Claim: "mesh-broker", Scope: ScopeMesh, Node: node, Module: module}}
}
// asg is a case's assignment in the earlier form: a node with the manifest of the build it runs, which the harness
// hears as a fingerprint and serves through a reader of entries; Unread is an entry the reader does not answer.
type asg struct {
Node string
Manifest Manifest
Unread bool
// Silent is a node that said no `running`: the module's entry on its branch is taken. Stopped says it stopped.
Silent, Stopped bool
}
type entriesFixture struct{ byFP, byBranch map[string]Manifest }
func (e entriesFixture) ByFingerprint(fp string) (Manifest, bool) { m, ok := e.byFP[fp]; return m, ok }
func (e entriesFixture) OnBranch(module, branch string) (Manifest, bool) {
m, ok := e.byBranch[module+"@"+branch]
return m, ok
}
func passOn(rec []Held, as []asg) ([]SeatPass, []string) {
e := entriesFixture{map[string]Manifest{}, map[string]Manifest{}}
var assignments []Assignment
heard := map[string]Heard{}
for i, a := range as {
fp := fmt.Sprintf("fp%d", i)
assignments = append(assignments, Assignment{Node: a.Node, Module: a.Manifest.Module, Branch: "trunk"})
switch {
case a.Stopped:
heard[a.Node+"/"+a.Manifest.Module] = Heard{Stopped: true}
case a.Silent:
if !a.Unread {
e.byBranch[a.Manifest.Module+"@trunk"] = a.Manifest
}
default:
heard[a.Node+"/"+a.Manifest.Module] = Heard{Fingerprint: fp}
if !a.Unread {
e.byFP[fp] = a.Manifest
}
}
}
return SeatsToPassOn(rec, assignments, func(node, module string) (Heard, bool) {
h, ok := heard[node+"/"+module]
return h, ok
}, e)
}
// novox/hq ADR 0660 "How it is checked", and ADR 0449 §4's original case.
func TestSeatsToPassOn(t *testing.T) {
passSeat(t)
@@ -34,46 +80,57 @@ func TestSeatsToPassOn(t *testing.T) {
cases := []struct {
name string
rec []Held
as []Assignment
as []asg
to string // "node/module" of the one pass, "" for none
saidW []string
}{
{"holder assigned nowhere, one claimant (ADR 0449 §4)", rec,
[]Assignment{{Node: "novox", Manifest: broker("new")}}, "novox/new", nil},
[]asg{{Node: "novox", Manifest: broker("new")}}, "novox/new", nil},
{"holder assigned but its manifest dropped the claim", rec,
[]Assignment{{Node: "novox", Manifest: dropped("old")}, {Node: "novox", Manifest: broker("new")}}, "novox/new", nil},
[]asg{{Node: "novox", Manifest: dropped("old")}, {Node: "novox", Manifest: broker("new")}}, "novox/new", nil},
{"holder claims but cannot hold the seat", rec,
[]Assignment{{Node: "novox", Manifest: Manifest{Module: "old", Claims: []Claim{{Name: "mesh-broker", Scope: ScopeMesh}}}},
[]asg{{Node: "novox", Manifest: Manifest{Module: "old", Claims: []Claim{{Name: "mesh-broker", Scope: ScopeMesh}}}},
{Node: "ace", Manifest: broker("new")}}, "ace/new", nil},
{"the running build still claims: the holder keeps the seat", rec,
[]Assignment{{Node: "novox", Manifest: broker("old")}, {Node: "novox", Manifest: broker("new")}}, "", nil},
[]asg{{Node: "novox", Manifest: broker("old")}, {Node: "novox", Manifest: broker("new")}}, "", nil},
{"one of the holder's two nodes still claims it", rec,
[]Assignment{{Node: "novox", Manifest: dropped("old")}, {Node: "ace", Manifest: broker("old")},
[]asg{{Node: "novox", Manifest: dropped("old")}, {Node: "ace", Manifest: broker("old")},
{Node: "g14", Manifest: broker("new")}}, "", nil},
{"holder's manifest unread: still assigned", rec,
[]Assignment{{Node: "novox", Manifest: Manifest{Module: "old"}, Unread: true}, {Node: "novox", Manifest: broker("new")}}, "", nil},
[]asg{{Node: "novox", Manifest: Manifest{Module: "old"}, Unread: true}, {Node: "novox", Manifest: broker("new")}}, "", []string{"no entry is answered for old on novox"}},
{"an unread assignment is no claimant", rec,
[]Assignment{{Node: "novox", Manifest: dropped("old")}, {Node: "ace", Manifest: Manifest{Module: "new"}, Unread: true}}, "", nil},
[]asg{{Node: "novox", Manifest: dropped("old")}, {Node: "ace", Manifest: Manifest{Module: "new"}, Unread: true}}, "", []string{"no entry is answered for new on ace"}},
{"a claimant whose running manifest does not claim is not counted", rec,
[]Assignment{{Node: "novox", Manifest: dropped("old")}, {Node: "ace", Manifest: dropped("new")}}, "", nil},
[]asg{{Node: "novox", Manifest: dropped("old")}, {Node: "ace", Manifest: dropped("new")}}, "", nil},
{"no claimant keeps the record", rec,
[]Assignment{{Node: "novox", Manifest: dropped("old")}, {Node: "novox", Manifest: dropped("other")}}, "", nil},
[]asg{{Node: "novox", Manifest: dropped("old")}, {Node: "novox", Manifest: dropped("other")}}, "", nil},
{"holder gone, no claimant keeps the record", rec, nil, "", nil},
{"one module on two nodes is two claimants: none passed, said", rec,
[]Assignment{{Node: "novox", Manifest: dropped("old")}, {Node: "ace", Manifest: broker("new")}, {Node: "novox", Manifest: broker("new")}},
[]asg{{Node: "novox", Manifest: dropped("old")}, {Node: "ace", Manifest: broker("new")}, {Node: "novox", Manifest: broker("new")}},
"", []string{"old", "new on ace", "new on novox", "seat mesh-broker --to <node>/<module>"}},
{"two claimants, holder gone: said", rec,
[]Assignment{{Node: "novox", Manifest: broker("a")}, {Node: "ace", Manifest: broker("b")}},
[]asg{{Node: "novox", Manifest: broker("a")}, {Node: "ace", Manifest: broker("b")}},
"", []string{"a on novox", "b on ace", "seat mesh-broker --to <node>/<module>"}},
{"a seat with two holders on record is not read", append(rec[:1:1], Held{Claim: "mesh-broker", Scope: ScopeMesh, Node: "ace", Module: "x"}),
[]Assignment{{Node: "novox", Manifest: broker("new")}}, "", nil},
[]asg{{Node: "novox", Manifest: broker("new")}}, "", nil},
// Design 47 G §459, §199, §404.
{"nothing heard: the module's entry on its branch is taken", rec,
[]asg{{Node: "novox", Manifest: dropped("old"), Silent: true}, {Node: "ace", Manifest: broker("new"), Silent: true}}, "ace/new", nil},
{"nothing heard of the holder: its entry on the branch still claims, so it keeps the seat", rec,
[]asg{{Node: "novox", Manifest: broker("old"), Silent: true}, {Node: "ace", Manifest: broker("new")}}, "", nil},
{"a stopped holder passes no seat to itself: the one claimant takes it", rec,
[]asg{{Node: "novox", Manifest: broker("old"), Stopped: true}, {Node: "ace", Manifest: broker("new")}}, "ace/new", nil},
{"a stopped module claims nothing", rec,
[]asg{{Node: "novox", Manifest: dropped("old")}, {Node: "ace", Manifest: broker("new"), Stopped: true}}, "", nil},
{"an entry not answered keeps the holder on record and says it", rec,
[]asg{{Node: "novox", Manifest: dropped("old"), Unread: true}, {Node: "ace", Manifest: broker("new")}}, "", []string{"no entry is answered for old on novox (fp0)"}},
{"a replicated seat is never passed",
[]Held{{Claim: "mesh-dns-resolver", Scope: ScopeMesh, Node: "novox", Module: "gone"}},
[]Assignment{{Node: "novox", Manifest: Manifest{Module: "r", Claims: []Claim{{Name: "mesh-dns-resolver", Scope: ScopeMesh}}}}}, "", nil},
[]asg{{Node: "novox", Manifest: Manifest{Module: "r", Claims: []Claim{{Name: "mesh-dns-resolver", Scope: ScopeMesh}}}}}, "", nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
passes, said := SeatsToPassOn(c.rec, c.as)
passes, said := passOn(c.rec, c.as)
if c.to == "" && len(passes) != 0 || c.to != "" && len(passes) != 1 {
t.Fatalf("passes=%+v said=%v", passes, said)
}