From fa512c8637a638792df998a24aa67b5a1d0bdb9a Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 28 Oct 2026 09:43:40 +0100 Subject: [PATCH 1/4] =?UTF-8?q?The=20node=20seat=20node-engine:=20held=20b?= =?UTF-8?q?y=20every=20node's=20engine,=20its=20six=20verbs=20and=20three?= =?UTF-8?q?=20events,=20and=20no=20module=20may=20claim=20it=20(issue=2077?= =?UTF-8?q?4,=20ADR=200563=20=C2=A71)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go/catalogue/engine_seat.go | 57 +++++++++++++++++++++++++++++++++++++ go/catalogue/seats.go | 16 ++++++++++- go/catalogue/seats_test.go | 24 ++++++++++++++-- 3 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 go/catalogue/engine_seat.go diff --git a/go/catalogue/engine_seat.go b/go/catalogue/engine_seat.go new file mode 100644 index 0000000..71a8afd --- /dev/null +++ b/go/catalogue/engine_seat.go @@ -0,0 +1,57 @@ +package catalogue + +// The node seat node-engine (novox/hq ADR 0563 §1): held on every node by that node's engine (mesh-host), which serves +// its verbs on its own bus account. No module claims it and no assignment holds it: what the mesh knows of its holder +// is the node's record and the engine's haves (ADR 0563 §1 changes ADR 0110 §6 and §7 for this one seat). +// +// Its verbs carry no signature (§7): the runtime proves the caller, which matters only for the record. The read-only +// ones change nothing or are bounded in the engine's own state (§2); hand-over and access-act perform nothing on the +// call, and act only on the operator's warrant to the engine's own ask (§3). + +// EngineSeat is the node seat every node's engine holds. +const EngineSeat = "node-engine" + +// EngineVerbs are the node-engine seat's verbs, in the order ADR 0563 §1 names them. +func EngineVerbs() []Verb { + return []Verb{ + {Name: "hand-over", Description: "Hand a directory this machine uses as found to the mesh (novox/hq ADR 0563 §3, " + + "§5): the engine judges the path as it does every hand-over (absolute, as it states it, used as found, " + + "reached through no link) and refuses in words, or asks the operator on their channel at the level approve, " + + "binding the owner, group and mode it will set. Nothing changes on the call: only the operator's yes hands " + + "it over. One ask is open per path; acts shows where it stands.", + Input: schema(map[string]string{"path": "the directory's absolute path, as the engine states it", + "why": "why it is asked, in words the operator reads"}, []string{"path"})}, + {Name: "access-act", Description: "Ask the operator for the take act on a library whose root holds data outside its " + + "group (novox/hq ADR 0328 §7, ADR 0563 §3, §4), on the library's home engine: the access as declared and its " + + "root as last declared, judged and refused in words or asked at the level approve. Only the operator's yes " + + "sets the library's group, group write and setgid under its root. One ask is open per access.", + Input: schema(map[string]string{"act": "the act: take", "access": "the access, library:", + "why": "why it is asked, in words the operator reads"}, []string{"act", "access"})}, + {Name: "judge-directories", Description: "Which of this machine's declared directories the engine would refuse to " + + "make or keep, one line each (novox/hq ADR 0563 §2). Read-only: changes no owner or mode.", + Input: schema(map[string]string{}, nil)}, + {Name: "setuid-search", Description: "Throw away the engine's last search for setuid programs and start a full one " + + "(novox/hq ADR 0563 §2); until it completes root-free answers that the machine is not judged yet. Refused while " + + "one runs, and when one was started within the hour, naming when the next may start.", + Input: schema(map[string]string{"why": "why it is asked, in words"}, nil)}, + {Name: "root-free", Description: "Whether this machine is root-free (novox/hq ADR 0259 §8, ADR 0563 §6): free only " + + "on the engine's own verdict, at most fifteen minutes old, that the account agents run as cannot become root " + + "without a person, and the login shell's execute not served here. Anything else is no pass, and says why. " + + "Answered by this machine's engine alone.", + Input: schema(map[string]string{}, nil)}, + {Name: "acts", Description: "Every act this machine's engine was asked through this seat (novox/hq ADR 0563 §9): " + + "each hand-over, access act and setuid search, open, done or refused, with who asked, the ask and the " + + "operator's warrant. Read-only.", + Input: schema(map[string]string{"limit": "at most this many, newest"}, nil)}, + } +} + +// EngineEvents are what the seat says of an act, each naming the act, the path or access, the caller as the runtime +// proved it and the ask (ADR 0563 §1). +var EngineEvents = []string{"asked", "done", "refused"} + +// engineSeat is the seat's row in the closed set. +func engineSeat() Seat { + return Seat{Name: EngineSeat, Scope: ScopeNode, Serves: EngineVerbs(), Emits: append([]string(nil), EngineEvents...), + HeldByTheEngine: true, Decision: "novox/hq ADR 0563"} +} diff --git a/go/catalogue/seats.go b/go/catalogue/seats.go index 0417420..4b00a05 100644 --- a/go/catalogue/seats.go +++ b/go/catalogue/seats.go @@ -67,6 +67,10 @@ type Seat struct { // names paths. Compiled, never stored, like Receives: adding or taking away a path is a decision, // recorded, and the store's rows carry no column for it. Paths []SeatPath + // HeldByTheEngine says the seat is held on every node by that node's engine, which serves its verbs on its own bus + // account (novox/hq ADR 0563 §1): no module claims it and no assignment holds it. Compiled, never stored, like + // Receives. + HeldByTheEngine bool // Decision is the record that made it a seat. Decision string } @@ -445,6 +449,8 @@ var defaultSeats = append([]Seat{ // A node's files, read and managed (novox/hq ADR 0394, files.go): beside node-reads until mesh-files is assigned // on every node, then node-reads goes (ADR 0394 §9). {Name: FilesSeat, Scope: ScopeNode, Decision: "novox/hq ADR 0394", Serves: filesVerbs()}, + // The node's engine's own verbs and asks (novox/hq ADR 0563 §1): held by the engine on every node, by no module. + engineSeat(), }, // The graphical session's roles (novox/hq ADR 0208), last because they are a workstation's. graphicalSessionSeats()...) @@ -518,7 +524,8 @@ func UseSeats(s []Seat) { row.Paths = d.Paths row.Replicated = d.Replicated row.PulledByKind = d.PulledByKind - row.Yields = d.Yields // never stored (ADR 0553) + row.Yields = d.Yields // never stored (ADR 0553) + row.HeldByTheEngine = d.HeldByTheEngine // never stored (ADR 0563) row.Serves = optionalAsCompiled(row.Serves, d.Serves) } merged = append(merged, row) @@ -631,6 +638,13 @@ func claimProblems(m Manifest) []string { } for _, c := range m.Claims { + if s, known := SeatNamed(c.Name); known && s.HeldByTheEngine { + // The node's engine holds it on every node, and no module may (novox/hq ADR 0563 §1): a module answering + // root-free or a hand-over would be an agent answering it. + problems = append(problems, fmt.Sprintf("%s claims %s, which every node's engine holds itself and no "+ + "module may (novox/hq ADR 0563 §1)", m.Module, c.Name)) + continue + } if _, known := SeatNamed(c.Name); known { // **A seat's scope and what it delivers are not judged here** (novox/hq ADR 0122). // This function runs wherever a manifest is parsed, and one of those places is the diff --git a/go/catalogue/seats_test.go b/go/catalogue/seats_test.go index 137cc98..4812062 100644 --- a/go/catalogue/seats_test.go +++ b/go/catalogue/seats_test.go @@ -58,6 +58,24 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) { if strings.Join(checkerVerbs, ",") != "try,try-read,try-end" { t.Errorf("the checker seat serves %v", checkerVerbs) } + // The node seat node-engine is the node's engine's (novox/hq ADR 0563 §1): at node scope, delivering nothing, its six + // verbs and three events, held by no assignment, and a module's claim of it refused. + if engine, ok := SeatNamed(EngineSeat); !ok || engine.Scope != ScopeNode || engine.Delivers != "" || !engine.HeldByTheEngine { + t.Errorf("node-engine is %+v, not a node seat delivering nothing that the engine holds", engine) + } else { + var verbs []string + for _, v := range engine.Serves { + verbs = append(verbs, v.Name) + } + if strings.Join(verbs, ",") != "hand-over,access-act,judge-directories,setuid-search,root-free,acts" || + strings.Join(engine.Emits, ",") != "asked,done,refused" { + t.Errorf("node-engine serves %v and emits %v", verbs, engine.Emits) + } + } + if _, err := ParseManifest([]byte(`{"module":"thing","version":"1","claims":[{"name":"node-engine","scope":"node"}]}`)); err == nil || + !strings.Contains(err.Error(), "node-engine") { + t.Errorf("a module's claim of node-engine was not refused: %v", err) + } // Forty-six with the three build seats replacing node-build-agent and the retired mesh-build-machine (novox/hq // ADR 0525 §2, §10); forty-five with node-files (novox/hq ADR 0394); forty-four again once node-reads goes. // Forty-four with node-provisioner (novox/hq ADR 0349 clause 3); forty-three with mesh-provisioner (novox/hq ADR 0349 clause 3); forty-two with mesh-config (novox/hq ADR 0348 §9); forty-one with node-reads (novox/hq ADR 0310); forty with node-nfs-server and node-mounts (novox/hq ADR 0263); thirty-eight with node-resolver (novox/hq ADR 0247); thirty-seven with mesh-delivery (novox/hq ADR 0239); thirty-six since node-resolver-config retired @@ -71,9 +89,9 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) { // node-container-runtime (ADR 0207); nineteen with node-environment and node-login-shell (ADR 0203, // ADR 0204); seventeen with node-build-agent (ADR 0190). One fewer once the retired // mesh-build-machine row goes, when no registered manifest claims it. - // Forty-seven with mesh-delivery-checker (novox/hq ADR 0547 §10, issue 774). - if len(Seats()) != 47 { - t.Errorf("the mesh defines %d seats rather than 47; the set is closed, so a change here is "+ + // Forty-seven with mesh-delivery-checker (novox/hq ADR 0547 §10, issue 774); forty-eight with node-engine (novox/hq ADR 0563 §1). + if len(Seats()) != 48 { + t.Errorf("the mesh defines %d seats rather than 48; the set is closed, so a change here is "+ "a decision (novox/hq ADR 0110): %s", len(Seats()), seatNames()) } } From bec931e72e1665203d6d7da00c13c430883ce8dd Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 28 Oct 2026 09:45:48 +0100 Subject: [PATCH 2/4] =?UTF-8?q?A=20node's=20engine=20answers=20its=20own?= =?UTF-8?q?=20node's=20node-engine=20verbs,=20says=20the=20seat's=20events?= =?UTF-8?q?=20and=20asks=20the=20operator=20as=20node-engine-,=20and?= =?UTF-8?q?=20no=20other=20account=20answers=20them=20(issue=20774,=20ADR?= =?UTF-8?q?=200563=20=C2=A71,=20=C2=A73,=20=C2=A76)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go/asks/asks.go | 5 +++ go/buscompose/buscompose_test.go | 63 +++++++++++++++++++++++++++++++- go/buscompose/engine.go | 30 +++++++++++++++ go/buscompose/nats.go | 4 ++ go/buscompose/users.go | 2 +- 5 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 go/buscompose/engine.go diff --git a/go/asks/asks.go b/go/asks/asks.go index 596bb8f..480f212 100644 --- a/go/asks/asks.go +++ b/go/asks/asks.go @@ -571,3 +571,8 @@ type ProofAnswer struct { Accepted bool `json:"accepted"` Words string `json:"words"` } + +// EngineAsker is the asker name a node's engine asks the operator under (novox/hq ADR 0563 §3, which changes ADR 0259 +// §2 to admit a node's engine as an asker): `node-engine-`, the last token of the by-caller subjects granted to that +// node's engine account alone, so no engine acts on another node's warrant. +func EngineAsker(node string) string { return "node-engine-" + node } diff --git a/go/buscompose/buscompose_test.go b/go/buscompose/buscompose_test.go index 2489938..6e89be1 100644 --- a/go/buscompose/buscompose_test.go +++ b/go/buscompose/buscompose_test.go @@ -54,6 +54,18 @@ func mayBeyondTheControllers(user string) map[string]bool { VaultSecretChanged: true} } +// engineBeyond is a node's engine holding its node seat and asking the operator in its own name (novox/hq ADR 0563 §1, +// §3), beyond the controller's on purpose: its own node's verbs, events and discovery, the bus's discovery asked, and +// the router's seat traffic under node-engine-. +func engineBeyond(user, g string) bool { + node, ok := strings.CutPrefix(user, "node.") + if !ok { + return false + } + return strings.HasPrefix(g, "mesh.seat."+EngineSeat+".") && strings.Contains(g, "."+node) || + strings.HasPrefix(g, "$SRV.") || strings.Contains(g, ".node-engine-"+node) +} + // createsAnAccountsConsumer is the grants part's create and read of a durable consumer an account needs, by its stream // and name (novox/hq ADR 0561 §6): beyond the controller's on purpose, since the controller made them alone. func createsAnAccountsConsumer(user, g string) bool { @@ -113,7 +125,7 @@ func grantsAsTheControllers(t *testing.T, got, want string) { } for g := range now { if !was[g] && !intended[g] && !mayBeyondTheControllers(user)[g] && !createdAdvisory.MatchString(`"`+g+`", `) && - !createsAnAccountsConsumer(user, g) { + !createsAnAccountsConsumer(user, g) && !engineBeyond(user, g) { t.Errorf("%s: the copy grants %s beyond the controller's, and no decision names it", user, g) } } @@ -218,6 +230,55 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) { t.Error("a user of a seat not pulled by kind reads DEAD_LETTERS") } }) + // ADR 0563 §1, §3, §6: each node's engine alone answers its own node's node-engine verbs and says its events, any + // runtime may call them, and it asks the operator under node-engine- alone. + t.Run("a node's engine holds its node seat and asks in its own name", func(t *testing.T) { + router := Seat{Name: AsksSeat, Accepts: []string{"ask", "cancel"}, Emits: []string{"decided"}, + ByCaller: []string{"ask", "cancel", "decided"}, Records: []string{"messenger_asks"}} + users, err := Users(Records{Nodes: []string{"one", "two"}, Assigned: map[string][]Declared{ + "one": {{Module: "messenger", Holds: []Seat{router}, Invokes: []string{"seat:" + EngineSeat + ".root-free"}}}, + "two": {{Module: RuntimeModule}, {Module: "shop", Invokes: []string{"*"}}}}}) + if err != nil { + t.Fatal(err) + } + for _, u := range users { + perms, err := PermissionsFor(u) + if err != nil { + t.Fatal(err) + } + answers := MaySubscribe(perms, "mesh.seat."+EngineSeat+".tool.root-free.one") || + MaySubscribe(perms, "mesh.seat."+EngineSeat+".call.root-free.one.messenger~one") + if engineOne := u.Kind == KindNode && u.Node == "one"; answers != engineOne { + t.Errorf("%s answers one's root-free: %v", u.Username(), answers) + } + switch { + case u.Kind == KindNode && u.Node == "one": + for subject, want := range map[string]bool{ + "mesh.seat.operator-channel.accept.ask.node-engine-one": true, + "mesh.seat.operator-channel.accept.cancel.node-engine-one": true, + "mesh.seat.operator-channel.accept.ask.node-engine-two": false, + "$JS.API.DIRECT.GET.KV_messenger_asks.$KV.messenger_asks.node-engine-one.hand-over-1": true, + "$JS.API.DIRECT.GET.KV_messenger_asks.$KV.messenger_asks.node-engine-two.hand-over-1": false, + "mesh.seat." + EngineSeat + ".event.done.one": true, + "mesh.seat." + EngineSeat + ".event.done.two": false, + "$SRV.INFO": true, + } { + if got := MayPublish(perms, subject); got != want { + t.Errorf("node one may publish %s: %v, want %v", subject, got, want) + } + } + if !MaySubscribe(perms, "mesh.seat.operator-channel.event.decided.node-engine-one") || + MaySubscribe(perms, "mesh.seat.operator-channel.event.decided.node-engine-two") || + !MaySubscribe(perms, "$SRV.INFO."+EngineSeat) || MaySubscribe(perms, "mesh.seat."+EngineSeat+".tool.hand-over.two") { + t.Errorf("node one hears %v", perms.Subscribe) + } + case u.Kind == KindNodeTools || u.Module == "messenger": + if !MayPublish(perms, "mesh.seat."+EngineSeat+".tool.root-free.one") { + t.Errorf("%s cannot call one's root-free", u.Username()) + } + } + } + }) // Issue 1126 (ADR 0550 §5): a node's engine asks the vault's `own` in its own name while the vault serves it, and no // other node's. t.Run("a node asks the vault its own secrets", func(t *testing.T) { diff --git a/go/buscompose/engine.go b/go/buscompose/engine.go new file mode 100644 index 0000000..95fa03d --- /dev/null +++ b/go/buscompose/engine.go @@ -0,0 +1,30 @@ +package buscompose + +import ( + "git.novox.be/novox/mesh-sdk/go/asks" + "git.novox.be/novox/mesh-sdk/go/catalogue" +) + +// The node seat node-engine (novox/hq ADR 0563 §1): every node's engine holds it on its own bus account, and no module +// claims it. Its verbs and events are the catalogue's (catalogue.EngineVerbs), read here rather than copied. + +// EngineSeat is the seat. +const EngineSeat = catalogue.EngineSeat + +// engineGrants is what a node's engine is granted to hold its node seat and to ask the operator (ADR 0563 §1, §3): it +// answers its own node's verbs (the caller-named subjects are added beside them with every principal's), says the +// seat's events about its node, answers discovery for the seat, asks who answers what (the login shell's execute, ADR +// 0563 §6), and asks the operator under asks.EngineAsker(node) on the router's seat as its holder declares it, as any +// user of that seat is granted. +func engineGrants(p Principal) (pub, sub []string) { + for _, v := range catalogue.EngineVerbs() { + sub = append(sub, "mesh.seat."+EngineSeat+".tool."+v.Name+"."+p.Node) + } + for _, e := range catalogue.EngineEvents { + pub = append(pub, "mesh.seat."+EngineSeat+".event."+e+"."+p.Node) + } + sub = append(sub, announcing(EngineSeat)...) + pub = append(pub, discovering()...) + tp, ts := seatTrafficGrants(SeatTrafficOf(asks.EngineAsker(p.Node), nil, p.Uses, nil)) + return append(pub, tp...), append(sub, ts...) +} diff --git a/go/buscompose/nats.go b/go/buscompose/nats.go index 682c37e..b32d35f 100644 --- a/go/buscompose/nats.go +++ b/go/buscompose/nats.go @@ -953,6 +953,10 @@ func PermissionsFor(p Principal) (Permissions, error) { // on it rather than at the next unrelated change (ADR 0547 §2). sub = append(sub, VaultSecretChanged) } + // And its node seat, node-engine, and its asks to the operator in its own name (novox/hq ADR 0563 §1, §3). + ep, es := engineGrants(p) + pub = append(pub, ep...) + sub = append(sub, es...) // And, on the node holding mesh-broker, the grants part's sealed user list it follows from take-over (novox/hq ADR // 0547 clause 7): one key, and an object by its name. if p.FollowsGrantsUserList { diff --git a/go/buscompose/users.go b/go/buscompose/users.go index 15cbd14..20a0988 100644 --- a/go/buscompose/users.go +++ b/go/buscompose/users.go @@ -165,7 +165,7 @@ func Users(r Records) ([]Principal, error) { } out = append(out, Principal{Kind: KindNode, Node: node, WitnessesController: witness, Checks: checks, MovesIntoTheVault: servesMoveIn(r), StatesItsKeyToTheVault: servesVerb(r, VaultNodeKey), - AsksItsOwnSecrets: servesVerb(r, VaultOwn), + AsksItsOwnSecrets: servesVerb(r, VaultOwn), Uses: asksSeatOf(r), FollowsGrantsUserList: bus, ServedReads: servedOn(served, node)}) // **And the machine's provisioner client, on an account of its own** (novox/hq ADR 0349 clause 11): one // per machine that runs it, delivered to the module running it. From 49e853986bae75d98578dc9c578d202371865fa4 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 28 Oct 2026 09:58:27 +0100 Subject: [PATCH 3/4] =?UTF-8?q?A=20module's=20trusted=20list=20of=20caller?= =?UTF-8?q?s=20in=20the=20SDK,=20so=20the=20vault's=20secret-ask=20answers?= =?UTF-8?q?=20the=20callers=20its=20manifest=20names=20as=20the=20runtime?= =?UTF-8?q?=20proved=20them=20(issue=20774,=20ADR=200563=20=C2=A78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go/caller_test.go | 15 +++++++++++++++ go/callers.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 go/callers.go diff --git a/go/caller_test.go b/go/caller_test.go index 7fae75c..8f87cb1 100644 --- a/go/caller_test.go +++ b/go/caller_test.go @@ -75,6 +75,21 @@ func TestACallerInTheArgumentsIsNeverTheCaller(t *testing.T) { if got != "shanks.node-tools" { t.Fatalf("with a caller in the arguments the tool read %q, want the runtime's", got) } + // A module's trusted list of callers (issue 774, ADR 0563 §8) grants the callers it names as the runtime proved them: + // a module on any machine, . exactly, person. exactly; no wildcard, never one unproven. + callers, err := ParseCallers([]byte(`{"callers":["node-tools","ace.claude-code","person.jo","*"]}`)) + if err != nil { + t.Fatal(err) + } + for caller, want := range map[string]bool{"shanks.node-tools": true, "ace.claude-code": true, "g14.claude-code": false, + "person.jo": true, "person.node-tools": false, Unproven: false, "": false, "novox.mesh-vault": false} { + if callers.Allows(caller) != want { + t.Errorf("%q allowed: %v, want %v", caller, !want, want) + } + } + if _, err := ParseCallers([]byte(`{"callers":["x"],"more":1}`)); err == nil { + t.Error("a list with a field it does not know was read") + } } // A runtime that names no caller — one from before issue 365, or a call on the subject that names none — is diff --git a/go/callers.go b/go/callers.go new file mode 100644 index 0000000..74a7ce3 --- /dev/null +++ b/go/callers.go @@ -0,0 +1,48 @@ +package stdio + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" +) + +// Callers is a module's trusted list of who may call a verb of its (novox/hq ADR 0459 §2, ADR 0563 §8), as the mesh +// writes a list setting into a file the module reads: `{"callers": [...]}`. An entry is a module's name (that module on +// any machine), exactly ., or exactly person.. Each caller is the runtime's proof (CallerOf), never +// an argument; nobody beyond the list is granted, a wildcard is no entry, and an unproven caller never matches. +type Callers []string + +// ParseCallers reads a callers file: the list under `callers`, and nothing else. +func ParseCallers(raw []byte) (Callers, error) { + var f struct { + Callers Callers `json:"callers"` + } + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&f); err != nil { + return nil, fmt.Errorf("the callers setting is not a list of callers: %v", err) + } + return f.Callers, nil +} + +// Allows says whether caller, as the runtime proved it, is one the list names. +func (c Callers) Allows(caller string) bool { + if caller == "" || caller == Unproven { + return false + } + for _, entry := range c { + if entry == "" || strings.ContainsAny(entry, "*> ") { + continue + } + if entry == caller { + return true + } + if !strings.Contains(entry, ".") { + if _, module, ok := strings.Cut(caller, "."); ok && module == entry && !strings.HasPrefix(caller, "person.") { + return true + } + } + } + return false +} From f80d5d3d864966ef1289ebdb24d7d22bdd5f1d93 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 28 Oct 2026 10:00:47 +0100 Subject: [PATCH 4/4] =?UTF-8?q?The=20vault's=20secret-ask=20is=20said=20to?= =?UTF-8?q?=20answer=20the=20callers=20its=20manifest=20names=20(issue=207?= =?UTF-8?q?74,=20ADR=200563=20=C2=A78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go/catalogue/seats.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/go/catalogue/seats.go b/go/catalogue/seats.go index 4b00a05..b5e1010 100644 --- a/go/catalogue/seats.go +++ b/go/catalogue/seats.go @@ -175,7 +175,8 @@ var defaultSeats = append([]Seat{ "origin": "issued for a bus account the controller minted for a core or moved module and hands " + "at once (ADR 0374, issue 787); left out for a value given at the terminal"}), nil)}, {Name: "secret-ask", Optional: true, Description: "Ask the operator for a module's own secret at a desk, " + - "from the controller alone: the ask is recorded in the vault's store and bounded before the prompt " + + "from the controller or a caller the vault's manifest names, named as the bus proved it (novox/hq ADR " + + "0563 §8): the ask is recorded in the vault's store and bounded before the prompt " + "opens, and what is typed is sealed to a key the vault made for the call and opens there alone.", Input: schema(vaultIntake(map[string]string{"node": "the machine the secret is for", "module": "the module", "name": "the secret's name in the module",