No client names a holder: each reader keeps its own constant, held to its manifest (ADR 0625 section 4, issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 18:37:28 +01:00
parent 2dbb0da8a5
commit c8241d0be6
40 changed files with 187 additions and 176 deletions
@@ -2,9 +2,6 @@ package buscompose
import (
"encoding/json"
"git.novox.be/novox/mesh-contracts/go/seats/meshbroker"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshnodeenroller"
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
"maps"
"os"
"path/filepath"
@@ -14,9 +11,12 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-contracts/go/seats/meshbroker"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshnodeenroller"
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-sdk/go/announce"
"git.novox.be/novox/mesh-sdk/go/catalogue"
@@ -60,7 +60,7 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) {
// ADR 0567 §3, §9: a node reads every key of the seat's placement and its candidates (it composes with the whole
// proposal laid over), and none of its open proposals; it writes neither.
reads := strings.Join(NodeComposeReads("laptop"), " ")
for bucket, granted := range map[string]bool{meshmoduleassignerclient.PlacementBucket: true, PlacementCandidatesBucket: true,
for bucket, granted := range map[string]bool{PlacementBucket: true, PlacementCandidatesBucket: true,
PlacementProposalsBucket: false} {
if strings.Contains(reads, "$JS.API.DIRECT.GET.KV_"+bucket+".>") != granted || strings.Contains(reads, "$KV."+bucket+".laptop") {
t.Errorf("a node is granted %s: %v, want %v", bucket, !granted, granted)
@@ -655,7 +655,7 @@ func healthChecks(t *testing.T) {
{Module: "mesh-provisioner-client", ProvisionsNode: true},
// ADR 0571 clauses 2, 4 (issue 1955): the seat mesh-artifact-sweep says artifacts-gone; the assigner keeps seat-holders.
{Module: "artifact-store-tools", Holds: []Seat{{Name: "mesh-artifact-sweep", Emits: []string{"artifacts-gone"}}},
Reads: []string{meshmoduleassignerclient.SeatHoldersRead}},
Reads: []string{SeatHoldersRead}},
{Module: AssignerModule, State: []Bucket{bucket(AssignerModule, meshmoduleassigner.StateSeatHolders)}},
// ADR 0571 clause 3: disk-load keeps the readings; the backup holder reads them.
// ADR 0577 clause 1: and it reads its own node's keys of the node-engine's data items, which restic may not.
@@ -15,6 +15,13 @@ const (
// EnrolmentModule is the module holding the seat mesh-node-enroller.
EnrolmentModule = "mesh-enrolment"
// PlacementBucket and SeatHoldersBucket are the assigner's states placement and seat-holders (reads:
// mesh-delivery-assigner.placement; the seat-holders state is read as a named reader), PlacementRead the address of the first.
PlacementBucket = AssignerModule + "_placement"
PlacementRead = AssignerModule + ".placement"
SeatHoldersRead = AssignerModule + ".seat-holders"
SeatHoldersBucket = AssignerModule + "_seat-holders"
AssignerWantsBucket = AssignerModule + "_wants"
PlacementCandidatesBucket = AssignerModule + "_candidates"
PlacementProposalsBucket = AssignerModule + "_placement-proposals"
@@ -32,6 +32,7 @@ func namesAreTheManifests(t *testing.T) {
{"reads", HaveBucket[:strings.Index(HaveBucket, "_")] + ".have", HaveBucket},
{"reads", NodeLoginsRead, NodeLoginsBucket},
{"reads", EnrolmentsRead, EnrolmentsBucket},
{"invokes", PlacementRead, PlacementBucket},
{"invokes", AssignerModule + ".wants", AssignerWantsBucket},
{"invokes", IndexerModule + ".modules", ModulesBucket},
{"invokes", IndexerModule + ".manifests", ManifestsBucket},
@@ -5,7 +5,6 @@ import (
"fmt"
"strings"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
"github.com/nats-io/nats.go"
@@ -49,7 +48,7 @@ var ProvisionerReads = []string{ConfigResolvedBucket}
// reason** (2026-10-30 recovery, issue 774): the client V3 watches every machine's plans to bind a provider's
// consumers, and granted only its own machine's keys it read none. ADR 0570 clause 3 has providers act on the seat's
// provision keys instead; once the client does, plans go back to its own machine's keys.
var ProvisionerReadsWhole = []string{meshmoduleassignerclient.PlacementBucket, meshprovisionerclient.PlansBucket}
var ProvisionerReadsWhole = []string{PlacementBucket, meshprovisionerclient.PlansBucket}
// BoundState is a providing module's state of entries, one per consumer (mesh-sdk go/provider, issue 678): a
// consumer's machine's client reads its own keys of it, `<node>.<module>.<provision>[.<local>]`, to fill
@@ -2,12 +2,12 @@ package buscompose
import (
"fmt"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-sdk/go/announce"
"slices"
"strings"
"time"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-sdk/go/announce"
)
// A module's state on the bus (novox/hq ADR 0201, design 32 §4, design 25 §3).
@@ -341,7 +341,7 @@ const ResolvedBucket = "mesh-config_resolved"
// secret. Reads only; the node is checked as every subject token is by the caller's principal.
func NodeComposeReads(node string) []string {
var out []string
for _, bucket := range []string{meshmoduleassignerclient.PlacementBucket, PlacementCandidatesBucket, ModulesBucket,
for _, bucket := range []string{PlacementBucket, PlacementCandidatesBucket, ModulesBucket,
ManifestsBucket, builds.BuilderBuildsBucket} {
out = append(out, readGrants(bucket, "")...)
}
@@ -351,7 +351,7 @@ func NodeComposeReads(node string) []string {
// NodeComposeAdvisories are the stream-created advisories of the seat mesh-placement's placement and candidates
// (ADR 0567 §9): what a node agent hears to bind each once it is made. Those two and no wider.
func NodeComposeAdvisories() []string {
return []string{StreamCreatedAdvisory(meshmoduleassignerclient.PlacementBucket), StreamCreatedAdvisory(PlacementCandidatesBucket)}
return []string{StreamCreatedAdvisory(PlacementBucket), StreamCreatedAdvisory(PlacementCandidatesBucket)}
}
// ServedReadGrants is the read of each key of mesh-config's resolved values named, `<node>.<module>.<key>`
@@ -17,6 +17,8 @@ import (
"sync"
"time"
"mesh-grants/internal/buscompose"
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
@@ -30,7 +32,6 @@ import (
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"mesh-grants/internal/buscompose"
)
// The states of other parts the grants part reads, as its manifest's reads name them (`<module>.<state>`).
@@ -48,7 +49,7 @@ const (
ReadAccounts = "mesh-vault.bus-accounts"
// ReadPlacement is the seat mesh-placement's placement (ADR 0567 §1, §9): each node's modules and the seats
// they hold.
ReadPlacement = meshmoduleassignerclient.PlacementRead
ReadPlacement = buscompose.PlacementRead
// ReadEnrolments is the seat mesh-enrolment's open enrolments, read as a named reader (ADR 0587 §2, §4): a key at
// live or redeeming composes the user enrol.<node>.
ReadEnrolments = buscompose.EnrolmentsRead