Skip to content

Add chat_whoami() and chat_addressed() - #9

Merged
TroyHernandez merged 3 commits into
mainfrom
whoami
Aug 7, 2026
Merged

TroyHernandez merged 3 commits into
mainfrom
whoami

Conversation

@TroyHernandez

Copy link
Copy Markdown
Contributor

Phase 1e of the corteza transport-blindness migration.

corteza asks two questions of every inbound message: is this mine, and was I addressed. It answered both by parsing Matrix user ids — splitting @bot:example.org on the colon for a localpart, then looking for @bot in the body. That is transport knowledge sitting in the consumer that is supposed to be transport-blind.

What lands

chat_whoami(client) returns a chat_identity() with the account id and, where the adapter has it without asking the server, a display name. Matrix reads it off the config it already holds, Slack calls auth.test once and caches it (the answer is a property of the token, and chat_addressed() asks once per message), IRC reports its NICK, loopback is fixed.

chat_addressed(client, message) is a second verb rather than a field, because the plain-text form of a mention is per-transport and writing one into a consumer is how that consumer learns which transport it is on. Matrix has @bot and the full user id, Slack has <@U0123>, IRC has a bare nick.

The default reads declared mentions only, so an adapter that does not override it under-reports. A bot that misses being addressed goes quiet; one that over-reports talks over people, and unprompted is worse than absent.

New capability flag: whoami. TRUE on all four adapters.

A bug fixed on the way through

The old \b boundary after @bot ends the word at a . or -, both legal in a Matrix localpart, so @bot.deploy read as a mention of @bot — the wrong bot answered and the right one never saw it. A negative lookahead over the localpart grammar gets it right. Identifiers are escaped before interpolation too, so @a.bot no longer matches @axbot.

Verification

735/735 passing. 15 mutations run against the new guards, all caught.

One survived the first pass and was a real defect, not a test gap: R evaluates arguments lazily, so identity_mentioned(chat_whoami(client)$id, message) short-circuited at an empty mentions list and never forced the promise. An adapter that could not say who it was quietly answered FALSE to "were you addressed" instead of raising — and silence is what that bug looks like from outside, which is also what working looks like. Fixed with force(id).

A second survived because the test could not discriminate: with a malformed user_id the fallback pattern happened to behave the same either way. matrix_localpart() is now tested directly, with a body containing a bare @ — the case where an unguarded empty localpart reads every email address as a mention.

Also in here

DESIGN.md gains the two contracts phase 1 does not cover: state-reading (chat_channels, chat_history, chat_pending, chat_mark_read) and credential lifecycle. The second records why phase 1f is blocked — corteza rotates its own access token when a /model command renames the bot, and disk is how that rotation reaches the adapter, so chat_poll()$client cannot be removed until chat_set_identity() exists.

corteza's consumer is a follow-up PR, gated on this one reaching drat as 0.0.1.15.

corteza asks two questions of every inbound message: is this mine, and
was I addressed. It answered both by parsing Matrix user ids -- splitting
"@bot:example.org" on the colon to get a localpart and looking for
"@bot" in the body. That is transport knowledge in a consumer that is
supposed to be transport-blind.

chat_whoami() returns a chat_identity() with the account id and, where
the adapter has it without asking the server, a display name. Matrix
reads it off the config it already holds, Slack calls auth.test once and
caches, IRC reports its NICK, loopback is fixed.

chat_addressed() answers the second question, because the plain-text
form of a mention is per-transport and writing one into a consumer is
how that consumer learns which transport it is on. The default reads
declared mentions only, so an adapter that skips it under-reports: a bot
that misses being addressed goes quiet, one that over-reports talks over
people, and unprompted is worse than absent.

Matrix's method fixes a matching bug in passing. The old \\b boundary
after "@bot" ends the word at a "." or "-", both legal in a
localpart, so "@bot.deploy" read as a mention of "@bot" -- the wrong
bot answered and the right one never saw it. A negative lookahead over
the localpart grammar gets it right, and identifiers are escaped before
interpolation so "@a.bot" no longer matches "@AxBot".
The malformed-user_id test passed with the guard mutated out: with
user_id "bot" the fallback pattern is "@bot", and neither "anything
at all" nor "bot" distinguishes it from the empty-localpart pattern
the guard exists to prevent. Test the helper's own answers, and use a
body with a bare @ in it -- the case where an unguarded empty localpart
reads every email address as a mention.
State-reading and credential lifecycle. The migration plan assumed the
four remaining mx.api calls mapped one-to-one onto missing verbs; three
of them are one operation the contract has no shape for, and there are
eleven mx.client calls the plan never counted.

Records the concrete reason 1f is blocked: corteza rotates its own
access token when a /model command renames the bot, and disk is how that
rotation reaches the adapter. chat_poll()$client is a symptom of two
rotation paths sharing a file, so chat_set_identity() has to come first.
@TroyHernandez
TroyHernandez merged commit f98e95b into main Aug 7, 2026
2 checks passed
@TroyHernandez
TroyHernandez deleted the whoami branch August 7, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant