The assigner takes an add-node from mesh-enrolment and writes the joining node with no ask
propose takes the enrolment's warrant {node, address, ask, warrant} and refuses an add-node from any caller but
mesh-enrolment, with no warrant or with a warrant naming another node or address (delivery.AddNodeRefusal); the joining node
counts as having a record for this one change, as ADR 0587 §7 changes ADR 0567 §2. The assigner's process then writes
placement.<node> at once, with no node to answer and no ask raised, and says placement-changed carrying the enrolment's
warrant; the hand-act record of that warrant is the enrolment's, made when it was given. The holder of mesh-enrolment calls
propose with the change, the keys and the warrant read from the router's record of the ask. Tests: rows appended to the
seat's verbs test and a case to the whole-seat integration test; none added, so none to pay for (the budget is paid in
774/S60).
(issue 774, 774/S59)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -1133,4 +1133,30 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
||||
if failed.Verdict == nil || !strings.Contains(strings.Join(failed.Verdict.Fails, "; "), "laptop (offline) did not answer") {
|
||||
t.Errorf("the silent node's verdict = %+v", failed.Verdict)
|
||||
}
|
||||
|
||||
// 774/S59 (ADR 0587 §7): once the lease is handed away, a joining node is written on the enrolment's warrant with no node to
|
||||
// answer and no ask raised, and placement-changed carries that warrant.
|
||||
b.putJSON(t, buscompose.LeaseBucket, buscompose.ControllerLeaseKey, []byte(`{"handed-to":"mesh-grants","handed-by":"operator"}`))
|
||||
for len(heard) > 0 {
|
||||
<-heard
|
||||
}
|
||||
joined, err := verbs.doPropose(map[string]any{"why": "enrolment of ace", "warrant": `{"node":"ace","address":"10.9.0.2","ask":"enrol-ace-1","warrant":"enrol-ace-1@2026-10-28T20:00:00Z"}`,
|
||||
"changes": `[{"op":"add-node","node":"ace","decided":{"address":"10.9.0.2"},"keys":{"identity":"aWQ=","sealing":"c2Vh"}}]`}, "novox.mesh-enrolment")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
select {
|
||||
case e := <-heard:
|
||||
if e.Proposal.ID != joined.(proposed).Proposal.ID || e.Warrant == nil || e.Warrant.Warrant != "enrol-ace-1@2026-10-28T20:00:00Z" || len(e.Nodes) != 1 || e.Nodes[0].Node != "ace" {
|
||||
t.Errorf("placement-changed for the joining node = %+v", e)
|
||||
}
|
||||
case <-time.After(15 * time.Second):
|
||||
t.Fatal("no placement-changed heard for the joining node")
|
||||
}
|
||||
if ace, err := b.buckets[delivery.PlacementBucket].Get(b.ctx, "ace.node"); err != nil || !strings.Contains(string(ace.Value()), "10.9.0.2") {
|
||||
t.Errorf("the joining node's key = %v, %v", ace, err)
|
||||
}
|
||||
if akey, _ := delivery.PlacementAskKey(joined.(proposed).Proposal.ID); b.hasKey(delivery.AssignerAsksBucket, akey) || b.hasKey(delivery.PlacementProposalsBucket, joined.(proposed).Proposal.ID) {
|
||||
t.Error("an ask was raised for the joining node, or its proposal stays open")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,6 +268,10 @@ func (p *passer) stepProposal(ctx context.Context, prop delivery.PlacementPropos
|
||||
p.endProposal(ctx, prop, "adopt and hand-back are gone since the lease write: placement is the mesh's alone")
|
||||
return
|
||||
}
|
||||
if prop.Warrant != nil {
|
||||
p.addNode(ctx, prop, revs)
|
||||
return
|
||||
}
|
||||
akey, _ := delivery.PlacementAskKey(prop.ID)
|
||||
again := !prop.Again.IsZero() && (prop.Verdict == nil || prop.Again.After(prop.Verdict.At))
|
||||
if prop.State == delivery.ProposalAsked {
|
||||
@@ -544,6 +548,40 @@ func (p *passer) warranted(ctx context.Context, prop delivery.PlacementProposal,
|
||||
p.followWrite(prop, answer, changes)
|
||||
}
|
||||
|
||||
// addNode writes a joining node on the warrant of the enrolment that issued its token (ADR 0587 §7): the operator's Approve
|
||||
// was the issue's, so no node is waited on and no ask is raised. The key is created at the revision it was laid over (none: a
|
||||
// node with a key is no joining node); the enrolment recorded the warrant in the hand-act log when it was given, so this does
|
||||
// not record it again; placement-changed carries the warrant. A key written meanwhile ends the proposal, saying so.
|
||||
func (p *passer) addNode(ctx context.Context, prop delivery.PlacementProposal, revs map[string]uint64) {
|
||||
current, _ := p.seatPlacements()
|
||||
if moved(prop, revs) {
|
||||
p.endProposal(ctx, prop, "a key it was laid over was written since: the enrolment proposes the node again")
|
||||
return
|
||||
}
|
||||
laid, findings := delivery.LayChanges(current, prop.Changes)
|
||||
node := prop.Changes[0].Node
|
||||
value, ok := laid[node]
|
||||
if len(findings) > 0 || !ok {
|
||||
p.endProposal(ctx, prop, "the node cannot be added: "+strings.Join(findings, "; "))
|
||||
return
|
||||
}
|
||||
rev, err := p.writeNode(ctx, node, &value)
|
||||
if err != nil {
|
||||
p.endProposal(ctx, prop, "the node's key was not written: "+err.Error())
|
||||
return
|
||||
}
|
||||
c := delivery.ChangeOf(delivery.Placement{Node: node}, value)
|
||||
c.Before, c.After = prop.Base[node], rev
|
||||
p.deleteCandidates(ctx, prop.ID)
|
||||
p.deletePulses(ctx, prop.ID)
|
||||
_ = p.kvs[delivery.PlacementProposalsBucket].Delete(ctx, prop.ID)
|
||||
p.w.put(delivery.PlacementProposalsBucket, prop.ID, nil)
|
||||
w := prop.Warrant
|
||||
p.sayChanged(prop, delivery.Answer{Option: assign.OptionYes, Warrant: w.Warrant,
|
||||
Words: fmt.Sprintf("the operator approved the machine %s at %s through the enrolment's ask %s", w.Node, w.Address, w.Ask)},
|
||||
[]delivery.PlacementChange{c})
|
||||
}
|
||||
|
||||
// followWrite follows the keys a warrant wrote: the warrant recorded and placement-changed said. No verb of the
|
||||
// controller is called: the placement is the one source of where modules run (ADR 0567 §8, issue 774).
|
||||
func (p *passer) followWrite(prop delivery.PlacementProposal, answer delivery.Answer, changes []delivery.PlacementChange) {
|
||||
|
||||
@@ -217,9 +217,16 @@ func (s *seatTools) doPropose(args map[string]any, by string) (any, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
warrant, err := warrantOf(args["warrant"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
now := s.now()
|
||||
p, laid, findings := assign.Propose(assign.ProposeIn{ID: delivery.NewProposalID(now), By: by, Why: arg(args, "why"), At: now,
|
||||
Changes: changes, Current: current, Revisions: revs, Held: held, Recorded: recorded})
|
||||
Changes: changes, Current: current, Revisions: revs, Held: held, Recorded: recorded, Warrant: warrant})
|
||||
if refusal := delivery.AddNodeRefusal(p, moduleOfCaller(by)); refusal != "" {
|
||||
findings = append(findings, refusal)
|
||||
}
|
||||
if len(findings) > 0 {
|
||||
return nil, fmt.Errorf("the proposal is refused: %s", strings.Join(findings, "; "))
|
||||
}
|
||||
@@ -229,6 +236,40 @@ func (s *seatTools) doPropose(args map[string]any, by string) (any, error) {
|
||||
return proposed{Proposal: p, Follow: followProposal(p.ID)}, nil
|
||||
}
|
||||
|
||||
// warrantOf reads propose's warrant, an enrolment's for an add-node: an object, given as one or as its text; none is none.
|
||||
func warrantOf(v any) (*delivery.AddNodeWarrant, error) {
|
||||
var raw []byte
|
||||
switch w := v.(type) {
|
||||
case nil:
|
||||
return nil, nil
|
||||
case string:
|
||||
if strings.TrimSpace(w) == "" {
|
||||
return nil, nil
|
||||
}
|
||||
raw = []byte(w)
|
||||
default:
|
||||
var err error
|
||||
if raw, err = json.Marshal(w); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var out delivery.AddNodeWarrant
|
||||
dec := json.NewDecoder(strings.NewReader(string(raw)))
|
||||
dec.DisallowUnknownFields()
|
||||
if err := dec.Decode(&out); err != nil {
|
||||
return nil, fmt.Errorf("the warrant is no enrolment's warrant {node, address, ask, warrant}: %w", err)
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// moduleOfCaller is the module a caller is, as the runtime proves it: a module's bus user is `<node>.<module>`.
|
||||
func moduleOfCaller(by string) string {
|
||||
if _, module, ok := strings.Cut(by, "."); ok {
|
||||
return module
|
||||
}
|
||||
return by
|
||||
}
|
||||
|
||||
// doWithdraw is the verb withdraw (§2): the proposal is marked, and the assigner deletes it and its candidates and says
|
||||
// placement-withdrawn carrying it whole.
|
||||
func (s *seatTools) doWithdraw(args map[string]any, by string) (any, error) {
|
||||
|
||||
@@ -150,6 +150,27 @@ func TestTheSeatsVerbs(t *testing.T) {
|
||||
if _, err := s.doPropose(map[string]any{"why": "hold", "changes": `[{"op":"hold","node":"anchor","module":"web","claim":"node-shell","scope":"node"}]`}, "op"); err != nil {
|
||||
t.Errorf("a hold: %v", err)
|
||||
}
|
||||
// 774/S59/AC1 (ADR 0587 §7): an add-node from mesh-enrolment whose warrant names this node and this address is proposed, with
|
||||
// the node a joining one with no record; from another caller, naming another node or address, or with no warrant it is refused.
|
||||
addNode := func(by, warrant string) error {
|
||||
_, err := s.doPropose(map[string]any{"why": "enrolment of ace", "warrant": warrant, "changes": []any{map[string]any{"op": "add-node", "node": "ace",
|
||||
"decided": map[string]any{"address": "10.9.0.2"}, "keys": map[string]any{"identity": "aWQ=", "sealing": "c2Vh"}}}}, by)
|
||||
return err
|
||||
}
|
||||
good := `{"node":"ace","address":"10.9.0.2","ask":"enrol-ace-1","warrant":"enrol-ace-1@2026-10-28T20:00:00Z"}`
|
||||
for _, c := range []struct{ by, warrant, want string }{
|
||||
{"novox.mesh-grants", good, "mesh-enrolment's alone"},
|
||||
{"novox.mesh-enrolment", `{"node":"g14","address":"10.9.0.2","ask":"a","warrant":"a@t"}`, "carries the warrant of g14"},
|
||||
{"novox.mesh-enrolment", `{"node":"ace","address":"10.9.0.9","ask":"a","warrant":"a@t"}`, "carries the warrant of ace at 10.9.0.9"},
|
||||
{"novox.mesh-enrolment", "", "carries no warrant"},
|
||||
} {
|
||||
if err := addNode(c.by, c.warrant); err == nil || !strings.Contains(err.Error(), c.want) {
|
||||
t.Errorf("an add-node by %s with warrant %q: %v, want a refusal saying %q", c.by, c.warrant, err, c.want)
|
||||
}
|
||||
}
|
||||
if err := addNode("novox.mesh-enrolment", good); err != nil {
|
||||
t.Errorf("an add-node from mesh-enrolment: %v", err)
|
||||
}
|
||||
if _, err := s.doHandBack(map[string]any{"why": "back"}, "op"); err == nil || !strings.Contains(err.Error(), "since the lease write") {
|
||||
t.Errorf("a hand-back: %v", err)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ type ProposeIn struct {
|
||||
Revisions map[string]uint64
|
||||
Held func(module string) bool
|
||||
Recorded func(node string) bool
|
||||
// Warrant is the enrolment's warrant an add-node carries (ADR 0587 §7); only that change has one.
|
||||
Warrant *delivery.AddNodeWarrant
|
||||
}
|
||||
|
||||
// Propose lays a proposal over the current keys (§2): the proposal (state answering, each changed node's base revision),
|
||||
@@ -33,7 +35,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) (delivery.PlacementProposal, map[string]*delivery.Placement, []string) {
|
||||
p := delivery.PlacementProposal{ID: in.ID, By: strings.TrimSpace(in.By), Why: strings.TrimSpace(in.Why), At: in.At.UTC(),
|
||||
Changes: in.Changes, Base: map[string]uint64{}, State: delivery.ProposalAnswering}
|
||||
Changes: in.Changes, Warrant: in.Warrant, Base: map[string]uint64{}, State: delivery.ProposalAnswering}
|
||||
var findings []string
|
||||
if p.By == "" {
|
||||
findings = append(findings, "the runtime names no caller: a proposal is made by someone")
|
||||
@@ -75,6 +77,18 @@ func Propose(in ProposeIn) (delivery.PlacementProposal, map[string]*delivery.Pla
|
||||
if recorded == nil {
|
||||
recorded = func(string) bool { return false }
|
||||
}
|
||||
// A joining node writes its record only once it holds its account, which the add-node's write gives it: this one change by
|
||||
// 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 == delivery.OpAddNode {
|
||||
joining[c.Node] = true
|
||||
}
|
||||
}
|
||||
if len(joining) > 0 {
|
||||
was := recorded
|
||||
recorded = func(node string) bool { return joining[node] || was(node) }
|
||||
}
|
||||
for _, r := range delivery.CheckPlacements(all, held, recorded) {
|
||||
findings = append(findings, r)
|
||||
}
|
||||
|
||||
@@ -55,7 +55,9 @@ type Holder struct {
|
||||
// Warrant and Act record in the hand-act log, through the seat mesh-hand-acts: the operator's warrant for an ask, and an
|
||||
// act done by hand (a revoke).
|
||||
Warrant func(ask string) error
|
||||
Act func(what, why, cause string) error
|
||||
// WarrantOf is the warrant's id as the teller wrote it (the ask's id and when), read from the router's record of the ask.
|
||||
WarrantOf func(ask string) (string, error)
|
||||
Act func(what, why, cause string) error
|
||||
// Loaded says whether the bus node loaded the bus user (ADR 0587 §3: redeem answers once node.<node> is in its list).
|
||||
Loaded func(user string) (bool, error)
|
||||
Now func() time.Time
|
||||
@@ -91,32 +93,13 @@ func (h *Holder) say(event string, body any, id string) error {
|
||||
return h.Say(event, body, id)
|
||||
}
|
||||
|
||||
// AddNode is what the holder asks mesh-placement to add (ADR 0587 §7): the node, its address, the public halves of its
|
||||
// keys, and the issue's warrant, which names this node and this address.
|
||||
// AddNode is what the holder asks mesh-placement to add (ADR 0587 §7): the proposal's one change, a joining node with the
|
||||
// address the operator approved and the public halves of its keys, and the issue's warrant, which names this node and this
|
||||
// address (delivery.AddNodeWarrant).
|
||||
type AddNode struct {
|
||||
Node string `json:"node"`
|
||||
Address string `json:"address"`
|
||||
Why string `json:"why"`
|
||||
Keys NodeKeys `json:"keys"`
|
||||
Warrant Warrant `json:"warrant"`
|
||||
}
|
||||
|
||||
// NodeKeys are the public halves of a joining node's keys: identity, private network, sealing and serving, and the
|
||||
// tunnel it found, as delivery.Redeem presents them.
|
||||
type NodeKeys struct {
|
||||
Public string `json:"public_key"`
|
||||
Overlay string `json:"overlay_key,omitempty"`
|
||||
Sealing string `json:"sealing_key,omitempty"`
|
||||
Serving string `json:"serving_key,omitempty"`
|
||||
Tunnel json.RawMessage `json:"tunnel,omitempty"`
|
||||
}
|
||||
|
||||
// Warrant is the issue's warrant as the proposal carries it: the ask the operator answered, the node and the address it
|
||||
// named.
|
||||
type Warrant struct {
|
||||
Ask string `json:"ask"`
|
||||
Node string `json:"node"`
|
||||
Address string `json:"address"`
|
||||
Why string
|
||||
Change delivery.PlacementProposalChange
|
||||
Warrant delivery.AddNodeWarrant
|
||||
}
|
||||
|
||||
// issuing says whether issue is on: only while the controller's lease is handed to a holder that is no controller (ADR 0587
|
||||
|
||||
@@ -211,15 +211,17 @@ func newRig(t *testing.T) *rig {
|
||||
AddNode: func(a AddNode) error {
|
||||
r.added = append(r.added, a)
|
||||
if r.placeErr == nil { // placement writes the node as the assigner does on the warrant
|
||||
r.rd["mesh-delivery-assigner.placement"][a.Node+".node"] = `{"v":1,"node":"` + a.Node + `","decided":{"address":"` + a.Address + `"},"modules":[]}`
|
||||
n := a.Change.Node
|
||||
r.rd["mesh-delivery-assigner.placement"][n+".node"] = `{"v":1,"node":"` + n + `","decided":{"address":"` + a.Change.Decided.Address + `"},"modules":[]}`
|
||||
}
|
||||
return r.placeErr
|
||||
},
|
||||
Warrant: func(ask string) error { r.warr = append(r.warr, ask); return nil },
|
||||
Act: func(what, why, cause string) error { r.acts = append(r.acts, what); return nil },
|
||||
Loaded: func(string) (bool, error) { return r.loaded, nil },
|
||||
Now: func() time.Time { return r.now },
|
||||
Logf: func(f string, a ...any) { t.Logf(f, a...) }}
|
||||
Warrant: func(ask string) error { r.warr = append(r.warr, ask); return nil },
|
||||
WarrantOf: func(ask string) (string, error) { return ask + "@2026-11-02T10:00:00Z", nil },
|
||||
Act: func(what, why, cause string) error { r.acts = append(r.acts, what); return nil },
|
||||
Loaded: func(string) (bool, error) { return r.loaded, nil },
|
||||
Now: func() time.Time { return r.now },
|
||||
Logf: func(f string, a ...any) { t.Logf(f, a...) }}
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
@@ -92,13 +92,15 @@ func newHolder() *Holder {
|
||||
call := Call(stdio.Ask)
|
||||
rec := handacts.Recorder{Ask: stdio.Ask}
|
||||
rd := runtimeReader{}
|
||||
client := asker.New("asked")
|
||||
return &Holder{
|
||||
Store: runtimeStore{}, Read: rd, Vault: vaultOver{call: call, read: rd}, Asker: asker.New("asked"),
|
||||
Store: runtimeStore{}, Read: rd, Vault: vaultOver{call: call, read: rd}, Asker: client,
|
||||
Say: func(event string, body any, id string) error {
|
||||
return stdio.SeatPublish(SeatEventSubject(event), body, id)
|
||||
},
|
||||
AddNode: addNodeOver(call),
|
||||
Warrant: func(ask string) error { _, err := rec.RecordWarrant(Module, ask); return err },
|
||||
AddNode: addNodeOver(call),
|
||||
WarrantOf: warrantOf(client),
|
||||
Warrant: func(ask string) error { _, err := rec.RecordWarrant(Module, ask); return err },
|
||||
Act: func(what, why, cause string) error {
|
||||
_, err := rec.Record(handacts.Request{What: what, Why: why, Cause: cause})
|
||||
return err
|
||||
@@ -110,6 +112,20 @@ func newHolder() *Holder {
|
||||
}
|
||||
}
|
||||
|
||||
// warrantOf is the warrant id the teller writes, `<ask>@<when>`, read from the router's record of the ask.
|
||||
func warrantOf(c *asker.Client) func(string) (string, error) {
|
||||
return func(ask string) (string, error) {
|
||||
state, w, err := c.Where(ask)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if w == nil {
|
||||
return "", fmt.Errorf("the router records the ask %s as %q, with no warrant", ask, state)
|
||||
}
|
||||
return ask + "@" + w.At.UTC().Format(time.RFC3339Nano), nil
|
||||
}
|
||||
}
|
||||
|
||||
// loadedVia says a bus user is loaded when the grants part's last pass lists it among the composed users and the bus node's
|
||||
// loaded list holds it (its credentials row says loaded).
|
||||
func loadedVia(rd Reader) func(string) (bool, error) {
|
||||
|
||||
@@ -10,11 +10,6 @@ import (
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// keysOf are the public halves a redeem presented.
|
||||
func keysOf(a delivery.Redeem) NodeKeys {
|
||||
return NodeKeys{Public: a.PublicKey, Overlay: a.OverlayKey, Sealing: a.SealingKey, Serving: a.ServingKey, Tunnel: a.Tunnel}
|
||||
}
|
||||
|
||||
// Redeem is the joining node-engine's one call (ADR 0587 §5, §6). It answers only a caller the runtime proves to be
|
||||
// enrol.<node>, for a key at stage live (or at redeeming with the same keys' digest), checks the proof against the identity key
|
||||
// presented, and then, each step read back and none repeated that is done: spends the token (the key to redeeming, by
|
||||
@@ -50,7 +45,7 @@ func (h *Holder) Redeem(caller string, a delivery.Redeem) (delivery.RedeemAnswer
|
||||
if err != nil {
|
||||
return delivery.RedeemAnswer{}, fmt.Errorf("the vault did not sign the certificate of %s (call redeem again with the same keys): %w", e.Node, err)
|
||||
}
|
||||
if err := h.place(e, keysOf(a)); err != nil {
|
||||
if err := h.place(e, a); err != nil {
|
||||
return delivery.RedeemAnswer{}, err
|
||||
}
|
||||
if h.Loaded != nil {
|
||||
@@ -122,7 +117,7 @@ func (h *Holder) spend(node, digest string) (Enrolment, error) {
|
||||
}
|
||||
|
||||
// place writes the node into placement with the issue's warrant, unless placement holds it at the approved address already.
|
||||
func (h *Holder) place(e Enrolment, keys NodeKeys) error {
|
||||
func (h *Holder) place(e Enrolment, a delivery.Redeem) error {
|
||||
placements, err := h.placements()
|
||||
if err != nil {
|
||||
return fmt.Errorf("placement cannot be read (call redeem again with the same keys): %w", err)
|
||||
@@ -130,11 +125,17 @@ func (h *Holder) place(e Enrolment, keys NodeKeys) error {
|
||||
if p, ok := placements[e.Node]; ok && p.Decided.Address == e.Address {
|
||||
return nil
|
||||
}
|
||||
if h.AddNode == nil {
|
||||
if h.AddNode == nil || h.WarrantOf == nil {
|
||||
return errors.New("no route to mesh-placement")
|
||||
}
|
||||
if err := h.AddNode(AddNode{Node: e.Node, Address: e.Address, Why: "enrolment of " + e.Node + ": " + e.Why, Keys: keys,
|
||||
Warrant: Warrant{Ask: e.Ask, Node: e.Node, Address: e.Address}}); err != nil {
|
||||
warrant, err := h.WarrantOf(e.Ask)
|
||||
if err != nil {
|
||||
return fmt.Errorf("the operator's warrant for %s cannot be read from the router (call redeem again with the same keys): %w", e.Ask, err)
|
||||
}
|
||||
change := delivery.PlacementProposalChange{Op: delivery.OpAddNode, Node: e.Node, Decided: &delivery.NodeDecided{Address: e.Address},
|
||||
Keys: &delivery.JoiningKeys{Identity: a.PublicKey, Overlay: a.OverlayKey, Sealing: a.SealingKey, Serving: a.ServingKey}}
|
||||
if err := h.AddNode(AddNode{Why: "enrolment of " + e.Node + ": " + e.Why, Change: change,
|
||||
Warrant: delivery.AddNodeWarrant{Node: e.Node, Address: e.Address, Ask: e.Ask, Warrant: warrant}}); err != nil {
|
||||
return fmt.Errorf("%w (call redeem again with the same keys)", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -77,7 +77,8 @@ func TestRedeemRefusesWhatItMustAndSpendsTheTokenOnce(t *testing.T) {
|
||||
if e.Stage != StageRedeeming || e.KeysDigest != good.KeysDigest() || r.st.ttl[NodeKey("ace")] != 0 || r.st.ttl[AddressKey("10.9.0.2")] != 0 {
|
||||
t.Errorf("spent %+v, ttl %v/%v", e, r.st.ttl[NodeKey("ace")], r.st.ttl[AddressKey("10.9.0.2")])
|
||||
}
|
||||
if len(r.added) != 1 || r.added[0].Warrant != (Warrant{Ask: "ask-ace", Node: "ace", Address: "10.9.0.2"}) || ans.Signature == "" || ans.Account != "account-ace" || ans.Login != "node.ace" || ans.Signer != "vault-signer" || ans.Certificate != "PEM-serving-a" {
|
||||
if len(r.added) != 1 || r.added[0].Warrant != (delivery.AddNodeWarrant{Ask: "ask-ace", Node: "ace", Address: "10.9.0.2", Warrant: "ask-ace@2026-11-02T10:00:00Z"}) ||
|
||||
r.added[0].Change.Keys.Digest() != good.KeysDigest() || r.added[0].Change.Decided.Address != "10.9.0.2" || ans.Signature == "" || ans.Account != "account-ace" || ans.Login != "node.ace" || ans.Signer != "vault-signer" || ans.Certificate != "PEM-serving-a" {
|
||||
t.Errorf("placement %+v, answer %+v", r.added, ans)
|
||||
}
|
||||
r.loaded = false
|
||||
|
||||
@@ -95,12 +95,9 @@ func (v vaultOver) Signer() (string, error) {
|
||||
// addNodeOver asks mesh-placement to add the node with the issue's warrant (ADR 0587 §7).
|
||||
func addNodeOver(call Call) func(AddNode) error {
|
||||
return func(a AddNode) error {
|
||||
_, err := call(placementAdd, map[string]any{
|
||||
"why": a.Why,
|
||||
"changes": []any{map[string]any{"op": "add-node", "node": a.Node, "address": a.Address, "keys": a.Keys, "warrant": a.Warrant}},
|
||||
})
|
||||
_, err := call(placementAdd, map[string]any{"why": a.Why, "changes": []any{a.Change}, "warrant": a.Warrant})
|
||||
if err != nil {
|
||||
return fmt.Errorf("mesh-placement did not take the node %s: %w", a.Node, err)
|
||||
return fmt.Errorf("mesh-placement did not take the node %s: %w", a.Change.Node, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user