aboutsummaryrefslogtreecommitdiff
path: root/docs/design/2026-08-19-agent-text-relay-fallback-decision.org
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-08-19 15:02:32 -0700
committerCraig Jennings <c@cjennings.net>2026-08-19 15:02:32 -0700
commit62a7fbf695405d8804220c1cf1b280ee8d612082 (patch)
tree66f5c4123b512fcdb2422b335a7e14d5e5446167 /docs/design/2026-08-19-agent-text-relay-fallback-decision.org
parent10d4e3b17dddbe8df47a80444688d13be15dc016 (diff)
downloadrulesets-62a7fbf695405d8804220c1cf1b280ee8d612082.tar.gz
rulesets-62a7fbf695405d8804220c1cf1b280ee8d612082.zip
fix(agent-text): relay through a host that holds the account
The relay target was hardcoded to velox, and velox's reinstall wiped its signal-cli registration. A send from velox then relayed to itself, and any machine without a local account relayed into the same dead end. Only ratio still worked, because it holds the account as a linked device and sends directly. The old failure text blamed velox for being down. It was up, just unregistered. The target is now an ordered list, overridable per invocation, and the loop skips any candidate matching this machine's name. The skip is the actual fix. Reaching the relay branch means the account isn't local, so relaying to ourselves can't work whatever the list order. Both sides of that comparison are domain-stripped, or an FQDN nodename would silently disable the guard. A relay host that is reachable but unregistered exits 1, not 0, so the loop advances instead of reporting a delivery that never happened. I checked that rather than assume it. Silent non-delivery is the worst failure this tool has, and a test pins it. The failure message now names which branch failed, so a local send failure no longer sends anyone chasing the tailnet. protocols.org and the pager runbook both asserted the topology the reinstall destroyed. They now say how to derive which machine holds the account instead of naming one. The snapshot form is what rotted. The runbook's linking recipe is marked impossible for now, since the primary's keys are gone and a linked device can't authorize another.
Diffstat (limited to 'docs/design/2026-08-19-agent-text-relay-fallback-decision.org')
-rw-r--r--docs/design/2026-08-19-agent-text-relay-fallback-decision.org155
1 files changed, 155 insertions, 0 deletions
diff --git a/docs/design/2026-08-19-agent-text-relay-fallback-decision.org b/docs/design/2026-08-19-agent-text-relay-fallback-decision.org
new file mode 100644
index 0000000..48dd572
--- /dev/null
+++ b/docs/design/2026-08-19-agent-text-relay-fallback-decision.org
@@ -0,0 +1,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.