This Week in Matrix 2021-12-17

17.12.2021 00:00 — This Week in Matrix Thib

Matrix Live 🎙

A supercharged episode this week with five super exciting demos!

Dept of Spec 📜

TravisR reports

Hey all, it's me again, not-anoa, with your spec update. I don't have a graph for you this week, but I do have curated content which hopefully holds you over until next week 🙂

This week we've seen a few new MSCs get opened up:

As demonstrated, a few of them are follow-on work from aggregations. A lot of the work is an effort to get MSC2675 - Serverside aggregations of message relationships through FCP - it's changed quite a bit in the last week, so if you reviewed it before then please give it a quick read!

We're also gearing up for MSC1767 - Extensible event types & fallback in Matrix receiving attention early next year. This is currently being evaluated by features like MSC3245 - Voice messages, MSC3381 - Polls, and MSC3488 - Static location sharing. If all goes according to plan, the first pieces of Extensible Events will land soon.

Thib has caught me quite close to the deadline on TWIM, so while I don't have a random MSC for you this week I do recommend some light reading around aggregations and extensible events - these are both major features for Matrix and help shape the future for other, even more exciting, features.

Nico announces

Random MSC

Since the script didn't run, I manually generated random numbers until I actually got one, that matches an MSC and is not a draft!

So the random MSC is: MSC2461: Proposal for Authenticated Content Repository API

The goal of this MSC is to restrict who can access files in your content repository, which is used to share files, images, voice messages and more on Matrix. Currently this is secured by using random identifiers, but this MSC wants to add a few more restrictions: A signed-in user can still access everything, but if you don't provide an access token, when downloading media, you either can access no media, only media that was sent from that server, only media which the server already downloaded or everything. The level of access can be set by the server admin.

I think this would be really nice to have, but it has various challenges when implementing it in clients and when it needs to work across federation. It could limit how much random users can fill up your disk though, which is especially important for small server admins!

Dept of Servers 🏢

Synapse

Synapse is the reference homeserver for Matrix

callahad reports

Happy holidays! This week we released Synapse 1.49.0, our last planned release of the year. This is the last release that supports Python 3.6 or PostgreSQL 9.6; if you've not upgraded, now is the time!

Most notably, this release includes stable support for MSC2918: Refresh tokens. This is a more secure alternative to long-lived access tokens, and we'd encourage clients to implement support for refresh tokens as described in the MSC.

We also released Sygnal 0.11, which includes loads of bugfixes. For the Element-managed Sygnal instances, this release has reduced our daily Sentry error rate by over 99%, dramatically improving the signal-to-noise ratio of our monitoring.

See you in January with Synapse 1.50! 🎄

Sydent

Sydent is the reference Matrix Identity server. It provides a lookup service, so that you can find a Matrix user via their email address or phone number (if they have chosen to share it).

dmr announces

I've just published the third and final post on Sydent's type annotations (part 1, part 2). This one is more reflective and tries to quantify our efforts: how well have we done?

Thanks for reading---I hope it's been useful.

Homeserver Deployment 📥️

Helm Chart

Matrix Kubernetes applications packaged into helm charts

Ananace announces

And yet again more updates have been done on my Helm Charts - bringing element-web up to 1.9.7 and matrix-synapse up to 1.49.0

Dept of Clients 📱

Nheko

Desktop client for Matrix using Qt and C++17.

Nico reports

We are planning to have a small release next week, that fixes a few issues with the 0.9.0 release. It would be lovely if some of you could test one of our nightlies or could check if the translations for a language you speak are up to date in our weblate.

Some of the fixes this week include another crash fix for handling matrix links from your browser, notification bubbles that can show values over 9000(!), better preview images for sticker and emote packs created in Nheko, allowing you to click links in replies, a few layout and click area fixes. Nheko now also keeps track of your latest reactions and gives you easy access to them in the hover menu.

Nheko now also finally supports pinned messages! Most of you probably don't know, but that feature has pretty much always been in the matrix spec, but very few clients expose it. Today Nheko joins that rank! It's part of our goal to provide better support for building communities. Topics can be quite limiting, because they can only contain plain text. Pinned messages allow for much more creative freedom! They can also be encrypted, while state events currently never are, but the key for that isn't reshared, so currently experience in encrypted rooms is a tradeoff. Maybe we'll go for an encrypted description event in the future, but for now this seems to be a good solution to bridge the gap.

Let's hope the current master branch is good and we'll have a release with ALL THE FIXES next week! And thank you everyone, who already translated and reported issues! It took less than 10 hours to have 5 languages updated to 100%! Last year we didn't even have that many languages at 100%! You guys are AMAZING! <3

Element

Everything related to Element but not strictly bound to a client

kittykat announces

Development has kicked off for location sharing! Watch this space for more news in the new year.

Threads

  • List of threads in a room is now more accurate and viewing very long threads has improved with the integration with the homeserver APIs. (These APIs are not enabled on matrix.org yet.)
  • Threads on mobile platforms are catching up to Web, with many changes in review.
  • Iterated on the design for restricted history threads and search results across all platforms.
  • Design for thread previews in room list has been improved for mobile platforms.
  • Improved Android bottom sheet expandable and scrollable behaviour design.
  • We’ve created MSC3567 to fix some edge cases with API calls

Polls

  • Only one Android bug is outstanding!
  • Gathering feedback to incorporate into the next phase of development, join us for the community testing session on Monday at 17:00 in #element-community-testing:matrix.org

VoIP

  • Continuing with polishing & bug fixing for full mesh calling app. One remaining bug somewhere causing members to not connect properly. Finalising how much of registration & login we can/want to implement for the short term until it’s replaced by OAuth login.

Community testing sessions

  • Tested all three Release Candidates (RCs) at the same time for the first time! Did not find any new web issues when testing first time user experiences and basic interactions on Web. We found 6 new issues on Android and 9 on iOS.
  • Tried out the Information Architecture changes on web with the Delight team. Very exciting to see these changes available in Labs already!
  • Closed 33 out of 60 re-tested encryption issues on Web and prioritised a few to be considered for upcoming work.
  • Next sessions, join #element-community-testing:matrix.org :
    • 17:00 UTC / 18:00 CET on Monday 20th December to test the new Polls feature
    • 16:30 UTC / 17:30 CET on Tuesday 21st December to squash more encryption bugs!

Element Web/Desktop

Secure and independent communication, connected via Matrix. Come talk with us in #element-web:matrix.org!

kittykat announces

  • We’ve been working on auto-generating code and documentation for events raised by our client analytics (code here, PR for documentation generation here). This allows us to publish a comprehensive list of everything our analytics capture, which is great both for end users and for people doing analysis.
  • In labs (you can enable labs features in settings on develop.element.io or on Nightly)
    • First milestone reached on Information Architecture! To try it out, enable “Threaded messaging”, “Use new room breadcrumbs” and “New spotlight search experience” in the Labs settings.
    • We’re actively collecting feedback on IA to review in the new year.
    • Starting work on Message Bubble defects

Element iOS

Secure and independent communication for iOS, connected via Matrix. Come talk with us in #element-ios:matrix.org!

kittykat reports

  • Fixed an issue around some voice messages not playing in bridged rooms.
  • Polls changes are in this week’s release candidate (RC) and will be available behind a Labs flag in the next release.
  • Analytics changes have been merged into the RC and opt-in will be available soon.
  • In development:
    • Spaces is coming closer to completion: we’re there on space creation, adding rooms to spaces, space management and more. Release coming in the new year!
    • Work on implementing new login flow continues, with more improvements incoming.

Element Android

Secure and independent communication for Android, connected via Matrix. Come talk with us in #element-android:matrix.org!

kittykat announces

  • Improvements to the timeline performance (faster display, faster scroll) after updates to the way we store timeline events.
  • Analytics framework has been merged, opt-in request will be shown to users once more translations have landed. For now, you can enable it in the settings.
  • Work on Message Bubbles has started!

Cinny

Cinny is a Matrix client focused on simplicity, elegance and security

ajbura says

Cinny v1.6.0

Features

  • Room Timeline
    • Add pagination in room timeline
    • Replies link back to original message event
    • Use formatted_body to parse markdown
    • Support rich replies
    • Separation of read/unread messages in the room
    • Typing outside of an input box should focus the message field
    • Spoiler display support
    • User pill display support
    • Custom emoji display support
    • Performance improvements
  • Export E2E key for decrypting history in another client
  • Replaced go-to commands with Room search modal (Ctrl + k)
  • Remember people panel state
  • Twemojified all kind of text (except inputs)
  • Add option to hide membership and user events from timeline
  • Messages now span to full viewport width
  • Add animation on hover in sidebar/avatar

Bugs

  • Fix defer typing notifications until it can't be a command
  • Fix checkbox in register page
  • Fix app sending read receipt in background
  • Fix crash on creating room
  • Fix dark theme colors

Security update

Find more about Cinny at https://cinny.in/ Join our channel at: #cinny:matrix.org Github: https://github.com/ajbura/cinny Twitter: https://twitter.com/@cinnyapp

Dept of Non Clients 🎛️

Matrix Highlight

A decentralized and federated way of annotating the web based on Matrix.

Daniel announces

I've been working on a matrix-based tool for highlighting and annotating websites. By building on top of matrix, we can effectively have a decentralized, federated and collaborative way to leave notes and highlights on pages. I wrote a brief introduction on my blog, as well as made a little bit of a simple demo video. Here's a copy-pasted list of planned and existing features:

  • Current: Create and send website annotations over Matrix.
  • Current: Store data in a decentralized and federated manner.
  • Current: Share highlights with other users, including those on other servers.
  • Current: Group annotations together and create multiple annotation groups
  • Planned: Use Matrix's End-to-End encryption to ensure the secure transmission and storage of highlight data.
  • Planned: Leverage the new m.thread MSC to allow users to comment on and discuss highlights.
  • Planned: Use something like ArchiveBox to cache the current version of a website and prevent annotations from breaking.
  • Planned Highlight PDFs in addition to web pages.

Come join #matrix-highlight:matrix.danilafe.com to receive updates about the project!

I haven't published the code just yet, but I'm going to as soon as the tool is in better shape.

Daniel announces

Matrix highlight for probably the last time this week. Highlight comments and self-editing are implemented, though I'm not sure I'll stick with this particular model.

Populus Viewer

A Social Annotation Tool Powered by Matrix

gleachkr announces

I've been teaching a class this semester using a tool built on the matrix-js-sdk and tentatively entitled populus-viewer. Populus-viewer uses Matrix as a backend for the social annotation of PDFs, with the goal of helping matrix become a platform for teaching and scholarly collaboration. If you're interested in learning more, or adopting populus-viewer in your teaching, come visit #opentower:matrix.org!

Populus-Viewer currently supports:

  • Annotation of PDFs with highlights and pin-drops
  • Matrix conversations based on annotations
  • Audio and video messages
  • Replies, reactions, and redactions
  • Markdown for rich text
  • LaTeX for mathematical notation
  • Typing notifications
  • Synchronized reading position across devices
  • SSO, with single-click links for embedding in an LMS like Canvas or Blackboard.

As the project develops, I'm hoping to continue to polish the reading experience, and to add support for other mime types (audio and video especially).

matrix-streamchat

Matrix powered stream overlay for OBS, to integrate live chat in your favorite (selfhosted) streaming setups.

f0x announces

TWIM I got started on the chat part of matrix-streamchat, to provide a lightweight embeddable Matrix client to be used alongside streams in Owncast and PeerTube. It will use guest access, and lots more features to come like extensive custom emote support. For now refactoring a bunch of things first before adding more flashy things, but who knows, you might see me do it live on https://stream.pixie.town

Dept of SDKs and Frameworks 🧰

vodozemac

An implementation of Olm and Megolm in pure Rust.

Matthew says

Introducing vodozemac (https://github.com/matrix-org/vodozemac) - a rewrite of libolm in Rust by poljar and dkasak! The intention is for this to become the reference Olm implementation going forwards, and to get it audited asap (and benefit from all of Rust’s nice safety and parallelism features, and better crypto primitives!)

simplematrixbotlib

simplematrixbotlib is an easy to use bot library for the Matrix ecosystem written in Python and based on matrix-nio.

krazykirby99999 says

Version 2.5.0 Released!

simplematrixbotlib is an easy to use bot library for the Matrix ecosystem written in Python and based on matrix-nio. Version 2.5.0 adds improvements to the config feature.

Feature Changes:

  • Add allow/block lists: This allows bot developers to specify allow/block lists of users who have permission to interact with the bot using regex.
  • Permissions can checked with Match.is_from_allowed_user(), which lets the bot developer choose which responses are restricted.
  • The allow/block lists can by modified at runtime via the Config.add_allowlist(), Config.remove_allowlist(), Config.add_blocklist(), and Config.remove_blocklist() methods.

A thank you to HarHarLinks for their contributions to version 2.5.0!

Request additional features here.

View source on Github View package on PyPi View docs on readthedocs.io https://matrix.to/#/#simplematrixbotlib:matrix.org

Dimension

An open source integration manager for matrix clients, like Element.

TravisR announces

Dimension, an integration manager alternative for Element, has received a bunch of updates over the last couple weeks:

  • Added (early) support for matrix-hookshot's GitHub, Jira, and Webhooks bridging.
  • Most of a redesign complete to make it feel more like an Element UI rather than something special and third party.

If you're interested in helping out in getting the redesign finished, please check out https://github.com/turt2live/matrix-dimension/issues/458 which has reference mockups and linked issues. The major parts are the "complex bots" (Travis CI, RSS, etc) and the sticker integration. Unfortunately, I don't have enough free time to work on it myself in the near term, but will get back to it eventually 🙂

And now, a complementary screenshot of the Good™ parts:

Dept of Ops 🛠

matrix-commit

A Github Action for sending messages to a Matrix Room.

krazykirby99999 reports

Example Usage:

# .github/workflows/matrix-commit.yml
on:
  push:
    branches:
      - master

jobs:
  matrix_action_job:
    runs-on: ubuntu-latest
    name: Send Message to Matrix Room
    steps:

    - name: Checkout
      uses: actions/checkout@v2

    - name: matrix-commit
      uses: krazykirby99999/matrix-commit@v1

      with:
        homeserver: ${{ secrets.BOT_HOMESERVER }}
        username: ${{ secrets.BOT_USERNAME }}
        access_token: ${{ secrets.BOT_ACCESS_TOKEN }}

        room_id: ${{ secrets.ROOM_ID }}
        message: "#### New Commit:"

Notes:

Syntax:

  • The homeserver should be in the form of https://domain.tld
  • The username should be the username, not the user id. (krazykirby99999, not @krazykirby99999:matrix.org)
  • The room_id should be the internal room id of the room, not the published address. (!QQpfJfZvqxbCfeDgCj:matrix.org, not #thisweekinmatrix:matrix.org) This can be found under Room Options > Advanced > Room Information in the Element Client.

Other

  • If the room_id is not specified, the bot will send the message to all joined rooms.
  • If the message is not specified, it will default to Commit:.
  • The bot will join all invited rooms upon the start of an action.

Contributions are welcome - https://github.com/KrazyKirby99999/matrix-commit

Example Image

Dept of Bots 🤖

matrix-imposter-bot

A Matrix appservice for relaying messages.

mr_johnson22 says

matrix-imposter-bot - A bot that uses your account to repeat other people's messages. This gives relay-bot capabilities to puppet-only bridges.

I made this project a while ago to hack in a relay mode to the mautrix Facebook bridge. But as of this week, that bridge supports relaying natively! 🎉 Thus, my main motivation for maintaining imposter-bot is obsolete, and the project will be on indefinite hiatus.

With that said, it can (mostly) still be used to add relay support to any bridges that don't yet support a relay mode themselves--but native relay support is always better!

Thanks to everyone who's shown interest in the project, and to tulir for making such great bridges!

Dept of Interesting Projects 🛰️

ChatStat

An R package To Gather Stats From Chat Platforms

Gwmngilfen announces

A project has started to re-implement the venerable mIRCstats for Matrix! It's in very early stages, right now it only does "getting a data-frame of events for a list of rooms" and has no actual visualisations baked in yet. However, we're moving quickly, and I hope to have some initial easy-to-use viz in place over the Christmas break.

The project is written in R (because I am an R user, and its good for data and viz work :P) and you can find it here. If you're new to R and want to give it a go, check out the extremely brief howto I just wrote here. I look forward to all the ways you will tell me it's broken!

Dept of Built on Matrix 🏗️

Saint Petersburg Widget (A Board Game build on Matrix)

Timo K. announces

Board games are great. And Matrix and its widget api turned out to be an excellent environment to create collaborative board games. With some really impressive conditions:

  • I don't have to maintain a server with a database.
  • I don't have to create a custom account systems user need to register. They play with their matrix account, which also makes accessibility great. Someone invites you in a room with the game and you can play!
  • I just need to host one static file and ppl will be able to play as long as that static site exists.

This project tries to be two things. A tech demo and inspiration to what is possible with widgets (Especially, with the changes on how widgets can be displayed in element (Check the "matrix live" Demos! 😊) ) Second it should serve as source and resource. For ideas and solutions on how no trust games can be executed without server (third party) side logic. And, for the ones interested, also as a resource on how widgets are implemented.

Last but not least the game Saint Petersburg is really fun. It takes a couple of minutes to grasp the rules but it is one of those games where there are so many things that can be considered with simple rules that it becomes more and more exciting with each round. So I really invite you to check out the rules and give it a try. Its best to start in the Git Repo or join the this room: #st-petersburg-auth:matrix.org

To put it simple, the widget works like this: The game state is stored in the room state and is updated through the widget directly. This of course raises questions: How is it still possible to prohibit users from cheating and manually changing parameters like, how much money they own. Everyone (who has the permission) is always able to send whatever state events they want? How is it possible to draw random cards if there is no third party involved. Could I not just send a state event with the cards that I hope are going to be drawn and are beneficial for me. Can we make card drawing deterministic? Not really since then everyone know what is going to happen. Which kind of breaks the game...

I would be super happy, if someone is interested and wants to find answer to the questions above by checking out the README.

Matrix in the News 📰

kim says

There is an article (exclusive to paid subscribers) in the German tech news/magazine website heise.de about "Running your own messaging service using the matrix server" https://www.heise.de/ratgeber/Eigener-Chatserver-Mit-dem-Matrix-Server-einen-Messaging-Dienst-betreiben-6289020.html

I'm not a subscriber but sounds like they go over how to set up using the Ansible playbook #matrix-docker-ansible-deploy:devture.com

Dept of Ping 🏓

Here we reveal, rank, and applaud the homeservers with the lowest ping, as measured by pingbot, a maubot that you can host on your own server.

#ping:maunium.net

Join #ping:maunium.net to experience the fun live, and to find out how to add YOUR server to the game.

RankHostnameMedian MS
1envs.net451.5
2cri.epita.fr1019
3aria-net.org1446.5
4flueren.eu1576
5matrix.home.boris-wach.de3181
6trygve.me3519
7utzutzutz.net4038
8matx.myecloud.org4855.5
9rollyourown.xyz4989.5
10grimneko.de5127

#ping-no-synapse:maunium.net

Join #ping-no-synapse:maunium.net to experience the fun live, and to find out how to add YOUR server to the game.

RankHostnameMedian MS
1rustybever.be461
2conduit.supercable.onl524
3dendrite.supercable.onl539
4conduit.cyberdi.sk953.5
5matrix.awesomesheep48.me1434.5
6s2.toldi.eu1593
70x1a8510f2.space2125
8dendrite.beckmeyer.us8497

That's all I know 🏁

See you next week, and be sure to stop by #twim:matrix.org with your updates!

Type coverage for Sydent: evaluation

17.12.2021 00:00 — Tech David Robertson

This is the third in a series of three posts which discuss recent work to improve type annotations in Sydent, the reference Matrix Identity server. Last time we discussed the mechanics of how we added type coverage. Now I want to reflect on how well we did. What information and guarantees did we gain from mypy? How could we track our progress and measure the effect of our work? And lastly, what other tools are out there apart from mypy?

The best parts of --strict

While the primary goal was to improve Sydent's coverage and robustness, to some extent this was an experiment too. How much could we get out of typing and static analysis, if we really invested in thorough annotations? Sydent is a small project that would make for a good testbed! I decided my goal would be to get Sydent passing mypy under --strict mode. This is a command line option which turns on a number of extra checks (though not everything); it feels similar to passing -Wall -Wextra -Werror to gcc. It's a little extreme, but Sydent is a small project and this would be a good chance to see how hard it would be. In my view, the most useful options implied by strict mode were as follows.

--check-untyped-defs

By default, mypy will only analyze the implementation of functions that are annotated. On the one hand, without annotations for inputs and the return type, it's going to be hard for mypy to thoroughly check the soundness of your function. On the other, it can still do good work with the type information it has from other sources. Mypy can

  • infer the type of literals, e.g. deducing x: str from x = "hello";
  • lookup the return types of standard library calls, via typeshed; and similarly
  • lookup the return types of any annotated functions in your code or dependencies.

The information is already available for free: we may as well try to use it to spot problems.

--disallow-untyped-defs and friends

This flag forces you to fully annotate every function. There are less extreme versions available, e.g. --disallow-incomplete-defs; but I think this is a good option to ensure full coverage of your module. It means you can rely on mypy's error output as a to-do list.

One downside to this: sometimes I felt like I was writing obvious boilerplate annotations, e.g.

    def __str__(self) -> str:
        ...

There was one particular example of this that crops up a lot. Mypy has a special exception for a class's __init__ and __init_subclass__ methods. If a return type annotation is missing, it will assume these functions return None instead of Any. (See here for its implementation.) This is normally compatible with --disallow-untyped-defs and --disallow-incomplete-defs, with one exception. If your __init__ function takes no arguments other than self, mypy won't consider it annotated, and you'll need to write -> None explicitly.

It's also worth mentioning --disallow-untyped-calls, which will cause an error if an annotated function calls an unannotated function. Again, it helps to ensure that mypy has a complete picture of the types in your function's implementation. It also helps to highlight dependencies—if you see errors from this, it might be more practical to annotate the functions and modules it's calling first.

--warn-return-any

If I've written a function and annotated it to return an int, mypy will rightly complain if its implementation actually goes on to return a str.

def foo() -> int:
    # error: Incompatible return value type (got "str", expected "int")
    return "hello"

If mypy isn't sure what type I'm returning though, i.e. if I'm returning an expression of type Any, then by default mypy will trust that we've done the right thing.

def i_promise_this_is_an_int():
    return "hello"

def bar() -> int:
    reveal_type(i_promise_this_is_an_int()) # Any
    return i_promise_this_is_an_int()

Enabling --warn-return-any will disable this behaviour; to make this error pass we'll have to prove to mypy that i_promise_this_is_an_int() really is an int. Sometimes that will be the case, and an extra annotation will provide the necessary proof. At other times (like in this example), investigation will prove that there really is a bug!

--strict-equality

This is a bit like a limited form of gcc's -Wtautological-compare. Mypy will report and reject equality tests between incompatible types. If mypy can spot that an equality is always False, there's a good chance of there being a bug in your program, or else an incorrect annotation.

I'm not sure how general this check is, since users can define their own types with their own rules for equality by overriding eq. Perhaps it only applies to built-in types?

Quantifying coverage

It was important to have some way to numerically evaluate our efforts to improve type coverage. It's a fairly abstract piece of work: there's nothing user-visible about it, unless we happen to discover a bug and fix it.

The most obvious metric is the number of total errors reported by mypy. Before the recent sprint, we had roughly 600 errors total.

dmr on titan in sydent on  HEAD (3dde3ad) via 🐍 v3.9.7 (env)
2021-11-08 12:35:37 ✔  $ mypy --strict sydent
Found 635 errors in 59 files (checked 78 source files)

This is a decent way to measure your progress when working on a particular module or package, but it's not perfect, because the errors aren't independent. Fixing one could fix another ten or reveal another twenty—the numeric value can be erratic.

Reports

I found mypy's various reports to be a better approach here. There were three reports I found particularly useful.

--html-report

This produces a main index page showing the "imprecision" of each module. At the bottom of the table is a total imprecision value across the entire project.

HTML report, index page. A table showing each module's precision and number of lines of code.

The precision for each module is broken down line-by-line and colour-coded accordingly, which is useful for getting an intuition for what makes a line imprecise. More on that shortly.

HTML report, module page. Most lines of source code are highlighted green; a minority are highlighted red.

--txt-report

This reproduces the index page from the html report as a plain text file. It's slightly easier to parse—that's how I got the data for the precision line graphs in part one of this series. That was a quick and dirty hack, though; a proper analysis of precision probably ought to read from the json or xml output formats. Here's a truncated sample:

+-----------------------------------+-------------------+----------+
| Module                            | Imprecision       | Lines    |
+-----------------------------------+-------------------+----------+
| sydent                            |   0.00% imprecise |    1 LOC |
| sydent.config                     |   0.00% imprecise |  266 LOC |
| sydent.config._base               |   0.00% imprecise |   31 LOC |
| sydent.config.crypto              |  15.94% imprecise |   69 LOC |
| ...                               |               ... |      ... |
| sydent.validators                 |   0.00% imprecise |   61 LOC |
| sydent.validators.common          |   7.35% imprecise |   68 LOC |
| sydent.validators.emailvalidator  |   1.30% imprecise |  154 LOC |
| sydent.validators.msisdnvalidator |   1.34% imprecise |  149 LOC |
+-----------------------------------+-------------------+----------+
| Total                             |   5.95% imprecise | 9707 LOC |
+-----------------------------------+-------------------+----------+

--any-exprs-report

Selecting this option generate two reports: any-exprs.txt and types-of-anys.txt. The latter is interesting to understand where the Anys come from, but the former is more useful for quantifying the progress of typing. Another sample:

                  Name   Anys   Exprs   Coverage
-------------------------------------------------
                sydent      0       2    100.00%
         sydent.config      0     185    100.00%
   sydent.config._base      0       3    100.00%
  sydent.config.crypto     34      80     57.50%
sydent.config.database      0       8    100.00%
   sydent.config.email      0      86    100.00%
                   ...    ...     ...        ...
-------------------------------------------------
                 Total    544   11366     95.21%

The breakdown in types-of-anys.txt has more gory detail. I found the "Unimported" column particularly interesting: it lets us see how exposed we are to a lack of typing in our dependencies.

                             Name   Unannotated   Explicit   Unimported   Omitted Generics   Error   Special Form   Implementation Artifact
-------------------------------------------------------------------------------------------------------------------------------------------
                           sydent             0          0            0                  0       0              0                         0
                    sydent.config             0          3            0                  0       0              0                         0
              sydent.config._base             0          0            0                  0       0              0                         0
                              ...           ...        ...          ...                ...     ...            ...                       ...
        sydent.util.versionstring             0         80            0                  0       0              0                         0
                sydent.validators             0          4            0                  0       0              0                         0
         sydent.validators.common             0         20            0                  0       0              0                         0
 sydent.validators.emailvalidator             0          8            0                  0       0              0                         0
sydent.validators.msisdnvalidator             0          8            0                  0       0              0                         0
-------------------------------------------------------------------------------------------------------------------------------------------
                            Total             9       1276          273                  0      37              0                        17

The meaning of precision

There are two metrics I chose to focus on:

  • the proportion of "imprecise" lines across the project; I also used the complement, precision = 100% - imprecision, and
  • the proportion of expressions whose type is not Any.

These are plotted in the graph at the top of this writeup. I could see that precision and the proportion of typed expressions were correlated, but I didn't understand how they differed. I couldn't see an explanation in the mypy docs, so I went digging into the mypy source code. My understanding is as follows.

  1. There are five kinds of precision. Full details are visible in the --lineprecision-report.
  2. Two kinds of precision, EMPTY and UNANALYZED convey no information, because there's nothing to analyze.
  3. A line is marked as precise, imprecise or any based on the expressions it uses.
    • An expression that has type Any leads to precision ANY.
    • I think an expression that involves Any but is not Any counts as imprecise. For instance, Dict[str, Any].
    • Remaining expressions have precision PRECISE.
  4. A line's precision is the worst of all its expressions' precisions.
    • ANY is worse than IMPRECISE, which is worse than PRECISE.

The "imprecision" number reported by mypy counts the number of lines classified as IMPRECISE or ANY.

On balance, my preferred metric is the line-level (im)precision percentage. There wasn't much difference between the two in my experience, but the colour-coded visualisation in the HTML report is a neat feature to have. Maybe in the future there could be a version of the HTML report that colour-codes each expression?

The larger typing ecosystem

There are plenty of articles out there about the typing. As well as the mypy blog itself, see Daniele Varrazzo's post on psycopg3 (2020), Dropbox's blog post (2019), Zulip's blog post (2016), Glyph's blog post on Protocols (2020) and a follow-up comparing them to zope.interface (2021) and Nylas's blog (2019). I'm sure there's plenty more out there.

It's worth highlighting the typeshed project, which maintains stubs for the standard library, plus popular third-party libraries. I submitted a PR to add a single type hint—it was a very pleasant experience! Microsoft has an incubator of sorts for stubs too.

Other typecheckers

After the sprint to improve coverage, I spent a short amount of time trying the alternative type checkers out there. Mypy isn't the only typechecker out there—other companies have built and open-sourced their own tools, with different strengths, weaknesses and goals. This is by no means an authoritative, exhaustive survey—just my quick notes.

Pyre (Facebook)

  • I couldn't work out how to configure paths to resolve import errors; in the end, I wasn't able to process much of Sydent's source code.
  • Couldn't get it to process annotations like syd: "Sydent" where "Sydent" is an import guarded by TYPE_CHECKING.
  • No plugin system that I could see. That said, it has a separate mode/program for running "Taint Analysis" to spot security issues.
  • Seemed stricter by default compared to mypy: there was less inference of types.
  • Also has its own strict mode.

Pyright (Microsoft)

  • Didn't seem to recognise getLogger as being imported from logging. Not sure what happened there—maybe something wrong with its bundled version of typeshed?
  • In a few places, Sydent uses urllib.parse.quote but only imports urllib. We must be unintentionally relying on our dependencies to import urllib.parse somewhere! Mypy didn't complain about this; pyright did.
  • Seemed to give a better explanations of why complex types were incompatible. For example:
    /home/dmr/workspace/sydent/sydent/replication/pusher.py
       /home/dmr/workspace/sydent/sydent/replication/pusher.py:77:16 - error: Expression of type "DeferredList" cannot be assigned to return type "Deferred[List[Tuple[bool, None]]]"
         TypeVar "_DeferredResultT@Deferred" is contravariant
           TypeVar "_T@list" is invariant
             Tuple entry 2 is incorrect type
               Type "None" cannot be assigned to type "_DeferredResultT@_DeferredListResultItemT" (reportGeneralTypeIssues)
     /home/dmr/workspace/sydent/sydent/sms/openmarket.py
       /home/dmr/workspace/sydent/sydent/sms/openmarket.py:93:13 - error: Argument of type "dict[_KT@dict, list[bytes]]" cannot be assigned to parameter "rawHeaders" of type "Mapping[AnyStr@__init__, Sequence[AnyStr@__init__]] | None" in function "__init__"
         Type "dict[_KT@dict, list[bytes]]" cannot be assigned to type "Mapping[AnyStr@__init__, Sequence[AnyStr@__init__]] | None"
           TypeVar "_KT@Mapping" is invariant
             Type "_KT@dict" is incompatible with constrained type variable "AnyStr"
           Type cannot be assigned to type "None" (reportGeneralTypeIssues)
    
    This would have been really helpful when interpreting mypy's error reports; I'd love to see something like it in mypy. Here's another example where I tried running against a Synapse file.
    /home/dmr/workspace/synapse/synapse/storage/databases/main/cache.py
    /home/dmr/workspace/synapse/synapse/storage/databases/main/cache.py:103:53 - error: Expression of type "list[tuple[Unknown, Tuple[Unknown, ...]]]" cannot be assigned to declared type "List[Tuple[int, _CacheData]]"
      TypeVar "_T@list" is invariant
        Tuple entry 2 is incorrect type
          Tuple size mismatch; expected 3 but received indeterminate number (reportGeneralTypeIssues)
    
    This is really valuable information. It's worth considering Pyright as an option to get a second opinion!
  • It looks like Pyright's name for Any is Unknown. I think that does a better job of emphasising that Unknown won't be type checked. I'd certainly be more reluctant to type x: Unknown versus x: Any!
  • Pyright is the machinery behind Pylance, which drives VS Code's Python extension. That alone probably makes it worthy of more eyes.
  • Seemed like it was the best-placed alternative typechecker to challenge mypy (the de-facto standard).

Pytype (Google)

  • Google internal? Seems to be maintained by one person semiregularly by "syncing" from Google.
  • Apparently contains a script merge-pyi to annotate a source file given a stub file.
  • No support for TypedDict: as soon as it saw one in Sydent, it stopped all analysis.
  • No Python 3.10 support (according to the README anyway).
  • I think it might use a different kind of typing semantics; its typing FAQ speaks of "descriptive typing" and a more lenient approach.

PyCharm

PyCharm has its own means to typechecking code as you write it. It's definitely caught bugs before, and having the instant feedback as you type is really nice! I have seen it struggle with zope.interface and some uses of Generics though.

Runtime uses of annotations

When annotations were first introduced, they were a generic means to associate Python objects with parts of a program. (It was only later that the community agreed that we really want to use them to annotate types). These annotations are available at runtime in the __annotations__ attribute. There's also a helper function in typing which will help resolve forward references.

>>> from typing import get_type_hints
>>> def foo(x: int) -> str: pass
...
>>> get_type_hints(foo)
{'x': <class 'int'>, 'return': <class 'str'>}

Programs and libraries are free to use these annotations at runtime as they see fit. The most well-known examples are probably dataclasses, attrs with auto_attribs=True and Pydantic. I'd be interested to learn if anyone else is consuming annotations at runtime!

Summary

All in all, in a two-week sprint we were able to get Sydent's mypy coverage from a precision of 83% up to 94%. Our work would have spotted the bytes-versus-strings bug; we understand why the missing await wasn't detected. We fixed other small bugs too as part of the process. As well as fix bugs, I've hopefully made the source code clearer for future readers (but that one is hard to quantify).

There's room to spin out contributions upstream too. I submitted two PRs to twisted upstream; have started to work on annotations for pynacl in my spare time; and submitted a quick fix to typeshed.

Looking forward, I think we'd get a quick gain from ensuring that our smaller libraries (signedjson, canonicaljson) are annotated. We'll be sticking with mypy for now—the mypy-zope plugin is crucial given our reliance on twisted. We're also working to improve Sygnal and Synapse—though not to the extreme standard of --strict across everything.

I'd say the biggest outstanding hole is our processing of JSON objects. There's too much Dict[str, Any] flying around. The ideal for me would be to define dataclass or attr.s class C, and be able to deserialise a JSON object to C, including automatic (deep) type checking. Pydantic sounds really close to what we want, but I'm told it will by default gladly interpret the json string "42" as the Python integer 42, which isn't what we'd like. More investigation needed there. There are other avenues to explore too, like jsonschema-typed, typedload or attrs-strict.

To end, I'd like to add a few personal thoughts. Having types available in the source code is definitely A Good Thing. But there is a part of me that wonders if it might have been worth writing our projects in a language which incorporates types from day one. There are always trade-offs, of course: runtime performance, build times, iteration speed, ease of onboarding new contributors, ease of deployment, availability of libraries, ability to shoot yourself in the foot... the list goes on.

On a more upbeat note, adding typing is a great way to get familiar with new source code. It involves a mixture of reading, cross-referencing, deduction, analysis, all across a wide variety of files. It'd be a lot easier to type as you write from the get-go, but typing after the fact is still a worthy use of time and effort.


Many thanks for reading! If you've got any corrections, comments or queries, I'm available on Matrix at @dmrobertson:matrix.org.

On Matrix and the log4j vulnerabilities

15.12.2021 12:58 — Security Matrix Security Team

There is currently a lot of buzz and uncertainty around a number of vulnerabilities discovered in the log4j library in the Java ecosystem. These vulnerabilities are collectively known as "Log4Shell" and currently encompass CVE-2021-44228 and CVE-2021-45046.

First and foremost, there are to our knowledge no Matrix homeservers written in Java. Synapse, the canonical implementation developed by the Matrix Foundation and the implementation that is backing matrix.org, is written in Python and thus unaffected. P2P Matrix relies on Dendrite, our next-gen homeserver which is written in Go and is unaffected. Conduit, a community homeserver, is written in Rust and also unaffected. Supporting components like Sygnal and Sydent are written in Python and unaffected.

There are two components that are commonly used in the Matrix ecosystem that do rely on Java. These are Jitsi, specifically the Jitsi Videobridge for VoIP, and signald used by the Signal bridge. Both components pull in log4j as part of their (transitive) dependencies. We're not aware of other bridges that are dependent on Java-based components.

For both of these projects updates have been published that integrate log4j 2.15.0 covering the initial CVE and we're currently waiting for additional updates to be published that integrate log4j 2.16.0 to cover the second. In the meantime, we've put all mitigations we are aware of in place on our systems and we strongly recommend everyone do the same.

For what mitigations to put in place, we recommend following the recommendations provided by LunaSec. They also provide a lot of background information on the vulnerabilities and how to audit for them.

Please keep an eye out for releases from the Jitsi and signald projects and follow their upgrade instructions to update your own deployments as soon as possible.

Synapse 1.49.0 released

14.12.2021 00:00 — Releases Brendan Abolivier

Synapse 1.49.0 is out now!

Platform deprecations

Synapse 1.49.0 is the last version of Synapse to officially support Python 3.6 and PostgreSQL 9.6. This follows our platform dependency deprecation policy.

As a consequence of this, Synapse 1.49.0 is the last version of Synapse to support Ubuntu 18.04 LTS (Bionic Beaver), as it ships with Python 3.6.

On the topic of supported Ubuntu releases, please note that Ubuntu 21.04 (Hirsute Hippo) reaches its own end of life on January 20, 2022. Past this date we will stop producing new packages for Ubuntu 21.04.

Improved documentation

Up until now, a lot of very useful information was stored on the Synapse repo's wiki, which wasn't well advertised nor well reviewed.

With this release, we have migrated most of this information to Synapse's documentation website, so all the information you need to set up, maintain and troubleshoot a Synapse instance lives at the same place. Included in these new pages are the server admin FAQ and a guide to Synapse's Grafana dashboard.

The media repository documentation has also been updated with a lot of details about how Synapse stores media files.

Refresh tokens

When a Matrix client needs to authenticate a request to a homeserver, it uses what is called an access token. Sometimes server administrators might not want a user's access token to live forever (e.g. for security reasons). To address this concern, MSC2918 introduces the concept of refresh tokens to Matrix.

Initial support for refresh tokens in Synapse was introduced in version 1.38.0. Synapse 1.49.0 finalises and stabilises this implementation, allowing any client that supports this feature to use it as it is currently described in the related MSC.

Everything else

This release introduces the last changes needed to Synapse for basic threading support. It also introduces support for MSC3030, which allows clients to jump to a specific date in a room's history (expect a sneak peek of this in the next episode of Matrix Live!).

Another interesting point is the addition of a couple of admin APIs for federation. More specifically, they allow you to visualise all of the other homeservers your Synapse instance has been interacting with, as well as how successful the last attempts at communicating with them have been.

Please see the Synapse release notes for a complete list of changes in this release.

Synapse is a Free and Open Source Software project, and we'd like to extend our thanks to everyone who contributed to this release, including Dirk Klimpel, Maximilian Bosch and Tulir Asokan.

Till next year

This is the last release of Synapse of 2021! The Synapse team will take a break for the holidays, pushing the next release of Synapse (1.50.0) to January 11, 2022.

We'd like to thank everyone who has been using Synapse, contributing to it, and/or supporting us for the past year, and we hope to see you again in 2022! 🎆

Disclosure: buffer overflow in libolm and matrix-js-sdk

13.12.2021 18:35 — Security Matrix Security Team
Last update: 13.12.2021 16:11

Today we are releasing security updates to libolm, matrix-js-sdk, and several clients including Element Web / Desktop. Users are encouraged to upgrade as soon as possible. This resolves the pre-disclosure issued on December 3rd.

Fixed library versions are:

Client versions incorporating the fixes are:

These releases mitigate a buffer overflow in olm_session_describe, a libolm debugging function used by matrix-js-sdk in its end-to-end encryption (E2EE) implementation. If you rely on matrix-js-sdk for E2EE, you are affected. This vulnerability has been assigned CVE-2021-44538.

Clients which do not use matrix-js-sdk for E2EE, like FluffyChat or Element Android / iOS, are not affected.

This issue has been present since the introduction of the olm_session_describe function in October 2019 (commits: libolm, matrix-js-sdk).

We do not believe it is practical to successfully exploit this issue. However, upgrading remains important as the overflow can be triggered remotely.

Separately from the above vulnerability, we noticed during an internal audit that the libolm bindings in matrix-js-sdk were not zeroing out certain arrays containing entropy for cryptographic operations. This causes the entropy to remain resident in memory longer than necessary. As a defense-in-depth measure, this release of libolm now proactively overwrites those arrays when it is safe to do so.

Lastly, we are also taking this opportunity to update the version of Electron bundled with Element Desktop, pulling in the latest backported security fixes there.

The buffer overflow was found and reported by GitHub user @brevilo in the course of developing jOlm, a library of Java bindings to libolm; thank you. If you believe you've discovered a security vulnerability in Matrix or its implementations, please see our Security Disclosure Policy for how to get in touch.

This Week in Matrix 2021-12-10

10.12.2021 19:24 — This Week in Matrix Thib
Last update: 10.12.2021 19:09

The Adventures of TWIM bot

Last week the Matrix Scientists had turned this room into a portal to TWIM bot's ship tank so we could all fuel it with news about our work. The spec and hookshot news were reported late when the tank was already full, sending TWIM bot's ship into hyperspeed mode 🚀

We lost contact with the bot for a few days but managed to restore a connection with it. It appeared to have crashed on the green planet of Fuj'ehr. Its ship shattered in pieces at impact! 💥

The bot managed to find most of them, but some critical pieces of the engine were missing. We needed to find those pieces quickly before the mushy ground of Fuj'ehr swallows them forever! 😱

The Editor has asked the Matrix Scientists to reconfigure the room and bridge it to TWIM bot's navigation tools. Each news report highlighted the position of a piece of engine on its map. 🗺️

Matrix Live 🎙

This week my guests are the FluffyChat and MinesTRIX maintainers, following the v1 release of the simple and beautiful FluffyChat… and they want to work together!

Dept of Status of Matrix 🌡️

Austin Huang reports

Version numbers of each homeserver, updated daily using a GitHub Action, are now added onto my public homeserver list, in hopes that it will further aid those choosing a homeserver to use Matrix on.

Very handy to quickly know if a server is well maintained or not before making it your new home!

Dept of Spec 📜

anoa says

Here's your weekly spec update! The heart of Matrix is the specification - and this is modified by Matrix Spec Change (MSC) proposals. Learn more about how the process works at https://spec.matrix.org/unstable/proposals.

MSC Status

New MSCs:

MSCs with proposed Final Comment Period:

  • No MSCs entered proposed FCP state this week.

MSCs in Final Comment Period:

  • No MSCs are in FCP.

Merged MSCs:

Spec Updates

Extensible events are coming! With lots of potential usecases to be built on top of the concept (such as threading, polls, and any type of data one would like to through on top of existing room events), extensible events are finally getting some love. New MSCs are available above, which detail some of these usecases. Exciting times!

A small correction from last week's issue: the next aggregations MSC to be focused on (after MSC2675 (serverside aggregations) is MSC2677 (annotations and reactions), as it's a more pressing blocker for usecases such as threading and polls.

Otherwise, the Spec Core Team is continuing to wind down in preparation for the holidays ☃️

Random MSC of the Week

The random spec of the week is... MSC3015: Room state personal overrides.

Quite a novel concept, and one that would enable many usecases, such as the ones described in the MSC itself. Check it out if that's something that interests you!

Dept of Servers 🏢

Synapse

Synapse is the reference homeserver for Matrix

dmr reports

This week we cut Synapse release candidate 1.49.0rc1 It includes a bunch of work to supporta plethora of MSCs (MSC 2675, MSC3030, MSC2918, MSC2946), a crop of bugfixes and improvements to our documentation which incorporate Synapse's old wiki. And as ever, there's a bunch of internal type hinting to keep the Synapse team's blood pressure at healthy levels.

The formal release of Synapse 1.49.0 is scheduled for the coming Tuesday. This will be the last Synapse release of 2021 as the Synapse team prepare for a break over the Christmas period. Releases will continue at the usual pace in the new year, with 1.50.0rc1 slated for 2022/01/04 and 1.50.0 for 2022/01/11.

Please note: Synapse 1.49 will be the last version to support Python 3.6, PostrgreSQL 9.6, and Ubuntu 18.04 LTS (Bionic): by our next release, these will have reached their upstream end-of-life. If you're reliant on any of these platforms, please ensure you have plans to upgrade.

In other news, we're preparing to release a new version of Sygnal with a series of fixes for common errors. This should make Sygnal administrators much happier by removing an awful lot of error spam from logs!

Sydent

Sydent is the reference Matrix Identity server. It provides a lookup service, so that you can find a Matrix user via their email address or phone number (if they have chosen to share it).

dmr says

The second blog post on improving Sydent's type coverage should be published now. See the first post here from last week.

Homeserver Deployment 📥️

Helm Chart

Matrix Kubernetes applications packaged into helm charts

Ananace reports

This week has seen yet another set of updates to my Helm Charts, with element-web being bumped to 1.9.6 and matrix-synapse seeing fixes to non-standard port configurations and better support for modern ingressClass handling.

Dept of Clients 📱

Nheko

Desktop client for Matrix using Qt and C++17.

Nico reports

If you are using Nheko on a mobile device like the PinePhone, you should now be able to swipe between the room list and the spaces list. Since I don't use a PinePhone, feedback will be appreciated!

Neochat

A client for matrix, the decentralized communication protocol

Tobias Fella announces

NeoChat version 21.12 is out! You may have noticed that this version number is roughly twenty times higher than the previous one. This means that NeoChat is now twenty times as good as the last version. Or it means that version numbers are utterly meaningless and we switched to a date-based version number system since NeoChat is now released together with many other plasma-mobile related apps. This also means that new versions will arrive monthly from now on. New features and fixes in this version include - but are not limited to:

  • Spell checking while writing a message
  • Improved markdown to html conversion when sending a message
  • Built-in theme switching
  • Various fixes to login, logout and account switching
  • Support for custom emojis
  • Support for Spoilers
  • Support for Blurhashes

Element

Everything related to Element but not strictly bound to a client

Danielle Kirkwood announces

Threads

  • Threads is making excellent progress; This week we held 2 internal testing sessions, both of which went swimmingly.
  • We’re continuing our hard-work on Notifications to fix those up as best we can.
  • Also, we started work on the Threads Filter. The filter will allow you to choose between all the threads in a room and threads you’ve actively participated in.
  • If you’re using the Labs version of Threads, let us know what you think so far!

Polls

  • Exciting news on Polls; All development on Polls MVP is nearly complete, and will soon be making their way to a production environment near you!
  • Polls will be available behind Labs flags at first. We're excited to see people using it and we’re looking forward to hearing any feedback/comments.

Community Testing

  • Closed 20 out of 36 issues in encryption and verification (E2EE) this week.
  • We are planning three testing sessions for next week:
    • Tuesday 17:00-18:00 UTC - first time user experience on iOS, Android and Web
    • Wednesday: 16:00-17:30 UTC - information architecture changes on Web (with Michael and Nique joining us from the Delight team)
    • Thursday 16:30-18:00 UTC - bug squash edition on encryption, can we get the issue count to an all new low?
  • Join us! We’re at #element-community-testing:matrix.org

Element Web/Desktop

Secure and independent communication, connected via Matrix. Come talk with us in #element-web:matrix.org!

Danielle Kirkwood announces

  • We are monitoring and triaging feedback, which is submitted through the new feedback interface in the app.
  • In Labs:
    • Work continues on Information Architecture: this week we’ve made a spotlight search labs feature that we’ll be testing to replace the current filter.
    • We’re also starting to test preferences per space, so keep your eyes peeled for those.

madlittlemods (Eric Eastwood) reports:

Jump to date headers soon in Element

From the experimental MSC3030 implementation merge to Synapse update in TWIM last week, we now also have the start of some client usage in Element to make featureful jump to date headers!

If you've ever tried to find a message back in the past, you've experienced the burdensome task of having to scroll back manually for days, even months! With the jump to date headers, that will be a thing of the past 😌. Clicking any date separator in the room timeline, will give shortcuts to jump to last week, last month, jump to any date using the date picker, or even the beginning of the room to follow a room upgrade chain.

This is currently still in a draft pull request state but will give another update when it lands in Element Labs for everyone to use.

Element iOS

Secure and independent communication for iOS, connected via Matrix. Come talk with us in #element-ios:matrix.org!

Danielle Kirkwood reports

  • On iOS it’s been a week of completing things!
  • We’ve made some final changes to PostHog analytics and MatrixKit has been integrated in element-ios.
  • There have also been lots of bug fixes - especially around an app crash.
  • Don’t forget! As per our update from last week, our release candidates are now prepared on Tuesdays (not Wednesdays).

Element Android

Secure and independent communication for Android, connected via Matrix. Come talk with us in #element-android:matrix.org!

Danielle Kirkwood reports

  • Element Android 1.3.9 has been released on the PlayStore and is available for the beta testers:
    • This version adds support for draft voice messages and a new design for URL previews.
  • Opt-in PostHog analytics will land soon, and will be included in the next release.
  • A new "Legals” screen has been added to Settings in order for users to see all legal info pages for Element, the user's homeserver and the user's identity server (if any).
  • The next release candidate will be prepared next Tuesday.

Commune

Commune is a communications suite built on top of matrix. Commune aims to bring together chat, discussions, email and other interactive apps into a single matrix client.

erlend_sh says

So here’s the thing ahq (dev) and I (product) have been working on: https://github.com/commune-org/commune It’s a chat/forum hybrid. Still in pre-alpha, proof-of-concept stage.

Dept of Non Clients 🎛️

matrix-streamchat

Matrix powered stream overlay for OBS, to integrate live chat in your favorite (selfhosted) streaming setups.

f0x announces

TWIM I wrote a Matrix powered stream overlay for OBS, to integrate live chat in your favorite (selfhosted) streaming setups. Was a great little 2 evening project to develop while livestreaming it's development :)

You can find the code and instructions at https://git.pixie.town/f0x/matrix-streamchat and a hosted instance at https://streamchat.pixie.town

Dept of SDKs and Frameworks 🧰

jOlm

Olm bindings for Java

brevilo announces

jOlm has seen two releases since the previous update, v1.0.7 and v1.0.8. jOlm now supports (and requires at least) the latest libolm version 3.2.7. Please note that we deprecated a number of methods in favor of renamed siblings. The majority of the old ones will be removed in the upcoming jOlm 1.1 release, likely published soon after libolm's announced security release on Dec. 13th. Please follow suit and update your implementations accordingly.

Summary:

  • 🧰 Maintenance and upstream update releases
  • ⚠️ Renamed methods for improved coherence and due to upstream changes (old ones deprecated)
  • ✅ Up to date with Olm 3.2.7 (new minimum requirement)

Changelog:

  • Deprecated methods (see the individual release notes for full details):
    • Account.markOneTimeKeysAsPublished()
    • Account.fallbackKey()
    • InboundGroupSession.export()
    • InboundGroupSession.importer()
    • Utility.ed25519_verify()
  • Refined unit tests
  • Updated dependencies

Cheers!

Dept of Videos 📹

Matthew reports

We previewed yet more native Matrix VoIP conferencing at CommCon: https://www.youtube.com/watch?v=A4k7DVIK5TE&list=PLvNS4EBAxmJJbvGW-PfXdXOSy9AjHjCLV

Half-Shot says

If you've got room for more video content, I also said more things about bridges. In this one, we do a live code session for a twilio bridge and watch it fly! https://www.youtube.com/watch?v=S5q3FLLvRn4

Dept of Interesting Projects 🛰️

Henri says

Wily Messenger Matrix client

Wily has launched an iOS Matrix client to enable messaging in restricted- or poor networks. mText and Room events are transferred as DNS payload, thus bypassing most captive portals, while message headers are minimized to enable messaging in very low bandwidth/high latency networks.

Wily Messenger is in POC stage, missing i.e. encryption at the moment, among others. We are committed to develop it further and invite a Kotlin developer to join our journey. DM @hp:hq.wily.im

Download: https://apps.apple.com/lv/app/id1576476396

Room of the Week 📆

TravisR says

We've set up a new Element Space for the Element family of clients and projects, finally. Feel free to join it at #community:element.io and be sure to check out #community:matrix.org while you're there for everything Matrix related.

There's also #element-translators:matrix.org for the Element Translators community out there.

Dept of Ping 🏓

Here we reveal, rank, and applaud the homeservers with the lowest ping, as measured by pingbot, a maubot that you can host on your own server.

#ping:maunium.net

Join #ping:maunium.net to experience the fun live, and to find out how to add YOUR server to the game.

RankHostnameMedian MS
1envs.net494.5
2nicoll.xyz548
3matrix.markshorten.co.uk857.5
4helderferreira.io1373
5almum.de1886.5
6diasp.in2958.5
7matrix.liamgooch.com2982
8mailstation.de3030.5
9kde.org3234
10matrix.org3415

#ping-no-synapse:maunium.net

Join #ping-no-synapse:maunium.net to experience the fun live, and to find out how to add YOUR server to the game.

RankHostnameMedian MS
1envs.net138.5
2construct.supercable.onl165
3weasy-is-my.name317
4grin.hu403
5dendrite.supercable.onl531
6spacedn.com534.5
7matrix.org828
8matrix.awesomesheep48.me1084
9dendrite.s3cr3t.me1103.5
100x1a8510f2.space1332

The Adventures of TWIM bot continued

The members of the Federation have been very active and helped TWIM bot to find all the pieces of its ship! TWIM bot assembled everything together and put the engine back in its place... but at the last moment, as it was ready to take off, another bright spot appeared on the map following madlittlemods late report!

How could this happen? All the pieces of the engine were already there! Unsure of what to do, the bot asked Earth for directions. Earth confirmed: it was worth going to that new bright spot on the map.

The bot welded back the plate of the engine casing, made sure nothing could get into the ship in its absence, and started heading to the mysterious spot. The signal of our communication tools with TWIM bot weakens as it enters in the thick forest of Fuj'ehr…

That's all I know 🏁

See you next week, and be sure to stop by #twim:matrix.org with your updates!

Type coverage for Sydent: annotation

10.12.2021 00:00 — Tech David Robertson

This is the second in a series of three posts which discuss recent work to improve type annotations in Sydent, the reference Matrix Identity server. Last time we discussed the motivation for doing this work in the first place: the why. Now I want to talk about the how. How did we add annotations to individual files, and across the project as whole? What common idioms did we learn on the way?

The process of improving coverage

From experience adding typing to Synapse, we decided to annotate one module at a time. We configured a list of files in pyproject.toml which we knew passed mypy's checks. We could run mypy in CI to check that new PRs didn't introduce type problems in modules already covered.

From there, the workflow was

  1. Choose a new module to annotate. Add it to the list of files in mypy's configuration.
  2. Run mypy. See how many errors you get.
  3. Choose an error. Fix it. Re-run mypy.
  4. Repeat until no errors remaining.
  5. Submit for review.

There are two parts in there which involve a choice. Being honest, I made those choices unscientifically: I tried to choose the easy tasks to do first. My first target was actually the entire sydent.util subpackage. Probably a bit too large for a first bite! My thinking was that util sounded like something with few dependencies that would have impact across the whole source tree.

Within a given module, I'd try to fix easier errors first: partly for confidence, partly to build up momentum, and partly to get myself familiar with that piece of source code. For example, I'd often start by telling mypy that mylist = [] was actually was a List[str] (rather than the generic List[Any] which it would use otherwise).

Picking and choosing easy targets works fairly well, but sometimes that means you end up fixing an error that's really a symptom of an earlier one. Other times fixing one error, e.g. by giving a return type annotation to a function—would solve a series of other errors throughout the file. Watching the total number of errors mypy reports bob up and down was intriguing!

In retrospect, I think it would be smoother to generate some kind of dependency graph for the package. I'm imagining a DAG where whose vertices are modules, and there's an edge A -> B if A imports from B. The sinks of this DAG (i.e. modules which don't depend on any others in the package) are the ideal place to start: you can get something strictly typechecked there without having to annotate a long chain of dependencies across other files. Another strategy would be to see which modules were the least precise according to mypy's reports—but more on those next time.

You're at the mercy of your dependencies

I think this is my single biggest takeaway from the process of adding annotations to Sydent. I'll admit the phrasing is melodramatic, but I think it rings true.

Improving coverage boils down to giving the typechecker more information about your program. The more information it has, the more it can check—and the more errors it can spot. (Hopefully this doesn't make typing come across like a pyramid scheme.) If your dependencies aren't typed, mypy can't validate you're correctly providing inputs and correctly consuming outputs. You might have a bigger impact on overall typing coverage by annotating a dependency (directly or via stubs). I have a hunch that bugs are more likely in code that uses an external dependency: we're much more familiar with the details of our own source code compared to that of a third party we trust.

It's worth looking to see if your dependencies have a newer version including type annotations. Failing that, they may have a stub package added to typeshed and published on PyPI. I saw example of both cases when choosing how to configure mypy for Sydent. If some of your dependencies are under your control, consider annotating them—you'll feel the benefits across multiple projects pretty quickly.

Annotations when working with twisted

Twisted is Sydent's biggest dependency, and I certainly felt at its mercy! In particular, it has a few quirks which make it trickier to annotate applications using it. Here's a summary of the work we had to do to get those annotations working.

Partially typed modules and stubs

Early into the process, mypy reported that calling the function twisted.python.log.err was an error. It did so because I was running mypy in --strict mode. We'll talk more about why I did so and what this means next time; for now, it's enough to know that calling a function that isn't fully annotated from within a function that is constitutes an error under strict mode. twisted is partially annotated: many key modules and functions have type annotations, but others don't. I was reluctant to give up on --strict. Instead, I decided to stub the err function myself.

A stub is a cut-down version of a python function, class or module which lives in a .pyi file. All implementation details are removed; only type annotations remain. Stubs are useful when you want to write annotations for code you don't control. The typeshed library is probably the best example: a collection of third party stubs for the standard library, plus some popular third party packages. Microsoft's python-type-stubs is another example. They'd also solve the problem I mentioned at the end of the first part: I could use a stub to teach mypy that IResponse.headers was a Headers object.

Writing a stub for log.err was straightforward, thanks mainly to Twisted's thorough documentation. I chose to write it by hand, rather than use stubgen. I'd heard of the latter, but was reluctant to use it for a few reasons.

  • Twisted is a big project with many big files. I didn't want to commit huge stub files for me and my colleagues to maintain.
  • The more our stubs cover, the larger the risk of a stub becoming out-of-date with twisted itself. Upstream twisted is the best place for these annotations.
  • We only need annotations for the bits of twisted that we're using.
  • And, being honest: I wanted the low-level experience of writing stubs, cross-referencing between the source and working how to best capture its type semantics.

I think this decision to write a targeted stub made sense at the time. After all, twisted.python.logging.err is just one simple function! But for the project as a whole, I regret not using stubgen to generate module-level stubs. The reason for this is that a .pyi stub file accounts for an entire module: no more and no less. This means that the stubs I was writing for parts of twisted only covered the functions and classes I'd stubbed. Any existing annotations in the twisted source code would be ignored, along with any types that mypy was able to infer for itself.

I think it would have been more efficient to use stubgen to generate stubs and patch them up, rather than writing them. That would have helped avoid a few cases I encountered where stubbing one function would cause additional typechecking failures (because mypy was no longer examining the twisted source for that file). It would also have meant that I could just faithfully stub Twisted as it is; in practice, I would sometimes hesitate to stub to avoid having to cover another module. sydent.http was the most painful part of the source tree for this: that's where we make the most use of Twisted.

defer.inlineCallbacks

This is a decorator which allows us to write code in the style of async/await without actually using that syntax. (Twisted predates asyncio and the async/await syntax, introduced in Python 3.4 and 3.5 respectively. It was originally released in 2002, back when Python had released version 2.2.) We only use it in one place in Sydent nowadays, but I've seen used across Synapse too. I mention it here because it was a bit fiddly to annotate. Here's its use in Sydent:

    @defer.inlineCallbacks
    def request(
        self,
        method: bytes,
        uri: bytes,
        headers: Optional["Headers"] = None,
        bodyProducer: Optional["IBodyProducer"] = None,
    ) -> Generator["defer.Deferred[Any]", Any, IResponse]:

Here, request is a generator function because its body uses the yield keyword. Yielding allows the function to relinquish control back to twisted's reactor, only for its execution to be resumed asynchronously in the future. The Generator type takes three parameters:

  • a YieldType, Deferred[Any];
  • a SendType, Any; and
  • a ReturnType, IResponse.

Why have I opted to use Any here, when we've seen (and will see) that this limits mypy's ability to run checks? The answer is that we yield two different types within the function. Firstly a routing result:

        routing: _RoutingResult
        routing = yield defer.ensureDeferred(self._route_matrix_uri(parsed_uri))

and later, the IResponse we go on to return:

        res: IResponse
        res = yield agent.request(method, uri, headers, bodyProducer)
  • In this example:

  • We yield a value y: Deferred[Any].

  • That will later be resolved by twisted to an x: Any value. Twisted will send that value to request, and the execution continues.

  • This repeats, until we eventually return an IResponse.

I could more correctly annotate the YieldType as Deferred[Union[_RoutingResult, IResponse]] so that the SendType was Union[_RoutingResult, IResponse]. But this would mean we end up having some kind of type check at each yield point: a cast, or a type: ignore, or a runtime isinstance check. It didn't feel like it was worth the boilerplate, especially since I could narrow e.g. res: Any to res: IResponse with minimal effort.

This problem doesn't arise with using the async/await syntax:

async def foo() -> int:
    return 1

async def bar() -> None:
    x = await foo()
    reveal_type(foo())
    reveal_type(x)
$ mypy example.py
example.py:6: note: Revealed type is "typing.Coroutine[Any, Any, builtins.int]"
example.py:7: note: Revealed type is "builtins.int*"

Behind the scenes, I think that x = await foo() is really using the same mechanism as the inlineCallbacks approach.

  • An async def function is really a generator function behind the scenes.
  • When we await foo(), we yield the expression foo()
  • Then the machinery running our coroutine c will call c.send(x) to resume execution, where x is the value produced by waiting for foo().

With the await form, mypy knows two things:

  • the value foo() which was yielded should be Awaitable[T], and
  • the value x send to the coroutine should come from awaiting foo(), and therefore be of type T.

Mypy can't assume or enforce these rules for the yield form, which can yield and send whatever it likes. There's no reason why the send type should be related to the yield type. Here's a toy example:

from typing import Any, Dict, Generator


def generator_function() -> Generator[int, str, Dict[str, Any]]:
  y: int = 10
  x: str = yield y
  print("Coroutine was sent", x)  # -> Coroutine was sent hello
  return {"got": x, "done": True}

coroutine = generator_function()
y = next(coroutine)

try:
  coroutine.send("hello")
except StopIteration as e:
  return_value = e.value
  print(return_value)  # -> {'got': 'hello', 'done': True}

All in all, the handling of inlineCallbacks is a situation specific to working with (older?) twisted code. It's still nice to understand what's going on behind the scenes though!

zope.interface.Interface

Twisted makes use of zope's Interface to define a number of abstract interface classes. Speaking personally, I've not seen it used outside twisted, and I think that means it's not supported by much of the typechecking tooling. For example, I've definitely seen PyCharm struggle to realise that it's okay to pass a Response to a function which expects an IResponse! Here's a more complicated example where PyCharm isn't happy with me widening the type LoggingHostnameEndpoint to IStreamClientEndpoint, even though the latter implements the former.

Screenshot from pycharm showing a false positive warning

Mypy out of the box doesn't play well with a zope Interface (nor does any other typechecker I tried). Fortunately, the excellent mypy-zope plugin helps here: it teaches mypy that any class like Response which @implements(IResponse) can be passed in place of an IResponse.

Tricks of the trade

At this point I'd like to share a few generic lessons about typing I'd picked up. Nothing ground-breaking here: I think these are all fairly well-known. Hopefully they're the start of a good cheat sheet for annotating—though it pales in comparison to the Mypy cheat sheet.

Annotating decorators

Annotating decorators is fiddly, but it's also vitally important. An unannotated decorator will mask or throw away your decoratee's annotations! The way to write the annotation is best explained by the mypy docs, but briefly: it involves a TypeVar and sometimes a cast too. Here's an example.

from typing import TypeVar, Callable, Any, cast

F = TypeVar("F", bound=Callable[..., Any])

def decorator(input: F) -> F:
    def wrapped(*args: Any, **kwargs: Any) -> Any:
        print(f"Calling {f.__name__}")
        return input_func(*args, **kwargs)
    return cast(F, wrapped)

The idea here is

  1. Use Callable[..., Any] to describe a generic function with no particular signature.
  2. Use that as a bound on a type variable F. At each usage of @decorator, mypy will deduce a more specific version of F, e.g. Callable[[int], str].
  3. Within that usage of @decorator, F is fixed to that specific type. We use -> F to express that "we return a function with the same signature as the decoratee".
  4. Unfortunately, we don't have a good way to tell mypy that wrapped also has that signature F. We resort to a cast to force mypy to accept this without proof.

This might change in the future, e.g. when ParamSpec is fully understood by mypy.

Prefer object over Any

Both of these are general types for expressing "I don't know anything about this expression". But only the former will undergo static type checks. We want those type checks to guard against bugs accidentally introduced in the future. For instance, imagine a class with an Any attribute.

from typing import Any
import dataclasses

@dataclasses.dataclass
class C:
    label: Any

Imagine in the future we add a new method which assumes that label is a string:

    def greeting(self) -> str:
        return "My name is " + self.label

Mypy will consider this valid, because no type-checking is done on an Any value. It will complain that it can't prove that greeting returns a str, if --warn-return-any is enabled; but putting that aside, it can't identify the call site as a bug. The bug slips through to runtime.

C(123).greeting()  # TypeError: can only concatenate str (not "int") to str

Replacing the Any with object does allow mypy to spot the problem.

error: Unsupported operand types for + ("str" and "object")  [operator]

# type: ignore and cast sparingly

There's a good chance that mypy knows better than we do, so we should only overrule it if there's no better option. There are two techniques for this. One option is to tell mypy to just silence the error, by appending a # type: ignore comment to the erroneous line. The other is to force it to accept that a certain expression has a given type: that's what cast is for.

I never like using either of these, but sometimes they're the most practical choice. I'd recommend two best practices for their use, however:

  1. Only ignore a specific error code, e.g. # type: ignore[assignment]. Those codes can be displayed by passing --show-error-codes to mypy.
  2. Leave a comment before every #type: ignore[...] and every cast to justify their correctness. I don't think this is a widely-held practice. I picked it up from the Rust world, where it's encouraged as a way to justify unsafe source code.

There's good stuff in typing

It's worth a read through the module documentation—plenty of things in there I wish I'd known about sooner. There's lots in the toolkit to chose from. Some bits I use fairly often include:

  • Protocol: lets you formalise duck typing. To use it, define a class that inherits from Protocol. Its methods and attributes are all stubs which describe what you require of objects belonging to this type. It's like an abstract base class or interface, but purely at typecheck time.
  • Generic: define your own generic types. A bit of a slippery slope to type mania!
  • [Optional](https://docs.python.org/3/library/typing.html?highlight=typing%20generic#typing.Optional): I use this all the time. It's always good to make your None`s explicit!
  • NewType: I haven't had a chance to use it much. As I understand it, it's a way to define a "strong typedef". For example, we can use it to distinguish lengths from durations, even if they're both represented by a float at runtime.

I want to call out two parts of typing in particular:

overload

overload is a way to provide extra information about a function depending on how it's called. For instance, consider this function which takes a str or bytes as input and returns its uppercase version.

def upper(x: Union[bytes, str]) -> Union[bytes, str]:
    return x.upper()

The problem with this annotation is that we'll have to check at every call site to see if the return value was a bytes or a str object. But we know that uppercasing a str gives us a str, and uppercasing a bytes gives us a bytes. We can use overload to express this.

from typing import overload

@overload
def upper(x: str) -> str: ...

@overload
def upper(x: bytes) -> bytes: ...

def upper(x: Union[bytes, str]) -> Union[bytes, str]:
    return x.upper()

The first two @overload definitions are like stubs: they're purely used for their annotations. The actual runtime implementation is specified at the end, undecorated. (NB: this specific example is better expressed without overloads, by using AnyStr.)

I was really impressed to see how mypy could use this overloading information. Here's an example:

from typing import overload, Literal

@overload
def f(x: int) -> Literal[1]: ...

@overload
def f(x: str) -> Literal[2]: ...

def f(x: object) -> int:
    if isinstance(x, int):
        return 1
    elif isinstance(x, str):
        return 2
    return 3

if f(10) == 2:
    print("potato")

We can see that f(10) == 1 and so the equality is always False: we'll never print the word "potato". Mypy can reason through this too, if we ask it nicely.

$ mypy example.py
Success: no issues found in 1 source file

$ mypy --strict-equality --warn-unreachable example.py
example.py:16: error: Non-overlapping equality check (left operand type: "Literal[1]", right operand type: "Literal[2]")
example.py:17: error: Statement is unreachable
Found 2 errors in 1 file (checked 1 source file)

TypedDict

I mentioned this in last week's post) when talking about the missing await bug. Subclassing from TypedDict allows us to define a new type whose values are dictionaries with

  • a fixed set of keys (some mandatory, some not), and
  • a fixed type for each key's value.

PEP 589 can best describe the motivation, but in short: there's a lot of source code out there that passes dictionaries around. TypedDict is a way to gradually add typechecking to that code, without having to refactor it to use e.g. a dataclass or a NamedTuple. Let's have a quick example.

from typing import List, TypedDict

class Person(TypedDict):
    full_name: str
    nicknames: List[str]
    age: int

p: Person = {
    "full_name": "David Matthew Robertson",
    "nicknames": ["dmr"],
    "age": 29,
}

Mypy will detect if you delete or omit a required key, or if you insert a key that's not part of the type.

# error: Key "age" of TypedDict "Person" cannot be deleted
del p["age"]

# error: Missing keys ("full_name", "nicknames", "age") for TypedDict "Person"
p2: Person = {}

# error: TypedDict "Person" has no key "eye_colour"
p["eye_colour"] = "brown"

TypedDict is a useful tool, but there are a few important gotchas to be aware of. In my view, these are all minor and worth putting up with, to benefit from the extra type information that a TypedDict provides. Let's take a look.

TypedDict is incompatible with Dict

This one surprised me at first. Why can't I pass my TypedDict to a function that accepts a generic dictionary?

import json
from typing import Dict, TypedDict

class Foo(TypedDict):
    bar: str

def print_size(d: Dict[object, object]) -> None:
    print(len(d))

f: Foo = {"bar": "baz"}
# error: Argument 1 to "print_size" has incompatible type "Foo"; expected "Dict[object, object]"
print_size(f)

The answer is that it wouldn't be type-safe! For all we know, the function print_size might mutate its argument d. It might add a key, remove a key, or change the type of a value. Each of those would break the contract that TypedDict is supposed to enforce, so mypy is correct to flag this as an error. This GitHub issue has more discussion.

There are few workarounds for this kind of problem.

  • The function might be able to accept a TypedDict directly.
  • The function could accept a Mapping instead of a Dict. This is type-safe because the Mapping type does not offer mutating methods such as pop, __setitem__, __del__; but it only works if the function does not mutate the dictionary.
  • A more drastic approach would discard the TypedDict information with a cast to Dict[str, object] or similar. If so, I'd strongly recommend a comment explaining why the cast is correct and safe.
Mixing mandatory and required fields

Secondly, defining a TypedDict with a mixture of optional and required keys is a little fiddly. All keys can be made optional by passing total=False in the class definition. To have some keys optional and others mandatory, we have to make two TypedDicts. Say for instance we wanted to allow a potentially-missing favourite_colour field to Person. We can't add an annotation favourite_colour: Optional[str] to the first class body. That's optional in a different sense: it would mean that favourite_colour is a mandatory field which is allowed to be None. Instead, we apply total=False to a subclass:

from typing import List, TypedDict

class _PersonRequired(TypedDict):
    full_name: str
    nicknames: List[str]
    age: int

class Person(_PersonRequired, total=False):
    favourite_colour: str

This means that a valid Person dictionary may have no favourite_colour key. If it is present, it must be a str.

The syntax is unfortunately a little clunky. PEP 655 acknowledges this and proposes an alternative.

TypedDict is typecheck-time only

One final note: a TypedDict does no validation or conversion whatsoever. If mypy can't know the keys and values a dictionary will have a typecheck-time, you'll need to validate it by hand at runtime. We see this a lot because when deserialising json objects into a dictionary. Here's an example.

import json
from typing import TypedDict

class Foo(TypedDict):
    bar: str

# note: Revealed type is "Any"
reveal_type(json.loads("{}"))
# no error. mypy can't do analysis on Any.
f: Foo = json.loads("{}")

Next time

This covers a lot of the machinery and the day-to-day process of annotating Sydent. The last part of this series will give us a chance to quantify our efforts and reflect on the wider typing ecosystem.


Many thanks for reading! If you've got any corrections, comments or queries, I'm available on Matrix at @dmrobertson:matrix.org.

Pre-disclosure: upcoming security release of libolm and matrix-js-sdk

03.12.2021 00:00 — Security Matrix Security Team

On Monday, 13th December we plan to publish a security release of libolm at 15:00 UTC to address a single high severity issue. To the best of our knowledge, only matrix-js-sdk and clients relying on it for E2EE are affected by this issue. This includes Element Web/Desktop and their forks (like SchildiChat). The release of libolm will be immediately followed by a security release of matrix-js-sdk and the affected clients. Users of these clients are encouraged to upgrade as soon as the patched versions are released.

We will be reaching out to downstream packagers to ensure they can prepare patched versions of the affected packages at the time of the release. The details of the vulnerability will be disclosed in a blog post on the day of the release. There is so far no evidence of the vulnerability being exploited in the wild.

The patched version numbers will be as follows:

  • libolm 3.2.8
  • matrix-js-sdk 15.2.1
  • Element Web/Desktop 1.9.7

Thank you for your patience while we work to resolve this issue.

Edit, 2021-12-13: Added patched release numbers.

This Week in Matrix 2021-12-03

03.12.2021 00:00 — This Week in Matrix Thib

The Adventures of TWIM bot

One thing you might not know is that TWIM bot is a space traveler, sent by the Matrix scientists to explore that zone called "The Possibilities". The #twim:matrix.org room is a portal to its energy tank, and we had received a distress signal!

To help the TWIM explorer fulfil its mission, we asked the Matrix community to fuel it with news before it crashed into space debris made of aggregated ignorance!

This week again, the community has been very active and explored many possibilities of the Matrix universe!

Matrix Live 🎙

For this week's Matrix Live my guest is Amandine and we're discussing how Element and 50 other organisations are trying to shape the future of EU's law for more interoperability. Bonus point: we have a double bridge demo with Matrix, Slack and Telegram!

Dept of Status of Matrix 🌡️

FOSDEM!

This year, the Matrix.org Foundation is excited to host the first ever Matrix.org Foundation and Community devroom at FOSDEM. A full day of talks, demos and workshops around Matrix itself and projects built on top of Matrix. Read (and answer to) our Call for Partipactions!

Finnish Admins to the Rescue

cos says

A group of Finnish Matrix admins have set up a free homeserver for Finnish public called pikaviestin.fi (literally instant messenger dot fi). It offers a bunch of bridges and registration requires an e-mail address in one of Finnish e-mail providers or organizations. We welcome all Finns to register there and help decentralize Matrix. Support room can be found at #aula:pikaviestin.fi

That's a fantastic initiative! Kudos to all the sysadmins involved!

Dept of Spec 📜

anoa reports

Here's your weekly spec update! The heart of Matrix is the specification - and this is modified by Matrix Spec Change (MSC) proposals. Learn more about how the process works at https://spec.matrix.org/unstable/proposals.

MSC Status

New MSCs:

MSCs with proposed Final Comment Period:

  • No MSCs entered proposed FCP state this week.

MSCs in Final Comment Period:

Merged MSCs:

  • No MSCs were merged this week.

Spec Updates

The end of the year is drawing to a close. Thus many of the Spec Core Team members are focusing on implementation in order to meet deadlines. Review is still occurring though! As above, we have MSC3419 (allow guests to send more event types). This was born out of next-generation VoIP work, but it should have a positive impact on improving the guest experience in Matrix on the whole.

Otherwise work is still ongoing by Bruno and others on untangling the aggregations MSCs, specifically MSC2675 and MSC2676.

And finally, Alexandre Franke has PR'd some work to allow for matrix.org's OpenAPI spec to be widely available, meaning anyone with a Swagger (or other OpenAPI viewer) client can easily pull it and start sending requests against a Matrix homeserver. Fun times!

Random MSC of the Week

The random spec of the week is... MSC3419: Allow guests to send more event types.

Random numbers, ladies and gentleman.

Dept of Servers 🏢

Synapse

Synapse is the reference homeserver for Matrix

callahad says

Goooood evening TWIM readers!

I want to start by drawing attention to a blog post which we published today: Type coverage for Sydent: motivation. This the first in a series of three articles discussing what we've learned from making Sydent pass the mypy type checker in strict mode. Improving type coverage across Synapse, Sygnal, and Sydent has been a major focus of the backend team at Element for the past few months, and we think we've learned a few useful things in the process.

This week we also released Synapse 1.48 with loads of internal improvements, new Admin APIs, better alignment with the Matrix 1.1 spec, and more. We're planning one more release for the year, 1.49 on December 14th, and then we're taking a break until Synapse 1.50 on January 11th.

Importantly: Synapse 1.49 will be the last release to support Python 3.6, PostrgreSQL 9.6, and Ubuntu 18.04 LTS (Bionic) — if you're reliant on any of these platforms, please ensure you have plans to upgrade.

Let us know what you think of the article (and the Synapse release!), and we'll see you next week!

Sydent

Sydent is the reference Matrix Identity server. It provides a lookup service, so that you can find a Matrix user via their email address or phone number (if they have chosen to share it).

dmr reports

I've just published a blog post (part one of three) about our efforts to improve Sydent's type coverage. It should hopefully be of interest to anyone who works with Python or is interested in static analysis more generally.

Gitter

madlittlemods (Eric Eastwood) reports

In the vein of Gitter feature parity on Matrix, we've made the first steps towards a better public static archive. We merged an experimental implementation of MSC3030 into Synapse which lets you use the unstable /timestamp_to_event client API endpoint go from a given timestamp to the closest event ID. This will allow us to implement a calendar jump to date interface to be able to navigate to any day in the rooms history. Our first target to add the jump to date UI in is Hydrogen since we plan to server-side render Hydrogen for the actual public static archive as well.

To enable the MSC3030 unstable API endpoints in Synapse, add experimental_features -> msc3030_enabled: true to your homeserver.yaml:

GET /_matrix/client/unstable/org.matrix.msc3030/rooms/<roomID>/timestamp_to_event?ts=<timestamp>&dir=<direction>
{
    "event_id": ...
    "origin_server_ts": ...
}

Also as part of MSC3030, when you use the client API endpoint, if your homeserver sees that the closest event it has locally in the database is next to a gap in the history, it will go out and ask other federated homeservers what they have as the closest event instead.

GET /_matrix/federation/unstable/org.matrix.msc3030/timestamp_to_event/<roomID>?ts=<timestamp>&dir=<direction>
{
    "event_id": ...
    "origin_server_ts": ...
}

*--

MSC2716 to import batches of historical messages is still marching along getting some polishing passes and strengthening the assertions in the Complement tests to make sure things are going absolutely correctly. It's also good to see Beeper utilizing it and catching a few bugs along the way 💪.

Dept of Bridges 🌉

Hookshot

Half-Shot reports very late, to the great despair of TWIM's editor:

Hookshot gets provisioning!

Stop the press. This is a last minute TWIM. We've been beavering away on matrix-hookshot. It's gained many features in the last week, but the big thing is that hookshot has gained the ability to provision connections over a provisioning API, which means it should hook nicely into Dimension (and other integration managers, in the future)!

Other notable features are:

  • Support for multiple webhooks per room
  • Support for the username/text fields on an incoming webhook (slack style)
  • Named webhooks, so each hook now has a sensible displayname
  • The ability to spawn GitHub actions from rooms using the !gh workflow run command
  • Lots of new supported events from GitLab, such as reviews and tag pushes
  • Hosted documentation (so all of the above is easy to setup), it's a bit in progress atm.

We're aiming for a release very very soon, hopefully in the next week or so!

Homeserver Deployment 📥️

Helm Chart

Matrix Kubernetes applications packaged into helm charts

Ananace says

And this week, as a complete and utter surprise, my Helm Charts got updates; with matrix-synapse updated to 1.48.0

Dept of Clients 📱

SchildiChat

SchildiChat is a fork of Element that focuses on UI changes such as message bubbles and a unified chat list for both direct messages and groups, which is a more familiar approach to users of other popular instant messengers.

qg announces

In a new release being published just now we added the possibility to mark rooms as unread also on Web/Desktop (using MSC2867, huge thanks to @alangecker for his PR on Element Web!). This has already been implemented in SchildiChat-Android and is now enabled on both by default.

Nheko

Desktop client for Matrix using Qt and C++17.

Nico says

We finally figured out what caused the issues with the flatpak on GNOME, especially on Arm. It should now work properly, if you use Flathub. On the Pinephone (and other systems, that don't set a locale/use the C locale), timestamps should now not be needlessly long anymore. Redactions got a face-lift to distinguish them better from normal messages. We added a workaround for Synapse not allowing you to leave a banned room. We now delete the room from the room list permanently if Synapse returns "unknown room" when trying to leave it. Spaces can now show the entire hierarchy in the sidebar (if you pull it out) and you can navigate to subspaces by clicking on them in the roomlist, even if you collapsed the space hierarchy in the sidebar.

That's all, now let me bake some cookies! 🍪

FluffyChat

Krille Fear says

Today we have released FluffyChat 1.0.0 with a whole new design, a lot of bug fixes and huge performance improvements.

New design

The new design has bigger message bubbles with fancy shadows and bigger fonts. The contrast has been improved and some elements, like the time on every single message bubble, are now hidden by default. But they are not gone! Detailed message information are now accessible in the new message info page, where we not also can see the message type and the timestamp, but also the whole JSON source code of each timeline event.

Spaces

Spaces have got a lot improvements and bug fixes. They have moved to the bottom bar of the chat list (while this bottom bar is still hidden if you have not joined any space yet). The multi account switcher have instead been moved to a top left drop-down menu. So we finally got rid of the drawer, which seems to be a deprecated material design feature anyway. This new UX makes spaces much easier to use. You can long press on them to go to the space settings and long press on any chat in the chat list, to add or remove a chat to (or from) a space. We still have no support for the spaces summary API though so we don't have yet the ability to discover new rooms inside of a space but this feature might land soon in the Matrix Dart SDK.

Multi Account

FluffyChats multi account is still in beta but got a lot of bug fixes as well. You are now able to sort your accounts in "bundles" which can be very handy. The new account switcher button gives you a much better overview over your connected Matrix accounts now.

Performance

We did a lot refactoring under the hood in our Matrix Dart SDK and have improved our in-app database a lot. On the web it now uses IndexedDB natively while it tunes all database transactions on all platforms. This leads to the fastest FluffyChat experience we ever had and makes the app finally kinda usable with bigger accounts on all platforms. The room list is now lazy loaded which speeds up the app start (especially with multi account enabled) a lot. Choose your own primary color This was a long requested feature. You can now choose your favorite color to style your FluffyChat for your needs:

What will you choose? Let me know in the comments. I mostly like blue on my Ubuntu desktop.

New major version?

Ahhh by the way... What does it mean that we now have FluffyChat 1.0.0? It does NOT mean that the previous versions were not yet stable or ready for daily use. It just means that we make so many changes at once that we thought, bumping the first digit of our pseudo-semver version string might make sense. We totally messed up our versioning and are now going to do it better. Promised!! What's next? We are often asked: What is the roadmap of FluffyChat? Well... we still don't have a clear roadmap and might never have. FluffyChat is completely driven by volunteers. But what I can say that we would like to do in the next months is:

  • Better QA -> We would like to write some integration tests, push release candidates before new releases and involve everyone in testing them to offer the best stability possible.
  • Native video calls -> Yes! There will soon land support for native video calls in the Matrix Dart SDK and we are going to implement this in FluffyChat.
  • Stories -> Like you might know from SnapChat, WhatsApp or Instagram, stories are little messages you can send to all of your contacts and which will disappear after 24 hours. I would really like to implement this in FluffyChat!
  • Better notifications for iOS
  • Deeper support for spaces
  • Knocking feature
  • Drag&Drop for web

But as I said this is what we would like to do. We can't give any warranties on anything. We can only do our best. But you can help us if you like (You don't have to).

  • Join the FluffyChat community: https://matrix.to/#/#fluffychat:matrix.org
  • Report bugs at our issue tracker: https://gitlab.com/famedly/fluffychat/-/issues
  • Help with the translations and join our translators team: https://matrix.to/#/#fluffychat-translation:matrix.org
  • Help with development directly in GitLab <3
  • ... or support us on Liberapay so we can organize more FluffyChat developer meetings: https://matrix.to/#/#fluffychat-translation:matrix.org

The complete changelog for FluffyChat 1.0.0:

  • design: Chat backup dialog as a banner
  • design: Encrypted by design, all users valid is normal not green
  • design: Move video call button to menu
  • design: Display edit marker in new bubbles
  • design: Floating input bar
  • design: Minor color changes
  • design: Move device ID to menu
  • design: Place share button under qr code
  • design: Redesign and simplify bootstrap
  • design: Remove cupertino icons
  • feat: Display typing indicators with gif
  • feat: Fancy chat list loading animation
  • feat: New database backend with FluffyBox
  • feat: Make the main color editable for users
  • feat: Move styles one settings level up
  • feat: Multiple mute, pin and mark unread
  • feat: New chat design
  • feat: New chat details design
  • feat: New Public room bottom sheet
  • feat: New settings design
  • feat: Nicer images, stickers and videos
  • feat: nicer loading bar
  • feat: Open im.fluffychat uris
  • feat: Redesign multiaccounts and spaces
  • feat: Redesign start page
  • feat: Send reactions to multiple events
  • feat: Speed up app start
  • feat: Use SalomonBottomBar
  • feat: Drag&Drop to send multiple files on desktop and web
  • fix: Adjust color
  • fix: Automatic key requests
  • fix: Bootstrap loop
  • fix: Chat background
  • fix: Chat list flickering
  • fix: Contrast in dark mode
  • fix: Crash when there is no prev message
  • fix: Do display error image widget
  • fix: Do not display bottombar in selectmode
  • fix: Dont enable encryption with bots
  • fix: Dont loose selected events
  • fix: Dont rerun server checks
  • fix: download path for saving files
  • fix: Hide FAB in new chat page if textfield has focus
  • fix: Let bottom space bar scroll
  • fix: Load spaces on app start
  • fix: Only mark unread if actually marked
  • fix: Public room design
  • fix: Remove avatar from room
  • fix: Remove broken docker job
  • fix: Report sync status error
  • fix: Self sign while bootstrap
  • fix: Sender name prefix in DM rooms
  • fix: Set room avatar
  • fix: Various multiaccount fixes
  • fix: Wrong version in snap packages

What a massive update! Little birds told me we will hear about FluffyChat very soon!

Element

Everything related to Element but not strictly bound to a client

kittykat says

Threads

  • On Web, work continues on notifications and integration with homeserver APIs to improve user experience.
  • On Mobile, link sharing has been added and work is about to start on notifications.

Polls

  • Polls are nearly ready! If you enable this feature in labs, you can create a poll with several options, and people can vote on it.
  • We’re working on the finishing touches, and the first version of polls will be available in a release (on Element Desktop, Web, Android and iOS) within a few weeks.

Community testing

  • We closed 34 encryption bugs which had been resolved by improvements to the workflows and user interfaces.
  • Due to the overwhelming success with bug squash sessions in the last few weeks, we are making these a regular feature. Our next session will be on Thursday 9 December at 17:00 UTC.
  • For information about upcoming sessions and to join in, join #element-community-testing:matrix.org

Element Web/Desktop

Secure and independent communication, connected via Matrix. Come talk with us in #element-web:matrix.org!

kittykat announces

  • In labs, work continues on Information Architecture: new history interaction to replace breadcrumbs
  • We are monitoring and triaging feedback which is submitted through the new feedback UI in the app.
  • Fixed long standing bug around link formatting - links are not formatted as markdown any more.

Element iOS

Secure and independent communication for iOS, connected via Matrix. Come talk with us in #element-ios:matrix.org!

kittykat says

  • Analytics: final changes to allow opt-in analytics reporting with PostHog
  • MatrixKit has been integrated into element-ios in preparation for moving to the SwiftUI framework
  • Release Candidates are now scheduled on Tuesdays (previously on Wednesdays) which will bring them in line with Web releases.

Element Android

Secure and independent communication for Android, connected via Matrix. Come talk with us in #element-android:matrix.org!

kittykat announces

  • Element 1.3.9 has been submitted to Google: it adds support for voice message drafts and many bug fixes.
  • Starting work on new login flow: the user will be asked if they have an account or want to create one on the very first screen.
  • Analytics (PostHog): implementing the opt-in screen. Should be included in the 1.3.10 release.
  • Release Candidates are now scheduled on Tuesdays (previously on Wednesdays) which will bring them in line with Web releases.

Dept of SDKs and Frameworks 🧰

Trixnity

Multiplatform Kotlin SDK for Matrix

Benedict says

Trixnity, a multiplatform Matrix SDK written in Kotlin, has grown up since the last release 6 month ago! It has it first release candidate for v1.0.0!

If you don't heard about Trixnity: Trixnity aims to be strongly typed, customizable and easy to use. You can register custom events and Trixnity will take care, that you can send and receive that type.

The most exciting thing is the new trixnity-client module. It provides a high level client implementation and allows you to easily implement clients for Desktop, Mobile and Web. You just need to render data from and passing user interactions to Trixnity. The key features are:

  • exchangeable database
  • fast cache on top of the database
  • E2E (olm, megolm)
  • verification
  • room list
  • timelines
  • user and room display name calculation
  • asynchronous message sending without caring about E2E stuff or online status
  • media support (thumbnail generation, offline "upload", etc.)
  • redactions

At the moment, Trixnity only supports JVM in all modules, but JS and Native will follow soon (to be exact: when Kotlin 1.6.10 and ktor 2.0.0 is released). I also implemented the module trixnity-olm, which implements the wrappers of libolm for Kotlin JVM/JS/Native.

Cross signing is one of the next big features, I want to implement.

simplematrixbotlib

simplematrixbotlib is an easy to use bot library for the Matrix ecosystem written in Python and based on matrix-nio.

krazykirby99999 reports

Version 2.4.1 Released!

Docs Changes:

  • Added missing await statements to several examples
  • Added additional clarification on using the "m.notice" msgtype
  • Used Markdown instead of HTML to display a specific link

Example usage is shown below:

import simplematrixbotlib as botlib

creds = botlib.Creds("https://home.server", "user", "pass")
bot = botlib.Bot(creds)
PREFIX = '!'

@bot.listener.on_message_event
async def echo(room, message):
    match = botlib.MessageMatch(room, message, bot, PREFIX)
    if match.is_not_from_this_bot() and match.prefix() and match.command( "echo"):
        response = " ".join(arg for arg in match.args())
        await bot.api.send_text_message(room.room_id, response)

bot.run()

A thank you to HarHarLinks for their contributions to version 2.4.1!

Request additional features here.

View source on Github View package on PyPi View docs on readthedocs.io https://matrix.to/#/#simplematrixbotlib:matrix.org

Dept of Videos 📹

andybalaam announces

I'm exploring the matrix-rust-sdk on my live stream every week. I'm working on a simple Rust bot for Matrix. Come watch me struggle with the compiler on PeerTube or Twitch every wednesday at 14:00 UTC!

Room of the Week 📆

Timo ⚡️ says

Hi everyone! Did you ever feel lost in the Matrix world? The room directory is big, but it's still hard to find something you like. Or are you a room moderator, but there is not much activity in your room because it doesn't have enough users?

This is why I want to share rooms (or spaces) I find interesting.


This week's room is: #audiophiles:matrix.org

"Headphones, Speakers, IEM and any audio related equipment. Music recommendations as well."


If you want to suggest a room for this section, tell me in #roomoftheweek:fachschaften.org

Dept of Ping 🏓

Here we reveal, rank, and applaud the homeservers with the lowest ping, as measured by pingbot, a maubot that you can host on your own server.

#ping:maunium.net

Join #ping:maunium.net to experience the fun live, and to find out how to add YOUR server to the game.

RankHostnameMedian MS
1matrix.markshorten.co.uk1013
2helderferreira.io1099
3envs.net1438
4thomcat.rocks2552.5
5matrix.sp-codes.de3732.5
6jauriarts.org3756.5
7trygve.me3829
8grimneko.de5095.5
9kittenface.studio5606
10jeroenhd.nl6970

#ping-no-synapse:maunium.net

Join #ping-no-synapse:maunium.net to experience the fun live, and to find out how to add YOUR server to the game.

RankHostnameMedian MS
1matrix.awesomesheep48.me1042
20x1a8510f2.space1076
3dendrite.s3cr3t.me4405

The Adventures of TWIM bot continued

Following the late reports of the spec and hookshot updates, TWIM bot's ship went into hyperspeed. Our dear bot lost control of the ship and we lost its signal. We're doing our best to contact it and hope it's safe!

That's all I know 🏁

See you next week, and be sure to stop by #twim:matrix.org with your updates!

Type coverage for Sydent: motivation

03.12.2021 00:00 — Tech David Robertson

This is the first of three posts on improving type coverage in Sydent. Join us next Friday for the second part!

Sydent is the reference Matrix Identity server. It provides a lookup service, so that you can find a Matrix user via their email address or phone number (if they've chosen to share it).

We recently worked on improving Sydent's type coverage: the proportion of its source code with explicit annotations denoting the kind of data that each variable, expression and return value is expected to hold. These annotations are optional, but if present, they allow tools like mypy to analyze your programs and spot entire classes of bugs before you ship them! In this instance, we aimed to make Sydent pass mypy --strict, which it now does. Here's what the process looked like:

Coverage as measured by mypy. Precision and the number of typed expressions increase over the latter half of 2021.

Two lines show two different measures of how well-typed the project is. The grey region covers our two-week sprint towards improving coverage; the earliest data point is from just before previous efforts to improve typing earlier in the year.

In a series of posts, I'd like to reflect on this sprint and share what we've learned. In particular, I aim to:

  • explain why we wanted to improve type coverage now;
  • work through examples to see how (if?) mypy could have spotted bugs;
  • describe the annotation process;
  • illustrate common patterns I learned along the way;
  • discuss the checks that mypy provides; and finally
  • reflect on the state of Python's typing ecosystem.

In this first part, we'll concentrate on the first two topics; the second covers the middle two; and the third the last two.

Why do this now?

It took us a long time (too long) to notice that the Sydent instance serving matrix.org was failing to send SMS messages for verification. We suspected that something was going wrong with our API call to OpenMarket. Our first step was to improve logging, so we could start to deduce what was going wrong and why. Whilst trawling through logs, we spotted one problem which meant we weren't actually sending off the API request in the first place. Further investigation revealed a strings-versus-bytes confusion which meant that we would always (incorrectly) interpret the API response as having failed.

All in all, phone number verification was unknowingly broken in the 2.4.0 release, to be fixed in 2.4.6 a month later. How could we do better? Better test coverage is (as ever) one answer. But it struck me that the two bugs we'd encountered might be ripe for automatic detection:

  • we created an Awaitable but didn't await it or use it in any way, and
  • we tried to look up a str key in a dictionary which mapped bytes to bytes.

Could a static analysis tool like mypy detect these? How much work would it take to do so? Are there other bugs and problems we could spot with it? I was curious to answer these questions and learn more about the tools that Python's typing ecosystem provides.

Could typing have spotted these problems?

Let's start with the first of question: what can mypy detect?

The missing await

Instead of writing x = await foo(), we simply had x = foo() and didn't then go on to await x. Mypy doesn't have means to detect this at present. There was interest in this issue on such a feature, with related threads here and here.

Are there other opportunities to spot the error? Here's the relevant bit of source code from before the fix.

            sid = self.sydent.validators.msisdn.requestToken(
                phone_number_object, clientSecret, sendAttempt, brand
            )
            resp = {
                "success": True,
                "sid": str(sid),
                "msisdn": msisdn,
                "intl_fmt": intl_fmt,
            }

The call to requestToken produces a value of type Awaitable[int]. If we tried to assign that to an expression of type int we'd get an error that mypy can spot.

$ cat example.py
async def foo() -> int:
    return 1

async def bar():
    x = foo()      # no error
    y: int = foo() # error: rhs is Awaitable[int], but lhs expects int

$ mypy --check-untyped-defs example.py
example.py:6: error: Incompatible types in assignment (expression has type "Coroutine[Any, Any, int]", variable has type "int")
Found 1 error in 1 file (checked 1 source file)

Note that we have to specifically ask mypy to typecheck the body of bar by passing --check-untyped-defs; by default, mypy will only typecheck annotated code.

We might also have been able to detect the error by looking at how we used sid. Unfortunately, the only use of was in a conversion str(sid), which is a perfectly type-safe call for both sid: int and sid: Awaitable[int]. But let's put that aside for a second—suppose we added "sid": sid directly into the resp dictionary. Could mypy have spotted there was a problem with that?

Unfortunately not. Because resp has no annotation, we have to rely on how it's used to spot any type inconsistencies. There's only one use of resp: as the return value from its enclosing function, render_POST. Mypy's only chance to spot a type problem would be to compare the mypy's inferred type for resp to the return type of render_POST. What are those types? We can use reveal_type to see the former is Dict[str, object]. For the latter:

    @jsonwrap
    def render_POST(self, request: Request) -> JsonDict:

The return type is JsonDict, which is an alias for Dict[str, Any]. This is bad news, because Dict[str, object] and Dict[str, Any] are compatible. Digging a level deeper, this is because sid: Any holds true for both sid: int and sid: Awaitable[int]—so there's no error to spot here. The Any type is compatible with any other type whatsoever! Mypy uses Any as a way to defer all type checking to runtime; mypy won't (and can't!) statically analyse the usage of an expression of type Any. Indeed, mypy's reports will tell you how many Anys you're working with, and offer a variety of options to warn or error on usages of Any.

If we were inserting sid directly into a dictionary, we could do better by annotating the dictionary (or the function's return type) as a TypedDict. This is a way to specify a dictionary with a fixed set of keys, each with a fixed type. It comes in really handy for Sydent, Sygnal and Synapse—all of the Matrix APIs exchange JSON dictionaries, so anything we can do to teach mypy about their shape and types is gold dust.

In short, there were options for detecting this with some code changes, but no magic wand that would have spotted the error in the code as written.

The strings/bytes confusion

Our error was here:

        headers = dict(resp.headers.getAllRawHeaders())
        request_id = None
        if "X-Request-Id" in headers:
            request_id = headers["X-Request-Id"][0]

In this sample, resp.headers is a twisted.web.http_headers.Headers instance. getAllRawHeaders is documented as returning an iterable of (bytes, Sequence[bytes]) pairs. Even better, mypy can see this because getAllRawHeaders is annotated (many thanks to the twisted authors for this). Mypy should be able to deduce that we build a dictionary headers: Dict[bytes, Sequence[bytes]. We can check this using reveal_type:

        headers = dict(resp.headers.getAllRawHeaders())
        reveal_type(headers)
$ mypy
sydent/sms/openmarket.py:110: note: Revealed type is "builtins.dict[builtins.bytes*, typing.Sequence*[builtins.bytes]]"

(The * in builtins.bytes* here means mypy has inferred that the dictionary's keys are bytes, rather than being told explicitly that they must be bytes.)

That's all fine and dandy. But why didn't we spot this before if the annotations were all in place in twisted? Let's put aside the fact that, erm, we weren't running mypy in Sydent's CI until the recent sprint, unlike our other projects. Checking out the problematic version, we can run mypy on the file we know to contain the bug.

$ git checkout v2.4.0
$ mypy --strict sydent/sms/openmarket.py
sydent/sms/openmarket.py:82: error: Dict entry 0 has incompatible type "str": "int"; expected "str": "str"  [dict-item]

Huh. Mypy spots something, but not the error we were hoping for. What's going on here? We can ask mypy to show its working with reveal_type again.

        resp = await self.http_cli.post_json_get_nothing(
            API_BASE_URL, send_body, {"headers": req_headers}
        )
        reveal_type(resp)
        headers = dict(resp.headers.getAllRawHeaders())
        reveal_type(resp.headers)
        reveal_type(resp.headers.getAllwRawHeaders())
        reveal_type(headers)

This yields:

$ mypy sydent/sms/openmarket.py
sydent/sms/openmarket.py:82: error: Dict entry 0 has incompatible type "str": "int"; expected "str": "str"  [dict-item]
sydent/sms/openmarket.py:102: note: Revealed type is "twisted.web.iweb.IResponse*"
sydent/sms/openmarket.py:104: note: Revealed type is "Any"
sydent/sms/openmarket.py:105: note: Revealed type is "Any"
sydent/sms/openmarket.py:106: note: Revealed type is "builtins.dict[Any, Any]"
Found 1 error in 1 file (checked 1 source file)

Ahh, the Any type. As mentioned above, this represents a value whose type can't be statically determined. We're left to runtime checks to detect the problem. But we won't detect it at runtime, because dictionaries don't enforce any kind of type requirements on their keys and values.

The problem here is that mypy can't see that resp.headers is a twisted Headers object. If we could inform it of this, mypy would spot our bug:

        import twisted.web.http_headers
        raw_headers: twisted.web.http_headers.Headers = resp.headers
        reveal_type(resp)
        headers = dict(raw_headers.getAllRawHeaders())
        reveal_type(raw_headers)
        reveal_type(raw_headers.getAllRawHeaders())
        reveal_type(headers)
$ mypy sydent/sms/openmarket.py
sydent/sms/openmarket.py:82: error: Dict entry 0 has incompatible type "str": "int"; expected "str": "str"  [dict-item]
sydent/sms/openmarket.py:104: note: Revealed type is "twisted.web.iweb.IResponse*"
sydent/sms/openmarket.py:106: note: Revealed type is "twisted.web.http_headers.Headers"
sydent/sms/openmarket.py:107: note: Revealed type is "typing.Iterator[Tuple[builtins.bytes, typing.Sequence[builtins.bytes]]]"
sydent/sms/openmarket.py:108: note: Revealed type is "builtins.dict[builtins.bytes*, typing.Sequence*[builtins.bytes]]"
sydent/sms/openmarket.py:114: error: Invalid index type "str" for "Dict[bytes, Sequence[bytes]]"; expected type "bytes"  [index]
sydent/sms/openmarket.py:114: error: Argument 1 to "split" of "bytes" has incompatible type "str"; expected "Optional[bytes]"  [arg-type]
Found 3 errors in 1 file (checked 1 source file)

There it is, on line 114: Invalid index type "str" for "Dict[bytes, Sequence[bytes]]"; expected type "bytes".

Unfortunately it'd be a pain to annotate our application code to mark every use of IResponse.headers as a Headers object. We'll see a better way to do things in this the next post, which discusses the nitty-gritty details of adding annotations file-by-file.


Many thanks for reading! If you've got any corrections, comments or queries, I'm available on Matrix at @dmrobertson:matrix.org.