diff --git a/go/seats/nodeengine/contract.go b/go/seats/nodeengine/contract.go index 44e19e5..f4fb044 100644 --- a/go/seats/nodeengine/contract.go +++ b/go/seats/nodeengine/contract.go @@ -1280,6 +1280,12 @@ type CompositionAnswerWant struct { Yes string `json:"yes,omitempty"` } +// CompositionAnswerWithheldItem is a type of the contract. +type CompositionAnswerWithheldItem struct { + Subject string `json:"subject"` + Why string `json:"why"` +} + // CompositionAnswer is what composition answers. type CompositionAnswer struct { // the module's last apply that did not end running; absent when none @@ -1305,6 +1311,10 @@ type CompositionAnswer struct { Started bool `json:"started"` // the module's want as the agent holds it; absent when it holds none Want *CompositionAnswerWant `json:"want,omitempty"` + // the module's marked subjects the grants part withholds from this node's runtime, each with why, read through mesh-grants' verb withheld (design 46 approval mark 6) + Withheld []CompositionAnswerWithheldItem `json:"withheld,omitempty"` + // withheld unknown: the grants part does not answer, when its verb withheld did not answer + WithheldUnknown string `json:"withheld-unknown,omitempty"` } // ComposeInput is what compose takes. diff --git a/go/seats/nodeengine/contract_test.go b/go/seats/nodeengine/contract_test.go index 441832c..497fc5f 100644 --- a/go/seats/nodeengine/contract_test.go +++ b/go/seats/nodeengine/contract_test.go @@ -123,6 +123,10 @@ func TestFixtures(t *testing.T) { 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}, + {name: "composition names what the grants part withholds of the module", subject: "", given: "{\"composition\":{\"key\":\"\"},\"judged\":{\"judgement\":\"\",\"waits_for_yes\":false,\"words\":\"\"},\"module\":\"zsh\",\"node\":\"workstation\",\"started\":false,\"withheld\":[{\"subject\":\"mesh.mod.zsh.tool.execute\",\"why\":\"its tool runner does not state approval-mark/1\"}]}", wire: "{\"composition\":{\"key\":\"\"},\"judged\":{\"judgement\":\"\",\"waits_for_yes\":false,\"words\":\"\"},\"module\":\"zsh\",\"node\":\"workstation\",\"started\":false,\"withheld\":[{\"subject\":\"mesh.mod.zsh.tool.execute\",\"why\":\"its tool runner does not state approval-mark/1\"}]}", + decode: func(b []byte) (any, error) { var v CompositionAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "composition says withheld unknown when the grants part does not answer", subject: "", given: "{\"composition\":{\"key\":\"\"},\"judged\":{\"judgement\":\"\",\"waits_for_yes\":false,\"words\":\"\"},\"module\":\"zsh\",\"node\":\"workstation\",\"started\":false,\"withheld-unknown\":\"withheld unknown: the grants part does not answer\"}", wire: "{\"composition\":{\"key\":\"\"},\"judged\":{\"judgement\":\"\",\"waits_for_yes\":false,\"words\":\"\"},\"module\":\"zsh\",\"node\":\"workstation\",\"started\":false,\"withheld-unknown\":\"withheld unknown: the grants part does not answer\"}", + decode: func(b []byte) (any, error) { var v CompositionAnswer; 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 2e3cc3f..28acd5a 100644 --- a/seats/node-engine/fixtures/verbs.json +++ b/seats/node-engine/fixtures/verbs.json @@ -218,5 +218,46 @@ }, "read": "first-views" } + }, + { + "name": "composition names what the grants part withholds of the module", + "of": "verb:composition.answer", + "given": { + "node": "workstation", + "module": "zsh", + "started": false, + "composition": { + "key": "" + }, + "judged": { + "judgement": "", + "waits_for_yes": false, + "words": "" + }, + "withheld": [ + { + "subject": "mesh.mod.zsh.tool.execute", + "why": "its tool runner does not state approval-mark/1" + } + ] + } + }, + { + "name": "composition says withheld unknown when the grants part does not answer", + "of": "verb:composition.answer", + "given": { + "node": "workstation", + "module": "zsh", + "started": false, + "composition": { + "key": "" + }, + "judged": { + "judgement": "", + "waits_for_yes": false, + "words": "" + }, + "withheld-unknown": "withheld unknown: the grants part does not answer" + } } ] diff --git a/seats/node-engine/seat.json b/seats/node-engine/seat.json index 6ff2c9e..37bc985 100644 --- a/seats/node-engine/seat.json +++ b/seats/node-engine/seat.json @@ -494,6 +494,29 @@ "provisioner_account" ], "description": "of the node's own composition, its own fields beside what runs names" + }, + "withheld": { + "type": "array", + "description": "the module's marked subjects the grants part withholds from this node's runtime, each with why, read through mesh-grants' verb withheld (design 46 approval mark 6)", + "items": { + "type": "object", + "properties": { + "subject": { + "type": "string" + }, + "why": { + "type": "string" + } + }, + "required": [ + "subject", + "why" + ] + } + }, + "withheld-unknown": { + "type": "string", + "description": "withheld unknown: the grants part does not answer, when its verb withheld did not answer" } }, "required": [ diff --git a/ts/seats/nodeengine/index.d.ts b/ts/seats/nodeengine/index.d.ts index 7051477..1d9f9c9 100644 --- a/ts/seats/nodeengine/index.d.ts +++ b/ts/seats/nodeengine/index.d.ts @@ -913,6 +913,11 @@ export interface CompositionAnswerWant { "yes"?: string; } +export interface CompositionAnswerWithheldItem { + "subject": string; + "why": string; +} + /** is what composition answers. */ export interface CompositionAnswer { /** the module's last apply that did not end running; absent when none */ @@ -938,6 +943,10 @@ export interface CompositionAnswer { "started": boolean; /** the module's want as the agent holds it; absent when it holds none */ "want"?: CompositionAnswerWant; + /** the module's marked subjects the grants part withholds from this node's runtime, each with why, read through mesh-grants' verb withheld (design 46 approval mark 6) */ + "withheld"?: CompositionAnswerWithheldItem[]; + /** withheld unknown: the grants part does not answer, when its verb withheld did not answer */ + "withheld-unknown"?: string; } /** is what compose takes. */