Skip to content

Drive credentials and state through the contract; drop mx.* entirely - #173

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

TroyHernandez merged 6 commits into
mainfrom
whoami

Conversation

@TroyHernandez

@TroyHernandez TroyHernandez commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

The finish line the migration plan was aimed at: corteza's runtime makes no mx.api or mx.client call at all.

Depends on cornball-ai/chat.api#10.

What moved

Fifteen sites, in two groups the plan had not separated.

Three were current-state reads — which rooms am I in, what was recently said, what invitations are standing. chat_poll() only answers what changed since a cursor, which is no use to a process that just started. They go through chat_channels(), chat_history() and chat_pending(). The startup invite catch-up was the last raw sync, and it was raw precisely because an invitation is standing state that a cursor cannot deliver.

Eleven were credentials. Config path, load, save, session, relogin, configure, the profile rename. chat.api owns them now, and the chat_config it hands back keeps corteza's own fields — bots, operators, model preferences live in the same file.

Plus the read receipt (chat_mark_read()), and the identity work from #172 before it.

1f falls out

chat_poll() no longer returns $client. corteza took the post-sync config out of it and rebuilt a client per send, because a /model rename could rotate the token and the file they shared was the only thing keeping the two copies in step. chat_set_identity() puts the rotation on the client that performed it, so there is one client for the poll and every send that follows it.

Dependencies

mx.api leaves Suggests. matrix_require_mx() checks chat.api and nothing else — repeating a downstream package's own requirements, which transport packages it needs and at what version, is corteza asserting facts only chat.api can keep true.

mx.client stays in Suggests deliberately: the tests stub it to drive a real relogin through the whole stack, which is what proves the rotation propagates. That is a test dependency, and it is the honest place for it.

A test asserts the finish line directly — a grep over every function body in the namespace for mx.api, mx.client, mx.crypto. Nothing weaker holds it, since any one of those calls would work perfectly on a host that happens to have the package installed.

Verification

2808/2808. 7 mutations, all caught.

Two survived the first pass, both in matrix_backfill_sessions() — inverting the kind filter and ignoring the self flag each left the suite green, because that function had no direct test at all. It could not easily have had one: it drove mx.api, so reaching it meant reaching a homeserver. On chat_channels() and chat_history() it is seamable, and it now has five. What it does matters more than its size — it is everything a restarted process knows about the conversation it is walking into. Get the order backwards and the model reads the room in reverse; lose the self flag and the bot re-reads every reply it ever sent as somebody talking to it.

Two questions get asked of every inbound message here: is this us, and
were we spoken to. Both were answered with Matrix string surgery --
splitting "@bot:example.org" on its 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, and it was wrong in
a way nobody would notice: the \\b boundary ends a word at ".", so
"@bot.deploy" read as a mention of "@bot".

matrix_poll() now calls chat.api::chat_whoami() once for the identity
and chat.api::chat_addressed() once per message, while the contract's
chat_message is still in hand. The verdict rides onto the corteza record
as `addressed` and the reply gate reads that. matrix_message_mentions_self
is gone; matrix_known_bots() takes the id rather than reading cfg$user_id.

The backfill path still reads cfg$user_id. It drives mx.api directly and
has no chat client, so it is spelled out at the call site rather than
hidden behind a default argument, and it goes when the backfill contract
does.
Two mutations survived the first pass. Replacing self_id with a literal
changed nothing any test could see, because every message in the
integration test was is_self and skipped the gate entirely. self_id is
load-bearing elsewhere: it is the bot's own entry in the known-bots
list, so a wrong one makes a two-member room read as two humans and
every ingested message picks up a "[sender]" prefix. That is now
asserted on the transcript.
The finish line the migration plan was aimed at: corteza's runtime makes
no mx.api or mx.client call at all, and mx.api leaves Suggests.

Fifteen sites, in two groups the plan had not separated. Three were
current-state reads -- which rooms am I in, what was recently said, what
invitations are standing -- and chat_poll() only answers what changed
since a cursor, which is no use to a process that just started. They go
through chat_channels(), chat_history() and chat_pending(). The startup
invite catch-up was the last raw sync, and it was raw precisely because
an invitation is standing state that a cursor cannot deliver.

The other eleven were credentials. Config path, load, save, session,
relogin, configure, the profile rename. chat.api owns them now, and the
chat_config it hands back keeps corteza's own fields -- bots, operators,
model preferences live in the same file.

That also unblocks 1f. chat_poll() no longer returns $client. corteza
took the post-sync config out of it and rebuilt a client per send,
because a /model rename could rotate the token and the file they shared
was the only thing keeping the two copies in step. chat_set_identity()
puts the rotation on the client that performed it, so there is one
client for the poll and every send that follows it.

matrix_require_mx() checks chat.api and nothing else. Repeating a
downstream package's own requirements -- which transport packages it
needs, at what version -- is corteza asserting facts only chat.api can
keep true.

Tests still stub mx.client, deliberately: driving a real relogin through
the whole stack is what proves the rotation propagates. That is why it
stays in Suggests when mx.api does not.
Two mutations survived the first pass -- inverting the kind filter and
ignoring the self flag both left the suite green -- because
matrix_backfill_sessions() had no direct coverage at all. It could not
easily have any before: it drove mx.api and reaching it meant reaching a
homeserver. On chat_channels() and chat_history() it is seamable.

What it does matters more than its size. It is everything a restarted
process knows about the conversation it is walking into: get the order
backwards and the model reads the room in reverse, lose the self flag
and the bot re-reads every reply it ever sent as somebody talking to it.
@TroyHernandez TroyHernandez changed the title Ask the adapter who we are and whether we were addressed Drive credentials and state through the contract; drop mx.* entirely Aug 7, 2026
@TroyHernandez
TroyHernandez marked this pull request as ready for review August 7, 2026 19:30
It returns list(messages, cursor) now. The cursor goes unread here on
purpose: backfill wants the tail of the conversation, not the room's
whole history, so `limit` is the window and paging further back would
grow a restart's context without bound.
The verify step read corteza:::.MX_CLIENT_MIN, which the previous commit
deleted along with the mx.client calls that justified it. That turned
the job red on an object-not-found rather than on anything about the
dependency, which is a worse failure than the one it was written to
catch.

Presence is still checked for all three: mx.client is what the
integration tests stub to drive a real relogin through the whole stack,
and a runner without it skips them silently.
@TroyHernandez
TroyHernandez merged commit 7758422 into main Aug 7, 2026
2 checks passed
@TroyHernandez
TroyHernandez deleted the whoami branch August 7, 2026 21:27
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