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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user