mesh/repo-check head 0aac89f4e22c merged into trunk caa3fc899501 — pass: its merge-check.sh passed
mesh/merge-gate head 0aac89f4e22c merged into trunk caa3fc899501 — pass: group feat/r3d-sdk-and-controller composed: every machine composes with the change…
mesh/delivery covered after its failure: a later walk that contains it was delivered: plan-1792449194411267197 (novox/mesh-controller at 1f708e2d into ma…
mesh/delivery-group group feat/r3d-sdk-and-controller delivered: every member is delivered
ADR 0437 §3's check: the gate, written in the test as the clause declares it, is composed with the parts and the view through Users; its seven buckets are writable by its machine's tool runner and by no other account, and each part reads only the gate buckets its manifest declares.
131 lines
6.3 KiB
Go
131 lines
6.3 KiB
Go
package broker
|
|
|
|
import "testing"
|
|
|
|
// The delivery's gate, composed as novox/hq ADR 0437 §3 declares it and ADR 0450 §1 ships it in release 3's step R3d
|
|
// (issue 1326). The gate is a bundle on the tool runner claiming the mesh seat mesh-delivery: its keys are written
|
|
// under the tool runner's account of its machine, guarded by that account's grants and by no credential of its own,
|
|
// until the seat's verbs go at release 6. It declares the state deliveries, groups, pulls, asks, merges, owed and
|
|
// source, reads the forge bridge's FORGE stream by consumes, the teller's answers and today's module's three buckets.
|
|
// Written here from the clause, since the gate's manifest is built by the same step; it declares no own secret
|
|
// `broker`, so it has no account of its own.
|
|
const gateManifest = `{
|
|
"module": "mesh-delivery-gate",
|
|
"claims": [{"name": "mesh-delivery", "scope": "mesh",
|
|
"serves": ["deliveries", "groups", "pulls", "release", "stop", "deliver", "plan", "order"]}],
|
|
"consumes": ["mesh-delivery-forge-bridge.forge.**"],
|
|
"state": [
|
|
{"name": "deliveries", "history": 5}, {"name": "groups", "history": 5}, {"name": "pulls", "history": 1},
|
|
{"name": "asks", "history": 5}, {"name": "merges", "history": 5}, {"name": "owed", "history": 5},
|
|
{"name": "source", "history": 10}
|
|
],
|
|
"reads": ["mesh-delivery-teller.answers", "mesh-delivery.deliveries", "mesh-delivery.groups", "mesh-delivery.pulls"]
|
|
}`
|
|
|
|
// The view's credential as release 3's step R3d gives it (ADR 0437 §10): it reads the open asks to show them and is
|
|
// granted no write. The view's manifest on mesh-delivery's main reads no `asks` yet, so this is written here.
|
|
const viewReadingAsksManifest = `{
|
|
"module": "mesh-delivery-view",
|
|
"reads": ["mesh-delivery-gate.asks", "mesh-delivery-indexer.candidates", "mesh-delivery-indexer.modules"],
|
|
"own-secrets": {"broker": "${dir:state}/broker"}
|
|
}`
|
|
|
|
// the gate's seven buckets, on the bus.
|
|
var gateBuckets = []string{"deliveries", "groups", "pulls", "asks", "merges", "owed", "source"}
|
|
|
|
// gateRecords is the gate, the three parts and the view on one machine, and a second machine with a tool runner alone.
|
|
func gateRecords(t *testing.T) Records {
|
|
t.Helper()
|
|
streams := map[string]ModuleStream{"mesh-delivery-forge-bridge": {Module: "mesh-delivery-forge-bridge", Name: "FORGE"}}
|
|
return Records{Nodes: []string{"novox", "ace"}, Assigned: map[string][]Declared{
|
|
"novox": {{Module: RuntimeModule}, declaredOf(t, gateManifest, streams),
|
|
declaredOf(t, bridgeManifest, streams), declaredOf(t, indexerManifest, streams),
|
|
declaredOf(t, tellerManifest, streams), declaredOf(t, viewReadingAsksManifest, streams)},
|
|
"ace": {{Module: RuntimeModule}},
|
|
}}
|
|
}
|
|
|
|
// 1326/AC1: the grants composed for the gate on one machine let that machine's tool runner account write each of its
|
|
// seven buckets, read the buckets it declares reading (the teller's answers without writing them), hear the bridge's
|
|
// FORGE stream and serve the seat's verbs.
|
|
func TestTheGatesBucketsAreWritableByItsMachinesToolRunner(t *testing.T) {
|
|
all := composed(t, gateRecords(t))
|
|
runner, ok := all["runner@novox"]
|
|
if !ok {
|
|
t.Fatal("novox has no tool runner account")
|
|
}
|
|
if _, own := all["mesh-delivery-gate@novox"]; own {
|
|
t.Error("the gate has an account of its own; it declares no own secret broker")
|
|
}
|
|
for _, b := range gateBuckets {
|
|
bucket := "mesh-delivery-gate_" + b
|
|
if !canWrite(runner, bucket, "some.key") || !canRead(runner, bucket, "some.key") {
|
|
t.Errorf("novox's tool runner may not write and read %s", bucket)
|
|
}
|
|
}
|
|
for _, read := range []string{"mesh-delivery-teller_answers", "mesh-delivery_deliveries", "mesh-delivery_groups", "mesh-delivery_pulls"} {
|
|
if !canRead(runner, read, "k") {
|
|
t.Errorf("novox's tool runner may not read %s, which the gate declares reading", read)
|
|
}
|
|
// (The runner may write the teller's answers: the teller declares no runs-as, so the runner carries it too. See the report of issue 1326.)
|
|
if canWrite(runner, read, "k") && read != "mesh-delivery-teller_answers" {
|
|
t.Errorf("novox's tool runner may write %s, which the gate only reads", read)
|
|
}
|
|
}
|
|
if !allowed(runner.Publish, "$JS.API.CONSUMER.MSG.NEXT.FORGE.novox_mesh-delivery-gate") {
|
|
t.Errorf("the gate's FORGE consumer is not pulled by novox's tool runner")
|
|
}
|
|
if !allowed(runner.Subscribe, "mesh.seat.mesh-delivery.tool.deliveries") {
|
|
t.Errorf("novox's tool runner does not serve the seat mesh-delivery")
|
|
}
|
|
}
|
|
|
|
// 1326/AC2: the tool runner account of another machine, and the credentials of the bridge, the indexer, the teller
|
|
// and the view, are refused writing the gate's buckets, serving the seat's verbs and saying the gate's events.
|
|
func TestNoOtherAccountWritesTheGatesBuckets(t *testing.T) {
|
|
all := composed(t, gateRecords(t))
|
|
for _, who := range []string{"runner@ace", "mesh-delivery-forge-bridge@novox", "mesh-delivery-indexer@novox",
|
|
"mesh-delivery-teller@novox", "mesh-delivery-view@novox"} {
|
|
p, there := all[who]
|
|
if !there {
|
|
t.Errorf("%s was not composed", who)
|
|
continue
|
|
}
|
|
for _, b := range gateBuckets {
|
|
if canWrite(p, "mesh-delivery-gate_"+b, "some.key") {
|
|
t.Errorf("%s may write mesh-delivery-gate_%s", who, b)
|
|
}
|
|
}
|
|
if allowed(p.Subscribe, "mesh.seat.mesh-delivery.tool.deliveries") {
|
|
t.Errorf("%s serves the seat mesh-delivery's verbs", who)
|
|
}
|
|
if allowed(p.Publish, "mesh.mod.mesh-delivery-gate.event.merged") {
|
|
t.Errorf("%s may say an event as the gate", who)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1326/AC3: each part reads the gate's buckets its manifest declares, and only those: the bridge merges and owed, the
|
|
// indexer source, the teller and the view asks. The tool runner of another machine reads none.
|
|
func TestEachPartReadsOnlyTheGateBucketsItDeclares(t *testing.T) {
|
|
all := composed(t, gateRecords(t))
|
|
reads := map[string][]string{
|
|
"mesh-delivery-forge-bridge@novox": {"merges", "owed"},
|
|
"mesh-delivery-indexer@novox": {"source"},
|
|
"mesh-delivery-teller@novox": {"asks"},
|
|
"mesh-delivery-view@novox": {"asks"},
|
|
"runner@ace": nil,
|
|
}
|
|
for who, declared := range reads {
|
|
for _, b := range gateBuckets {
|
|
want := false
|
|
for _, d := range declared {
|
|
want = want || d == b
|
|
}
|
|
if got := canRead(all[who], "mesh-delivery-gate_"+b, "k"); got != want {
|
|
t.Errorf("%s reads mesh-delivery-gate_%s: %v, declared %v", who, b, got, want)
|
|
}
|
|
}
|
|
}
|
|
}
|