diff --git a/go/seats/nodeengine/contract.go b/go/seats/nodeengine/contract.go index 79347cb..825e95e 100644 --- a/go/seats/nodeengine/contract.go +++ b/go/seats/nodeengine/contract.go @@ -121,6 +121,7 @@ const ( StatePackages = "packages" StateHealth = "health" StateFindings = "findings" + StateBounds = "bounds" ) // HaveByNodeModuleKey is the key of one value of the state have under the form ".". @@ -393,6 +394,42 @@ func DecodeFindingsByKeyValue(raw []byte) (FindingsByKeyValue, error) { return v, nil } +// BoundsByWhatIDKey is the key of one value of the state bounds under the form ".". +func BoundsByWhatIDKey(what, id string) string { return what + "." + id } + +// ParseBoundsByWhatIDKey reads the parts of a key of the form ".", in the order of the form; the last result is false for a key of another form. +func ParseBoundsByWhatIDKey(key string) (string, string, bool) { + rest := key + v0, after0, found0 := strings.Cut(rest, ".") + if !found0 || v0 == "" { + return "", "", false + } + rest = after0 + if rest == "" { + return "", "", false + } + v2 := rest + rest = "" + if rest != "" { + return "", "", false + } + return v0, v2, true +} + +// DecodeBoundsByWhatIDValue reads one value of the state bounds under the form ".", refusing a field BoundsByWhatIDValue does not know and any text after the value. +func DecodeBoundsByWhatIDValue(raw []byte) (BoundsByWhatIDValue, error) { + var v BoundsByWhatIDValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + // AccountsHanded is the hashes of the users of the list, sealed to the vault. type AccountsHanded struct { // when @@ -1684,3 +1721,12 @@ const ( FindingsByKeyValueSeverityUrgent = "urgent" FindingsByKeyValueSeverityWarning = "warning" ) + +// BoundsByWhatIDValue is one value of the state bounds under the key .. +type BoundsByWhatIDValue struct { + Module string `json:"module,omitempty"` + // when the wait began, RFC 3339 + Since string `json:"since"` + // layout version, 1 + V int64 `json:"v"` +} diff --git a/go/seats/nodeengine/contract_test.go b/go/seats/nodeengine/contract_test.go index b3285b5..be4495f 100644 --- a/go/seats/nodeengine/contract_test.go +++ b/go/seats/nodeengine/contract_test.go @@ -93,6 +93,8 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v HealthByNodeValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "a have that waits for the recovery module, with its wait, ask-at and act (design 47 G 560, 629)", subject: "", given: "{\"detail\":\"waiting for a restore point\",\"fp\":\"0123456789ab\",\"health\":\"waits\",\"phase\":\"held\",\"v\":1,\"wait\":{\"act\":\"a-20261111t100000\",\"ask-at\":\"2026-11-11T10:00:00Z\",\"bound\":\"90m\",\"clock-from\":\"2026-11-11T10:00:00Z\",\"on\":\"the recovery module's ended\",\"owner\":\"node-engine\",\"reason\":\"recovery wait\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"compose\",\"words\":\"waiting for the point of postgres's data\"}}", wire: "{\"detail\":\"waiting for a restore point\",\"fp\":\"0123456789ab\",\"health\":\"waits\",\"phase\":\"held\",\"v\":1,\"wait\":{\"act\":\"a-20261111t100000\",\"ask-at\":\"2026-11-11T10:00:00Z\",\"bound\":\"90m\",\"clock-from\":\"2026-11-11T10:00:00Z\",\"on\":\"the recovery module's ended\",\"owner\":\"node-engine\",\"reason\":\"recovery wait\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"compose\",\"words\":\"waiting for the point of postgres's data\"}}", decode: func(b []byte) (any, error) { var v HaveByNodeModuleValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "a bound of a wait on the recovery module's restore point", subject: "", given: "{\"module\":\"mesh-delivery-assigner\",\"since\":\"2026-11-11T14:00:00Z\",\"v\":1}", wire: "{\"module\":\"mesh-delivery-assigner\",\"since\":\"2026-11-11T14:00:00Z\",\"v\":1}", + decode: func(b []byte) (any, error) { var v BoundsByWhatIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "manifest-reads answers what the engine reads and when it stated it", subject: "", given: "{\"manifest-reads\":[\"form:seat-reference\",\"word:series\"],\"node\":\"ace\",\"stated-at\":\"2026-11-04T10:00:00Z\",\"v\":1}", wire: "{\"manifest-reads\":[\"form:seat-reference\",\"word:series\"],\"node\":\"ace\",\"stated-at\":\"2026-11-04T10:00:00Z\",\"v\":1}", decode: func(b []byte) (any, error) { var v ManifestReadsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "manifest-reads of an engine built before it said them is the baseline", subject: "", given: "{\"node\":\"g14\",\"stated-at\":\"2026-11-04T10:05:00Z\",\"v\":1}", wire: "{\"node\":\"g14\",\"stated-at\":\"2026-11-04T10:05:00Z\",\"v\":1}", diff --git a/seats/node-engine/fixtures/states.json b/seats/node-engine/fixtures/states.json index aa0ae83..db64100 100644 --- a/seats/node-engine/fixtures/states.json +++ b/seats/node-engine/fixtures/states.json @@ -538,5 +538,14 @@ }, "health": "waits" } + }, + { + "name": "a bound of a wait on the recovery module's restore point", + "of": "state:bounds/.", + "given": { + "v": 1, + "since": "2026-11-11T14:00:00Z", + "module": "mesh-delivery-assigner" + } } ] diff --git a/seats/node-engine/seat.json b/seats/node-engine/seat.json index 19a8dd5..3225f06 100644 --- a/seats/node-engine/seat.json +++ b/seats/node-engine/seat.json @@ -1596,6 +1596,39 @@ } ], "description": "the findings this node's engine raises about itself, one key per finding, in the layout of the seat mesh-conditions (its findings state): a key written raises the condition of the same key, a key deleted clears it (novox/hq ADR 0564 §2, ADR 0636 §1); today `machine..seat-list-unread`" + }, + { + "name": "bounds", + "template": "state", + "writer": "holder", + "history": 1, + "key-forms": [ + { + "key": ".", + "value": { + "type": "object", + "description": "one bound a wait of the node-engine runs against, kept with its own time to live: its expiry is heard as the bus's delete marker and ends the wait (design 47 G §560, A-77)", + "properties": { + "v": { + "type": "integer", + "description": "layout version, 1" + }, + "since": { + "type": "string", + "description": "when the wait began, RFC 3339" + }, + "module": { + "type": "string" + } + }, + "required": [ + "v", + "since" + ] + } + } + ], + "description": "the bounds of the node-engine's waits, each a key . with its own time to live (design 47 G A-92)" } ], "defs": { diff --git a/ts/seats/nodeengine/index.d.ts b/ts/seats/nodeengine/index.d.ts index c34cace..8eeb44e 100644 --- a/ts/seats/nodeengine/index.d.ts +++ b/ts/seats/nodeengine/index.d.ts @@ -74,6 +74,7 @@ export declare const states: { readonly packages: "packages"; readonly health: "health"; readonly findings: "findings"; + readonly bounds: "bounds"; }; /** The key of one value of the state have under the form ".". */ @@ -124,6 +125,12 @@ export declare function findingsByKeyKey(key: string): string; /** The parts of a key of the form "", or undefined for a key of another form. */ export declare function parseFindingsByKeyKey(_key: string): { key: string } | undefined; +/** The key of one value of the state bounds under the form ".". */ +export declare function boundsByWhatIDKey(what: string, id: string): string; + +/** The parts of a key of the form ".", or undefined for a key of another form. */ +export declare function parseBoundsByWhatIDKey(_key: string): { what: string; id: string } | undefined; + /** the hashes of the users of the list, sealed to the vault */ export interface AccountsHanded { /** when */ @@ -1297,3 +1304,12 @@ export interface FindingsByKeyValue { "v": number; } +/** is one value of the state bounds under the key .. */ +export interface BoundsByWhatIDValue { + "module"?: string; + /** when the wait began, RFC 3339 */ + "since": string; + /** layout version, 1 */ + "v": number; +} + diff --git a/ts/seats/nodeengine/index.js b/ts/seats/nodeengine/index.js index 2c527bf..1ce11bb 100644 --- a/ts/seats/nodeengine/index.js +++ b/ts/seats/nodeengine/index.js @@ -90,6 +90,7 @@ export const states = { packages: "packages", health: "health", findings: "findings", + bounds: "bounds", }; /** The key of one value of the state have under the form ".". */ @@ -230,3 +231,22 @@ export function parseFindingsByKeyKey(_key) { return { key }; } +/** The key of one value of the state bounds under the form ".". */ +export function boundsByWhatIDKey(what, id) { + return what + "." + id; +} + +/** The parts of a key of the form ".", or undefined for a key of another form. */ +export function parseBoundsByWhatIDKey(_key) { + let _rest = _key; + const _at0 = _rest.indexOf("."); + if (_at0 <= 0) return undefined; + const what = _rest.slice(0, _at0); + _rest = _rest.slice(_at0 + 1); + if (_rest === "") return undefined; + const id = _rest; + _rest = ""; + if (_rest !== "") return undefined; + return { what, id }; +} +