AM-D3 path: the assignment half carries each node's readings with the runner's fingerprint
The grants part reads a node's capability readings and the runner that announced them from the assignment half; the assigner reads both from the node record it already holds, the digest over all of it. Builds: design 46 7 §6 (the assignment half carries, for each node, its capability readings, each with the fingerprint of the runner that announced it), design 47 G §194, lead answer A-203 (readings, fingerprint) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -79,23 +79,36 @@ func (p *passer) keepGraph(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// capabilityDigests is the digest of each node's capability reading, the statement its engine made, by node.
|
||||
// capabilityDigests is each node's capability reading, by node: the statement its engine made of what it reads, and the
|
||||
// capabilities its tool runner states with the fingerprint of the runner that announced them, as the node's record carries
|
||||
// them (design 46 approval mark §6, lead answer A-203), the digest over all of it.
|
||||
func (p *passer) capabilityDigests() []meshwants.AssignmentRevisionCapabilitiesItem {
|
||||
if p.reads == nil {
|
||||
return nil
|
||||
}
|
||||
records := p.w.records()
|
||||
known := p.w.knownNodes()
|
||||
slices.Sort(known)
|
||||
var out []meshwants.AssignmentRevisionCapabilitiesItem
|
||||
for _, node := range known {
|
||||
if !p.reads.Known(node) {
|
||||
read := p.reads != nil && p.reads.Known(node)
|
||||
rec, recorded := records[node]
|
||||
if !read && !recorded {
|
||||
continue
|
||||
}
|
||||
reads := slices.Clone(p.reads.Reads([]string{node})[node])
|
||||
slices.Sort(reads)
|
||||
raw, _ := json.Marshal(reads)
|
||||
var reads []string
|
||||
if read {
|
||||
reads = slices.Clone(p.reads.Reads([]string{node})[node])
|
||||
slices.Sort(reads)
|
||||
}
|
||||
item := meshwants.AssignmentRevisionCapabilitiesItem{Node: node, Readings: slices.Clone(rec.RunnerCapabilities),
|
||||
Fingerprint: rec.RunnerFingerprint}
|
||||
slices.Sort(item.Readings)
|
||||
raw, _ := json.Marshal(struct {
|
||||
Reads []string `json:"reads"`
|
||||
Readings []string `json:"readings"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
}{reads, item.Readings, item.Fingerprint})
|
||||
sum := sha256.Sum256(raw)
|
||||
out = append(out, meshwants.AssignmentRevisionCapabilitiesItem{Node: node, Digest: "sha256:" + hex.EncodeToString(sum[:])})
|
||||
item.Digest = "sha256:" + hex.EncodeToString(sum[:])
|
||||
out = append(out, item)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
mcc "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
mwc "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshwantsclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
@@ -37,6 +39,14 @@ func TestTheWantsAreSaidWantedFromTheKeptAssignmentGraphRevision(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
placeOn(t, p.w, "ace", "web")
|
||||
// Design 46 approval mark §6: a node's reading keeps the fingerprint of the runner that announced it, as its record says.
|
||||
rec, _ := json.Marshal(nodeengine.HaveByNodeNodeValue{V: 1, Record: &nodeengine.NodeRecord{V: 1, Node: "ace",
|
||||
RunnerCapabilities: []string{"bus-state-calls", "approval-mark/1"}, RunnerFingerprint: "sha256:runner"}})
|
||||
p.w.put(buckets.Have, "ace.node", rec)
|
||||
if c := p.capabilityDigests(); len(c) != 1 || c[0].Node != "ace" || c[0].Fingerprint != "sha256:runner" ||
|
||||
!slices.Equal(c[0].Readings, []string{"approval-mark/1", "bus-state-calls"}) || !strings.HasPrefix(c[0].Digest, "sha256:") {
|
||||
t.Errorf("the capability readings are %+v", c)
|
||||
}
|
||||
half := mcc.Half{Revision: meshcatalog.CatalogRevision{Branch: trunkBranch, Kind: "trunk", Revision: 4, Source: 9,
|
||||
Entries: []meshcatalog.EntryRef{{Module: "web", Digest: "sha256:" + strings.Repeat("a", 64)}}},
|
||||
Entries: map[string]meshcatalog.CatalogEntry{"web": {Module: "web", Fp: fpTest}}}
|
||||
|
||||
@@ -202,5 +202,8 @@ func EntryDigest(e meshwants.AssignmentEntry) string {
|
||||
// Same says two revisions list the same entries computed from the same inputs, so the second is no new revision.
|
||||
func Same(a, b meshwants.AssignmentRevision) bool {
|
||||
return slices.Equal(a.Entries, b.Entries) && slices.Equal(a.Catalogs, b.Catalogs) && a.Placements == b.Placements &&
|
||||
slices.Equal(a.Capabilities, b.Capabilities)
|
||||
slices.EqualFunc(a.Capabilities, b.Capabilities, func(x, y meshwants.AssignmentRevisionCapabilitiesItem) bool {
|
||||
// A reading's digest is over all of it: its readings and its runner's fingerprint among it.
|
||||
return x.Node == y.Node && x.Digest == y.Digest
|
||||
})
|
||||
}
|
||||
|
||||
@@ -481,9 +481,13 @@ type AssignmentEntry struct {
|
||||
|
||||
// AssignmentRevisionCapabilitiesItem is a type of the contract.
|
||||
type AssignmentRevisionCapabilitiesItem struct {
|
||||
// the digest of the node's capability reading
|
||||
// the digest of the node's capability reading: the engine's manifest reads, its tool runner's readings and that runner's fingerprint
|
||||
Digest string `json:"digest"`
|
||||
Node string `json:"node"`
|
||||
// the fingerprint of the tool runner that announced the readings; empty when none is known
|
||||
Fingerprint string `json:"fingerprint,omitempty"`
|
||||
Node string `json:"node"`
|
||||
// the capabilities the node's tool runner states of the calls it answers, as the node's record carries them (design 46 approval mark 6)
|
||||
Readings []string `json:"readings,omitempty"`
|
||||
}
|
||||
|
||||
// AssignmentRevision is an assignment graph revision: the list of its entries' digests and the inputs it was computed from, so it can be computed again and compared.
|
||||
|
||||
@@ -117,7 +117,7 @@ func TestFixtures(t *testing.T) {
|
||||
decode: func(b []byte) (any, error) { var v RevisionInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
|
||||
{name: "revision asks a candidate's", subject: "", given: "{\"group\":\"feat/47-G\",\"heads\":\"0123456789ab\"}", wire: "{\"group\":\"feat/47-G\",\"heads\":\"0123456789ab\"}",
|
||||
decode: func(b []byte) (any, error) { var v RevisionInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
|
||||
{name: "revision answers one revision naming its inputs", subject: "", given: "{\"revision\":{\"at\":\"2026-11-09T19:00:00Z\",\"capabilities\":[{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"node\":\"n1\"}],\"catalogs\":[{\"branch\":\"main\",\"revision\":12,\"source\":3589}],\"cause\":\"catalog-revision\",\"entries\":[{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"module\":\"xclip\",\"node\":\"n1\"}],\"kind\":\"trunk\",\"placements\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"revision\":40,\"v\":1}}", wire: "{\"revision\":{\"at\":\"2026-11-09T19:00:00Z\",\"capabilities\":[{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"node\":\"n1\"}],\"catalogs\":[{\"branch\":\"main\",\"revision\":12,\"source\":3589}],\"cause\":\"catalog-revision\",\"entries\":[{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"module\":\"xclip\",\"node\":\"n1\"}],\"kind\":\"trunk\",\"placements\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"revision\":40,\"v\":1}}",
|
||||
{name: "revision answers one revision naming its inputs", subject: "", given: "{\"revision\":{\"at\":\"2026-11-09T19:00:00Z\",\"capabilities\":[{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"fingerprint\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"node\":\"n1\",\"readings\":[\"approval-mark/1\",\"bus-state-calls\"]}],\"catalogs\":[{\"branch\":\"main\",\"revision\":12,\"source\":3589}],\"cause\":\"catalog-revision\",\"entries\":[{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"module\":\"xclip\",\"node\":\"n1\"}],\"kind\":\"trunk\",\"placements\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"revision\":40,\"v\":1}}", wire: "{\"revision\":{\"at\":\"2026-11-09T19:00:00Z\",\"capabilities\":[{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"fingerprint\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"node\":\"n1\",\"readings\":[\"approval-mark/1\",\"bus-state-calls\"]}],\"catalogs\":[{\"branch\":\"main\",\"revision\":12,\"source\":3589}],\"cause\":\"catalog-revision\",\"entries\":[{\"digest\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"module\":\"xclip\",\"node\":\"n1\"}],\"kind\":\"trunk\",\"placements\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"revision\":40,\"v\":1}}",
|
||||
decode: func(b []byte) (any, error) { var v RevisionAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
|
||||
{name: "entry asks by node and module in a revision", subject: "", given: "{\"module\":\"xclip\",\"node\":\"n1\",\"revision\":40}", wire: "{\"module\":\"xclip\",\"node\":\"n1\",\"revision\":40}",
|
||||
decode: func(b []byte) (any, error) { var v EntryInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
|
||||
|
||||
@@ -31,7 +31,12 @@
|
||||
"capabilities": [
|
||||
{
|
||||
"node": "n1",
|
||||
"digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
"digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"readings": [
|
||||
"approval-mark/1",
|
||||
"bus-state-calls"
|
||||
],
|
||||
"fingerprint": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
}
|
||||
],
|
||||
"cause": "catalog-revision",
|
||||
|
||||
@@ -899,7 +899,18 @@
|
||||
},
|
||||
"digest": {
|
||||
"type": "string",
|
||||
"description": "the digest of the node's capability reading"
|
||||
"description": "the digest of the node's capability reading: the engine's manifest reads, its tool runner's readings and that runner's fingerprint"
|
||||
},
|
||||
"readings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "the capabilities the node's tool runner states of the calls it answers, as the node's record carries them (design 46 approval mark 6)"
|
||||
},
|
||||
"fingerprint": {
|
||||
"type": "string",
|
||||
"description": "the fingerprint of the tool runner that announced the readings; empty when none is known"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -150,9 +150,13 @@ export interface AssignmentEntry {
|
||||
}
|
||||
|
||||
export interface AssignmentRevisionCapabilitiesItem {
|
||||
/** the digest of the node's capability reading */
|
||||
/** the digest of the node's capability reading: the engine's manifest reads, its tool runner's readings and that runner's fingerprint */
|
||||
"digest": string;
|
||||
/** the fingerprint of the tool runner that announced the readings; empty when none is known */
|
||||
"fingerprint"?: string;
|
||||
"node": string;
|
||||
/** the capabilities the node's tool runner states of the calls it answers, as the node's record carries them (design 46 approval mark 6) */
|
||||
"readings"?: string[];
|
||||
}
|
||||
|
||||
/** an assignment graph revision: the list of its entries' digests and the inputs it was computed from, so it can be computed again and compared */
|
||||
|
||||
Reference in New Issue
Block a user