Prove every changed health check on a bed before the catalogue merges (hq ADR 0240, to-be 48 Phase D)

Two of nineteen image checks read unhealthy while working in the mesh's hands;
adopted without proof they would have put back two good builds. The replays
every catalogue merge check runs now start each long-running container whose
declared check or image the change touches, alone, with what its module
declares and nothing of the mesh's, and require the check to see the program
within its grace: a program that stays up while its check does not see it
fails the change; one that does not stay up alone is said and left to the
first machine's gate; a check needing a provider must only reach the program.
R-studio replays the studio's false unhealthy — a server bound to HOSTNAME's
address and an image check asking localhost — failed on the bed, and proved
with HOSTNAME=0.0.0.0.
This commit is contained in:
jochen
2026-10-07 15:12:26 +02:00
parent 69e1215424
commit eecbfd6693
3 changed files with 610 additions and 0 deletions
+410
View File
@@ -0,0 +1,410 @@
package replays
import (
"bufio"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"time"
)
// The bed (novox/hq ADR 0240 rule 7, to-be 48 §8, Phase D): **a declaration is proved before it is trusted.**
//
// Two of the nineteen image checks the catalogue's containers ship read *unhealthy* while working in the
// mesh's hands — the studio's asked an address its program does not bind, the flow editor's read settings
// from a path the module mounted elsewhere. Read without proof, they would have put back two good builds.
// So the catalogue's merge check starts every resource whose declared check or image the change touches,
// **alone**, on a throwaway runtime, and requires its check to see the program within its grace.
//
// **What it proves is the check's wiring**: that it can see the program working — a property of the image
// and the declaration, not of the mesh. A resource is started with what the module declares and nothing of
// the mesh's: its literal environment, its arguments, its files where the module writes them in full, and an
// empty directory for every other place it mounts; no secret, no binding, no provider. So:
//
// - a check naming a provision in `needs` gets no provider here, and must only reach the program — an
// http answer of any status, a connect;
// - a program that does not stay up alone — it needs what the mesh gives it — is said as not proved here,
// and judged on the first machine's gate, never passed as proved and never failed;
// - a program that stays up while its check does not see it **fails**: that is the studio's fault.
//
// A tool check and a unit's own readiness need the mesh and a machine; they are said, not proved.
// BedResource is one resource the bed proves: its module, its id, and the resource as the manifest says it.
type BedResource struct {
Module string
ID string
Resource map[string]any
// Listens are the module's endpoints, by name: the container's own port for each.
Listens map[string]int
}
// BedVerdict is what the bed found of one resource.
type BedVerdict struct {
Proved bool
// Failed says the program stayed up and its check did not see it: the change's fault.
Failed bool
Said string
}
// BedLook is how often the bed looks: often, because it proves the wiring, not the timing.
var BedLook = 2 * time.Second
// BedFloor is the least the bed waits for a check to pass, whatever the grace: a program needs a moment to
// listen even when the module declared no grace.
var BedFloor = 30 * time.Second
// ChangedHealth is every long-running container resource of the catalogue at root that declares `health`
// and whose resource changed against base (a git ref in that checkout): its declaration, its image, or
// anything else of it. A manifest new on this branch counts whole. Read through git; an error when the
// base cannot be read.
func ChangedHealth(root, base string, show func(ref, path string) ([]byte, error)) ([]BedResource, error) {
paths, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json"))
if err != nil {
return nil, err
}
var out []BedResource
for _, p := range paths {
raw, err := os.ReadFile(p)
if err != nil {
return nil, err
}
rel, _ := filepath.Rel(root, p)
was := map[string]string{}
if before, err := show(base, rel); err == nil {
for _, r := range resourcesOf(before) {
was[fmt.Sprint(r["id"])] = canonical(r)
}
}
module, listens, resources := manifestParts(raw)
for _, r := range resources {
if _, declared := r["health"]; !declared || fmt.Sprint(r["type"]) != "container" || !stays(r) {
continue
}
if was[fmt.Sprint(r["id"])] == canonical(r) {
continue
}
out = append(out, BedResource{Module: module, ID: fmt.Sprint(r["id"]), Resource: r, Listens: listens})
}
}
sort.Slice(out, func(a, b int) bool { return out[a].Module+"."+out[a].ID < out[b].Module+"."+out[b].ID })
return out, nil
}
func stays(r map[string]any) bool {
once, _ := r["run-once"].(bool)
return !once && r["schedule"] == nil
}
func canonical(r map[string]any) string {
raw, _ := json.Marshal(r)
return string(raw)
}
func resourcesOf(raw []byte) []map[string]any {
_, _, rs := manifestParts(raw)
return rs
}
func manifestParts(raw []byte) (string, map[string]int, []map[string]any) {
var m struct {
Module string `json:"module"`
Listens []struct {
Name string `json:"name"`
Port int `json:"port"`
} `json:"listens"`
Resources []map[string]any `json:"resources"`
}
_ = json.Unmarshal(raw, &m)
listens := map[string]int{}
for _, l := range m.Listens {
if l.Name != "" {
listens[l.Name] = l.Port
}
}
return m.Module, listens, m.Resources
}
// placeholder is anything the mesh fills in on a machine: a value holding one is not the module's alone.
var placeholder = regexp.MustCompile(`\$\{[^}]*\}`)
// Prove starts one resource alone on the runtime and looks at it with its declared check until the check
// sees it, the program stops, or its grace (at least BedFloor) runs out. Everything it made is removed.
func (d *Docker) Prove(ctx context.Context, r BedResource, files map[string]string) BedVerdict {
h, _ := r.Resource["health"].(map[string]any)
kind := fmt.Sprint(h["kind"])
switch kind {
case "tool", "unit":
return BedVerdict{Said: fmt.Sprintf("%s.%s: a %s check needs the mesh and a machine; judged on the first "+
"machine's gate", r.Module, r.ID, kind)}
}
image, _ := r.Resource["image"].(string)
if image == "" || placeholder.MatchString(image) {
return BedVerdict{Said: fmt.Sprintf("%s.%s: its image is built by the mesh (%v); judged on the first machine's gate",
r.Module, r.ID, r.Resource["artifact"])}
}
if err := d.Pull(ctx, image); err != nil {
return BedVerdict{Said: fmt.Sprintf("%s.%s: its image could not be fetched here: %v", r.Module, r.ID, oneLine(err.Error()))}
}
scratch, err := os.MkdirTemp("", "mesh-bed-")
if err != nil {
return BedVerdict{Said: err.Error()}
}
defer os.RemoveAll(scratch)
// Its literal environment, and every env-file the module writes in full.
var env []string
if e, ok := r.Resource["env"].(map[string]any); ok {
for k, v := range e {
if s := fmt.Sprint(v); !placeholder.MatchString(s) {
env = append(env, k+"="+s)
}
}
}
if list, ok := r.Resource["env-file"].([]any); ok {
for _, f := range list {
if content, ok := files[fmt.Sprint(f)]; ok {
env = append(env, envLines(content)...)
}
}
}
sort.Strings(env)
// Its mounts: a file the module writes in full, or an empty place of its own.
var binds []string
if list, ok := r.Resource["volumes"].([]any); ok {
for i, v := range list {
src, dst, rest := splitMount(fmt.Sprint(v))
if dst == "" || !strings.HasPrefix(dst, "/") {
continue
}
local := filepath.Join(scratch, "m"+strconv.Itoa(i))
if content, ok := files[src]; ok {
if err := os.WriteFile(local, []byte(content), 0o644); err != nil {
continue
}
} else if err := os.MkdirAll(local, 0o777); err != nil {
continue
}
_ = os.Chmod(local, 0o777)
binds = append(binds, local+":"+dst+rest)
}
}
var args []string
if list, ok := r.Resource["args"].([]any); ok {
for _, a := range list {
args = append(args, fmt.Sprint(a))
}
}
network := fmt.Sprintf("mesh-bed-%d", time.Now().UnixNano())
netID, err := d.Network(ctx, network)
if err != nil {
return BedVerdict{Said: "the bed's network could not be made: " + err.Error()}
}
defer d.RemoveNetwork(context.Background(), netID)
grace, _ := time.ParseDuration(fmt.Sprint(h["grace"]))
if _, said := h["grace"]; !said {
grace = 60 * time.Second
}
if grace < BedFloor {
grace = BedFloor
}
config := map[string]any{"Image": image, "Env": env, "Labels": map[string]string{d.Label: "bed"}}
if len(args) > 0 {
config["Cmd"] = args
}
switch kind {
case "exec":
config["Healthcheck"] = map[string]any{"Test": []string{"CMD-SHELL", fmt.Sprint(h["command"])},
"Interval": BedLook.Nanoseconds(), "Timeout": BedLook.Nanoseconds() - 1, "Retries": 1}
case "runtime":
// The image's own command, adopted by name: an empty Test keeps it.
config["Healthcheck"] = map[string]any{"Interval": BedLook.Nanoseconds(), "Timeout": BedLook.Nanoseconds() - 1,
"Retries": 1}
}
config["HostConfig"] = map[string]any{"Binds": binds, "NetworkMode": network}
name := fmt.Sprintf("mesh-bed-%s-%s-%d", r.Module, r.ID, time.Now().UnixNano())
var made struct{ ID string }
if err := d.call(ctx, http.MethodPost, "/containers/create?name="+name, config, &made); err != nil {
return BedVerdict{Said: fmt.Sprintf("%s.%s could not be made here: %v", r.Module, r.ID, oneLine(err.Error()))}
}
defer d.Remove(context.Background(), made.ID)
if err := d.call(ctx, http.MethodPost, "/containers/"+made.ID+"/start", nil, nil); err != nil {
return BedVerdict{Said: fmt.Sprintf("%s.%s could not be started here: %v", r.Module, r.ID, oneLine(err.Error()))}
}
port := r.Listens[fmt.Sprint(h["endpoint"])]
needs := fmt.Sprint(h["needs"]) != "" && h["needs"] != nil
deadline := time.Now().Add(grace)
last := "it was never looked at"
for {
var seen struct {
State struct {
Running bool
Health *struct {
Status string
Log []struct{ Output string }
}
}
NetworkSettings struct {
Networks map[string]struct{ IPAddress string }
}
}
if err := d.call(ctx, http.MethodGet, "/containers/"+made.ID+"/json", nil, &seen); err != nil {
return BedVerdict{Said: err.Error()}
}
if !seen.State.Running {
return BedVerdict{Said: fmt.Sprintf("%s.%s does not stay up alone — it needs what the mesh gives it — so its "+
"check is judged on the first machine's gate: %s", r.Module, r.ID, oneLine(lastLine(d.Logs(ctx, made.ID))))}
}
ok := false
switch kind {
case "exec", "runtime":
hs := seen.State.Health
switch {
case hs == nil:
last = "the container carries no check: its image ships none to adopt"
case hs.Status == "healthy":
ok = true
default:
last = "the runtime says " + hs.Status
if n := len(hs.Log); n > 0 {
last += ": " + oneLine(hs.Log[n-1].Output)
}
}
case "http", "tcp":
address := ""
for _, n := range seen.NetworkSettings.Networks {
address = n.IPAddress
}
ok, last = look(ctx, kind, address, port, h, needs)
default:
return BedVerdict{Said: fmt.Sprintf("%s.%s declares a %s check the bed does not know", r.Module, r.ID, kind)}
}
if ok {
return BedVerdict{Proved: true, Said: fmt.Sprintf("%s.%s: its %s check sees the program", r.Module, r.ID, kind)}
}
if time.Now().After(deadline) || ctx.Err() != nil {
return BedVerdict{Failed: true, Said: fmt.Sprintf("%s.%s stays up and its %s check does not see it within %s: %s",
r.Module, r.ID, kind, grace, last)}
}
select {
case <-ctx.Done():
case <-time.After(BedLook):
}
}
}
// look is one http or tcp look at the program, from outside it, as the node-engine makes it. A check that
// needs a provider only has to reach the program: any answer, a connect.
func look(ctx context.Context, kind, address string, port int, h map[string]any, needs bool) (bool, string) {
if address == "" || port == 0 {
return false, "it has no address on the bed's network yet"
}
at := net.JoinHostPort(address, strconv.Itoa(port))
ctx, cancel := context.WithTimeout(ctx, BedLook)
defer cancel()
if kind == "tcp" {
c, err := (&net.Dialer{}).DialContext(ctx, "tcp", at)
if err != nil {
return false, oneLine(err.Error())
}
c.Close()
return true, ""
}
scheme, _ := h["scheme"].(string)
if scheme == "" {
scheme = "http"
}
path, _ := h["path"].(string)
if path == "" {
path = "/"
}
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, scheme+"://"+at+path, nil)
res, err := (&http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse },
Transport: insecure()}).Do(req)
if err != nil {
return false, oneLine(err.Error())
}
res.Body.Close()
if needs {
return true, ""
}
want, _ := h["status"].(float64)
switch {
case want != 0 && res.StatusCode != int(want):
return false, fmt.Sprintf("answered %d, expected %d", res.StatusCode, int(want))
case want == 0 && res.StatusCode >= 400:
return false, fmt.Sprintf("answered %d", res.StatusCode)
}
return true, ""
}
func splitMount(v string) (src, dst, rest string) {
parts := strings.SplitN(v, ":", 3)
if len(parts) < 2 {
return "", "", ""
}
if len(parts) == 3 {
rest = ":" + parts[2]
}
return parts[0], parts[1], rest
}
func envLines(content string) []string {
var out []string
s := bufio.NewScanner(strings.NewReader(content))
for s.Scan() {
line := strings.TrimSpace(s.Text())
if line == "" || strings.HasPrefix(line, "#") || !strings.Contains(line, "=") || placeholder.MatchString(line) {
continue
}
out = append(out, line)
}
return out
}
func lastLine(s string) string {
lines := strings.Split(strings.TrimSpace(s), "\n")
return lines[len(lines)-1]
}
// FilesOf is every file a module writes in full — its content declared, nothing the mesh fills in — by the
// path it writes it at, as the module names it: what the bed gives a container that mounts or reads it.
func FilesOf(manifest []byte) map[string]string {
_, _, rs := manifestParts(manifest)
out := map[string]string{}
for _, r := range rs {
if fmt.Sprint(r["type"]) != "file" {
continue
}
path, _ := r["path"].(string)
content, ok := r["content"].(string)
if path == "" || !ok || placeholder.MatchString(content) {
continue
}
out[path] = content
}
return out
}
// insecure is a transport that asks whether a program answers, not whom to trust.
func insecure() *http.Transport {
return &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, DisableKeepAlives: true}
}
// oneLine is the first line of what was said, short.
func oneLine(s string) string {
line, _, _ := strings.Cut(strings.TrimSpace(s), "\n")
if len(line) > 300 {
line = line[:300] + "…"
}
return line
}
+161
View File
@@ -0,0 +1,161 @@
package replays
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
)
// **The catalogue's bed** (novox/hq ADR 0240 rule 7, to-be 48 §8, Phase D): every long-running resource whose
// declared `health` or image the change touches is started alone and its check must see the program within
// its grace. Run by every merge check of the catalogue on the build seat, with the change's catalogue at
// MESH_REPLAY_CATALOGUE; by hand against the catalogue beside the lab. What changed is against
// MESH_BED_BASE (origin/main by default) in that checkout.
func TestBedProvesEveryChangedHealthCheck(t *testing.T) {
root := orDefault(os.Getenv("MESH_REPLAY_CATALOGUE"), filepath.Join("..", "..", "mesh-catalog"))
if _, err := os.Stat(filepath.Join(root, "modules")); err != nil {
t.Skipf("no catalogue at %s (MESH_REPLAY_CATALOGUE names it)", root)
}
base := orDefault(os.Getenv("MESH_BED_BASE"), "origin/main")
show := func(ref, path string) ([]byte, error) {
return exec.Command("git", "-c", "safe.directory=*", "-C", root, "show", ref+":"+path).Output()
}
if _, err := exec.Command("git", "-c", "safe.directory=*", "-C", root, "rev-parse", "--verify", base).Output(); err != nil {
// What changed cannot be told, so every declared check is proved: the bed never passes what it did
// not look at.
t.Logf("%s is not in the catalogue at %s (%v): every declared check is proved", base, root, err)
}
changed, err := ChangedHealth(root, base, show)
if err != nil {
t.Fatal(err)
}
if len(changed) == 0 {
t.Logf("no declared check or image of a long-running resource changed against %s: nothing to prove", base)
return
}
docker, ok := DockerFromEnv()
if !ok {
t.Fatalf("%d changed check(s) and no container runtime to prove them on", len(changed))
}
ctx, cancel := context.WithTimeout(t.Context(), 30*time.Minute)
defer cancel()
for _, r := range changed {
manifest, _ := os.ReadFile(filepath.Join(root, "modules", r.Module, "module.json"))
v := docker.Prove(ctx, r, FilesOf(manifest))
switch {
case v.Failed:
t.Errorf("FAILS ON THE BED: %s", v.Said)
case v.Proved:
t.Logf("proved: %s", v.Said)
default:
t.Logf("NOT PROVED HERE: %s", v.Said)
}
}
}
// **R-studio — a check that asks an address the program does not bind fails the bed, not a machine**
// (novox/hq ADR 0240 Phase D, done when). The hosted database suite's studio binds the address the runtime
// puts in HOSTNAME, so it answered only on its network address while its image's own check asked localhost:
// unhealthy for ever while working, until the module set HOSTNAME=0.0.0.0. Adopted by name without proof, it
// would have put back a good build.
//
// The replay is that image in miniature: a web server that binds $HOSTNAME's address, and an image check that
// asks localhost. Adopted as the module declared it before the fix, the bed fails it; with the fix, it proves
// it. The image is built here and removed after, with everything the bed made.
func TestBedFailsTheStudiosFalseUnhealthy(t *testing.T) {
docker, ok := DockerFromEnv()
if !ok {
t.Skip("no container runtime: the studio is a container")
}
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Minute)
defer cancel()
if err := docker.Pull(ctx, "busybox:1.36"); err != nil {
t.Fatal(err)
}
tag := fmt.Sprintf("mesh-replay-studio:%d", time.Now().UnixNano())
if err := docker.Build(ctx, tag, StudioDockerfile); err != nil {
t.Fatal(err)
}
defer docker.RemoveImage(context.Background(), tag)
was := BedFloor
BedFloor = 15 * time.Second
defer func() { BedFloor = was }()
studio := func(env map[string]any) BedResource {
r := map[string]any{"id": "studio", "type": "container", "name": "supabase-studio", "image": tag,
"health": map[string]any{"kind": "runtime", "grace": "10s"}}
if env != nil {
r["env"] = env
}
return BedResource{Module: "supabase", ID: "studio", Resource: r, Listens: map[string]int{}}
}
before := docker.Prove(ctx, studio(nil), nil)
if !before.Failed || !strings.Contains(before.Said, "does not see it") {
t.Fatalf("the studio's false unhealthy was not failed on the bed: %+v", before)
}
t.Logf("before the fix: %s", before.Said)
after := docker.Prove(ctx, studio(map[string]any{"HOSTNAME": "0.0.0.0"}), nil)
if !after.Proved {
t.Fatalf("the studio with HOSTNAME=0.0.0.0 was not proved: %+v", after)
}
t.Logf("with the fix: %s", after.Said)
}
// StudioDockerfile is the studio in miniature: it serves on the address HOSTNAME names, and its own check
// asks localhost, as the studio's image does.
const StudioDockerfile = `FROM busybox:1.36
RUN mkdir -p /www && echo studio > /www/index.html
HEALTHCHECK --interval=5s --timeout=2s --retries=2 CMD wget -q -O /dev/null http://localhost:3000/ || exit 1
CMD addr=$(grep -w "$HOSTNAME" /etc/hosts | head -n 1 | cut -f 1); exec httpd -f -p "${addr:-$HOSTNAME}:3000" -h /www
`
// What the bed proves is what the change touches: a long-running container whose declared check or image
// changed, or that is new; never a step, never one left as it was.
func TestTheBedProvesWhatTheChangeTouches(t *testing.T) {
root := t.TempDir()
git := func(args ...string) {
t.Helper()
cmd := exec.Command("git", append([]string{"-C", root, "-c", "user.email=lab@example", "-c", "user.name=lab"}, args...)...)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v: %v %s", args, err, out)
}
}
write := func(module, body string) {
t.Helper()
if err := os.MkdirAll(filepath.Join(root, "modules", module), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, "modules", module, "module.json"), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
git("init", "-q", "-b", "main")
write("web", `{"module":"web","resources":[{"id":"server","type":"container","image":"web@sha256:a","health":{"kind":"runtime"}},
{"id":"seed","type":"container","image":"web@sha256:a","run-once":true,"health":{"kind":"runtime"}}]}`)
write("db", `{"module":"db","resources":[{"id":"server","type":"container","image":"db@sha256:a","health":{"kind":"runtime"}}]}`)
git("add", "-A")
git("commit", "-qm", "base")
write("web", `{"module":"web","resources":[{"id":"server","type":"container","image":"web@sha256:b","health":{"kind":"runtime"}},
{"id":"seed","type":"container","image":"web@sha256:b","run-once":true,"health":{"kind":"runtime"}}]}`)
write("cache", `{"module":"cache","resources":[{"id":"server","type":"container","image":"cache@sha256:a","health":{"kind":"tcp","endpoint":"redis"}},
{"id":"plain","type":"container","image":"cache@sha256:a"}]}`)
show := func(ref, path string) ([]byte, error) {
return exec.Command("git", "-C", root, "show", ref+":"+path).Output()
}
changed, err := ChangedHealth(root, "main", show)
if err != nil {
t.Fatal(err)
}
var got []string
for _, r := range changed {
got = append(got, r.Module+"."+r.ID)
}
if strings.Join(got, ",") != "cache.server,web.server" {
t.Fatalf("the bed would prove %v; want the new module's checked container and the one whose image moved", got)
}
}
+39
View File
@@ -1,6 +1,7 @@
package replays
import (
"archive/tar"
"bytes"
"context"
"encoding/binary"
@@ -210,3 +211,41 @@ func (d *Docker) Exec(ctx context.Context, id string, cmd ...string) (int, error
}
return -1, fmt.Errorf("%v did not end", cmd)
}
// Build builds an image from a Dockerfile alone, tagged as given, and labelled so a replay that dies is
// cleaned up by it.
func (d *Docker) Build(ctx context.Context, tag, dockerfile string) error {
var archive bytes.Buffer
tw := tar.NewWriter(&archive)
if err := tw.WriteHeader(&tar.Header{Name: "Dockerfile", Mode: 0o644, Size: int64(len(dockerfile))}); err != nil {
return err
}
if _, err := tw.Write([]byte(dockerfile)); err != nil {
return err
}
if err := tw.Close(); err != nil {
return err
}
labels, _ := json.Marshal(map[string]string{d.Label: "1"})
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://docker/build?t="+url.QueryEscape(tag)+
"&labels="+url.QueryEscape(string(labels))+"&rm=1", &archive)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-tar")
res, err := d.http.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
said, _ := io.ReadAll(res.Body)
if res.StatusCode >= 300 || bytes.Contains(said, []byte(`"error"`)) {
return fmt.Errorf("building %s: %s %s", tag, res.Status, strings.TrimSpace(string(said)))
}
return nil
}
// RemoveImage removes an image.
func (d *Docker) RemoveImage(ctx context.Context, ref string) {
_ = d.call(ctx, http.MethodDelete, "/images/"+url.PathEscape(ref)+"?force=1", nil, nil)
}