Move the delivery module to the gate: mesh-delivery-gate claims the seat with its own buckets and reads (issue 1346)

This commit is contained in:
2026-10-20 00:19:19 +00:00
parent c0880c4dc2
commit 09f0b0d5ab
70 changed files with 111 additions and 54 deletions
@@ -1,6 +1,8 @@
# mesh-delivery
# mesh-delivery-gate
The `mesh-delivery` module of the mesh catalogue: claims the seat `mesh-delivery`.
The `mesh-delivery-gate` module of the mesh catalogue: the gate of design 47. It claims the seat `mesh-delivery`, whose
name stays (ADR 0437 §11), and takes over from the module `mesh-delivery` (ADR 0437 §3, ADR 0450 §6). Its buckets are
`mesh-delivery-gate_*`.
## Files it does not know
@@ -27,8 +27,12 @@ import (
//
// The terminal Approve (ADR 0398 §3) is issue 953; today the proving channel is the operator's phone.
// Module is this module's name: the one the bus grants its asks under.
const Module = "mesh-delivery"
// Module is this module's name: the one the bus names its buckets and events under (novox/hq ADR 0437 §3). Seat is
// the seat it claims, whose name stays `mesh-delivery` (ADR 0437 §11).
const (
Module = "mesh-delivery-gate"
Seat = "mesh-delivery"
)
// The two answers, both at the level approve.
const (
@@ -61,7 +65,7 @@ func headsString(heads map[string]string) string {
// mergeAct is what an answer binds: this ask, the answer, and the heads it covers.
func mergeAct(ask, option string, heads map[string]string) asks.Act {
return asks.Act{"verb": Module + ".merge", "ask": ask, "option": option, "heads": headsString(heads)}
return asks.Act{"verb": Seat + ".merge", "ask": ask, "option": option, "heads": headsString(heads)}
}
var unshownPath = regexp.MustCompile(`[/\\@~$]|\d+\.\d+\.\d+|[A-Za-z0-9+/=_]{24,}|(?i)\b[a-z0-9-]+\.[a-z]{2,12}\b`)
@@ -75,7 +75,7 @@ func newTestBus(t *testing.T) *testBus {
b := &testBus{nc: nc, js: js, kvs: map[string]jetstream.KeyValue{}}
// The controller's job: a bucket per state the manifest declares.
m, _ := readManifest(t)
for _, name := range m.State {
for _, name := range m.buckets() {
kv, err := js.CreateKeyValue(context.Background(), jetstream.KeyValueConfig{Bucket: m.Module + "_" + name, History: 1})
if err != nil {
t.Fatal(err)
@@ -121,7 +121,7 @@ type askedOfMesh struct {
func (b *testBus) start(t *testing.T) *runtime {
t.Helper()
cmd := exec.Command(os.Args[0], "-test.run=^$")
cmd.Env = append(os.Environ(), bundleVar+"=1", "MESH_SERVED_MODULE=mesh-delivery", "MESH_MODULE=mesh-delivery", "MESH_NODE=test")
cmd.Env = append(os.Environ(), bundleVar+"=1", "MESH_SERVED_MODULE=mesh-delivery-gate", "MESH_MODULE=mesh-delivery-gate", "MESH_NODE=test")
in, err := cmd.StdinPipe()
if err != nil {
t.Fatal(err)
@@ -1,4 +1,4 @@
// mesh-delivery: the holder of the mesh-delivery seat (novox/hq ADR 0239, to-be 47). A Go bundle the node's
// mesh-delivery-gate: the holder of the mesh-delivery seat (novox/hq ADR 0239, to-be 47). A Go bundle the node's
// runtime launches. It owns the delivery — one commit in one repository, from its pull request's head to
// every machine — and the delivery group above it: their states in one compiled table, kept in its own state
// on the bus, every transition said as an event, noted on the commit and shown on the pull request through
@@ -246,7 +246,7 @@ func need(a map[string]any, keys ...string) error {
}
func tools(h *Holder, l *listening) []stdio.Tool {
seat := "mesh-delivery."
seat := Seat + "."
list := []stdio.Tool{
{Name: seat + "deliveries",
Description: "Every delivery not final, those that ended in the last day, and any stop `stalled` still lists, one line each: its id " +
@@ -26,10 +26,25 @@ type manifest struct {
Consumes []string `json:"consumes"`
Emits []string `json:"emits"`
Invokes []string `json:"invokes"`
State []string `json:"state"`
State []any `json:"state"`
Reads []string `json:"reads"`
Tools []string `json:"tools"`
}
// buckets are the names of the state the manifest declares, each a bare name or an object with a name and a history.
func (m manifest) buckets() []string {
var out []string
for _, s := range m.State {
switch v := s.(type) {
case string:
out = append(out, v)
case map[string]any:
out = append(out, v["name"].(string))
}
}
return out
}
func readManifest(t *testing.T) (manifest, string) {
t.Helper()
raw, err := os.ReadFile(filepath.Join("..", "..", "module.json"))
@@ -45,35 +60,43 @@ func readManifest(t *testing.T) (manifest, string) {
func TestItClaimsTheDeliverySeatAndFollowsWhatItHandles(t *testing.T) {
m, raw := readManifest(t)
if m.Module != "mesh-delivery" || len(m.Claims) != 1 || m.Claims[0].Name != "mesh-delivery" || m.Claims[0].Scope != "mesh" {
if m.Module != "mesh-delivery-gate" || len(m.Claims) != 1 || m.Claims[0].Name != "mesh-delivery" || m.Claims[0].Scope != "mesh" {
t.Fatalf("claims %+v", m.Claims)
}
// The seat is the mesh's own (mesh-*): a module claims it and never declares it.
if len(m.Seats) != 0 {
t.Fatal("the manifest declares a seat; mesh-delivery is the mesh's own")
}
if !reflect.DeepEqual(m.Consumes, followed) {
t.Fatalf("consumes %v, follows %v", m.Consumes, followed)
// The thirteen verbs of the seat (ADR 0437 §3, §11).
if len(m.Claims[0].Serves) != 13 {
t.Fatalf("serves %d verbs, wanted 13: %v", len(m.Claims[0].Serves), m.Claims[0].Serves)
}
if !reflect.DeepEqual(m.Consumes, append(append([]string{}, followed...), "mesh-delivery-forge-bridge.forge.**")) {
t.Fatalf("consumes %v, follows %v and the bridge's forge messages", m.Consumes, followed)
}
if !reflect.DeepEqual(m.Emits, []string{"transition", "group", "merged"}) {
t.Fatalf("emits %v", m.Emits)
}
if !reflect.DeepEqual(m.State, []string{"deliveries", "groups", "asked", "pulls"}) {
t.Fatalf("state %v", m.State)
// Seven buckets, and no `asked` (the teller asks, ADR 0437 §6): ADR 0450 §3.
buckets := m.buckets()
if !reflect.DeepEqual(buckets, []string{"deliveries", "groups", "pulls", "asks", "merges", "owed", "source"}) {
t.Fatalf("state %v", buckets)
}
// It calls exactly what its ports ask: the controller's verbs and the forge's tools.
// What it reads: the carry from today's module's three buckets, the teller's answers, the seat's holder and the
// machines' haves (ADR 0450 §6 and its insight of 2026-10-20).
if !reflect.DeepEqual(m.Reads, []string{"mesh-delivery.deliveries", "mesh-delivery.groups", "mesh-delivery.pulls",
"mesh-delivery-teller.answers", "mesh-controller.assignments", "node-engine.have"}) {
t.Fatalf("reads %v", m.Reads)
}
// It calls exactly the controller's seven verbs (ADR 0450 §7) and reads the forge with three tools; the forge's
// writes are the bridge's (ADR 0437 §8). It uses no operator-channel: the teller asks (ADR 0437 §6).
want := []string{}
for _, v := range []string{"delivery-plan", "delivery-check", "deliver", "delivery-backups-again", "delivery-stop", "delivery-walks", "forget-checks"} {
for _, v := range []string{"deliver", "delivery-stop", "delivery-backups-again", "delivery-walks", "delivery-check", "forget-checks", "plans"} {
want = append(want, "seat:"+ControllerSeat+"."+v)
}
want = append(want, "gitea.gitea_note_append", "gitea.gitea_delivery_view", "gitea.gitea_commit_status",
"gitea.gitea_commit_statuses", "gitea.gitea_contains", "gitea.gitea_open_pulls",
// The merge on a ready mark (novox/hq issue 722): the merge tool, a comment, a label taken off.
"gitea.gitea_merge_pull_request", "gitea.gitea_add_comment")
// A merge into main asks the operator for the Approve (novox/hq ADR 0398 §3, issue 948), as the knowledge base asks.
want = append(want, "seat:operator-channel.notify")
if !reflect.DeepEqual(m.Uses, []string{"operator-channel"}) {
t.Fatalf("uses %v", m.Uses)
want = append(want, "gitea.gitea_commit_statuses", "gitea.gitea_contains", "gitea.gitea_open_pulls")
if len(m.Uses) != 0 {
t.Fatalf("uses %v: the teller asks, not the gate", m.Uses)
}
if !reflect.DeepEqual(m.Invokes, want) {
t.Fatalf("invokes %v, wanted %v", m.Invokes, want)
@@ -1,22 +1,22 @@
module mesh-delivery
module mesh-delivery-gate
go 1.25.0
go 1.26.0
require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013182924-5933eb757d44
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019230201-5786ba190c69
github.com/nats-io/nats-server/v2 v2.11.17
github.com/nats-io/nats.go v1.51.0
github.com/nats-io/nats.go v1.54.0
)
require (
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/klauspost/compress v1.18.5 // indirect
github.com/klauspost/compress v1.20.0 // indirect
github.com/minio/highwayhash v1.0.4 // indirect
github.com/nats-io/jwt/v2 v2.8.1 // indirect
github.com/nats-io/nkeys v0.4.15 // indirect
github.com/nats-io/nkeys v0.4.16 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/crypto v0.57.0 // indirect
golang.org/x/sys v0.48.0 // indirect
golang.org/x/time v0.15.0 // indirect
)
@@ -1,11 +1,15 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013182924-5933eb757d44 h1:r5xIo5qWOXQdCSMxvqJy8kV4U+P/QWXFK6971Ha6wU8=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013182924-5933eb757d44/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019230201-5786ba190c69 h1:kvtV/SPsdRVUL/BZ3xxGS6U8YrXy/t7TRBjVrD6VLw8=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019230201-5786ba190c69/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/minio/highwayhash v1.0.4 h1:asJizugGgchQod2ja9NJlGOWq4s7KsAWr5XUc9Clgl4=
github.com/minio/highwayhash v1.0.4/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
github.com/nats-io/jwt/v2 v2.8.1 h1:V0xpGuD/N8Mi+fQNDynXohVvp7ZztevW5io8CUWlPmU=
@@ -14,14 +18,22 @@ github.com/nats-io/nats-server/v2 v2.11.17 h1:GKEghcFK6A+aFx11Yf1LjgLC3txAwvyhnY
github.com/nats-io/nats-server/v2 v2.11.17/go.mod h1:B1sFVz4StNosQ903ak4N1G01Fl/9f8e06mXpFIE2K24=
github.com/nats-io/nats.go v1.51.0 h1:ByW84XTz6W03GSSsygsZcA+xgKK8vPGaa/FCAAEHnAI=
github.com/nats-io/nats.go v1.51.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno=
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
github.com/nats-io/nats.go v1.54.0/go.mod h1:y+DZoD1oBOYfZTU681eTUiUjI0vbqYGixNVFHcjHJ0k=
github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=
github.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs=
github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg=
github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
@@ -1,7 +1,7 @@
{
"module": "mesh-delivery",
"module": "mesh-delivery-gate",
"version": "1",
"slug": "deliver",
"slug": "gate",
"claims": [
{
"name": "mesh-delivery",
@@ -31,7 +31,8 @@
"gitea.forge.pull-request.edited",
"gitea.forge.pull-request.closed",
"mesh-controller.checked",
"mesh-controller.plan-moved"
"mesh-controller.plan-moved",
"mesh-delivery-forge-bridge.forge.**"
],
"emits": [
"transition",
@@ -39,31 +40,46 @@
"merged"
],
"invokes": [
"seat:mesh-controller.delivery-plan",
"seat:mesh-controller.delivery-check",
"seat:mesh-controller.deliver",
"seat:mesh-controller.delivery-backups-again",
"seat:mesh-controller.delivery-stop",
"seat:mesh-controller.delivery-backups-again",
"seat:mesh-controller.delivery-walks",
"seat:mesh-controller.delivery-check",
"seat:mesh-controller.forget-checks",
"gitea.gitea_note_append",
"gitea.gitea_delivery_view",
"gitea.gitea_commit_status",
"seat:mesh-controller.plans",
"gitea.gitea_commit_statuses",
"gitea.gitea_contains",
"gitea.gitea_open_pulls",
"gitea.gitea_merge_pull_request",
"gitea.gitea_add_comment",
"seat:operator-channel.notify"
],
"uses": [
"operator-channel"
"gitea.gitea_open_pulls"
],
"state": [
"deliveries",
"groups",
"asked",
"pulls"
"pulls",
{
"name": "asks",
"history": 5
},
{
"name": "merges",
"history": 10
},
{
"name": "owed",
"history": 5
},
{
"name": "source",
"history": 64
}
],
"reads": [
"mesh-delivery.deliveries",
"mesh-delivery.groups",
"mesh-delivery.pulls",
"mesh-delivery-teller.answers",
"mesh-delivery-teller.answers-placeholder",
"mesh-controller.assignments",
"node-engine.have"
],
"tools": [
"delivery_status"
@@ -84,10 +100,10 @@
"kind": "bundle",
"language": "go",
"system": "arch",
"from": "cmd/mesh-delivery",
"binary": "mesh-delivery",
"from": "cmd/mesh-delivery-gate",
"binary": "mesh-delivery-gate",
"loads": [
"mesh-delivery"
"mesh-delivery-gate"
]
}
]