R-1: W1.3: a process reads its own build from MESH_FINGERPRINT once, and says it

The node-engine hands each process the fingerprint of the want it applies (design 47, The fingerprint handover; mesh-host
W13.1). process.OwnBuild reads it once at the first ask, which Connect and Dial make before they dial, and says it in
the journal; with none, or one that is no single word, it says "own build unknown" and why.

Builds: design 47 G §201, §205, §239

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 10:51:06 +01:00
parent 91ad895c83
commit b37ed85559
4 changed files with 111 additions and 0 deletions
+47
View File
@@ -294,3 +294,50 @@ func TestModuleAccountNamesAndDialOptions(t *testing.T) {
t.Errorf("a fingerprint did not pin the connection")
}
}
// W1.3 (design 47 *The fingerprint handover*): a process's own build is the fingerprint the node-engine handed it, read
// once at its start; with none, or with one that is no single word, it says its own build is unknown and why.
func TestTheOwnBuildIsTheFingerprintTheProcessWasStartedWith(t *testing.T) {
for _, c := range []struct{ name, env, fp, unknown string }{
{"handed", "src2:1480985e", "src2:1480985e", ""},
{"handed with spaces around", " src2:1480985e\n", "src2:1480985e", ""},
{"not handed", "", "", "own build unknown: started without MESH_FINGERPRINT"},
{"not one word", "src2:1 src2:2", "", `own build unknown: MESH_FINGERPRINT "src2:1 src2:2" is not one fingerprint`},
} {
b := ReadOwnBuild(func(k string) string {
if k != FingerprintEnv {
t.Fatalf("%s: read %s", c.name, k)
}
return c.env
})
if b.Fingerprint != c.fp || b.Unknown != c.unknown {
t.Errorf("%s: got %+v, want fingerprint %q, unknown %q", c.name, b, c.fp, c.unknown)
}
}
var said []Build
keptSay, keptOnce := sayOwnBuild, ownBuild
t.Cleanup(func() { sayOwnBuild, ownBuild = keptSay, keptOnce })
sayOwnBuild, ownBuild = func(b Build) { said = append(said, b) }, sync.OnceValue(readAndSay)
t.Setenv(FingerprintEnv, "src2:aaaa")
first := OwnBuild()
t.Setenv(FingerprintEnv, "src2:bbbb")
if again := OwnBuild(); again != first || first.Fingerprint != "src2:aaaa" {
t.Errorf("read once: first %+v, then %+v", first, again)
}
if len(said) != 1 || said[0].String() != "src2:aaaa" {
t.Errorf("the journal is told once, at the first read: %+v", said)
}
// Both ways a process reaches the bus read it at the start, before they dial: a refused dial still said it.
for name, dial := range map[string]func(Account) error{
"Connect": func(a Account) error { _, err := Connect(a); return err },
"Dial": func(a Account) error { _, _, err := a.Dial(nats.RetryOnFailedConnect(false)); return err },
} {
said, ownBuild = nil, sync.OnceValue(readAndSay)
if err := dial(Account{URL: "nats://127.0.0.1:1", User: "n1.m", Password: "pw"}); err == nil {
t.Fatalf("%s: dialled a port nothing listens on", name)
}
if len(said) != 1 || said[0].Fingerprint != "src2:bbbb" {
t.Errorf("%s: the process's own build is not said at its start: %+v", name, said)
}
}
}
+1
View File
@@ -53,6 +53,7 @@ var refusedPublish = regexp.MustCompile(`(?i)Publish to "(\S+)"`)
// permission violation refusing a publish this connection is waiting an answer to, and cancels that wait
// at once.
func Connect(account Account, opts ...nats.Option) (*Conn, error) {
OwnBuild()
c := &Conn{account: account, pending: map[string]map[uint64]context.CancelCauseFunc{}}
nc, err := nats.Connect(account.URL, c.options(opts)...)
if err != nil {
+1
View File
@@ -52,6 +52,7 @@ func (a Account) DialOptions(opts ...nats.Option) []nats.Option {
// Dial connects as the module's own principal and opens JetStream on the connection. The caller closes the
// connection.
func (a Account) Dial(opts ...nats.Option) (*nats.Conn, jetstream.JetStream, error) {
OwnBuild()
nc, err := nats.Connect(a.URL, a.DialOptions(opts...)...)
if err != nil {
return nil, nil, fmt.Errorf("connecting to the bus: %w", err)
+62
View File
@@ -0,0 +1,62 @@
package process
import (
"fmt"
"log"
"os"
"strings"
"sync"
)
// FingerprintEnv is the environment value the node-engine starts each process with, a service and a container alike:
// the fingerprint of the want it applies (novox/hq ADR 0656; design 47 *The fingerprint handover*).
const FingerprintEnv = "MESH_FINGERPRINT"
// OwnBuildUnknown is what a process started with no fingerprint says of its own build, in its journal and to whoever
// reads it (the node-engine's process builds, the indexer acting on it as ADR 0639 §8 has it).
const OwnBuildUnknown = "own build unknown"
// Build is the build a process was started with: its fingerprint, or why it is unknown.
type Build struct {
Fingerprint string `json:"fingerprint,omitempty"`
// Unknown says why there is no fingerprint, starting with OwnBuildUnknown; empty when there is one.
Unknown string `json:"unknown,omitempty"`
}
// String is the fingerprint, or why the build is unknown.
func (b Build) String() string {
if b.Fingerprint != "" {
return b.Fingerprint
}
return b.Unknown
}
// ReadOwnBuild is the build getenv's FingerprintEnv names: one word, or unknown with the reason.
func ReadOwnBuild(getenv func(string) string) Build {
fp := strings.TrimSpace(getenv(FingerprintEnv))
switch {
case fp == "":
return Build{Unknown: OwnBuildUnknown + ": started without " + FingerprintEnv}
case strings.ContainsAny(fp, " \t\r\n"):
return Build{Unknown: fmt.Sprintf("%s: %s %q is not one fingerprint", OwnBuildUnknown, FingerprintEnv, fp)}
}
return Build{Fingerprint: fp}
}
// sayOwnBuild writes the process's own build in its journal; a variable so a test reads what it says.
var sayOwnBuild = func(b Build) { log.Printf("own build: %s", b) }
// readAndSay reads the build from this process's environment and says it.
func readAndSay() Build {
b := ReadOwnBuild(os.Getenv)
sayOwnBuild(b)
return b
}
// ownBuild is read once; a test that reads it makes it again (sync.OnceValue(readAndSay)) and puts it back.
var ownBuild = sync.OnceValue(readAndSay)
// OwnBuild is this process's own build, read once from its environment at the first ask and said in its journal then;
// every later ask answers the same, whatever the environment says since. Connect and Dial ask it, so every process
// that reaches the bus says it at its start.
func OwnBuild() Build { return ownBuild() }