package main // The rehearsal of the operator's answers โ€” not a drill, which in the glossary is something broken on purpose (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 rehearse [--for 15m] // // It asks with two answers, Approve and Decline, each bound to the rehearsal's own act and **both at the level // approve** (the review of 2026-10-09, M1: an acknowledgement never shares an ask with an approval), so only a // channel that proves who answered carries either โ€” the rehearsal is of exactly that. 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** (startedAtTheTerminal): a command a verb runs, an ordinary mesh-cli line and anything the // serving controller started are refused, so no agent starts a rehearsal โ€” a // rehearsal 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" "time" "github.com/nats-io/nats.go" "git.novox.be/novox/mesh-sdk/go/asks" "github.com/novox/mesh-controller/internal/conditions" ) // rehearsalVerb is the act a rehearsal's answers bind: nothing is called. const rehearsalVerb = "rehearsal" // rehearsalActions are the rehearsal's two answers. func rehearsalActions() []conditions.Action { return []conditions.Action{ {Label: "Approve", Verb: rehearsalVerb, Level: conditions.LevelApprove, Arguments: map[string]string{"rehearsal": "approve"}}, {Label: "Decline", Verb: rehearsalVerb, Level: conditions.LevelApprove, Arguments: map[string]string{"rehearsal": "decline"}}, } } // rehearsalAsk is the rehearsal's ask, as the router is sent it. func rehearsalAsk(id string, now time.Time, lasts time.Duration) (asks.Ask, map[string]int) { q := asks.Ask{ID: id, Headline: "Rehearsal: approve this test question?", Who: asks.Operator, Explanation: "Needs you: approve or decline. You started this rehearsal 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: "rehearsal." + id} options := map[string]int{} for i, act := range rehearsalActions() { 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: doesRehearsal(act), Level: asks.Level(act.Level), Binds: binds}) } return q, options } func doesRehearsal(act conditions.Action) string { if act.Arguments["rehearsal"] == "approve" { return "nothing changes; your approval is recorded" } return "nothing changes; your answer is recorded" } func rehearseCommand(ctx context.Context, args []string) error { // The terminal as main judges it (startedAtTheTerminal): not a verb, not the serving controller or anything it // started, and a mesh-cli line only when it is the control-node's operator's (novox/hq ADR 0272 ยง4). if !startedAtTheTerminal() { return errors.New("rehearse is the controller's terminal's alone: a verb, a mesh-cli line from anybody but " + "the control-node's operator, or a process the serving controller started may not start one, so no agent " + "asks the operator a question they did not start (novox/hq ADR 0259)") } set := flag.NewFlagSet("rehearse", 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 rehearsal 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 := rehearsalAsk(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.Create(ctx, asked{ID: id, Condition: q.About, Ask: q, Actions: rehearsalActions(), Options: options, State: askOpen, Opened: now, Rehearsal: true}); err != nil { return fmt.Errorf("the rehearsal 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 rehearsal could not be asked: %w", err) } fmt.Printf("rehearsal %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 }