diff --git a/internal/link/have.go b/internal/link/have.go index f1a5a696..bc261de0 100644 --- a/internal/link/have.go +++ b/internal/link/have.go @@ -311,7 +311,7 @@ func (q *Session) nodeFactsWithRecord(r Report, before *nodeengine.HaveByNodeNod n := nodeFacts(r) if q.Keys != nil { keys := q.Keys() - keys.Stated = q.runnerStated + keys.Stated, keys.StatedBy = q.runnerStated, q.runnerFingerprint n.Record = recordOf(q.Membership.Node, r, keys) } if before != nil && before.Record != nil { diff --git a/internal/link/have_test.go b/internal/link/have_test.go index 132fa730..a4f0bd16 100644 --- a/internal/link/have_test.go +++ b/internal/link/have_test.go @@ -344,7 +344,8 @@ func TestTheNodeKeyCarriesTheNodesFacts(t *testing.T) { keys := RecordKeys{TunnelKey: "tk", SealingKey: "sk", ServingKey: "pk", Certificate: "cert", Trusts: []string{"controller key sha256:aa", "vault key sha256:bb"}} // and what it trusts (L3 s6, ADR 0550 §2) q.Keys = func() RecordKeys { return keys } - l.stated = map[string]string{CapabilitiesKey: "ledger-calls, bus-state-calls"} // the tool runner's calls (ENG-2) + l.stated = map[string]string{CapabilitiesKey: "ledger-calls, bus-state-calls", // the tool runner's calls (ENG-2) + FingerprintKey: "sha256:runner"} // and the runner that stated them (design 46 approval mark §6) rep.Profile = map[string]any{"capabilities": []any{map[string]any{"name": "systemd", "present": true}, map[string]any{"name": "docker", "present": false}, map[string]any{"name": "apt", "present": true}}, "machine": map[string]any{"networks": []any{map[string]any{"link": "eth0", "network": "192.0.2.0/24"}}}} @@ -357,7 +358,8 @@ func TestTheNodeKeyCarriesTheNodesFacts(t *testing.T) { wantRecord := nodeengine.NodeRecord{V: sdkkeys.Version, Node: "n1", TunnelKey: "tk", SealingKey: "sk", ServingKey: "pk", Certificate: "cert", Trusts: keys.Trusts, Capabilities: []string{"apt", "bus-state-calls", "ledger-calls", "systemd", CapabilityWantParts}, OutwardLinks: []string{"eth0"}, NetworksSaid: true, Networks: []nodeengine.AttachedNetwork{{Link: "eth0", Network: "192.0.2.0/24"}}, Contract: NodeKeyContract, - ManifestReads: catalogue.ManifestReads()} // what it reads of a manifest: its contract's (ADR 0614 §4) + ManifestReads: catalogue.ManifestReads(), // what it reads of a manifest: its contract's (ADR 0614 §4) + RunnerCapabilities: []string{"bus-state-calls", "ledger-calls"}, RunnerFingerprint: "sha256:runner"} if !nodeengineclient.SameNodeRecord(*got.Record, wantRecord) { t.Errorf("record = %+v, want %+v", *got.Record, wantRecord) } diff --git a/internal/link/record.go b/internal/link/record.go index bd93ea82..8cf26bc6 100644 --- a/internal/link/record.go +++ b/internal/link/record.go @@ -33,6 +33,9 @@ type RecordKeys struct { // Stated is the capabilities its tool runner states of the calls it answers (CapabilitiesKey), which the record says // beside the machine's own: a module whose manifest asks for one is held off a machine whose runner does not state it. Stated []string + // StatedBy is the fingerprint of the tool runner that stated Stated, as its PING carries it (FingerprintKey): the record + // takes both, so a reading is tied to the runner that made it (design 46 approval mark §6, lead answer A-203). + StatedBy string } // buildReads are the words and forms this engine reads of a manifest (novox/hq ADR 0614 §4): the reader capabilities of @@ -135,7 +138,13 @@ func recordOf(node string, r Report, keys RecordKeys) *nodeengine.NodeRecord { if c != "" && !slices.Contains(rec.Capabilities, c) { rec.Capabilities = append(rec.Capabilities, c) } + if c != "" { + rec.RunnerCapabilities = append(rec.RunnerCapabilities, c) + } } + // The runner's own reading beside the merge, which stays for placement (design 46 approval mark §6, lead answer A-203). + sort.Strings(rec.RunnerCapabilities) + rec.RunnerFingerprint = keys.StatedBy sort.Strings(rec.Capabilities) if p.Machine != nil { rec.NetworksSaid = true diff --git a/internal/link/session.go b/internal/link/session.go index 4c2e3704..986c5cc4 100644 --- a/internal/link/session.go +++ b/internal/link/session.go @@ -92,6 +92,8 @@ type Session struct { // runnerStated is the capabilities this machine's tool runner last stated of the calls it answers (askRunner), said in the // node's record. Held under saying. runnerStated []string + // runnerFingerprint is the fingerprint of the runner that stated runnerStated (FingerprintKey). Held under saying. + runnerFingerprint string // saying is held while a facts event is noted, so the haves are written in the order the facts are made. saying sync.Mutex @@ -581,5 +583,5 @@ func (q *Session) askRunner(ctx context.Context, bus Bus) { stated = append(stated, c) } } - q.runnerStated = stated + q.runnerStated, q.runnerFingerprint = stated, strings.TrimSpace(md[FingerprintKey]) } diff --git a/internal/link/witnessing.go b/internal/link/witnessing.go index 14cd1ecc..455bfd12 100644 --- a/internal/link/witnessing.go +++ b/internal/link/witnessing.go @@ -56,6 +56,9 @@ func (l *natsLink) Ping(ctx context.Context, service, id string) error { const ( ToolRunnerService = "node-tools" CapabilitiesKey = "capabilities" + // FingerprintKey is the metadata key of its PING it states its own fingerprint under, beside CapabilitiesKey (design 46 + // approval mark §6, lead answer A-203). + FingerprintKey = "fingerprint" ) // Stater is a link that can ask what a service's instance states of itself in its PING: its metadata.