Files
mesh-delivery/modules/mesh-delivery-forge-bridge/manifest_test.go
T
jschoubben ad561e485f FB7, FB8: the forge bridge asks its findings state and finding stream in the template form
The manifest's requires gain findings (state, history 5) and finding-events (events, 7 days,
16 MiB) and its consumes mesh-conditions.findings-asked, as every part but a foundation part
declares them; the bridge gains the finding branch-unprotected (next commit).

Builds: design 47 G §387, §529, §383
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:21:51 +01:00

217 lines
9.7 KiB
Go

package manifest
import (
"bytes"
"encoding/json"
"io/fs"
"os"
"path/filepath"
"reflect"
"slices"
"strings"
"testing"
)
// The controller's manifest reader is mesh-controller internal/catalogue (Manifest, manifest.go: Consumes :521,
// Invokes :590, Streams :613, Reads :659; StreamDeclaration, streams.go), an internal package of another repository.
// These tests decode the manifest strictly into the fields those types name (DisallowUnknownFields) and hold the
// rules of issue 1235 (ADR 0437 §4). The merge gate's module check proves the controller takes the manifest.
type stream struct {
Name string `json:"name"`
Events []string `json:"events"`
}
type state struct {
Name string `json:"name"`
History int `json:"history"`
}
// kept is an entry of requires in its long form: kept data on the bus (novox/hq ADR 0595).
type kept struct {
Name string `json:"name"`
Storage string `json:"storage"`
Template string `json:"template"`
Options map[string]any `json:"options"`
}
type artifact struct {
Name string `json:"name"`
Kind string `json:"kind"`
Language string `json:"language"`
System string `json:"system"`
From string `json:"from"`
Binary string `json:"binary"`
Loads []string `json:"loads"`
Paths []string `json:"paths"`
Series string `json:"series"`
}
type manifest struct {
Module string `json:"module"`
Version string `json:"version"`
Slug string `json:"slug"`
Consumes []string `json:"consumes"`
Streams []stream `json:"streams"`
State []state `json:"state"`
Requires []kept `json:"requires"`
Reads []string `json:"reads"`
Tools []string `json:"tools"`
Invokes []string `json:"invokes"`
OwnSecrets map[string]string `json:"own-secrets"`
SecretsOwner string `json:"secrets-owner"`
Resources []map[string]any `json:"resources"`
Build struct {
Artifacts []artifact `json:"artifacts"`
} `json:"build"`
}
func load(t *testing.T) (manifest, map[string]json.RawMessage) {
t.Helper()
raw, err := os.ReadFile("module.json")
if err != nil {
t.Fatal(err)
}
var m manifest
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&m); err != nil {
t.Fatalf("the controller's reader refuses a key this manifest has: %v", err)
}
var top map[string]json.RawMessage
if err := json.Unmarshal(raw, &top); err != nil {
t.Fatal(err)
}
return m, top
}
func sorted(s []string) []string {
s = slices.Clone(s)
slices.Sort(s)
return s
}
func TestManifestDeclarations(t *testing.T) { // 1235/AC1
m, top := load(t)
if m.Module != "mesh-delivery-forge-bridge" {
t.Errorf("module = %q", m.Module)
}
if !reflect.DeepEqual(m.Streams, []stream{{Name: "forge", Events: []string{"forge.**", "owed.ended"}}}) {
t.Errorf("streams = %+v", m.Streams)
}
// What the gate and the checker owe the forge is each seat's event owed, heard on its own stream (design 47 *What the
// gate owes the forge*), never read from their state. The forge's events are the seat git's (design 47 *The seat git*); forge.delete: a branch's end, said as a
// branch-deleted kind (design 51, DLV-FB1). The state performed's binding is asked again on the seat mesh-broker's
// recreated and dropped, and asked of its show (design 47 G §577).
// Every move of a watched branch is heard on branch-moved, and the watched branches from the registrations, before the
// first the gate's source (design 47 G §4, §11, §507).
if want := []string{"git.branch-moved", "git.forge-delete", "git.pull-closed", "git.pull-merged", "git.pull-updated", "mesh-broker.dropped", "mesh-broker.recreated", "mesh-catalog.registration-changed", "mesh-conditions.findings-asked", "mesh-delivery-checker.owed", "mesh-delivery.owed", "mesh-delivery.source-moved"}; !reflect.DeepEqual(sorted(m.Consumes), want) {
t.Errorf("consumes = %v, want %v", m.Consumes, want)
}
// A group is read from the seat mesh-delivery's verb groups (design 47, acceptance 49): the bridge reads no state of
// another module's.
if len(m.Reads) != 0 {
t.Errorf("reads = %v, want none", m.Reads)
}
if want := []string{"seat:git.branch", "seat:git.comment", "seat:git.file", "seat:git.merge-pull", "seat:git.note", "seat:git.open-pulls",
"seat:git.pull", "seat:git.status", "seat:git.tags", "seat:git.trees", "seat:git.view", "seat:mesh-broker.show", "seat:mesh-catalog.registrations", "seat:mesh-delivery.groups", "seat:mesh-delivery.source", "seat:mesh-hand-acts.hand-act"}; !reflect.DeepEqual(sorted(m.Invokes), want) {
t.Errorf("invokes = %v, want %v", m.Invokes, want)
}
if b, ok := m.OwnSecrets["broker"]; !ok || len(m.OwnSecrets) != 1 || b != "${dir:state}/broker" {
t.Errorf("own-secrets = %v, want exactly broker", m.OwnSecrets)
}
if m.SecretsOwner != "mesh-delivery-forge-bridge" {
t.Errorf("secrets-owner = %q", m.SecretsOwner)
}
// The head last written on FORGE for each pull request, so a sync writes only what differs (ADR 0443 §5), and what a
// merge read of each module directory by its tree, so a merge reads the forge only for the directories it changed
// (issue 1126). What it performed of what is owed is kept data in the new form, one key per act, kept 7 days as the
// owners' streams owed are; its size is the template's own (design 47 *What the gate owes the forge*, ADR 0595 §3).
if !reflect.DeepEqual(m.State, []state{{Name: "heads", History: 1}, {Name: "inputs", History: 1}, {Name: "end-asks", History: 1}, {Name: "owed-asks", History: 1}}) { // 1279/AC6, 1126, 1585
t.Errorf("state = %+v, want the buckets heads, inputs, end-asks and owed-asks", m.State)
}
// Design 47 G §387, §529: the bridge gains the finding branch-unprotected, so it asks the findings state and the finding stream
// by their templates (7 days, 16 MiB) as every part but a foundation part does.
if want := []kept{{Name: "performed", Storage: "key-value", Template: "state", Options: map[string]any{"history": float64(1), "ttl-seconds": float64(604800)}},
{Name: "findings", Storage: "key-value", Template: "state", Options: map[string]any{"history": float64(5)}},
{Name: "finding-events", Storage: "stream", Template: "events", Options: map[string]any{
"events": []any{"finding-written.*", "finding-cleared.*", "findings-stated.*"}, "max-age-days": float64(7), "max-mib": float64(16)}}}; !reflect.DeepEqual(m.Requires, want) {
t.Errorf("requires = %+v, want performed, findings and finding-events as %+v", m.Requires, want)
}
// 1585/T18: the tools read the groups and ask the bridge to say a group's end again, through its state end-asks. The tool bridge_merged_message answers a merge's merged message again (issue 1585); bridge_tags reads a package's tags for the builder's floor (design 51).
if want := []string{"bridge_group_end_again", "bridge_groups", "bridge_merged_message", "bridge_owed", "bridge_owed_again", "bridge_owed_drop", "bridge_tags"}; !reflect.DeepEqual(sorted(m.Tools), want) {
t.Errorf("tools = %v, want %v", m.Tools, want)
}
// No other state, no seat, no event (ADR 0437 §4; ADR 0521: no check.status); its kept data on the bus is the one entry of requires above.
for _, k := range []string{"logs", "emits", "claims", "provides", "uses", "defines-seats", "seat", "seats"} {
if _, ok := top[k]; ok {
t.Errorf("the manifest declares %q: the bridge has none", k)
}
}
}
func TestManifestProcess(t *testing.T) { // 1235/AC1
m, _ := load(t)
var procs []map[string]any
for _, r := range m.Resources {
if r["type"] == "process" {
procs = append(procs, r)
}
if r["type"] == "tool" || r["type"] == "seat" {
t.Errorf("resource %v: the bridge serves no tool and holds no seat", r)
}
}
if len(procs) != 1 {
t.Fatalf("%d process resources, want one", len(procs))
}
p := procs[0]
if p["artifact"] != "program" || !reflect.DeepEqual(p["run"], []any{"./mesh-delivery-forge-bridge"}) {
t.Errorf("process = %v", p)
}
env, _ := p["env"].(map[string]any)
if len(env) != 1 || env["MESH_BROKER_FILE"] != "${dir:state}/broker" {
t.Errorf("env = %v", p["env"])
}
if _, bus := p["bus"]; bus {
t.Errorf("the process declares a bus: it runs on its own broker credential")
}
// Issue 1966's bootstrap: the package artifact forgemsg is out of the manifest until the new builder runs; the follow-up
// puts it back with its assertion (ADR 0573 §1: three artifacts, the third the package forgemsg).
if len(m.Build.Artifacts) != 2 || m.Build.Artifacts[1].Name != "tools" || m.Build.Artifacts[1].From != "cmd/mesh-delivery-forge-bridge-tools" {
t.Fatalf("artifacts = %+v, want the program and the tools bundle", m.Build.Artifacts)
}
a := m.Build.Artifacts[0]
if st, err := os.Stat(a.From); err != nil || !st.IsDir() || a.Binary != "mesh-delivery-forge-bridge" || !strings.HasSuffix(a.From, a.Binary) {
t.Errorf("artifact = %+v", a)
}
}
func TestServesNoRequestSubjectAndHoldsNoTicker(t *testing.T) { // 1235/AC6 (ADR 0437 §8)
forbidden := []string{"Subscribe(", "QueueSubscribe", "ChanSubscribe", "time.NewTicker", "time.Tick", "time.NewTimer", "time.AfterFunc", "time.After(", "time.Sleep"}
var files int
for _, root := range []string{"cmd", "internal"} {
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
return err
}
files++
src, err := os.ReadFile(path)
if err != nil {
return err
}
for _, f := range forbidden {
if strings.Contains(string(src), f) {
t.Errorf("%s holds %s: the bridge serves no request subject and holds no timer", path, f)
}
}
return nil
})
if err != nil {
t.Fatal(err)
}
}
if files < 2 {
t.Fatalf("read %d source files, want the program's", files)
}
}