109 lines
8.0 KiB
Go
109 lines
8.0 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// The replays of the controller's incidents (novox/hq to-be 45 §9, M9): each a scripted replay of what
|
|
// happened, asserting the rule's outcome rather than the fix's mechanism, so it can be run against the
|
|
// commit before the fix and fail there, and against the fix and pass. **Written only with what the
|
|
// controller had before each fix** — the stores, register, assign, planFor, declarationFor — so the
|
|
// prover (mesh-lab replays/cmd/prove) can lay this file over the older commit and run it there.
|
|
//
|
|
// Registered in mesh-lab's replays/register.go with the fix each one proves; run by this repository's
|
|
// merge check on every pull request with the rest of the suite.
|
|
|
|
// **R263 — a consumer's identity never refuses its provider's machine.** On 2026-10-06 the network
|
|
// manager came to require the mesh's resolver; on a machine whose name made its identity 23 to 26
|
|
// characters against the one global bound of 20, the anchor — the resolver's holder, carrying every
|
|
// consumer's grant — could not compose, and no push to it could go through. The outcome asserted: the
|
|
// provider's machine composes whatever its consumers are called, and a provision that mints no
|
|
// credential holds no consumer to a key's length.
|
|
|
|
// **R273 — a binding to a consumer's data does not move by itself.** On 2026-10-05 a rule written for
|
|
// the resolver re-bound every database consumer on the home server — which runs its own store, beside
|
|
// its applications' data — to the store seat's holder on the control node, which made each a new empty
|
|
// database; five applications ran on empty data for twenty hours. The outcome asserted: a consumer on a
|
|
// machine running its own store stays bound to it while another machine holds the store's seat; the
|
|
// resolver, which every holder answers alike, follows its seat.
|
|
|
|
// **R-crashloop — a module that crash-loops after it applied fails its gate on its first machine** (novox/hq
|
|
// ADR 0240; research 032 §6). On the home server the agent server restarted about a hundred times while the
|
|
// mesh read it applied, its tools served and no condition raised — the gate judged a module by what the
|
|
// mesh saw from outside, and nothing looked at what it ran. The outcome asserted: a build whose container
|
|
// exits at start never passes its gate on the first machine, is put back there at the bound, and never
|
|
// reaches the second.
|
|
//
|
|
// The machine is heard as a node-engine says it: its report of the apply, then the same account said again
|
|
// later, each carrying what the engine states of what it runs — `starting` as the apply ends, then the
|
|
// crash loop. What it states of the crash loop is MESH_REPLAY_STATEMENT when the lab's replay ran the
|
|
// engine against a real container (mesh-lab replays, R-crashloop), and otherwise what the engine said of
|
|
// one, kept below. Heard as bytes, so an older controller reads them as it reads any report — this file is
|
|
// written only with what the controller had before the judging, for the prover to lay over that commit.
|
|
|
|
// **R145 — a web application that accepts TCP and answers nothing is raised within two looks** (novox/hq
|
|
// ADR 0240 rule 4 and Phase B, issue 145). For eleven hours a web application's port was open and its
|
|
// program ran while every request hung, and the mesh said its machine was healthy; a person found it.
|
|
// Liveness cannot see it and a TCP check cannot either: the port is open. The module's declared HTTP check
|
|
// can. The engine's half (mesh-host internal/liveness TestReplaySilentWebAppIsSaidUnhealthy) states what it
|
|
// found looking at such a program; here the controller hears that statement on two looks in a row and
|
|
// raises the module's condition — the second, never the first. `null` is an engine older than the
|
|
// readiness check: it states the program alive, and nothing is raised.
|
|
|
|
// **R296 — a plan's clock counts from its tier, not from its last save.** On 2026-10-07 a plan for a
|
|
// merge to the catalogue, its one module's build asked at 19:48:41 and queued at the build seat for
|
|
// minutes, read "tier 1 of 1, building for 1s", then 4s, then 9s: the line counted from the plan's last
|
|
// save, and every advance — on the 30-second tick and after every build outcome on the mesh — saved it
|
|
// whether or not anything moved. A clock reset by each save is never LATE, so `plans` and `status` could
|
|
// not say a plan was stuck. The outcome asserted: a plan in its tier for forty minutes, saved seconds ago,
|
|
// says forty minutes and LATE.
|
|
func TestReplay296APlansLineCountsFromWhenItEnteredItsTier(t *testing.T) {
|
|
now := time.Date(2026, 10, 7, 20, 30, 0, 0, time.UTC)
|
|
entered := time.Date(2026, 10, 7, 19, 48, 41, 0, time.UTC)
|
|
asked := entered
|
|
p := inventory.Plan{ID: "plan-296", Repository: "novox/mesh-catalog", Branch: "main", Commit: "3ad22356",
|
|
State: inventory.PlanBuilding, Created: entered, TierEntered: entered, Updated: now.Add(-4 * time.Second),
|
|
Tiers: [][]string{{"app"}}, Modules: map[string]*inventory.PlanModule{"app": {State: "asked", AskedAt: &asked}}}
|
|
line := planLine(p, now)
|
|
if !strings.Contains(line, "for 41m19s") || !strings.Contains(line, "LATE") {
|
|
t.Fatalf("a plan in its tier since 19:48:41, read at 20:30:00 and saved 4s before, reads %q", line)
|
|
}
|
|
}
|
|
|
|
// **R296, the save.** The same live case from the store's side: a plan whose only build was asked and
|
|
// still waits is advanced as the tick and the mesh's build outcomes advance it, and is not written again —
|
|
// its revision and its last save stay where its last change left them. Before the fix each advance saved
|
|
// it, and its revision went from 3 to 6.
|
|
|
|
// **R299 — a holder of a seat whose verbs are all optional is not silent.** On 2026-10-07 the
|
|
// node-uplink seat gained its first verbs, `resolvers` and `links`, both optional while its holders catch
|
|
// up (ADR 0246, step 1); its holders served neither, so they registered nothing on the bus's discovery for
|
|
// it, and the self-check's D3 raised `seat.node-uplink.<machine>.silent` on every machine of the mesh,
|
|
// which the healer then acted on. The outcome asserted: through D3 itself — the seats read back from the
|
|
// store as the controller reads them, a holder on record on every machine heard from, and nothing
|
|
// answering the discovery — no holder of a seat whose verbs are all optional is said silent.
|
|
//
|
|
// The live seat is replayed while its verbs are all optional; beside it, a seat whose verbs are all
|
|
// optional by definition, so the outcome is still held once node-uplink's verbs are required (step 3).
|
|
|
|
// **R300 — a merge that adds a module builds it.** On 2026-10-07 a pull request to the catalogue added
|
|
// one module, systemd-resolved; its delivery plan said "builds new: modules/systemd-resolved, sent
|
|
// nowhere", and at the merge the controller said it "changed nothing any module the mesh holds is built
|
|
// from" and built nothing, so the module was never registered and `assign` refused it until a person
|
|
// built it by hand. The outcome asserted: the merge asks the build seat for the new module's directory, at
|
|
// the branch merged into, from the repository as the mesh spells it, and opens no plan for it.
|
|
|
|
// **R301 — a person's push of a held recorded build is no repair.** On 2026-10-07 the operator approved,
|
|
// machine by machine, new builds of modules whose upgrade policy records rather than rolls out — the
|
|
// resolver, the FortiClient adapter, the network managers, the packet filter, mail. A recorded build
|
|
// moves only by a person's push (ADR 0242), so each was `push <machine>` through the seat with the
|
|
// operator's yes as its why and a word for the change as its cause. The hand-act log counted every one
|
|
// as a repair, and S15 raised `mesh.hand-acts.split-dns.healer-wanted` ("repaired by hand 4 times in 14
|
|
// days … a healer is wanted"), and the same for `words` and `uplink-verbs`. The outcome asserted: a push
|
|
// that carries only held recorded builds, recorded as the push records it, wants no healer however
|
|
// often the person gives that word — through S15 itself, reading the log as the watchdog reads it.
|