Area 8 (join 11): CanHold refuses a holder of a marked verb without its key-ttl approvals state, or not in Go

approvals is skipped from a holder's verbs only where the seat has a marked verb.

Builds: design 47 G A-266, A-270, §677

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 11:03:55 +01:00
parent 22ded4011f
commit 0b5ea03aa4
3 changed files with 112 additions and 3 deletions
+43 -1
View File
@@ -1,6 +1,12 @@
package catalogue
import "testing"
import (
"encoding/json"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/seatdef"
)
// The login shell's `execute` runs any command as the operator account, which can become root without a
// person, so the operator withheld it on the control-node until a call to it needs a person's approval
@@ -34,3 +40,39 @@ func TestTheLoginShellsExecuteIsOptionalSoAMachineMayWithholdIt(t *testing.T) {
}
t.Run("compiled", check)
}
// Design 47 G §677, A-266, A-270: a process that serves a marked verb holds the gate only in Go and with a state `approvals`
// declared with key-ttl; no holder names the verb approvals where a verb is marked, and a seat with none leaves it to its holder.
func TestAHolderOfAMarkedVerbDeclaresItsApprovalsStateWithKeyTTL(t *testing.T) {
seat, ok := SeatNamed(LoginShellSeat)
if !ok {
t.Fatal("node-login-shell is not defined")
}
holder := func(language string, state ...BusStateEntry) Manifest {
return Manifest{Module: "zsh", Version: "1", Claims: []Claim{{Name: LoginShellSeat, Scope: ScopeNode, Serves: []string{"execute"}}},
Resources: []map[string]any{{"id": "p", "type": "process", "artifact": "program", "serves": []any{"seat:" + LoginShellSeat + ".execute"}}},
Build: &Build{Artifacts: []Artifact{{Name: "program", Language: language}}}, BusState: state}
}
keyed := BusStateEntry{Name: "approvals", Storage: StorageKeyValue, Template: TemplateState,
Options: map[string]json.RawMessage{OptionLifetimePerKey: json.RawMessage("true")}}
plain := BusStateEntry{Name: "approvals", Storage: StorageKeyValue, Template: TemplateState}
for name, c := range map[string]struct {
m Manifest
refused string
}{
"go with key-ttl": {holder("go", keyed), ""},
"go without key-ttl": {holder("go", plain), "key-ttl"},
"go with no state": {holder("go"), "key-ttl"},
"typescript with key-ttl": {holder("typescript", keyed), "only Go"},
} {
err := CanHold(c.m, seat)
if (c.refused == "") != (err == nil) || err != nil && !strings.Contains(err.Error(), c.refused) {
t.Errorf("%s: %v, want refusal containing %q", name, err, c.refused)
}
}
// A seat with no marked verb: a holder serving no verb named approvals is still refused for it.
open := Seat{Name: "s", Scope: ScopeNode, Serves: []Verb{{Name: "a"}, {Name: seatdef.ApprovalsVerb}}}
if got := unservedVerbs([]string{"a"}, open.Serves); len(got) != 1 || got[0] != "approvals" {
t.Errorf("a seat with no marked verb: approvals unserved is %v, want it named", got)
}
}
+63
View File
@@ -2,6 +2,7 @@ package catalogue
import (
"fmt"
"slices"
"strings"
"sync/atomic"
)
@@ -201,6 +202,9 @@ func CanHold(m Manifest, seat Seat) error {
"serves or among its own tools",
m.Module, seat.Name, strings.Join(missing, ", "))
}
if err := approvalsStateRefusal(m, seat); err != nil {
return err
}
// And nothing the seat does not promise: a verb named here that the protocol lacks is served
// to nobody, which is a typo the holder would otherwise discover as a caller's timeout.
if extra := unpromised(claimed.Serves, seat.Serves); len(extra) > 0 {
@@ -211,6 +215,65 @@ func CanHold(m Manifest, seat Seat) error {
return nil
}
// approvalsStateRefusal is why a process of m that serves a marked verb of seat cannot hold the gate, or nothing (design 47 G
// §677, A-270): only Go holds the gate, and its template state `approvals` (a `requires` entry) is declared with `key-ttl`,
// since its bound keys `unanswered.<call>` need it. A marked verb served by the tool runner's bundle needs neither.
func approvalsStateRefusal(m Manifest, seat Seat) error {
var marked []string
for _, v := range seat.Serves {
if v.NeedsApproval {
marked = append(marked, "seat:"+seat.Name+"."+v.Name)
}
}
if len(marked) == 0 {
return nil
}
for id, serves := range m.ProcessServes() {
var verbs []string
for _, s := range serves {
if slices.Contains(marked, s) {
verbs = append(verbs, s)
}
}
if len(verbs) == 0 {
continue
}
if language := processLanguage(m, id); language != "" && !compilesToABinary(language) {
return fmt.Errorf("%s serves %s from its process %s written in %s: only Go holds the gate of a marked verb "+
"(design 47 G §677)", m.Module, strings.Join(verbs, ", "), id, language)
}
kept := false
for _, e := range m.BusState {
if e.Name == "approvals" && e.Template == TemplateState && string(e.Options[OptionLifetimePerKey]) == "true" {
kept = true
}
}
if !kept {
return fmt.Errorf("%s serves %s from its process %s but declares no state `approvals` with the option key-ttl "+
"(design 47 G §677, A-270): a holder of a marked verb keeps its calls' bound keys there", m.Module, strings.Join(verbs, ", "), id)
}
}
return nil
}
// processLanguage is the language of the artifact the process resource id runs, or empty where the manifest names none.
func processLanguage(m Manifest, id string) string {
if m.Build == nil {
return ""
}
for _, r := range m.Resources {
if fmt.Sprint(r["type"]) != "process" || fmt.Sprint(r["id"]) != id {
continue
}
for _, a := range m.Build.Artifacts {
if a.Name == fmt.Sprint(r["artifact"]) {
return a.Language
}
}
}
return ""
}
func providesAt(m Manifest, provision, scope string) bool {
for _, o := range m.Provides {
if o.Name == provision && o.At() == scope {
+6 -2
View File
@@ -140,11 +140,15 @@ func unservedVerbs(tools []string, promised []Verb) []string {
for _, t := range tools {
has[t] = true
}
marked := false
for _, v := range promised {
marked = marked || v.NeedsApproval
}
var missing []string
for _, v := range promised {
// The verb approvals is the gate's, served wherever a marked verb is (the seat server or the tool runner), so no
// holder names it (design 47 G lead answer A-266).
if !has[v.Name] && !v.Optional && v.Name != seatdef.ApprovalsVerb {
// holder names it there (design 47 G lead answer A-266); a seat with no marked verb leaves it to its holder.
if !has[v.Name] && !v.Optional && !(marked && v.Name == seatdef.ApprovalsVerb) {
missing = append(missing, v.Name)
}
}