Merge pull request 'The vault reads a machine's placement once per answer and says how long each answer took (issue 774)' (#576) from fix/vault-own-loop into main

This commit was merged in pull request #576.
This commit is contained in:
2026-10-31 00:40:42 +00:00
3 changed files with 64 additions and 10 deletions
+36 -5
View File
@@ -11,8 +11,10 @@ import (
"encoding/json"
"errors"
"fmt"
"slices"
"sort"
"strings"
"time"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/catalogue"
@@ -87,7 +89,7 @@ func (v *Vault) credentialRefusal(node string, c CredentialAsked, plan *planRead
}
// The consuming module stands on the machine: read from the placement and the assignments, never assumed (one
// that cannot be read refuses). The assignments' mirror can be behind, so one it does not hold is confirmed directly.
placed, err := v.placedOn(node, c.ConsumerModule)
placed, err := plan.placedOn(c.ConsumerModule)
if err != nil {
return fmt.Sprintf(notYetPrefix+"where %s stands could not be read: %v", c.ConsumerModule, err)
}
@@ -122,13 +124,19 @@ func (v *Vault) credentialRefusal(node string, c CredentialAsked, plan *planRead
}
// planReader reads the assignments of one machine's modules for one answer of `own`, each module's once (issue 1126,
// 2026-10-28: one read per own secret and per credential, 203 on novox, held the answer past the node-engine's bound).
// 2026-10-28: one read per own secret and per credential, 203 on novox, held the answer past the node-engine's bound),
// and the machine's placement once (issue 774, 2026-10-31: read once per credential, 143 bus reads in one answer to ace).
type planReader struct {
node string
reader AssignmentReader
confirm AssignmentReader
why string
got map[string]planRead
// placement reads the machine's placed modules; placed and placedErr keep the one read for the answer.
placement func() ([]string, error)
placedRead bool
placed []string
placedErr error
}
type planRead struct {
@@ -142,7 +150,17 @@ func (v *Vault) plansOf(node string) *planReader {
if d, ok := v.Askers.(Declared); ok && d.Confirm != nil {
confirm = standing(d.Confirm)
}
return &planReader{node: node, confirm: confirm, reader: read, why: why, got: map[string]planRead{}}
return &planReader{node: node, confirm: confirm, reader: read, why: why, got: map[string]planRead{},
placement: func() ([]string, error) { return v.placedModules(node) }}
}
// placedOn says module is placed on the machine by the catalogue, from the machine's placement read once for the answer.
func (p *planReader) placedOn(module string) (bool, error) {
if !p.placedRead {
p.placed, p.placedErr = p.placement()
p.placedRead = true
}
return slices.Contains(p.placed, module), p.placedErr
}
// read is module's assignment on the machine, nil when there is none, or why the vault reads none.
@@ -222,7 +240,22 @@ func composeOrigin(origin string) string {
}
// OwnOfNode answers the node-engine of a machine the own secrets of its modules, each sealed to the machine's stated key.
// Every answer says how long it took, and a refusal is said too (issue 774, 2026-10-31: ace's asks went unanswered past
// the node-engine's 15 s and the vault's journal named none of them, so whether they arrived could not be told).
func (v *Vault) OwnOfNode(caller string, asked ...CredentialAsked) (OwnAnswer, error) {
began := v.Now()
a, err := v.ownOfNode(caller, asked...)
took := v.Now().Sub(began).Round(time.Millisecond)
if err != nil {
v.log("own for %s, %d credentials asked, refused after %s: %v", caller, len(asked), took, err)
return a, err
}
v.log("handed %s's node-engine the %d own secrets of its modules and %d credentials (%d not held), sealed to its key, in %s",
a.Node, len(a.Own), len(a.Credentials), len(a.Missing), took)
return a, nil
}
func (v *Vault) ownOfNode(caller string, asked ...CredentialAsked) (OwnAnswer, error) {
node, ok := nodeEngineOf(caller)
if !ok {
if caller == "" || caller == stdio.Unproven {
@@ -328,8 +361,6 @@ func (v *Vault) OwnOfNode(caller string, asked ...CredentialAsked) (OwnAnswer, e
return OwnAnswer{}, fmt.Errorf("nothing is handed out to %s: who was handed its %d values could not be recorded: %w",
caller, len(handings), err)
}
v.log("handed %s's node-engine the %d own secrets of its modules and %d credentials (%d not held), sealed to its key",
node, len(out.Own), len(out.Credentials), len(out.Missing))
return out, nil
}
@@ -2,6 +2,7 @@ package main
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
@@ -143,6 +144,11 @@ func TestOwnReadsEachAssignmentOnceAndSyncsItsRecordOnce(t *testing.T) {
t.Fatal(err)
}
}
// Issue 774, 2026-10-31 (ace's 143 credentials went unanswered past the node-engine's 15 s while novox's asked every
// 8 s): the machine's placement, one key, is read once per answer too, never once per credential.
placed := placementsOf(t, map[string][]string{"anchor": {"web"}})
placementReads := 0
v.Placement = PlacementReader{Keys: placed.Keys, Read: func(key string) ([]byte, error) { placementReads++; return placed.Read(key) }}
for k := range reads {
delete(reads, k)
}
@@ -161,6 +167,10 @@ func TestOwnReadsEachAssignmentOnceAndSyncsItsRecordOnce(t *testing.T) {
if len(reads) != 2 {
t.Errorf("the answer read %v, want each of anchor's two modules once", reads)
}
if placementReads != 1 {
t.Errorf("one answer of two credentials read anchor's placement %d times, want once", placementReads)
}
v.Placement = PlacementReader{}
if n := v.Store.syncs - syncs; n != 1 {
t.Errorf("one answer synced the record %d times", n)
}
@@ -182,9 +192,15 @@ func TestOwnReadsEachAssignmentOnceAndSyncsItsRecordOnce(t *testing.T) {
if err := os.Mkdir(filepath.Join(v.dir, "store", "handed.jsonl"), 0o700); err != nil {
t.Fatal(err)
}
// And the refusal is said in the vault's journal with how long it took, as every answer is.
var said []string
v.Log = func(format string, args ...any) { said = append(said, fmt.Sprintf(format, args...)) }
if a, err := v.OwnOfNode("node.anchor"); err == nil {
t.Errorf("handed with no record of it: %+v", a)
}
if last := strings.Join(said, "\n"); !strings.Contains(last, "own for node.anchor, 0 credentials asked, refused after ") {
t.Errorf("a refused own was said as %q", last)
}
}
func mustHanded(t *testing.T, v testVault) []Handing {
+12 -5
View File
@@ -58,22 +58,29 @@ func (v *Vault) placements() (map[string]delivery.Placement, error) {
// placedOn says module is placed on node by the catalogue.
func (v *Vault) placedOn(node, module string) (bool, error) {
placed, err := v.placedModules(node)
return slices.Contains(placed, module), err
}
// placedModules is every module the catalogue places on node, read from the node's one key of the placement; none when
// the vault reads no placement or the node has no key.
func (v *Vault) placedModules(node string) ([]string, error) {
if v.Placement.Read == nil {
return false, nil
return nil, nil
}
key, err := delivery.PlacementKey(node)
if err != nil {
return false, err
return nil, err
}
raw, err := v.Placement.Read(key)
if err != nil || raw == nil {
return false, err
return nil, err
}
p, err := delivery.DecodePlacement(raw)
if err != nil {
return false, fmt.Errorf("the placement of %s is not readable: %w", node, err)
return nil, fmt.Errorf("the placement of %s is not readable: %w", node, err)
}
return slices.Contains(p.Assigned(), module), nil
return p.Assigned(), nil
}
// standsOn says module stands on node: placed there, or assigned there while the assignments stand. A read that fails is