The node-engine reads each assignment by the build its node keeps and says a seat pass in its answer (issue 1966, ADR 0660)
mesh/delivery merged: source.main moved to novox/mesh-host@6dd8e08a84ed
mesh/delivery-group group fix/1966-seat-pass-shared merged: every member is merged
mesh/check checking 60f413f9bc6a on 6dd8e08a84ed | novox/mesh-host#212 in group fix/1966-seat-pass-shared

Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
This commit is contained in:
2026-11-08 15:44:00 +01:00
parent 5108c28e22
commit 60f413f9bc
3 changed files with 54 additions and 14 deletions
+4 -1
View File
@@ -60,7 +60,8 @@ type Declared struct {
ForeseenPorts []string ForeseenPorts []string
// CoreBoxesWanted are the core credentials whose box the send must ask the vault for and keep. // CoreBoxesWanted are the core credentials whose box the send must ask the vault for and keep.
CoreBoxesWanted []CoreBoxWanted CoreBoxesWanted []CoreBoxWanted
// Passes are the seats the mesh would pass on, for the caller to record, and Said the lines found while composing. // Passes are the seats composed as passed to their only claimant (ADR 0660), recorded by nobody here, and Said the lines found
// while composing, the passes among them.
Passes Passes Passes Passes
Said []string Said []string
} }
@@ -228,6 +229,8 @@ func (c *composer) declaration(nodeName string, mode Mode) (Declared, error) {
return Declared{}, err return Declared{}, err
} }
out.Passes = passes out.Passes = passes
// A pass is composed, never recorded (ADR 0660 §4): the answer says it, with what two claimants leave unsettled.
out.Said = append(out.Said, passes.Lines()...)
for _, l := range left { for _, l := range left {
out.Said = append(out.Said, l.String()) out.Said = append(out.Said, l.String())
} }
+35 -12
View File
@@ -25,9 +25,10 @@ func unresolvable(err error) bool {
return errors.As(err, &n) return errors.As(err, &n)
} }
// Passes is what a composition found that the mesh must record, and did not: each seat held once whose holder on record // Passes is what a composition found and composed with: each seat held once whose holder on record has left it (assigned
// is assigned nowhere that exactly one claimant takes over (novox/hq ADR 0449 clause 4), and the lines that say what was // nowhere, or none of its assignments' manifests claims the seat) that exactly one claimant takes over (novox/hq ADR 0449
// found. The caller records a pass and writes its hand act; this package does neither. // clause 4, widened by ADR 0660), and the lines that say what was found. A pass is composed and said, never recorded: neither
// this package nor its caller writes the holdings (ADR 0660 clause 4).
type Passes struct { type Passes struct {
// Passes are the seats to pass, in the order they were found. // Passes are the seats to pass, in the order they were found.
Passes []catalogue.SeatPass Passes []catalogue.SeatPass
@@ -92,8 +93,8 @@ func (c *composer) theRestOfTheMeshNow(exclude string) (catalogue.World, Passes,
pins map[string]catalogue.Chosen pins map[string]catalogue.Chosen
} }
var others []candidate var others []candidate
// Every machine's assignments, before the holdings are read by any resolution (novox/hq ADR 0449 clause 4): a seat // Every machine's assignments, before the holdings are read by any resolution (novox/hq ADR 0449 clause 4, ADR 0660): a
// held once whose holder on record is assigned on no machine passes to its only claimant here. // seat held once whose holder on record has left it passes to its only claimant here, composed and not recorded.
everyAssigned := map[string][]string{} everyAssigned := map[string][]string{}
for _, n := range nodes { for _, n := range nodes {
if len(n.Assigned) > 0 { if len(n.Assigned) > 0 {
@@ -101,7 +102,7 @@ func (c *composer) theRestOfTheMeshNow(exclude string) (catalogue.World, Passes,
} }
} }
var passes Passes var passes Passes
holdings, passes = passSeatsOn(holdings, assignmentsOf(shelf, everyAssigned)) holdings, passes = passSeatsOn(holdings, c.assignmentsOf(shelf, everyAssigned))
for _, n := range nodes { for _, n := range nodes {
if n.Name == exclude { if n.Name == exclude {
continue continue
@@ -175,8 +176,8 @@ func (c *composer) theRestOfTheMeshNow(exclude string) (catalogue.World, Passes,
return world, passes, left, nil return world, passes, left, nil
} }
// passSeatsOn finds, in the composition that finds it, the new holder of each seat held once whose holder on record is // passSeatsOn finds, in the composition that finds it, the new holder of each seat held once whose holder on record has left
// assigned nowhere and which has exactly one claimant able to hold it (novox/hq ADR 0449 clause 4), and answers the // it and which has exactly one claimant able to hold it (catalogue.SeatsToPassOn, novox/hq ADR 0660), and answers the
// holdings as they are after it, with the passes. Nothing is recorded. // holdings as they are after it, with the passes. Nothing is recorded.
func passSeatsOn(holdings []catalogue.Held, assignments []catalogue.Assignment) ([]catalogue.Held, Passes) { func passSeatsOn(holdings []catalogue.Held, assignments []catalogue.Assignment) ([]catalogue.Held, Passes) {
passes, said := catalogue.SeatsToPassOn(holdings, assignments) passes, said := catalogue.SeatsToPassOn(holdings, assignments)
@@ -196,9 +197,11 @@ func passSeatsOn(holdings []catalogue.Held, assignments []catalogue.Assignment)
return holdings, out return holdings, out
} }
// assignmentsOf are the assignments of every machine, as the passing of a seat reads them. A module assigned and // assignmentsOf are the assignments of every machine, as the passing of a seat reads them (novox/hq ADR 0660 clause 2): each
// missing from the shelf is still assigned: an assignment marked unread, so its holder is not taken for gone. // with the manifest of the build its node runs where the Input holds it (the node's Kept: what it runs of a module kept at
func assignmentsOf(shelf map[string]catalogue.Manifest, assigned map[string][]string) []catalogue.Assignment { // that build), else the trunk's on the shelf, which for the composing node's own modules is already the build they are
// wanted at. A module assigned and missing from both is still assigned, marked unread, so its holder is not taken for gone.
func (c *composer) assignmentsOf(shelf map[string]catalogue.Manifest, assigned map[string][]string) []catalogue.Assignment {
var out []catalogue.Assignment var out []catalogue.Assignment
names := make([]string, 0, len(assigned)) names := make([]string, 0, len(assigned))
for node := range assigned { for node := range assigned {
@@ -206,8 +209,17 @@ func assignmentsOf(shelf map[string]catalogue.Manifest, assigned map[string][]st
} }
sort.Strings(names) sort.Strings(names)
for _, node := range names { for _, node := range names {
var kept map[string]catalogue.Manifest
for _, n := range c.in.Nodes {
if n.Name == node {
kept = n.Kept
}
}
for _, module := range assigned[node] { for _, module := range assigned[node] {
m, ok := shelf[module] m, ok := kept[module]
if !ok {
m, ok = shelf[module]
}
if !ok { if !ok {
m = catalogue.Manifest{Module: module} m = catalogue.Manifest{Module: module}
} }
@@ -217,6 +229,17 @@ func assignmentsOf(shelf map[string]catalogue.Manifest, assigned map[string][]st
return out return out
} }
// Lines are what the passes say, one each, then what the finding itself says: a pass is composed so and recorded by nobody
// (ADR 0660 clause 4).
func (p Passes) Lines() []string {
var out []string
for _, x := range p.Passes {
out = append(out, fmt.Sprintf("%s: %s on %s is on record as its holder and holds it no more; composed as held by %s on %s, not recorded",
x.Seat.Name, x.From.Module, x.From.Node, x.To.Module, x.To.Node))
}
return append(out, p.Said...)
}
// servedOnNode is what a module on a node tells a consumer of one of its provisions, with THAT node's ports on it: the // servedOnNode is what a module on a node tells a consumer of one of its provisions, with THAT node's ports on it: the
// port the node was given over the one the mesh assigned over the manifest's own, settled with the node's settings // port the node was given over the one the mesh assigned over the manifest's own, settled with the node's settings
// layers. The one derivation for every reader of a provider's address. // layers. The one derivation for every reader of a provider's address.
+15 -1
View File
@@ -183,7 +183,7 @@ func TestNoSeatIsPassedForTwoClaimantsAnAssignedHolderOrNoClaimant(t *testing.T)
// seat_pass_test.go TestAHolderAssignedButNotOnTheShelfIsNotTakenForGone. // seat_pass_test.go TestAHolderAssignedButNotOnTheShelfIsNotTakenForGone.
func TestAHolderAssignedButNotOnTheShelfIsNotTakenForGone(t *testing.T) { func TestAHolderAssignedButNotOnTheShelfIsNotTakenForGone(t *testing.T) {
withSeat(t) withSeat(t)
as := assignmentsOf(map[string]catalogue.Manifest{"gate": aBrokerClaiming("gate")}, as := (&composer{}).assignmentsOf(map[string]catalogue.Manifest{"gate": aBrokerClaiming("gate")},
map[string][]string{"novox": {"gate", "old-broker"}}) map[string][]string{"novox": {"gate", "old-broker"}})
passes, _ := catalogue.SeatsToPassOn( passes, _ := catalogue.SeatsToPassOn(
[]catalogue.Held{{Claim: "mesh-broker", Scope: catalogue.ScopeMesh, Node: "novox", Module: "old-broker"}}, as) []catalogue.Held{{Claim: "mesh-broker", Scope: catalogue.ScopeMesh, Node: "novox", Module: "old-broker"}}, as)
@@ -264,4 +264,18 @@ func TestAHolderWhoseManifestDroppedTheSeatGoesToItsOnlyClaimant(t *testing.T) {
!strings.Contains(said, "seat mesh-broker --to <node>/<module>") { !strings.Contains(said, "seat mesh-broker --to <node>/<module>") {
t.Fatalf("two claimants: %v %+v", err, passes) t.Fatalf("two claimants: %v %+v", err, passes)
} }
// The build the holder's node runs still claims the seat, though the trunk dropped it: the holder keeps it (ADR 0660 §2).
running := mk("gate")
running.Nodes[0].Kept = map[string]catalogue.Manifest{"old-broker": aBrokerClaiming("old-broker")}
_, passes, _, err = TheRestOfTheMesh(running, "")
if err != nil || len(passes.Passes) != 0 || len(passes.Said) != 0 {
t.Fatalf("a holder whose running build claims the seat lost it: %v %+v", err, passes)
}
// And a pass is said in the composition's answer, not recorded.
var d Declared
d, _, err = Compose(mk("gate"), "novox")
said = strings.Join(d.Said, "\n")
if err != nil || !strings.Contains(said, "mesh-broker: old-broker on novox") || !strings.Contains(said, "held by gate on novox, not recorded") {
t.Fatalf("the answer does not say the pass: %v %q", err, said)
}
} }