Add drill: an ask the operator starts at the controller's terminal, whose approval performs nothing and is recorded (hq ADR 0259)
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestTheInstallersFirstUserListIsWhatTheControllerWouldCompose (0.65s)
mesh/delivery-group group feat/the-controller-asks-the-operator rejected: a member's own check failed
mesh/delivery superseded: a newer head of the same pull request

The live acceptance needs an approval the operator can ask for at will and that changes nothing. A
drill is asked like any condition's ask, bound to its own act, claimed once on its warrant and recorded
as a warrant hand-act with who answered, through which channel and the proofs. A verb's process may not
start one, so no agent asks the operator a question they did not start.
This commit is contained in:
2026-10-09 11:07:13 +02:00
parent 8312f5bdee
commit 1e361a17a9
4 changed files with 183 additions and 5 deletions
+11 -5
View File
@@ -84,6 +84,9 @@ type asked struct {
// Acted is what the controller did on the warrant: empty before it did anything, "acting" while it acts,
// then "done", "failed: …" or "nothing: …". Anything but empty is never acted on again.
Acted string `json:"acted,omitempty"`
// Drill is an ask started at the controller's terminal (drill.go): about no condition, its answers
// perform nothing, and the reconciling of conditions leaves it alone.
Drill bool `json:"drill,omitempty"`
}
// askedStore keeps the asks (broker.AskedBucket).
@@ -202,7 +205,7 @@ func (a *asker) reconcile(ctx context.Context) error {
}
byCondition := map[string]asked{}
for _, r := range all {
if r.State == askOpen {
if r.State == askOpen && !r.Drill {
if prior, held := byCondition[r.Condition]; !held || r.Opened.After(prior.Opened) {
byCondition[r.Condition] = r
}
@@ -226,7 +229,7 @@ func (a *asker) reconcile(ctx context.Context) error {
}
byCondition = map[string]asked{}
for _, r := range all {
if r.State == askOpen {
if r.State == askOpen && !r.Drill {
byCondition[r.Condition] = r
}
}
@@ -490,7 +493,7 @@ func (a *asker) Decided(ctx context.Context, body []byte) error {
if err != nil {
return err
}
stillOpen := false
stillOpen := r.Drill // a drill is about no condition
for _, c := range open {
stillOpen = stillOpen || c.Key == r.Condition
}
@@ -521,9 +524,12 @@ func (a *asker) Decided(ctx context.Context, body []byte) error {
args["why"] = why
}
var acted error
if act.Arguments["silence"] != "" {
switch {
case r.Drill && act.Verb == drillVerb:
// A drill's answer performs nothing: it is recorded below as the operator's decision.
case act.Arguments["silence"] != "":
acted = a.silence(ctx, act.Arguments["silence"], conditions.MaxSilence, byWords(w), why)
} else {
default:
acted = a.call(ctx, act, args)
}
r.Ended = a.now()
+110
View File
@@ -0,0 +1,110 @@
package main
// The drill of the operator's answers (novox/hq ADR 0259, the live acceptance after rollout): an ask the
// operator starts at the controller's terminal, answered on the phone, whose approval changes nothing and is
// recorded as a person's decision like any other.
//
// mesh-controller drill [--for 15m]
//
// It asks with two answers — Approve (an approval, so only a channel that proves who answered carries it) and
// Decline (an acknowledgement) — each bound to the drill's own act. The serving controller acts on the warrant
// as on any other: it claims the ask once, checks the act is the one bound, performs nothing, and records the
// hand-act `warrant` with who answered, through which channel, and the proofs. `hand-acts` then shows it.
//
// **The terminal's alone**: a command a verb runs (MESH_VERB set) is refused, so no agent starts a drill — a
// drill is a question the operator expects, and one an agent could start would teach them to approve what they
// did not ask for.
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"time"
"github.com/nats-io/nats.go"
"git.novox.be/novox/mesh-sdk/go/asks"
"github.com/novox/mesh-controller/internal/conditions"
)
// drillVerb is the act a drill's answers bind: nothing is called.
const drillVerb = "drill"
// drillActions are the drill's two answers.
func drillActions() []conditions.Action {
return []conditions.Action{
{Label: "Approve", Verb: drillVerb, Level: conditions.LevelApprove, Arguments: map[string]string{"drill": "approve"}},
{Label: "Decline", Verb: drillVerb, Level: conditions.LevelAcknowledge, Arguments: map[string]string{"drill": "decline"}},
}
}
// drillAsk is the drill's ask, as the router is sent it.
func drillAsk(id string, now time.Time, lasts time.Duration) (asks.Ask, map[string]int) {
q := asks.Ask{ID: id, Headline: "Drill: approve this test question?", Who: asks.Operator,
Explanation: "Needs you: approve or decline. You started this drill at the controller's terminal. Approving " +
"changes nothing on the mesh; it is recorded as your decision, so you can check the record.",
OnExpiry: "nothing is done", Expires: now.Add(lasts), About: "drill." + id}
options := map[string]int{}
for i, act := range drillActions() {
binds, _ := asks.ActDigest(boundAct(act))
oid := optionID(act.Label)
options[oid] = i
q.Options = append(q.Options, asks.Option{ID: oid, Label: act.Label, Does: doesDrill(act), Level: asks.Level(act.Level),
Binds: binds})
}
return q, options
}
func doesDrill(act conditions.Action) string {
if act.Arguments["drill"] == "approve" {
return "nothing changes; your approval is recorded"
}
return "nothing changes; your answer is recorded"
}
func drillCommand(ctx context.Context, args []string) error {
if os.Getenv(verbVar) != "" {
return errors.New("drill is the controller's terminal's alone: a verb may not start one, so no agent asks " +
"the operator a question they did not start (novox/hq ADR 0259)")
}
set := flag.NewFlagSet("drill", flag.ContinueOnError)
lasts := set.Duration("for", 15*time.Minute, "how long the question waits for an answer")
if err := set.Parse(args); err != nil {
return err
}
if *lasts < time.Minute || *lasts > askApproveFor {
return fmt.Errorf("a drill waits between a minute and %s", askApproveFor)
}
js, err := aBus()
if err != nil {
return err
}
defer js.Close()
now := time.Now()
id := newAskID()
q, options := drillAsk(id, now, *lasts)
if err := q.Check(now); err != nil {
return err
}
store := busAsked{conn: js.Conn()}
// Kept before it is published, as the asker keeps every ask, so a warrant always finds it.
if err := store.Put(ctx, asked{ID: id, Condition: q.About, Ask: q, Actions: drillActions(), Options: options,
State: askOpen, Opened: now, Drill: true}); err != nil {
return fmt.Errorf("the drill could not be kept in the controller's asks: %w", err)
}
body, err := json.Marshal(q)
if err != nil {
return err
}
if _, err := js.Context().Publish(asks.AskSubject(askerName), body, nats.MsgId("ask."+id), nats.Context(ctx)); err != nil {
return fmt.Errorf("the drill could not be asked: %w", err)
}
fmt.Printf("drill %s asked: answer it on your phone before %s. Then `mesh-controller hand-acts` shows the "+
"answer as a warrant, with who answered, through which channel and the proofs; nothing else changes.\n",
id, q.Expires.Local().Format("15:04"))
return nil
}
+60
View File
@@ -0,0 +1,60 @@
package main
import (
"context"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/asks"
)
// A drill (the live acceptance of novox/hq ADR 0259): its approval is a warrant like any other — claimed once,
// its act checked against what the option bound, recorded as the operator's decision with who, how and the
// proofs — and it performs nothing. The reconciling of conditions leaves it open.
func TestADrillsApprovalIsRecordedAndPerformsNothing(t *testing.T) {
r := newAskerRig(t)
q, options := drillAsk("cdrill", r.now, askerDrillFor)
if err := q.Check(r.now); err != nil {
t.Fatalf("the drill's ask is refused: %v", err)
}
r.store["cdrill"] = asked{ID: "cdrill", Condition: q.About, Ask: q, Actions: drillActions(), Options: options,
State: askOpen, Opened: r.now, Drill: true}
if err := r.a.reconcile(context.Background()); err != nil {
t.Fatal(err)
}
if got := r.store["cdrill"]; got.State != askOpen {
t.Fatalf("the reconciling of conditions ended the drill: %+v", got)
}
approve, _ := q.Option("approve")
w := asks.Warrant{Ask: "cdrill", Asker: "mesh-controller", Outcome: asks.OutcomeChosen, Option: approve.ID,
Label: approve.Label, Level: approve.Level, Channel: "telegram", Proofs: []string{"P1"}, At: r.now,
AskDigest: q.Digest(), By: &asks.Person{Who: asks.Operator, Kind: "telegram", Identity: "42", Verified: "user id verified"}}
answerWith(t, r, w)
answerWith(t, r, w) // heard again
if len(r.called)+len(r.silenced) != 0 {
t.Errorf("a drill performed something: %v %v", r.called, r.silenced)
}
if len(r.acts) != 1 {
t.Fatalf("hand-acts %+v", r.acts)
}
act := r.acts[0]
if act.Verb != handActWarrant || act.By != "the operator, as telegram identity 42" || act.Ask != "cdrill" ||
strings.Join(act.Args, " ") != "drill drill=approve" || act.Outcome != "done" || strings.Join(act.Proofs, ",") != "P1" {
t.Errorf("the drill's record: %+v", act)
}
if !personsDecision(act) {
t.Error("a drill's answer counts as a repair")
}
}
// Only the terminal starts a drill: a verb's process is refused before anything is asked.
func TestADrillIsTheTerminalsAlone(t *testing.T) {
t.Setenv(verbVar, "mesh-controller.command")
if err := drillCommand(context.Background(), nil); err == nil || !strings.Contains(err.Error(), "terminal") {
t.Fatalf("a verb started a drill: %v", err)
}
}
// askerDrillFor is how long the test's drill waits.
const askerDrillFor = 15 * time.Minute
+2
View File
@@ -78,6 +78,8 @@ func run() error {
return rotateCommand(ctx, args[1:])
case "ask":
return askCommand(ctx, args[1:])
case "drill":
return drillCommand(ctx, args[1:])
case "builds":
return buildsCommand(ctx, args[1:])
// The build queue, controlled by hand (novox/hq ADR 0219).