A-342 engine-node-env: every process runs with its node's name as MESH_NODE
mesh/delivery merged: source.main moved to novox/mesh-host@fd023f7473bc
mesh/delivery-group group feat/47-G-join12 merged: every member is merged
mesh/check checking 5caa760f5eaa on fd023f7473bc | novox/mesh-host#228 in group feat/47-G-join12

The apply gives each process the declaration's For as MESH_NODE beside its own env, so a process that reads MESH_NODE (mesh-sdk, the zfs follower's writer and its node-service-manager calls) names its node.

Builds: design 47 G A-342

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-13 09:20:46 +01:00
parent d63aae1d55
commit 5caa760f5e
4 changed files with 32 additions and 3 deletions
+5 -2
View File
@@ -785,7 +785,7 @@ func applyScoped(
// change one apply late, and never misses it. // change one apply late, and never misses it.
// And one patience with the artifact store for the whole apply (novox/hq issue 291): a fetch it // And one patience with the artifact store for the whole apply (novox/hq issue 291): a fetch it
// does not answer during a maintenance window waits for the window instead of failing. // does not answer during a maintenance window waits for the window instead of failing.
in := inputs{declares: map[string]string{}, known: &known, windows: windows, away: newStoreAway(windows, log), in := inputs{node: d.For, declares: map[string]string{}, known: &known, windows: windows, away: newStoreAway(windows, log),
groups: wanted, handed: handed} groups: wanted, handed: handed}
// Where nothing is placed or mounted from, judged before anything is applied (novox/hq issue 339), and // Where nothing is placed or mounted from, judged before anything is applied (novox/hq issue 339), and
// which declared directories were already here: one this apply makes is the mesh's, one found may not be. // which declared directories were already here: one this apply makes is the mesh's, one found may not be.
@@ -1625,7 +1625,7 @@ func applyOne(ctx context.Context, sys system.System, r declaration.Resource, ru
if err != nil { if err != nil {
return begin(r), err return begin(r), err
} }
return applyProcess(ctx, res, run, changed, previous, held, in.away) return applyProcess(ctx, withNode(res, in.node), run, changed, previous, held, in.away)
case *declaration.Action: case *declaration.Action:
return applyAction(ctx, res, run) return applyAction(ctx, res, run)
case *declaration.Network: case *declaration.Network:
@@ -2816,6 +2816,9 @@ type inputs struct {
// fetched is every archive's and process's bytes this apply fetched before anything was replaced (fetchFirst), each // fetched is every archive's and process's bytes this apply fetched before anything was replaced (fetchFirst), each
// taken by its applier once (artifact). // taken by its applier once (artifact).
fetched map[string]fetchedArtifact fetched map[string]fetchedArtifact
// node is the node's own name, the declaration's For: every process runs with it as MESH_NODE beside its own env
// (design 47 G A-342). Empty where the declaration names none (the first node's carried bundle).
node string
} }
// fileDigest is what a file the container reads holds, by digest. // fileDigest is what a file the container reads holds, by digest.
+1 -1
View File
@@ -95,7 +95,7 @@ func WriteFilledWith(
"yet taken (%s), so nothing was written", path, held.Why)} "yet taken (%s), so nothing was written", path, held.Why)}
} }
in := inputs{declares: map[string]string{}, known: &known, windows: windows, away: newStoreAway(windows, log), in := inputs{node: d.For, declares: map[string]string{}, known: &known, windows: windows, away: newStoreAway(windows, log),
groups: groupsWanted(d.Resources), handed: Handed{}} groups: groupsWanted(d.Resources), handed: Handed{}}
in.refused = refusedPlaces(d.Resources) in.refused = refusedPlaces(d.Resources)
in.agentHomes = rootNeverHomes(d) in.agentHomes = rootNeverHomes(d)
+19
View File
@@ -274,6 +274,25 @@ func applyProcess(ctx context.Context, r *declaration.Process, run Runner,
return out, nil return out, nil
} }
// NodeEnv is the variable every process the node-engine runs reads its node's name from (design 47 G A-342), the one
// mesh-sdk reads (MESH_NODE).
const NodeEnv = "MESH_NODE"
// withNode is the process as it runs on node: its own env with NodeEnv beside it (design 47 G A-342). The validator
// refuses a resource that sets it, so the mesh's value is the only one; with no node named the process is as declared.
func withNode(r *declaration.Process, node string) *declaration.Process {
if node == "" {
return r
}
p := *r
p.Env = make(map[string]string, len(r.Env)+1)
for k, v := range r.Env {
p.Env[k] = v
}
p.Env[NodeEnv] = node
return &p
}
// unitFor is the unit the mesh writes for a daemon. // unitFor is the unit the mesh writes for a daemon.
// //
// **Generated whole and never edited in place**, the same rule as every other managed file: an // **Generated whole and never edited in place**, the same rule as every other managed file: an
+7
View File
@@ -319,11 +319,17 @@ func TestAProcessAppliedAgainIsUnchangedAndNotRestarted(t *testing.T) {
} }
d := declare(t, `{"id":"node-tools.runtime","type":"process","name":"node-tools","source":"`+serving(t, body)+ d := declare(t, `{"id":"node-tools.runtime","type":"process","name":"node-tools","source":"`+serving(t, body)+
`","digest":"`+digest+`","run":["node","main.js"],"env":{"MESH_TOOL_MODULES":"a=/x/index.js"}}`) `","digest":"`+digest+`","run":["node","main.js"],"env":{"MESH_TOOL_MODULES":"a=/x/index.js"}}`)
d.For = "anchor"
first, state, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginDeclared, run, nil, nil) first, state, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginDeclared, run, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Design 47 G A-342: the process runs with its node's name as MESH_NODE beside its own env.
if b, err := os.ReadFile(filepath.Join(units, "node-tools.service")); err != nil || !strings.Contains(string(b), `Environment="MESH_NODE=anchor"`) ||
!strings.Contains(string(b), `Environment="MESH_TOOL_MODULES=`) {
t.Errorf("the unit does not carry the node's name beside its env (%v):\n%s", err, b)
}
if o := outcomeOf(first, "node-tools.runtime"); o.Action != "created" { if o := outcomeOf(first, "node-tools.runtime"); o.Action != "created" {
t.Fatalf("first apply: %+v, want created", o) t.Fatalf("first apply: %+v, want created", o)
} }
@@ -360,6 +366,7 @@ func TestAProcessAppliedAgainIsUnchangedAndNotRestarted(t *testing.T) {
// An input of its unit that differs restarts it, and the line says which (issue 1966). // An input of its unit that differs restarts it, and the line says which (issue 1966).
changed := declare(t, `{"id":"node-tools.runtime","type":"process","name":"node-tools","source":"`+serving(t, body)+ changed := declare(t, `{"id":"node-tools.runtime","type":"process","name":"node-tools","source":"`+serving(t, body)+
`","digest":"`+digest+`","run":["node","main.js"],"env":{"MESH_TOOL_MODULES":"a=/x/index.js,b=/y/index.js"}}`) `","digest":"`+digest+`","run":["node","main.js"],"env":{"MESH_TOOL_MODULES":"a=/x/index.js,b=/y/index.js"}}`)
changed.For = "anchor"
fourth, _, err := Apply(context.Background(), archHost(t), changed, state, store.OriginDeclared, run, nil, nil) fourth, _, err := Apply(context.Background(), archHost(t), changed, state, store.OriginDeclared, run, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)