Repository navigation
Drive credentials and state through the contract; drop mx.* entirely - #173
Merged
Merged
Conversation
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
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The finish line the migration plan was aimed at: corteza's runtime makes no
mx.apiormx.clientcall 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 throughchat_channels(),chat_history()andchat_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_configit 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/modelrename 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.apileaves 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.clientstays 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. Onchat_channels()andchat_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.