Stop serving the vault's move-asks, which only the controller called

move-asks copied the controller's record of asks into the vault's once;
the controller is not placed (ADR 0590) and nothing else calls it. move-in
stays: the node-engine calls it. The drift test names the node's own
composition now that the controller composes for none (issue 1126).

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 12:29:56 +01:00
parent a73b02ea30
commit f8e4c1b0ae
4 changed files with 2 additions and 174 deletions
-91
View File
@@ -33,7 +33,6 @@ import (
"errors"
"fmt"
"slices"
"strconv"
"strings"
"time"
@@ -259,96 +258,6 @@ func (v *Vault) MoveTake(caller, call, sealed, readBackTo string) (MovedIn, erro
// ---- move-asks ----------------------------------------------------------------------------------------------
// MovedAsk is one row of the controller's record of asks at a desk (its secret_ask table): no value.
type MovedAsk struct {
ID int64 `json:"id"`
Node string `json:"node"`
Module string `json:"module"`
Name string `json:"name"`
Desk string `json:"desk"`
AskedBy string `json:"asked_by"`
OpenedAt time.Time `json:"opened_at"`
EndedAt *time.Time `json:"ended_at,omitempty"`
Outcome string `json:"outcome,omitempty"`
}
// AsksMoved is what a copy of the record of asks did.
type AsksMoved struct {
Copied int `json:"copied"`
Already int `json:"already"`
}
// MovedAsksAtOnce bounds one call: the controller sends its record in parts.
const MovedAsksAtOnce = 500
// movedAskID is the vault's id of a row of the controller's record.
func movedAskID(id int64) string { return "controller-" + strconv.FormatInt(id, 10) }
// MoveAsks copies rows of the controller's record of asks into the vault's, as they are: each ask's opening and,
// when it ended, its end — once, so a copy run again adds nothing. From the controller alone, whose record it is.
// Every row is judged before any is written.
func (v *Vault) MoveAsks(caller string, asks []MovedAsk) (AsksMoved, error) {
if err := fromTheController(caller, "move-asks"); err != nil {
return AsksMoved{}, err
}
if len(asks) > MovedAsksAtOnce {
return AsksMoved{}, fmt.Errorf("%d asks at once is more than %d; send them in parts", len(asks), MovedAsksAtOnce)
}
type lines struct{ opened, ended AskRecord }
var all []lines
for _, a := range asks {
secret, err := OwnSecret(a.Node, a.Module, a.Name)
if err != nil {
return AsksMoved{}, fmt.Errorf("ask %d: %w; nothing was copied", a.ID, err)
}
if !namePart.MatchString(a.Desk) || a.OpenedAt.IsZero() || a.ID <= 0 {
return AsksMoved{}, fmt.Errorf("ask %d names no desk, no time it opened or no id; nothing was copied", a.ID)
}
l := lines{opened: AskRecord{ID: movedAskID(a.ID), At: a.OpenedAt.UTC(), Event: AskOpened, Secret: secret,
Node: a.Node, Module: a.Module, Name: a.Name, Desk: a.Desk, AskedBy: askedByName(a.AskedBy),
Caller: ControllerCaller}}
if a.EndedAt != nil {
if a.Outcome != "given" && !askOutcomes[a.Outcome] {
return AsksMoved{}, fmt.Errorf("ask %d ended %q, which is no outcome the record knows; nothing was "+
"copied", a.ID, a.Outcome)
}
l.ended = AskRecord{ID: movedAskID(a.ID), At: a.EndedAt.UTC(), Event: AskEnded, Secret: secret,
Outcome: a.Outcome}
}
all = append(all, l)
}
v.Store.asksMu.Lock()
defer v.Store.asksMu.Unlock()
held, err := v.Store.asks()
if err != nil {
return AsksMoved{}, fmt.Errorf("the record of asks cannot be read: %w; nothing was copied", err)
}
seen := map[string]bool{}
for _, r := range held {
if r.Event == AskOpened {
seen[r.ID] = true
}
}
out := AsksMoved{}
for _, l := range all {
if seen[l.opened.ID] {
out.Already++
continue
}
if err := v.recordAsk(l.opened); err != nil {
return out, fmt.Errorf("ask %s could not be copied, after %d: %w", l.opened.ID, out.Copied, err)
}
if l.ended.ID != "" {
if err := v.recordAsk(l.ended); err != nil {
return out, fmt.Errorf("the end of ask %s could not be copied, after %d: %w", l.opened.ID, out.Copied, err)
}
}
seen[l.opened.ID] = true
out.Copied++
}
return out, nil
}
// StateNodeKey records the sealing key the node-engine of caller's machine states in its own name (novox/hq ADR 0372,
// issue 786), at its start and whenever the key changes, whether or not it ever moved a secret in. The machine is the
// caller's, as the bus proved it (`node.<machine>`), never an argument. A first key is handled like a changed one: the
+2 -60
View File
@@ -239,57 +239,6 @@ func TestAMoveTakesNothingItCannotOpenOrReadBack(t *testing.T) {
}
}
// The controller's record of asks is copied as it is, once: each ask's opening and its end, who asked and at which
// desk, and the bounds then count them. From the controller alone.
func TestTheRecordOfAsksIsCopiedAsItIsOnce(t *testing.T) {
v := newVault(t, nil)
now := v.Now()
ended := now.Add(-30 * time.Second)
asks := []MovedAsk{
{ID: 41, Node: "anchor", Module: "telegram", Name: "telegram-token", Desk: "laptop", AskedBy: "g14/claude-code",
OpenedAt: now.Add(-50 * time.Minute), EndedAt: &ended, Outcome: "given"},
{ID: 42, Node: "anchor", Module: "telegram", Name: "telegram-token", Desk: "laptop", AskedBy: "g14/claude-code",
OpenedAt: now.Add(-40 * time.Minute), EndedAt: &ended, Outcome: "dismissed"},
{ID: 43, Node: "anchor", Module: "telegram", Name: "telegram-token", Desk: "laptop", AskedBy: "ace/agent",
OpenedAt: now.Add(-20 * time.Minute)},
}
if _, err := v.MoveAsks("node.anchor", asks); err == nil {
t.Error("a node-engine copied the record of asks")
}
copied, err := v.MoveAsks(ControllerCaller, asks)
if err != nil || copied.Copied != 3 || copied.Already != 0 {
t.Fatalf("copied %+v, %v", copied, err)
}
again, err := v.MoveAsks(ControllerCaller, asks)
if err != nil || again.Copied != 0 || again.Already != 3 {
t.Fatalf("copied again %+v, %v", again, err)
}
all, _ := v.Store.Asks(100)
if len(all) != 5 {
t.Fatalf("%d lines: %+v", len(all), all)
}
opened := map[string]AskRecord{}
for _, r := range all {
if r.Event == AskOpened {
opened[r.ID] = r
}
}
if r := opened["controller-41"]; r.Secret != "own/anchor/telegram/telegram-token" || r.AskedBy != "g14/claude-code" ||
r.Desk != "laptop" || !r.At.Equal(asks[0].OpenedAt) || r.Caller != ControllerCaller {
t.Errorf("ask 41: %+v", r)
}
// Three asks in the hour, from the controller's record: the vault's bound holds them.
if _, err := v.AskOpen(ControllerCaller, telegramAsk("g14/claude-code")); err == nil ||
!strings.Contains(err.Error(), "in the last hour") {
t.Errorf("an ask after three copied in the hour: %v", err)
}
bad := []MovedAsk{{ID: 44, Node: "anchor", Module: "telegram", Name: "x", Desk: "laptop", AskedBy: "a",
OpenedAt: now, EndedAt: &ended, Outcome: "whatever"}}
if _, err := v.MoveAsks(ControllerCaller, bad); err == nil {
t.Error("an ask that ended in no outcome the record knows was copied")
}
}
// The two steps as the runtime calls the tool: the caller is the one it proved, never an argument.
func TestMoveInThroughTheTool(t *testing.T) {
v := newVault(t, nil)
@@ -317,13 +266,6 @@ func TestMoveInThroughTheTool(t *testing.T) {
if rec.MadeAt.Year() != 2025 || rec.MovedFrom == nil || rec.MovedFrom.Generation != "g-1" {
t.Errorf("kept %+v", rec)
}
asks := toolNamed(t, definitions(&Env{Vault: v.Vault}), Seat+".move-asks")
moved, err := asks.runAs("controller", map[string]any{"asks": []any{map[string]any{"id": 7, "node": "anchor",
"module": "telegram", "name": "telegram-token", "desk": "laptop", "asked_by": "g14/claude-code",
"opened_at": "2026-10-01T10:00:00Z", "ended_at": "2026-10-01T10:00:20Z", "outcome": "given"}}})
if err != nil || moved.(AsksMoved).Copied != 1 {
t.Fatalf("asks through the tool: %+v, %v", moved, err)
}
}
// Issue 1126, cause C4: a machine's node-engine hands the vault what runs of its own machine's own secret, where the
@@ -445,7 +387,7 @@ func TestANodeEngineHandsTheVaultWhatRunsOnItsOwnMachine(t *testing.T) {
Module: "postgres", Why: "the resource differs", Bodies: []delivery.ResourceBodies{{ID: "postgres.needs-superuser-password",
Composed: `{"sealed":"` + delivery.MaskedAnotherValue + `"}`, Running: `{"sealed":"` + delivery.MaskedAnotherValue + `"}`}}}}}
have, err := delivery.EncodeHaveNode(delivery.HaveNode{Candidates: map[string]delivery.CandidateAnswer{"pro": answer},
Composes: delivery.ComposesController})
Composes: delivery.ComposesOwn})
if err != nil {
t.Fatal(err)
}
@@ -457,7 +399,7 @@ func TestANodeEngineHandsTheVaultWhatRunsOnItsOwnMachine(t *testing.T) {
}
if len(drift.Secrets) != 1 || drift.Secrets[0].Secret != "own/anchor/postgres/superuser-password" ||
drift.Secrets[0].Generation != 2 || drift.Secrets[0].Previous != 1 || drift.Secrets[0].Takeable ||
len(drift.Nodes) != 1 || drift.Nodes[0].ValuesDiffer["pro"] != 1 || drift.Nodes[0].Composes != delivery.ComposesController {
len(drift.Nodes) != 1 || drift.Nodes[0].ValuesDiffer["pro"] != 1 || drift.Nodes[0].Composes != delivery.ComposesOwn {
t.Errorf("own_drift: %+v", drift)
}
}
@@ -350,28 +350,6 @@ func definitions(env *Env) []toolDef {
}
return v.OwnOfNode(caller, asked...)
}},
{Name: Seat + ".move-asks",
Description: "Copy rows of the controller's record of asks at a desk into the vault's, as they are and once " +
"(design 57's V3): from the controller alone, at most 500 at a time. No value is in any of them.",
Input: map[string]any{"type": "object", "properties": map[string]any{
"asks": map[string]any{"type": "array", "description": "the controller's rows: id, node, module, name, " +
"desk, asked_by, opened_at, and ended_at and outcome when the ask ended",
"items": map[string]any{"type": "object"}}}},
run: func(caller string, a map[string]any) (any, error) {
v, err := env.ready()
if err != nil {
return nil, err
}
raw, err := json.Marshal(a["asks"])
if err != nil {
return nil, err
}
var asks []MovedAsk
if err := json.Unmarshal(raw, &asks); err != nil {
return nil, fmt.Errorf("asks is not the controller's rows: %w", err)
}
return v.MoveAsks(caller, asks)
}},
{Name: "secret_asks",
Description: "The record of asks for a module's own secret at a desk, newest first: who asked as the bus named " +
"them, for what, at which desk, when, and how each ended. Never a value.",
-1
View File
@@ -204,7 +204,6 @@
"rotate",
"move-in",
"node-key",
"move-asks",
"own",
"issue-account",
"sign",