node-engine seat: a node's windows, busy, strays and filters are lists, as the engine writes them (the contract said objects and refused every real value; ADR 0634; issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 15:19:03 +01:00
parent 8c5f85e86b
commit 8e51d93c2f
4 changed files with 37 additions and 12 deletions
+13
View File
@@ -148,3 +148,16 @@ func TestHealthAndDataCodecs(t *testing.T) {
t.Errorf("node key %q %v", k, err)
}
}
// The lists a node's facts carry (windows, busy, strays, filters) are arrays on the bus, as the engine has always written
// them; the contract once said objects and refused every real value.
func TestHaveNodeReadsTheListsTheEngineWrites(t *testing.T) {
raw := []byte(`{"v":1,"windows":[{"step":"backup"}],"busy":[{"id":"x"}],"strays":[{"kind":"container"}],"filters":[{"name":"f"}]}`)
n, err := DecodeHaveNode(raw)
if err != nil {
t.Fatal(err)
}
if len(n.Windows) != 1 || len(n.Busy) != 1 || len(n.Strays) != 1 || len(n.Filters) != 1 {
t.Fatalf("lists lost: %+v", n)
}
}
+4 -4
View File
@@ -1288,7 +1288,7 @@ const (
// HaveByNodeNodeValue is one value of the state have under the key <node>.node.
type HaveByNodeNodeValue struct {
Busy map[string]any `json:"busy,omitempty"`
Busy []map[string]any `json:"busy,omitempty"`
// the node's answer to each placement candidate naming it, by proposal id
Candidates map[string]CandidateAnswer `json:"candidates,omitempty"`
// the digest of the node's own composition it runs
@@ -1296,14 +1296,14 @@ type HaveByNodeNodeValue struct {
// own, or none (empty)
Composes string `json:"composes,omitempty"`
ComposesWhy string `json:"composes-why,omitempty"`
Filters map[string]any `json:"filters,omitempty"`
Filters []map[string]any `json:"filters,omitempty"`
Profile map[string]any `json:"profile,omitempty"`
Record *NodeRecord `json:"record,omitempty"`
Sealed []string `json:"sealed,omitempty"`
SealedAt map[string]string `json:"sealed-at,omitempty"`
Strays map[string]any `json:"strays,omitempty"`
Strays []map[string]any `json:"strays,omitempty"`
V int64 `json:"v"`
Windows map[string]any `json:"windows,omitempty"`
Windows []map[string]any `json:"windows,omitempty"`
}
// ComposedByNodeModuleFingerprintValue is one value of the state composed under the key <node>.<module>.<fingerprint>.
+16 -4
View File
@@ -856,16 +856,28 @@
"description": "the digest of the node's own composition it runs"
},
"windows": {
"type": "object"
"type": "array",
"items": {
"type": "object"
}
},
"busy": {
"type": "object"
"type": "array",
"items": {
"type": "object"
}
},
"strays": {
"type": "object"
"type": "array",
"items": {
"type": "object"
}
},
"filters": {
"type": "object"
"type": "array",
"items": {
"type": "object"
}
},
"profile": {
"type": "object"
+4 -4
View File
@@ -1043,7 +1043,7 @@ export interface HaveByNodeModuleValue {
/** is one value of the state have under the key <node>.node. */
export interface HaveByNodeNodeValue {
"busy"?: Record<string, unknown>;
"busy"?: Record<string, unknown>[];
/** the node's answer to each placement candidate naming it, by proposal id */
"candidates"?: Record<string, CandidateAnswer>;
/** the digest of the node's own composition it runs */
@@ -1051,14 +1051,14 @@ export interface HaveByNodeNodeValue {
/** own, or none (empty) */
"composes"?: string;
"composes-why"?: string;
"filters"?: Record<string, unknown>;
"filters"?: Record<string, unknown>[];
"profile"?: Record<string, unknown>;
"record"?: NodeRecord;
"sealed"?: string[];
"sealed-at"?: Record<string, string>;
"strays"?: Record<string, unknown>;
"strays"?: Record<string, unknown>[];
"v": number;
"windows"?: Record<string, unknown>;
"windows"?: Record<string, unknown>[];
}
/** is one value of the state composed under the key <node>.<module>.<fingerprint>. */