A node record states the reader capabilities its engine reads, a module value the ones its manifest needs, and a want of rule 9 or 10 reads (ADR 0614 §2, §4, issue 774)
mesh/delivery merged: source.main moved to novox/mesh-sdk@5a38a1221659
mesh/delivery-group group feat/l3-final merged: every member is merged
mesh/check pass 85ba0a9a70d8 on 52e564845e44 | unit 3760 run, 4992 kept, 56 skipped | integration 3841 run, 5103 kept, 59 skipped | over bound: unit, …

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
This commit is contained in:
2026-11-03 23:18:22 +01:00
parent 73c8f15151
commit 85ba0a9a70
8 changed files with 36 additions and 10 deletions
+5 -4
View File
@@ -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"
+8 -2
View File
@@ -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
}
+4 -2
View File
@@ -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 {
+9
View File
@@ -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
}
+3 -1
View File
@@ -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 {
+4
View File
@@ -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).
+2
View File
@@ -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)
+1 -1
View File
@@ -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) {