Merge branch 'feat/modules-name-no-node' into bulk-c

This commit is contained in:
jochen
2026-10-22 21:48:45 +02:00
17 changed files with 150 additions and 150 deletions
@@ -134,8 +134,8 @@ func aResolverMachine(t *testing.T, port string, now func() time.Time) *Machine
}
}
write(ConfigFile, "bind-interfaces\nlisten-address=127.0.0.2\nlisten-address=127.0.0.1\nno-resolv\n")
write(NodesFile, "local=/internal/\naddress=/novox.internal/127.0.0.2\nhost-record=novox.internal,127.0.0.2\n"+
"address=/ace.internal/10.77.0.2\nhost-record=ace.internal,10.77.0.2\n")
write(NodesFile, "local=/internal/\naddress=/node-c.internal/127.0.0.2\nhost-record=node-c.internal,127.0.0.2\n"+
"address=/node-a.internal/10.77.0.2\nhost-record=node-a.internal,10.77.0.2\n")
if err := os.MkdirAll(filepath.Join(root, filepath.Dir(StateFile)), 0o755); err != nil {
t.Fatal(err)
}
@@ -147,13 +147,13 @@ func TestTheConfigurationAndTheRosterSayWhereAndWhatToAsk(t *testing.T) {
if strings.Join(listen, " ") != "10.77.0.1 127.0.0.1" {
t.Fatalf("listen addresses %v", listen)
}
names := AnsweredNames("local=/internal/\naddress=/ace.internal/10.77.0.2\nhost-record=ace.internal,10.77.0.2\n" +
"host-record=novox.internal,10.77.0.1\n")
names := AnsweredNames("local=/internal/\naddress=/node-a.internal/10.77.0.2\nhost-record=node-a.internal,10.77.0.2\n" +
"host-record=node-c.internal,10.77.0.1\n")
q, ok := Question(listen, names)
if !ok || q.Name != "novox.internal" || q.Address != "10.77.0.1" {
if !ok || q.Name != "node-c.internal" || q.Address != "10.77.0.1" {
t.Fatalf("asks %+v", q)
}
if q, _ := Question([]string{"127.0.0.1"}, names); q.Name != "ace.internal" {
if q, _ := Question([]string{"127.0.0.1"}, names); q.Name != "node-a.internal" {
t.Fatalf("with no own name in the roster, asks %+v", q)
}
}
@@ -165,7 +165,7 @@ func TestALookFindsAResolverThatStoppedAnsweringOnOneAddress(t *testing.T) {
m := aResolverMachine(t, port, time.Now)
private := startStandIn(t, "127.0.0.2", port, "127.0.0.2")
startStandIn(t, "127.0.0.1", port, "127.0.0.2")
if l := m.Look(t.Context()); !l.Healthy || len(l.Answers) != 4 || l.Asked.Name != "novox.internal" {
if l := m.Look(t.Context()); !l.Healthy || len(l.Answers) != 4 || l.Asked.Name != "node-c.internal" {
t.Fatalf("a resolver answering everywhere: %+v", l)
}
private.stop()
@@ -173,14 +173,14 @@ func TestALookFindsAResolverThatStoppedAnsweringOnOneAddress(t *testing.T) {
if l.Healthy || len(l.Wrong) != 2 {
t.Fatalf("a resolver that stopped answering on its private address: %+v", l)
}
if why := l.Why(); !strings.Contains(why, "127.0.0.2:"+port+" over udp did not answer novox.internal: refused") ||
if why := l.Why(); !strings.Contains(why, "127.0.0.2:"+port+" over udp did not answer node-c.internal: refused") ||
!strings.Contains(why, "over tcp") || strings.Contains(why, "127.0.0.1:") {
t.Fatalf("said as %q", why)
}
}
func TestAWrongAnswerIsNotAnAnswer(t *testing.T) {
q := Named{Name: "novox.internal", Address: "10.77.0.1"}
q := Named{Name: "node-c.internal", Address: "10.77.0.1"}
for _, c := range []struct {
a Answer
want string
@@ -198,7 +198,7 @@ func TestAWrongAnswerIsNotAnAnswer(t *testing.T) {
func failing(at time.Time) Look {
return Look{At: at, Answers: []Answer{{Server: "10.77.0.1:53", Over: "udp", Failed: "refused: nothing listens there"}},
Wrong: []string{"10.77.0.1:53 over udp did not answer novox.internal: refused: nothing listens there"}}
Wrong: []string{"10.77.0.1:53 over udp did not answer node-c.internal: refused: nothing listens there"}}
}
func healthy(at time.Time) Look { return Look{At: at, Healthy: true} }
@@ -323,7 +323,7 @@ func TestTheWatchRestartsAResolverThatStoppedAnsweringAndHealthSaysWhetherItHelp
look()
}
h := m.Health(t.Context())
if h.Healthy || !strings.Contains(h.Why, "did not answer novox.internal: refused") ||
if h.Healthy || !strings.Contains(h.Why, "did not answer node-c.internal: refused") ||
!strings.Contains(h.Why, "which did not help") {
t.Fatalf("a restart that did not help: %q", h.Why)
}
@@ -482,10 +482,10 @@ func TestResolveAsksOnlyAListenAddress(t *testing.T) {
m := aResolverMachine(t, port, time.Now)
startStandIn(t, "127.0.0.2", port, "127.0.0.2")
startStandIn(t, "127.0.0.1", port, "127.0.0.2")
if _, err := m.Resolve(t.Context(), "novox.internal", "8.8.8.8", ""); err == nil || !strings.Contains(err.Error(), "not one the resolver listens on") {
if _, err := m.Resolve(t.Context(), "node-c.internal", "8.8.8.8", ""); err == nil || !strings.Contains(err.Error(), "not one the resolver listens on") {
t.Fatalf("another address was asked: %v", err)
}
got, err := m.Resolve(t.Context(), "novox.internal", "127.0.0.2", "tcp")
got, err := m.Resolve(t.Context(), "node-c.internal", "127.0.0.2", "tcp")
if err != nil {
t.Fatal(err)
}
@@ -493,10 +493,10 @@ func TestResolveAsksOnlyAListenAddress(t *testing.T) {
if len(answers) != 1 || answers[0].Over != "tcp" || answers[0].Code != "NOERROR" || answers[0].Addresses[0] != "127.0.0.2" {
t.Fatalf("%+v", answers)
}
if got, _ := m.Resolve(t.Context(), "novox.internal", "", ""); len(got.(map[string]any)["answers"].([]Answer)) != 4 {
if got, _ := m.Resolve(t.Context(), "node-c.internal", "", ""); len(got.(map[string]any)["answers"].([]Answer)) != 4 {
t.Fatalf("every listen address over both: %+v", got)
}
if _, err := m.Resolve(t.Context(), "novox.internal", "", "quic"); err == nil {
if _, err := m.Resolve(t.Context(), "node-c.internal", "", "quic"); err == nil {
t.Fatal("over quic was accepted")
}
if _, err := m.Resolve(t.Context(), "", "", ""); err == nil {
@@ -540,12 +540,12 @@ func TestTheAnnouncerSaysWhatChangedAndRemembersOnlyWhatWasSaid(t *testing.T) {
}
// The roster changes: one machine added, one gone. An unreadable roster says nothing.
nodes := filepath.Join(m.Root, NodesFile)
if err := os.WriteFile(nodes, []byte("host-record=novox.internal,127.0.0.2\nhost-record=shanks.internal,10.77.0.4\n"), 0o644); err != nil {
if err := os.WriteFile(nodes, []byte("host-record=node-c.internal,127.0.0.2\nhost-record=node-d.internal,10.77.0.4\n"), 0o644); err != nil {
t.Fatal(err)
}
Tell(m, &a, emit)
if got := strings.Join(said, "|"); !strings.Contains(got, "name.added shanks.internal") ||
!strings.Contains(got, "name.removed ace.internal") || len(said) != 2 {
if got := strings.Join(said, "|"); !strings.Contains(got, "name.added node-d.internal") ||
!strings.Contains(got, "name.removed node-a.internal") || len(said) != 2 {
t.Fatalf("the roster's change: %v", said)
}
said = nil
+17 -17
View File
@@ -108,13 +108,13 @@ const grants = new ApiGrants(new InfluxDBClient(`http://127.0.0.1:${port}`, ADMI
const PW = "mesh-minted-password-of-forty-characters";
/** Grafana on ace, as the mesh hands it to the provisioner. */
/** Grafana on node-a, as the mesh hands it to the provisioner. */
function grafana(password = PW, values: Record<string, unknown> = { access: "read" }) {
return { as: "mesh_ace_grafana", password, consumer: "ace", values };
return { as: "mesh_node-a_grafana", password, consumer: "node-a", values };
}
/** Node-RED on ace: writes one bucket. */
/** Node-RED on node-a: writes one bucket. */
function nodered(password = PW, values: Record<string, unknown> = { access: "write", buckets: ["zurag"] }) {
return { as: "mesh_ace_nodered", password, consumer: "ace", values };
return { as: "mesh_node-a_nodered", password, consumer: "node-a", values };
}
function only(token: string): Rec {
@@ -144,7 +144,7 @@ test("what a contribution may ask for, and what is refused", () => {
test("a reader is given one authorization, reading every bucket of the org, under the mesh's password", async () => {
assert.equal(await grants.ensure(grafana()), "created");
const a = only("mesh_ace_grafana");
const a = only("mesh_node-a_grafana");
assert.equal(a.orgID, "org1");
assert.equal(a.status, "active");
assert.ok(a.description.startsWith(MARK));
@@ -158,8 +158,8 @@ test("a writer is allowed its own buckets only, and a missing one is made — ne
const made = buckets.find((b) => b.name === "printer");
assert.ok(made, "the missing bucket was created");
assert.deepEqual(made!.retentionRules, [], "kept for ever: retention is the operator's choice");
assert.deepEqual(perms(only("mesh_ace_nodered")), [`write:buckets:${made!.id}`, "write:buckets:zb"]);
assert.equal(await grants.remove("mesh_ace_nodered"), "removed");
assert.deepEqual(perms(only("mesh_node-a_nodered")), [`write:buckets:${made!.id}`, "write:buckets:zb"]);
assert.equal(await grants.remove("mesh_node-a_nodered"), "removed");
assert.equal(buckets.length, 2, "withdrawing the consumer leaves every bucket and its data");
});
@@ -168,30 +168,30 @@ test("applying the same grant again writes nothing", async () => {
calls = [];
assert.equal(await grants.ensure(grafana()), "unchanged");
assert.ok(calls.every((c) => c.startsWith("GET")), `only reads: ${calls.join(", ")}`);
only("mesh_ace_grafana");
only("mesh_node-a_grafana");
});
test("a rotated password is set in place; a changed access remakes only the mesh's own", async () => {
await grants.ensure(nodered());
const id = only("mesh_ace_nodered").id;
const id = only("mesh_node-a_nodered").id;
assert.equal(await grants.holds(nodered("rotated-password-0123456789")), false);
assert.equal(await grants.ensure(nodered("rotated-password-0123456789")), "updated");
assert.equal(only("mesh_ace_nodered").id, id, "updated, not replaced");
assert.equal(only("mesh_node-a_nodered").id, id, "updated, not replaced");
assert.equal(await grants.holds(nodered("rotated-password-0123456789")), true);
await grants.ensure(nodered(PW, { access: "read-write", buckets: ["zurag"] }));
assert.deepEqual(perms(only("mesh_ace_nodered")), ["read:buckets:zb", "write:buckets:zb"]);
assert.deepEqual(perms(only("mesh_node-a_nodered")), ["read:buckets:zb", "write:buckets:zb"]);
assert.equal(await grants.holds(nodered(PW, { access: "read-write", buckets: ["zurag"] })), true);
});
test("an authorization disabled, re-passworded or deleted behind the mesh's back is not held, and is made whole", async () => {
await grants.ensure(grafana());
only("mesh_ace_grafana").status = "inactive";
only("mesh_node-a_grafana").status = "inactive";
assert.equal(await grants.holds(grafana()), false);
assert.equal(await grants.ensure(grafana()), "updated");
assert.equal(await grants.holds(grafana()), true);
only("mesh_ace_grafana").password = "somebody-else-set-this";
only("mesh_node-a_grafana").password = "somebody-else-set-this";
assert.equal(await grants.holds(grafana()), false);
await grants.ensure(grafana());
assert.equal(await grants.holds(grafana()), true);
@@ -211,14 +211,14 @@ test("holds only reads, and a bucket gone missing is not held rather than made",
});
test("an authorization of the same name the mesh did not make is refused, and left exactly as it was", async () => {
auths = [{ id: "theirs", token: "mesh_ace_grafana", orgID: "org1", status: "active", description: "hand-made",
auths = [{ id: "theirs", token: "mesh_node-a_grafana", orgID: "org1", status: "active", description: "hand-made",
permissions: [{ action: "write", resource: { type: "buckets", orgID: "org1" } }], password: "their-password" }];
const before = JSON.stringify(auths);
await assert.rejects(grants.ensure(grafana()), /did not make/);
assert.equal(JSON.stringify(auths), before);
assert.ok(calls.every((c) => c.startsWith("GET")), `only reads: ${calls.join(", ")}`);
assert.equal(await grants.holds(grafana()), false);
assert.equal(await grants.remove("mesh_ace_grafana"), "not ours");
assert.equal(await grants.remove("mesh_node-a_grafana"), "not ours");
assert.equal(auths.length, 1, "never deleted");
});
@@ -240,7 +240,7 @@ test("an org the instance does not have, or a non-mesh name, makes nothing", asy
test("a withdrawn consumer's authorization is removed, and an absent one is not an error", async () => {
await grants.ensure(grafana());
assert.equal(await grants.remove("mesh_ace_grafana"), "removed");
assert.equal(await grants.remove("mesh_node-a_grafana"), "removed");
assert.equal(auths.length, 0);
assert.equal(await grants.remove("mesh_ace_grafana"), "absent");
assert.equal(await grants.remove("mesh_node-a_grafana"), "absent");
});
@@ -151,22 +151,22 @@ func TestNewJudgeCollectsAPendingAnswer(t *testing.T) {
var sawTicket string
collected := 0
ask := func(key string, body map[string]any) (json.RawMessage, error) {
if key == "claude-code.claude_code_judge@novox" {
if key == "claude-code.claude_code_judge@node-c" {
sawWithin = body["within"].(int)
return json.Marshal(map[string]any{"pending": true, "ticket": "tk-1", "node": "novox"})
return json.Marshal(map[string]any{"pending": true, "ticket": "tk-1", "node": "node-c"})
}
if key == "claude-code.claude_code_judged@novox" {
if key == "claude-code.claude_code_judged@node-c" {
sawTicket = body["ticket"].(string)
collected++
if collected < 2 {
return json.Marshal(map[string]any{"pending": true, "ticket": "tk-1", "node": "novox"})
return json.Marshal(map[string]any{"pending": true, "ticket": "tk-1", "node": "node-c"})
}
return json.Marshal(map[string]any{"answer": json.RawMessage(`{"mails":[{"id":1,"mark":"neither"}]}`)})
}
return nil, fmt.Errorf("unexpected ask %s", key)
}
now := time.Now()
judge := NewJudge(ask, "novox", func() time.Time { return now })
judge := NewJudge(ask, "node-c", func() time.Time { return now })
raw, outcome, why := judge("the question", AnswerSchema())
if outcome != OutcomeOK {
t.Fatalf("outcome = %q, why = %q, want OutcomeOK", outcome, why)
@@ -198,9 +198,9 @@ func TestNewJudgeTimesOut(t *testing.T) {
} else {
now = now.Add(JudgeWithin + 2*time.Minute) // collecting again takes "too long"
}
return json.Marshal(map[string]any{"pending": true, "ticket": "tk-1", "node": "novox"})
return json.Marshal(map[string]any{"pending": true, "ticket": "tk-1", "node": "node-c"})
}
judge := NewJudge(ask, "novox", func() time.Time { return now })
judge := NewJudge(ask, "node-c", func() time.Time { return now })
_, outcome, why := judge("the question", AnswerSchema())
if outcome != OutcomeTimedOut {
t.Fatalf("outcome = %q, why = %q, want OutcomeTimedOut", outcome, why)
@@ -212,7 +212,7 @@ func TestNewJudgeRefused(t *testing.T) {
ask := func(string, map[string]any) (json.RawMessage, error) {
return nil, fmt.Errorf("holds no licence")
}
judge := NewJudge(ask, "novox", time.Now)
judge := NewJudge(ask, "node-c", time.Now)
_, outcome, why := judge("the question", AnswerSchema())
if outcome != OutcomeRefused || !strings.Contains(why, "licence") {
t.Fatalf("outcome = %q, why = %q, want OutcomeRefused naming the licence", outcome, why)
@@ -228,14 +228,14 @@ func TestNewJudgeSortsItsCauseByTheRealJudgesWords(t *testing.T) {
err string
want HealthOutcome
}{
{"timed out", "the agent on novox did not answer within 40s; the judgement was ended", OutcomeTimedOut},
{"out of shape", `the agent on novox answered, and not in the shape asked: "mails" is not an array`, OutcomeOutOfShape},
{"refused", "the agent on novox could not judge: holds no licence", OutcomeRefused},
{"timed out", "the agent on node-c did not answer within 40s; the judgement was ended", OutcomeTimedOut},
{"out of shape", `the agent on node-c answered, and not in the shape asked: "mails" is not an array`, OutcomeOutOfShape},
{"refused", "the agent on node-c could not judge: holds no licence", OutcomeRefused},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
ask := func(string, map[string]any) (json.RawMessage, error) { return nil, fmt.Errorf("%s", c.err) }
judge := NewJudge(ask, "novox", time.Now)
judge := NewJudge(ask, "node-c", time.Now)
_, outcome, why := judge("the question", AnswerSchema())
if outcome != c.want {
t.Fatalf("outcome = %q, want %q", outcome, c.want)
@@ -355,7 +355,7 @@ func TestRunOneBatchNeverAsksToSendMoveFlagOrDelete(t *testing.T) {
return json.Marshal(map[string]any{"text": "hi"})
}
readText := NewReadText(recordingAsk)
judge := NewJudge(recordingAsk, "novox", time.Now)
judge := NewJudge(recordingAsk, "node-c", time.Now)
if err := RunOneBatch(ctx, store, readText, judge, waiting, time.Now, noopLogf); err != nil {
t.Fatal(err)
}
@@ -1,6 +1,6 @@
package main
// Callers is the trusted setting `callers` (lead-m2@g14's decision at this milestone's prepare step, said to
// Callers is the trusted setting `callers` (lead-m2@node-b's decision at this milestone's prepare step, said to
// the operator; novox/hq ADR 0459 §2: "each answers only a caller the operator grants it"): who may call one
// of mail-reading's six verbs. An entry is a bare module name (granted wherever it runs, on any machine),
// exactly <machine>.<module>, or exactly person.<name> — the same shapes mesh-mail's own trusted setting
@@ -5,15 +5,15 @@ package main
import "testing"
func TestCallersAllowsEachShape(t *testing.T) {
c := Callers{"mesh-mail-web", "novox.mesh-knowledge", "person.jochen"}
c := Callers{"mesh-mail-web", "node-c.mesh-knowledge", "person.jochen"}
cases := []struct {
caller string
want bool
}{
{"mesh-mail-web", true}, // a bare module name
{"shanks.mesh-mail-web", true}, // ... on any machine
{"novox.mesh-knowledge", true}, // exactly <machine>.<module>
{"shanks.mesh-knowledge", false}, // ... but not on another machine
{"node-d.mesh-mail-web", true}, // ... on any machine
{"node-c.mesh-knowledge", true}, // exactly <machine>.<module>
{"node-d.mesh-knowledge", false}, // ... but not on another machine
{"person.jochen", true}, // exactly person.<name>
{"person.someoneelse", false}, // ... but not another person
{"mesh-mail-notifier", false}, // not named at all
@@ -114,12 +114,12 @@ func (fi *fakeIssuer) idToken(clientID, username string, now time.Time, claimsPa
return sign(claims, kid, key)
}
// testAuth is an Auth over fi, with its own callback redirect https://mail.g14.internal/signed-in, and
// testAuth is an Auth over fi, with its own callback redirect https://mail.node-b.internal/signed-in, and
// deterministic time so a test controls what "12 hours ago" or "expired" means.
func testAuth(t *testing.T, fi *fakeIssuer, operatorsPath string, now func() time.Time) *Auth {
t.Helper()
cfg := OIDCConfig{ClientID: "mail-web", ClientSecret: "s3cr3t", Issuer: fi.Server.URL}
authenticator, err := NewAuthenticator(context.Background(), cfg, "https://"+InternalHost("g14")+SignedInPath)
authenticator, err := NewAuthenticator(context.Background(), cfg, "https://"+InternalHost("node-b")+SignedInPath)
if err != nil {
t.Fatal(err)
}
@@ -183,13 +183,13 @@ func followBounce(t *testing.T, s *Server, w *httptest.ResponseRecorder, host st
func TestNoSessionRedirectsToTheIssuersAuthorisationEndpoint(t *testing.T) {
fi := newFakeIssuer(t)
auth := testAuth(t, fi, operatorsFile(t, "alice"), nil)
s := NewServer(InternalHost("g14"), auth, nil)
s := NewServer(InternalHost("node-b"), auth, nil)
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.Host = InternalHost("g14")
r.Host = InternalHost("node-b")
s.ServeHTTP(w, r)
w2 := followBounce(t, s, w, InternalHost("g14"))
w2 := followBounce(t, s, w, InternalHost("node-b"))
if w2.Code != http.StatusFound {
t.Fatalf("status %d, want 302", w2.Code)
@@ -211,7 +211,7 @@ func TestNoSessionRedirectsToTheIssuersAuthorisationEndpoint(t *testing.T) {
if q.Get("code_challenge") == "" || q.Get("code_challenge_method") != "S256" {
t.Fatalf("no PKCE challenge: %q %q", q.Get("code_challenge"), q.Get("code_challenge_method"))
}
if q.Get("redirect_uri") != "https://"+InternalHost("g14")+SignedInPath {
if q.Get("redirect_uri") != "https://"+InternalHost("node-b")+SignedInPath {
t.Fatalf("redirect_uri: %q", q.Get("redirect_uri"))
}
}
@@ -225,11 +225,11 @@ func TestNoSessionRedirectsToTheIssuersAuthorisationEndpoint(t *testing.T) {
func TestRequireSessionBouncesOnceThenSignsIn(t *testing.T) {
fi := newFakeIssuer(t)
auth := testAuth(t, fi, operatorsFile(t, "alice"), nil)
s := NewServer(InternalHost("g14"), auth, nil)
s := NewServer(InternalHost("node-b"), auth, nil)
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/folder?account=a&folder=INBOX", nil)
r.Host = InternalHost("g14")
r.Host = InternalHost("node-b")
s.ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("the first cookieless GET: status %d, want 200 (the bounce)", w.Code)
@@ -252,7 +252,7 @@ func TestRequireSessionBouncesOnceThenSignsIn(t *testing.T) {
// A second cookieless arrival, at the marked URL: sign in directly, no further bounce (no loop).
w2 := httptest.NewRecorder()
r2 := httptest.NewRequest(http.MethodGet, refreshURL, nil)
r2.Host = InternalHost("g14")
r2.Host = InternalHost("node-b")
s.ServeHTTP(w2, r2)
if w2.Code != http.StatusFound {
t.Fatalf("the second cookieless GET: status %d, want 302 to sign in (no loop)", w2.Code)
@@ -277,7 +277,7 @@ func TestRequireSessionBouncesOnceThenSignsIn(t *testing.T) {
// A POST with no session cookie: sign in directly, never bounced.
w3 := httptest.NewRecorder()
r3 := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(""))
r3.Host = InternalHost("g14")
r3.Host = InternalHost("node-b")
s.ServeHTTP(w3, r3)
if w3.Code != http.StatusFound {
t.Fatalf("a POST with no session cookie: status %d, want 302 (no bounce)", w3.Code)
@@ -292,9 +292,9 @@ func startSignIn(t *testing.T, s *Server, auth *Auth, path string) (state string
t.Helper()
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, path, nil)
r.Host = InternalHost("g14")
r.Host = InternalHost("node-b")
s.ServeHTTP(w, r)
w2 := followBounce(t, s, w, InternalHost("g14"))
w2 := followBounce(t, s, w, InternalHost("node-b"))
if w2.Code != http.StatusFound {
t.Fatalf("after the bounce: status %d, want 302 to the issuer", w2.Code)
}
@@ -308,7 +308,7 @@ func startSignIn(t *testing.T, s *Server, auth *Auth, path string) (state string
func callback(s *Server, state, code string) *httptest.ResponseRecorder {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, SignedInPath+"?state="+url.QueryEscape(state)+"&code="+url.QueryEscape(code), nil)
r.Host = InternalHost("g14")
r.Host = InternalHost("node-b")
s.ServeHTTP(w, r)
return w
}
@@ -323,7 +323,7 @@ func TestACallbackWithAValidIDTokenNamingAnOperatorSetsTheSessionCookie(t *testi
fi := newFakeIssuer(t)
now := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
auth := testAuth(t, fi, operatorsFile(t, "alice"), func() time.Time { return now })
s := NewServer(InternalHost("g14"), auth, nil)
s := NewServer(InternalHost("node-b"), auth, nil)
const returnPath = "/folder?account=a&folder=INBOX"
state := startSignIn(t, s, auth, returnPath)
@@ -361,7 +361,7 @@ func TestACallbackForAnIdentityNotInOperatorsIsRefused(t *testing.T) {
fi := newFakeIssuer(t)
now := time.Now()
auth := testAuth(t, fi, operatorsFile(t, "alice"), func() time.Time { return now })
s := NewServer(InternalHost("g14"), auth, nil)
s := NewServer(InternalHost("node-b"), auth, nil)
state := startSignIn(t, s, auth, "/")
fi.nextIDToken = fi.idToken("mail-web", "mallory", now, nil, fi.key, fi.kid)
@@ -404,7 +404,7 @@ func TestACallbackIsRefusedOnAnUnknownStateOrAnInvalidIDToken(t *testing.T) {
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
auth := testAuth(t, fi, operatorsFile(t, "alice"), func() time.Time { return now })
s := NewServer(InternalHost("g14"), auth, nil)
s := NewServer(InternalHost("node-b"), auth, nil)
state := c.setup(auth, s)
fi.nextIDToken = c.token()
w := callback(s, state, "some-code")
@@ -425,7 +425,7 @@ func TestASessionTwelveHoursOldIsTreatedAsNoSession(t *testing.T) {
start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
now := start
auth := testAuth(t, fi, operatorsFile(t, "alice"), func() time.Time { return now })
s := NewServer(InternalHost("g14"), auth, nil)
s := NewServer(InternalHost("node-b"), auth, nil)
state := startSignIn(t, s, auth, "/")
// The ID token's own iat/exp are judged against the real wall clock (see the comment in
@@ -446,7 +446,7 @@ func TestASessionTwelveHoursOldIsTreatedAsNoSession(t *testing.T) {
// Just under 12 hours: still a session.
now = start.Add(12*time.Hour - time.Minute)
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.Host = InternalHost("g14")
r.Host = InternalHost("node-b")
r.AddCookie(cookie)
w2 := httptest.NewRecorder()
s.ServeHTTP(w2, r)
@@ -457,7 +457,7 @@ func TestASessionTwelveHoursOldIsTreatedAsNoSession(t *testing.T) {
// Past 12 hours: no session any more — redirected to sign in again.
now = start.Add(12*time.Hour + time.Minute)
r2 := httptest.NewRequest(http.MethodGet, "/", nil)
r2.Host = InternalHost("g14")
r2.Host = InternalHost("node-b")
r2.AddCookie(cookie)
w3 := httptest.NewRecorder()
s.ServeHTTP(w3, r2)
@@ -471,7 +471,7 @@ func TestCallbackWithEmptyOperatorsLetsNobodyIn(t *testing.T) {
fi := newFakeIssuer(t)
now := time.Now()
auth := testAuth(t, fi, operatorsFile(t), func() time.Time { return now })
s := NewServer(InternalHost("g14"), auth, nil)
s := NewServer(InternalHost("node-b"), auth, nil)
state := startSignIn(t, s, auth, "/")
fi.nextIDToken = fi.idToken("mail-web", "alice", now, nil, fi.key, fi.kid)
@@ -12,7 +12,7 @@ import (
// 1283/AC1: a request whose Host is not mail.<node>.internal — a public name, an IP, or none — is answered
// 404, naming nothing of the mail.
func TestServerAnswers404ForAnyHostButItsOwnInternalName(t *testing.T) {
s := NewServer(InternalHost("g14"), nil, nil)
s := NewServer(InternalHost("node-b"), nil, nil)
for _, host := range []string{"example.com", "203.0.113.5", "", "mail.other-node.internal"} {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/", nil)
@@ -29,10 +29,10 @@ func TestServerAnswers404ForAnyHostButItsOwnInternalName(t *testing.T) {
// Its own internal name is answered (not 404).
func TestServerAnswersItsOwnInternalName(t *testing.T) {
s := NewServer(InternalHost("g14"), nil, nil)
s := NewServer(InternalHost("node-b"), nil, nil)
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.Host = InternalHost("g14")
r.Host = InternalHost("node-b")
s.ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d, want 200", w.Code)
@@ -41,10 +41,10 @@ func TestServerAnswersItsOwnInternalName(t *testing.T) {
// A Host with a port still matches, port and all (the mesh's own listen carries one).
func TestServerAnswersItsOwnInternalNameWithAPort(t *testing.T) {
s := NewServer(InternalHost("g14"), nil, nil)
s := NewServer(InternalHost("node-b"), nil, nil)
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.Host = InternalHost("g14") + ":8099"
r.Host = InternalHost("node-b") + ":8099"
s.ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status %d, want 200", w.Code)
@@ -53,8 +53,8 @@ func TestServerAnswersItsOwnInternalNameWithAPort(t *testing.T) {
// 1283/AC2: every answer (200 and 404 alike) carries the content security policy.
func TestServerSetsTheContentSecurityPolicyOnEveryAnswer(t *testing.T) {
s := NewServer(InternalHost("g14"), nil, nil)
cases := []string{"example.com", InternalHost("g14")}
s := NewServer(InternalHost("node-b"), nil, nil)
cases := []string{"example.com", InternalHost("node-b")}
for _, host := range cases {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/", nil)
@@ -69,12 +69,12 @@ func TestServerSetsTheContentSecurityPolicyOnEveryAnswer(t *testing.T) {
// 1283/AC3: open with nothing given answers the root; with account, folder and uid together, that mail's own
// address.
func TestOpenAnswersTheRootOrOneMailsAddress(t *testing.T) {
b := &Bundle{Env: Env{Node: "g14"}}
b := &Bundle{Env: Env{Node: "node-b"}}
out, err := b.openTool(map[string]any{})
if err != nil {
t.Fatal(err)
}
if got := out.(map[string]any)["url"]; got != "https://mail.g14.internal/" {
if got := out.(map[string]any)["url"]; got != "https://mail.node-b.internal/" {
t.Fatalf("root url: %v", got)
}
@@ -83,7 +83,7 @@ func TestOpenAnswersTheRootOrOneMailsAddress(t *testing.T) {
t.Fatal(err)
}
url := out.(map[string]any)["url"].(string)
if !strings.HasPrefix(url, "https://mail.g14.internal/mail?") ||
if !strings.HasPrefix(url, "https://mail.node-b.internal/mail?") ||
!strings.Contains(url, "account=mesh") || !strings.Contains(url, "folder=INBOX") || !strings.Contains(url, "uid=7") {
t.Fatalf("mail url: %s", url)
}
@@ -116,7 +116,7 @@ func TestServerStartsAndAnswersAPageWithACwdAndHomeReadOnly(t *testing.T) {
}
_ = os.Setenv("HOME", filepath.Join(ro, "home-does-not-exist"))
s := NewServer(InternalHost("g14"), nil, nil)
s := NewServer(InternalHost("node-b"), nil, nil)
srv := httptest.NewServer(s)
defer srv.Close()
@@ -124,7 +124,7 @@ func TestServerStartsAndAnswersAPageWithACwdAndHomeReadOnly(t *testing.T) {
if err != nil {
t.Fatal(err)
}
req.Host = InternalHost("g14")
req.Host = InternalHost("node-b")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
@@ -21,7 +21,7 @@ func TestQuoting(t *testing.T) {
if got := Literal(`it's`); got != `'it''s'` {
t.Fatal(got)
}
if got := RoleStatement(false, "mesh_ace_letta", "p'w"); got != `CREATE ROLE "mesh_ace_letta" WITH LOGIN PASSWORD 'p''w' VALID UNTIL 'infinity'` {
if got := RoleStatement(false, "mesh_node-a_letta", "p'w"); got != `CREATE ROLE "mesh_node-a_letta" WITH LOGIN PASSWORD 'p''w' VALID UNTIL 'infinity'` {
t.Fatal(got)
}
if got := RoleStatement(true, "r", "p"); !strings.HasPrefix(got, `ALTER ROLE "r" WITH LOGIN`) {
@@ -61,7 +61,7 @@ func TestExtensionsFromAContribution(t *testing.T) {
func TestAnExtensionTheServerDoesNotOfferIsRefusedByName(t *testing.T) {
f, c := newFake(admin)
f.answer(`FROM pg_available_extensions`, []string{"name"}, []string{"vector"}, []string{"pg_trgm"})
err := c.EnsureExtensions(ctx, "mesh_ace_letta", []string{"vector", "made_up", "plv8"})
err := c.EnsureExtensions(ctx, "mesh_node-a_letta", []string{"vector", "made_up", "plv8"})
if err == nil || !strings.Contains(err.Error(), `"made_up"`) || !strings.Contains(err.Error(), `"plv8"`) {
t.Fatalf("refusal does not name the extensions: %v", err)
}
@@ -73,28 +73,28 @@ func TestAnExtensionTheServerDoesNotOfferIsRefusedByName(t *testing.T) {
func TestExtensionsAreInstalledAsTheAdminInTheConsumersDatabase(t *testing.T) {
f, c := newFake(admin)
f.answer(`FROM pg_available_extensions`, []string{"name"}, []string{"vector"})
if err := c.EnsureExtensions(ctx, "mesh_ace_letta", []string{"vector"}); err != nil {
if err := c.EnsureExtensions(ctx, "mesh_node-a_letta", []string{"vector"}); err != nil {
t.Fatal(err)
}
last := f.calls[len(f.calls)-1]
if last.SQL != `CREATE EXTENSION IF NOT EXISTS "vector"` || last.Database != "mesh_ace_letta" || last.User != "postgres" || last.Password != "admin-secret" {
if last.SQL != `CREATE EXTENSION IF NOT EXISTS "vector"` || last.Database != "mesh_node-a_letta" || last.User != "postgres" || last.Password != "admin-secret" {
t.Fatalf("%+v", last)
}
noDrop(t, f.statements())
f.reset()
if err := c.EnsureExtensions(ctx, "mesh_ace_letta", nil); err != nil || len(f.calls) != 0 {
if err := c.EnsureExtensions(ctx, "mesh_node-a_letta", nil); err != nil || len(f.calls) != 0 {
t.Fatalf("asked the server for nothing to do: %v %v", f.calls, err)
}
}
func TestCreateIsIdempotent(t *testing.T) {
f, c := newFake(admin)
if err := c.CreateDatabaseAndRole(ctx, "mesh_ace_letta", "mesh_ace_letta", "pw"); err != nil {
if err := c.CreateDatabaseAndRole(ctx, "mesh_node-a_letta", "mesh_node-a_letta", "pw"); err != nil {
t.Fatal(err)
}
sql := f.statements()
if !has(sql, `^CREATE ROLE "mesh_ace_letta"`) || !has(sql, `^CREATE DATABASE "mesh_ace_letta" OWNER "mesh_ace_letta"$`) {
if !has(sql, `^CREATE ROLE "mesh_node-a_letta"`) || !has(sql, `^CREATE DATABASE "mesh_node-a_letta" OWNER "mesh_node-a_letta"$`) {
t.Fatal(strings.Join(sql, "\n"))
}
@@ -102,14 +102,14 @@ func TestCreateIsIdempotent(t *testing.T) {
f, c = newFake(admin)
f.answer(`FROM pg_roles`, []string{"?column?"}, []string{"1"})
f.answer(`FROM pg_database`, []string{"?column?"}, []string{"1"})
if err := c.CreateDatabaseAndRole(ctx, "mesh_ace_letta", "mesh_ace_letta", "pw"); err != nil {
if err := c.CreateDatabaseAndRole(ctx, "mesh_node-a_letta", "mesh_node-a_letta", "pw"); err != nil {
t.Fatal(err)
}
sql = f.statements()
if !has(sql, `^ALTER ROLE "mesh_ace_letta" WITH LOGIN PASSWORD 'pw' VALID UNTIL 'infinity'`) || has(sql, `CREATE DATABASE`) {
if !has(sql, `^ALTER ROLE "mesh_node-a_letta" WITH LOGIN PASSWORD 'pw' VALID UNTIL 'infinity'`) || has(sql, `CREATE DATABASE`) {
t.Fatal(strings.Join(sql, "\n"))
}
if !has(sql, `^GRANT ALL PRIVILEGES ON DATABASE "mesh_ace_letta" TO "mesh_ace_letta"$`) {
if !has(sql, `^GRANT ALL PRIVILEGES ON DATABASE "mesh_node-a_letta" TO "mesh_node-a_letta"$`) {
t.Fatal(strings.Join(sql, "\n"))
}
noDrop(t, sql)
@@ -67,9 +67,9 @@ func (w *world) give(given ...map[string]any) {
func TestAddingExtensionsAppliesTheConsumerAgain(t *testing.T) {
w := newWorld(t)
w.give(map[string]any{"as": "mesh_ace_letta", "values": map[string]any{"name": "letta"}})
w.give(map[string]any{"as": "mesh_node-a_letta", "values": map[string]any{"name": "letta"}})
w.h.Reconcile(ctx)
w.give(map[string]any{"as": "mesh_ace_letta", "values": map[string]any{"name": "letta", "extensions": []any{"vector"}}})
w.give(map[string]any{"as": "mesh_node-a_letta", "values": map[string]any{"name": "letta", "extensions": []any{"vector"}}})
w.h.Reconcile(ctx)
if len(w.a.created) != 2 {
t.Fatalf("created %d times", len(w.a.created))
@@ -88,14 +88,14 @@ func TestProvisionerCreatesTheDatabaseThenItsExtensions(t *testing.T) {
f.answer(`FROM pg_available_extensions`, []string{"name"}, []string{"vector"})
var events []string
a := provisioner{pg: c, announce: func(e string, _ map[string]string) { events = append(events, e) }}
p := harness.Provision{As: "mesh_ace_letta", Password: "pw", Values: map[string]any{"name": "letta", "extensions": []any{"vector"}}}
p := harness.Provision{As: "mesh_node-a_letta", Password: "pw", Values: map[string]any{"name": "letta", "extensions": []any{"vector"}}}
if err := a.Create(ctx, p); err != nil {
t.Fatal(err)
}
sql := f.statements()
// The extension once the database exists, and last the active mark (novox/hq ADR 0230).
if !strings.HasPrefix(sql[len(sql)-2], `CREATE EXTENSION IF NOT EXISTS "vector"`) || !has(sql, `CREATE DATABASE "mesh_ace_letta"`) ||
sql[len(sql)-1] != `COMMENT ON ROLE "mesh_ace_letta" IS '{"mesh":"consumer"}'` {
if !strings.HasPrefix(sql[len(sql)-2], `CREATE EXTENSION IF NOT EXISTS "vector"`) || !has(sql, `CREATE DATABASE "mesh_node-a_letta"`) ||
sql[len(sql)-1] != `COMMENT ON ROLE "mesh_node-a_letta" IS '{"mesh":"consumer"}'` {
t.Fatal(strings.Join(sql, "\n"))
}
if strings.Join(events, ",") != "database.provisioned" {
@@ -117,7 +117,7 @@ func TestProvisionerCreatesTheDatabaseThenItsExtensions(t *testing.T) {
f.reset()
f.answer(`FROM pg_roles`, []string{"?column?"}, []string{"1"})
if err := a.Retire(ctx, "mesh_ace_letta", nil, "test", time.Now()); err != nil {
if err := a.Retire(ctx, "mesh_node-a_letta", nil, "test", time.Now()); err != nil {
t.Fatal(err)
}
noDrop(t, f.statements())
@@ -28,7 +28,7 @@ const ProvisionName = "postgres-database"
// consumers. What differs from keycloak is only this declaration (issue 849).
//
// It serves its consumers the port its server is published on, on its machine (servedPort), never the container's
// own 5432: on a machine that publishes it elsewhere (6852:5432 on novox) nothing answers 5432 (issue 957).
// own 5432: on a machine that publishes it elsewhere (6852:5432 on node-c) nothing answers 5432 (issue 957).
func moved(consumersFile string, h *harness.Harness, servedPort int) (*harness.Moved, *provider.Provider, provider.Bus, error) {
return harness.NewMoved(consumersFile, h, harness.MovedSpec{Module: "postgres", Provision: ProvisionName,
Offer: provider.Offer{Credential: provider.PerConsumer, Serves: map[string]any{"port": servedPort}},
@@ -3,7 +3,7 @@ package main
import "testing"
// postgres serves its consumers the port its server is published on, on its machine, as its own code reaches it:
// on novox the manifest's 5432 is published as 6852, and a consumer handed 5432 reached nothing (issue 957).
// on node-c the manifest's 5432 is published as 6852, and a consumer handed 5432 reached nothing (issue 957).
func TestPostgresServesThePortItsServerIsPublishedOn(t *testing.T) {
env := func(vars map[string]string) func(string) string { return func(k string) string { return vars[k] } }
for _, c := range []struct {
@@ -9,8 +9,8 @@ package main
// database and a retired one is still worth backing up, and because re-enabling must give it back as it
// was. The mark is the role's comment, a JSON object the mesh owns:
//
// {"mesh":"consumer","node":"ace"} active
// {"mesh":"consumer","node":"ace","retired":"2026-10-06T…Z","why":"…"} retired
// {"mesh":"consumer","node":"node-a"} active
// {"mesh":"consumer","node":"node-a","retired":"2026-10-06T…Z","why":"…"} retired
//
// Asked for again, the ordinary create sets LOGIN and the password again and writes the active mark.
// Deleted — only through `cleanup delete` — the database is dropped, then the role, unless it still owns
@@ -12,15 +12,15 @@ import (
func TestRetiringLocksMarksAndDropsNothing(t *testing.T) {
f, c := newFake(admin)
f.answer(`shobj_description\(oid`, []string{"c"}, []string{`{"mesh":"consumer","node":"ace"}`})
f.answer(`shobj_description\(oid`, []string{"c"}, []string{`{"mesh":"consumer","node":"node-a"}`})
f.answer(`SELECT 1 FROM pg_roles`, []string{"?column?"}, []string{"1"})
at := time.Date(2026, 10, 6, 12, 0, 0, 0, time.UTC)
if err := c.RetireRole(ctx, "mesh_ace_letta", "the mesh stopped asking for it", at); err != nil {
if err := c.RetireRole(ctx, "mesh_node-a_letta", "the mesh stopped asking for it", at); err != nil {
t.Fatal(err)
}
sql := f.statements()
if !has(sql, `ALTER ROLE "mesh_ace_letta" NOLOGIN`) || !has(sql, `pg_terminate_backend.*usename = 'mesh_ace_letta'`) ||
!has(sql, `COMMENT ON ROLE "mesh_ace_letta" IS '\{"mesh":"consumer","node":"ace","retired":"2026-10-06T12:00:00Z","why":"the mesh stopped asking for it"\}'`) {
if !has(sql, `ALTER ROLE "mesh_node-a_letta" NOLOGIN`) || !has(sql, `pg_terminate_backend.*usename = 'mesh_node-a_letta'`) ||
!has(sql, `COMMENT ON ROLE "mesh_node-a_letta" IS '\{"mesh":"consumer","node":"node-a","retired":"2026-10-06T12:00:00Z","why":"the mesh stopped asking for it"\}'`) {
t.Fatal(strings.Join(sql, "\n"))
}
noDrop(t, sql)
@@ -38,14 +38,14 @@ func TestRetiringLocksMarksAndDropsNothing(t *testing.T) {
func TestTheInventoryIsWhatTheMeshMadeByMarkOrShape(t *testing.T) {
f, c := newFake(admin)
f.answer(`FROM pg_roles r`, []string{"rolname", "rolcanlogin", "mark", "mesh_shaped", "size"},
[]string{"active_marked", "t", `{"mesh":"consumer","node":"ace"}`, "f", "100"},
[]string{"active_marked", "t", `{"mesh":"consumer","node":"node-a"}`, "f", "100"},
[]string{"active_shaped", "t", "", "t", "200"},
[]string{"retired_marked", "f", `{"mesh":"consumer","node":"ace","retired":"2026-10-06T12:00:00Z","why":"gone"}`, "t", "300"},
[]string{"retired_marked", "f", `{"mesh":"consumer","node":"node-a","retired":"2026-10-06T12:00:00Z","why":"gone"}`, "t", "300"},
[]string{"locked_before", "f", "", "t", "400"},
[]string{"hal_registry", "t", "", "f", "500"},
[]string{"jochens", "t", "", "t", ""},
)
f.answer(`_deleted_`, []string{"datname", "size"}, []string{"mesh_novox_gitea_deleted_20261005", "7771827"})
f.answer(`_deleted_`, []string{"datname", "size"}, []string{"mesh_node-c_gitea_deleted_20261005", "7771827"})
inv, err := c.Inventory(ctx)
if err != nil {
t.Fatal(err)
@@ -57,7 +57,7 @@ func TestTheInventoryIsWhatTheMeshMadeByMarkOrShape(t *testing.T) {
t.Fatalf("%+v", inv.Retired)
}
r := inv.Retired[0]
if r.Consumer != "retired_marked" || r.Node != "ace" || r.Why != "gone" || r.SizeBytes != 300 ||
if r.Consumer != "retired_marked" || r.Node != "node-a" || r.Why != "gone" || r.SizeBytes != 300 ||
r.RetiredAt.Format(time.RFC3339) != "2026-10-06T12:00:00Z" {
t.Fatalf("%+v", r)
}
@@ -72,13 +72,13 @@ func TestTheInventoryIsWhatTheMeshMadeByMarkOrShape(t *testing.T) {
func TestDeletingRefusesALoginThatCanConnect(t *testing.T) {
f, c := newFake(admin)
f.answer(`SELECT rolcanlogin`, []string{"rolcanlogin"}, []string{"t"})
if _, err := c.DeleteRetired(ctx, harness.Retired{Consumer: "mesh_ace_baserow", Kind: harness.KindConsumer}); err == nil {
if _, err := c.DeleteRetired(ctx, harness.Retired{Consumer: "mesh_node-a_baserow", Kind: harness.KindConsumer}); err == nil {
t.Fatal("deleted an active consumer")
}
noDrop(t, f.statements())
f, c = newFake(admin)
if _, err := c.DeleteRetired(ctx, harness.Retired{Consumer: "mesh_ace_baserow", Kind: KindSetAside}); err == nil {
if _, err := c.DeleteRetired(ctx, harness.Retired{Consumer: "mesh_node-a_baserow", Kind: KindSetAside}); err == nil {
t.Fatal("dropped a database not set aside")
}
noDrop(t, f.statements())
@@ -88,20 +88,20 @@ func TestDeletingDropsTheDatabaseThenTheRole(t *testing.T) {
f, c := newFake(admin)
f.answer(`SELECT rolcanlogin`, []string{"rolcanlogin"}, []string{"f"})
f.answer(`SELECT pg_database_size`, []string{"size"}, []string{"9000"})
freed, err := c.DeleteRetired(ctx, harness.Retired{Consumer: "mesh_ace_letta", Kind: harness.KindConsumer})
freed, err := c.DeleteRetired(ctx, harness.Retired{Consumer: "mesh_node-a_letta", Kind: harness.KindConsumer})
if err != nil || freed != 9000 {
t.Fatal(freed, err)
}
sql := strings.Join(f.statements(), "\n")
db, role := strings.Index(sql, `DROP DATABASE "mesh_ace_letta"`), strings.Index(sql, `DROP ROLE "mesh_ace_letta"`)
db, role := strings.Index(sql, `DROP DATABASE "mesh_node-a_letta"`), strings.Index(sql, `DROP ROLE "mesh_node-a_letta"`)
if db < 0 || role < db {
t.Fatal(sql)
}
// A role still owning a set-aside database is kept, and said.
f, c = newFake(admin)
f.answer(`SELECT rolcanlogin`, []string{"rolcanlogin"}, []string{"f"})
f.answer(`SELECT datname FROM pg_database WHERE datdba`, []string{"datname"}, []string{"mesh_ace_letta_deleted_20261005"})
if _, err := c.DeleteRetired(ctx, harness.Retired{Consumer: "mesh_ace_letta", Kind: harness.KindConsumer}); err == nil ||
f.answer(`SELECT datname FROM pg_database WHERE datdba`, []string{"datname"}, []string{"mesh_node-a_letta_deleted_20261005"})
if _, err := c.DeleteRetired(ctx, harness.Retired{Consumer: "mesh_node-a_letta", Kind: harness.KindConsumer}); err == nil ||
!strings.Contains(err.Error(), "still owns") || has(f.statements(), `DROP ROLE`) {
t.Fatal(err, f.statements())
}
@@ -48,7 +48,7 @@ func keygenStandIn(ctx context.Context, env []string, name string, args ...strin
// 1480/AC1
func TestTheBundlesKeeperIsForgekeysOfKindMachineWithItsEntryAtNodeAccount(t *testing.T) {
home, st := t.TempDir(), &recordState{}
k := keeperFor(home, "g14", "nox", keygenStandIn, st)
k := keeperFor(home, "node-b", "nox", keygenStandIn, st)
k.Now = func() time.Time { return time.Date(2026, 10, 20, 12, 0, 0, 0, time.UTC) }
if err := k.Once(context.Background()); err != nil {
t.Fatal(err)
@@ -56,15 +56,15 @@ func TestTheBundlesKeeperIsForgekeysOfKindMachineWithItsEntryAtNodeAccount(t *te
if _, err := os.Stat(filepath.Join(home, ".ssh", "mesh_ed25519")); err != nil {
t.Errorf("the key file mesh_ed25519 was not made in an empty home: %v", err)
}
raw, ok := st.puts["g14.nox"]
raw, ok := st.puts["node-b.nox"]
if !ok || len(st.puts) != 1 {
t.Fatalf("entries = %v, want one at g14.nox", st.puts)
t.Fatalf("entries = %v, want one at node-b.nox", st.puts)
}
var e forgekey.KeyEntry
if err := json.Unmarshal(raw, &e); err != nil {
t.Fatal(err)
}
if e.Kind != "machine" || e.Node != "g14" || e.Account != "nox" || !strings.HasSuffix(e.Public, " nox@g14 (mesh)") {
if e.Kind != "machine" || e.Node != "node-b" || e.Account != "nox" || !strings.HasSuffix(e.Public, " nox@node-b (mesh)") {
t.Errorf("entry = %s", raw)
}
}
@@ -83,9 +83,9 @@ func client(h string, f *fake) *Client {
// a found predecessor file and a module's drop-in beside it; the operator's lines below.
var layout = map[string]string{
"config": "# BEGIN mesh ssh-client.config\n# the mesh's region\nInclude ~/.ssh/config.d/*\n# END mesh ssh-client.config\n\n" +
"ServerAliveInterval 30\nHost *\n AddKeysToAgent yes\nHost ace\n User wrong\nHost box\n HostName 192.0.2.7\n IdentityFile ~/.ssh/id_box\n",
"config.d/00-mesh": "# Generated by the mesh. Do not edit\n\nHost ace ace.internal\n HostName ace.internal\n User ace\n\nHost shanks shanks.internal\n HostName shanks.internal\n User op\n",
"config.d/mesh": "Host ace\n\tHostName ace.internal\n\tUser ace\n",
"ServerAliveInterval 30\nHost *\n AddKeysToAgent yes\nHost node-a\n User wrong\nHost box\n HostName 192.0.2.7\n IdentityFile ~/.ssh/id_box\n",
"config.d/00-mesh": "# Generated by the mesh. Do not edit\n\nHost node-a node-a.internal\n HostName node-a.internal\n User node-a\n\nHost node-d node-d.internal\n HostName node-d.internal\n User op\n",
"config.d/mesh": "Host node-a\n\tHostName node-a.internal\n\tUser node-a\n",
"config.d/work": "# Generated by the mesh. Do not edit\nHost forge.example\n Port 2222\n",
}
@@ -99,8 +99,8 @@ func TestHostsSayWhereEachCameFromInTheOrderSshReadsThem(t *testing.T) {
for _, s := range got["sections"].([]Section) {
who = append(who, s.Patterns[0]+"@"+s.Source+"/"+s.From)
}
want := []string{"ace@config.d/00-mesh/mesh", "shanks@config.d/00-mesh/mesh", "ace@config.d/mesh/drop-in",
"forge.example@config.d/work/drop-in", "*@config/operator", "ace@config/operator", "box@config/operator"}
want := []string{"node-a@config.d/00-mesh/mesh", "node-d@config.d/00-mesh/mesh", "node-a@config.d/mesh/drop-in",
"forge.example@config.d/work/drop-in", "*@config/operator", "node-a@config/operator", "box@config/operator"}
if !reflect.DeepEqual(who, want) {
t.Fatalf("order/source:\n%v\nwant\n%v", who, want)
}
@@ -108,7 +108,7 @@ func TestHostsSayWhereEachCameFromInTheOrderSshReadsThem(t *testing.T) {
t.Errorf("an operator line after the include read as part of the last included section: %v", got["global"])
}
dups := got["duplicates"].([]map[string]any)
if len(dups) != 1 || dups[0]["host"] != "ace" || dups[0]["wins"] != "config.d/00-mesh:3 (mesh)" {
if len(dups) != 1 || dups[0]["host"] != "node-a" || dups[0]["wins"] != "config.d/00-mesh:3 (mesh)" {
t.Errorf("duplicates: %v", dups)
}
sections := got["sections"].([]Section)
@@ -245,30 +245,30 @@ func TestRevokeKeepsTheFileFirstAndRefusesALockout(t *testing.T) {
func scanOf(host, pub string) string { return host + " " + pub + "\n" }
func TestKnownHostComparesWhatIsKnownWithWhatIsOffered(t *testing.T) {
h := home(t, map[string]string{"config": layout["config"], "known_hosts": "ace.internal " + edPub + "\n"})
h := home(t, map[string]string{"config": layout["config"], "known_hosts": "node-a.internal " + edPub + "\n"})
offered := edPub
f := &fake{answer: func(c call) forgekey.Ran {
switch {
case c.name == "ssh-keygen" && c.args[0] == "-F":
return forgekey.Ran{Stdout: "# Host ace.internal found: line 1\nace.internal " + edPub + "\n"}
return forgekey.Ran{Stdout: "# Host node-a.internal found: line 1\nace.internal " + edPub + "\n"}
case c.name == "ssh-keyscan":
return forgekey.Ran{Stdout: scanOf("ace.internal", offered)}
return forgekey.Ran{Stdout: scanOf("node-a.internal", offered)}
case c.name == "ssh-keygen" && c.args[0] == "-R":
return forgekey.Ran{}
}
return forgekey.Ran{Status: 1}
}}
c := client(h, f)
got, err := c.KnownHost(context.Background(), "ace.internal", 22, false)
got, err := c.KnownHost(context.Background(), "node-a.internal", 22, false)
if err != nil || got["state"] != "matches" {
t.Fatalf("%v %v", got, err)
}
offered = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGZha2VrZXlmYWtla2V5ZmFrZWtleWZha2VrZXlmYWs="
got, _ = c.KnownHost(context.Background(), "ace.internal", 22, false)
got, _ = c.KnownHost(context.Background(), "node-a.internal", 22, false)
if !strings.HasPrefix(got["state"].(string), "changed") {
t.Fatalf("%v", got["state"])
}
got, err = c.KnownHost(context.Background(), "ace.internal", 22, true)
got, err = c.KnownHost(context.Background(), "node-a.internal", 22, true)
if err != nil || got["refreshed"] != true {
t.Fatalf("%v %v", got, err)
}
@@ -278,7 +278,7 @@ func TestKnownHostComparesWhatIsKnownWithWhatIsOffered(t *testing.T) {
}
sawRemove := false
for _, c := range f.recorded() {
if c.name == "ssh-keygen" && reflect.DeepEqual(c.args[:2], []string{"-R", "ace.internal"}) {
if c.name == "ssh-keygen" && reflect.DeepEqual(c.args[:2], []string{"-R", "node-a.internal"}) {
sawRemove = true
}
if c.name == "ssh-keyscan" && !reflect.DeepEqual(c.args[:4], []string{"-T", "5", "-p", "22"}) {
@@ -311,19 +311,19 @@ func TestAnUnreachableHostIsNotRefreshed(t *testing.T) {
}
func TestTestSaysHowItAuthenticatedOrWhyNot(t *testing.T) {
ok := "debug1: Connecting to ace.internal [10.0.0.2] port 22.\ndebug1: Server accepts key: /h/.ssh/id_ed25519 ED25519 " + edFP + "\nAuthenticated to ace.internal ([10.0.0.2]:22) using \"publickey\".\n"
ok := "debug1: Connecting to node-a.internal [10.0.0.2] port 22.\ndebug1: Server accepts key: /h/.ssh/id_ed25519 ED25519 " + edFP + "\nAuthenticated to node-a.internal ([10.0.0.2]:22) using \"publickey\".\n"
f := &fake{answer: func(c call) forgekey.Ran { return forgekey.Ran{Stderr: ok} }}
got, err := client(t.TempDir(), f).Test(context.Background(), "ace")
got, err := client(t.TempDir(), f).Test(context.Background(), "node-a")
if err != nil || got["ok"] != true || got["method"] != "publickey" || got["connected_to"] != "10.0.0.2:22" {
t.Fatalf("%v %v", got, err)
}
args := strings.Join(f.recorded()[0].args, " ")
if !strings.Contains(args, "BatchMode=yes") || !strings.Contains(args, "StrictHostKeyChecking=yes") || !strings.HasSuffix(args, "ace true") {
if !strings.Contains(args, "BatchMode=yes") || !strings.Contains(args, "StrictHostKeyChecking=yes") || !strings.HasSuffix(args, "node-a true") {
t.Fatalf("not a batch test: %s", args)
}
denied := "debug1: Offering public key: /h/.ssh/id_box RSA " + rsaFP + "\nop@ace.internal: Permission denied (publickey).\n"
denied := "debug1: Offering public key: /h/.ssh/id_box RSA " + rsaFP + "\nop@node-a.internal: Permission denied (publickey).\n"
f = &fake{answer: func(c call) forgekey.Ran { return forgekey.Ran{Status: 255, Stderr: denied} }}
got, _ = client(t.TempDir(), f).Test(context.Background(), "ace")
got, _ = client(t.TempDir(), f).Test(context.Background(), "node-a")
if got["ok"] != false || got["why"] != "no key it offered was accepted" || !reflect.DeepEqual(got["offered"], []string{"/h/.ssh/id_box"}) {
t.Fatalf("%v", got)
}
@@ -358,7 +358,7 @@ func TestCheckFindsWhatIsWrongAndSaysWhatItDidNotCheck(t *testing.T) {
}
all := strings.Join(whats, "\n")
for _, want := range []string{"writable by others (0666)", "private key readable by others (0644)", "no passphrase",
"not the first thing in the file", "Host ace is defined 3 times", "RSA of 2048 bits", "not known: the first connection would ask", "config.bak-1"} {
"not the first thing in the file", "Host node-a is defined 3 times", "RSA of 2048 bits", "not known: the first connection would ask", "config.bak-1"} {
if !strings.Contains(all, want) {
t.Errorf("missing %q in:\n%s", want, all)
}
@@ -453,13 +453,13 @@ func TestTheManifestsRegionIsWhatSshReads(t *testing.T) {
if !strings.Contains(region, "Include ~/.ssh/config.d/*") {
t.Fatalf("no include: %q", region)
}
h := home(t, map[string]string{"config": "# BEGIN mesh ssh-client.config\n" + region + "# END mesh ssh-client.config\n\nCompression yes\nHost ace\n User wrong\n",
h := home(t, map[string]string{"config": "# BEGIN mesh ssh-client.config\n" + region + "# END mesh ssh-client.config\n\nCompression yes\nHost node-a\n User wrong\n",
"config.d/00-mesh": layout["config.d/00-mesh"]})
p, err := Parse(h)
if err != nil {
t.Fatal(err)
}
if p.Sections[0].Source != MeshFile || p.Sections[0].Options["user"] != "ace" || len(p.Global) != 1 || !strings.HasSuffix(p.Global[0], " Compression yes") {
if p.Sections[0].Source != MeshFile || p.Sections[0].Options["user"] != "node-a" || len(p.Global) != 1 || !strings.HasSuffix(p.Global[0], " Compression yes") {
t.Fatalf("%+v %v", p.Sections, p.Global)
}
}
@@ -44,7 +44,7 @@ func sweepHome(t *testing.T) (*Client, string) {
put("work_key-cert.pub", "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29t work\n", 0o644)
put("config.bak-20260807-015327", "old\n", 0o600)
put("known_hosts.old", "old\n", 0o644)
put("config.d/mesh", "Host ace\n", 0o600)
put("config.d/mesh", "Host node-a\n", 0o600)
put("removed-hal-20261002/config", "old\n", 0o600)
put("removed-drone-20261004/id_drone", key, 0o600) // a key inside a leftover folder
put("removed-drone-20261004/config", "old\n", 0o600)
@@ -136,7 +136,7 @@ func TestSweepOnceSaysEachMove(t *testing.T) {
func TestSweepLeavesSocketsAndUnreadable(t *testing.T) {
c, dir := sweepHome(t)
sock := filepath.Join(dir, "cm-ace")
sock := filepath.Join(dir, "cm-node-a")
if l, err := net.Listen("unix", sock); err == nil {
defer l.Close()
} else {
@@ -92,12 +92,12 @@ func TestManifestKeepsKeysAndRequiresForgeAccount(t *testing.T) {
// Issue 955, area S3: the three files the forge account fills in — known_hosts, the forge's Host, git's rewrite.
var sampleBound = map[string]string{
"known_host": "[novox.internal]:222 ssh-ed25519 AAAAC3Nza",
"known_host": "[node-c.internal]:222 ssh-ed25519 AAAAC3Nza",
"host_key": "ssh-ed25519 AAAAC3Nza",
"ssh_host": "novox.internal",
"ssh_host": "node-c.internal",
"ssh_alias": "mesh-forge",
"ssh_port": "222",
"ssh_base": "ssh://git@novox.internal:222/",
"ssh_base": "ssh://git@node-c.internal:222/",
"https_base": "https://git.novox.be/",
}
@@ -151,7 +151,7 @@ func TestForgeFilesRenderFromTheBoundValues(t *testing.T) {
if forge["into"] != nil {
t.Errorf("10-forge is a block, want the whole file")
}
want := "Host mesh-forge\n HostName novox.internal\n Port 222\n HostKeyAlias mesh-forge\n User git\n IdentityFile ~/.ssh/mesh_ed25519\n IdentitiesOnly yes\n"
want := "Host mesh-forge\n HostName node-c.internal\n Port 222\n HostKeyAlias mesh-forge\n User git\n IdentityFile ~/.ssh/mesh_ed25519\n IdentitiesOnly yes\n"
if got := renderBound(t, templateText(t, forge)); got != want {
t.Errorf("10-forge = %q, want %q", got, want)
}