H-20: a tools bundle's words hold its module's want fingerprint

Builds: design 47 G §519, §618
This commit is contained in:
2026-11-12 04:55:33 +01:00
parent 94f6c3dba4
commit 4d656fe4e1
4 changed files with 92 additions and 3 deletions
+2 -2
View File
@@ -388,8 +388,8 @@ func ToolsRunning(running []engcomp.Composed) map[string][]catalogue.Bundle {
}
var own map[string]string
for w, v := range words[module] {
if w == catalogue.FilesOwnedPaths || w == catalogue.ReadsSecretPlaces {
continue // the mesh's to say, composed again
if w == catalogue.FilesOwnedPaths || w == catalogue.ReadsSecretPlaces || w == engcomp.FingerprintVar {
continue // the mesh's to say, composed again (the fingerprint is the want's, §519)
}
if own == nil {
own = map[string]string{}
+6 -1
View File
@@ -180,7 +180,12 @@ func TestCompositionsAreTheDeclarationSplitByModule(t *testing.T) {
}
}
}
if env[catalogue.RuntimeToolModules] != served || env[catalogue.RuntimeToolEnv] != `{"lamp":{"LAMP_CONF":"/var/lib/lamp/conf"}}` {
// §519: the words each bundle's module is given hold the fingerprint of the want it applies, where there is one.
words := `{"lamp":{"LAMP_CONF":"/var/lib/lamp/conf"}}`
if fp := row.fps["lamp"]; fp != "" {
words = `{"lamp":{"LAMP_CONF":"/var/lib/lamp/conf","MESH_FINGERPRINT":"` + fp + `"}}`
}
if env[catalogue.RuntimeToolModules] != served || env[catalogue.RuntimeToolEnv] != words {
t.Errorf("%s: the runtime serves %v with %v, want what runs of lamp, %s", row.name,
env[catalogue.RuntimeToolModules], env[catalogue.RuntimeToolEnv], served)
}
+21
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
seat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"slices"
"strings"
"testing"
@@ -203,6 +204,26 @@ func TestComparingCompositions(t *testing.T) {
t.Errorf("the node's own carries %s: %s", FingerprintVar, raw)
}
}
// §519, §618: the tool runner's process hands each bundle's module its own want's fingerprint in the words of its
// bundles, a module with bundles and no words included, and the runner's own fingerprint reaches no other module's.
fpLamp, fpBare := "src2:"+strings.Repeat("1", 64), "src2:"+strings.Repeat("2", 64)
runner, err := Split("novox", []json.RawMessage{json.RawMessage(`{"id":"r","type":"process","module":"nats","run":["x"],"env":{` +
`"` + catalogue.RuntimeToolModules + `":"bare=/b/x,lamp=/l/y","` + catalogue.RuntimeToolEnv + `":"{\"lamp\":{\"LAMP_CONF\":\"/c\"}}"}}`)},
map[string]string{"nats": fp, "lamp": fpLamp, "bare": fpBare}, nil, NodeOwn{})
if err != nil {
t.Fatal(err)
}
var proc struct{ Env map[string]string }
_ = json.Unmarshal(runner[mustKey(t, "novox", "nats", fp)].Resources[0], &proc)
var given map[string]map[string]string
if err := json.Unmarshal([]byte(proc.Env[catalogue.RuntimeToolEnv]), &given); err != nil {
t.Fatal(err)
}
if given["lamp"][FingerprintVar] != fpLamp || given["lamp"]["LAMP_CONF"] != "/c" || given["bare"][FingerprintVar] != fpBare ||
len(given["nats"]) != 0 || proc.Env[FingerprintVar] != fp {
t.Errorf("the bundles' words are %v and the runner's env %v", given, proc.Env)
}
}
func mustKey(t *testing.T, node, module, fp string) string {
+63
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"regexp"
"strings"
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
@@ -239,6 +240,9 @@ func Split(node string, resources []json.RawMessage, fps map[string]string, fill
if raw, err = withFingerprint(raw, head.Type, fps[head.Module]); err != nil {
return nil, err
}
if raw, err = withBundleFingerprints(raw, head.Type, fps); err != nil {
return nil, err
}
}
c := out[key]
c.Resources = append(c.Resources, raw)
@@ -286,3 +290,62 @@ func withFingerprint(raw json.RawMessage, kind, fp string) (json.RawMessage, err
fields["env"] = body
return json.Marshal(fields)
}
// withBundleFingerprints is raw, when it is a process whose env hands tools bundles their words (catalogue.RuntimeToolEnv),
// with each module's words holding FingerprintVar set to that module's fingerprint in fps: a bundle reads the fingerprint of
// its own module's want from the environment the tool runner starts it with (design 47 G §519). A module fps names no
// fingerprint for keeps its words, and the runner's own fingerprint reaches no bundle but its own module's (§618).
func withBundleFingerprints(raw json.RawMessage, kind string, fps map[string]string) (json.RawMessage, error) {
if kind != string(declaration.TypeProcess) {
return raw, nil
}
var fields map[string]json.RawMessage
if err := json.Unmarshal(raw, &fields); err != nil {
return nil, fmt.Errorf("compositions: a %s resource is not JSON: %w", kind, err)
}
var env map[string]string
if had, ok := fields["env"]; !ok || json.Unmarshal(had, &env) != nil || env[catalogue.RuntimeToolModules] == "" {
return raw, nil
}
given := map[string]map[string]string{}
if had := env[catalogue.RuntimeToolEnv]; had != "" {
if err := json.Unmarshal([]byte(had), &given); err != nil {
return nil, fmt.Errorf("compositions: the %s of a process is not words by module: %w", catalogue.RuntimeToolEnv, err)
}
}
for module, words := range given {
if fp := fps[module]; fp != "" {
if words == nil {
words = map[string]string{}
given[module] = words
}
words[FingerprintVar] = fp
}
}
for module, fp := range fps { // a module with bundles and no words of its own still reads its fingerprint
if _, has := given[module]; !has && fp != "" && hasLoadedBundle(env, module) {
given[module] = map[string]string{FingerprintVar: fp}
}
}
body, err := json.Marshal(given)
if err != nil {
return nil, err
}
env[catalogue.RuntimeToolEnv] = string(body)
envBody, err := json.Marshal(env)
if err != nil {
return nil, err
}
fields["env"] = envBody
return json.Marshal(fields)
}
// hasLoadedBundle is whether the runtime's served list (catalogue.RuntimeToolModules, `module=path` entries) names a bundle of module.
func hasLoadedBundle(env map[string]string, module string) bool {
for _, entry := range strings.Split(env[catalogue.RuntimeToolModules], ",") {
if strings.HasPrefix(entry, module+"=") {
return true
}
}
return false
}