G §422, §698: the node-engine serves first-views, first-view-again and entry-again, and its manifest names the verbs it serves
first-views answers {first-views, entries, wants}, entry-again asks one entry again as a recorded hand act and
answers {asked, read}; first-view-again names no view the engine follows (it follows no branch) and refuses in
words. module.json's claim of node-engine lists every verb the engine serves.
Builds: design 47 G §422, §698
This commit is contained in:
@@ -390,3 +390,27 @@ func composeCandidateFrom(node string, shadow *atomic.Pointer[selfcompose.Shadow
|
||||
return json.RawMessage(raw), nil
|
||||
}
|
||||
}
|
||||
|
||||
// firstViewsFrom answers the seat's verb first-views from the composer of this run (design 47 G §698): the views the node-engine
|
||||
// follows (none), the entries it keeps and the wants it composes from.
|
||||
func firstViewsFrom(composer *atomic.Pointer[selfcompose.Composer]) func() (any, error) {
|
||||
return func() (any, error) {
|
||||
c := composer.Load()
|
||||
if c == nil {
|
||||
return nil, errors.New("this node composes none of its own modules now (it applies the controller's compositions, or its node agent is starting), so it keeps no entry")
|
||||
}
|
||||
views, entries, wants := c.FirstViews()
|
||||
return map[string]any{"first-views": views, "entries": entries, "wants": wants}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// entryAgainFrom asks one entry of the catalog half again, for the seat's verb entry-again (design 47 G §698).
|
||||
func entryAgainFrom(composer *atomic.Pointer[selfcompose.Composer]) func(ctx context.Context, fingerprint string) error {
|
||||
return func(ctx context.Context, fingerprint string) error {
|
||||
c := composer.Load()
|
||||
if c == nil {
|
||||
return errors.New("this node composes none of its own modules now, so it keeps no entry to ask again")
|
||||
}
|
||||
return c.EntryAgain(ctx, fingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1019,6 +1019,7 @@ func runLink(ctx context.Context, opts options) error {
|
||||
queue.Acts.Composition = compositionFrom(current)
|
||||
queue.Acts.Compose = composeFrom(current, &composerNow)
|
||||
queue.Acts.Packages = packagesFrom(mine.Node, queue, &composerNow)
|
||||
queue.Acts.FirstViews, queue.Acts.EntryAgain = firstViewsFrom(&composerNow), entryAgainFrom(&composerNow)
|
||||
queue.Acts.SeatList, queue.Acts.SeatListAgain = seatListRead, seatListAgain
|
||||
queue.Acts.ProcessBuilds = func(module string) (any, error) {
|
||||
d, err := declaredNow(opts.state, nil)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package link
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
@@ -55,6 +56,12 @@ const (
|
||||
// (novox/hq ADR 0636 §7). The node-engine seat's definition carries both verbs.
|
||||
ActSeatList = nodeengine.VerbSeatList
|
||||
ActSeatListAgain = nodeengine.VerbSeatListAgain
|
||||
// ActFirstViews reads the node-engine's first views: the views it follows, the entries it keeps of the builds its wants
|
||||
// and runnings name, and the wants it composes from; ActFirstViewAgain asks a view again and ActEntryAgain an entry
|
||||
// (design 47 G §422, §698).
|
||||
ActFirstViews = nodeengine.VerbFirstViews
|
||||
ActFirstViewAgain = nodeengine.VerbFirstViewAgain
|
||||
ActEntryAgain = nodeengine.VerbEntryAgain
|
||||
// ActProcessBuilds reads the build each process on this node was started with (design 47, *The fingerprint handover*);
|
||||
// it reads only; who may call it is the seat definition's (design 47 G §440, §447).
|
||||
ActProcessBuilds = nodeengine.VerbProcessBuilds
|
||||
@@ -159,6 +166,10 @@ type HandActs struct {
|
||||
// SeatList answers the seat list the engine holds; SeatListAgain makes it read the list now and answers what it holds.
|
||||
SeatList func() (any, error)
|
||||
SeatListAgain func() (any, error)
|
||||
// FirstViews answers the verb first-views; EntryAgain asks one entry of the catalog half again, refusing in words one the
|
||||
// engine keeps none of.
|
||||
FirstViews func() (any, error)
|
||||
EntryAgain func(ctx context.Context, fingerprint string) error
|
||||
// ProcessBuilds answers the build each process of the node was started with, narrowed to a module when one is given.
|
||||
ProcessBuilds func(module string) (any, error)
|
||||
// ComposeCandidate composes a check's candidate (the indexer's candidate key) on this node in shadow and answers per
|
||||
@@ -236,6 +247,21 @@ func (h *HandActs) Call(c EngineCall) (any, error) {
|
||||
return nil, errors.New("this engine reads no seat list yet: its node agent has not started")
|
||||
}
|
||||
return h.SeatList()
|
||||
case ActFirstViews:
|
||||
if h.FirstViews == nil {
|
||||
return nil, errors.New("this engine keeps no first views yet: its node agent has not started")
|
||||
}
|
||||
return h.FirstViews()
|
||||
case ActFirstViewAgain:
|
||||
if arg("owner") == "" || arg("state") == "" || arg("why") == "" {
|
||||
return nil, errors.New("first-view-again names the owner, the state and why")
|
||||
}
|
||||
// The node-engine follows no branch and no view of another part's state: the views it holds are its entries (entry-again)
|
||||
// and its seat list (seat-list-again).
|
||||
return nil, fmt.Errorf("this engine follows no first view of %s's %s: its entries are asked again by entry-again, and its seat list by seat-list-again",
|
||||
arg("owner"), arg("state"))
|
||||
case ActEntryAgain:
|
||||
return h.AskEntryAgain(c.Caller, arg("fingerprint"), arg("why"))
|
||||
case ActProcessBuilds:
|
||||
if h.ProcessBuilds == nil {
|
||||
return nil, errors.New("this engine reads no process yet: its node agent has not started")
|
||||
@@ -284,6 +310,39 @@ func (h *HandActs) Call(c EngineCall) (any, error) {
|
||||
return nil, fmt.Errorf("the node seat %s serves no verb %q", EngineSeat, c.Verb)
|
||||
}
|
||||
|
||||
// AskEntryAgain asks fingerprint's entry of the catalog half again by hand: a hand act recorded with who asked and why, then
|
||||
// done. The answer is the ask and the verb that reads where it stands (design 47 G §698).
|
||||
func (h *HandActs) AskEntryAgain(caller, fingerprint, why string) (any, error) {
|
||||
if h.EntryAgain == nil {
|
||||
return nil, errors.New("this engine reads no entry by fingerprint yet: its node agent has not started")
|
||||
}
|
||||
if fingerprint == "" || why == "" {
|
||||
return nil, errors.New("entry-again names the build's fingerprint and why")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), VaultWithin)
|
||||
defer cancel()
|
||||
a := HandAct{ID: h.id(ActEntryAgain), Act: ActEntryAgain, Subject: fingerprint, Caller: caller, Why: why, At: h.now().UTC(), State: HandDone}
|
||||
err := h.EntryAgain(ctx, fingerprint)
|
||||
if err != nil {
|
||||
a.State, a.Said = HandRefused, err.Error()
|
||||
}
|
||||
// A done ask is kept as an act; a refused one, which any caller may cause, rolls with the judgements.
|
||||
record := h.record
|
||||
if err != nil {
|
||||
record = h.roll
|
||||
}
|
||||
if rerr := record(a); rerr != nil {
|
||||
return nil, rerr
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]any{
|
||||
"asked": map[string]any{"v": sdkkeys.Version, "fingerprint": fingerprint, "why": why, "by": caller, "at": a.At},
|
||||
"read": ActFirstViews,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ---- the record --------------------------------------------------------------------------------------------------
|
||||
|
||||
// ActStore is the engine's own state, this node's keys alone (BusState prefixes them with the node): each act one key,
|
||||
|
||||
@@ -271,6 +271,16 @@ func TestALinkThatHearsHandOversIsGivenTheQueues(t *testing.T) {
|
||||
ManifestReads: func() ([]string, time.Time) {
|
||||
return []string{"form:a", "seat:b"}, time.Date(2026, 11, 4, 10, 0, 0, 0, time.UTC)
|
||||
},
|
||||
// Design 47 G §422, §698: the first views and the entries asked again.
|
||||
FirstViews: func() (any, error) {
|
||||
return map[string]any{"first-views": []any{}, "entries": []any{}, "wants": []any{}}, nil
|
||||
},
|
||||
EntryAgain: func(_ context.Context, fingerprint string) error {
|
||||
if fingerprint == "src2:gone" {
|
||||
return errors.New("no entry of src2:gone is kept")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
// Design 47: the build each process was started with, narrowed by module.
|
||||
ProcessBuilds: func(module string) (any, error) {
|
||||
return map[string]any{"processes": []any{}, "module": module}, nil
|
||||
@@ -315,6 +325,11 @@ func TestALinkThatHearsHandOversIsGivenTheQueues(t *testing.T) {
|
||||
{"seat-list-again", "mesh.seat.node-engine.call.seat-list-again.n1.ace~mesh-healers", `"read":true`},
|
||||
{"seat-list-again", "mesh.seat.node-engine.call.seat-list-again.n1.ace~messenger", `"read":true`},
|
||||
|
||||
{"first-views", "mesh.seat.node-engine.call.first-views.n1.person~jochen", `"entries":[],"first-views":[],"wants":[]`},
|
||||
{"first-view-again", "mesh.seat.node-engine.call.first-view-again.n1.person~jochen " + `{"owner":"mesh-catalog","state":"modules","why":"doubt"}`, `follows no first view of mesh-catalog's modules`},
|
||||
{"entry-again", "mesh.seat.node-engine.call.entry-again.n1.person~jochen " + `{"fingerprint":"src2:a","why":"doubt"}`, `"fingerprint":"src2:a","v":1,"why":"doubt"},"read":"first-views"`},
|
||||
{"entry-again", "mesh.seat.node-engine.call.entry-again.n1.person~jochen " + `{"fingerprint":"src2:gone","why":"doubt"}`, `no entry of src2:gone is kept`},
|
||||
{"entry-again", "mesh.seat.node-engine.call.entry-again.n1.person~jochen " + `{"fingerprint":"src2:a"}`, `entry-again names the build's fingerprint and why`},
|
||||
{"process-builds", "mesh.seat.node-engine.call.process-builds.n1.person~jochen " + `{"module":"web"}`, `"module":"web","processes":[]`},
|
||||
{"process-builds", "mesh.seat.node-engine.call.process-builds.n1.ace~messenger", `"processes":[]`},
|
||||
{"unserved", "mesh.seat.node-engine.call.unserved.n1.x", `"error":"the node seat node-engine serves no verb \"unserved\""`},
|
||||
@@ -329,8 +344,8 @@ func TestALinkThatHearsHandOversIsGivenTheQueues(t *testing.T) {
|
||||
}
|
||||
}
|
||||
acts, _ := h.Acts(0)
|
||||
if got := acts.(map[string]any)["acts"].([]HandAct); len(got) != 3 || got[0].Caller != "unproven" ||
|
||||
got[2].Caller != "shanks.node-tools" || got[2].Act != ActJudgeDirectories {
|
||||
if got := acts.(map[string]any)["acts"].([]HandAct); len(got) != 5 || got[2].Caller != "unproven" ||
|
||||
got[4].Caller != "shanks.node-tools" || got[4].Act != ActJudgeDirectories || got[0].Act != ActEntryAgain || got[0].State != HandRefused {
|
||||
t.Errorf("the judgements are recorded as %+v", got)
|
||||
}
|
||||
})
|
||||
|
||||
+20
-1
@@ -17,7 +17,26 @@
|
||||
"claims": [
|
||||
{
|
||||
"name": "node-engine",
|
||||
"scope": "node"
|
||||
"scope": "node",
|
||||
"serves": [
|
||||
"hand-over",
|
||||
"access-act",
|
||||
"judge-directories",
|
||||
"setuid-search",
|
||||
"root-free",
|
||||
"acts",
|
||||
"packages",
|
||||
"composition",
|
||||
"compose",
|
||||
"compose-candidate",
|
||||
"manifest-reads",
|
||||
"seat-list",
|
||||
"seat-list-again",
|
||||
"first-views",
|
||||
"first-view-again",
|
||||
"entry-again",
|
||||
"process-builds"
|
||||
]
|
||||
}
|
||||
],
|
||||
"invokes": [
|
||||
|
||||
Reference in New Issue
Block a user