H-15: the apply guard and the yields read the node-engine's module from the entry claiming its seat
The module is the one whose catalog entry claims node-engine, else the one this node heard run the build its own fingerprint names; no name is fixed in the guard or in the compose. Builds: design 47 G §364, §527, §428 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -670,6 +670,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
|
||||
composerNow.Store(composer) // what the verb compose asks a pass of, this run's alone
|
||||
session.SetManifestOf(composer.ManifestOf) // the event running names the manifest of the build that runs
|
||||
go followRunning(run, session, composer, say)
|
||||
apply.UseEngineModule(composer.EngineModule) // the guard of the engine's own trees fixes no name (§527)
|
||||
env.Composed, env.SelfComposed, env.Policy, env.ComposedToday = composer, true, composer.Policy, composer.ComposedToday
|
||||
env.WhyUnmade, env.Waiting = composer.WhyUnmade, composer.Waiting
|
||||
env.HoldsData = composer.HoldsData // what runs from the kept declaration says Data as its manifest does
|
||||
@@ -683,6 +684,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
|
||||
current.set(nil)
|
||||
composerNow.Store(nil)
|
||||
session.SetManifestOf(nil)
|
||||
apply.UseEngineModule(nil)
|
||||
shadowNow.Store(nil)
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
|
||||
@@ -54,6 +54,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/declaration"
|
||||
@@ -303,8 +304,28 @@ func splitQuoted(s string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// engineModule is the module whose resources may place in the engine's own trees.
|
||||
const engineModule = "mesh-host"
|
||||
// engineModuleOf answers the module whose resources may place in the engine's own trees: the module whose entry of the
|
||||
// catalog half claims the seat node-engine (design 47 G §527), asked of the node's composer; none until one is given.
|
||||
var engineModuleOf atomic.Pointer[func() string]
|
||||
|
||||
// UseEngineModule gives the apply guard the function that answers the node-engine's own module; nil takes it away. The
|
||||
// guard fixes no name (§364, §527).
|
||||
func UseEngineModule(f func() string) {
|
||||
if f == nil {
|
||||
engineModuleOf.Store(nil)
|
||||
return
|
||||
}
|
||||
engineModuleOf.Store(&f)
|
||||
}
|
||||
|
||||
// isEngineModule is whether module is the node-engine's own.
|
||||
func isEngineModule(module string) bool {
|
||||
f := engineModuleOf.Load()
|
||||
if f == nil || module == "" {
|
||||
return false
|
||||
}
|
||||
return (*f)() == module
|
||||
}
|
||||
|
||||
// passwdFile is the user database homes are read from. A variable so a test names its own.
|
||||
var passwdFile = "/etc/passwd"
|
||||
@@ -531,7 +552,7 @@ func refuseOne(path string, kind placing, module, owner string, declared map[str
|
||||
return &PlacementRefusedError{Path: path, Why: "nothing is placed in or mounted from " + tree}
|
||||
}
|
||||
}
|
||||
if module != engineModule {
|
||||
if !isEngineModule(module) {
|
||||
for _, tree := range engineTrees {
|
||||
if atOrBelow(path, tree) {
|
||||
return &PlacementRefusedError{Path: path, Why: tree + " is the node-engine's own, placed in by " +
|
||||
|
||||
@@ -193,4 +193,24 @@ func TestAModulesOwnPlacesAreStillPlaced(t *testing.T) {
|
||||
if err := refusePath("/var/lib/notes/data", placingDirectory, "notes", "1001:2000"); err != nil {
|
||||
t.Errorf("an owner below /var/lib: %v", err)
|
||||
}
|
||||
// Design 47 G §364, §527: the node-engine's own trees are placed in by the module the composer says claims its seat,
|
||||
// whatever that module is called, and by no other; with none said, by nobody.
|
||||
tree := engineTrees[0]
|
||||
t.Cleanup(func() { UseEngineModule(nil) })
|
||||
for _, c := range []struct {
|
||||
engine func() string
|
||||
module string
|
||||
placed bool
|
||||
}{
|
||||
{func() string { return "the-engine" }, "the-engine", true},
|
||||
{func() string { return "the-engine" }, "notes", false},
|
||||
{func() string { return "the-engine" }, "", false},
|
||||
{func() string { return "" }, "", false},
|
||||
{nil, "the-engine", false},
|
||||
} {
|
||||
UseEngineModule(c.engine)
|
||||
if err := refusePath(tree, placingDirectory, c.module, ""); (err == nil) != c.placed {
|
||||
t.Errorf("%s for module %q: %v, want placed %v", tree, c.module, err, c.placed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,6 +160,13 @@ type Scope struct {
|
||||
// composing node's own consumer (design 47 G §199): the passing of a seat reads it, and a module nothing was heard of
|
||||
// counts by the trunk's entry. Never read from a have key, a want or a call.
|
||||
Heard map[string]map[string]Heard
|
||||
// Engine is told, at each composing of the node's input, the node-engine's own module: the one whose entry of the catalog half
|
||||
// claims the seat node-engine, else the one of the node's heard modules that runs the build OwnFingerprint names (design 47 G
|
||||
// §527); "" when neither shows it. Nil: told to nobody.
|
||||
Engine func(module string)
|
||||
// OwnFingerprint is the fingerprint this node-engine was started at (its MESH_FINGERPRINT); empty when it was started
|
||||
// without one, as an engine of before G.
|
||||
OwnFingerprint string
|
||||
// HeldToday is, in an answer, the modules the node keeps today as they run ("node" for its own composition), each with
|
||||
// how ("held", "failed", "runs a build of the old form"): a candidate that cannot compose one, or holds one only
|
||||
// behind one of them, keeps it as today, which is said under CandidateAnswer.Held and is no refusal (issue 1126).
|
||||
@@ -593,6 +600,31 @@ type Heard struct {
|
||||
Stopped bool
|
||||
}
|
||||
|
||||
// EngineModule is the node-engine's own module (design 47 G §527): the module whose catalog entry claims the seat node-engine,
|
||||
// which the validator makes one per revision (§508). With none claiming it, which only a revision of before G shows, as at a
|
||||
// revert of G, the module this node heard run the build its own fingerprint names; "" when neither shows it.
|
||||
func EngineModule(in compose.Input, node, ownFingerprint string) string {
|
||||
for _, name := range sortedKeys(in.Catalogue) {
|
||||
if resolution.ClaimsEngine(in.Catalogue[name]) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
if ownFingerprint == "" {
|
||||
return ""
|
||||
}
|
||||
for i := range in.Nodes {
|
||||
if in.Nodes[i].Name != node {
|
||||
continue
|
||||
}
|
||||
for _, module := range sortedKeys(in.Nodes[i].Heard) {
|
||||
if in.Nodes[i].Heard[module].Fingerprint == ownFingerprint {
|
||||
return module
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// keepHeard puts, as each node's Heard, what it said it runs of each module it is assigned, with the manifest of the build
|
||||
// read by the event's digest; a digest that cannot be read leaves the entry unread, and the passing says so (§459).
|
||||
func keepHeard(in *compose.Input, s Scope, read func(module, digest string) (catalogue.Manifest, error)) {
|
||||
@@ -643,6 +675,9 @@ func Compose(ctx context.Context, r Reader, mesh Mesh, s Scope, fps map[string]s
|
||||
if err != nil {
|
||||
return nil, nil, held, err
|
||||
}
|
||||
if s.Engine != nil {
|
||||
s.Engine(EngineModule(in, s.Node, s.OwnFingerprint))
|
||||
}
|
||||
if s.Holds != nil {
|
||||
holds := map[string]compose.Hold{}
|
||||
for name := range held {
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"github.com/novox/mesh-host/internal/compositions"
|
||||
"maps"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
@@ -592,6 +593,19 @@ func TestInput(t *testing.T) {
|
||||
t.Errorf("anchor's Heard holds %+v, want f2 unread", n.Heard)
|
||||
}
|
||||
}
|
||||
// Design 47 G §527: the node-engine's own module is the one whose entry claims its seat; with none, the one this node heard
|
||||
// run the build its own fingerprint names; with neither, none.
|
||||
if got := EngineModule(in, "laptop", "f1"); got != "xterm" {
|
||||
t.Errorf("by the own fingerprint: %q, want xterm", got)
|
||||
}
|
||||
if got := EngineModule(in, "laptop", "other"); got != "" {
|
||||
t.Errorf("an unheard fingerprint names %q", got)
|
||||
}
|
||||
in.Catalogue = maps.Clone(in.Catalogue)
|
||||
in.Catalogue["the-engine"] = catalogue.Manifest{Module: "the-engine", Claims: []catalogue.Claim{{Name: "node-engine"}}}
|
||||
if got := EngineModule(in, "laptop", "f1"); got != "the-engine" {
|
||||
t.Errorf("by the claim: %q, want the-engine", got)
|
||||
}
|
||||
// Issue 1126, the seed's refusals. restic and the provisioner's client as the catalogue holds them (mesh-catalog
|
||||
// c05aa5e6), restic moved with an own secret the vault leaves out (it hands it to the client, mesh-vault ownnode.go
|
||||
// handed): composed from the indexer's manifest restic is not handed and stands in for that secret, as novox's,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
@@ -56,8 +57,16 @@ func systemPath(path string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// engineModule is the node-engine's own module, the one that places in engineTrees.
|
||||
const engineModule = "mesh-host"
|
||||
// ClaimsEngine is whether a manifest claims the seat node-engine: the node-engine's own module is the one whose entry of
|
||||
// the catalog half does (design 47 G §527), never a name fixed in code.
|
||||
func ClaimsEngine(m mcat.Manifest) bool {
|
||||
for _, c := range m.Claims {
|
||||
if c.Name == nodeengine.Seat {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// accessRef is how a module names one of its accesses: ${access:<id>}.
|
||||
var accessRef = regexp.MustCompile(`\$\{access:([a-z0-9][a-z0-9-]*)\}`)
|
||||
|
||||
@@ -310,16 +310,25 @@ func TestAModuleOfItsOwnAccountIsServedByARuntimeOfItsOwn(t *testing.T) {
|
||||
}
|
||||
gob, img := builder("gob", "node-build-go"), builder("img", "node-build-image")
|
||||
with.Needed["gob"], with.Needed["img"] = map[string]string{"broker": "own"}, map[string]string{"broker": "own"}
|
||||
// The node-engine's module is the one whose entry claims its seat (design 47 G §527), whatever it is called; with none in
|
||||
// the resolution its settings are not found and the default stands.
|
||||
engine := mcat.Manifest{Module: "mesh-host", Claims: []mcat.Claim{{Name: "node-engine"}}}
|
||||
for _, c := range []struct {
|
||||
settings mcat.SettingsBy
|
||||
share string
|
||||
noEngine bool
|
||||
}{
|
||||
{nil, "50"},
|
||||
{mcat.SettingsBy{"mesh-host": {{From: "anchor", Values: map[string]any{"build-cpu-share": float64(75)}}}}, "75"},
|
||||
{mcat.SettingsBy{"mesh-host": {{From: "anchor", Values: map[string]any{"build-cpu-share": float64(0)}}}}, "50"},
|
||||
{nil, "50", false},
|
||||
{mcat.SettingsBy{"mesh-host": {{From: "anchor", Values: map[string]any{"build-cpu-share": float64(75)}}}}, "75", false},
|
||||
{mcat.SettingsBy{"mesh-host": {{From: "anchor", Values: map[string]any{"build-cpu-share": float64(0)}}}}, "50", false},
|
||||
{mcat.SettingsBy{"mesh-host": {{From: "anchor", Values: map[string]any{"build-cpu-share": float64(75)}}}}, "50", true},
|
||||
} {
|
||||
with.Settings = c.settings
|
||||
runtimes, err := resolutionOwnRuntimes(mcat.Resolution{Node: "anchor", Modules: []mcat.Manifest{gob, img, telegram, goRuntime}}, with)
|
||||
modules := []mcat.Manifest{gob, img, telegram, goRuntime, engine}
|
||||
if c.noEngine {
|
||||
modules = modules[:4]
|
||||
}
|
||||
runtimes, err := resolutionOwnRuntimes(mcat.Resolution{Node: "anchor", Modules: modules}, with)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -21,14 +21,14 @@ func resolutionYieldsOf(r mcat.Resolution, m mcat.Manifest, with mcat.Rendering)
|
||||
if !yields(m) {
|
||||
return nil, nil
|
||||
}
|
||||
engine := mcat.Manifest{Module: engineModule}
|
||||
var engine mcat.Manifest // the module claiming the seat node-engine; none in the resolution leaves the default
|
||||
for _, e := range r.Modules {
|
||||
if e.Module == engineModule {
|
||||
if ClaimsEngine(e) {
|
||||
engine = e
|
||||
}
|
||||
}
|
||||
share := mcat.BuildCPUDefaultPercent
|
||||
for _, s := range Effective(engine, with.Settings[engineModule]) {
|
||||
for _, s := range Effective(engine, with.Settings[engine.Module]) {
|
||||
if s.Key == mcat.BuildCPUShareSetting {
|
||||
if n, ok := percentOf(s.Value); ok {
|
||||
share = n
|
||||
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
"github.com/novox/mesh-host/internal/compositions"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
"maps"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sort"
|
||||
@@ -180,6 +181,8 @@ type Composer struct {
|
||||
// node's own consumer (NoteRunning), under mu; heardRead is whether the first read of the stream ended (RunningRead).
|
||||
heard map[string]map[string]heardRun
|
||||
heardRead bool
|
||||
// engine is the node-engine's own module as the last composing of the node's input found it (Scope.Engine), under mu.
|
||||
engine string
|
||||
// rehand asks for a pass that hands again what the agent waits for (Waiting).
|
||||
rehand chan struct{}
|
||||
// sealedOwn is each own secret's sealed value as first handed, by module/name and generation: a value sealed again
|
||||
@@ -1002,7 +1005,7 @@ func (c *Composer) compose(ctx context.Context) []agent.Update {
|
||||
var keptMu sync.Mutex
|
||||
var holds map[string]compose.Hold
|
||||
fromManifest := map[string]string{} // by module, why it is composed from the indexer's manifest and not a build
|
||||
scope := frombus.Scope{Node: c.env.Node, KeptAt: kept, InOwn: inOwn, Running: running, Heard: c.heardNow(), Kept: func(module, words string) {
|
||||
scope := frombus.Scope{Node: c.env.Node, KeptAt: kept, InOwn: inOwn, Running: running, Heard: c.heardNow(), Engine: c.noteEngine, OwnFingerprint: os.Getenv(engcomp.FingerprintVar), Kept: func(module, words string) {
|
||||
keptMu.Lock()
|
||||
defer keptMu.Unlock()
|
||||
if !slices.Contains(keptRuns[module], words) {
|
||||
@@ -2351,3 +2354,20 @@ func (c *Composer) heardNow() map[string]map[string]frombus.Heard {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// noteEngine keeps the node-engine's own module a composing of the node's input found; one that finds none changes nothing.
|
||||
func (c *Composer) noteEngine(module string) {
|
||||
if module == "" {
|
||||
return
|
||||
}
|
||||
c.mu.Lock()
|
||||
c.engine = module
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// EngineModule is the node-engine's own module (design 47 G §527), for the apply guard; "" until a composing found it.
|
||||
func (c *Composer) EngineModule() string {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.engine
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user