From 78e61c94371d364984579f8a85a618a62a8f9f31 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 21:36:34 +0100 Subject: [PATCH] K25 (D-33): the node-engine's first-views answers entries and wants, and the seat gains entry-again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds: design 47 G §698 (§683, §422) Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ --- go/seats/nodeengine/contract.go | 64 +++++++++++- go/seats/nodeengine/contract_test.go | 6 ++ seats/node-engine/fixtures/verbs.json | 53 ++++++++++ seats/node-engine/seat.json | 134 +++++++++++++++++++++++++- ts/seats/nodeengine/index.d.ts | 53 ++++++++++ ts/seats/nodeengine/index.js | 1 + 6 files changed, 308 insertions(+), 3 deletions(-) diff --git a/go/seats/nodeengine/contract.go b/go/seats/nodeengine/contract.go index 96e1647..79347cb 100644 --- a/go/seats/nodeengine/contract.go +++ b/go/seats/nodeengine/contract.go @@ -30,6 +30,7 @@ const ( VerbSeatListAgain = "seat-list-again" VerbFirstViews = "first-views" VerbFirstViewAgain = "first-view-again" + VerbEntryAgain = "entry-again" VerbProcessBuilds = "process-builds" ) @@ -46,7 +47,7 @@ const ( // Verbs is every verb the seat serves, in its definition's order: a new copy each call. func Verbs() []string { - return []string{VerbHandOver, VerbAccessAct, VerbJudgeDirectories, VerbSetuidSearch, VerbRootFree, VerbActs, VerbPackages, VerbComposition, VerbCompose, VerbComposeCandidate, VerbManifestReads, VerbSeatList, VerbSeatListAgain, VerbFirstViews, VerbFirstViewAgain, VerbProcessBuilds} + return []string{VerbHandOver, VerbAccessAct, VerbJudgeDirectories, VerbSetuidSearch, VerbRootFree, VerbActs, VerbPackages, VerbComposition, VerbCompose, VerbComposeCandidate, VerbManifestReads, VerbSeatList, VerbSeatListAgain, VerbFirstViews, VerbFirstViewAgain, VerbEntryAgain, VerbProcessBuilds} } // Events is every event published on the seat's subject: a new copy each call. @@ -1366,10 +1367,41 @@ type SeatListAgainAnswer = SeatList type FirstViewsInput struct { } +// FirstViewsAnswerEntriesItem is a type of the contract. +type FirstViewsAnswerEntriesItem struct { + // the build's fingerprint + Fingerprint string `json:"fingerprint"` + // since when it stands in that state, RFC 3339 + Since time.Time `json:"since"` + // asked of the indexer, answered, or not found by it + State string `json:"state"` +} + +// The values of FirstViewsAnswerEntriesItem's state. +const ( + FirstViewsAnswerEntriesItemStateAsked = "asked" + FirstViewsAnswerEntriesItemStateAnswered = "answered" + FirstViewsAnswerEntriesItemStateNotFound = "not-found" +) + +// FirstViewsAnswerWantsItem is a type of the contract. +type FirstViewsAnswerWantsItem struct { + // the fingerprint wanted + Fingerprint string `json:"fingerprint"` + // since when the want is kept, RFC 3339 + KeptSince time.Time `json:"kept-since"` + // the module the want is of + Module string `json:"module"` +} + // FirstViewsAnswer is what first-views answers. type FirstViewsAnswer struct { + // one row per entry the node-engine keeps of a module's build by its fingerprint: when it asked the indexer's entry and where that stands (design 47 G 698) + Entries []FirstViewsAnswerEntriesItem `json:"entries"` // one status per owner and state followed, as mesh-sdk's views runtime keeps it FirstViews []map[string]any `json:"first-views"` + // the wants the node-engine keeps (design 47 G 683, 698) + Wants []FirstViewsAnswerWantsItem `json:"wants"` } // FirstViewAgainInput is what first-view-again takes. @@ -1406,6 +1438,36 @@ type FirstViewAgainAnswer struct { Read string `json:"read"` } +// EntryAgainInput is what entry-again takes. +type EntryAgainInput struct { + // the build's fingerprint, as first-views' entries name it + Fingerprint string `json:"fingerprint"` + // why it is asked again, in plain words + Why string `json:"why"` +} + +// EntryAgainAnswerAsked is the hand ask written. +type EntryAgainAnswerAsked struct { + // when it was asked, RFC 3339 + At time.Time `json:"at"` + // the caller the runtime proved + By string `json:"by"` + // the build's fingerprint + Fingerprint string `json:"fingerprint"` + // the layout's version + V int64 `json:"v"` + // why it was asked again + Why string `json:"why"` +} + +// EntryAgainAnswer is what entry-again answers. +type EntryAgainAnswer struct { + // the hand ask written + Asked EntryAgainAnswerAsked `json:"asked"` + // the verb that reads the status, first-views + Read string `json:"read"` +} + // ProcessBuildsInput is what process-builds takes. type ProcessBuildsInput struct { // only this module's processes diff --git a/go/seats/nodeengine/contract_test.go b/go/seats/nodeengine/contract_test.go index 7551025..b3285b5 100644 --- a/go/seats/nodeengine/contract_test.go +++ b/go/seats/nodeengine/contract_test.go @@ -115,6 +115,12 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v ProcessBuildsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "first-view-again names the owner, its state and why", subject: "", given: "{\"owner\":\"mesh-delivery-indexer\",\"state\":\"manifests\",\"why\":\"the picture lags the indexer by hand\"}", wire: "{\"owner\":\"mesh-delivery-indexer\",\"state\":\"manifests\",\"why\":\"the picture lags the indexer by hand\"}", decode: func(b []byte) (any, error) { var v FirstViewAgainInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "first-views answers its entries and its kept wants", subject: "", given: "{\"entries\":[{\"fingerprint\":\"b3f1c9\",\"since\":\"2026-11-11T08:00:00Z\",\"state\":\"asked\"},{\"fingerprint\":\"a77d02\",\"since\":\"2026-11-11T08:01:00Z\",\"state\":\"answered\"},{\"fingerprint\":\"0c1e55\",\"since\":\"2026-11-11T08:02:00Z\",\"state\":\"not-found\"}],\"first-views\":[],\"wants\":[{\"fingerprint\":\"b3f1c9\",\"kept-since\":\"2026-11-11T07:00:00Z\",\"module\":\"mesh-delivery-indexer\"}]}", wire: "{\"entries\":[{\"fingerprint\":\"b3f1c9\",\"since\":\"2026-11-11T08:00:00Z\",\"state\":\"asked\"},{\"fingerprint\":\"a77d02\",\"since\":\"2026-11-11T08:01:00Z\",\"state\":\"answered\"},{\"fingerprint\":\"0c1e55\",\"since\":\"2026-11-11T08:02:00Z\",\"state\":\"not-found\"}],\"first-views\":[],\"wants\":[{\"fingerprint\":\"b3f1c9\",\"kept-since\":\"2026-11-11T07:00:00Z\",\"module\":\"mesh-delivery-indexer\"}]}", + decode: func(b []byte) (any, error) { var v FirstViewsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "entry-again names the fingerprint and why", subject: "", given: "{\"fingerprint\":\"b3f1c9\",\"why\":\"the entry could not be read\"}", wire: "{\"fingerprint\":\"b3f1c9\",\"why\":\"the entry could not be read\"}", + decode: func(b []byte) (any, error) { var v EntryAgainInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "entry-again answers what was asked and where it is read", subject: "", given: "{\"asked\":{\"at\":\"2026-11-11T08:00:00Z\",\"by\":\"operator@shanks\",\"fingerprint\":\"b3f1c9\",\"v\":1,\"why\":\"the entry could not be read\"},\"read\":\"first-views\"}", wire: "{\"asked\":{\"at\":\"2026-11-11T08:00:00Z\",\"by\":\"operator@shanks\",\"fingerprint\":\"b3f1c9\",\"v\":1,\"why\":\"the entry could not be read\"},\"read\":\"first-views\"}", + decode: func(b []byte) (any, error) { var v EntryAgainAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, } for _, c := range cases { v, err := c.decode([]byte(c.given)) diff --git a/seats/node-engine/fixtures/verbs.json b/seats/node-engine/fixtures/verbs.json index a1488ee..2e3cc3f 100644 --- a/seats/node-engine/fixtures/verbs.json +++ b/seats/node-engine/fixtures/verbs.json @@ -165,5 +165,58 @@ "state": "manifests", "why": "the picture lags the indexer by hand" } + }, + { + "name": "first-views answers its entries and its kept wants", + "of": "verb:first-views.answer", + "given": { + "first-views": [], + "entries": [ + { + "fingerprint": "b3f1c9", + "state": "asked", + "since": "2026-11-11T08:00:00Z" + }, + { + "fingerprint": "a77d02", + "state": "answered", + "since": "2026-11-11T08:01:00Z" + }, + { + "fingerprint": "0c1e55", + "state": "not-found", + "since": "2026-11-11T08:02:00Z" + } + ], + "wants": [ + { + "module": "mesh-delivery-indexer", + "fingerprint": "b3f1c9", + "kept-since": "2026-11-11T07:00:00Z" + } + ] + } + }, + { + "name": "entry-again names the fingerprint and why", + "of": "verb:entry-again.input", + "given": { + "fingerprint": "b3f1c9", + "why": "the entry could not be read" + } + }, + { + "name": "entry-again answers what was asked and where it is read", + "of": "verb:entry-again.answer", + "given": { + "asked": { + "v": 1, + "fingerprint": "b3f1c9", + "why": "the entry could not be read", + "by": "operator@shanks", + "at": "2026-11-11T08:00:00Z" + }, + "read": "first-views" + } } ] diff --git a/seats/node-engine/seat.json b/seats/node-engine/seat.json index ddab4d6..19a8dd5 100644 --- a/seats/node-engine/seat.json +++ b/seats/node-engine/seat.json @@ -715,7 +715,7 @@ }, { "name": "first-views", - "description": "The node-engine's first views of the owners' states it follows (design 47 G 422, 204): per owner and state, the tool it asks, the revision its picture holds and why it is behind, when it asked and was answered, how many asks and whether its condition is raised; served by mesh-sdk's runtime as the view's read tool.", + "description": "The node-engine's first views of the owners' states it follows (design 47 G 422, 204): per owner and state, the tool it asks, the revision its picture holds and why it is behind, when it asked and was answered, how many asks and whether its condition is raised; served by mesh-sdk's runtime as the view's read tool; its entries and its kept wants beside (design 47 G 698).", "class": "read", "input": { "type": "object", @@ -724,7 +724,9 @@ "answer": { "type": "object", "required": [ - "first-views" + "first-views", + "entries", + "wants" ], "properties": { "first-views": { @@ -733,6 +735,65 @@ "type": "object" }, "description": "one status per owner and state followed, as mesh-sdk's views runtime keeps it" + }, + "entries": { + "type": "array", + "description": "one row per entry the node-engine keeps of a module's build by its fingerprint: when it asked the indexer's entry and where that stands (design 47 G 698)", + "items": { + "type": "object", + "required": [ + "fingerprint", + "state", + "since" + ], + "properties": { + "fingerprint": { + "type": "string", + "description": "the build's fingerprint" + }, + "state": { + "type": "string", + "enum": [ + "asked", + "answered", + "not-found" + ], + "description": "asked of the indexer, answered, or not found by it" + }, + "since": { + "type": "string", + "format": "date-time", + "description": "since when it stands in that state, RFC 3339" + } + } + } + }, + "wants": { + "type": "array", + "description": "the wants the node-engine keeps (design 47 G 683, 698)", + "items": { + "type": "object", + "required": [ + "module", + "fingerprint", + "kept-since" + ], + "properties": { + "module": { + "type": "string", + "description": "the module the want is of" + }, + "fingerprint": { + "type": "string", + "description": "the fingerprint wanted" + }, + "kept-since": { + "type": "string", + "format": "date-time", + "description": "since when the want is kept, RFC 3339" + } + } + } } } } @@ -816,6 +877,75 @@ } } }, + { + "name": "entry-again", + "description": "Ask the indexer's entry of a build again now, by hand (design 47 G 683, 698): a hand act, its caller and why kept; the answer says what was asked and the verb that reads it.", + "class": "act", + "input": { + "type": "object", + "required": [ + "fingerprint", + "why" + ], + "properties": { + "fingerprint": { + "type": "string", + "description": "the build's fingerprint, as first-views' entries name it" + }, + "why": { + "type": "string", + "description": "why it is asked again, in plain words" + } + } + }, + "answer": { + "type": "object", + "required": [ + "asked", + "read" + ], + "properties": { + "asked": { + "type": "object", + "description": "the hand ask written", + "required": [ + "v", + "fingerprint", + "why", + "by", + "at" + ], + "properties": { + "v": { + "type": "integer", + "description": "the layout's version" + }, + "fingerprint": { + "type": "string", + "description": "the build's fingerprint" + }, + "why": { + "type": "string", + "description": "why it was asked again" + }, + "by": { + "type": "string", + "description": "the caller the runtime proved" + }, + "at": { + "type": "string", + "format": "date-time", + "description": "when it was asked, RFC 3339" + } + } + }, + "read": { + "type": "string", + "description": "the verb that reads the status, first-views" + } + } + } + }, { "name": "process-builds", "description": "The build each process on this node was started with (design 47 *The revision view*): per service and container the fingerprint the node-engine handed it in MESH_FINGERPRINT, or why it is unknown, and when it started. Narrowed by module. Reads only.", diff --git a/ts/seats/nodeengine/index.d.ts b/ts/seats/nodeengine/index.d.ts index 61262b3..c34cace 100644 --- a/ts/seats/nodeengine/index.d.ts +++ b/ts/seats/nodeengine/index.d.ts @@ -20,6 +20,7 @@ export declare const verbs: { readonly seatListAgain: "seat-list-again"; readonly firstViews: "first-views"; readonly firstViewAgain: "first-view-again"; + readonly entryAgain: "entry-again"; readonly processBuilds: "process-builds"; }; @@ -1021,10 +1022,32 @@ export type SeatListAgainAnswer = SeatList; export interface FirstViewsInput { } +export interface FirstViewsAnswerEntriesItem { + /** the build's fingerprint */ + "fingerprint": string; + /** since when it stands in that state, RFC 3339 */ + "since": string; + /** asked of the indexer, answered, or not found by it */ + "state": "asked" | "answered" | "not-found"; +} + +export interface FirstViewsAnswerWantsItem { + /** the fingerprint wanted */ + "fingerprint": string; + /** since when the want is kept, RFC 3339 */ + "kept-since": string; + /** the module the want is of */ + "module": string; +} + /** is what first-views answers. */ export interface FirstViewsAnswer { + /** one row per entry the node-engine keeps of a module's build by its fingerprint: when it asked the indexer's entry and where that stands (design 47 G 698) */ + "entries": FirstViewsAnswerEntriesItem[]; /** one status per owner and state followed, as mesh-sdk's views runtime keeps it */ "first-views": Record[]; + /** the wants the node-engine keeps (design 47 G 683, 698) */ + "wants": FirstViewsAnswerWantsItem[]; } /** is what first-view-again takes. */ @@ -1061,6 +1084,36 @@ export interface FirstViewAgainAnswer { "read": string; } +/** is what entry-again takes. */ +export interface EntryAgainInput { + /** the build's fingerprint, as first-views' entries name it */ + "fingerprint": string; + /** why it is asked again, in plain words */ + "why": string; +} + +/** the hand ask written */ +export interface EntryAgainAnswerAsked { + /** when it was asked, RFC 3339 */ + "at": string; + /** the caller the runtime proved */ + "by": string; + /** the build's fingerprint */ + "fingerprint": string; + /** the layout's version */ + "v": number; + /** why it was asked again */ + "why": string; +} + +/** is what entry-again answers. */ +export interface EntryAgainAnswer { + /** the hand ask written */ + "asked": EntryAgainAnswerAsked; + /** the verb that reads the status, first-views */ + "read": string; +} + /** is what process-builds takes. */ export interface ProcessBuildsInput { /** only this module's processes */ diff --git a/ts/seats/nodeengine/index.js b/ts/seats/nodeengine/index.js index ce9c2e2..2c527bf 100644 --- a/ts/seats/nodeengine/index.js +++ b/ts/seats/nodeengine/index.js @@ -20,6 +20,7 @@ export const verbs = { seatListAgain: "seat-list-again", firstViews: "first-views", firstViewAgain: "first-view-again", + entryAgain: "entry-again", processBuilds: "process-builds", };