aboutsummaryrefslogtreecommitdiff
path: root/docs/design/2026-08-19-agent-text-relay-fallback-decision.org
blob: 48dd572a6c2d89b3120cc31248cf228957fd2d35 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#+TITLE: agent-text relay fallback — decision record
#+AUTHOR: Craig Jennings
#+DATE: 2026-08-19

* What broke

velox's 2026-08-13 reinstall wiped its =signal-cli= registration. Its account
store came back empty.

That alone would have cost velox one channel. =agent-text= made it cost the
fleet, because the script hardcoded =VELOX_HOST= as its single ssh relay
target. So a send from velox relayed to velox, hit the same empty store, and
failed; and any third machine without a local account relayed into the same
dead end. Only ratio still worked, and only because it holds the account as a
linked device and takes the direct branch.

The failure text said "velox down or unreachable?", which was actively
misleading. velox was up and on the tailnet the whole time. It simply was not
registered any more.

* Why re-registering velox was not the fix

velox held the *primary* registration, not a linked device, and its keys are
gone. In Signal a linked device cannot authorize another device, so ratio
(device 2) cannot link velox back. There is no QR to scan.

Re-registering the number from velox would work, but it mints a new primary,
drops ratio's link, and needs an SMS or voice code delivered to
+15045173983 — and if that code cannot be received, the identity is
unrecoverable. That is real downside risk taken on to restore exactly the
single-primary topology that had just proved fragile. I chose the relay
repoint instead.

* Two proposals, one chosen

Both =.emacs.d= (2026-08-14) and archsetup (2026-08-16) diagnosed this
independently and proposed replacing the constant with an ordered relay list.
I took archsetup's, on one difference.

=.emacs.d='s version tries each host in order and stops at the first success.
On velox that works — but only because ratio happens to be listed first. It
never skips *self*, so the failure it fixes is still reachable: a fleet whose
list led with velox would relay velox to velox exactly as before. It repairs
the instance rather than the defect.

archsetup's adds the guard:

: [ "${host%%.*}" = "$self" ] && continue

Reaching the relay branch *means* the account is not local. So an ssh round
trip to this machine lands on the same store that just came back empty and
cannot succeed, whatever the list order. That is the defect; the ordering was
the symptom.

archsetup's also makes the list overridable (=AGENT_TEXT_RELAYS=), which is a
genuine improvement over both the old constant and a fixed array — the fleet
assumption can be stepped around per invocation without editing the script.

* The question neither proposal had answered

=.emacs.d= raised the sharpest point in either handoff and left it open: a
relay host that is *reachable but unregistered* looks identical to success
from the caller's side unless the exit code is checked. The loop breaks on
=rc -eq 0=, so if =signal-cli send= exits zero against an empty account store,
the loop reports success for a message that went nowhere — the worst possible
outcome for a channel whose entire job is reaching me when I am away.

Answered empirically before applying, on velox, which is itself the
reachable-but-unregistered case:

: $ signal-cli -a +15045173983 send -m ... <uuid>
: User +15045173983 is not registered.
: exit code: 1

So the failure is clean and the loop advances correctly. Recorded in
=protocols.org= so the next reader does not have to re-derive it.

* What else was stale

Fixing the script exposed that =protocols.org= "Reaching Craig" asserted the
same dead fact — the account "is registered on velox (primary) and ratio
(linked device)". Rewritten to state the *derivation* (any machine whose local
=signal-cli= holds it sends directly; check with =signal-cli listAccounts=)
rather than the snapshot, per the host-identity rule. The snapshot form is
what rotted; a second snapshot would rot the same way at the next rebuild.

The same section's =signal-mcp= claim was also false by then: velox's global
=~/.claude.json= carries only =linear= and =slack-deepsat= after the reinstall.
Softened to "may or may not be registered on the machine you're on".

* What the review caught

The guard I chose this proposal *for* had the same shape of flaw I rejected the
other proposal over, and I did not see it. It read:

: [ "${host%%.*}" = "$self" ] && continue

That strips the domain off the candidate host but not off =uname -n=. Both
machines return a short nodename today, so it worked — and nodename is just
whatever =/etc/hostname= holds. One =hostnamectl set-hostname= to an FQDN on
any machine and the guard silently stops firing, restoring the 2026-08-13 bug
on the away channel with nothing to announce it. So the defect fix was itself
instance-dependent, which is the exact criticism I had made of .emacs.d's
version one section above. Both sides are stripped now, and the test that used
to assert the direction that already worked was repointed at the direction that
was broken.

Three more, all correct and all fixed here:

- The failure message named the relay list on *both* branches, so a local
  =signal-cli= failure on the machine that actually holds the account — ratio,
  the fleet's only working sender — would send a debugger chasing the tailnet
  while the fault sat on the box in front of them. That is the same misdirection
  as the old "velox down or unreachable?" text that this change exists to
  remove. It now carries a per-branch reason.
- The header's "Verified end to end ... 2026-08-16 (velox -> ratio, relay)" was
  archsetup's verification, not mine, sitting in a list of two I could vouch for
  and inheriting their credibility. Their handoff reports running the exact
  relay command from velox and getting a Signal timestamp with rc 0. That is
  real, and it is on report — the header now says so.
- =protocols.org= and the script header both point at the runbook in
  =docs/design/= as authoritative, and that runbook still asserted the retired
  topology in four places. Correcting two surfaces and leaving the one they
  route to is worse than not having started: the contradiction reads as a
  disagreement between sources rather than one uniformly old story. The runbook
  now opens with a dated superseded note, and =signal-receive.sh='s header
  comment moved to the derivation form.

* Verification

- =bash -n= and =shellcheck -S warning= clean.
- =agent-text.bats= 5 tests → 10, all green. The suite now stubs =uname= so the
  self-skip is exercised deterministically instead of depending on which
  machine runs it.
- Mutation-proven rather than assumed. Deleting the self-relay guard reds tests
  3, 4 and 9; making the loop break unconditionally reds test 5; seeding =rc=0=
  instead of =1= reds test 9. Each mutation is caught by a distinct test, so
  the suite guards the behavior rather than merely covering the lines.
- Full suite: only the pre-existing =ai-launcher-runtime.bats= codex failure
  remains, which is a velox environment gap tracked separately.
- The two review fixes are mutation-proven the same way rather than assumed:
  reverting the self-side strip reds the FQDN test, and making the failure
  message branch-blind again reds the local-failure test. Suite is 11 tests.

* Provenance of the relay verification

Recorded here because the working directory was filed away and this is the only
surviving copy. archsetup's 2026-08-16 handoff states: "Verified from velox
before proposing: signal-cli is at /usr/bin/signal-cli on ratio's
non-interactive PATH, and the exact relay command agent-text would run returned
a Signal timestamp with rc 0. Direct send from ratio also verified. bash -n and
shellcheck -S warning are both clean on the attached file."

That is the source for the header's 2026-08-16 line. I did not re-run a live
relay send, because doing so delivers a real message to Craig's phone.