diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go index ff21ffbe..a8c18e92 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go @@ -1233,7 +1233,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) { t.Fatalf("must answer = %v, want both nodes", p.Must) } answerAll(p) - p = proposalNow(p.ID, "asked", func(p mma.Proposal) bool { return p.State == plc.ProposalAsked }) + p = proposalNow(p.ID, "asked", func(p mma.Proposal) bool { return p.State == mmac.ProposalAsked }) akey, _ := delivery.PlacementAskKey(p.ID) b.answer(t, akey, assign.OptionYes) select { @@ -1277,7 +1277,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) { return strings.Contains(p.Words, "laid again") && len(p.Must) > 0 }) answerAll(relaid) - proposalNow(moved.ID, "asked", func(p mma.Proposal) bool { return p.State == plc.ProposalAsked }) + proposalNow(moved.ID, "asked", func(p mma.Proposal) bool { return p.State == mmac.ProposalAsked }) akey, _ = delivery.PlacementAskKey(moved.ID) b.answer(t, akey, assign.OptionNo) for deadline := time.Now().Add(15 * time.Second); b.hasKey(plc.ProposalsBucket, moved.ID); time.Sleep(50 * time.Millisecond) { @@ -1297,7 +1297,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) { t.Fatal(err) } silent := q.(proposed).Proposal - decided := proposalNow(silent.ID, "decided without its silent nodes", func(p mma.Proposal) bool { return p.State == plc.ProposalAsked }) + decided := proposalNow(silent.ID, "decided without its silent nodes", func(p mma.Proposal) bool { return p.State == mmac.ProposalAsked }) if decided.Verdict == nil || decided.Verdict.State != delivery.VerdictPass || !strings.Contains(strings.Join(decided.Verdict.Notes, "; "), "no answer from") || !strings.Contains(strings.Join(decided.Verdict.Notes, "; "), "laptop (offline)") { t.Errorf("the silent node's verdict = %+v", decided.Verdict) diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat.go index 6aa91479..9e5b551c 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat.go @@ -258,13 +258,13 @@ func (p *passer) ask(ctx context.Context, prop mma.Proposal, akey, brief string) if pk, err := plc.PulseAskKey(prop.ID); err == nil { p.pulse(ctx, pk, assign.ExpiresAt(placed).Sub(p.clock())) } - prop.State, prop.Ask = plc.ProposalAsked, placed.ID + prop.State, prop.Ask = mmac.ProposalAsked, placed.ID return prop, true } // stepProposal takes one proposal one step further. func (p *passer) stepProposal(ctx context.Context, prop mma.Proposal, rev uint64, current map[string]mma.Placement, revs map[string]uint64) { - if !p.saidProposed[prop.ID] && prop.State == plc.ProposalAnswering && prop.Verdict == nil { + if !p.saidProposed[prop.ID] && prop.State == mmac.ProposalAnswering && prop.Verdict == nil { p.sayEvent(mma.EventPlacementProposed, mma.PlacementEvent{Proposal: prop}, prop.ID) p.saidProposed[prop.ID] = true } @@ -283,7 +283,7 @@ func (p *passer) stepProposal(ctx context.Context, prop mma.Proposal, rev uint64 } akey, _ := delivery.PlacementAskKey(prop.ID) again := prop.Again != nil && !prop.Again.IsZero() && (prop.Verdict == nil || prop.Again.After(prop.Verdict.At)) - if prop.State == plc.ProposalAsked { + if prop.State == mmac.ProposalAsked { askPulse, _ := plc.PulseAskKey(prop.ID) switch { case p.answeredWith(akey, assign.OptionYes): @@ -327,11 +327,11 @@ func (p *passer) stepProposal(ctx context.Context, prop mma.Proposal, rev uint64 prop.Verdict = &v switch v.State { case delivery.VerdictFail: - prop.State, prop.Ask = plc.ProposalFailed, "" + prop.State, prop.Ask = mmac.ProposalFailed, "" case plc.VerdictWaits: - prop.State, prop.Ask = plc.ProposalAnswering, "" + prop.State, prop.Ask = mmac.ProposalAnswering, "" case delivery.VerdictPass: - if prop.State != plc.ProposalAsked || again { + if prop.State != mmac.ProposalAsked || again { var ok bool if prop, ok = p.ask(ctx, prop, akey, judge.Brief(prop, v)); !ok { return @@ -535,7 +535,7 @@ func (p *passer) addNode(ctx context.Context, prop mma.Proposal, revs map[string laid, findings := plc.LayChanges(current, prop.Changes) findings = append(findings, assign.ControllerRefusals(prop.Changes, laid)...) node := prop.Changes[0].Node - if prop.Changes[0].Op == plc.OpRemoveNode { + if prop.Changes[0].Op == mmac.OpRemoveNode { p.removeNode(ctx, prop, current, findings) return } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools.go index 34251101..beab6d65 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools.go @@ -209,7 +209,7 @@ func (s *seatTools) doPropose(args map[string]any, by string) (any, error) { return nil, err } for _, c := range changes { - if c.Op == plc.OpSeed { + if c.Op == mmac.OpSeed { return nil, errors.New("the seed is gone with adopt: propose takes place, take-off, pin, unpin, hold, unhold, decide and config") } } diff --git a/modules/mesh-delivery-assigner/internal/assign/seat.go b/modules/mesh-delivery-assigner/internal/assign/seat.go index 501696f2..cf657c2d 100644 --- a/modules/mesh-delivery-assigner/internal/assign/seat.go +++ b/modules/mesh-delivery-assigner/internal/assign/seat.go @@ -42,7 +42,7 @@ type ProposeIn struct { // record). A key whose value the proposal leaves as it is changes nothing and is not laid. func Propose(in ProposeIn) (mma.Proposal, map[string]*mma.Placement, []string) { p := mma.Proposal{ID: in.ID, By: strings.TrimSpace(in.By), Why: strings.TrimSpace(in.Why), At: in.At.UTC(), - Changes: in.Changes, Warrant: in.Warrant, Base: map[string]int64{}, State: plc.ProposalAnswering} + Changes: in.Changes, Warrant: in.Warrant, Base: map[string]int64{}, State: mmac.ProposalAnswering} var findings []string if p.By == "" { findings = append(findings, "the runtime names no caller: a proposal is made by someone") @@ -54,7 +54,7 @@ func Propose(in ProposeIn) (mma.Proposal, map[string]*mma.Placement, []string) { findings = append(findings, "a proposal changes something: it names no change") } for i, c := range in.Changes { - if c.Op == plc.OpSeed { + if c.Op == mmac.OpSeed { findings = append(findings, fmt.Sprintf("change %d: the seed is gone with adopt, the controller's export being gone", i+1)) } } @@ -99,7 +99,7 @@ func Propose(in ProposeIn) (mma.Proposal, map[string]*mma.Placement, []string) { // this one caller is the exception to a node with no record (ADR 0587 §7, changing ADR 0567 §2). joining := map[string]bool{} for _, c := range in.Changes { - if c.Op == plc.OpAddNode { + if c.Op == mmac.OpAddNode { joining[c.Node] = true } } @@ -127,7 +127,7 @@ func ControllerRefusals(changes []mma.ProposalChange, laid map[string]mma.Placem var out []string nodes := map[string]bool{} for i, c := range changes { - places := c.Module == ControllerModule && c.Op != plc.OpTakeOff + places := c.Module == ControllerModule && c.Op != mmac.OpTakeOff if c.Whole != nil && slices.Contains(mmac.Assigned(*c.Whole), ControllerModule) { places = true } @@ -235,6 +235,6 @@ func Rest(p mma.Proposal, written map[string]*mma.Placement) (mma.Proposal, bool rest.Base[node] = base } } - rest.Must, rest.Verdict, rest.Ask, rest.State = nil, nil, "", plc.ProposalAnswering + rest.Must, rest.Verdict, rest.Ask, rest.State = nil, nil, "", mmac.ProposalAnswering return rest, len(rest.Changes) > 0 } diff --git a/modules/mesh-delivery-assigner/internal/assign/seat_test.go b/modules/mesh-delivery-assigner/internal/assign/seat_test.go index 2be59eb0..cf4ad446 100644 --- a/modules/mesh-delivery-assigner/internal/assign/seat_test.go +++ b/modules/mesh-delivery-assigner/internal/assign/seat_test.go @@ -28,9 +28,9 @@ func TestThePlacementSeatDecides(t *testing.T) { Held: func(m string) bool { return m != "ghost" }, Recorded: func(n string) bool { return n == "anchor" || n == "laptop" }} } - move := []mma.ProposalChange{{Op: plc.OpTakeOff, Node: "anchor", Module: "web"}, {Op: plc.OpPlace, Node: "laptop", Module: "web"}} + move := []mma.ProposalChange{{Op: mmac.OpTakeOff, Node: "anchor", Module: "web"}, {Op: mmac.OpPlace, Node: "laptop", Module: "web"}} p, laid, findings := Propose(in(move...)) - if len(findings) != 0 || p.State != plc.ProposalAnswering || p.Base["anchor"] != 4 || p.Base["laptop"] != 9 || + if len(findings) != 0 || p.State != mmac.ProposalAnswering || p.Base["anchor"] != 4 || p.Base["laptop"] != 9 || !slices.Equal(mmac.Assigned(*laid["laptop"]), []string{"shell", "web"}) || !slices.Equal(mmac.Assigned(*laid["anchor"]), []string{"store"}) { t.Fatalf("a move: %+v, %+v, %v", p, laid, findings) } @@ -41,18 +41,18 @@ func TestThePlacementSeatDecides(t *testing.T) { in ProposeIn want string }{ - "a module not held": {in(mma.ProposalChange{Op: plc.OpPlace, Node: "laptop", Module: "ghost"}), "the indexer holds no module ghost"}, - "a node with no record": {in(mma.ProposalChange{Op: plc.OpPlace, Node: "stranger", Module: "web"}), "stranger is a node with no record"}, - "nothing changes": {in(mma.ProposalChange{Op: plc.OpPlace, Node: "anchor", Module: "web"}), "leave every placement as it is"}, + "a module not held": {in(mma.ProposalChange{Op: mmac.OpPlace, Node: "laptop", Module: "ghost"}), "the indexer holds no module ghost"}, + "a node with no record": {in(mma.ProposalChange{Op: mmac.OpPlace, Node: "stranger", Module: "web"}), "stranger is a node with no record"}, + "nothing changes": {in(mma.ProposalChange{Op: mmac.OpPlace, Node: "anchor", Module: "web"}), "leave every placement as it is"}, "no why": {func() ProposeIn { i := in(move...); i.Why = " "; return i }(), "says why"}, "no caller": {func() ProposeIn { i := in(move...); i.By = ""; return i }(), "names no caller"}, - "a seed by hand": {in(mma.ProposalChange{Op: plc.OpSeed, Node: "laptop", Whole: &laptop}), "gone with adopt"}, + "a seed by hand": {in(mma.ProposalChange{Op: mmac.OpSeed, Node: "laptop", Whole: &laptop}), "gone with adopt"}, // 1126: the controller ended by its unassignment; placed again it would be a second writer beside the parts that // act without its lease. - "the controller placed again": {in(mma.ProposalChange{Op: plc.OpPlace, Node: "laptop", Module: "mesh-controller"}), "ended by its unassignment"}, - "the controller seeded whole": {in(mma.ProposalChange{Op: plc.OpSeed, Node: "anchor", Whole: &mma.Placement{V: keys.Version, Node: "anchor", + "the controller placed again": {in(mma.ProposalChange{Op: mmac.OpPlace, Node: "laptop", Module: "mesh-controller"}), "ended by its unassignment"}, + "the controller seeded whole": {in(mma.ProposalChange{Op: mmac.OpSeed, Node: "anchor", Whole: &mma.Placement{V: keys.Version, Node: "anchor", Decided: anchor.Decided, Modules: []mma.PlacedModule{{Module: "mesh-controller"}}}}), "two writers (issue 1126)"}, - "a pin of nobody placed": {in(mma.ProposalChange{Op: plc.OpPin, Node: "laptop", Provision: "db", Provider: &mma.Provider{Node: "laptop", Module: "store"}}), "not placed there"}, + "a pin of nobody placed": {in(mma.ProposalChange{Op: mmac.OpPin, Node: "laptop", Provision: "db", Provider: &mma.Provider{Node: "laptop", Module: "store"}}), "not placed there"}, } { if _, _, f := Propose(c.in); !slices.ContainsFunc(f, func(s string) bool { return strings.Contains(s, c.want) }) { t.Errorf("%s: findings %v, want one naming %q", name, f, c.want) @@ -61,12 +61,12 @@ func TestThePlacementSeatDecides(t *testing.T) { // Every kind of change is taken: the placement is the one source, and the controller's verbs limit nothing (ADR 0567 §8). for _, c := range []mma.ProposalChange{ - {Op: plc.OpConfig, Node: "anchor", Module: "web", Values: map[string]any{"puid": 1}}, - {Op: plc.OpPlace, Node: "laptop", Module: "web", Ports: map[string]int64{"80": 8080}, Bindings: map[string]mma.Provider{"db": {Node: "anchor", Module: "store"}}}, - {Op: plc.OpConfig, Node: "anchor", Module: "web", Layer: map[string]any{"places": []any{}}}, - {Op: plc.OpConfig, Node: "anchor", Module: "web", MeshWide: map[string]any{"accesses": []any{}}}, - {Op: plc.OpDecide, Node: "laptop", Field: "site", Value: json.RawMessage(`"attic"`)}, - {Op: plc.OpHold, Node: "anchor", Module: "store", Claim: "node-shell", Scope: "node"}, + {Op: mmac.OpConfig, Node: "anchor", Module: "web", Values: map[string]any{"puid": 1}}, + {Op: mmac.OpPlace, Node: "laptop", Module: "web", Ports: map[string]int64{"80": 8080}, Bindings: map[string]mma.Provider{"db": {Node: "anchor", Module: "store"}}}, + {Op: mmac.OpConfig, Node: "anchor", Module: "web", Layer: map[string]any{"places": []any{}}}, + {Op: mmac.OpConfig, Node: "anchor", Module: "web", MeshWide: map[string]any{"accesses": []any{}}}, + {Op: mmac.OpDecide, Node: "laptop", Field: "site", Value: json.RawMessage(`"attic"`)}, + {Op: mmac.OpHold, Node: "anchor", Module: "store", Claim: "node-shell", Scope: "node"}, } { if _, _, f := Propose(in(c)); len(f) != 0 { t.Errorf("%s is refused: %v", c.Op, f) @@ -75,7 +75,7 @@ func TestThePlacementSeatDecides(t *testing.T) { // A key moved while the warrant wrote: what is left to write is the other nodes' changes, to be laid and asked again. if rest, ok := Rest(p, map[string]*mma.Placement{"anchor": laid["anchor"]}); !ok || len(rest.Changes) != 1 || rest.Changes[0].Node != "laptop" || - len(rest.Base) != 1 || rest.State != plc.ProposalAnswering || rest.Must != nil { + len(rest.Base) != 1 || rest.State != mmac.ProposalAnswering || rest.Must != nil { t.Errorf("the rest = %+v, %v", rest, ok) } if _, ok := Rest(p, laid); ok { @@ -100,7 +100,7 @@ func TestThePlacementSeatDecides(t *testing.T) { t.Fatalf("the ask = %+v, %v", a, err) } q := p - q.Changes = append(slices.Clone(p.Changes), mma.ProposalChange{Op: plc.OpUnpin, Node: "laptop", Provision: "x"}) + q.Changes = append(slices.Clone(p.Changes), mma.ProposalChange{Op: mmac.OpUnpin, Node: "laptop", Provision: "x"}) if occ.Performs(mustAsk(t, a).Options[0], PlacementAct(q)) == nil { t.Error("the ask's yes binds changes it was not asked about") } diff --git a/modules/mesh-delivery-assigner/internal/placement/placement_test.go b/modules/mesh-delivery-assigner/internal/placement/placement_test.go index cdf2da22..ae1f396c 100644 --- a/modules/mesh-delivery-assigner/internal/placement/placement_test.go +++ b/modules/mesh-delivery-assigner/internal/placement/placement_test.go @@ -97,10 +97,10 @@ func TestPlacement(t *testing.T) { t.Run("changes laid over the keys", func(t *testing.T) { laid, findings := LayChanges(current, []seat.ProposalChange{ - {Op: OpPlace, Node: "g14", Module: "new"}, - {Op: OpTakeOff, Node: "g14", Module: "zsh"}, - {Op: OpUnpin, Node: "g14", Provision: "shell"}, - {Op: OpDecide, Node: "novox", Field: "site", Value: "attic"}, + {Op: client.OpPlace, Node: "g14", Module: "new"}, + {Op: client.OpTakeOff, Node: "g14", Module: "zsh"}, + {Op: client.OpUnpin, Node: "g14", Provision: "shell"}, + {Op: client.OpDecide, Node: "novox", Field: "site", Value: "attic"}, }) if len(findings) != 0 || len(laid) != 2 || !reflect.DeepEqual(client.Assigned(laid["g14"]), []string{"app", "new"}) || len(laid["g14"].Holds) != 0 || laid["g14"].Pins != nil || laid["novox"].Decided.Site != "attic" || !laid["novox"].Decided.Hub { @@ -109,21 +109,21 @@ func TestPlacement(t *testing.T) { if !reflect.DeepEqual(client.Assigned(current["g14"]), []string{"zsh", "app"}) { t.Error("laying changed the current placement") } - if fresh, f := LayChanges(current, []seat.ProposalChange{{Op: OpPlace, Node: "pi", Module: "zsh"}}); len(f) != 0 || fresh["pi"].Node != "pi" { + if fresh, f := LayChanges(current, []seat.ProposalChange{{Op: client.OpPlace, Node: "pi", Module: "zsh"}}); len(f) != 0 || fresh["pi"].Node != "pi" { t.Errorf("a node with no key: %+v, %v", fresh, f) } for name, c := range map[string]struct { change seat.ProposalChange want string }{ - "not placed": {seat.ProposalChange{Op: OpTakeOff, Node: "g14", Module: "nope"}, "not placed"}, - "no such field": {seat.ProposalChange{Op: OpDecide, Node: "g14", Field: "colour", Value: "x"}, "no decided field"}, - "wrong type": {seat.ProposalChange{Op: OpDecide, Node: "g14", Field: "hub", Value: "x"}, "does not take"}, + "not placed": {seat.ProposalChange{Op: client.OpTakeOff, Node: "g14", Module: "nope"}, "not placed"}, + "no such field": {seat.ProposalChange{Op: client.OpDecide, Node: "g14", Field: "colour", Value: "x"}, "no decided field"}, + "wrong type": {seat.ProposalChange{Op: client.OpDecide, Node: "g14", Field: "hub", Value: "x"}, "does not take"}, "unknown op": {seat.ProposalChange{Op: "move", Node: "g14"}, "no change of placement"}, - "pin of nobody": {seat.ProposalChange{Op: OpPin, Node: "g14", Provision: "x"}, "provider"}, - "not held": {seat.ProposalChange{Op: OpUnhold, Node: "g14", Module: "app", Claim: "x"}, "holds no"}, - "bad port": {seat.ProposalChange{Op: OpPlace, Node: "g14", Module: "x", Ports: map[string]int64{"a": 1}}, "port"}, - "seed of another": {seat.ProposalChange{Op: OpSeed, Node: "g14", Whole: &novox}, "seed"}, + "pin of nobody": {seat.ProposalChange{Op: client.OpPin, Node: "g14", Provision: "x"}, "provider"}, + "not held": {seat.ProposalChange{Op: client.OpUnhold, Node: "g14", Module: "app", Claim: "x"}, "holds no"}, + "bad port": {seat.ProposalChange{Op: client.OpPlace, Node: "g14", Module: "x", Ports: map[string]int64{"a": 1}}, "port"}, + "seed of another": {seat.ProposalChange{Op: client.OpSeed, Node: "g14", Whole: &novox}, "seed"}, } { if _, f := LayChanges(current, []seat.ProposalChange{c.change}); len(f) != 1 || !strings.Contains(f[0], c.want) { t.Errorf("%s: %v, want a finding naming %q", name, f, c.want) @@ -136,8 +136,8 @@ func TestPlacement(t *testing.T) { t.Errorf("NewProposalID = %q", got) } p := seat.Proposal{ID: id, By: "operator@anchor", Why: "move the app", At: time.Unix(1, 0).UTC(), - Changes: []seat.ProposalChange{{Op: OpTakeOff, Node: "g14", Module: "zsh"}}, Base: map[string]int64{"g14": 4}, - State: ProposalAnswering} + Changes: []seat.ProposalChange{{Op: client.OpTakeOff, Node: "g14", Module: "zsh"}}, Base: map[string]int64{"g14": 4}, + State: client.ProposalAnswering} raw, err := EncodeProposal(p) if err != nil { t.Fatal(err) @@ -150,7 +150,7 @@ func TestPlacement(t *testing.T) { } seed := p seed.Seed, seed.ExportDigest = true, ExportDigest(current) - seed.Changes = []seat.ProposalChange{{Op: OpSeed, Node: "g14", Whole: &g14}} + seed.Changes = []seat.ProposalChange{{Op: client.OpSeed, Node: "g14", Whole: &g14}} if _, err := EncodeProposal(seed); err != nil { t.Errorf("the seed: %v", err) } @@ -161,17 +161,17 @@ func TestPlacement(t *testing.T) { } // The digests are what the earlier holder's code made of the same values (issue 1966: a proposal's ask binds its // changes' digest, and a seed its export's), whatever order the generated types keep their fields in. - rich := seat.Proposal{ID: id, By: "operator@anchor", Why: "w", At: time.Unix(1, 0).UTC(), State: ProposalAnswering, + rich := seat.Proposal{ID: id, By: "operator@anchor", Why: "w", At: time.Unix(1, 0).UTC(), State: client.ProposalAnswering, Base: map[string]int64{"g14": 4, "lab": 0, "novox": 2}, Changes: []seat.ProposalChange{ - {Op: OpTakeOff, Node: "g14", Module: "zsh"}, - {Op: OpPlace, Node: "g14", Module: "new", Ports: map[string]int64{"80": 8080}, Bindings: map[string]seat.Provider{"pg": {Node: "novox", Module: "postgres"}}}, - {Op: OpPin, Node: "g14", Provision: "shell", Provider: &seat.Provider{Node: "g14", Module: "bash"}}, - {Op: OpHold, Node: "g14", Module: "app", Claim: "node-shell", Scope: "node", Kind: "k", Site: "s"}, - {Op: OpDecide, Node: "novox", Field: "site", Value: "attic"}, - {Op: OpDecide, Node: "novox", Field: "peer-names", Value: map[string]any{"k": "v"}}, - {Op: OpConfig, Node: "g14", Module: "app", MeshWide: map[string]any{"a": float64(1)}, Layer: map[string]any{"b": "c"}, MeshWideValues: map[string]any{"d": true}, Values: map[string]any{"e": []any{"f"}}}, - {Op: OpAddNode, Node: "lab", Decided: &seat.NodeDecided{Address: "10.0.0.7", Account: "x", AgentAccountHome: "/h"}, Keys: &seat.ProposalChangeKeys{Overlay: "wg", Identity: "id", Sealing: "se"}}, + {Op: client.OpTakeOff, Node: "g14", Module: "zsh"}, + {Op: client.OpPlace, Node: "g14", Module: "new", Ports: map[string]int64{"80": 8080}, Bindings: map[string]seat.Provider{"pg": {Node: "novox", Module: "postgres"}}}, + {Op: client.OpPin, Node: "g14", Provision: "shell", Provider: &seat.Provider{Node: "g14", Module: "bash"}}, + {Op: client.OpHold, Node: "g14", Module: "app", Claim: "node-shell", Scope: "node", Kind: "k", Site: "s"}, + {Op: client.OpDecide, Node: "novox", Field: "site", Value: "attic"}, + {Op: client.OpDecide, Node: "novox", Field: "peer-names", Value: map[string]any{"k": "v"}}, + {Op: client.OpConfig, Node: "g14", Module: "app", MeshWide: map[string]any{"a": float64(1)}, Layer: map[string]any{"b": "c"}, MeshWideValues: map[string]any{"d": true}, Values: map[string]any{"e": []any{"f"}}}, + {Op: client.OpAddNode, Node: "lab", Decided: &seat.NodeDecided{Address: "10.0.0.7", Account: "x", AgentAccountHome: "/h"}, Keys: &seat.ProposalChangeKeys{Overlay: "wg", Identity: "id", Sealing: "se"}}, }} for name, c := range map[string]struct{ got, want string }{ "export": {ExportDigest(current), "sha256:742f03d8790d7efa6abff76636f4a69ba2704d646233960f52ce08604b96e128"}, @@ -190,10 +190,10 @@ func TestPlacement(t *testing.T) { // 774/S42/AC1 (ADR 0587 §7): an add-node carries its enrolment's warrant naming its node and address, as its one // change, from the enroller alone; a warrant naming another node or address is refused. // At the operator's yes it carries the tunnel key alone (the operator, 2026-11-02 01:05). - joining := seat.ProposalChange{Op: OpAddNode, Node: "lab", Decided: &seat.NodeDecided{Address: "10.0.0.7"}, + joining := seat.ProposalChange{Op: client.OpAddNode, Node: "lab", Decided: &seat.NodeDecided{Address: "10.0.0.7"}, Keys: &seat.ProposalChangeKeys{Overlay: "wg"}} add := seat.Proposal{ID: id, By: "ace.mesh-enrolment", Why: "lab joins", At: time.Unix(1, 0).UTC(), - Changes: []seat.ProposalChange{joining}, Base: map[string]int64{"lab": 0}, State: ProposalAnswering, + Changes: []seat.ProposalChange{joining}, Base: map[string]int64{"lab": 0}, State: client.ProposalAnswering, Warrant: &seat.Warrant{Node: "lab", Address: "10.0.0.7", Ask: "a-1", Warrant: "w-1"}} if raw, err := EncodeProposal(add); err != nil { t.Errorf("an add-node: %v", err) @@ -206,7 +206,7 @@ func TestPlacement(t *testing.T) { if laid, f := LayChanges(current, add.Changes); len(f) != 0 || laid["lab"].Decided.Address != "10.0.0.7" || len(laid["lab"].Modules) != 0 { t.Errorf("an add-node laid: %+v, %v", laid, f) } - if _, f := LayChanges(current, []seat.ProposalChange{{Op: OpAddNode, Node: "g14", Decided: &seat.NodeDecided{Address: "10.0.0.9"}, + if _, f := LayChanges(current, []seat.ProposalChange{{Op: client.OpAddNode, Node: "g14", Decided: &seat.NodeDecided{Address: "10.0.0.9"}, Keys: &seat.ProposalChangeKeys{Overlay: "wg"}}}); len(f) != 1 || !strings.Contains(f[0], "placed already") { t.Errorf("an add-node of a placed node: %v", f) } @@ -229,7 +229,7 @@ func TestPlacement(t *testing.T) { p.Changes = []seat.ProposalChange{c} }, "beside another change": func(p *seat.Proposal) { - p.Changes = append(p.Changes, seat.ProposalChange{Op: OpPlace, Node: "lab", Module: "zsh"}) + p.Changes = append(p.Changes, seat.ProposalChange{Op: client.OpPlace, Node: "lab", Module: "zsh"}) }, "the hub": func(p *seat.Proposal) { c := joining @@ -237,7 +237,7 @@ func TestPlacement(t *testing.T) { p.Changes = []seat.ProposalChange{c} }, "a warrant on a move": func(p *seat.Proposal) { - p.Changes, p.Base = []seat.ProposalChange{{Op: OpTakeOff, Node: "g14", Module: "zsh"}}, map[string]int64{"g14": 4} + p.Changes, p.Base = []seat.ProposalChange{{Op: client.OpTakeOff, Node: "g14", Module: "zsh"}}, map[string]int64{"g14": 4} }, } { q := add @@ -256,7 +256,7 @@ func TestPlacement(t *testing.T) { } // An enrolment's end takes its never-confirmed node off again: the enroller's alone, of a node running nothing. remove := add - remove.Changes = []seat.ProposalChange{{Op: OpRemoveNode, Node: "lab"}} + remove.Changes = []seat.ProposalChange{{Op: client.OpRemoveNode, Node: "lab"}} if _, err := EncodeProposal(remove); err != nil || AddNodeRefusal(remove, "mesh-delivery-assigner", enroller) == "" || AddNodeRefusal(remove, enroller, enroller) != "" || !slices.Equal(RemovedNodes(remove), []string{"lab"}) { t.Errorf("a remove-node: %v, %q", err, AddNodeRefusal(remove, "mesh-delivery-assigner", enroller)) @@ -265,7 +265,7 @@ func TestPlacement(t *testing.T) { if laid, f := LayChanges(joined, remove.Changes); len(f) != 0 || len(laid) != 0 { t.Errorf("a joining node removed: %v, %v", laid, f) } - if _, f := LayChanges(current, []seat.ProposalChange{{Op: OpRemoveNode, Node: "g14"}}); len(f) != 1 || + if _, f := LayChanges(current, []seat.ProposalChange{{Op: client.OpRemoveNode, Node: "g14"}}); len(f) != 1 || !strings.Contains(f[0], "runs modules") { t.Errorf("a running node removed: %v", f) } @@ -280,7 +280,7 @@ func TestPlacement(t *testing.T) { "no change": func(p *seat.Proposal) { p.Changes = nil }, "no base": func(p *seat.Proposal) { p.Base = map[string]int64{"novox": 1} }, "no state": func(p *seat.Proposal) { p.State = "" }, - "asked, no ask": func(p *seat.Proposal) { p.State = ProposalAsked }, + "asked, no ask": func(p *seat.Proposal) { p.State = client.ProposalAsked }, "seed mixed": func(p *seat.Proposal) { p.Seed, p.ExportDigest = true, "d" }, "bad change": func(p *seat.Proposal) { p.Changes[0].Op = "swap" }, "seed, no digest": func(p *seat.Proposal) { *p = seed; p.ExportDigest = "" }, diff --git a/modules/mesh-delivery-assigner/internal/placement/proposal.go b/modules/mesh-delivery-assigner/internal/placement/proposal.go index 98db84ca..cdf6ea6a 100644 --- a/modules/mesh-delivery-assigner/internal/placement/proposal.go +++ b/modules/mesh-delivery-assigner/internal/placement/proposal.go @@ -17,37 +17,6 @@ import ( // lays them over the current keys, names the revision of each key it laid them over, and either refuses at once with every // finding or writes the proposal under its id in its state `proposals` and a candidate for each node it must hear from. -// The operations a proposal's change names (ADR 0567 §2). -const ( - OpPlace = "place" // place a module on a node, with its host ports and bindings - OpTakeOff = "take-off" // take a module off a node - OpPin = "pin" // pin the provider of a provision on a node - OpUnpin = "unpin" // remove a pin - OpHold = "hold" // a module placed on the node holds a seat - OpUnhold = "unhold" // it holds it no more - OpDecide = "decide" // set a decided field of the node (NodeDecided by its JSON name), its address among them - OpConfig = "config" // set a module's config placement or values on the node - OpSeed = "seed" // the node's whole placement: the take-over (adopt, §7), never given by a caller - // OpAddNode adds a joining node with its decided fields and the public halves of its keys, on the warrant of the - // enrolment that issued its token (novox/hq ADR 0587 §7): the enroller's alone, the proposal's one change. - OpAddNode = "add-node" - // OpRemoveNode takes a joining node off placement again when its enrolment ends unconfirmed, revoked or expired - // (the operator's decision of 2026-11-02 01:05): the enroller's alone, on the same warrant, the proposal's one - // change, of a node with no module and no holding. The holder deletes its key (RemovedNodes). - OpRemoveNode = "remove-node" -) - -// The states of an open proposal. -const ( - // ProposalAnswering: its candidates are written and the nodes it must hear from have not all answered. - ProposalAnswering = "answering" - // ProposalFailed: its verdict failed; it stays open, for its proposer to read and withdraw, or for a node's later - // answer to judge it again. - ProposalFailed = "failed" - // ProposalAsked: its verdict passed and the operator is asked to approve it. - ProposalAsked = "asked" -) - // VerdictWaits is a proposal's verdict while a node it must hear from has not answered within the bound; it passes or // fails as a check's verdict does (ADR 0567 §4). const VerdictWaits = "waits" @@ -65,7 +34,7 @@ func Nodes(p seat.Proposal) []string { return sortedKeys(p.Base) } func RemovedNodes(p seat.Proposal) []string { var out []string for _, c := range p.Changes { - if c.Op == OpRemoveNode { + if c.Op == client.OpRemoveNode { out = append(out, c.Node) } } @@ -84,22 +53,22 @@ func CheckChange(c seat.ProposalChange) error { return nil } switch c.Op { - case OpPlace, OpTakeOff, OpConfig: + case client.OpPlace, client.OpTakeOff, client.OpConfig: return needs("module", keys.CheckName("module", c.Module) == nil) - case OpPin: + case client.OpPin: return needs("provision or provider", c.Provision != "" && c.Provider != nil && c.Provider.Node != "" && c.Provider.Module != "") - case OpUnpin: + case client.OpUnpin: return needs("provision", c.Provision != "") - case OpHold, OpUnhold: + case client.OpHold, client.OpUnhold: return needs("module or claim", keys.CheckName("module", c.Module) == nil && c.Claim != "") - case OpDecide: + case client.OpDecide: return needs("field or value", c.Field != "" && c.Value != nil) - case OpSeed: + case client.OpSeed: if c.Whole == nil || c.Whole.Node != c.Node { return fmt.Errorf("placement: the seed of %s names no placement of that node", c.Node) } return client.CheckPlacement(*c.Whole) - case OpAddNode: + case client.OpAddNode: if c.Decided == nil || c.Decided.Address == "" || c.Keys == nil || c.Keys.Overlay == "" { return fmt.Errorf("placement: the add-node of %s names no address or no tunnel key", c.Node) } @@ -114,7 +83,7 @@ func CheckChange(c seat.ProposalChange) error { return fmt.Errorf("placement: the add-node of %s places a module; a module is placed by a proposal of its own", c.Node) } return nil - case OpRemoveNode: + case client.OpRemoveNode: if c.Module != "" || c.Decided != nil || c.Keys != nil || c.Whole != nil { return fmt.Errorf("placement: the remove-node of %s names more than its node", c.Node) } @@ -157,16 +126,16 @@ func LayChanges(current map[string]seat.Placement, changes []seat.ProposalChange findings = append(findings, fmt.Sprintf("change %d: %s is not placed on %s", i+1, c.Module, c.Node)) } switch c.Op { - case OpSeed: + case client.OpSeed: p = clonePlacement(*c.Whole) - case OpAddNode: + case client.OpAddNode: // A joining node has no key yet (ADR 0587 §7): one that has is no joining node. if _, has := current[c.Node]; has { findings = append(findings, fmt.Sprintf("change %d: %s is placed already, so it does not join", i+1, c.Node)) continue } p = seat.Placement{V: keys.Version, Node: c.Node, Decided: *c.Decided, Modules: []seat.PlacedModule{}} - case OpRemoveNode: + case client.OpRemoveNode: // Only a joining node never confirmed goes this way: one with no module and no holding (a node that runs // anything is taken off module by module, by a proposal the operator approves). cur, has := current[c.Node] @@ -176,7 +145,7 @@ func LayChanges(current map[string]seat.Placement, changes []seat.ProposalChange findings = append(findings, fmt.Sprintf("change %d: %s runs modules or holds seats, so it is no joining node to remove", i+1, c.Node)) } continue - case OpPlace: + case client.OpPlace: m := seat.PlacedModule{Module: c.Module, Ports: c.Ports, Bindings: c.Bindings} if at >= 0 { o := p.Modules[at] @@ -186,7 +155,7 @@ func LayChanges(current map[string]seat.Placement, changes []seat.ProposalChange } else { p.Modules = append(p.Modules, m) } - case OpTakeOff: + case client.OpTakeOff: if at < 0 { notPlaced() continue @@ -199,7 +168,7 @@ func LayChanges(current map[string]seat.Placement, changes []seat.ProposalChange } } p.Holds = holds - case OpConfig: + case client.OpConfig: if at < 0 { notPlaced() continue @@ -217,23 +186,23 @@ func LayChanges(current map[string]seat.Placement, changes []seat.ProposalChange if c.Values != nil { m.Values = c.Values } - case OpPin: + case client.OpPin: if p.Pins == nil { p.Pins = map[string]seat.Provider{} } p.Pins[c.Provision] = *c.Provider - case OpUnpin: + case client.OpUnpin: delete(p.Pins, c.Provision) if len(p.Pins) == 0 { p.Pins = nil } - case OpHold: + case client.OpHold: if at < 0 { notPlaced() continue } p.Holds = append(p.Holds, seat.Holding{Claim: c.Claim, Scope: c.Scope, Module: c.Module, Site: c.Site, Kind: c.Kind}) - case OpUnhold: + case client.OpUnhold: kept := p.Holds[:0:0] for _, h := range p.Holds { if h.Module != c.Module || h.Claim != c.Claim { @@ -245,7 +214,7 @@ func LayChanges(current map[string]seat.Placement, changes []seat.ProposalChange continue } p.Holds = kept - case OpDecide: + case client.OpDecide: d, err := decide(p.Decided, c.Field, c.Value) if err != nil { findings = append(findings, fmt.Sprintf("change %d: %v", i+1, err)) @@ -339,7 +308,7 @@ func CheckProposal(p seat.Proposal) error { if err := CheckChange(c); err != nil { return fmt.Errorf("placement: the proposal %s, change %d: %w", p.ID, i+1, err) } - if (c.Op == OpSeed) != p.Seed { + if (c.Op == client.OpSeed) != p.Seed { return fmt.Errorf("placement: the proposal %s mixes the seed with other changes", p.ID) } if _, ok := p.Base[c.Node]; !ok { @@ -347,8 +316,8 @@ func CheckProposal(p seat.Proposal) error { } } switch p.State { - case ProposalAnswering, ProposalFailed: - case ProposalAsked: + case client.ProposalAnswering, client.ProposalFailed: + case client.ProposalAsked: if p.Ask == "" || p.Verdict == nil || p.Verdict.State != VerdictPass { return fmt.Errorf("placement: the proposal %s is asked with no ask or no passing verdict", p.ID) } @@ -367,7 +336,7 @@ func CheckProposal(p seat.Proposal) error { func checkAddNode(p seat.Proposal) error { adds := 0 for _, c := range p.Changes { - if c.Op == OpAddNode || c.Op == OpRemoveNode { + if c.Op == client.OpAddNode || c.Op == client.OpRemoveNode { adds++ } } @@ -384,7 +353,7 @@ func checkAddNode(p seat.Proposal) error { if w == nil || w.Ask == "" || w.Warrant == "" { return fmt.Errorf("placement: the add-node of %s carries no warrant of its enrolment", c.Node) } - if c.Op == OpRemoveNode { + if c.Op == client.OpRemoveNode { if w.Node != c.Node { return fmt.Errorf("placement: the remove-node of %s carries the warrant of %s", c.Node, w.Node) }