|
|
|
@@ -13,8 +13,9 @@ import (
|
|
|
|
|
// and streams, `reads` watching another module's bucket and never writing it, `consumes` hearing the named events,
|
|
|
|
|
// `invokes` calling the named tools, `uses` the seat operator-channel's by-caller subjects under the module's own name.
|
|
|
|
|
//
|
|
|
|
|
// The three manifests are copied from mesh-delivery's main at 6a23627426c6 (modules/<module>/module.json, read
|
|
|
|
|
// 2026-10-19), whole, so a change to one of them there that changes its grant is a change to read here.
|
|
|
|
|
// The four manifests (the watchman's is the fourth, issue 1329) are copied from mesh-delivery's main at 9c15112d935a
|
|
|
|
|
// (modules/<module>/module.json, read 2026-10-20; the first three are unchanged since 6a23627426c6), whole, so a change
|
|
|
|
|
// to one of them there that changes its grant is a change to read here.
|
|
|
|
|
const bridgeManifest = `{
|
|
|
|
|
"module": "mesh-delivery-forge-bridge",
|
|
|
|
|
"version": "1",
|
|
|
|
@@ -160,6 +161,69 @@ const indexerManifest = `{
|
|
|
|
|
}
|
|
|
|
|
}`
|
|
|
|
|
|
|
|
|
|
const watchmanManifest = `{
|
|
|
|
|
"module": "mesh-delivery-watchman",
|
|
|
|
|
"version": "1",
|
|
|
|
|
"slug": "watchman",
|
|
|
|
|
"reads": [
|
|
|
|
|
"mesh-controller.checks-running"
|
|
|
|
|
],
|
|
|
|
|
"state": [
|
|
|
|
|
{ "name": "presence", "history": 20 },
|
|
|
|
|
{ "name": "pulse", "key-ttl": true },
|
|
|
|
|
{ "name": "stuck", "history": 20 }
|
|
|
|
|
],
|
|
|
|
|
"own-secrets": {
|
|
|
|
|
"broker": "${dir:state}/broker"
|
|
|
|
|
},
|
|
|
|
|
"secrets-owner": "mesh-delivery-watchman",
|
|
|
|
|
"resources": [
|
|
|
|
|
{
|
|
|
|
|
"id": "account",
|
|
|
|
|
"type": "user",
|
|
|
|
|
"name": "mesh-delivery-watchman",
|
|
|
|
|
"shell": "/usr/bin/nologin",
|
|
|
|
|
"home": "/var/lib/mesh-delivery-watchman"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"id": "state",
|
|
|
|
|
"type": "directory",
|
|
|
|
|
"mode": "0700",
|
|
|
|
|
"owner": "mesh-delivery-watchman",
|
|
|
|
|
"place": ".",
|
|
|
|
|
"unknown": "program"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"id": "watchman",
|
|
|
|
|
"type": "process",
|
|
|
|
|
"name": "mesh-delivery-watchman",
|
|
|
|
|
"artifact": "program",
|
|
|
|
|
"run": [
|
|
|
|
|
"./mesh-delivery-watchman"
|
|
|
|
|
],
|
|
|
|
|
"env": {
|
|
|
|
|
"MESH_BROKER_FILE": "${dir:state}/broker"
|
|
|
|
|
},
|
|
|
|
|
"user": "mesh-delivery-watchman",
|
|
|
|
|
"health": {
|
|
|
|
|
"kind": "unit"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
"build": {
|
|
|
|
|
"artifacts": [
|
|
|
|
|
{
|
|
|
|
|
"name": "program",
|
|
|
|
|
"kind": "bundle",
|
|
|
|
|
"language": "go",
|
|
|
|
|
"system": "arch",
|
|
|
|
|
"from": "cmd/mesh-delivery-watchman",
|
|
|
|
|
"binary": "mesh-delivery-watchman"
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}`
|
|
|
|
|
|
|
|
|
|
const tellerManifest = `{
|
|
|
|
|
"module": "mesh-delivery-teller",
|
|
|
|
|
"version": "1",
|
|
|
|
@@ -488,3 +552,87 @@ func TestNoPartsCredentialReachesBeyondItsDeclaration(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// toolServer is a module that serves a tool on its own broker credential: the runner carries it (ADR 0175).
|
|
|
|
|
func toolServer() Declared {
|
|
|
|
|
return Declared{Module: "some-tools", Serves: []string{"some_tool"},
|
|
|
|
|
State: []Bucket{{Module: "some-tools", Name: "kept", History: 1}}}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 1329/AC1: composed from the manifests of mesh-delivery's main, the machine's tool runner may write no bucket of the
|
|
|
|
|
// forge bridge, the indexer, the teller or the watchman, publish none of their stream events nor the teller's
|
|
|
|
|
// operator-channel accept subjects, and pull none of their consumers; each part's own credential still may.
|
|
|
|
|
func TestTheToolRunnerCarriesNoneOfTheDeliverysParts(t *testing.T) {
|
|
|
|
|
streams := map[string]ModuleStream{"mesh-delivery-forge-bridge": {Module: "mesh-delivery-forge-bridge", Name: "FORGE"}}
|
|
|
|
|
all := composed(t, Records{Nodes: []string{"novox"}, Assigned: map[string][]Declared{"novox": {
|
|
|
|
|
{Module: RuntimeModule}, declaredOf(t, bridgeManifest, streams), declaredOf(t, indexerManifest, streams),
|
|
|
|
|
declaredOf(t, tellerManifest, streams), declaredOf(t, watchmanManifest, streams),
|
|
|
|
|
}}})
|
|
|
|
|
runner := all["runner@novox"]
|
|
|
|
|
buckets := map[string][]string{
|
|
|
|
|
"mesh-delivery-forge-bridge": {"heads"}, "mesh-delivery-indexer": {"candidates", "modules"},
|
|
|
|
|
"mesh-delivery-teller": {"answers"}, "mesh-delivery-watchman": {"presence", "pulse", "stuck"},
|
|
|
|
|
}
|
|
|
|
|
for part, names := range buckets {
|
|
|
|
|
own, there := all[part+"@novox"]
|
|
|
|
|
if !there {
|
|
|
|
|
t.Errorf("%s has no credential of its own", part)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
for _, name := range names {
|
|
|
|
|
if canWrite(runner, part+"_"+name, "some.key") {
|
|
|
|
|
t.Errorf("novox's tool runner may write %s_%s", part, name)
|
|
|
|
|
}
|
|
|
|
|
if !canWrite(own, part+"_"+name, "some.key") {
|
|
|
|
|
t.Errorf("%s may not write its own bucket %s", part, name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, subject := range []string{
|
|
|
|
|
"mesh.stream.mesh-delivery-forge-bridge.event.forge.merged",
|
|
|
|
|
"mesh.seat.operator-channel.accept.ask.mesh-delivery-teller",
|
|
|
|
|
"mesh.seat.operator-channel.accept.cancel.mesh-delivery-teller",
|
|
|
|
|
"mesh.seat.operator-channel.accept.condition.mesh-delivery-teller",
|
|
|
|
|
"$JS.API.CONSUMER.MSG.NEXT.FORGE.novox_mesh-delivery-indexer",
|
|
|
|
|
} {
|
|
|
|
|
if allowed(runner.Publish, subject) {
|
|
|
|
|
t.Errorf("novox's tool runner may publish %s", subject)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !allowed(all["mesh-delivery-forge-bridge@novox"].Publish, "mesh.stream.mesh-delivery-forge-bridge.event.forge.merged") ||
|
|
|
|
|
!allowed(all["mesh-delivery-teller@novox"].Publish, "mesh.seat.operator-channel.accept.ask.mesh-delivery-teller") ||
|
|
|
|
|
!allowed(all["mesh-delivery-indexer@novox"].Publish, "$JS.API.CONSUMER.MSG.NEXT.FORGE.novox_mesh-delivery-indexer") {
|
|
|
|
|
t.Error("a part's own credential lost what its manifest grants")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 1329/AC2: a module with tools and a broker secret of its own stays carried by the tool runner, beside its own
|
|
|
|
|
// credential (ADR 0175); a module with neither tools, a seat, waits nor a check is not.
|
|
|
|
|
func TestTheToolRunnerStillCarriesAModuleWithToolsAndItsOwnCredential(t *testing.T) {
|
|
|
|
|
process := Declared{Module: "only-process", State: []Bucket{{Module: "only-process", Name: "mine", History: 1}}}
|
|
|
|
|
all := composed(t, Records{Nodes: []string{"novox"}, Assigned: map[string][]Declared{"novox": {
|
|
|
|
|
{Module: RuntimeModule}, toolServer(), process,
|
|
|
|
|
}}})
|
|
|
|
|
runner := all["runner@novox"]
|
|
|
|
|
if !canWrite(runner, "some-tools_kept", "k") {
|
|
|
|
|
t.Error("novox's tool runner does not carry a module with tools")
|
|
|
|
|
}
|
|
|
|
|
if !canWrite(all["some-tools@novox"], "some-tools_kept", "k") {
|
|
|
|
|
t.Error("the module with tools lost its own credential")
|
|
|
|
|
}
|
|
|
|
|
if canWrite(runner, "only-process_mine", "k") {
|
|
|
|
|
t.Error("novox's tool runner carries a module that has nothing to serve")
|
|
|
|
|
}
|
|
|
|
|
if !canWrite(all["only-process@novox"], "only-process_mine", "k") {
|
|
|
|
|
t.Error("the process module lost its own credential")
|
|
|
|
|
}
|
|
|
|
|
for name, d := range map[string]Declared{
|
|
|
|
|
"a held seat": {Module: "m", Holds: []Seat{{Name: "some-seat"}}},
|
|
|
|
|
"a wait": {Module: "m", Waits: []Wait{{}}},
|
|
|
|
|
"a check": {Module: "m", Checks: []string{"m.check"}},
|
|
|
|
|
} {
|
|
|
|
|
if !runnerServes(d) {
|
|
|
|
|
t.Errorf("the tool runner does not serve a module with %s", name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|