Grant a node's agent the seat's placement and candidates, so it composes from the seat's state (ADR 0567 §3, §9, issue 1126)
This commit is contained in:
@@ -9,6 +9,8 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// Defends issue 1377 and novox/hq ADR 0547 §5: this is the controller's bus composer copied, and it composes byte for
|
||||
@@ -118,6 +120,15 @@ func grantsAsTheControllers(t *testing.T, got, want string) {
|
||||
}
|
||||
|
||||
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{delivery.PlacementBucket: true, delivery.PlacementCandidatesBucket: true,
|
||||
delivery.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)
|
||||
}
|
||||
}
|
||||
raw, err := os.ReadFile(filepath.Join("testdata", "controller-user-lists.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -898,8 +898,8 @@ func PermissionsFor(p Principal) (Permissions, error) {
|
||||
// its own modules reads the other nodes' public records, which its firewall and resolver need (ADR 0547 §2). Read
|
||||
// only: each node still writes its own keys alone.
|
||||
pub = append(pub, readGrants(delivery.HaveBucket, "*."+delivery.HaveNodeWord)...)
|
||||
// And what it composes its own modules from (ADR 0547 §2, ADR 0549 §3): the indexer's placement, modules and
|
||||
// manifests, the builder's builds (the manifest of a build its want names), and mesh-config's resolved values of
|
||||
// And what it composes its own modules from (ADR 0547 §2, ADR 0567 §9): the seat mesh-placement's placement and
|
||||
// candidates, the indexer's modules and manifests, the builder's builds (the manifest of a build its want names), and mesh-config's resolved values of
|
||||
// its own node alone. Read only.
|
||||
pub = append(pub, NodeComposeReads(p.Node)...)
|
||||
// And, of the other nodes' resolved values, the keys their modules serve from (issue 1126), each one key.
|
||||
|
||||
@@ -311,12 +311,12 @@ func RaiseBuckets(a BucketAsserter, buckets []Bucket) (undeclared []string, err
|
||||
// ResolvedBucket is mesh-config's state of resolved values, keyed `<node>.<module>.<key>`.
|
||||
const ResolvedBucket = "mesh-config_resolved"
|
||||
|
||||
// NodeComposeReads is what a node's agent reads to compose its own modules (novox/hq ADR 0547 §2, ADR 0549 §3, issue
|
||||
// 1126): every key of the indexer's placement, placement candidates, modules and manifests and of the builder's builds,
|
||||
// and of mesh-config's resolved values only its own node's, `<node>.>`. Every candidate key, not only `*.<node>`: a node
|
||||
// answering its candidate composes with the whole group's placement (the seed places every node at once, and a binding
|
||||
// crosses nodes), and nothing in a placement is secret. Reads only; the node is checked as every subject token is by the
|
||||
// caller's principal.
|
||||
// NodeComposeReads is what a node's agent reads to compose its own modules (novox/hq ADR 0547 §2, ADR 0567 §3, §9,
|
||||
// issue 1126): every key of the seat mesh-placement's placement and candidates (both the assigner's states), of the
|
||||
// indexer's modules and manifests and of the builder's builds, and of mesh-config's resolved values only its own node's,
|
||||
// `<node>.>`. Every candidate key, not only `*.<node>`: a node answering its candidate composes with the whole
|
||||
// proposal laid over (the seed places every node at once, and a binding crosses nodes), and nothing in a placement is
|
||||
// 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{delivery.PlacementBucket, delivery.PlacementCandidatesBucket, delivery.ModulesBucket,
|
||||
|
||||
@@ -10,9 +10,10 @@ import (
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// Export is one node's placement file as the composition input the controller fills says it (novox/hq ADR 0555 §6,
|
||||
// issue 1126): the inverse of Input for what a person decides, so the controller's one read-only verb
|
||||
// `placement-export` answers, from its own store by its own code, exactly what the seed writes into the catalogue.
|
||||
// Export is one node's placement as the composition input the controller fills says it (novox/hq ADR 0555 §6, ADR 0567
|
||||
// §7): the inverse of Input for what a person decides, so the controller's one read-only verb `placement-export`
|
||||
// answers, from its own store by its own code, exactly what the seat's adopt proposes as the seed. Its answer must stay
|
||||
// stable for the same store, since adopt checks its digest again at the warrant.
|
||||
// What the machine observes (its record) is not in it.
|
||||
func Export(in compose.Input, node string) (delivery.Placement, error) {
|
||||
var n *compose.Node
|
||||
@@ -24,8 +25,8 @@ func Export(in compose.Input, node string) (delivery.Placement, error) {
|
||||
if n == nil {
|
||||
return delivery.Placement{}, fmt.Errorf("frombus: the input has no node %q", node)
|
||||
}
|
||||
// At the version the placement file is read at, with a list of modules even when none is placed: the export is the
|
||||
// file the seed writes (cmd/placement-seed), and the reader refuses a value of version 0 or without its modules.
|
||||
// At the version a placement key is read at, with a list of modules even when none is placed: the export is the
|
||||
// seed's value, and the reader refuses a value of version 0 or without its modules.
|
||||
p := delivery.Placement{V: delivery.Version, Node: node, Modules: []delivery.PlacedModule{}, Decided: delivery.NodeDecided{
|
||||
Endpoint: n.Place.Endpoint, Site: n.Place.Site, Hub: n.Place.Hub, Address: n.Place.Address,
|
||||
PublicDomain: n.PublicDomain, Account: n.Account, AccountHome: n.AccountHome, AgentAccount: n.AgentAccount,
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
// and the grants part compose with one composer over the same inputs (novox/hq ADR 0547 §2, ADR 0549 §3; issue 1126).
|
||||
//
|
||||
// **One composer, three callers.** compose.Compositions is the composer; Input is how each caller fills its
|
||||
// compose.Input from the bus: the node agent for its own node (ADR 0547 §2), the assigner's composition check for every
|
||||
// node (ADR 0539 §6's check, ADR 0547 §4), and the grants part (ADR 0547 §5). Each reads the bus with its own credential
|
||||
// compose.Input from the bus: the node agent for its own node (ADR 0547 §2), the seat mesh-placement's holder judging a
|
||||
// proposal for every node (ADR 0567 §4), and the grants part (ADR 0547 §5). Each reads the bus with its own credential
|
||||
// through a Reader, so this package holds no bus client, no store and no timer: what it is given is all it reads.
|
||||
//
|
||||
// **What it reads, and from whom.**
|
||||
// - which modules run on each node, with their pins, holdings, host ports, bindings and settings placement: the
|
||||
// indexer's state `placement`, one key per node (ADR 0549 §2);
|
||||
// seat mesh-placement's state `placement`, one key per node (ADR 0567 §1);
|
||||
// - what a person decided of each node (its endpoint, site, hub, address on the private network, public domain and
|
||||
// accounts): the same placement key, Placement.Decided;
|
||||
// - what each node observes of itself (its tunnel key, capabilities, keys, certificate, links and networks): the
|
||||
@@ -107,9 +107,9 @@ type Mesh struct {
|
||||
// build of at the trunk's FP2 composes from the indexer's manifest, as before. Every leaves the catalogue as the indexer
|
||||
// read it: the check of every node judges placement, and reads no archive.
|
||||
//
|
||||
// Placements, when set, stands in for the indexer's placement of the nodes it names: a node's candidate placement
|
||||
// (delivery.PlacementCandidate), so a node composes in shadow with a catalogue pull request's placement and compares
|
||||
// the result with what it runs (issue 1126); a nil value removes the node's placement. The rest is read as always.
|
||||
// Placements, when set, stands in for the placement of the nodes it names: a proposal's candidates
|
||||
// (delivery.PlacementCandidate), so a node composes in shadow with a proposal laid over and compares the result with
|
||||
// what it runs (ADR 0567 §3); a nil value removes the node's placement. The rest is read as always.
|
||||
//
|
||||
// InOwn is the modules the composing node runs inside its own composition (InOwn of what it runs), composed there again
|
||||
// (compose.Node.InOwn) rather than cut out as modules no build is given for (issue 1126).
|
||||
|
||||
Reference in New Issue
Block a user