messenger: approve only on the controller's recent word that root is free, and close the review's gaps (hq ADR 0259)

The review of 2026-10-09: the router read the absence of an agent-root condition as a pass, and its minute
hung on a break-glass file, taking the work it already held. An approval now rests on the controller's
verb root-free, asked live for the router's machine and the channel's, failing closed on every other
answer, and a stale word on the kinds promises no verified sender. Break-glass runs in the minute, says
on every channel what root did, and ends the router's own open questions. An ask offering an approval is
answered only where it could be approved; a message says who asks, what each answer does and what expiry
does. The router's own asks bind the account they are about; a link is checked against one per kind as it
is made; an unlinked account does not come back by a code within a day; a fresh link is unlinked from the
desk; no link begins while the desk cannot show its code, and a code is marked secret on every path. Links
are bounded per account in the router's state; a warrant's message id cannot be foreseen. Moves to
mesh-sdk 76902998 (canonical digests).
This commit is contained in:
2026-10-09 17:08:17 +02:00
parent f30089c288
commit ff79defc2b
13 changed files with 1111 additions and 146 deletions
+182 -26
View File
@@ -93,6 +93,9 @@ type AskStore interface {
DeleteLink(kind string) error
GetUnlinked(kind string) (*Unlinked, error)
PutUnlinked(kind string, u Unlinked) error
// GetLinksAsked and PutLinksAsked keep when links were asked, by account and by kind (allowLink).
GetLinksAsked(key string) ([]time.Time, error)
PutLinksAsked(key string, times []time.Time) error
}
type askEntry struct {
@@ -161,7 +164,8 @@ func (h *Holder) openAsk(r *Record) *AskRecord {
}
// carriers are the kinds that can carry an answer of a level now: ready, offering a choice, and for
// approve verifying their sender with an account of the operator's linked.
// approve verifying their sender with an account of the operator's linked, where the controller says now
// that no agent can become root (rootFree, asked live).
func (h *Holder) carriers(level asks.Level) []string {
if h.Bench == nil {
return nil
@@ -176,7 +180,7 @@ func (h *Holder) carriers(level asks.Level) []string {
case asks.Acknowledge:
out = append(out, k.Kind)
case asks.Approve:
if h.Bench.Promises(k.Kind, "verified-sender") && h.linked(k.Kind) && h.rootFreeFor(k) == "" {
if h.Bench.Promises(k.Kind, "verified-sender") && h.linked(k.Kind) && h.rootFree(k, false) == "" {
out = append(out, k.Kind)
}
}
@@ -185,32 +189,155 @@ func (h *Holder) carriers(level asks.Level) []string {
return out
}
// rootFreeFor is why a kind's proof is not believed now, or "": an agent can become root without a person
// where the router or the kind's holder runs, which the controller's self-check says as an open condition
// (novox/hq ADR 0259 §8, rule 2).
func (h *Holder) rootFreeFor(k KindInfo) string {
// RootVerdict is the controller's word on one machine (its verb root-free, novox/hq ADR 0259 §8): free only
// when it measured, recently, that no agent can become root there without a person.
type RootVerdict struct {
Machine string `json:"machine"`
Free bool `json:"free"`
Why string `json:"why"`
Judged string `json:"judged,omitempty"`
}
// RootFreeVerb is the controller's verb the router asks, as this module, which machines are root-free.
const RootFreeVerb = "seat:" + ControllerSeat + ".root-free"
// RootFreeWithin is how long the router waits for the controller's word; RootFreeKept how long a word is
// used to render a message's buttons. An answer is always judged on a word asked then.
const (
RootFreeWithin = 10 * time.Second
RootFreeKept = 30 * time.Second
)
// askRootFree asks the controller about machines, failing on no answer in time.
func (h *Holder) askRootFree(machines []string) (map[string]RootVerdict, error) {
if h.RootFree != nil {
return h.RootFree(machines)
}
if h.Ask == nil {
return nil, errors.New("this router cannot ask the controller")
}
type answer struct {
raw json.RawMessage
err error
}
done := make(chan answer, 1)
go func() {
raw, err := h.Ask(RootFreeVerb, map[string]any{"machines": machines})
done <- answer{raw, err}
}()
var a answer
select {
case a = <-done:
case <-time.After(RootFreeWithin):
return nil, fmt.Errorf("the controller did not answer within %s", RootFreeWithin)
}
if a.err != nil {
return nil, a.err
}
var body struct {
Machines []RootVerdict `json:"machines"`
}
if err := json.Unmarshal(a.raw, &body); err != nil {
return nil, fmt.Errorf("the controller's answer could not be read: %v", err)
}
out := map[string]RootVerdict{}
for _, v := range body.Machines {
out[v.Machine] = v
}
return out, nil
}
// rootFree is why a kind's proof is not believed now, or "" when the controller says, now, that no agent can
// become root without a person on the router's machine and on the kind's (novox/hq ADR 0259 §8, rule 2, as
// the review of 2026-10-09 asked: a positive, recent pass, never the absence of a condition). Anything else
// fails: a router that does not know its machine, a kind whose machine is not said, a controller that does
// not answer, a machine missing from its answer, a word that is not free. cached allows a word up to
// RootFreeKept old, for rendering; an answer is always judged with cached false.
func (h *Holder) rootFree(k KindInfo, cached bool) string {
if h.Node == "" {
return "this router does not know which machine it runs on, so it cannot ask whether an agent can become root there"
}
if k.Node == "" {
return "the mesh has not said which machine " + Display(k.Kind) + " runs on"
}
machines := []string{h.Node}
if k.Node != h.Node {
machines = append(machines, k.Node)
}
now := h.Now()
key := strings.Join(machines, ",")
h.mu.Lock()
defer h.mu.Unlock()
for _, r := range h.open {
if r.Kind != KindAgentRoot || !r.Cleared.IsZero() {
continue
kept, ok := h.rootWord[key]
h.mu.Unlock()
var verdicts map[string]RootVerdict
if cached && ok && now.Sub(kept.at) < RootFreeKept && now.Sub(kept.at) >= 0 {
verdicts = kept.verdicts
} else {
got, err := h.askRootFree(machines)
if err != nil {
return "whether an agent can become root where it runs could not be asked: " + err.Error()
}
machine := strings.TrimPrefix(strings.TrimSuffix(r.Key, "."+KindAgentRoot), "machine.")
if machine == k.Node || (h.Node != "" && machine == h.Node) {
return "a program working for you can become root on " + machine + " without asking you, so an answer " +
"proven there proves nothing until that changes"
verdicts = got
h.mu.Lock()
if h.rootWord == nil {
h.rootWord = map[string]rootWord{}
}
h.rootWord[key] = rootWord{at: now, verdicts: got}
h.mu.Unlock()
}
for _, m := range machines {
v, said := verdicts[m]
switch {
case !said:
return "the controller said nothing of whether an agent can become root on " + m
case !v.Free:
why := v.Why
if why == "" {
why = "it is not measured that it cannot"
}
return "a program working for you may become root on " + m + " without asking you (" + why +
"), so an answer proven there proves nothing until that changes"
}
}
return ""
}
// rootWord is the controller's last word on a set of machines, and when it was asked.
type rootWord struct {
at time.Time
verdicts map[string]RootVerdict
}
// approveHolds is why an answer on a kind could not approve now, or "": the kind verifies its sender, an
// account is linked there, and the controller says no agent can become root where it runs.
func (h *Holder) approveHolds(kind string, cached bool) string {
info, held := h.Bench.Kind(kind)
switch {
case !held:
return "this channel is not one the mesh holds for answers"
case !h.Bench.Promises(kind, "verified-sender"):
return Display(kind) + " cannot prove who answered"
case !h.linked(kind):
return "no " + Display(kind) + " account is linked as you"
}
return h.rootFree(info, cached)
}
// answerable is whether an option of an ask is answered on a kind, by the ask's highest level: an ask that
// offers an approval is answered only where it could be approved, whichever of its options is chosen, so an
// acknowledgement from where approval is not proven never ends a question that waits for one (the review of
// 2026-10-09).
func answerLevel(a asks.Ask) asks.Level {
if a.Highest().Rank() > asks.Acknowledge.Rank() {
return a.Highest()
}
return asks.Acknowledge
}
// ConditionOwner is the module whose asks may be shown on a condition's message: the controller, which
// raises the conditions.
const ConditionOwner = "mesh-controller"
// KindAgentRoot is the controller's condition while an agent can become root without a person on a machine.
const KindAgentRoot = "agent-root"
// TakeAsk takes one ask or cancel from the seat's queue. asker is the subject's last token, which the bus
// grants that module alone. An error is returned only when the ask could not be recorded, so the queue
// offers it again; a refusal is said to the asker and the work is taken.
@@ -274,8 +401,10 @@ func (h *Holder) TakeAsk(verb, asker string, body json.RawMessage) error {
return nil
}
rec := AskRecord{Asker: asker, Ask: a, State: StateOpen, Opened: now, Tickets: map[string]Ticket{}}
for _, o := range a.Options {
for _, kind := range h.carriers(o.Level) {
// Every option on the kinds that carry the ask's highest level: an ask with an approval is answered only
// where it could be approved (answerLevel).
for _, kind := range h.carriers(answerLevel(a)) {
for _, o := range a.Options {
rec.Tickets[ticket()] = Ticket{Option: o.ID, Kind: kind}
}
}
@@ -400,7 +529,10 @@ func (h *Holder) tell(w asks.Warrant) error {
if h.Publish == nil {
return errors.New("this router cannot reach the bus")
}
err := h.Publish(asks.DecidedSubject(w.Asker), w, askKey(w.Asker, w.Ask)+"."+string(w.Outcome))
// A new message id each time (the review of 2026-10-09): an id anyone could foresee lets a publisher on
// the same stream take it first, and the bus would drop the warrant as a duplicate. The asker acts on its
// own record of the ask once, however many times it hears a warrant.
err := h.Publish(asks.DecidedSubject(w.Asker), w, "warrant."+ticket())
if err != nil {
h.Logf("[messenger] could not tell %s how its ask %s ended (%v); told again in a minute", w.Asker, w.Ask, err)
}
@@ -547,6 +679,16 @@ func (h *Holder) sweepAsks(now time.Time) {
// offers are an open ask's buttons on one kind: each option offered there, by its ticket.
func (h *Holder) offers(rec *AskRecord, kind string) []Choice {
var out []Choice
if answerLevel(rec.Ask) != asks.Acknowledge && rec.Asker != SelfAsker {
// Rendered only while approval holds there now, by the controller's recent word.
if h.approveHolds(kind, true) != "" {
return nil
}
} else if rec.Asker == SelfAsker && answerLevel(rec.Ask) != asks.Acknowledge {
if info, held := h.Bench.Kind(kind); !held || h.rootFree(info, true) != "" {
return nil
}
}
for _, o := range rec.Ask.Options {
for t, tk := range rec.Tickets {
if tk.Kind == kind && tk.Option == o.ID && !tk.Used {
@@ -657,23 +799,36 @@ func (h *Holder) judge(kind string, c asks.Choice, key string) (string, error) {
}
person.Verified = "user id verified"
} else {
if rec.Only != nil && rec.Only.Kind != kind {
return h.refuseChoice(kind, c, "this question was put to another channel")
}
person.Verified = "a desk click: whoever was at the operator's session on " + c.Sender.Identity
}
// Judged at the ask's highest level, whichever option was chosen (answerLevel): an acknowledgement on an
// ask that offers an approval needs what the approval needs, so it never ends that ask from where an
// approval is not proven.
level := o.Level
if answerLevel(rec.Ask).Rank() > level.Rank() {
level = answerLevel(rec.Ask)
}
proofs := []string{}
switch o.Level {
switch level {
case asks.Acknowledge:
case asks.Approve:
if !verifies {
return h.refuseChoice(kind, c, Display(kind)+" cannot prove who answered, so it can only acknowledge")
return h.refuseChoice(kind, c, Display(kind)+" cannot prove who answered, so it cannot answer a question "+
"that asks for an approval")
}
if now.Before(id.ApproveFrom) {
return h.refuseChoice(kind, c, "this account was linked at "+id.Linked.In(h.zone()).Format("15:04")+
" and can approve from "+id.ApproveFrom.In(h.zone()).Format("15:04"))
" and can answer this question from "+id.ApproveFrom.In(h.zone()).Format("15:04"))
}
if why := h.rootFreeFor(info); why != "" {
if why := h.rootFree(info, false); why != "" {
return h.refuseChoice(kind, c, why)
}
proofs = append(proofs, "P1")
if o.Level == asks.Approve {
proofs = append(proofs, "P1")
}
default:
return h.refuseChoice(kind, c, "it needs a code from the authenticator app, which the mesh does not take yet")
}
@@ -731,7 +886,8 @@ func (h *Holder) showOnHandle(kind string, m Message) (string, error) {
if h.Publish == nil {
return "", errors.New("this router cannot reach the bus")
}
out := asks.Message{Handle: newHandle(), Title: m.Title, Body: m.Body, Urgent: m.Urgent, Silent: m.Quiet, Reply: m.Reply, To: m.To}
out := asks.Message{Handle: newHandle(), Title: m.Title, Body: m.Body, Urgent: m.Urgent, Silent: m.Quiet, Reply: m.Reply, To: m.To,
Secret: m.Secret}
if out.To == "" && h.Bench != nil && h.Bench.To != nil {
out.To = h.Bench.To(kind)
}