From 85ba0a9a70d80651a87c3d868dc1ba06b2ece16e Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 3 Nov 2026 23:18:22 +0100 Subject: [PATCH] =?UTF-8?q?A=20node=20record=20states=20the=20reader=20cap?= =?UTF-8?q?abilities=20its=20engine=20reads,=20a=20module=20value=20the=20?= =?UTF-8?q?ones=20its=20manifest=20needs,=20and=20a=20want=20of=20rule=209?= =?UTF-8?q?=20or=2010=20reads=20(ADR=200614=20=C2=A72,=20=C2=A74,=20issue?= =?UTF-8?q?=20774)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NodeRecord.ManifestReads (`manifest-reads`) and Module.Needs (`needs`) are additive at Version 1, read leniently by every running part. checkRule takes 1 to 10 so a later release may number the verb wait and the capability hold; nothing writes 9 or 10 in this one. kept_until_delivered.go stays: this release still builds beside mesh-host 798d73e, and its head comment names the next mesh-sdk release as the one that removes it. Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn --- go/catalogue/kept_until_delivered.go | 9 +++++---- go/delivery/assigner.go | 10 ++++++++-- go/delivery/assigner_test.go | 6 ++++-- go/delivery/noderecord.go | 9 +++++++++ go/delivery/noderecord_test.go | 4 +++- go/delivery/source.go | 4 ++++ go/delivery/source_test.go | 2 ++ go/delivery/want_composed_test.go | 2 +- 8 files changed, 36 insertions(+), 10 deletions(-) diff --git a/go/catalogue/kept_until_delivered.go b/go/catalogue/kept_until_delivered.go index c5205e1..0f0e0fa 100644 --- a/go/catalogue/kept_until_delivered.go +++ b/go/catalogue/kept_until_delivered.go @@ -2,10 +2,11 @@ package catalogue // This file keeps, deprecated, the exported names feat/l3b-bus removed from this package while a running part still // uses them: an exported name is removed only after every user of it is delivered. The checker builds the running -// node-engine's composer (mesh-host main) beside this mesh-sdk (ADR 0548 §6), and mesh-host main, mesh-delivery main -// (the indexer's judge, the assigner's and mesh-grants' code and tests) and mesh-tools main (node-tools' tests) name -// them. Nothing in L3's own heads uses any of them. The second group removes this file, and its exemption in -// TestNoCatalogueManifestNamesAmqp, once mesh-host a24a1d72 and L3's mesh-delivery and mesh-tools heads run. +// node-engine's composer (mesh-host main) beside this mesh-sdk (ADR 0548 §6), and mesh-host main names them +// (RuntimeModule, CoreCredentialModules, FromBus). Nothing in the final L3 release's heads uses any of them, but that +// release still builds beside mesh-host 798d73e, so it keeps this file. The next release that changes mesh-sdk, once +// the final L3 release's node-engine runs on every node, removes this file and its exemption in +// TestNoCatalogueManifestNamesAmqp. import ( "fmt" diff --git a/go/delivery/assigner.go b/go/delivery/assigner.go index 3f439a2..1e26c9c 100644 --- a/go/delivery/assigner.go +++ b/go/delivery/assigner.go @@ -146,9 +146,15 @@ const ( RuleProvisions = 8 ) +// ruleRead is the highest rule a reader takes (novox/hq ADR 0614 §2, §4): 9 and 10 are kept for the verb wait and the +// capability hold, which a later release gives numbers of their own, so a want of that release reads on every node +// that runs this one. Nothing writes 9 or 10 yet: the verb wait is held under RuleProvisions and the capability hold +// under RuleComposed, each with words naming its edge. +const ruleRead = 10 + func checkRule(rule int, of string) error { - if rule < RuleBuildDone || rule > RuleProvisions { - return fmt.Errorf("delivery: %s names the rule %d, which is not one of the eight (1 to 8)", of, rule) + if rule < RuleBuildDone || rule > ruleRead { + return fmt.Errorf("delivery: %s names the rule %d, which is not one of 1 to %d", of, rule, ruleRead) } return nil } diff --git a/go/delivery/assigner_test.go b/go/delivery/assigner_test.go index b26279f..dfad9f2 100644 --- a/go/delivery/assigner_test.go +++ b/go/delivery/assigner_test.go @@ -79,6 +79,8 @@ func TestAssignerValuesRoundTrip(t *testing.T) { {FP: fp2of('a'), Composed: hashOf('1'), Newest: fp2of('b'), Rule: RuleCanary, Words: "the canary ace does not run it healthy yet"}, {Removed: true, Words: "the module left g14"}, {FP: fp2of('a'), Composed: hashOf('1'), Put: true, Hold: true, Words: "put by the operator: the bus's user list"}, + // A rule a later release numbers (9, 10: the verb wait, the capability hold, ADR 0614) reads here already. + {FP: fp2of('a'), Composed: hashOf('1'), Newest: fp2of('b'), Rule: 10, Words: "the verb x.y it invokes is served by no running build"}, } for _, w := range wants { data, err := EncodeWant(w) @@ -147,7 +149,7 @@ func TestAssignerValuesRefused(t *testing.T) { {Removed: true, FP: fp2of('a')}, {FP: fp2of('a'), Newest: fp2of('b')}, {FP: fp2of('a'), Newest: fp2of('a'), Rule: 3}, - {FP: fp2of('a'), Composed: hashOf('1'), Newest: fp2of('b'), Rule: 9}, + {FP: fp2of('a'), Composed: hashOf('1'), Newest: fp2of('b'), Rule: 11}, {FP: fp2of('a'), Composed: hashOf('1'), Hold: true}, {FP: fp2of('a'), Composed: hashOf('1'), Put: true, Newest: fp2of('b'), Rule: 3}, {Removed: true, Put: true}, @@ -164,7 +166,7 @@ func TestAssignerValuesRefused(t *testing.T) { {Want: fp2of('a'), Sent: fp2of('b'), Rule: 3, Unexplained: true}, {Want: fp2of('a'), Sent: fp2of('a'), Rule: 3}, {}, - {Want: fp2of('a'), Sent: fp2of('b'), Rule: 9}, + {Want: fp2of('a'), Sent: fp2of('b'), Rule: 11}, {WantRemoved: true, Want: fp2of('a'), Rule: 1}, } { if _, err := EncodeDifference(d); err == nil { diff --git a/go/delivery/noderecord.go b/go/delivery/noderecord.go index 423cf6b..52fa5f3 100644 --- a/go/delivery/noderecord.go +++ b/go/delivery/noderecord.go @@ -59,6 +59,10 @@ type NodeRecord struct { // key until the lease is handed away, and the vault's key and authority once it reads them from `signers`. The gate // before the lease write reads every node's from the bus. Trusts []string `json:"trusts,omitempty"` + // ManifestReads are the reader capabilities of the shared contract this machine's node-engine is built with + // (catalogue.ManifestReads, novox/hq ADR 0614 §4): the manifest words, forms and seat names it reads. Empty for an + // engine built before it said them, which a reader takes as the baseline (catalogue.StatedManifestReads). + ManifestReads []string `json:"manifest-reads,omitempty"` } func (r NodeRecord) check() error { @@ -70,6 +74,11 @@ func (r NodeRecord) check() error { return fmt.Errorf("delivery: the record of %s names an empty capability", r.Node) } } + for _, c := range r.ManifestReads { + if c == "" { + return fmt.Errorf("delivery: the record of %s names an empty reader capability", r.Node) + } + } return nil } diff --git a/go/delivery/noderecord_test.go b/go/delivery/noderecord_test.go index a896628..2bdd675 100644 --- a/go/delivery/noderecord_test.go +++ b/go/delivery/noderecord_test.go @@ -21,7 +21,8 @@ var secretNamed = regexp.MustCompile(`(?i)(secret|passw|token|api[-_]?key|privat func TestNodeRecord(t *testing.T) { hub := NodeRecord{Node: "novox", TunnelKey: "k", Capabilities: []string{"docker"}, SealingKey: "age1x", ServingKey: "pub", Certificate: "cert", OutwardLinks: []string{"eth0"}, Networks: []catalogue.AttachedNetwork{{Link: "eth0", Network: "192.0.2.0/24"}}, - NetworksSaid: true, Contract: 3, Trusts: []string{"controller key sha256:aa", "vault key sha256:bb"}} + NetworksSaid: true, Contract: 3, Trusts: []string{"controller key sha256:aa", "vault key sha256:bb"}, + ManifestReads: []string{"seat:mesh-catalog", "word:tool-runner"}} raw, err := EncodeNodeRecord(hub) if err != nil { t.Fatal(err) @@ -45,6 +46,7 @@ func TestNodeRecord(t *testing.T) { for name, r := range map[string]NodeRecord{ "empty capability": {Node: "ace", Capabilities: []string{""}}, + "empty reader capability": {Node: "ace", ManifestReads: []string{""}}, "node that is not a name": {Node: "a.b"}, } { if _, err := EncodeNodeRecord(r); err == nil { diff --git a/go/delivery/source.go b/go/delivery/source.go index 99a4f68..3956884 100644 --- a/go/delivery/source.go +++ b/go/delivery/source.go @@ -443,6 +443,10 @@ type Module struct { // Warnings are the problems of an accepted manifest a stricter judge outgrew (novox/hq ADR 0579 §1): Verdict is then // empty (accepted), and the watchman raises delivery.ManifestOutgrown until the manifest is mended. Warnings []string `json:"warnings,omitempty"` + // Needs are the reader capabilities the manifest's own words, forms and seat names need (catalogue.ManifestNeeds, + // novox/hq ADR 0614 §4), written by the judge with its verdict: the assigner holds a node's want while that node's + // engine does not state one of them. Empty in a value written before, which needs nothing. + Needs []string `json:"needs,omitempty"` } // VerdictOf is the verdict a modules value carries, or nil when it carries none (taken as accepted, TakeManifestVerdict). diff --git a/go/delivery/source_test.go b/go/delivery/source_test.go index 1c90a60..838d395 100644 --- a/go/delivery/source_test.go +++ b/go/delivery/source_test.go @@ -181,6 +181,8 @@ func TestSourceValuesRoundTrip(t *testing.T) { // 1200/AC3 for _, m := range []Module{ {FP: "src1:abc", Src: 5, FPSrc: 5, Repository: "novox/mesh-sdk", Commit: "cccc"}, {None: "a build that resolves packages when built", Repository: "novox/mesh-sdk", Commit: "cccc"}, + // The reader capabilities its manifest needs (ADR 0614 §4), written with the verdict. + {FP: "src1:abc", Repository: "novox/mesh-sdk", Commit: "cccc", Needs: []string{"seat:mesh-catalog", "word:tool-runner"}}, } { data, err = EncodeModule(m) check("module", data, err) diff --git a/go/delivery/want_composed_test.go b/go/delivery/want_composed_test.go index 77b9bf6..701022c 100644 --- a/go/delivery/want_composed_test.go +++ b/go/delivery/want_composed_test.go @@ -51,7 +51,7 @@ func TestAWantNamesItsComposition(t *testing.T) { {Want{Removed: true, Composed: hashOf('1')}, "removed want names no"}, {Want{FP: fp2of('a'), Composed: hashOf('1'), Backup: "later"}, `backup answer "later"`}, {Want{Removed: true, Backup: BackupAgain}, "removed want names no"}, - {Want{FP: fp2of('a'), Composed: hashOf('1'), Newest: fp2of('b'), Rule: 9}, "rule 9"}, + {Want{FP: fp2of('a'), Composed: hashOf('1'), Newest: fp2of('b'), Rule: 11}, "rule 11"}, } for _, r := range refusals { if _, err := EncodeWant(r.w); err == nil || !strings.Contains(err.Error(), r.why) {