package main import ( "testing" "time" "git.novox.be/novox/mesh-sdk/go/catalogue" "github.com/novox/mesh-controller/internal/conditions" "github.com/novox/mesh-controller/internal/inventory" ) // Between `assign` and `push` a module's own secrets are not made yet: the push makes them. D1 composed // the machine's declaration without making anything, failed on the missing secret, and raised an urgent // "nothing can be sent to " — seen live on 2026-10-06, a desktop notification for a machine // the next push sent to without a word (novox/hq issue 275). D1 now composes as the push would, with a // stand-in for what the push makes: pending, not broken, and said only past a bound, as a warning. // aKeeper is a module with an own secret the mesh makes — a backup repository's password. func aKeeper() catalogue.Manifest { return catalogue.Manifest{Module: "keeper", Version: "1", OwnSecrets: catalogue.OwnSecrets{"repository": {Path: "/var/lib/mesh/keeper/repository"}}, Resources: []map[string]any{ {"id": "state", "type": "directory", "path": "/var/lib/mesh/keeper", "mode": "0700"}, }} } // pushedBy records a send to a machine as a push does — composed on the send path, so its own secrets // are made — without a bus to carry it. func pushedBy(t *testing.T, open *stores, node string) string { t.Helper() ctx := t.Context() plan, settings, err := resolveNode(ctx, open, node) if err != nil { t.Fatal(err) } declared, err := declarationFor(ctx, open, node, plan, settings) if err != nil { t.Fatal(err) } body, err := declared.Body() if err != nil { t.Fatal(err) } record, err := open.inventory.NodeByName(ctx, node) if err != nil { t.Fatal(err) } digest := digestOf(body) // A push records the build of each module it carries (plan.go, Allocating); a module is carried by a send only when it was // built from a commit, for what a machine was sent is read from the commits its assignments name (issue 1109). builds := declared.Builds if builds == nil { current, err := open.inventory.CurrentBuilds(ctx) if err != nil { t.Fatal(err) } builds = map[string]string{} for _, m := range plan.Modules { builds[m.Module] = current[m.Module].Commit } } if err := open.inventory.RecordSent(ctx, record.ID, digest, builds); err != nil { t.Fatal(err) } return digest } // pushedAndApplied is pushedBy, and the machine reporting it applied that declaration. func pushedAndApplied(t *testing.T, open *stores, node string) { t.Helper() digest := pushedBy(t, open, node) record, err := open.inventory.NodeByName(t.Context(), node) if err != nil { t.Fatal(err) } if _, err := open.inventory.RecordDoing(t.Context(), record.ID, inventory.Doing{ Outcome: inventory.OutcomeApplied, Declared: digest}); err != nil { t.Fatal(err) } } // d1 runs D1 once. func d1(t *testing.T, open *stores) []conditions.Observation { t.Helper() got, err := probeDeclarations(t.Context(), &doctor{open: open}) if err != nil { t.Fatal(err) } return got } // **THE WINDOW, REPRODUCED**: assigned and not pushed, a module whose own secret the push makes is // waiting, not uncomposable; past the bound it is a warning naming what the push makes; pushed, nothing. // **A REAL FAILURE IS STILL URGENT**: a secret the push would be refused on is not one it will make. // A bus credential nobody issued (issue 203) fails the push, so D1 says it — urgent, in the push's words. // A given secret sealed to a key the machine no longer has is not the mesh's to make again: the push is // refused on it, so D1 is too. // The bound is read from when the machine began waiting: the oldest assignment since its last send. // **D3 AND D13 WAIT FOR THE SEND**: a holder is expected to answer on a machine once the machine was sent // it and had time to report — never between assign and push. func TestAHolderIsExpectedOnlyOnceSentAndReported(t *testing.T) { now := time.Now() sent := now.Add(-time.Minute) long := now.Add(-time.Hour) cases := []struct { name string send lastSend want bool }{ {"never sent", lastSend{}, false}, {"sent without it", lastSend{sent: &long, current: true, carried: map[string]string{"other": "c"}}, false}, {"sent with it, not reported yet", lastSend{sent: &sent, carried: map[string]string{"keeper": "c"}}, false}, {"sent with it and reported", lastSend{sent: &sent, current: true, carried: map[string]string{"keeper": "c"}}, true}, {"sent with it long ago, never reported", lastSend{sent: &long, carried: map[string]string{"keeper": "c"}}, true}, {"sent before builds were kept", lastSend{sent: &long, current: true}, true}, } for _, c := range cases { if got := c.send.settled("keeper", now); got != c.want { t.Errorf("%s: settled %v, want %v", c.name, got, c.want) } } } // And through the stores: assigned, not in the last send; pushed and reported, carried and settled.