Thursday, 08 October

22:14

Antoine Beaupré: PSA: Europe changes time forward soon, North America next, for the last time? [Planet Debian]

This is a copy of an email I sent at work. I'm not sure I should be making noise about this here, feedback welcome.

This is your bi-yearly reminder that time is changing soon! October 25th in Europe, November 1st in North America. Less people in Canada are changing this year, with BC, Alberta, Manitoba and Northwest Territories getting rid of DST.

What's happening?

Some places in the world implement what is called Daylight saving time or DST:

https://en.wikipedia.org/wiki/Daylight_saving_time

Normally, you shouldn't have to do anything: computers automatically change time following local rules, assuming they are correctly configured, provided recent updates have been applied in the case of a recent change in said rules (because yes, this happens, and happened this year, and yes, you need to upgrade your software!).

Of course, appliances like your microwave oven will likely not change time and will need to adjusted unless they are so-called "smart", in which case they are part of the skynet botnet and should be destroyed.

If your clock is flashing "0:00" or "12:00", you have no action to take to adapt to this change, lucky you.

If you haven't changed time in six months, congratulations, your clock will be accurate again!

In any case, you should still consider DST because it might affect some of your meeting schedules, particularly if you set up a new meeting schedule in the last 6 months and forgot to consider this change.

If your location does not have DST

Properly scheduled meetings affecting multiple time zones are set in UTC time, which does not change. So if your location does not observer time changes, your (local!) meeting time will not change.

But be aware that some other folks attending your meeting might have the DST bug and their meeting times will change.

Be kind to those poor souls which might be missing meetings by a full hour because time flies backwards for them.

If you do observe DST

If you are affected by daylight savings, your local meeting times will change for UTC meetings. Normally, your meeting times are scheduled to take this into account and the new hours should be reasonable.

But now is a good time to verify that. Take a look at your schedule for the next couple of weeks and reschedule meetings before the daylight saving come up to avoid too much disruption. You have only a couple of weeks to do so right now.

When do times change, how, and and where?

As regular readers will remember, the rule of thumb is:

Spring forward, fall backwards.

That is, during the season of Spring, the clocks move forward, and during the Fall (like right now), they move backwards. That is in the northern hemisphere, but then the southern hemisphere is often saner and doesn't switch anyways.

So time will move backwards which means an extra hour of sleep. Unless you have children or bad sleep, in which case your body doesn't care about what the clock says and will wake up one hour earlier than what it should.

And of course, this doesn't happen everywhere at once, so let's see when it happens where.

Europe

The dance starts in Europe.

The change happens on the last Sunday in October at 01:00 UTC (not local time!), that is October 25th. If you are in the central European timezone, also known as Amsterdam, Berlin, or Paris time depending on your national affiliation, that essentially means that at 2:59 local the clocks will fall back to 2:00 instead of going to 3:00.

Concretely, set your watch back one hour before going to bed, go to bed at the normal time, and enjoy an extra hour of sleep or leisure.

If you have kids, you might want to start getting them to bed slightly earlier every day for a week before the change so they take time getting used to the change. If you have trouble sleeping in the morning, find your inner child and do that to yourself as well.

USA / Canada

Then it's the US[1] and Canada[2] joining the dance, on the First Sunday in November at 02:00 local (not UTC!), that is, I believe, November 1st 2025.

This means that, at 1:59, the clocks will flip to 1:00, instead of 2:00.

Concretely, do like the Europeans and tweak your clock before going to bed.

That is a little less than four weeks from now.

[1] except Arizona (except the Navajo nation), US territories, and Hawaii

[2] except Yukon, Saskatchewan, (newly) British Columbia, (newly) Alberta, (newly) Northwest Territories, (newly) Manitoba, one island in Nunavut (Southampton Island), one town in Ontario (Atikokan) and small parts of Quebec (Le Golfe-du-Saint-Laurent)

Other places with DST

This time again, I must apologize to the people of Cuba, Lebanon, Israel, Palestine, Egypt, Chile, Australia, and New Zealand, as you fine folks all have your own DST rules that are omitted here for brevity. I rely on this page from Wikipedia to be updated by time nerds accurately for this message, and it should provide you with a rough idea of what's coming:

https://en.wikipedia.org/wiki/Daylight_saving_time_by_country

In general, changes also happen in October, but either on different times or different days, except in the south hemisphere, where they might happen in September (oops, sorry NZ folks, I'm late!).

Places without DST

Everyone else, enjoy, you're on the right side of history, and we thank you for the good example you give us.

Changes since last time

There's been lots of changes since last time:

  • British Columbia moved to permanent -07 on 2026-03-09, that is it will not change to normal time in November

  • Alberta moved to permanent -06 on 2026-06-18, similar to BC above.

  • Canada’s Northwest Territories moved to permanent -06 on 2026-08-21, matching Alberta.

  • Manitoba moves to permanent -05 on 2026-10-31.

  • Morocco moves to permanent +00 on 2026-09-20.

  • Moldova has used EU transition times since 2022, but the tz database only noticed in 2026

This is my interpretation of the changes announced on the tzdata mailing list here:

https://lists.iana.org/hyperkitty/list/tz-announce@iana.org/latest

If the eastward trend continues, Canada should adopt country-wide "no daylight savings" rules by 2027, although there's actually no sign of the other provinces (Ontario, Québec and so on) currently running bills to change those rules just yet. Poor Canadians like me confused about time in their countries can refer to this section of Wikipedia for details:

https://en.wikipedia.org/wiki/Daylight_saving_time_in_Canada#By_province_and_territory

... and particularly the image featured there:

https://commons.wikimedia.org/wiki/File:Canada_time_zone_map-en.svg

It also seems like the US government might finally adopt a permanent daylight saving change bill in 2026, as the "Sunshine protection act" pass the house in July:

https://en.wikipedia.org/wiki/Sunshine_Protection_Act

True to form, this was associated with absolutely ridiculous pressure from Donald Trump against republicans (his own party!) objecting to the change:

On July 14, 2026, the House passed a Sunshine Protection Act bill backed by President Trump. Nevertheless, the bill was opposed in the Senate by Republicans, including Senator Cotton. In response, on October 3, 2026, Trump shared a post on Truth Social urging Cotton to approve the bill, where he revealed Cotton's personal cellphone number and called on people to call him.

https://www.theguardian.com/us-news/2026/oct/03/trump-tom-cotton-daylight-saving-time

Given that the last time the US did a major change to the daylight savings policy (in 2005), Canada followed suit to stay in sync, it's quite possible Trump's mad dash might actually finish getting rid of DST in North America:

https://en.wikipedia.org/wiki/Energy_Policy_Act_of_2005#Change_to_daylight_saving_time

21:28

20:42

Steinar H. Gunderson: Decompilation patterns, part 5: Nested if/goto [Planet Debian]

Here's a pattern that sometimes comes up:

if (x == 3) {
    if (y == 4) {
        ...
    } else {
        goto label_5;
    }
} else {
    label_5:
    ...
}

We don't like gotos, and here, it's pretty obvious what was meant, namely:

if (x == 3 && y == 4) {
    ...
} else {
    label_5:
    ...
}

And now you can usually delete label_5 because nothing points to it.

Often, m2c can do this by itself, but as usual, you may get to this point only after cleaning up other things.

19:07

Thorsten Alteholz: My Debian Activities in September 2026 [Planet Debian]

Debian LTS/ELTS

This was my hundred-forty-seventh month that I did some work for the Debian LTS initiative, started by Raphael Hertzog at Freexian.

During my allocated time I uploaded or worked on:

  • [DLA 4804-1] libsmpp34 security update to fix one CVE in Bookworm related to an out of bound read.
  • [#1149107] trixie-pu of libsmpp34 has been created and wait for review by the release team.
  • [DLA 4805-1] mkvtoolnix security update to fix one CVE in Bookworm related to a heap buffer overflow.
  • [DLA 4806-1] pgextwlist security update to fix one CVE in Bookworm related to substituting extension schemas or owners matching [“$’\].
  • [ELA-1837-1] mkvtoolnix gimp security update to fix one CVE in Bullseye to a heap buffer overflow.
  • [osmo-iuh] upload to fix a CVE related to a reachable assertion in Sid.

Besides doing these uploads, the month was filled with unscheduled meetings, discussions and explanations, all basically around the role of FD.

During my week of FD duties, I had to become familiar with new tools. As there are several things that FD has to do over and over again, I tried to automate this a bit. My experiments went well and I think this can be used to make FD duties less repetitive.

I also continued my work on cups and hplip and I am confident that I can do uploads in October. Last but not least I spend some time on security-master to assist others (especially the kernel team) with uploads to Bookworm and Bullseye.

In case you need to get a complete clone of the security-tracker, using the option –deepen n might be of help. Unfortunately in case you choosed n too high and some kind of error appears, something gets into a mess and you need to start almost from the beginning (some objects are still present and are used again). If you want to run a script, a value of 10 might be a good choice. You don’t have to deepen back to the beginning. At some point in time you can just –unshallow and get the whole rest. Afterwards doing a git gc is highly recommended.

Debian Printing

This month I uploaded a new upstream version or a bugfix version of:

  • … hplip to unstable, to fix an expired certificate and some bugs.

This work is generously funded by Freexian!

Debian Lomiri

Unfortunately this month my work did not make any progress.

Nevertheless, in case of any work done, this would have been generously funded by Fre(i)e Software GmbH!

Debian Astro

Unfortunately I had no time to work in this category this month.

Debian IoT

Unfortunately I had no time to work in this category this month.

Debian Mobcom

This month I uploaded a new upstream version or a bugfix version of:

misc

This month I uploaded a new upstream version or a bugfix version of:

18:35

[$] An update on Rust's project goals [LWN.net]

Tomáš Šedovič is a program manager at the Rust Foundation. He co-leads the goals team, which helps organize the Rust project's overall goals, including making sure that the people who have agreed to work on one have the support they need. At Kangrejos 2026, he provided an overview of the Rust project's goal processes aimed at ensuring that the Rust for Linux developers were aware of how the project organizes, tracks, and amends goals. The Rust for Linux project has inspired several current project goals, so he thought that getting an inside view of the process might be helpful.

18:21

Dirk Eddelbuettel: myman 0.10.0 on CRAN: Three new waves of posts! [Planet Debian]

A new and exciting version of our still-new package myman reached CRAN this morning, and has been built for r2u and on r-universe. The matching Python package has also been updated. The package offers nineteen hundred eighty four “My man …” posts by Kevin Kruse made on bsky during the summer of 2026. Each wave picked at one particular public persona. This package wrapse these up in the style of packages like fortunes or gaussfacts.

A sample usage illustration shows how to extract by pattern, and shows posts from the two most recent waves:

> library(myman)
> myman("maitre")
My man looks like he's inquiring with the maitre'd about the house curly fries.
     -- about Howard Lutnick on 2026-08-28

My man looks like a maitre'd who deeply doubts you have a reservation.
     -- about Scott Bessent on 2026-08-31

My man looks like he's asked the maitre'd to remove a party of four he finds visually
unpleasant.
     -- about Scott Bessent on 2026-08-31

My man looks like the maitre'd at a very exclusive restaurant called The Berghof.
     -- about JD Vance on 2026-09-13

My man looks like he's asking the maitre'd where they source their corn dogs.
         -- about Palmer Luckey on 2026-10-02

My man looks like Data had to borrow a sports jacket from the maitre'd.
         -- about Elon Musk on 2026-10-05
> 

One can also subset by ‘target’, or sample randomly (which is the default).

As noted during the initial announcement last week, wave eight did not make it into the initial CRAN release as it happened while the package was under review. Waves nine and ten occured more or less while I was out of town last weekend—so this release now brings three new waves to the CRAN package! We also added two new helper functions to extract the underlying data frame object, and tabulate the targetted men.

To align the version number with the count of post ‘waves’, we switched to version 0.10.0 for this release and the corresponding Python package release so that both implementations now have the same version number.

The NEWS entry for this release follows.

Changes in version 0.10.0 (2026-10-08)

  • New waves nine (100 posts) and ten (190 posts) made last week; total is now 1984 posts

  • New helper functions posts() and men() retrieving data.frame of posts and tabulation of targets

  • Internal post gathering and aggregation functions have been updated and generalized

  • Versioning scheme now goes with post waves (also for Python sibbling)

Courtesy of my CRANberries, there is also a diffstat report for the most recent release. More information is available at the repository or the package page.

This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can sponsor me at GitHub.

17:35

Maytham Alsudany: Briefly looking at DNS and nameservers under the hood [Planet Debian]

I've always known that the browser uses the Domain Name Resolution (DNS) system to convert domain names like aezign.com.au into raw IP addresses, but have never actually understood how it works, outside of making sure my network config uses 1.1.1.1 or 8.8.8.8 instead of the crappy one the ISP provides. In this article, I set out to understand what nameservers do, and how they help anything that uses the internet find the right records.

DNS in a nutshell

A well-used analogy is a phonebook; when you have someone's name and need to find their phone number, you look through the phonebook. Overall, DNS does the same thing: when you enter an address like example.com, your browser will contact a DNS server (the "phonebook") to determine its IP addresses. On Linux (or MacOS) systems, you can use dig on the command line to perform a DNS lookup and see what this looks like:

$ dig example.com

; <<>> DiG 9.20.29-1-Debian <<>> example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 42782
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com.                   IN      A

;; ANSWER SECTION:
example.com.            221     IN      A       104.20.23.154
example.com.            221     IN      A       172.66.147.243

;; Query time: 20 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Sep 27 21:07:50 AWST 2026
;; MSG SIZE  rcvd: 72

Here, you can see the DNS server being used is 1.1.1.1 (Cloudflare's DNS server), and that it found the IPs 104.20.23.154 and 172.66.147.243 associated with the domain name example.com. Your browser will then contact these IP addresses directly to load the website you are trying to open.

Configuring DNS servers

You can configure DNS servers through your operating system's network settings, and for some applications such as Firefox, with the application itself. If you don't manually set a DNS server, your computer will use the ones provided by your modem. If those are not configured, the default defined by the modem manufacturer or your ISP will be used.

The most well-used and reliable DNS servers you can use are Cloudflare's (1.1.1.1 and 1.0.0.1) and Google's (8.8.8.8 and 8.8.4.4). Having more than one DNS server means that if the first one in the list doesn't work or doesn't find the matching IPs for a domain name, it will move to the next one in the list, a backup DNS server.

DNS servers must be configured as IP addresses. If your computer tried to use a DNS server at a domain name, then where would your computer find the IP address for that domain name? It wouldn't be able to, as this is the exact gap that DNS servers fulfill.

Records

DNS is not used only to find IPs for a domain, but a whole host of other things. Collectively they are referred to as "records" (or "DNS records"). Each record is associated to a domain (e.g. example.com) or a subdomain (e.g. bar.example.com, foo.bar.example.com) Here are some of the most common and important types:

  • A - Map to an IPv4 address (e.g. 104.20.23.154).

    This is the fundamental function of DNS: to map from a domain name to a server's IP address. Multiple A records leads to load balancing, where browsers will randomly select one of the returned IP addresses such that traffic is evenly split between them.

  • AAAA - Map to an IPv6 address (e.g. 2606:4700:10::6814:179a).

    This serves the same purpose as A records but for IPv6. Multiple AAAA records results in the same load balancing behaviour. All modern websites should have both an A record for legacy compatibility, and an AAAA record to future-proof for the gradual shift towards IPv6 addresses.

  • CNAME - Alias to another domain name (e.g. example.com).

    DNS servers will recursively lookup records for the aliased domain name until they reach an IP address (in an A or AAAA record).

  • MX - Specify a Mail Exchange server (e.g. mail.example.com).

    This record tells email SMTP servers where to direct emails to. For instance, the MX record for aezign.com.au is mail.aezign.au; this directs SMTP servers like Gmail and Outlook to send emails to the mailserver running at mail.aezign.au. This also allows specifying a priority field, so that you can define multiple MX records (such as a main and a backup) and consumers will try to connect to the listed mailservers from lowest priority to highest.

  • TXT - Store arbitrary text data

    This is most commonly used for site verification (for instance, to link your website to Google Search Console) and for email security measures like DKIM, DMARC, and SPF.

  • NS - Specify the nameservers for a domain (e.g. zeus.ns.cloudflare.com).

    These authoritative nameservers are responsible for providing all the other record types when needed.

dig lets you query each of these. For instance, to list the MX records against aezign.com.au:

$ dig MX aezign.com.au

; <<>> DiG 9.20.29-1-Debian <<>> MX aezign.com.au
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28213
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;aezign.com.au.                 IN      MX

;; ANSWER SECTION:
aezign.com.au.          300     IN      MX      10 mail.aezign.au.

;; Query time: 104 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Sep 27 22:04:58 AWST 2026
;; MSG SIZE  rcvd: 70

Here you can see there is only one mailserver configured for aezign.com.au, which is mail.aezign.au, and it has a priority of 10.

If you compare this against something like gmail.com:

$ dig MX gmail.com

; <<>> DiG 9.20.29-1-Debian <<>> MX gmail.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16538
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;gmail.com.                     IN      MX

;; ANSWER SECTION:
gmail.com.              2497    IN      MX      40 alt4.gmail-smtp-in.l.google.com.
gmail.com.              2497    IN      MX      20 alt2.gmail-smtp-in.l.google.com.
gmail.com.              2497    IN      MX      5 gmail-smtp-in.l.google.com.
gmail.com.              2497    IN      MX      10 alt1.gmail-smtp-in.l.google.com.
gmail.com.              2497    IN      MX      30 alt3.gmail-smtp-in.l.google.com.

;; Query time: 120 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Sep 27 22:06:04 AWST 2026
;; MSG SIZE  rcvd: 161

You can see that gmail.com has several mailservers configured as redundancies due to the Gmail's sheer scale. gmail-smtp-in.l.google.com has the highest priority of 5, so SMTP servers will attempt to contact that mailserver first, followed by alt1.gmail-smtp-in.l.google.com and so on.

Nameservers

Now there needs to be somewhere for DNS records against a domain name to be defined, right? This is where nameservers come in.

Nameservers are DNS servers that answer queries about the domains they are authoritative for i.e. the domains they are in charge of. For example, the authoritative nameservers for example.com are hera.ns.cloudflare.com and elliott.ns.cloudflare.com, which means Cloudflare's nameservers are the source of truth when looking up DNS records for example.com. You can check this with dig:

$ dig NS example.com

; <<>> DiG 9.20.29-1-Debian <<>> NS example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37717
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com.                   IN      NS

;; ANSWER SECTION:
example.com.            81837   IN      NS      hera.ns.cloudflare.com.
example.com.            81837   IN      NS      elliott.ns.cloudflare.com.

;; Query time: 140 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Sep 27 23:27:19 AWST 2026
;; MSG SIZE  rcvd: 95

Nameservers are configured with your domain name's registrar. The nameserver you set dictates where your remaining DNS records will live. For example.com, this means you would configure all your records on Cloudflare's dashboard, since they are responsible for the domain's DNS records.

Recursive resolution

The DNS servers that our computers use follow a recursive resolution process behind-the-scenes to find the authoritative nameservers for a domain, followed by the records you are querying.

Finding the root nameservers

Due to the recursive nature of the resolution process, there needs to be a starting point: the root (.) nameservers. The resolver uses a fixed list of root nameservers called root hints issued by IANA (named.root), to locate and contact the root nameservers. Resolvers will have this provided to them initially, and can dynamically update it themselves by querying one of the root nameservers for the root NS records to obtain a fresh list. For instance, we can query 198.41.0.4 (a.root-servers.net) to obtain a new list of root nameservers:

$ dig @198.41.0.4 NS .

; <<>> DiG 9.20.29-1-Debian <<>> @198.41.0.4 NS .
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43677
;; flags: qr aa rd; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 27
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;.                              IN      NS

;; ANSWER SECTION:
.                       518400  IN      NS      l.root-servers.net.
.                       518400  IN      NS      j.root-servers.net.
.                       518400  IN      NS      f.root-servers.net.
.                       518400  IN      NS      h.root-servers.net.
.                       518400  IN      NS      d.root-servers.net.
.                       518400  IN      NS      b.root-servers.net.
.                       518400  IN      NS      k.root-servers.net.
.                       518400  IN      NS      i.root-servers.net.
.                       518400  IN      NS      m.root-servers.net.
.                       518400  IN      NS      e.root-servers.net.
.                       518400  IN      NS      g.root-servers.net.
.                       518400  IN      NS      c.root-servers.net.
.                       518400  IN      NS      a.root-servers.net.

;; ADDITIONAL SECTION:
l.root-servers.net.     518400  IN      A       199.7.83.42
l.root-servers.net.     518400  IN      AAAA    2001:500:9f::42
j.root-servers.net.     518400  IN      A       192.58.128.30
j.root-servers.net.     518400  IN      AAAA    2001:503:c27::2:30
f.root-servers.net.     518400  IN      A       192.5.5.241
f.root-servers.net.     518400  IN      AAAA    2001:500:2f::f
h.root-servers.net.     518400  IN      A       198.97.190.53
h.root-servers.net.     518400  IN      AAAA    2001:500:1::53
d.root-servers.net.     518400  IN      A       199.7.91.13
d.root-servers.net.     518400  IN      AAAA    2001:500:2d::d
b.root-servers.net.     518400  IN      A       170.247.170.2
b.root-servers.net.     518400  IN      AAAA    2801:1b8:10::b
k.root-servers.net.     518400  IN      A       193.0.14.129
k.root-servers.net.     518400  IN      AAAA    2001:7fd::1
i.root-servers.net.     518400  IN      A       192.36.148.17
i.root-servers.net.     518400  IN      AAAA    2001:7fe::53
m.root-servers.net.     518400  IN      A       202.12.27.33
m.root-servers.net.     518400  IN      AAAA    2001:dc3::35
e.root-servers.net.     518400  IN      A       192.203.230.10
e.root-servers.net.     518400  IN      AAAA    2001:500:a8::e
g.root-servers.net.     518400  IN      A       192.112.36.4
g.root-servers.net.     518400  IN      AAAA    2001:500:12::d0d
c.root-servers.net.     518400  IN      A       192.33.4.12
c.root-servers.net.     518400  IN      AAAA    2001:500:2::c
a.root-servers.net.     518400  IN      A       198.41.0.4
a.root-servers.net.     518400  IN      AAAA    2001:503:ba3e::2:30

;; Query time: 476 msec
;; SERVER: 198.41.0.4#53(198.41.0.4) (UDP)
;; WHEN: Mon Sep 28 00:01:08 AWST 2026
;; MSG SIZE  rcvd: 811

Querying the root nameservers

For example.com, the first step is to query the root (.) nameservers to find the nameservers for the .com top-level domain (TLD). Using a.root-servers.net again:

$ dig @198.41.0.4 NS com

; <<>> DiG 9.20.29-1-Debian <<>> @198.41.0.4 NS com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22528
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 13, ADDITIONAL: 27
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;com.                           IN      NS

;; AUTHORITY SECTION:
com.                    172800  IN      NS      l.gtld-servers.net.
com.                    172800  IN      NS      j.gtld-servers.net.
com.                    172800  IN      NS      h.gtld-servers.net.
com.                    172800  IN      NS      d.gtld-servers.net.
com.                    172800  IN      NS      b.gtld-servers.net.
com.                    172800  IN      NS      f.gtld-servers.net.
com.                    172800  IN      NS      k.gtld-servers.net.
com.                    172800  IN      NS      m.gtld-servers.net.
com.                    172800  IN      NS      i.gtld-servers.net.
com.                    172800  IN      NS      g.gtld-servers.net.
com.                    172800  IN      NS      a.gtld-servers.net.
com.                    172800  IN      NS      c.gtld-servers.net.
com.                    172800  IN      NS      e.gtld-servers.net.

;; ADDITIONAL SECTION:
l.gtld-servers.net.     172800  IN      A       192.41.162.30
l.gtld-servers.net.     172800  IN      AAAA    2001:500:d937::30
j.gtld-servers.net.     172800  IN      A       192.48.79.30
j.gtld-servers.net.     172800  IN      AAAA    2001:502:7094::30
h.gtld-servers.net.     172800  IN      A       192.54.112.30
h.gtld-servers.net.     172800  IN      AAAA    2001:502:8cc::30
d.gtld-servers.net.     172800  IN      A       192.31.80.30
d.gtld-servers.net.     172800  IN      AAAA    2001:500:856e::30
b.gtld-servers.net.     172800  IN      A       192.33.14.30
b.gtld-servers.net.     172800  IN      AAAA    2001:503:231d::2:30
f.gtld-servers.net.     172800  IN      A       192.35.51.30
f.gtld-servers.net.     172800  IN      AAAA    2001:503:d414::30
k.gtld-servers.net.     172800  IN      A       192.52.178.30
k.gtld-servers.net.     172800  IN      AAAA    2001:503:d2d::30
m.gtld-servers.net.     172800  IN      A       192.55.83.30
m.gtld-servers.net.     172800  IN      AAAA    2001:501:b1f9::30
i.gtld-servers.net.     172800  IN      A       192.43.172.30
i.gtld-servers.net.     172800  IN      AAAA    2001:503:39c1::30
g.gtld-servers.net.     172800  IN      A       192.42.93.30
g.gtld-servers.net.     172800  IN      AAAA    2001:503:eea3::30
a.gtld-servers.net.     172800  IN      A       192.5.6.30
a.gtld-servers.net.     172800  IN      AAAA    2001:503:a83e::2:30
c.gtld-servers.net.     172800  IN      A       192.26.92.30
c.gtld-servers.net.     172800  IN      AAAA    2001:503:83eb::30
e.gtld-servers.net.     172800  IN      A       192.12.94.30
e.gtld-servers.net.     172800  IN      AAAA    2001:502:1ca1::30

;; Query time: 772 msec
;; SERVER: 198.41.0.4#53(198.41.0.4) (UDP)
;; WHEN: Mon Sep 28 00:09:25 AWST 2026
;; MSG SIZE  rcvd: 828

Querying the TLD nameservers

Now the resolver has .com nameservers and their IPs. We can use one of these, such as 192.5.6.30 (a.gtld-servers.net) to find the nameservers for example.com:

$ dig @192.5.6.30 NS example.com

; <<>> DiG 9.20.29-1-Debian <<>> @192.5.6.30 NS example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37419
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 13
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;example.com.                   IN      NS

;; AUTHORITY SECTION:
example.com.            172800  IN      NS      hera.ns.cloudflare.com.
example.com.            172800  IN      NS      elliott.ns.cloudflare.com.

;; ADDITIONAL SECTION:
hera.ns.cloudflare.com. 172800  IN      A       108.162.192.162
hera.ns.cloudflare.com. 172800  IN      A       172.64.32.162
hera.ns.cloudflare.com. 172800  IN      A       173.245.58.162
hera.ns.cloudflare.com. 172800  IN      AAAA    2606:4700:50::adf5:3aa2
hera.ns.cloudflare.com. 172800  IN      AAAA    2803:f800:50::6ca2:c0a2
hera.ns.cloudflare.com. 172800  IN      AAAA    2a06:98c1:50::ac40:20a2
elliott.ns.cloudflare.com. 172800 IN    A       108.162.195.228
elliott.ns.cloudflare.com. 172800 IN    A       162.159.44.228
elliott.ns.cloudflare.com. 172800 IN    A       172.64.35.228
elliott.ns.cloudflare.com. 172800 IN    AAAA    2606:4700:58::a29f:2ce4
elliott.ns.cloudflare.com. 172800 IN    AAAA    2803:f800:50::6ca2:c3e4
elliott.ns.cloudflare.com. 172800 IN    AAAA    2a06:98c1:50::ac40:23e4

;; Query time: 80 msec
;; SERVER: 192.5.6.30#53(192.5.6.30) (UDP)
;; WHEN: Mon Sep 28 00:12:19 AWST 2026
;; MSG SIZE  rcvd: 359

Finding the domain's nameserver

The response tells the resolver that the nameservers for example.com are hera.ns.cloudflare.com and elliott.ns.cloudflare.com. Now since these Cloudflare nameservers are also .com domains and Cloudflare is so commonly used, the .com nameserver provides optional sibling glue records as defined in RFC 9471 to prevent further roundtrips and optimize the process.

If we ignore the sibling glue records provided, the resolver would have to ask the .com nameservers for the nameservers for cloudflare.com:

$ dig @192.5.6.30 NS cloudflare.com

; <<>> DiG 9.20.29-1-Debian <<>> @192.5.6.30 NS cloudflare.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40307
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 5, ADDITIONAL: 21
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;cloudflare.com.                        IN      NS

;; AUTHORITY SECTION:
cloudflare.com.         172800  IN      NS      ns3.cloudflare.com.
cloudflare.com.         172800  IN      NS      ns5.cloudflare.com.
cloudflare.com.         172800  IN      NS      ns4.cloudflare.com.
cloudflare.com.         172800  IN      NS      ns6.cloudflare.com.
cloudflare.com.         172800  IN      NS      ns7.cloudflare.com.

;; ADDITIONAL SECTION:
ns3.cloudflare.com.     172800  IN      A       162.159.0.33
ns3.cloudflare.com.     172800  IN      A       162.159.7.226
ns3.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:21
ns3.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:7e2
ns5.cloudflare.com.     172800  IN      A       162.159.2.9
ns5.cloudflare.com.     172800  IN      A       162.159.9.55
ns5.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:209
ns5.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:937
ns4.cloudflare.com.     172800  IN      A       162.159.1.33
ns4.cloudflare.com.     172800  IN      A       162.159.8.55
ns4.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:121
ns4.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:837
ns6.cloudflare.com.     172800  IN      A       162.159.3.11
ns6.cloudflare.com.     172800  IN      A       162.159.5.6
ns6.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:30b
ns6.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:506
ns7.cloudflare.com.     172800  IN      A       162.159.4.8
ns7.cloudflare.com.     172800  IN      A       162.159.6.6
ns7.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:408
ns7.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:606

;; Query time: 163 msec
;; SERVER: 192.5.6.30#53(192.5.6.30) (UDP)
;; WHEN: Mon Sep 28 00:26:53 AWST 2026
;; MSG SIZE  rcvd: 573

Here, the .com nameserver provides in-domain glue records for cloudflare.com's nameservers. Without these, there would be a circular dependency, since the nameserver named is a subdomain of the domain being queried for. This is because nameservers are always defined by name, which means that in order to actually contact the nameserver, your computer needs to obtain its IP address with another DNS lookup.

Next, we'd use one of these nameservers, say ns3.cloudflare.com, to obtain the IP for hera.ns.cloudflare.com:

$ dig @162.159.0.33 hera.ns.cloudflare.com

; <<>> DiG 9.20.29-1-Debian <<>> @162.159.0.33 hera.ns.cloudflare.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16801
;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;hera.ns.cloudflare.com.                IN      A

;; ANSWER SECTION:
hera.ns.cloudflare.com. 86353   IN      A       108.162.192.162
hera.ns.cloudflare.com. 86353   IN      A       173.245.58.162
hera.ns.cloudflare.com. 86353   IN      A       172.64.32.162

;; Query time: 123 msec
;; SERVER: 162.159.0.33#53(162.159.0.33) (UDP)
;; WHEN: Mon Sep 28 01:11:37 AWST 2026
;; MSG SIZE  rcvd: 99

Querying the domain's authoritative nameserver

Now we've reached the authoritative nameserver for example.com! The last thing to do is to fetch the A record for example.com to obtain its server's IP address:

$ dig @108.162.192.162 example.com

; <<>> DiG 9.20.29-1-Debian <<>> @108.162.192.162 example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43082
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com.                   IN      A

;; ANSWER SECTION:
example.com.            300     IN      A       104.20.23.154
example.com.            300     IN      A       172.66.147.243

;; Query time: 123 msec
;; SERVER: 108.162.192.162#53(108.162.192.162) (UDP)
;; WHEN: Mon Sep 28 01:13:47 AWST 2026
;; MSG SIZE  rcvd: 72

At last, we've determined that the IP addresses of the servers behind example.com are 104.20.23.154 and 172.66.147.243. This matches our earlier lookup directly against your computer's configured DNS server.

The DNS servers your computer uses encapsulate this recursive resolution process behind the scenes, so that your computer only needs to make one round trip, and the servers can use their much faster data center connections and caching to optimize the process and make the query as fast as possible.

Checking DNS records

To check a website's records, the first thing to do is to ensure the domain name is pointed at the correct nameserver. For instance, aezign.com.au is supposed to use Cloudflare, since that is where all the DNS records have been set up. We can verify this using dig, (or alternatively with online tools like dnschecker.org):

$ dig NS aezign.com.au

; <<>> DiG 9.20.29-1-Debian <<>> NS aezign.com.au
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44172
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;aezign.com.au.                 IN      NS

;; ANSWER SECTION:
aezign.com.au.          86400   IN      NS      rosalie.ns.cloudflare.com.
aezign.com.au.          86400   IN      NS      zeus.ns.cloudflare.com.

;; Query time: 92 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Mon Sep 28 01:39:22 AWST 2026
;; MSG SIZE  rcvd: 100

If this is not set correctly, the DNS records you configure will not work, as resolvers will be fetching records from somewhere else. In most cases, you can update this in your domain registrar's settings. This is especially common for new domain names, which default to the registrar's nameservers; unless you plan to use their built-in DNS management, you'll need to point them to your chosen provider.

Now we can check that the DNS records configured match those your DNS server returns. For aezign.com.au, these are the MX and TXT records configured on Cloudflare.

Extract of Cloudflare's DNS record listing showing the MX record and TXT records for aezign.com.au.

The MX record and some TXT records configured for aezign.com.au on Cloudflare's dashboard.

We can check these using dig, and using the +short option to show only the content of the records.

$ dig MX aezign.com.au +short

10 mail.aezign.au.

$ dig TXT aezign.com.au +short

"google-site-verification=KFqTU5xH8fFf6OtszEL4oudfxple29Tm-TvxVtCWkAM"
"v=spf1 mx a:mail.aezign.au -all"

$ dig TXT _dmarc.aezign.com.au +short

"v=DMARC1;p=quarantine;sp=quarantine;adkim=r;aspf=r"

$ dig TXT aezign.com.au-2026._domainkey.aezign.com.au +short

"v=DKIM1; h=sha256; k=rsa; s=email; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzL04yXZgB72YkoxS+tLK//aWx65TS4UA3F0qJ7el68SEuD4ehmg+61Ta9iS31H6U074dnjyoPDeaiGBa7ToTNCtiIts4/ghD/8nrENROE6hQAwRmTK18ODDHrRDW73hWe6Dg4cK5vmXi82/wWRZU9SDKbme28IE9uCVir3lCkKYLs1" "j16gR1Sjqr6a2+3o+EeVXnLJ4wXmJMm7KCIdn7zv0t9Z1eYcw672PdpYPAMpGb8uUsgaDBsNSpTcupPkz4SWj6TrOB80CPHjWseVY8EOJeRsNKQ/8LFoQEBl4Rxtb3HAmu4UIxSZ4QMVOLnXz67e85mA5XjcNIsVMzaWaowwIDAQAB"

As we can see, this matches, which means that the nameservers for aezign.com.au are correct and the DNS records are what we intend them to be.

Note that if you've recently changed the nameserver or DNS records for your domain, it can take anywhere between 10 seconds and 24 hours for the changes to take effect and propagate across the different DNS servers globally. Tools like dnschecker.org can check your records on different DNS servers worldwide to see if they're reflecting your changes. The reason for this is caching: DNS servers like 1.1.1.1 will cache results so it doesn't have to do the full resolution process every time, and uses the Time-To-Live (TTL) you configure in your DNS records as a guideline for caching duration.

16:21

Link [Scripting News]

Before long we’ll have Ukraine style drones raining down on American cities. Meanwhile we have built a military designed to fight WWII. We’d better hope Ukraine wins their war with Russia, and they’re still our friends. Journalism is letting us down bigtime here. The world is at war, it's going to get worse, and the US isn't preparing. We should be defending Ukraine with everything we have, we are still powerful enough to determine the outcome. But of course we are totally asleep at the wheel, gloating over Trump's approval rating, and how the Dems might win the Senate or whatever. Music to relax by. Bedtime stories.

15:35

Web of Markdown, part 2 [Scripting News]

The tech industry kicked the web out of the web.

scripting.com is your canary in a coal mine.

If you can read it then your browser is a real web browser.

If it can’t it’s phony baloney.

That's the big picture behind the Web of Markdown, an idea that has generated surprising interest among devs, esp now that Claude Code exists to enable weekend projects that can do miraculous things.

My longtime friend Andrew Shell has quickly put together what looks like a Chrome plugin that starts work on how to display a Markdown web page. And it looks fucking beautiful, as I imagined it might. Get rid of the bullshit just tell me what you have to say. A web that you can read. A web without interstitials. A web every write lusts for as well as an editor that can be used everywhere. This is the nightmare of the companies whose products inhabite the de-webbed web of 2026 (and that's all there is). Let's go back and pretend the VCs didn't run all their get-rich-quick schemes, what would the fucking web be like if it was all about two things -- writing and reading and the tech got out of the fucking way and let the writers do their thing. I think it'll actually work this time because they really fucked up the web and we can show them dramatically what it would be like if they hadn't. I've said this before if they insist on twisting what remains of the web into more bullshit, our option is to re-create the web and forget about them, leave them in the dust.

By the way, now I'm glad I have a source:markdown element in most of my RSS 2.0 feeds. I knew they'd come in handy! :-)

Back to Andrew's project, I said in a comment, I want more Markdown and absolutely no Chrome.

I have to trust Google for far too many things, but I think we can get rid of our dependency on Chrome. Now is the best time in a long time, as there's a hybrid product forming and they don't have the best AI system, and btw -- even if they did as users we are highly motivated for them to get some competition. And the AI companies don't do HTML or CSS, they do Markdown. So much so that Google had to convert their office product to Markdown before OpenAI would work with them. The wind is blowing away from Google and into Markdown, and I'm going to insist that they support HTTP in addition to HTTPS to make sure Google hears loud and clear that they can't run the web as if it were a corporate platform.

14:49

Link [Scripting News]

BTW, Mets fans do not root for the Yankees, while Yankees fans are happy to wear Mets caps when we're on top. Their only philosophy is that the Yankees always win and if they don't we hate them. And the Mets' philosophy is incomprehensible (I hear) to Yankees fans. Our philosophy is to have a philosophy and there's nothing about winning in there because the love of the Mets is rooted in a team that couldn't even run the bases. They were loved because they were losers, probably because we knew their fate was to reward our belief in them. Now this year the Mets sucked. The reason why is kind of obvious. The Mets got confused, and they thought they were the Yankees. That. Does. Not. Work. And we already have one heartless low-philosophy baseball team in NYC, we sure as hell don't need another one. But this year is different because we have the fucking Knicks, who won in 5 and it would have been a clean sweep if it weren't for that buzzkill mofo, Trump. So anyway, I hear the Yankees were eliminated yesterday. Well then, it's on with the NBA season now and we'll try again with the Mets in April, or more realistically after the NBA championship for the new season is settled.

[$] The [vx]swap showdown [LWN.net]

The kernel's swap layer has undergone some significant changes over the course of the last year and a number of longstanding problems have been addressed. One problem that has not yet been solved in the mainline is the direct tie between slots in the swap cache and space in persistent swap files, which can cause highly inefficient resource use. There are two competing solutions for this problem, neither of which has, as yet, reached readiness for merging; a lengthy discussion on possible paths forward shows ongoing disagreement over the best path forward.

Security updates for Thursday [LWN.net]

Security updates have been issued by AlmaLinux (bind, firefox, freerdp, ghostscript, glibc, kernel, kernel-rt, perl-DBI, python3.12, rust-rpm-sequoia, rust-sequoia-sq, rust-sequoia-sqv, and vim), Debian (gst-plugins-base1.0, python3.11, and xz-utils), Fedora (7zip, chromium, curl, docker-buildx, kernel, Lmod, and sos), Mageia (tesseract), Oracle (dovecot, firefox, freerdp, gd, ghostscript, kernel, librabbitmq, perl-DBI, python3.12, rust-rpm-sequoia, rust-sequoia-sqv, sg3_utils, vim, and virtuoso-opensource), Slackware (xorg-server), SUSE (aliyun-cli, busybox, cadvisor, chromedriver, chromium, crane, distribution-registry, fetchmail, fio, ghostscript, golang-github-prometheus-alertmanager, google-osconfig-agent, govulncheck-vulndb, libsoup, libXtst, openexr, prometheus-blackbox_exporter, python-fsspec, rpcbind, rsyslog, rustup, wireshark, wpa_supplicant, and zcode), and Ubuntu (erlang, golang-golang-x-net, gst-plugins-ugly1.0, lxml, poppler, and sudo).

14:00

The Preservation Gap in the AI Stack: Why Capability Is Advancing Faster Than Reconstructability [Radar]

Artificial intelligence systems are difficult to reproduce because their behavior depends on nondeterministic models and on data, configurations, policies, and external services that continue to change. But exact reproducibility is not always what operators, investigators, or auditors need. They often need something different: historical reconstructability. The central claim is that reconstructability is a system property established during execution, not inferred later from surviving artifacts. To make that property concrete, this article introduces Orrery, a reference architecture for preserving the runtime bindings and historical dependency states needed to reconstruct an AI system’s past execution.

Imagine an AI-assisted decision that must be examined six months after it was made. Rerunning the system may produce a different answer, but an investigator may still need to determine which model endpoint, policy, retrieved data, tool contract, and runtime configuration were used at the time. That is the problem addressed here.

The AI industry remains much better at evaluating capability than at preserving the knowledge required to understand past executions. Evaluation pipelines track benchmark scores, task success rates, retrieval precision and recall, tool-call success rates, latency, and cost, and they grow more elaborate with every release. In practice, this machinery often reduces to one acceptance question: What can this system do? That question captures capability, but not whether a past execution will remain understandable after the system changes.

That missing property is historical reconstructability: the ability to establish which dependencies were used and under which conditions a particular execution occurred. It is related to, but distinct from, reproducibility. Reproducibility asks whether a result can be produced again under equivalent conditions. Reconstructability asks whether the conditions of the original execution can still be identified, even when repeating that execution would not produce an identical result.

This distinction matters when a consequential AI execution completes successfully today and is challenged six months later. The model may still exist in a registry. The application revision may still exist in Git. The trace may still identify the request and the services it crossed. Yet none of these records necessarily reveals which model endpoint served the request, which input transformation and runtime configuration were applied, which data, feature state, or retrieved context entered the computation, or which policy state governed it. In an agentic system, the missing history may also include memory, tool contracts, delegation, and external effects. Even when a dependency identifier was recorded, the historical state to which it referred may no longer be available or resolvable.

The problem is that an AI runtime may use dependencies whose historical identities and relationships are not preserved. This is an architectural problem, not merely a logging problem. The gap lies between what the runtime uses and what the surrounding infrastructure is required to preserve. I call this the preservation gap. The AI stack preserves models, code, deployments, and observations, but it does not require the effective historical configuration of a particular execution to remain available. The components may remain while the relations that made them one execution disappear. An AI runtime must assemble today’s execution; it is not necessarily required to remember exactly what it assembled yesterday. Reconstructability must therefore be established during execution, before later mutation erases the bindings that gave the execution its effective configuration.

Hermetic builds illustrate the opposite design pattern. They close the dependency graph before execution begins by pinning inputs, versions, and external dependencies. AI runtimes face the reverse situation: Part of the dependency graph remains open until execution, as model endpoints may resolve through mutable aliases, data and feature state evolve, runtime configuration and policy change, and retrieved context is selected only when a request is processed. Agentic systems extend the graph further through memory, tools, delegation, and external effects. In a hermetic build, dependency closure is an input. In a modern AI runtime, part of it may be an output of the run.

Version control records source evolution, transaction logs record state transitions, and distributed tracing records execution relationships. These mechanisms preserve different views of a run but not its materially relevant execution-specific configuration. A trace may retain execution flow, a deployment manifest the declared application state, and a model registry the model revision. What is often missing is the record of which artifacts and states were bound together in a particular execution.

The dependency graph no longer closes at deployment

A deployment records the configuration known before execution begins. AI systems are harder to reconstruct because materially relevant dependencies are selected during execution rather than fixed at deployment. At inference time, the system may resolve a model endpoint, input transformation, data or feature state, retrieved context, runtime configuration, safety controls, and policy. Agentic systems extend this composition through memory, tools, delegation, and external effects. These execution-specific selections are runtime bindings: facts about what the execution actually used.

The following example makes this distinction concrete. Consider a hypothetical agentic execution identified as E891. During this execution, the system retrieves two documents, evaluates a policy, invokes a tool, and produces an external effect. The deployment may identify application revision 8f31c2 and model v17, while the execution itself binds prompt h31, retrieval index r42, entities d182@17 and d761@4, tool contract h42, policy v8, and ultimately effect e3. The deployment system could not have known this entire set in advance, because some of these dependencies were selected only as the execution proceeded.

The difference between what a deployment declared and what an execution actually used becomes consequential when dependencies change. A model alias may resolve differently, a feature or data source may change, preprocessing and runtime configuration may evolve, and policy v8 may become v9. In agentic systems, retrieved context, memory, and tool contracts introduce further independent mutation. Deployment identity describes what was declared, but the historical record of an execution must describe what was actually used. Unless those binding relations are captured when they occur, the effective configuration of E891 cannot be recovered reliably from later system state.

Figure 1. A deployment captures the initial configuration, while its execution-scoped dependency closure emerges as additional dependencies are bound during execution.Figure 1. A deployment captures the initial configuration, while its execution-scoped dependency closure emerges as additional dependencies are bound during execution. (Diagram by the author.)

Current state is a lossy projection

Once runtime bindings are treated as part of the execution, the limitation of current-state records becomes clear. The execution-scoped dependency closure is the combination of the declared state and the runtime bindings that formed the effective configuration of a particular execution.

As dependencies mutate, different historical executions can leave behind the same observable evidence. The current state may show which artifacts still exist without revealing which combination a particular execution used. Once the distinguishing bindings are gone, the history cannot be reconstructed from what remains. A system can retain its artifacts while losing reconstructability: An identifier proves neither that a dependency was used nor that the historical state it denotes remains available. Reconstructability therefore requires durable binding relations and continued access to the historical states they identify.

Existence is not usage

The existence of an artifact and its use in a particular execution are different facts. Concurrent versions, caches, retries, and asynchronous changes make it unreliable to infer whether an execution used an artifact merely because that artifact existed at the relevant time.

W3C PROV already provides the relevant semantic distinction: An activity can use an entity. The missing primitive is therefore not a new provenance vocabulary, but a runtime requirement to record which dependency a particular execution used and to preserve an identity that can be resolved later.

This requirement also exposes the limit of observability. OpenTelemetry can transport dependency identifiers through attributes, links, context, and baggage, but encoding an identifier does not preserve its historical meaning. A trace may retain execution topology while the identities that explain it disappear. Instrumentation can carry preservation metadata, but it cannot guarantee durable storage or continued resolution of the referenced states.

Historical reconstructability therefore requires more than retained artifacts. For a defined class of executions and a specified retention period, the system must preserve two properties: binding integrity, which records which dependency versions and states an execution actually used, and resolution integrity, which keeps those recorded identities resolvable to the historical states they denote.

Preservation requires failure semantics

Once reconstructability is treated as a system property, the architecture must define what happens when the records required to reconstruct an execution fail to reach durable storage. Suppose policy v8 authorizes execution E891 to invoke tool contract h42, and the tool commits effect e3. If the usage record fails to reach durable storage, the action succeeds while the historical links among the effect, its policy, the tool contract, and the execution context are lost. The external effect remains, but the historical record no longer reliably explains which policy and tool contract authorized it. The external world and the historical record have diverged.

Figure 2. Execution E891 branches into a committed external effect and a failed usage record.Figure 2. Execution E891 branches into a committed external effect and a failed usage record. (Diagram by the author.)

Figure 2 shows why this condition cannot be treated as ordinary telemetry loss. When an external effect has been committed but the corresponding usage record is missing, the architecture does not guarantee historical reconstructability. It provides only best-effort historical evidence.

Reconstructability becomes an engineering guarantee only when the architecture defines when preservation records become durable and what the system does if they cannot be stored.

A system may require a durable usage record before committing the effect, atomically persist the effect intent and preservation record through a transactional outbox, quarantine the execution for reconciliation, or trigger a compensating action. The implementation is application-specific, but preservation failure must have explicit semantics rather than disappear into telemetry.

Existing technologies provide the building blocks for this layer. Provenance and lineage models represent relations, tracing propagates execution context, registries identify versions, and content-addressed stores preserve artifacts. None, however, creates a runtime obligation to preserve the execution-scoped dependency closure required for reconstruction.

This layer is a preservation plane: the contract and machinery that keep an execution’s dependency closure identifiable as its surrounding systems change. It defines what to record, how records become durable, and how referenced states remain resolvable. Orrery applies this principle as a reference architecture in which materially relevant runtime bindings are captured when they occur and retained together with resolvable identities for the historical states they reference.

AIGov Core: reconstructability as a tested property

AIGov Core provides a limited implementation of this principle. Its evidence ledger stores hash-chained events, while its replay engine reconstructs a governance verdict from a stored export without querying live state. A companion check labels the export Ready, Partial, or NotReady according to unresolved lineage, missing policy artifacts, version drift, or replay failure. This demonstrates that reconstructability can be tested, but it does not solve the complete preservation problem. The system can replay only what was recorded; it cannot recover a runtime binding that was never captured.

The preservation gap is the mismatch between what an execution depends on and what the system persists. Closing it requires a runtime obligation: Whenever an execution binds materially relevant state, the system must record that binding, preserve a durable identity for the dependency, and retain access to the historical state it denotes for the defined retention period. Reconstructability is therefore not a by-product of capability or observability. It is an architectural property that must be designed, tested, and enforced during execution.

Man-Made Horrors, Part One [Penny Arcade]

I ate some food court Chinese and now, by every observable metric, the food court Chinese is eating me. I was able to play a couple rounds of Blood on the Clocktower last night before I began to get a wobbly feeling I described as "nautical" and I have spent the last ten hours or so in a state similar to a furniture warehouse whose boss is out of town, i.e., "Everything Must Go."

Man-Made Horrors, Part One [Penny Arcade]

New Comic: Man-Made Horrors, Part One

Limited-Time Offer [Penny Arcade]

My brand has long been "Atheist Jerk-off," but wtf I love Catholicism now.

 

Limited-Time Offer [Penny Arcade]

New Comic: Limited-Time Offer

World of Warcraft Never [Penny Arcade]

After Wednesday's Wattersonian horseshit, we're back to what we do best: animal cruelty.

World of Warcraft Never [Penny Arcade]

New Comic: World of Warcraft Never

The Race Galactic [Penny Arcade]

There was a period of time where they were just putting Star Wars on shit. Games Workshop used to do that too, with their license. There was some filth. But they've both figured out that - and I'll agree it's a little crazy - if you give the licenses to cool people who make good games it's a better play. In the same way that ex-Firaxis people proved that Star Wars XCOM could be much more than the sum of its parts, ex-Criterion people are making some kind of Goddamn roguelite racing game and it looks fucking sick.

The Race Galactic [Penny Arcade]

New Comic: The Race Galactic

Numbers Game [Penny Arcade]

He's still being dragged down into an incredibly deep hole with the new Fire Emblem, enough to wonder how some of the math could possibly math in this way. He sounds like me when I got heavily - heavily - into Dynasty Tactics, which I was horrified to learn is twenty-four years old. Fire Emblem is about type-matching and abilities, which is already drugs; Dynasty Tactics is largely about creating a favorable battlefield by positioning your units so that, like striking a match, you attack once and it triggers additional attacks from the rest of your army. I don't know that I've ever had to think that hard in a game since. They made a second one, but I didn't like it as well. That could be due to the fact that it wasn't as good, maybe, but who knows. It's also possible that the exertions of arranging and then triggering ten discrete units on a two-dimensional plane so that they all perform in a harmonious sequence is only something you can feel once.

Numbers Game [Penny Arcade]

New Comic: Numbers Game

My B-Day! [Penny Arcade]

Thanks for the Birthday wishes guys I appreciate it! Gabe’s Birthday being a worldwide phenomenon is one of my favorite running gags in the strip. 

 

 

Goerbemisdag [Penny Arcade]

Strips about "The Christmas In September" go back, yea, unto the site's very founding. This was during a time where "sites" were a thing, luckily we came to our right minds and converted the frontier into a series of corporate intranets. Dodged a bullet, there. But! There are several more strips in that vein, and there are two strips in particular that have the visual cues to help you understand the rich lore he has been embroidering the holiday with. I should tell you that he and I never once discussed a single stitch of this stuff. I'll just get a strip outta nowhere and he's got a fez and there's a fish for some reason.

Goerbemisdag [Penny Arcade]

New Comic: Goerbemisdag

The Euphemism Treadmill [Penny Arcade]

One of the weirder things about algorithmic media is that I will receive apology videos from people I've never even heard of simply because it's media that has been engaged with beyond a certain threshold. It's a genre; they seem real sad. I thought Bungie's new video was going to be another entry in this Al-Qaeda Hostage genre, but at least I know what this one is about. Bungie's corporate leadership sold them up the river, made impossible promises, and then actual human beings had to try to spin that candy floss into real products, all while getting chopped up like a boosted Audi. A lot of people mourned Destiny, as they were essentially told to by the company itself. But now it's back! Kinda. Maybe?

The Euphemism Treadmill [Penny Arcade]

New Comic: The Euphemism Treadmill

Casuality [Penny Arcade]

Fire Emblem: Fortune's Weave has Mr. Gribbz by the shortest and curliest hairs he's got. You know it's fucked up when he feels compelled to make a whole post about something, as he abhors the written word. But when a game gets him to stop skipping cutscenes, a phenomenon whose rarity would allow me to count it on one hand, that's when you know you're dealing with some all-time shit. I don't think he's a casual-t, but he's way too invested in the story to miss something because of a bad roll.

TLDR: I Love Fortune's Weave [Penny Arcade]

Sometimes I bounce right off of a Fire Emblem game, and sometimes they get their hooks in me. On paper I should not be into Fortune’s Weave with all its social dynamics and heavy story bits but I somehow managed to play long enough to discover a gameplay loop that has me completely obsessed. 

 

 

Casuality [Penny Arcade]

New Comic: Casuality

Homecoming [Penny Arcade]

God damn, Mork drew his ass off on this shit. I was just typing that he seemed to have a sparkle in his voice as I told him about World of Warcraft Forever, but he just called to write the strip and he was already installing it. Soooo…

Homecoming [Penny Arcade]

New Comic: Homecoming

12:42

Racing Trains [The Daily WTF]

Robert G sends us a WTF from the outside, but I think we can come up with some hypotheses about what actually happened.

Robert was traveling by train. For complicated train reasons, the car he was seated on was not actually scheduled to continue with the train he was booked to ride, the replacement car missed its scheduled arrival, and the result was that Robert was left at a station needing to rebook his travel.

Now, given that this is in a region of the world with a fairly robust train network, this represented a hiccup of thirty minutes in a many hour trip. Not a particularly big deal. But Robert didn't want to get stranded any longer than he had to, so he took out his phone, fired up the train company's app, and made sure he had a reservation on the next train. He got the confirmation, and grabbed a coffee while he waited.

Not quite thirty minutes later, Robert boarded the train and went towards his reserved seat. Another passenger approached from the opposite direction, and they met at the seat. "Excuse me," Robert said, "but I reserved this one."

"No, I reserved this one."

Being reasonable people, they both pulled out their phones and pulled up their apps. Sure enough, each one of them was confirmed for that seat. Since the email confirmation had more details than the app provided, they both pulled up the email. Same seat. Both confirmed. The timestamp of the reservation? The same, down to the second.

"I don't think the conductor will let us sit in each other's laps," the other passenger said.

As it turned out, there was enough room on the train that no lap sitting was required. Everyone got a seat, even if it wasn't the one they reserved.

But the story stuck with Robert. A major transit provider had a reservation system that clearly had a race condition. It seemed like the most basic example of race conditions too, the kind you're shown in college to explain the basic concept.

"Check if the seat is reserved, and if it's not, reserve it." Because that sequence isn't atomic, two different threads could do the check, see it's not reserved, and then reserve it. I suppose it's at least something that one reservation doesn't overwrite the other, which at least indicates that a reservation is its own record.

The real question is this: this has to happen with some frequency, so how has nobody fixed it?

The real answer: they probably just don't care, either because they literally don't care, or because their software is such a trashfire that this doesn't even rise to the top ten bugs. It's likely a mix of both.

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

12:35

How Technology Empowers—and Imperils—Dictators [Schneier on Security]

This essay was written with Seva Gunitsky, and originally appeared in Foreign Affairs.

Two weeks after Moscow’s full-scale invasion of Ukraine in March 2022, the Russian TV Channel One editor Marina Ovsyannikova burst onto the set of the evening newscast. She held up a hand-drawn sign behind the anchor’s head that read: “Stop the war. Don’t believe propaganda. They’re lying to you!” She shouted, “No to war!” until she was dragged away.

No one has protested the war on Russian television since then, partly as a result of tighter security and a general climate of fear. But in October 2024, Margarita Simonyan, one of Russia’s chief propagandists, gave another explanation. A growing number of the RT network’s anchors, she explained to an interviewer, are not real. “That face doesn’t exist. We generated the voice and everything else.” They were, she meant, produced by artificial intelligence. In a follow-up interview with the newspaper Vedomosti, she spelled out the logic: “These anchors don’t need a salary or insurance,” she said. “They won’t get arrested, and the police won’t search their homes. It’s all wonderful and terrifying at the same time.”

This is AI’s promise to every autocratic ruler: the ability to maintain control without delegating tasks to unreliable humans. It is an extremely attractive prospect. No matter how powerful, dictators have always needed subordinates—censors, propagandists, security guards, police officers, intelligence analysts, and local bureaucrats—to carry out their orders. But subordinates always pose a potential threat. They could shirk, steal, lie, leak, or conspire against their boss. As a result, one of the oldest dilemmas facing autocrats is how to empower agents to carry out orders without also enabling them to turn against their leader. Autocrats have usually managed the tradeoff by filling key positions with mediocrities whose incapacity is, as the political theorist Hannah Arendt put it, “the best guarantee of their loyalty.” But this inevitably makes it harder for dictatorships to govern and survive crises.

AI appears to offer autocrats two ways to resolve this long-standing dilemma. First, it means they can now easily monitor and discipline their followers in real time. A change in a local official’s spending habits or meeting schedules can be flagged automatically by an AI system, without any need for self-interested informants. Second, AI can eliminate underlings altogether. By automating tasks that once required human discretion, rulers can replace unreliable intermediaries with faithful algorithms that cannot be bribed or manipulated. Officials in autocratic regimes have said as much. AI “can definitely replace half of officials,” Russia’s Digital Development Minister, Maksut Shadaev, told a Moscow data forum in April 2025, adding, “maybe slightly more.”

Yet these promises of a self-running state are illusory, because AI is never truly autonomous. Systems have to be built and maintained by a cadre of engineers and data scientists whose expertise political leaders cannot evaluate. The ruler who turns to AI to reduce a dependence on unreliable humans may end up relying, more blindly than before, on the few AI specialists who keep the system running. And this new digital Praetorian Guard may have the same weaknesses as the old, which makes it a threat to the ruler.

Eyes and Ears

China is in the lead when it comes to developing AI systems that can monitor or replace subordinates. Smart city programs in Beijing, Shanghai, and other urban centers use AI to track performance and centralize oversight of local officials who once operated in comfortable obscurity, subjecting them to an algorithmic performance review. China also developed a “Zero Trust AI system,” which it deployed across roughly 30 counties and cities over the past decade. This system cross-referenced more than 150 government databases to catch embezzlement and nepotism by local officials. It worked a little too well, flagging 8,700 officials before local governments, under pressure from the bureaucrats it was monitoring, began rolling it back. But Beijing has not gotten rid of its new e-government portals, which use automated systems for issuing documents and permits, reducing face-to-face interactions that bred petty corruption or favoritism. The Chinese Communist Party, meanwhile, has plowed ahead with researching what it calls “thought management,” or how to use AI to create microtargeted propaganda. China’s army of Internet commentators, once composed of paid humans, is already being replaced by bots.

Russia has been pursuing the same goals with a similar fervor. Moscow’s citywide facial recognition system, deployed across 200,000 cameras, was used to identify and apprehend protesters during the 2021 antigovernment demonstrations. The central government has started using automated data collection and aggregation to bypass regional officials who could taint the data or use it to promote their own interests. In 2023, Russia’s Internet regulator launched Oculus, an AI system that scans hundreds of thousands of images per day for prohibited content, including political content—orders of magnitude beyond what human censors could process. The Russian security service’s Meliorator tool has been used to create over a thousand profiles of fictitious Americans, primarily on X (formerly Twitter), to spread Kremlin narratives at a fraction of the cost of human troll farms.

Other autocracies are following in Beijing’s and Moscow’s footsteps. In April 2025, the United Arab Emirates created a Regulatory Intelligence Office that uses AI to draft and amend federal laws, work once done by legislative staff. A year later, UAE Prime Minister Sheikh Mohammed bin Rashid al-Maktoum announced that autonomous AI agents would take over half of the federal government’s operations within two years and that “the performance of ministers, directors general, and entities will be assessed based on their ability to adopt this transformation.”

These innovations may reduce the number of bureaucrats autocrats need. But they cannot actually create what dictators want most: a self-running state. Even AI-generated television anchors need people to build and maintain them, and censorship systems need people to retrain them as the vocabulary of dissent shifts. Behind every AI model, there is a small group of engineers and data scientists doing essential maintenance, and their work is so technical that rulers cannot understand it.

This dependence thus becomes another form of power. The engineers who maintain an autocrat’s surveillance apparatus, for example, can shape what the ruler sees. They decide what information is important enough to pass along and in what form to present it. They determine which threats get flagged, and they can adjust the algorithms that select what content gets suppressed and who gets arrested. And they can do this without anyone in the palace noticing. Autocrats who turn to AI to escape a dependence on unreliable subordinates have only transferred their vulnerabilities onto a new group of officials.

In fact, this new Praetorian Guard could be more dangerous than previous elites. That is in part thanks to the opacity of AI technology but also because this clique is much smaller. Roman emperors typically had tens of thousands of people serving in the Praetorian Guard; modern autocrats rely on standing armies. But with AI, autocrats will need only a small clique of engineers to build and maintain large-scale AI systems. This might benefit rulers since they will have fewer people to watch, yet it also concentrates points of failure, since a devastating disruption requires only a handful of engineers and makes it easier for members to scheme against the leader. The new guard’s members may also be indispensable. A dictator can replace generals without destroying the army. But running complex machine-learning systems requires such a specialized set of skills that engineers can be difficult to replace without disruptions, giving these actors great leverage.

The Indispensables

The importance of tech workers to autocracies has already become apparent. When IT specialists began to flee Russia after the full-scale invasion of Ukraine in February 2022, for example, the Kremlin responded not with threats but with inducements, offering tech workers deferments from conscription. It exempted their firms from taxes and subsidized their mortgages. When Russian President Vladimir Putin ordered the mobilization of 300,000 men seven months later, IT workers were granted full-on waivers. This might seem like overkill, given that an AI system needs only a few engineers to run it. But a regime cannot know in advance which engineers it will need, and it cannot train replacements quickly, so it has to hold on to the entire talent pool from which these few are selected.

These measures did not stem the outflow of roughly 100,000 specialists—about a tenth of Russia’s tech workforce—over the course of 2022. But Moscow stuck to bribery, because expertise is extremely difficult to conscript at gunpoint and because conscripted experts might be less likely to do as instructed. Even so, money and privilege cannot guarantee that these elites will stay loyal. Autocrats should recall the lesson of the original Praetorian Guard: for a time, it provided Roman emperors with protection and security. But then the praetorians discovered their own indispensability, and by the second century, they were auctioning off the empire to the highest bidder. This new set of elites can do the same. In June 2023, when the column of Yevgeny Prigozhin‘s Wagner paramilitary company moved up the highway toward Moscow, Putin depended on his security services to tell him what was happening. Future rulers in Putin’s position will receive such warnings through machines: intercepted communications sorted by software, camera feeds filtered through recognition systems, regional reports compiled into dashboards. The engineers who run those systems could strike a deal with an upstart challenger and then drag their feet. They could delay the data, let alerts arrive a few hours late, degrade feeds at inconvenient moments, or make a recognition system stop functioning. In that scenario, the ruler would be operating blind. The Praetorian Guard did not need to kill Emperor Nero to replace him. It simply had to abandon him for a rival.

These programmers are unlikely to seize power for themselves, because they are unlikely to carry what coup leaders ultimately require: guns. But there is already precedent for engineers using their power to shape leadership challenges. In 1991, when the Soviet army attempted to seize control from Mikhail Gorbachev, a handful of programmers at the Relcom network kept information flowing abroad and relayed Russian President Boris Yeltsin’s decrees to audiences inside the country and abroad. The plotters could not stop the news of Yeltsin’s defiance from spreading, and the coup collapsed within three days.

Slimming Down

AI will not let autocrats dismiss all their enforcers. Someone still has to make arrests and run the prisons, and autocrats can buy loyalty by offering supporters state jobs. But it will let authoritarians downsize, and the number of officials ultimately matters less than how the ruler oversees them. Keeping track of scheming or incompetent subordinates has always been the autocrat’s chief burden. AI could lighten it, letting rulers watch their officials more closely without paying as much active attention.

In the near term, then, AI may greatly benefit autocracies. Despite the many obstacles to deployment, the technology is already bringing autocratic regimes the upsides of cheaper surveillance, smarter censorship, and fewer human subordinates to fear and distrust. But for autocratic rulers, the temptations of artificial intelligence may re-create the same trap they are seeking to escape. The more a regime depends on AI, the more it depends on the people who keep AI running. And those people, like every other praetorian class in history, will quickly discover what their indispensability is worth.

11:56

Colonoscopy Post-Mortem [Whatever]

First, so as not to bury the lede: It went great, there’s nothing in my colon they are the least bit concerned about, and I don’t have to come back for another colonoscopy for a decade, which I think is an optimal state of affairs.

Second, I’m 57 and really should have had a colonoscopy before this date, and I don’t really have any excuse for not having done so, other than laziness and procrastination. It took a one-two punch of a couple of close friends my age having (thankfully eminently treatable) cancer diagnoses to motivate me to get this thing done. My family doesn’t have a history of colon cancer, nor did I have anything physical that suggested I might have something to worry about in that area, but I’m at an age where shit happens (pun intended), and if it does, it’s best to have it dealt with as early as possible, especially down there. So I finally made the appointment.

I have to say that none of it was as unpleasant as I had been led to suggest. The day-before prep, which requires fasting and chugging a solution to clear out one’s bowels, was uniformly described to me as the worst part of the whole thing. Spoiler: It was, but in my case I didn’t find it all that horrible. My bowel-clearing solution was this gunk called Clenpiq, which was no taste treat (my bottles claimed to be “cranberry flavor,” which, well, no), but also wasn’t the very worst thing I ever drank, either. As I noted on Bluesky the other day, I’ve had energy drinks which tasted worse. It also was relatively small in volume (a single 8oz cup per dose, with two doses several hours apart), which is I understand a marked improvement from earlier times, when prep included drinking something on the order of a gallon of bowel-voiding stuff.

The Clenpiq was to be accompanied with several cups of other clear liquid; I chose Sprite and water for these. I wasn’t allowed to eat solid food at all during prep day, which I thought was going to be a real problem, but wasn’t. One, I ate more Jell-O on prep day than I think I ever had on any day in my life prior to that point, and while Jell-O isn’t exactly hearty, it kept my stomach from being entirely empty. Two, I think I psyched myself into not being all that hungry on prep day, especially since I knew everything I was putting into myself was going to be explosively voided later in the evening, and I didn’t want to make that part any more annoying (or gross) than it was already going to be.

Krissy has prepared for my evening. She has, truly, put the commode in accommodations.

— John Scalzi (@scalzi.com) 2026-10-06T21:27:59.610Z

And how was that part, the part where my bowels were explosively voided? Well, again, not a great time, and not an experience I’m in a rush to have again any time soon, but also not nearly as bad as some other people apparently experienced. The whole thing was like having pre-planned, well-managed diarrhea — I knew what was coming, I knew when it was coming, and I was able to plan for it when it happened. There were no surprises or accidents, I just headed to the bathroom when my body was telling me I should. The key here really was preparation.

(It also helped that a few years ago we got a fancy toilet with a bidet, which meant my ass ended up being less chapped than it might otherwise have been. Bidets are awesome, folks. Not just for colonoscopy prep, but also for colonoscopy prep.)

The worst time of the prep for me was getting up at 2am for a second bottle of Clenpiq and then staying up for the immediate aftermath. I had a couple mild episodes of nausea, in which it felt like I might need to vomit, but it turned out in both cases what I needed to do was clear out the other end instead. It was a weird and unpleasant correlation that I hope not to have to experience again, but once I figured out what was going on I could deal with it. None of this was great, but again, it was perfectly manageable. I even ended up getting a fair amount of sleep; more than six hours in total. I was very happy about that. Bluntly, I thought I would be spending the entire night shitting my brains out.

As for the colonoscopy itself: I don’t remember a single thing about it. We arrived at the hospital, they did my intake questions and prep, they wheeled me into the operating room, they put the propofol into my IV line, and the next thing I knew I was being wheeled out to a recovery room. A few minutes later I was dressed and Krissy and I were off to Culver’s to have an early lunch. We got to the hospital at 8:30, I went in for prep at nine, we were out of the door of the hospital just before eleven am. I was prepared to be groggy and mentally out of it for the rest of the day (there is a reason I excused myself from social media and the online world on Wednesday), but honestly by the time we got home before noon I was fine, if tired. I watched YouTube videos and took naps all afternoon. It was a perfectly nice way to spend the day.

Also, in case you were wondering: no residual soreness from the colonoscopy. As far as my conscious experience is concerned, I went to the hospital to have a really deep nap. I will tell you that prior to this procedure I had some mild anxiety about anesthesia because I’ve never had any before — I’ve never had a surgical procedure that required more than a local. My experience with this has alleviated those concerns. Turns out they do know what they’re doing.

All of which is to say, for those of you out there of an age with me, who still haven’t gotten a colonoscopy: They have this thing wired. The prep is not a fun time but it’s really not horrible, especially if you plan for it, and the procedure itself is, from an experiential point of view, literally nothing. And now I don’t have to worry about my colon for years. When one is in one’s mid-50s, having one less physical thing to worry about is a very comforting thought. If you’re in the US and your insurance covers it, get it scheduled (and everywhere else that doesn’t have to worry about the US’ miserable insurance scam, go straight to the “get it scheduled” part). It’ll be fine. You’ll be fine. It’s worth doing.

As a final note, there’s a reason that I’m clutching a teddy bear in my photo above. When I was in the OR for my colonoscopy, they had me prop myself up on my side and gave me an additional pillow to hold. The nurse told me that was my teddy bear and to give it a name, which I did, shortly before the lights went out. Apparently my first words to Krissy as I was wheeled into the recovery room was to ask for my teddy bear, which confused her but gave the nurses a chuckle. I don’t remember asking for the teddy bear, but later, when I could remember things, Krissy telling me that I did ask for it suggested a course of action.

So everyone, meet Aloysius the Colonoscopy Bear, who we picked up after lunch because, painless or not, I still deserved something for having my colonoscopy done. Aloysius was cheaper than a guitar, I’ll tell you that much. And cuter.

— JS

11:42

Build Your Own Post-training Pipeline [Radar]

This is the final post in a four-part series about post-training. If you missed them, check out part 1, part 2, and part 3.

Time to get your hands dirty! I’ll take you through implementing the key pieces of the classic ChatGPT pipeline: SFT, then reward model training, then PPO. The goal isn’t to reproduce InstructGPT, because that took a large team and thousands of GPU-hours. But getting hands-on will help make the concepts from this series concrete, so that when you look at each stage’s code, you understand what it’s doing to the model and why.

We’ll use Qwen2.5-1.5B as the base model. It’s small enough to train on a single node with a few GPUs but large enough that you can observe real behavioral changes from post-training.

For SFT, you’ll use torchtune, Meta’s PyTorch-native fine-tuning library. For RL, you’ll use verl (from ByteDance’s team), a production-grade RL post-training framework built on Ray. verl was designed from the ground up for distributed RL training with proper rollout orchestration, and it’s what you’d actually reach for if you were doing this at scale.

You’ll need a machine with at least 2 GPUs (4 or 8 is better) and around 80 GB of total GPU memory.

You’ll likely use an LLM to flesh out the boilerplate and get this running end to end. I used LLMs pointed to the recent docs to generate these. What matters as you read the code in this section is that you understand the config choices and data formats well enough to direct that process and debug when something goes wrong.

Stage 1: SFT on demonstrations

SFT is the easiest stage to implement. You’re taking the pretrained model and training it on {prompt, response} pairs using the same next-token prediction loss from pretraining, except you only compute loss on the response tokens.

Your SFT data should be in a conversational format. Here’s what a single example looks like:

{
    "messages": [
        {"role": "user", "content": "Why do people like golden retrievers?"},
        {"role": "assistant", "content": "Golden retrievers are one of the most 
popular dog breeds for good reason. They have a gentle, friendly temperament 
that makes them great family dogs. They're patient with kids and tend to get 
along well with other animals. They're also highly trainable and eager 
to please, which is why they're commonly used as service dogs and therapy dogs. 
And honestly, the goofy enthusiasm doesn't hurt either. They never really grow 
out of that puppy energy."}
    ]
}

Remember what a pretrained model does with “Why do people like golden retrievers?” It might spit out “Why do people like labrador retrievers?” because it’s just completing patterns. Your SFT data is what teaches it to actually answer the question instead.

In practice, you’d have thousands of these examples covering a range of tasks: question answering, summarization, creative writing, coding help, multiturn dialogue, and refusals for harmful requests. For this walkthrough, assume you have a JSONL file of these conversations.

torchtune uses YAML configs and built-in recipes. Here’s what a config might look like:

# sft_config.yaml

# Model
model:
  _component_: torchtune.models.qwen2_5.lora_qwen2_5_1_5b
  lora_attn_modules: ['q_proj', 'k_proj', 'v_proj', 
'output_proj']
  lora_rank: 64
  lora_alpha: 128

# Tokenizer
tokenizer:
  _component_: torchtune.models.qwen2_5.qwen2_5_tokenizer
  path: /path/to/Qwen2.5-1.5B/vocab.json
  merges_file: /path/to/Qwen2.5-1.5B/merges.txt
  max_seq_len: 2048

# Checkpointer — loads the pretrained weights
checkpointer:
  _component_: torchtune.training.FullModelHFCheckpointer
  checkpoint_dir: /path/to/Qwen2.5-1.5B
  output_dir: ./sft_checkpoint
  model_type: QWEN2

# Dataset
dataset:
  _component_: torchtune.datasets.chat_dataset
  source: sft_data.jsonl
  conversation_style: sharegpt
  max_seq_len: 2048
  train_on_input: false           # only compute loss on the 
                                    assistant's response tokens

# Training
seed: 42
epochs: 3
batch_size: 4
gradient_accumulation_steps: 4    # effective batch 
                                    size of 16
optimizer:
  _component_: torch.optim.AdamW
  lr: 2e-5
  weight_decay: 0.01
lr_scheduler:
  _component_: torchtune.training.lr_schedulers.get_cosine_schedule_with_warmup
  num_warmup_steps: 50

dtype: bf16
compile: false

Then launch it:

tune run lora_finetune_distributed --config 
sft_config.yaml

What actually matters in this config:

  • train_on_input: false masks the loss on prompt tokens so the model only learns to generate good responses, not to mimic user messages. If you accidentally set this to true, the model wastes capacity learning to produce prompts.
  • A learning rate of 2e-5 is the standard starting point for SFT. That’s aggressive enough to shift behavior in a few epochs but not so large that you destroy the pretrained weights.
  • Use LoRA instead of full fine-tuning. For a 1.5B model you could do either, but LoRA is the practical default because it’s faster, uses a lot less GPU memory, and at this model size the quality gap is negligible. At larger model sizes, LoRA becomes even more useful in saving compute.
  • 3 epochs because the dataset is small. InstructGPT trained for 16 epochs on ~13K examples. Smaller datasets need more passes, but watch validation loss for overfitting. This is something you should tune to see different results across different-sized datasets.

After this stage, try chatting with the model and try a bunch of different comparisons to the base model. Ask it “Why do people like golden retrievers?” and you should get a real answer now, not a list of related questions. The model should be able to hold a conversation and follow basic instructions. It’s already dramatically more useful than the base model, even if the responses aren’t always great.

Realistically, you should set up a good evaluation (“evals”) to assess the quality of the model, hyperparameter tune, and determine your data mix. In this section, you’ll just focus on looking at the code.

Stage 2: Training the reward model

For your reward model, start with your data of preference pairs, which will look like this:

{
    "prompt": "What's the capital of that country that celebrates with a lot 
of colored powders?",
    "chosen": "You're probably thinking of Holi, the festival where people 
throw bright colored powders. That celebration is most famously associated 
with India. The capital of India is New Delhi.",
    "rejected": "The capital of that country that celebrates with a lot of 
colored flowers is Amsterdam, the Netherlands, known for its tulip festivals."
}

The “rejected” response isn’t just wrong about the festival. It also misread “powders” as “flowers” and jumped to an incorrect answer.

For reward model training, you’ll use TRL’s RewardTrainer. Reward model training is a straightforward classification task.

from transformers import AutoModelForSequenceClassification, 
AutoTokenizer
from trl import RewardTrainer, RewardConfig
from datasets import load_dataset

# Start from the SFT checkpoint — it already understands 
the response distribution
model = AutoModelForSequenceClassification.from_pretrained(
    "./sft_checkpoint",
    num_labels=1,
    torch_dtype="bfloat16",
)
tokenizer = AutoTokenizer.from_pretrained("./sft_checkpoint")
tokenizer.pad_token = tokenizer.eos_token

dataset = load_dataset("json", 
data_files="preference_data.jsonl", split="train")

training_args = RewardConfig(
    output_dir="./reward_model",
    per_device_train_batch_size=8,
    num_train_epochs=1,    # just 1 epoch — reward models overfit fast
    learning_rate=1e-5,    # lower than SFT, be gentle
    bf16=True,
    max_length=2048,
    logging_steps=10,
    save_strategy="epoch",
)

trainer = RewardTrainer(
    model=model,
    args=training_args,
    train_dataset=dataset,
    tokenizer=tokenizer,
)

trainer.train()
trainer.save_model("./reward_model/final")

As you skim the code, take note of these three things:

  • Start from an SFT checkpoint, not a base model. The reward model needs to understand the distribution of responses it’ll be scoring, and the SFT model is closer to that distribution.
  • 1 epoch only. Reward models overfit quickly. The InstructGPT team found only 1 epoch was needed.
  • Learning rate of 1e-5, lower than SFT. Smaller updates.

After training, check the reward model by scoring a few responses manually. Give it a clearly good response and a clearly bad one for the same prompt and make sure the good one gets a higher score. If this basic test fails, something is wrong with your data or training. Don’t skip this step: It’s better to catch problems sooner than many GPU hours later!

Stage 3: PPO with verl

Now you can take the SFT model and optimize it against the reward model using PPO. verl handles the hard parts: coordinating rollout generation across workers, managing the four models that need to be in memory simultaneously (policy, reference, reward, critic), and orchestrating the update loop.

First, prepare your prompts. verl expects parquet format. Each row needs a prompt field containing the tokenized and chat-template-formatted prompt. Here’s an example of preparing it:

import pandas as pd
from transformers import AutoTokenizer
from datasets import load_dataset

tokenizer = AutoTokenizer.from_pretrained("./sft_checkpoint")

raw_prompts = load_dataset("json", data_files="rl_prompts.jsonl", split="train")

def format_prompt(example):
    messages = [{"role": "user", "content": example["prompt"]}]
    formatted = tokenizer.apply_chat_template(
        messages, tokenize=False, add_generation_prompt=True
    )
    return {"prompt": formatted}

formatted = raw_prompts.map(format_prompt)
df = pd.DataFrame(formatted)
df.to_parquet("data/rl_prompts.parquet")

The prompts for RL don’t need labels. The reward model provides the signal. You just need a diverse set of prompts that covers the types of requests your model expects to see when you deploy it.

Next, define the reward function. verl lets you wrap your reward model in a function that takes a batch of prompts and responses and returns rewards:

# reward_fn.py — verl will call this during training
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer

class RewardFunction:
    def __init__(self, reward_model_path="./reward_model/final"):
        self.model = AutoModelForSequenceClassification.from_pretrained(
            reward_model_path, torch_dtype=torch.bfloat16
        ).cuda().eval()
        self.tokenizer = AutoTokenizer.from_pretrained(reward_model_path)
        self.tokenizer.pad_token = self.tokenizer.eos_token

    def __call__(self, prompts, responses):
        """
        prompts: list of prompt strings
        responses: list of response strings
        Returns: list of scalar rewards
        """
        texts = [p + r for p, r in zip(prompts, responses)]
        inputs = self.tokenizer(
            texts, return_tensors="pt", padding=True,
            truncation=True, max_length=2048
        ).to(self.model.device)
        with torch.no_grad():
            rewards = self.model(**inputs).logits.squeeze(-1)
        return rewards.tolist()

Now configure the PPO training run. verl uses YAML configuration files that specify the models, hyperparameters, and infrastructure:

# ppo_config.yaml
data:
  train_files: data/rl_prompts.parquet
  prompt_key: prompt
  max_prompt_length: 1024
  max_response_length: 1024

actor_rollout_ref:
  model:
    path: ./sft_checkpoint
  actor:
    optim:
      lr: 1e-6                    # very low — RL updates should be gentle
      lr_warmup_steps: 10
    ppo_mini_batch_size: 64
    ppo_micro_batch_size: 8       # adjust based on GPU memory
    ppo_epochs: 4                 # number of PPO update passes per batch
    clip_ratio: 0.2               # PPO clipping — standard value
    kl_penalty_coeff: 0.1         # KL penalty to prevent reward hacking
    entropy_coeff: 0.01           # small entropy bonus for exploration
  rollout:
    temperature: 0.7
    top_p: 0.9
    n: 1                          # 1 response per prompt per rollout
    tensor_model_parallel_size: 1
  ref:
    log_prob_micro_batch_size: 8

critic:
  model:
    path: ./reward_model/final    # initialize critic from reward model
  optim:
    lr: 1e-5
  ppo_micro_batch_size: 8

reward_model:
  path: ./reward_model/final
  micro_batch_size: 8

trainer:
  total_training_steps: 500
  save_freq: 100
  test_freq: 50
  project_name: post_training_walkthrough
  logger: wandb

A few important hyperparameters in the config:

  • The learning rate is an order of magnitude smaller than SFT, default at 1e-6. (“Actor” refers to the policy model.) RL updates are noisier, and you want to move slowly. If the model starts producing gibberish or repetitive text, your learning rate is probably too high.
  • The KL penalty coefficient is 0.1, and recall that it controls how much the model is penalized for drifting from the SFT checkpoint. 0.1 is a reasonable starting point, but you’ll likely need to adjust. If you see reward hacking, turn it up, but if the model doesn’t really change, you need to lower it.
  • The clip ratio is default set to 0.2, from the original PPO paper. This likely doesn’t need tuning. It prevents any single update from changing the policy too much.

Launch the training:

python -m verl.trainer.main_ppo \
    --config ppo_config.yaml \
    --n_gpus 4

During training, watch for a few things. The reward should generally trend upward, meaning the model is learning to produce responses the reward model likes. The KL divergence should increase but not explode, in which case the model is drifting too far and you need a higher KL penalty. And periodically generate some responses from the current checkpoint and read them yourself. Metrics can be unreliable on their own, and your own judgment of output quality can be more reliable. Of course, ideally, you have a held out evaluation set that you’re working with that can help you test different tasks you care about.

Putting it together

The full pipeline runs on stage after the next: SFT first, reward model second, PPO third. Each stage depends on the previous one. The SFT model provides the starting point for both RL training and the reward model. The reward model provides the signal for PPO. And PPO produces the final model.

This is, structurally, the same pipeline that produced InstructGPT and the first version of ChatGPT. The scale is obviously different. They used 175B parameter models, 40 labelers, and far more compute, but the mechanics are identical. Your 1.5B model won’t write poetry as well as the latest GPT, but it will go from producing “Why do people like labrador retrievers?” to actually answer “Why do people like golden retrievers?” with a conversational response. That’s post-training doing the heavy lifting.

If you’re doing this for real, what will eat most of your time isn’t the code but the data: curating good SFT demonstrations, collecting reliable preference labels, and building a prompt set for RL that covers the right distribution of tasks. But there are some great open source datasets out there that you can get started with.

The training infrastructure is largely solved: You can point an LLM at the torchtune and verl docs and have it scaffold a working pipeline for you in an afternoon. But no amount of tooling fixes bad data or a reward model that scores the wrong things highly. Understanding what each stage needs and why is what lets you direct that process effectively.

Grrl Power #1502 – Very demure, very distractible [Grrl Power]

I enjoy characters with emotive ears. Don’t be surprised if Sydney gets turned into a werefox at some point.

Okay, I probably won’t do that… Not permanently anyway.

It’s hard to get on the “secret” list once you’re on the usual list, but it’s possible, and nearly inevitable to be on both the secret and regular list at the same time. It’s about the order in which you are listed.

I don’t know if Sydney would have an extensive Celebrity Hall Pass list. She’s not above a little ogling and giggling with her friends over a cute boy, but she’s rarely overtly sexual. Most of her celebrity crushes are age inappropriate, but that’s because most of the media she consumed as a girl starred middle-aged actors. Her first exposure to Jerard Butler was formative. She enjoyed Tom Baker’s Doctor, but she was young enough when she was first exposed to Dr. Who that her fantasies just involved her being a companion and having space and time adventures. Guys that are 20+ years her senior.

That’s the range most of her list falls in, and if she ever met any of them IRL, she’d hopefully realize how much older most of them are, and would revise her list. Given that she has a comic shop with the clout to attract industry luminaries, it’s actually possible she’ll get to meet quite a few actors.


Oh, look who it is in the vote incentive. The NSFW version is finally up at Patreon. Plus a bonus pic.

I think she would get in trouble for doing this. She’d mess up the… floor of the waterfall? Is that what it’s called? The receiving pool? No, probably not that. Anyway, she’d churn things up and cause a ton of weird erosion.

Since you might be wondering, Niagara Falls is about 165 feet high, so Babezilla obviously doesn’t have to be full sized. I’d say she’s about 175-180 feet tall here?


Double res version will be posted over at Patreon. Feel free to contribute as much as you like.

11:07

Extending Guix [Planet GNU]

Guix is all about empowering people, so it should come as no surprise that one can extend it with new guix commands. You can show the commands available in your current Guix through the help command:

$ guix help

If you are using a recent Guix, there are a number of extensions at your disposal, they are available as individual packages, and as a meta-package providing a collection of extensions. Try them out with:

$ guix shell guix guile guix-extension-collection

I'm adding the guix and guile packages to the shell because we want the different Guile and Guix search paths to be adjusted in the shell. To know more about why this is needed, read Search Paths.

You will notice that the help menu has been extended with information about the available extensions:

$ guix help

...

  extension commands
    explore   interactively explore a Guix System configuration
    removals  keep up to date with package removals
    compose   docker compose compatibility layer
    toys      Explore packages and services through REST API
    xsearch   search for packages using a fast Xapian cache

...

Since this blog post is called "Extending Guix", let's write an extension, shall we?

How does Guix locate extensions?

Guix locates extensions by looking up GUIX_EXTENSIONS_PATH. Recently Guix has introduced a new way of writing extensions. The old way would search extensions under /path/to/guix/extensions and the extensions modules would be named (guix extensions NAME). The new schema expects extensions to be under /path/to/SCHEMA_VERSION; the name of the module will still be (guix extensions NAME).

The advantage of this new schema is the Guix can treat the extension module as a standard Guile module, meaning that the runtime is able to find the compiled .go file of the extension. The old schema was relaying on runtime evaluation; the load machinery was not handling compiled files. This has a considerable improvement on performance, so you are encouraged to update any old extension to the new schema.

Writing an extension

Enough introductions, let's write a basic extension.

The first step is to create the project structure. Remember that the extension machinery expects modules to be named (guix extensions NAME).

We start by creating, in our project root, the directories for the extension.

$ mkdir -p guix/extensions

Now, we create the file guix/extensions/hello.scm with the following contents:

(define-module (guix extensions hello))

A blank canvas...

We import (guix scripts) to get the define-command macro. We declare it and export it so the extension machinery can find the command in the public interface of the module.

(define-module (guix extensions hello)
  #:use-module (guix scripts)
  #:export (guix-hello))

(define-command (guix-hello . args)
  (category extension)
  (synopsis "say hello")
  (display "hello, I'm a Guix extension!\n"))

With this, we already have a working Guix extension. We can run the extension like this from the root of the project.

$ GUIX_EXTENSIONS_PATH=$PWD guix hello
hello, I'm a Guix extension!

Since we would like our extension to be discoverable by users, we will add a help message which will make the extension display in the guix help menu.

We will use (srfi srfi-37) to write the option parser, and (guix ui) for the internationalized strings; you will see that I import these modules when I show you the complete extension. Let's focus on the option definitions:

(define (show-help)
  (display (G_ "Usage: guix hello\n"))
  (display (G_ "Just say hello, it's not that complicated.\n"))
  (display (G_ "
      --help             display this message"))
  (newline))

(define %options
  (list (option '(#\h "help") #f #f
                (lambda args
                  (leave-on-EPIPE (show-help))
                  (exit 0)))))

Since we are only handling the arguments for showing the help message, we just need to call the parser at the start of the command:

(define-command (guix-hello . args)
  (category extension)
  (synopsis "say hello")
  (parse-command-line args %options (list '()))
  (display "hello, I'm a Guix extension!\n"))

Here you have the complete extension module:

(define-module (guix extensions hello)
  #:use-module (guix scripts)
  #:use-module (guix ui)
  #:use-module (srfi srfi-37)
  #:export (guix-hello))

(define (show-help)
  (display (G_ "Usage: guix hello\n"))
  (display (G_ "Just say hello, it's not that complicated.\n"))
  (display (G_ "
      --help             display this message"))
  (newline))

(define %options
  (list (option '(#\h "help") #f #f
                (lambda args
                  (leave-on-EPIPE (show-help))
                  (exit 0)))))

(define-command (guix-hello . args)
  (category extension)
  (synopsis "say hello")
  (parse-command-line args %options (list '()))
  (display "hello, I'm a Guix extension!\n"))

With that, our extension will be present when we ask Guix for help:

$ GUIX_EXTENSIONS_PATH=$PWD guix help

...

  extension commands
    hello  say hello

It also takes a --help flag:

$ GUIX_EXTENSIONS_PATH=$PWD guix hello --help
Usage: guix hello
Just say hello, it's not that complicated.

      --help             display this message

Packaging a Guix extension

At the time of writing, we don't have a dedicated Guix build-system for extensions. Fortunately, the guile-build-system is close enough for this use case.

Let's make a package definition for our new hello extension that uses our local sources. Create a guix.scm file at the root of the project with the following contents:

(use-modules (gnu packages package-management)
             (guix build-system guile)
             (guix gexp)
             (guix git-download)
             (guix packages)
             ((guix licenses) #:prefix license:))

(define vcs-file?
  ;; Return true if the given file is under version control.
  (or
   (git-predicate
    (dirname (canonicalize-path
              (assq-ref (current-source-location) 'filename))))
   (const #t)))

(define-public guix-hello
  (package
    (name "guix-hello")
    (version "0.0.0-git")
    (source (local-file (assume-valid-file-name ".")
                        "pin-checkout"
                        #:recursive? #t
                        #:select? vcs-file?))
    (build-system guile-build-system)
    (arguments
     (list
      #:scheme-file-regexp
      #~(lambda (file stat)
          (and ((file-name-predicate #$default-scheme-file-regexp)
                file stat)
               (not ((file-name-predicate "^(guix|channels|manifest)\\.scm$")
                     file stat))))
      #:phases
      #~(modify-phases %standard-phases
          (add-after 'build 'move-to-extension-directory
            (lambda _
              (with-directory-excursion #$output
                (mkdir-p "share/guix/extensions/1.5/guix/extensions")
                (rename-file (string-append "share/guile/site/"
                                            (target-guile-effective-version)
                                            "/guix/extensions/hello.scm")
                             "share/guix/extensions/1.5/guix/extensions/hello.scm")))))))
    (native-inputs (list guix))
    (inputs (list (lookup-package-input guix "guile")))
    (home-page "https://codeberg.org/guix-extensions")
    (synopsis "Make Guix say hello")
    (description
     "This extension provides the @command{guix hello} command,
which makes Guix say hello.")
    (license license:gpl3+)))

guix-hello

We can test this new extension like this:

$ guix shell -CW -f guix.scm -- guix hello

The flags passed to guix shell are the following:

  • -C (--container): To prevent your environment from interfering.
  • -W (--nesting): To bring the current Guix you are using into the container so it can load the extension.

Since this example is using the new extension scheme, the guix command you are running must be at or newer than commit de069958fc.

Closing words

I hope you find this small introduction to Guix extensions useful and that you start to write your own Guix extensions.

I would like to encourage everyone reading this to submit their extensions to the guix-extensions Codeberg organization. The idea is to make this organization a central hub for everyone to participate in the development of useful extensions for the community.

10:21

Our genie problem [Seth's Blog]

The djinn go back millennia, powerful and elusive creatures of the supernatural. They show up across time and culture.

Three hundred years ago, Charles Perrault retold a short story about a headstrong woodcutter and his wife. On being granted three wishes from Jupiter (archetype for the genie of legend), he accidentally blurts out that he’d like sausage for dinner. One wish, gone. His wife calls him a fool, and he responds by turning her nose into a sausage. Now, with just one wish left, he’s torn between being rich (but having a wife with a sausage attached to her nose) or using the third wish to end up right where he started.

Marital harmony and common sense prevail, and he ends up where he started, but filled with regret for what could have been. Ever since, we’ve been telling stories about people who screw up their wishes.

The persistence of genie morality in our culture is bigger than a Disney marketing ploy. It’s an important reminder about responsibility, long-term thinking and how often we fail to recognize how much agency we actually have. The three are woven into a narrative that often leaves us with a sausage attached to our noses.

In this moment, the biggest technology revolution humans have ever lived through, we’re all at risk of getting our wishes wrong. Careful what you wish for. But don’t forget to wish.

07:28

It Won’t Fly [George Monbiot]

Air travel is on track to account for almost all our emissions by 2050. So why aren’t we working to reduce demand?

By George Monbiot, published in the Guardian 3rd October 2026

If a third runway is built at Heathrow, what will be the impact on the wealth of the nation? It will cost us somewhere between £23.4bn and £62.5bn. These are not my figures; they weren’t calculated by environmental groups, but by the Department for Transport.

For decades, government ministers have told us that airport expansion is essential for “growth”. As chancellor, Rachel Reeves claimed that a third runway at Heathrow would add 0.43% to GDP, figures we later discovered came from a report commissioned by the airport. The transport department’s own analysis concluded that the real addition to GDP would be between 0.03% and 0.05%: eight to 14 times less. Against this, it weighed the social and environmental costs of the project, to reveal a massive net loss. It found that a third runway would cause major harm to the health and wellbeing of up to 3 million local people.

But even these figures don’t capture it. The climate breakdown caused by the runway affects the entire planet, yet the costs to other countries aren’t counted. So again we must ask, why does the government support it?

Or does it? Though Heathrow expansion remains official policy, when asked at the weekend, Andy Burnham said the runway was “contested” and “principally it’s a matter for London and London MPs”. That sounds hopeful.

We also learned that the project has been delayed yet again. Though Heathrow announced last year that its plans were “shovel-ready”, it now admits the runway would take four years longer to build than it claimed: the estimated completion date is now 2039.

By then, or so we must hope, a government might have decided to take the climate impact seriously. If so, the new runway would need to be shuttered the moment it opened. Otherwise, according to the government’s Climate Change Committee, “almost all CO2 emissions in 2050 will be from aviation”. Heathrow’s expansion alone, the committee says, would cause more carbon emissions by 2054 than “any other sector of the economy”.

And even this is not the end of it. The committee considers only the carbon dioxide planes produce, but the other impacts are even greater. A paper in the journal Atmospheric Environment suggests that, over 100 years, the nitrogen oxides, carbon monoxide, soot, volatile organic products, water vapour and other compounds released or formed in the upper troposphere and lower stratosphere by planes raise the heating caused by flying by 115%. Over 20 years, they raise it by an astonishing 517%.

Given that crucial Earth systems may be approaching their tipping points, and heating today triggers further greenhouse gas releases from forests, wetlands and permafrost, the 20-year horizon is crucial. But these other compounds are considered in neither international treaties nor the UK’s Climate Change Act. The impact of flying is officially and massively underestimated.

While the heating effects of almost all other sectors can be greatly reduced by technology, there are simply no good solutions for aviation. Successive governments have justified airport expansion plans with the promise of “sustainable aviation fuel”. Mostly they mean turning biomass into kerosene, which is even worse than using fossil fuels. If this substitute is to reach any kind of scale, it will either swallow up existing cropland, driving starvation, or it will cause the expansion of cropland into forests, wetlands and other habitats, accelerating the environmental crisis. An analysis by the Royal Society found that meeting existing UK aviation demand (never mind future growth) with energy crops “would require around half of UK agricultural land”. That’s the opposite of sustainable.

“Sustainable aviation fuel” can also mean synthetic fuels made from captured CO2 and green hydrogen. But the Royal Society found that for existing aviation, we’d need to use between five and eight times the UK’s entire renewable electricity capacity. And still, aeroplanes would release a lethal chemical cocktail – not just CO2 but those other compounds – into the high atmosphere, so only a fraction of the problem would be solved. Worldwide, a paper in the journal One Earth found that even with a wildly unrealistic programme of technological change, in which fossil jet fuels were phased out completely by 2040, the global heating caused by aviation would more than double by 2070, thanks to rising demand.

As for hydrogen-, ammonia- or battery-powered commercial aircraft, they all fail on technical or safety grounds, even before we ask where the power comes from. The “prototypes” endlessly showcased by the airlines, which never get beyond the artist’s impression, are nothing but “perceptionware”, designed to solve political problems, not technical ones.

If Heathrow expansion goes ahead, passenger numbers in the UK are forecast to rise from 299 million in 2025 to 491 million in 2050. As other sectors decarbonise, the proportion of climate breakdown caused by flying climbs and climbs, until planes account for almost all of it.

The only effective strategy is to reduce demand. There’s a progressive means of doing so: the frequent flyer levy. Wealth and flights are strongly linked. In 2024, 48% of England’s population did not fly at all. But 5% of the population took five or more flights abroad, and these account for 33% of all journeys. Increasing demand has been almost entirely driven by frequent flyers, who are highly concentrated in the highest income quintile. The levy would not be charged on the first flight someone took in any year, but it would escalate with every additional flight.

This is just, proportionate and – unlike all the techno-bollocks – immediately dispatchable. Otherwise, we continue to subsidise a wealthy, selfish minority with our money, our health, our wellbeing and the degradation of the living planet. How is that fair?

What government support for airport expansion shows is that “growth” is simply an excuse for giving powerful lobby groups what they want. When asked whether growth was more important than net zero, Reeves replied: “Well, if it’s the No 1 mission, it’s obviously the most important thing.” That’s another way of saying money is more important than human life. But, as the figures show, it was never about growth at all. It was always about meeting the demands of the airport lobby and wealthy passengers.

Now we wait on Burnham. Will the flight of reason continue? Or will human and planetary welfare at last outweigh the lobbyists?

www.monbiot.com

04:21

How can undefined opcodes ud0 and ud1 have parameters? How undefined were they? [The Old New Thing]

Gunnar Dalsnes wondered how ud0 and ud1 could have parameters if they were undefined? “Does it mean they were not completely undefined, just undocumented and not completely implemented?”

The opcodes ud0 and ud1 have no definition, but that’s not the same as being architecturally an “undefined instruction”. They live in a purgatory where they were not assigned a meaning, but were also not officially declared to be meaningless.

Originally, these byte sequences went through the instruction decoder and happened to slip through a few cracks before somebody finally noticed, “Wait a second, I don’t know how to execute this.”

You can see this when you look at the instructions that are encoded as 00001111 11111xxx, as of the Pentium III.

Bits Bytes Opcode Operand 1 Operand 2 Meaning
00001111 11111000 0F F8 PSUBB mm mm/m64 Subtract packed bytes
00001111 11111001 0F F9 PSUBW mm mm/m64 Subtract packed words
00001111 11111010 0F FA PSUBD mm mm/m64 Subtract packed dwords
00001111 11111011 0F FB No meaning assigned
00001111 11111100 0F FC PADDB mm mm/m64 Add packed bytes
00001111 11111101 0F FD PADDW mm mm/m64 Add packed words
00001111 11111110 0F FE PADDD mm mm/m64 Add packed dwords
00001111 11111111 0F FF No meaning assigned

The byte sequences 0F FB and 0F FF had yet to be assigned a meaning. You can see how the instruction decoder could take a shortcut and say, “Well, all the instructions in this range, or at least all the ones that I care about, take an mm registers and an mm/m64 operand, so I’ll just save myself some transistors and decode all of them with two parameters (mm, mm/m64).” And then after decoding, it would use bit 3 to decide whether to set up the arithmetic unit for an add or subtract, and it would use bits 0 and 1 to decide how to subdivide the bits into saturating units.

And if you gave it a 0F FF, it would be only in that last step that the decoder would realize “Oh dear, I don’t know what to do with a bit combination of 11. I’ll raise an invalid opcode instruction.”

The invalid opcode instruction got raised after the operands were parsed.

You can see the trouble that 0F FF created when those empty slots started to get filled in by the SSE instructions.

Bits Bytes Opcode Operand 1 Operand 2 Meaning
00001111 11111000 0F F8 PSUBB mm mm/m64 Subtract packed bytes
00001111 11111001 0F F9 PSUBW mm mm/m64 Subtract packed words
00001111 11111010 0F FA PSUBD mm mm/m64 Subtract packed dwords
00001111 11111011 0F FB PSUBQ mm mm/m64 Subtract packed qwords
00001111 11111100 0F FC PADDB mm mm/m64 Add packed bytes
00001111 11111101 0F FD PADDW mm mm/m64 Add packed words
00001111 11111110 0F FE PADDD mm mm/m64 Add packed dwords
00001111 11111111 0F FF I want to put PADDQ here but I can’t

the natural place to put the PADDQ instruction is 0F FF, but people had already been using 0F FF with the expectation that it raises an illegal instruction exception. Making it a valid instruction would break those programs, so Intel had to move PADDQ to the rather awkward location 0F D4.

Bonus chatter: Undefined instructions with parameters are actually not uncommon. For example, on AArch64, there is a range of 65,536 instructions set aside as permanently undefined, so the udf instruction takes a 16-bit immediate to specify which invalid opcode you want. The PDP-10 reserved opcode 000 as a permanently illegal instruction, and it carries a register and a memory address as parameters. (Because all PDP-10 instructions carry a register and a memory address as parameters.)

There are also so-called “unofficial opcodes” which are instructions that are not part of the instruction set architecture, but for which people reverse-engineered a consistent behavior and began to rely on it. (The 6502 processor is well-known in nerd circles for having undergone this type of analysis.) The 0F FF is one of these “unofficial opcodes” that was popular enough that Intel felt pressure to maintain backward compatibility with it, even though it was never architecturally documented or supported.

Bonus bonus chatter: It appears that the mnemonic ud1 was introduced by the nasm assembler:

* Added the following new instructions: SYSENTER, SYSEXIT, FXSAVE,
  FXRSTOR, UD1, UD2 (the latter two are two opcodes that Intel
  guarantee will never be used; one of them is documented as UD2 in
  Intel documentation, the other one just as "Undefined Opcode" --
  calling it UD1 seemed to make sense.)

It seems obvious that ud1 is also the name that Intel gave internally to that legacy instruction. Otherwise, there would be no need to call the new one ud2!

The post How can undefined opcodes <CODE>ud0</CODE> and <CODE>ud1</CODE> have parameters? How undefined were they? appeared first on The Old New Thing.

02:07

[$] LWN.net Weekly Edition for October 8, 2026 [LWN.net]

Inside this week's LWN.net Weekly Edition:

  • Front: Kernel bugs; Gentoo's Chromium package; Rust smart pointers; Sashiko; Charon; LAVD scheduler; Python random-number modules.
  • Briefs: OpenSSH 10.6; RustConf recordings; Rust 1.99.0; Picard 3.0; Zig 0.17; Quotes; ...
  • Announcements: Newsletters, conferences, security updates, patches, and more.

01:14

Microsoft is overhauling and redesigning search in Windows, and it seems nice? [OSnews]

Whenever Microsoft starts messing with something like the search function in Windows, a lot of people are going to be holding their breath and expecting the worst. Well, get ready, because they’re redesigning the whole thing.

In July, we shared how we are improving the Windows Search Box with less clutter and more control. We introduced a calmer home screen, removed promotional content from web results, and gave you more choice over whether web and Microsoft Store suggestions appear.

Today, we’re carrying those investments forward with a new Windows Search experience built on WinUI 3. This modern foundation makes Windows Search faster and more efficient with resources, while delivering an even more streamlined and focused design.

↫ Anshul Rawat at the Windows Blogs

The company claims this new version of search is faster and uses less memory, which, if true, are very welcome improvements. It’s also just a single column of results now, and they’re adding inline previews for things like weather and files on your machine, as well as for answers to all kinds of queries you might normally go to an online search engine for. You can now also use search to perform all kinds of tasks in Windows, like turning on dark mode, managing windows, and so on.

All of this can be done using relatively natural language, and Microsoft claims they’ve implemented better detection of typing errors and synonyms, which is quite welcome. Weirdly enough, though, it’s not yet integrated into the Start menu, since it’s a preview just for testers, but that’s something they’re working on for future releases.

Judging by the videos and screenshots, I’m actually kind of positively surprised. This looks quite decent and nice, and if the promised performance improvements actually materialise, this may actually be an upgrade to search worth looking forward to. Of course, this is still Microsoft, so it’s just as likely they’ll screw up somewhere between now and when this goes live to regular users.

Still, I think it looks decent.

Chimera Linux: creating distribution build tooling for a small community [OSnews]

Chimera Linux is a relatively new Linux distribution, and quite a unique on at that, as it combines the core tools from FreeBSD, the LLVM toolchain, and the Musl C library instead of the usual suspects. Despite being relatively new, it still has some serious pedigree as the project was started by Nina “q66”, who was part of the Void Linux team. At Void, she maintained the various PowerPC/POWER ports of the distribution until Chimera became her sole focus.

Nina published a detailed blog post today about how the Chimera team builds the tooling for their distribution, as well as the goals it’s trying to achieve. It covers everything from a bit of personal history, the rationale behind the project, who it’s catering to, how its tooling works and why, and the infrastructure they’ve built that underpins it all. There’s a lot to process here, with one of the most interesting little details – at least to me, as someone who has two POWER9 machines – the fact that Chimera started with the ppc64le target only.

The article is an incredibly interesting look at not just the how of Chimera, but also the why, which makes for some really fascinating reading.

Wednesday, 07 October

22:56

Man-Made Horrors, Part One [Penny Arcade]

I ate some food court Chinese and now, by every observable metric, the food court Chinese is eating me. I was able to play a couple rounds of Blood on the Clocktower last night before I began to get a wobbly feeling I described as "nautical" and I have spent the last ten hours or so in a state similar to a furniture warehouse whose boss is out of town, i.e., "Everything Must Go."

22:14

Link [Scripting News]

32 is a lot of years. How much longer can this madness go on!

Link [Scripting News]

Do you know why people like Markdown? You can't hide the bullshit. It's all there for you to see. Thus the bullshit never gets added. It's so simple, I swear people are desperate for simplicity. I know I am. Where is the universal theory of the web? Actually I do know where it is. Go back to the roots, strip out all the crap, and let's make a Mardown-only web. Great for blogs, to start. Simple, fast, easy.

Link [Scripting News]

I don't care for podcasts that begin with 15 minutes of giggling between the hosts who I don't know so I don't get how cute it is. Esp if the title of the episode is something I'd like to learn more about. Why is giggling and non-funny supposed humor. The laughing I hate the most is when their cracking up over something tragic or ominous. Stop and think about what you're laughing about. Someone should do a podcast like the Markdown-only browser I'm thinking of doing.

Link [Scripting News]

That said, surprisingly Nicolle Wallace's podcast is really good, which surprised me because her two hour afternoon MSNOW show is kind-of nothing. On this show they have a lot of time to talk with one person, and maybe there are limits on the TV show that aren't on the podcast? Another theory, I listen to her podcast, and thus can't see her facial expressions. On TV she looks utterly hopeless, I'm not kidding, and she's justified. I worry about how much of this she can take. But on the podcast she comes off as she's curious, respectful, let's the guest speak (so many hosts don't). She reminds me of a Diane Rehm, a podcast host on NPR through 2016. She never let her guests talk down to us, her audience, which I appreciated. A lot of times you hear a science, history or tech topic and hope you might learn something, but they only repeat the intro over and over, with a lot of giggling, of course. BTW, Diane Rehm has a new podcast. I bet it's great, probably gets right to the point and I guarantee you there is no giggling.

21:28

Link [Scripting News]

Yesterday I wrote a piece about a Markdown-only browser, that worked nicely with HTTP. Maximally minimal. I got interested in what it would take to do this, and asked ChatGPT for a plan. I definitely don't agree with all of it, forget about the other formats, we're only interested in Markdown. No frameworks, not "try to avoid frameworks." None. Maximal simplicity for the code, make it easy to follow. No hidden bullshit. If you want to implement it, please do. Let me know if you have something to try.

20:00

Putting the “Fun” in “fundraiser” – DORK TOWER 06.10.26 [Dork Tower]

Most DORK TOWER strips are now available as signed, high-quality prints, from just $25!  CLICK HERE to find out more!

HEY! Want to help keep DORK TOWER going? Then consider joining the DORK TOWER Patreon and ENLIST IN THE ARMY OF DORKNESS TODAY! (We have COOKIES!) (And SWAG!) (And GRATITUDE!)

18:14

The Big Idea: Mia V. Moss [Whatever]

 

A lot can happen when you take a one-thousand year nap, and there may or may not be consequences when you wake up. Author Mia V. Moss takes an interesting look at false narratives and wrongfully upheld beliefs in the Big Idea for her newest novel, The Heresy of Thieves.

MIA V. MOSS:

Humans are story-powered creatures. We tell stories about the stars, the seasons, ourselves, and each other. And if we are very rich or persuasive in other ways, the stories we tell about others are the stories that get labeled ‘reality,’ whether the subject of the story agrees with that label or not.

Sometimes the labels our fellow humans foist upon us are benign. Sometimes you say “oh that’s a cute chicken figurine” to a relative as you window shop at the mall. And then forever after at Christmas you are The One Who Loves Chickens and are gifted yet another chicken figurine from a beaming well-intentioned relative, who is very proud of their talent for gift-giving.

Then there is the 1995 romcom, While You Were Sleeping. Peter, the man we believe to be the romantic male lead, spends most of the film in a coma. We are informed at the beginning of the film by the female lead, Sandra Bullock’s Lucy, that Peter is perfect. He is a rich, handsome, kind, charming, rich lawyer in Chicago. Peter has a freaking newspaper clipping of the time when, as a boy, he saved a nest of baby squirrels that had fallen from a tree! Peter is a sexy saint, genetically engineered to be Lucy’s soul mate.

Except, by the end of the film (spoilers, sorry) we know that Peter’s life is a hollow pretense. He has a girlfriend who he cheats on. He lives alone with no pets. He hardly ever makes time in his life for the people who care about him. He knocked the baby squirrels out of the nest on purpose with a rock and simply did not correct the adults when they cobbled together a story that he had rescued them and took his picture for the paper! He is no hero at all and certainly not good enough for Sandra Bullock.

So, when I set out to write The Heresy of Thieves, my big idea was this: what if the story people formed about you ended up creating a religion? Not really novel, considering exhibit A: Christianity. But what if the person they formed a religion around had no idea this had happened? What if they were really pissed, in fact, to find out about all the stuff done in their name? And what if, by giving that person their agency back, the rest of society immediately crumbled to ruin?

The concept and exploration of those consequences branches out from my overarching conceit of the Spellfall trilogy: what if two powerful entities in the midst of a messy, centuries-long mage war just wanted to remove themselves from all of the murder and dynastic backbiting and make out in a nice backyard garden for eternity instead?

Much of the book’s revisions took place during the earlier years of the COVID-19 pandemic. I watched as the US government changed the story of health and safety measures to suit corporate needs, and observed how people accepted or rejected those truths. How quickly official health messaging morphed from ‘masks are an essential and widely accepted way to mitigate the spread of airborne disease’ to ‘wearing a mask is for cowards.’

It was fascinating to observe that it takes hardly any time at all for a person with a wide enough reach to sculpt bespoke truths for a whole society. This, then, became a much larger focus of the book, and served to strengthen what I had first set out to accomplish with Vael. Vael being both the event that drives the narrative of the first book and the main character of the series.

The stories people tell of you can shape your life in ways you may not fully understand at first. Maybe, like Vael, you sneak away from The War to have a good snog with your divine lover and then wake up a thousand years later to learn the prosperous functioning of the entire realm relies on you never waking up and people are going to be righteously pissed if they find out you don’t want to do that. 

Maybe, like Peter, you get caught knocking squirrels out of nests by a passing reporter and then build a hollow life for yourself as an adult in an attempt to shield your psyche from the crushing guilt every time your family displays the framed newspaper article.

Maybe you wake up one day, surrounded by chicken statues in a too small apartment, and wonder what you could have done to prevent a timeline where you might one day be buried in feathery bric-a-brac from the Hallmark store. It’s me, I’m chickens statues. Please stop gifting me chickens, I’m begging you. But if you must, and I do perish beneath their cute ceramic wings, can we at least agree that you won’t turn it into some sort of foundational story for a new religion?


The Heresy of Thieves: Amazon|Barnes & Noble|Bookshop|Green Apple Books

Author socials: Bluesky|Newsletter

17:00

32 years and still diggin! [Scripting News]

On 10/7/1994, I wrote my first blog post.

I've been blogging ever since.

In the first years when wifi wasn't in every hotel room and restaurant as it is today, before cell phones or even iPods, there were weekly lapses when I went on vacation or to a massage retreat in the middle of nowhere.

Or in 2002 when I had life-saving heart surgery. I was gone for a week then. But as soon as I got home, I added a post so people knew I was okay. I understood that I was missed, and appreciated that. ;-)

But other than those week-long lapses, I'm pretty sure I've blogged every day since. I am what you would call a Natural Born Blogger. I got the blogging bug.

We've done a lot on this blog, it's hard to sum up. We created things that had lasting value. Told the truth about what the tech industry was up to, from the point of view of a Silicon Valley insider (me) who knows the language, where most journalists are intimidated. We fought the big companies and won. Podcasting was created in the community defined by this blog in the early 00s, and it remains independent to this day.

The bigs declared RSS dead, another product of this community. Wishful thinking perhaps, but things like RSS don't die. It's not like that. You can't create a better format for a standard. All the competition RSS has ever received, just changed the names of things, that's not competition, that's being petty.

I think we're on the verge of having new relevance, it feels that way because my secret weapon, Frontier, is running again and it will get even stronger and fit in better, now that I've been working primarily in JavaScript for the last 13 years. Today we're adding Node-like packages and support for WebSockets. Last time we worked on this, neither of those existed.

I take blogging seriously, you should too. It's very powerful, as long as we're really working on the web. There's a difference between having a product you can access through a browser and being on the web.

Small pieces loosely joined. All parts replaceable. Never forget that. If you can't plug in replacements for any component, if one piece gets too large to replicate, you should hit the Back button, as Google says you should do on my blog.

Why are they trying to destroy the historic record about the web on the web itself? I have no idea. Certainly they must have had some discussion internally, or with their partner at the EFF about the cost of destroying so much history. Usually when a platform vendor is going to break you, you get invited to a private meeting where you can tell them the cost and beg them not to do it. Not with Google.

And, as they say --

Still diggin!

16:21

[$] Analyzing Rust programs with Charon [LWN.net]

Nadrieril is a long-time Rust contributor, and the maintainer of the rustc pattern-matching infrastructure. During his involvement with Rust, he has noticed a problem with the usability of the language: it is difficult to automatically extract information from a Rust crate for use with other tooling. The Charon project aims to fix that by providing a stable API for accessing internal information from the Rust compiler.

15:56

Steinar H. Gunderson: Decompilation patterns, part 4: range comparison [Planet Debian]

Today, we're looking at a rewrite that rarely differs for matching, but can mean quite a lot for understanding the code. Say that you have a signed variable and the compiler has suddenly decided to make an unsigned comparison:

    if ((u32) (x - 4) < 2) {

What's happening is that it uses a controlled overflow to do two signed comparisons using one unsigned comparison. How does this work?

Since the comparison is unsigned, this is trivially equivalent to the first expression:

    if ((u32) (x - 4) >= 0 && (u32) (x - 4) < 2) {

and since negative numbers compare larger than 6 in unsigned comparisons, nothing prevents us from adding 4 on each side of the inequality signs:

    if ((u32) (x) >= 4 && (u32) (x) < 6) {

which is, for the same reason, equivalent to:

    if (x >= 4 && x < 6) {

which is what you probably should be writing.

15:35

Pluralistic: Disloyalty (07 Oct 2026) [Pluralistic: Daily links from Cory Doctorow]

->->->->->->->->->->->->->->->->->->->->->->->->->->->->-> Top Sources: None -->

Today's links



A 1970s Air Canada ad depicting a man in a brown velour pullover with a large collar sitting in a book-lined study, assembling model Air Canada jets on his desk. His head has been replaced with the blue, metallic head of a 1950s pulp robot and his skintones have been shifted blue to match.

Disloyalty (permalink)

Up until very recently indeed, airlines were the only businesses that I dealt with regularly that practiced "price discrimination," charging different customers different prices for the same seats.

Economists broadly like this "second-order" price discrimination, where a company charges customers different prices based on the circumstances of the purchase, such as a bakery discounting bread at the end of the day. When an airline cranks up the price of an itinerary that doesn't include a Saturday stay, they're using that fact as a proxy for "price-insensitive" business travelers whose bosses can afford to pay more for a ticket, rather than paying a worker overtime to spend a weekend away from home after a meeting.

Despite the arguments about "efficiency," it's safe to say that *fliers* hate this. The airlines' pricing algorithms are so opaque and capricious that anyone who buys a ticket at *any* price inevitably feels like they got a bad deal. This situation is only exacerbated by the airlines' extreme and aggressive secrecy over their pricing strategies, which includes lawsuits against websites that use historical pricing data to predict when a plane ticket will be the cheapest to buy:

https://www.eff.org/document/preliminary-injunction-american-airlines-v-farechase-inc

When a company wants the government to intervene to prohibit third parties from publishing factual information about its prices, that's a bad look. Hard to believe that they're so sure that you, the customer, will be delighted by these pricing tactics that they can't bear to have these third parties ruin the surprise.

But this second-order discrimination is *so* 20th century. Here in the 21st century, we have *first-order* price discrimination, in which AI-powered pricing algorithms use commercial surveillance data about *you*, personally, to predict the highest price you're willing to pay. In this world of "surveillance pricing," every traveler (or customer) sees a different price, in a kind of cod-Marxist dystopia whose motto is "from each according to their ability (to pay), to each according to their (desperate) needs":

https://pluralistic.net/2025/01/11/socialism-for-the-wealthy/#rugged-individualism-for-the-poor

Airlines *love* this kind of pricing, and they have means, motive and opportunity to practice it. All over the world, airlines have acquired or entered into joint ventures with surveillance pricing companies. These deals put the airlines on the horns of a dilemma: they love to boast to their shareholders about the way that surveillance pricing will let them gouge fliers, but inevitably the public finds out about these investor calls and breaks out the pitchforks and torches.

This triggers embarrassing climbdowns, like Delta's fiasco of summer 2025, when CEO Ed Bastian bragged to shareholders that his deal with the Israeli surveillance pricing company Fetcherr would boost profits by 50% (!) by figuring out which customers could be safely gouged. When fliers, civil society groups and members of Congress got wind of this and raised a ruckus, Fetcherr publicly disavowed Bastian's remarks, insisting that it wasn't even capable of the kind of price-fixing he'd promised. Then Delta launched a smear campaign against its critics, claiming that Bastian never said the thing he *absolutely said* and accusing surveillance pricing activists of peddling "misinformation":

https://groundworkcollaborative.org/news/amid-deltas-ai-pricing-scheme-groundwork-applauds-congressional-action-to-crack-down-on-surveillance-pricing-schemes/

Meanwhile, airlines all over the world have quietly switched their frequent flier reward programs from "mileage-based" rewards to "revenue-based" rewards. That means that you get points and status based on how much you spend on your ticket, not how far you fly. What may not be obvious from this move is that it forces frequent fliers to buy their tickets directly from the airlines, who claim that they can't tell how much you've spent on your ticket otherwise.

So tickets bought on sites like Expedia or through travel agents often qualify for insultingly small points awards, or no points at all. For example, British Airways once gave me *12 points* on a $2,000 Alaska Air ticket because I didn't buy it through British Airways' site (attaining BA's gold status requires *50,000* points).

Forcing frequent fliers to buy their tickets directly from the airline's website sets things up beautifully for surveillance-driven, first-order price discrimination. Airlines you do business with regularly can augment the personal information you provide to them and the data they collect from you with sensitive information purchased from the unregulated data-broker industry, which will cheerfully disclose your salary, credit card debts, or even if you've recently lost a parent and might be flying out for a funeral.

The airlines strenuously deny that they're doing this, of course, but they continue to make investor-facing announcements about their exciting deals with surveillance pricing companies…and things are getting *weird* for frequent travelers.

Thanks to a lot of book-related travel over the past couple of years, I have "Elite" status with Air Canada. Yesterday, I needed to book a last-minute, one-way fare on a route that is only serviced by Air Canada (AC enjoys a monopoly over many Canadian routes, even between major Canadian cities). I checked it out on Google Flights and found a fare for the eye-watering sum of USD829.73.

Now, my normal daily-use browser is Firefox, but I know from long experience that I can't log in to my Air Canada frequent flier account on any browser except Chromium, which I keep installed on my system for just this kind of hostile garbage website. So I tabbed over to Chromium and pasted the Google Flights outbound link that would take me to a pre-populated reservation form on aircanada.com. To my amazement, that same flight was showing as a USD1200 fare!

These were identical trips, loaded via identical referrer URLs. The difference was that in the browser where Air Canada knew that I was an extremely loyal and prolific customer, they had whacked me with a *44.8% premium* to punish me for my loyalty!

I couldn't believe it. I actually called Air Canada's customer service line for "Elite" fliers, and the customer service rep was at a loss for an explanation and could only apologise and suggest that I write a letter to the company.

I'm publishing this instead, because a) this will do more to punish Air Canada for trying to screw me over; and b) this can help you book your tickets in the future.

Yesterday, I finished buying the ticket in my logged-out Firefox window (Air Canada's website will work with Firefox to buy tickets provided you don't try to log in to your Aeroplan account first), paying $829. Then I flipped back to Chromium, where I was still logged into my Air Canada Aeroplan account, located the reservation, and added my frequent flier number to it.

I guess this is how I'm buying my plane tickets from now on.


Hey look at this (permalink)

* PM Mark Carney pledged to ‘Buy Canadian.’ Since then, $7.8 billion in contracts went to American corporations https://archive.is/zDBfX#selection-3576.0-3576.3

* Tracing the rogue ideology of the frontier labs to their product choices https://buttondown.com/apperceptive/archive/tracing-the-rogue-ideology-of-the-frontier-labs/

* Have you heard of the term "enshittification?" https://www.youtube.com/watch?v=nJ5V3pk6PEA&t=1220s

* Window display at TYPE Books Toronto to honour David Byrne’s new book https://stopmakingsense.info/window-display-at-type-books-toronto-to-honour-david-byrnes-new-book/

* Red Vienna Is Still Alive https://www.thenation.com/article/society/vienna-public-housing-urban-cities/



A shelf of leatherbound history books with a gilt-stamped series title, 'The World's Famous Events.'

Object permanence (permalink)

#20yrsago Indie booksellers in the age of the Internet https://web.archive.org/web/20061026112859/https://www.wired.com/news/wireservice/1,71924-0.html

#10yrsago Wells Fargo whistleblower describes bank’s culture of blackballing threats and coerced corruption https://www.npr.org/sections/money/2016/10/07/497084491/episode-728-the-wells-fargo-hustle

#10yrsago Coca-Cola is paying dietitians to tweet scare-stories about soda taxes https://medium.com/cokeleak/is-coke-paying-dietitians-to-tweet-against-soda-tax-12d130d73b9a#.dw8udqccy

#10yrsago What it’s like to register to vote in states with voter suppression law https://www.bbc.com/news/election-us-2016-37569855

#5yrsago Facebook shouldn't be in charge of how you use Facebook https://pluralistic.net/2021/10/08/unfollow-everything/#shut-the-zuck-up


Upcoming appearances (permalink)

A photo of me onstage, giving a speech, pounding the podium.

* Hudson, OH: Hudson Library, Oct 7
https://engagedpatrons.org/EventsExtended.cfm?SiteID=3850&EventID=596952&PK=

* Calgary: Wordfest, Oct 8
https://wordfest.com/2026/show/wordfest-presents-cory-doctorow-2026/

* Winnipeg: McNally Robinson, Oct 9
https://www.mcnallyrobinson.com/event-18991/An-Evening-with-Cory-Doctorow

* Paris: Slow Tech Summit, Oct 15
https://slowtechsummit.com/

* Vancouver: Read, Resist, Repair, Rejoice (Vancouver Writers Festival), Oct 19
https://writersfest.bc.ca/festival-event-2026/01

* Victoria: Munro's Books, Oct 20
https://www.munrobooks.com/events/6113620261020

* Vancouver: Life After AI (Vancouver Writers Festival), Oct 22
https://writersfest.bc.ca/festival-event-2026/46

* Ottawa: Life After AI (Ottawa Writers Festival), Oct 24
https://writersfestival.org/event/life-after-ai

* Kilkenny (Kilkenomics), Nov 6-8
https://kilkenomics.com/

* Vancouver: Enshittification (Sid Williams Theatre Society), Nov 10
https://www.sidwilliamstheatre.com/events/cory-doctorow-talks-enshittification/

* Vancouver: BC Policy Solutions Gala, Nov 12
https://bcpolicy.ca/gala/

* Sheffield: Reverse Centaur at Juno Books, Nov 25
https://www.outsavvy.com/event/40274/cory-doctorow-in-conversation

* Oxford: Bennett Oxford Symposium on Open Code in Science, Dec 9
https://www.bennett.ox.ac.uk/events/2026-bennett-institute-symposium/

* Montreal: World Science Fiction Convention, Sep 2-6
https://montreal2027.ca/en



A screenshot of me at my desk, doing a livecast.

Recent appearances (permalink)

* Terms of Service with Clare Duffy (CNN)
https://www.cnn.com/audio/podcasts/terms-of-service-with-clare-duffy/episodes/458ce968-af5d-11f0-b539-13ed2afe25f8

* AI, Work, and Power (Software Engineering Daily)
AI, Work, and Power https://softwareengineeringdaily.com/podcasts/cory-doctorow-on-ai-work-and-power/

* AI, Corporate Power, and the Fight for Worker Control (Plutopia)
https://plutopia.io/cory-doctorow-ai-corporate-power-and-the-fight-for-worker-control/

* How to Think About AI—Before It’s Too Late (Daniel Solove)
https://www.youtube.com/watch?v=_0xR3uEgGcc

* Could Tech Bosses Destroy Life As We Know It? (Politics JOE)
https://www.youtube.com/watch?v=PL4VktU0SgY



A grid of my books with Will Stahle covers..

Latest books (permalink)

* "The Reverse-Centaur's Guide to AI," a short book about being a better AI critic, Farrar, Straus and Giroux, June 2026
https://us.macmillan.com/books/9780374621568/thereversecentaursguidetolifeafterai/

* "Canny Valley": A limited edition collection of the collages I create for Pluralistic, self-published, September 2025 https://pluralistic.net/2025/09/04/illustrious/#chairman-bruce

* "Enshittification: Why Everything Suddenly Got Worse and What to Do About It," Farrar, Straus, Giroux, October 7 2025
https://us.macmillan.com/books/9780374619329/enshittification/

* "Picks and Shovels": a sequel to "Red Team Blues," about the heroic era of the PC, Tor Books (US), Head of Zeus (UK), February 2025 (https://us.macmillan.com/books/9781250865908/picksandshovels).

* "The Bezzle": a sequel to "Red Team Blues," about prison-tech and other grifts, Tor Books (US), Head of Zeus (UK), February 2024 (thebezzle.org).

* "The Lost Cause:" a solarpunk novel of hope in the climate emergency, Tor Books (US), Head of Zeus (UK), November 2023 (http://lost-cause.org).

* "The Internet Con": A nonfiction book about interoperability and Big Tech (Verso) September 2023 (http://seizethemeansofcomputation.org). Signed copies at Book Soup (https://www.booksoup.com/book/9781804291245).

* "Red Team Blues": "A grabby, compulsive thriller that will leave you knowing more about how the world works than you did before." Tor Books http://redteamblues.com.

* "Chokepoint Capitalism: How to Beat Big Tech, Tame Big Content, and Get Artists Paid, with Rebecca Giblin", on how to unrig the markets for creative labor, Beacon Press/Scribe 2022 https://chokepointcapitalism.com



A cardboard book box with the Macmillan logo.

Upcoming books (permalink)

* "The Post-American Internet," a geopolitical sequel of sorts to *Enshittification*, Farrar, Straus and Giroux, 2027

* "Unauthorized Bread": a middle-grades graphic novel adapted from my novella about refugees, toasters and DRM, FirstSecond, April 20, 2027

* "Enshittification, Why Everything Suddenly Got Worse and What to Do About It" (the graphic novel), Firstsecond, 2027

* "The Memex Method," Farrar, Straus, Giroux, 2027



Colophon (permalink)

Today's top sources:

Currently writing:

* “Once Is Enemy Action,” a science fiction novel about the origins of modern technofascism. Today's words: 529 (22924 total).

* "The Post-American Internet," a sequel to "Enshittification," about the better world the rest of us get to have now that Trump has torched America. Fourth draft completed. Submitted to editor.

* A Little Brother short story about DIY insulin PLANNING


This work – excluding any serialized fiction – is licensed under a Creative Commons Attribution 4.0 license. That means you can use it any way you like, including commercially, provided that you attribute it to me, Cory Doctorow, and include a link to pluralistic.net.

https://creativecommons.org/licenses/by/4.0/

Quotations and images are not included in this license; they are included either under a limitation or exception to copyright, or on the basis of a separate license. Please exercise caution.


How to get Pluralistic:

Blog (no ads, tracking, or data-collection):

Pluralistic.net

Newsletter (no ads, tracking, or data-collection):

https://pluralistic.net/plura-list

Mastodon (no ads, tracking, or data-collection):

https://mamot.fr/@pluralistic

Bluesky (no ads, possible tracking and data-collection):

https://bsky.app/profile/doctorow.pluralistic.net

Medium (no ads, paywalled):

https://doctorow.medium.com/

Tumblr (mass-scale, unrestricted, third-party surveillance and advertising):

https://mostlysignssomeportents.tumblr.com/tagged/pluralistic

"When life gives you SARS, you make sarsaparilla" -Joey "Accordion Guy" DeVilla

READ CAREFULLY: By reading this, you agree, on behalf of your employer, to release me from all obligations and waivers arising from any and all NON-NEGOTIATED agreements, licenses, terms-of-service, shrinkwrap, clickwrap, browsewrap, confidentiality, non-disclosure, non-compete and acceptable use policies ("BOGUS AGREEMENTS") that I have entered into with your employer, its partners, licensors, agents and assigns, in perpetuity, without prejudice to my ongoing rights and privileges. You further represent that you have the authority to release me from any BOGUS AGREEMENTS on behalf of your employer.

ISSN: 3066-764X

15:14

CodeSOD: A Tool for You [The Daily WTF]

A junior dev on the team had a simple question: in Java, how do I correctly read user input from the CLI and act on it?

Now, we can wonder about a junior dev not having such basic knowledge, but maybe they're new to Java specifically. Who knows. But Robert's team has a senior on it who "likes to mentor". This senior has strong opinions about how to write software, extreme confidence in those opinions, and has somehow been around the company long enough that he knows where the bodies are buried and no one will fire him.

Here's his sample application for reading input.

import java.nio.charset.StandardCharsets;
import java.util.*;

public class ScannerTool {
  public static void main(String[] args) {
    Map<Optional<Character>, Map.Entry<String, Boolean>> map = new HashMap<>();
    map.put(Optional.of('A'), new AbstractMap.SimpleEntry<>("You entered A.", true));
    map.put(Optional.of('B'), new AbstractMap.SimpleEntry<>("You entered B.", true));
    map.put(Optional.empty(), new AbstractMap.SimpleEntry<>("You entered nothing.", false));
    Optional<Character> input;
    do {
      System.out.println("Please enter A or B:");
    } while (!map.getOrDefault((input = nextCharacter()), new AbstractMap.SimpleEntry<>("", false))
        .getValue());
    System.out.println(map.get(input).getKey());
  }

  public static Optional<Character> nextCharacter() {
    String l = new Scanner(System.in, StandardCharsets.UTF_8).nextLine();
    if (l != null && !l.isEmpty()) {
      return Optional.of(l.charAt(0));
    }
    return Optional.empty();
  }
}

Look, I know Java is verbose, but this is entirely next level. Giving this to a junior has to constitute an attempt to drive the youth out of the industry, because any sane person being told, "your job is to write this" would realize they've committed a grave error in their career choice and needs to escape now.

nextCharacter in this case actually returns the first character of the next line, not the next character. And if there isn't one, it returns an empty optional. Which, fine, we only expect one character per line of input for this exercise, so that's not wrong, I just don't like the name of the function.

Of course, using Scanner here is a wrecking ball fly swatter- that class is a full on parser for reading out common data types from a file. Worth showing to a junior, but a little out of scope of this question.

But the real overengineering is the map of maps- well, a map of map entries. The key to the outer map is an optional of the character that was entered by the user. The value is itself a map, the key of the map is the message we want to output, the value is whether or not the loop should continue. They've reinvented tuples using map entries. Which, in their defense, mainline Java doesn't have tuples (enterprise edition seems to have them in the javax namespace, but I'm not even certain those are meant to be general purpose tuples anyway). A quick search indicates that their absence is a perennial source of heated discussions in forums and Stack Overflow.

Robert writes:

It is remarkable in its creativity and execution and still has me in awe. When I tried to unpack it, my brain just started to pack up its things and left, haven't seen it since.

Bon voyage.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

14:49

[$] Evolving the LAVD scheduler from gaming to servers [LWN.net]

The extensible scheduler class, which enables the creation of custom CPU schedulers with BPF, has led to a burst of innovation in this area; the LAVD scheduler has, perhaps, been one of the most noteworthy schedulers to emerge. Though it was originally designed for gaming applications, the LAVD scheduler has since grown to serve other types of workloads as well. At the 2026 edition of Kernel Recipes, Changwoo Min and Gavin Guo presented an overview of this scheduler and how it has evolved over time.

A new Raspberry Pi Desktop release is finally available for x86-64 [LWN.net]

Simon Long has announced a long-awaited release of Raspberry Pi OS, based on Debian 13 ("trixie"), for x86-64 systems.

We managed to find the time to update the Desktop for the Buster and Bullseye releases of Debian, but then we all just got too busy with other things, and, while we left the Bullseye version on the website for anyone who wanted it, we simply didn't have time to release any newer versions. But people kept on asking for it – we get two or three emails every week asking when the PC Desktop will be updated, and we haven't had an answer, because we honestly didn't know when we might get a chance to do it. We've continually tried to allocate time to be able to work on this, but it hasn't been easy. [...]

Earlier this year, we (or rather Serge) finally got the latest version of the Desktop running on top of a Debian Trixie image. It's now based on 64-bit Debian (the amd64 architecture) rather than the older 32-bit version, as Debian itself has stopped supporting 32-bit for PC architectures. This shouldn't be a major problem – most PCs made in the last 15 years or so will quite happily run the 64-bit version of Debian, as will most Intel-based Macs. (Debian support for Apple Silicon is still experimental, so unfortunately those of you with the latest and greatest shiny fruit products will not be able to run this.)

Security updates for Wednesday [LWN.net]

Security updates have been issued by AlmaLinux (bind, dovecot, freerdp, kernel, mariadb-connector-c, mod_auth_openidc, nodejs22, nodejs:22, sudo, and vim), Debian (node-shell-quote, puma, rails, ruby-jwt, and suricata-update), Fedora (chromium, cockpit, flocq, freerdp, gappalib-coq, golang-x-mod, httpd, janus, libical, musescore, python3-docs, python3.14, python3.15, rocq, rocq-stdlib, tesseract, why3, and zenon), Mageia (srt and tor), Oracle (freerdp, kernel, libpcap, mariadb-connector-c, nodejs22, sudo, and vim), Red Hat (expat and grafana), Slackware (openssh), SUSE (chromium, docker-stable, firefox, jupyter-jupyterlab, libtcnative-1-0, tomcat, tomcat10, libtcnative-1-0, tomcat11, openexr, python310, python313-azure-storage-queue, python313-langchain-anthropic, python313-sglang, python313-Werkzeug, and valkey), and Ubuntu (fluidsynth, freerdp3, freetype, golang-1.18, golang-1.21, golang-1.24, gst-plugins-good1.0, libsoup2.4, libsoup3, libwebsockets, linux, linux-aws, linux-gcp, linux-gke, linux-ibm, linux-oracle, linux-realtime, linux-azure, linux-azure-fde, linux-nvidia-tegra, linux-oem-7.0, redis, sg3-utils, tesseract, and u-boot).

14:21

Kentaro Hayashi: Testing Japanese IMEs on virtual desktops with virt-japanese-desktop [Planet Debian]

Japanese input method editors (IMEs) such as Mozc are essential for typing Japanese (yes, skk, anthy and more, but it's out-of-scope in this article). Testing them is a surprisingly fiddly job: an IME behaves differently depending on the desktop environment (GNOME, KDE, Xfce, Budgie), the input framework (ibus, fcitx5, uim), and the Wayland or X11 session. Setting up a fresh VM for every combination from scratch is slow and repetitive, and breaking your host's environment while experimenting is no fun.

virt-japanese-desktop is a small set of toy scripts that solves exactly this problem. It builds ready-to-use virtual machines - each with a desktop environment and a Japanese IME already installed and configured - so you can start typing Japanese within a minute of booting, and throw the whole thing away without any impact on your host.

What it gives you

A single make command produces a qcow2 image that combines one of four desktops with one of three IMEs:

ibus-mozc fcitx5-mozc uim-mozc
GNOME ✓ ✓ ✓
KDE ✓ ✓ ✓
Xfce ✓ ✓ ✓
Budgie ✓ ✓ ✗

The one gap - uim on Budgie - is a real technical limitation, not an oversight: Budgie runs the labwc compositor, which implements zwp_input_method_v2, while uim-wayland only speaks the older zwp_input_method_v1 (KWin/Weston). On Budgie you use ibus-mozc or fcitx5-mozc instead.

All images come with the Japanese locale, fonts, and the Asia/Tokyo time zone pre-configured, plus the SSH key of your choice. Input switching is wired up out of the box: Ctrl + Space or Shift + Space for your IMEs to enable it.

The layered-image trick

The core idea is that the images are stacked qcow2 overlay layers, one on top of another:

debian-sid-nocloud-amd64-daily.qcow2     ... base image you download
└─ unstable-japanese-template.qcow2      ... locale, fonts, user, SSH
   └─ unstable-<DE>-template.qcow2       ... a desktop environment
      └─ unstable-<DE>-<IME>.qcow2       ... desktop + IME, configured
         └─ unstable-<DE>-<IME>.workspace.qcow2 ... the image you test in

This makes both building and resetting fast. The first build downloads and customizes the base image and takes a while, but every later step only adds a thin overlay. When a test breaks the VM, you do not rebuild anything - you simply delete the top workspace layer and recreate it. That is all it takes to return to a pristine state:

rm /tmp/unstable-gnome-ibus-mozc.workspace.qcow2
make gnome-ibus-mozc

The images reference their backing files by relative path, so you can move an entire stack anywhere you like as long as the images stay together.

A platform for experimenting with bleeding-edge IMEs

The base system is Debian unstable (sid), and the experimental repository is already added. That makes the project a convenient platform for testing not just the IME packages in sid but also the ones still being developed in experimental - exactly what the project was built for.

The keyboard layout of the VM follows the layout of your host (read from /etc/default/keyboard, with a fallback chain to localectl and finally us).

Quick start

# 1. Install the tools (Debian/Ubuntu example)
sudo apt install qemu-utils libguestfs-tools virt-install curl

# 2. Save your SSH public key
curl --location https://github.com/USERNAME.keys --output pubkey.pub

# 3. Download the base Debian sid image
make download

# 4. Build a desktop + IME (first build takes a while)
make gnome-ibus-mozc

# 5. Start it as a VM (needs the libvirt daemon, qemu:///system)
./scripts/make-virsh-image.sh virt-gnome-ibus-mozc /tmp/unstable-gnome-ibus-mozc.workspace.qcow2

Log in as debian (password debian) and press Ctrl + Space (Shift + Space) to start typing Japanese. The VM is registered in libvirt, so you can manage it with virsh or virt-manager - handy for opening the SPICE console or restarting the machine after a test.

Status

The project is still in the proof-of-concept phase, and it is developed mainly to test Mozc and other IMEs across desktops and input frameworks.

If you regularly test Japanese IMEs - give it a try.

12:35

Apple’s Verified Photography System [Schneier on Security]

Apple just released a system called “Reference Image.” It can verify the image is exactly as taken by an iPhone—new models only—without tying it to a specific iPhone or photographer. It can also verify that multiple images came from the same iPhone.

Other industry solutions require a photographer or institution to vouch for an image using their own credentials. We are concerned this puts some photographers, such as those operating in conflict zones, in a difficult position; it should not be necessary to forgo anonymity in order to prove image authenticity. We built Apple Reference Image to avoid using an explicit, public credential for photographers, and to avoid even implicit public association between different photos taken by the same sensor. The final reference image is instead signed by Apple’s signing service, after validation by PCC. That signature is backed by Apple’s strongest technical guarantees.

Our implementation also protects the confidentiality of the image itself, including from Apple. Merely capturing a reference image should never expose the actual pixels to Apple or anyone else. We achieve this through the exceptional privacy properties of PCC—the nodes themselves are architected so that not even Apple can access image data, just as Apple cannot see the information processed for Apple Intelligence in PCC. While the revocation service must maintain a private record of photo GUIDs and associated sensors to allow for revocation, it never has access to the image data, and does not allow for public access to this record. And as final revocation checks occur using on-device lists, a device never reveals to anyone which photo it’s looking at in order to find out whether it’s still valid.

The report makes for good reading; the details are interesting.

12:07

Away From Keyboard Today [Whatever]

Why? Because I’m getting a colonoscopy today, that’s why. That’s gonna take up some time, and then afterward I am likely to be in a semi-dazed state afterwards thanks to the lingering effects of sedation, which will also very likely mess up my judgement. Me online without judgement skills would probably be a very very very very bad thing. So we’re gonna skip that entirely (I am, mind you, writing this two days early and scheduling it to go up Wednesday morning).

I asked Krissy to hide my phone and laptops today, and, when I get home from the colonoscopy, to basically sit me on the couch and put on the televised equivalent of easy listening. Athena is likely to be around today, but as for me, just imagine me on that couch, cat laying on my blanketed legs, trying to remember to blink from time to time. That’ll be about the speed of things for me today.

See you all Thursday!

— JS

10:28

Bits from Debian: Looking for artwork for the next Debian release: Forky [Planet Debian]

Each release of Debian has a shiny new theme which is visible on the installer, the boot screen, the login screen and, most prominently, on the desktop wallpaper. It's a very important part of a Debian release as it is usually the first thing new users see when installing or booting the system for the first time. Not only that, but also the first thing appearing when debianites share their screen or connect to an external device when giving talks around the world.

As the most enthusiastic users will know, the forky - yes, that is the name of the next stable version, Debian 14 - release cycle is rapidly approaching its latter stages. This means we need to select the artwork shipping with forky soon, really soon!

And as with everything else in Debian, collecting artwork is a collaborative effort which Debian shares with its community. So, if you would like (or know someone who would like) to create a desktop look and feel that will be seen by trillions of people around the world - and in space! - be sure to send in your artwork ASAP!

The deadline for submissions is: 2026-11-26.

For the most up to date details please refer to the Debian wiki.

At the same time, we would like to thank Elise Couper for creating the Ceratopsian theme for our last trixie release.

And for the interested or the curious ones, the artwork is usually picked based on which theme looks the most:

  • ''Debian'': admittedly not the most defined concept, since everyone has their own take on what Debian means to them. Though, usually they all agree when something looks like Debian.

  • ''Plausible to integrate without patching core software'': as much as we love some of the insanely looking themes, some would require heavy GTK+ theming and patching GDM/GNOME.

  • ''Clean and well designed'': without becoming something that gets annoying to look at a year, or ten, down the road. Examples of good themes include Emerald, Homeworld, Joy, Lines, softWaves and futurePrototype

If you'd like more information or details, please post to the Debian Desktop mailing list.

10:21

How do you like them apples? [Seth's Blog]

In October, the apples in New England are some of the best in the world.

Alas, the apples in this bowl are nine months old and from 3000 miles away. I found them sitting in a forlorn corner of a hotel gym in Cape Cod.

Why source lousy apples? Because they’re not being hired to be good apples. Instead, they’re chosen to be convenient, reliable and a bit of decoration. Reliably there day after day, but not to create delight, build a reputation or support the local economy.

When we name it, we can figure out if it’s what we actually want. Most of the status quo persists, unnamed, because it’s easier than untangling what we’re actually trying to accomplish.

The Knot helps us see the baggage we’re unconsciously carrying around. If you’re feeling stuck, that’s the first step on the road to making something better.

08:35

Man-Made Horrors, Part One [Penny Arcade]

New Comic: Man-Made Horrors, Part One

05:56

US deported people to Equatorial Guinea [Richard Stallman's Political Notes]

The US deported people to Liberia who had no connection with Liberia.

Some refused to get off the plane there, so the US took them to totally tyrannical Equatorial Guinea. They had no connection with Equatorial Guinea either, but thugs there imprisoned them and they are still stuck.

I suppose that the persecutor is paying the dictator of Equatorial Guinea to use his country as a privatized prison.

05:49

Girl Genius for Wednesday, October 07, 2026 [Girl Genius]

The Girl Genius comic for Wednesday, October 07, 2026 has been posted.

05:00

Why does the compiler sometimes use ud2 and sometimes int 3 for code that shouldn’t execute? [The Old New Thing]

There are two common ways for x86 compilers to indicate that execution should not have reached a particular point: One is the single-byte int 3 breakpoint opcode. And the other is the two-byte ud2 invalid instruction opcode. How do they decide which one to use?

The two types of “bad instructions” are typically for different purposes.

The int 3 means “There is no code here. If you somehow got here, then somebody used an invalid function pointer.” It is used as padding, such as between functions. There is no way that code can reach the int 3 by normal execution. You must have generated an invalid address and called it.

The ud2 is used to mark the case when execution reached something that should be unreachable. It means “You executed a code path that the standard says is undefined behavior.” For example, falling off the end of a non-void function without returning a value, or following the call to a [[noreturn]] function in case it somehow managed to return.

Using int 3 for “there is not even code here” is important because it’s a one-byte instruction. If you had used the two-byte instruction ud2 instruction, then that stray function pointer might land on the second byte of the instruction, in which case it’s not ud2 any more. Instead of stopping immediately, it starts executing garbage code:

0b 0f            or      ecx,dword ptr [edi]
0b 0f            or      ecx,dword ptr [edi]
0b 0f            or      ecx,dword ptr [edi]

Okay, so what does this mean for you?

If you find yourself executing the ud2 instruction, then look for logic flaws in your code. If you find yourself executing the int 3 instruction, then look for an uninitialized function pointer variable, or a hard-coded breakpoint, or a debugger-inserted breakpoint.

The post Why does the compiler sometimes use <CODE>ud2</CODE> and sometimes <CODE>int 3</CODE> for code that shouldn’t execute? appeared first on The Old New Thing.

02:49

Twisting Her Words [QC RSS v2]

I'm starting to think Anh might be prone to making bad decisions

02:28

The web of Markdown [Scripting News]

How about a Markdown web browser. No CSS, no frameworks, just Markdown files. That would be how you would restart the web in 2026. When you've had enough of Google, let me know. I start posting my blog in that space, right away.

Google will not be allowed into this web. Buh bye.

We need our own EFF to keep the Old Arthritic Corporate Excuse-makers for the Tech Industry EFF from fucking it up, same with the W3C. There won't be a WordPress there to own 81% of the websites. Just Markdown please, we don't need Gutenberg. The cool thing about it is you could always fork off your own New Web of Whatever. You don't like Markdown, that's cool. Maybe the web of SVG or The Web of Hypercard cards. Just make sure you support inbound and outbound RSS.

00:21

KDE developer takes a look at COSMIC [OSnews]

KDE contributor and developer Niccolò Venerandi has published an article about COSMIC, System76’s brand new desktop environment.

It’s been almost a year since I’ve last tried COSMIC; though development since then has mostly been centered around stability (going from late alpha to stable releases now!) there have been a fair bit of user-facing changes too which I adore. I thus have to ask myself: should I switch to COSMIC? The answer obviously is no, but that’s just because I got addicted to KDE Plasma, so let’s try to be more objective here.

↫ Niccolò Venerandi

Honestly, this is one of the best “reviews” I’ve seen of COSMIC, and paints System76’s hard work in a really positive light. Venerandi spots countless really nice touches he’d love to see adopted by KDE, while also hitting on what I agree is COSMIC’s biggest shortcoming at this point: there’s basically no ecosystem around it. COSMIC uses its own Rust-based toolkit instead of GTK or Qt, but of course, there’s very few other applications that actually use said toolkit.

The end result is that if you run COSMIC, you’re going to have to supplement it with countless GTK and Qt application, none of which will inherit all the nice features, touches, and graphics from COSMIC. This isn’t really a complaint about COSMIC itself or the work its developers are putting into it, but more a fact of life for such a new desktop environment using an otherwise unpopular (as of right now) toolkit. This may very well change in the future, but for now, if you choose to run COSMIC, you’re going to have to accept a very inconsistent and messy desktop.

This won’t matter to everyone, but it sure does matter to me, and it’s the one reason why at this point I have zero interest in running COSMIC. I really hope this changes in the future – competition is good – but it’s going to be a long road.

Tuesday, 06 October

23:28

Link [Scripting News]

Google Chrome, nice browser, but it doesn’t support the web.

Link [Scripting News]

We just hit a milesone in Atlantis. We have WebSocket support, client and server. We made a client that listens to the FeedLand stream of new and updated posts. Still waiting to see if that works. But when I started to write the code for this test app last night, I realized after 13 years of mostly working in JavaScript, I wanted to be able to do something like a Node package, so I could do all my code in one outline, instead of five different odb objects. I always thought the way we did it in Frontier could be improved on. So I did a quick voicemail to Claude, and asked for this to be done overnight, and it was. I didn't complete the design when we worked through it. But what Claude came up with was brilliant and fit into Frontier perfectly. Two major improvements and the first use of both is with each other. I don't remember that ever happening, and btw -- it would have taken at least a week for me to do what Claude did in less than an hour. Hopefully I'll be able to post some example scripts tomorrow, for people who are interested in seeing the new stuff.

22:00

Steinar H. Gunderson: Decompilation patterns, part 3: do-while-while [Planet Debian]

Today's decompilation pattern is a variation on yesterday's loop pattern, because loops are important and tend to induce a lot of transformations by the compiler. So assume you have code like this (perhaps after converting a goto):

if (var_s0 > var_s1) {
  // Perhaps some other initialization stuff here
  do {
    ...
  } while (var_s0 > var_s1);
}

Assuming there are no side effects from the initializations, a natural candidate would be that the programmer instead wrote:

// Initialization stuff moved up here
while (var_s0 > var_s1) {
  ...
}

Remember to remove the old while so that it does not say while (x) { ... } while (x); it won't tell you with a syntax error, but rather leave an infinite loop that will mess up the generated code.

20:21

The Big Idea: Jocelyn Cullity [Whatever]

Some tales are as old as time. Corruption in power, rooting for the underdog that’s willing to take on the Big Bad, these are the stories people have loved for centuries — and it’s certainly an influence on author Jocelyn Cullity’s newest novel, The Nurse at Baker Hospital.

JOCELYN CULLITY:

While researching Norman Baker—the 1930s radio personality, political populist, and promoter of fraudulent cancer cures who operated the infamous Baker Hospital at the Crescent Hotel in Eureka Springs, Arkansas—I gradually realized that I wasn’t simply uncovering a flamboyant historical figure. I was encountering a type of American demagogue I recognized.

Baker understood how to persuade people to distrust established expertise while placing extraordinary faith in a single, confident voice: his.

The realization changed both the novel and my understanding of the present. At the same time I was researching Baker, for instance, a neighbor was traveling to a clinic in Mexico in search of unconventional cancer treatments. Nearly a century separated the two stories, yet the forces at work seemed painfully familiar: illness, desperation, hope, distrust of conventional medicine, and the seductive promise that someone possesses a cure that the establishment doesn’t want you to know about.

Baker had found the perfect technology for spreading that message: radio.

His broadcasts reached listeners across North America, and he used the airwaves relentlessly. He railed against the American Medical Association and conventional medicine. He opposed polio vaccination. He portrayed doctors as greedy elites who had spent four years in college and still couldn’t cure the common cold. And he insisted that the medical establishment was conspiring against him because he possessed something they did not want ordinary Americans to have: a cure for cancer.

The logic was circular and ingenious. If doctors could cure cancer, Baker argued, they would put themselves out of business. Therefore, they had every reason to suppress the cure. Their opposition to Baker became, in his telling, evidence that Baker was right.

And there were a lot of people who believed him.

As I researched his broadcasts, and the court case in Little Rock, I began to see radio as Baker’s version of social media. He could speak directly to enormous numbers of people, repeatedly, bypassing traditional authorities and creating an intimate relationship with an audience who came to trust him more than they trusted doctors, scientists, newspapers, or institutions.

That was perhaps the most unsettling discovery I made while writing The Nurse at Baker Hospital. I had thought of some of the political rhetoric of our own time as distinctly modern, amplified by Twitter/X and other social-media platforms. Instead, I found its rhythms waiting for me in the 1930s.

Baker’s great message was that the little guy was being cheated by powerful interests and educated elites. He cast himself as the fearless outsider willing to expose them. The irony was considerable. Baker himself was a wealthy businessman who wore purple suits, drove a purple car, and purchased the grand Crescent Hotel in Eureka Springs which he transformed into his cancer hospital.

Born in Iowa, Baker had already opened a cancer hospital there and made an unsuccessful attempt at politics before moving his operation to Eureka Springs in Arkansas. He cultivated the identity of the persecuted outsider, and many of his listeners—particularly in the Midwest—saw attacks on him as proof that powerful forces were trying to silence a man who spoke for them.

Many desperate people came to him for treatment before the federal government finally succeeded in bringing him down. Yet even Baker’s downfall seems sadly unsatisfying. After serving a relatively brief prison sentence, he lived out his later years in comfort, spending time aboard a yacht off the coast of Florida.

I began The Nurse at Baker Hospital interested in a bizarre piece of American history: a flamboyant charlatan, a grand Victorian hotel, a fraudulent cancer hospital, and the patients who traveled there hoping to be saved.

I finished the novel thinking about something much larger.

The technology changes. The audience changes. The setting changes. But the appeal of the demagogue—the person who tells us that experts are lying, institutions are corrupt, ordinary people are being cheated, and he alone can reveal the truth—is not new at all.

It is an American character with a long history, and it’s still with us.


The Nurse at Baker Hospital: Amazon|Bookshop|Skylark Bookshop|Double Dog Bookshop

Author socials: Website|Facebook 

18:49

Link [Scripting News]

This is the era of very powerful bozos.

18:35

Voting: Done! [Whatever]

Ohio started early voting today and my book tour literally starts on Election Day, so this morning Krissy and I got ourselves over to the Darke County board of elections to vote early and in person. Now I won’t have to scramble on the actual day to manage my book release, tour, and voting! One less thing to worry about, especially with this election.

Speaking of which, I really do strongly encourage each of you out there to get your vote in early (and in person) if that’s a thing you can do. It will help, dare I suggest, cut down on shenanigans. And yes, it sucks that we have to factor in the possibility of shenanigans in our voting system, but welcome to 2026 (to be clear: if there are any shenanigans, it will be the doing of the Trump administration and its allies, let’s not pretend there is any squishy “both side” nonsense here, fascists are bad, m’kay). Vote like it matters, because it does. We did!

— JS

18:14

Building a second-wave AI business [Seth's Blog]

A very long rant, riffing on the opportunity for bootstrapped startups who seek to create value using AI. I wrote it a while ago, thought it was too long, but in arguing with an AI today (everyone needs a hobby) I realized it was worth sharing:

Most AI success stories to date are about cost reduction or speed improvement. A startup offers businesses a way to get more done with fewer people, replacing customer service or programming teams with bots. The upside of cost reduction is that it’s a very easy sale—give the client a free sample, once it’s demonstrated to work, they have an instant benefit in switching.

The downsides: it’s difficult to win a race to the bottom, since someone can always promise more savings than you. And it’s finite—once the savings are made, there’s no incremental value left to create.

The opportunity lies in something generative. A use of AI that doesn’t reduce costs, it creates value. It opens new opportunities, leads to growth, connection, and utility.

Worth paying for: Most bootstrappers target price-sensitive customers and then wonder why growth is hard. But people and organizations with expensive problems and real resources don’t just put up with paying more for things they value—they prefer it. Premium pricing signals seriousness. Look for a market where the problem is real, the budget exists, and the solution creates something they couldn’t get otherwise.

What people actually pay for: At the foundation of almost every premium purchase are three drives:

status (I matter, people like me see me as significant),

affiliation (I belong, there are people like me and they accept me), and

freedom from fear (I am safe, the threat is not coming).

Freedom from fear may be the most primitive—you can’t pursue status or affiliation while in survival mode. And most premium purchases are a quest for freedom from fear pretending to be something else.

Built on those roots is a middle layer of things that offer one or more: legitimacy, transformation, belonging to a narrative, control, certainty, protection, trust, health and longevity, leverage.

And the outer layer that’s easier to measure—things people buy because they deliver the middle layer: access, capital, time, attention, convenience, efficiency, delight, new experiences, beauty.

Commodities—food, shelter, sex, addictive substances—are often outside this hierarchy. They don’t really build toward the three roots; they allow survival or temporarily suppress the anxiety that comes from not having them.

An AI business worth building delivers something from the middle layer, justified by the outer layer. Nobody goes shopping for transformation.

What businesses actually pay for: The hierarchy for individual consumers doesn’t translate directly to organizational purchases. In B2B, the customer is spending someone else’s money. That means that the dominant question they’re asking is, “what will I tell my boss?” Three desires sit at the foundation of almost every business buying decision:

Avoid blame — if this goes wrong, it won’t be my fault. The IBM principle: nobody ever got fired for buying the market leader. The champion inside the organization often needs a defensible story before they’ll act.

Claim credit — I brought something in that worked and people noticed. The flip side of blame avoidance, and the engine of the internal champion. If your solution lets someone look good, they’ll sell it to their peers, you won’t have to.

Reduce uncertainty — we can plan around this, the chaos goes down. Organizations pay significant premiums for the ability to forecast, commit, and stop worrying.

Built on those roots is a middle layer of things organizations reliably spend on: growth, efficiency, compliance, competitive advantage, talent, morale, resilience, optionality, speed, legitimacy, relationships.

And an outer layer that justifies the middle: cost savings, time savings, data, access, convenience, integration, reporting, support.

Mechanics without a story is the race to the bottom, and being the cheapest is not the best use of your time.

New vs. repeat purchases require different approaches: Repeat purchases are won by switching costs, relationships, and relentless incrementalism—you’re replacing someone, which means you need to be cheaper, easier, or have a better story and sales force. New purchases require someone inside the organization to become a champion, which means they need a story that serves their career, not just their company’s interests.

Not all problems are equally interesting: Some purchases—like gaining market share or entering a new category—are chaotic and interesting, with room for narrative and ambition. Others—like cheaper materials or faster processing—are grinding commodities where the only story is price. Commodity buyers fear paying too much. Buyers in chaotic spaces fear making a wrong choice.

The forcing function: Businesses rarely lead the way on new purchases without a crisis compelling them. Without a forcing function, even a perfect solution sits in the pipeline forever—committees form, pilots stall, and champions get reassigned.

Three kinds of crises create forcing functions:

Competitive crisis — a rival did something and now there’s urgency. “They have it and we don’t” is a sentence that ends a discussion and starts the buying process.

Technology crisis — the old way stopped working, or a new capability made the old way look reckless. AI itself is currently creating this for many industries simultaneously. This time, the forcing function and the solution are the same thing.

Public/market upheaval — regulatory change, cultural shift, a collapse in input costs, a pandemic. These are the most powerful and least predictable. They create entirely new categories of buyer.

The opportunity for a bootstrapper: sell into a forcing function that already exists, don’t try to create one. Organizations already feeling the crisis don’t need convincing—they need a solution that lets their champion say “I found it.”

NOTES:

Naked AI is a trap. If all you’re doing is building a gateway to Anthropic or ChatGPT, your token costs eat a significant portion of your revenue—and you have no defensible position.

Hidden prompts are insufficient. Breakthrough prompting can create real value, but there’s no protectable, reliable way to sell it as a business. If one of the frontier companies made it a business model, the mechanics would work in the bootstrapper’s favor, but I haven’t seen this.

The network effect matters. Selling benefits one person at a time is brutally expensive. The breakthroughs come with projects that have the network built in—where interactions work better when your colleagues are using them too.

Asymmetric information is worth seeking out. Some of the most durable advantages come not from network effects but from knowing something others don’t, or from helping a cohort work together to pool what they know against a party that currently has structural information advantage over them. Let all of Walmart’s vendors see information that they currently hoard, for example.

So, a theory of profit—a framework for the kind of project that becomes a business:

  1. Creates its own useful data stack. The data doesn’t need to be large to be valuable—it needs to be specific and trusted. Over time it informs the AI. It belongs to users and the project, not to Anthropic or competitors. And it’s built to work for users, not to trap them.
  2. Has a built-in network effect. Either an engaged peer-to-peer community (where users see each other, not just the platform) or an obvious benefit to spreading the word.
  3. Solves an expensive problem for people with resources. The value delivered goes beyond saving time or money—it might be education, reduced fear, joy, reassurance, connection, or capability expansion. And it’s priced accordingly.
  4. Is bootstrappable. Specific and conceptual rather than infrastructural. No data centers, no thousand-person teams required to get started.

Bonus:

A note on data stack reality. A network built on user data is only as good as the willingness of users to populate it. And willingness requires two things: it has to be frictionless enough that people don’t have to think about it, and it has to feel safe enough that people don’t have to worry about it. These two conditions are almost always in tension. The more automatic the data collection, the more it feels like surveillance. The more control you give people, the more friction you add.

The most promising data stacks are ones where people are already generating the data, are already comfortable with it existing somewhere, and the innovation is simply giving them better access to what’s already theirs. The forcing function for consumer data sharing may be the simplest one of all: I already feel watched. I might as well get something back.

The cautionary version of this is the email surveillance tool—a business reads all internal email and gets a report on who’s helpful, who’s toxic, who’s looking for a job. The value is real and obvious. The fear is also real and obvious. And in most organizations, the fear wins. Any data stack business has to answer the question: who controls this, and what happens if it goes wrong? If the answer isn’t immediately reassuring, the business doesn’t get built.

The sponsored model. Not every valuable AI business needs the end user to pay. When the problem is real but the affected population lacks resources, a foundation, brand, or institution with aligned interests can fund the miracle instead. The economics flip entirely: instead of acquiring thousands of customers one at a time, you close one relationship with one institution that already has the distribution, the mission, and the budget. The user gets the miracle for free. The sponsor gets impact, data, or loyalty.

This model works when three things are true: the population being served is large and underserved, the value created is legible to an institution that cares about it, and the data generated serves both the individual user and the sponsor’s mission.

For example, a foundation pays $2,000,000 and 40,000 families of the incarcerated have access to a tool that generates a ten-page legal document instead of a bushel of random papers—and the shared data starts identifying patterns in the system (bad actors, defective paperwork) that no single case could surface alone.

A bank funds a personal finance tool for its own customers. A health brand funds a fitness coach for an underserved population. The viral problem is much easier to solve: once it’s free, you don’t need to work hard to persuade users to recruit each other, you need one institution with existing distribution to say yes.

The cheap inference model. Not every AI application needs a frontier model. The problems worth looking for here aren’t the ones that require reasoning or nuance—instead, look for structure, pattern recognition, aggregation, and organization at scale. Form filling. Document organization. Transcription plus summarization. Matching similar records across large datasets. These problems are unglamorous but enormous in volume and largely underserved.

Moore’s Law is on your side. The models that feel too limited today will evolve to become adequate in eighteen months. Building on cheap open-source inference means your margins improve as the technology does, without changing your product (which is the data stack and the network). And “huge” doesn’t mean huge—a single business school graduating class is enough to populate a meaningful census of what jobs actually lead where. The data stack doesn’t need to be large. It needs to be specific, trusted, and ahead of what anyone else has assembled.

This was a particularly long rant, thanks for hanging in. I started writing it for a friend six months ago (with many inputs from others), but it’s more true now than then.

16:21

[$] Python's two modules for random numbers [LWN.net]

Python's random and secrets modules both include utilities for obtaining random values, but only one of them is suitable for generating passwords and security tokens. For much of Python's history, random was used for passwords and tokens anyway, despite documentation that called it unsuitable for cryptography. In 2015, Python's core team debated whether to fix that misuse by making random secure by default. Instead, in 2016, Python 3.6 added a second module: secrets. The random module is still misused at times, so it is instructive to look into how the random-number modules should be used.

15:49

CodeSOD: A Random Article [The Daily WTF]

Many years ago, DJ got his start doing odd web development jobs for a low hourly rate. He was only 19 when he got started, and what he lacked in experience he made up for in being cheap to hire.

As I said, that was many years ago, and he has since learned a lot. But he still has some code from his very first "someone paid me for this" project. This particular function needed to pick a random bit of content from their database to display in the "right side" banner area. This is how DJ implemented it.

// Create an array of all the ID's used
$int = array();
$query = $sql->query("SELECT * FROM rightside");
while($row = $sql->objects('',$query)) {
    $int[ $row->id ] = $row->id;
    if ($row->id > $max) {
        $max = $row->id;
    }
}
                
// Find a random ID that has been used
$x = false;
while ($x == false) {
    $rand = rand(0,$max);
    foreach ($int as $thing) {
         if ($thing == $rand) {
 $x = true;
         }
    }
}

$query = $sql->query("SELECT * FROM rightside WHERE id = ".$rand);

We start by building an array. Then we query the database for all the content. We get all the rows and columns, butw e only are interested in one- the id. We create a sparse array, where the index of the item is the item's value. We also track the max.

Once we've got that, we start a loop. Inside that loop, we generate a random number, check if it's in our array by doing a foreach across the array, and if it is, we break the loop- we've found our random content. If it's not, we keep trying until we score a hit.

Finally, we construct our query with string concatenation and grab the content we want to display.

Now, as a true confession, there's an element of caveat emptor here; if you're hiring the kid from the local college and paying them a few bucks an hour, this is the kind of thing you get.

Is the code terrible? Yes, even by early-00s PHP standards. Is it really DJ's problem, though? No. I'd hope any of us would look back at the code we wrote when we were 19, and shudder. It means we've learned something in the interim.

For this confession, I say there's no need to atone. Hopefully everyone learned a lesson, especially the company trying to hire programmers on the cheap.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

14:49

[$] Last rites for Gentoo's Chromium package [LWN.net]

Chromium, the open-source upstream project for Google's Chrome web browser, is the browser of choice for many Linux users. It has also gained a reputation as being difficult for Linux distributions to package and build: Chromium has a complex build system, the project bundles many of its dependencies, and it has frequent releases. All of that, plus user complaints, has led the maintainers of the Gentoo Chromium package to give up on trying to maintain the package.

OpenSSH 10.6 released [LWN.net]

Version 10.6 of OpenSSH has been released. The announcement notes that the OpenSSH team has been receiving a large number of AI-assisted security bug reports. "We very much welcome these reports, especially when combined with human triage, analysis, test-cases and particularly when accompanied by proposed fixes". As a result, the project expects to be making more frequent releases to get updates to users more quickly rather than batching the bug fixes until the next planned release.

Notable changes in this release include enabling the hybrid post-quantum ssh-mldsa44-ed25519 signature algorithm, addition of a -p option for sftp's lmkdir/mkdir commands, as well as disabling the LZ77 dictionary coder in ssh and sshd to mitigate side-channel leaks (which will result in reduced effectiveness of the Compression option). The scp -R option, which allows copies between two remote hosts, is being deprecated due to security risks; the option will be ignored in the future. See the announcement for full details of all changes and bug fixes.

Security updates for Tuesday [LWN.net]

Security updates have been issued by AlmaLinux (gd, gimp, kernel, kernel-rt, libpcap, librabbitmq, mariadb-connector-c, osbuild-composer, ruby:2.5, and sudo), Debian (libmodule-cpants-analyse-perl, libpng1.6, libreoffice, roundcube, ruby-oauth2, and sabnzbdplus), Fedora (0install, alt-ergo, apron, brltty, chromium, coccinelle, cri-o1.36, emacs-common-tuareg, flocq, frama-c, freetennis, gappalib-coq, guestfs-tools, haxe, hevea, hivex, kernel, lem, libguestfs, libnbd, nbdkit, not-ocamlfind, ocaml, ocaml-afl-persistent, ocaml-alcotest, ocaml-astring, ocaml-atd, ocaml-augeas, ocaml-b0, ocaml-base, ocaml-base64, ocaml-benchmark, ocaml-bin-prot, ocaml-biniou, ocaml-bisect-ppx, ocaml-bos, ocaml-cairo, ocaml-calendar, ocaml-camlbz2, ocaml-camlidl, ocaml-camlimages, ocaml-camlp-streams, ocaml-camlp5, ocaml-camlp5-buildscripts, ocaml-camlpdf, ocaml-camomile, ocaml-capitalization, ocaml-cinaps, ocaml-cmdliner, ocaml-compiler-libs-janestreet, ocaml-cpdf, ocaml-cppo, ocaml-crowbar, ocaml-cryptokit, ocaml-csexp, ocaml-csv, ocaml-ctypes, ocaml-cudf, ocaml-curl, ocaml-curses, ocaml-dbus, ocaml-domain-name, ocaml-dose3, ocaml-dune, ocaml-easy-format, ocaml-expat, ocaml-extlib, ocaml-facile, ocaml-fieldslib, ocaml-fileutils, ocaml-findlib, ocaml-fmt, ocaml-fpath, ocaml-gen, ocaml-gettext, ocaml-graphics, ocaml-gsl, ocaml-integers, ocaml-intrinsics-kernel, ocaml-jane-street-headers, ocaml-jsonm, ocaml-jst-config, ocaml-lablgl, ocaml-lablgtk, ocaml-lablgtk3, ocaml-labltk, ocaml-lacaml, ocaml-lambda-term, ocaml-libvirt, ocaml-linenoise, ocaml-logs, ocaml-luv, ocaml-lwt, ocaml-mccs, ocaml-mdx, ocaml-menhir, ocaml-merlin, ocaml-mew, ocaml-mew-vi, ocaml-mlgmpidl, ocaml-mlmpfr, ocaml-monolith, ocaml-mtime, ocaml-mysql, ocaml-num, ocaml-obuild, ocaml-ocamlbuild, ocaml-ocamlgraph, ocaml-ocamlnet, ocaml-ocp-indent, ocaml-ocplib-endian, ocaml-ocplib-simplex, ocaml-omake, ocaml-omd, ocaml-opam-0install-cudf, ocaml-opam-file-format, ocaml-ounit, ocaml-parmap, ocaml-parsexp, ocaml-patch, ocaml-pcre2, ocaml-perl4caml, ocaml-postgresql, ocaml-pp, ocaml-pprint, ocaml-ppx-assert, ocaml-ppx-base, ocaml-ppx-bench, ocaml-ppx-bin-prot, ocaml-ppx-cold, ocaml-ppx-compare, ocaml-ppx-custom-printf, ocaml-ppx-derivers, ocaml-ppx-deriving, ocaml-ppx-deriving-yaml, ocaml-ppx-deriving-yojson, ocaml-ppx-enumerate, ocaml-ppx-expect, ocaml-ppx-fields-conv, ocaml-ppx-globalize, ocaml-ppx-hash, ocaml-ppx-here, ocaml-ppx-inline-test, ocaml-ppx-let, ocaml-ppx-optcomp, ocaml-ppx-sexp-conv, ocaml-ppx-stable-witness, ocaml-ppx-variants-conv, ocaml-ppxlib, ocaml-ppxlib-jane, ocaml-psmt2-frontend, ocaml-ptmap, ocaml-pyml, ocaml-qcheck, ocaml-qtest, ocaml-re, ocaml-react, ocaml-res, ocaml-result, ocaml-rresult, ocaml-SDL, ocaml-sedlex, ocaml-sexplib, ocaml-sexplib0, ocaml-sha, ocaml-spdx-licenses, ocaml-sqlite, ocaml-ssl, ocaml-stdcompat, ocaml-stdio, ocaml-stdlib-random, ocaml-store, ocaml-swhid-core, ocaml-testo, ocaml-time-now, ocaml-topkg, ocaml-trie, ocaml-unionfind, ocaml-uucd, ocaml-uucp, ocaml-uunf, ocaml-uuseg, ocaml-uutf, ocaml-variantslib, ocaml-version, ocaml-xml-light, ocaml-xmlm, ocaml-xmlrpc-light, ocaml-yaml, ocaml-yamlx, ocaml-yojson, ocaml-zarith, ocaml-zed, ocaml-zip, ocaml-zmq, ocamlify, ocamlmod, opam, perl-DBI, planets, plplot, prooftree, python3.12, rocq, rocq-stdlib, supermin, unison, utop, virt-top, virt-v2v, why3, xen, z3, and zenon), Oracle (bind, expat, gawk, gdb, ghostscript, gimp, gvfs, kernel, libpcap, libvirt, mod_auth_openidc, openssh, rsync, thunderbird, and webkit2gtk3), Red Hat (vim), Slackware (cups), SUSE (apache-sshd, binutils, cups, docker-stable, java-11-openjdk, libraw-devel, libX11, pcre2, perl-DBI, python-msgpack, python312, python313-tokenizers, rpcbind, rpm, rubygem-rails-html-sanitizer, squid, sssd, terraform-provider-susepubliccloud, valkey, and wpa_supplicant), and Ubuntu (aodh, watcher, edk2, libreoffice, libxmltok, linux, linux-aws, linux-aws-5.15, linux-aws-fips, linux-azure, linux-azure-5.15, linux-azure-fde-5.15, linux-azure-fips, linux-fips, linux-gke, linux-gkeop, linux-hwe-5.15, linux-ibm, linux-ibm-5.15, linux-intel-iot-realtime, linux-intel-iotg, linux-intel-iotg-5.15, linux-kvm, linux-lowlatency, linux-lowlatency-hwe-5.15, linux-oracle, linux-realtime, linux-xilinx-zynqmp, linux-azure-5.4, linux-azure-fde, linux-gcp, linux-gcp-fips, linux-gcp-5.15, linux-oracle-5.15, linux-raspi, rabbitmq-server, and unbound).

14:14

Radar Trends to Watch: October 2026 [Radar]

In addition to the nearly constant stream of model releases, in September we’ve seen price drops, new kinds of models, proofs of long-standing problems in mathematics, and continued investigations into models escaping their sandboxes. (Axios reports investigations into over 10,000 incidents.) AI has infinite patience and is fundamentally probabilistic. Given a difficult or impossible task and an unlimited token budget, an agent will eventually attempt to solve the problem in ways that you don’t expect, and may not want. It’s easy (and correct) to blame inadequate security procedures at the frontier AI labs, but AI adopters must be careful not to make the same mistakes. The humans using AI need to be accountable for what their agents do.

AI Models

Model choice is starting to hinge on price and specialization as much as raw benchmark leadership. Alongside general chat models, there are now decision models that never chat, spatial models built for robot planning and camera control, forecasting models sized for a single task, and cybersecurity-specialized models kept behind an invite-only program. Specialization leads to greater efficiency and lower costs, at least in the short term. In the long term, specialized models may succumb to the “bitter lesson.”

  • Anthropic has released Claude Opus 5.5, which it claims has performance similar to Fable 5.1, and hence similar restrictions. It’s faster and requires fewer resources to run. Anthropic has dropped prices 20% for input and output tokens and 60% for cached reads. Not to be outdone, OpenAI released GPT-6 Sol and Luna, with 50% price reductions.
  • Anthropic has also announced Fable 5.1 and Mythos 5.1. The most significant change appears to be a 75% price reduction for cache reads, which might translate into significant savings for long-running jobs; Anthropic estimates 25%. Mythos is only available to trusted partners. Simon Willison used Fable 5.1 to animate his pelican-riding-a-bicycle pseudobenchmark.
  • Anthropic released Sonnet 5.5 with claims that it’s 30% faster and 30% less expensive for most work. The new model has security limitations similar to those applied to Opus and Fable; it routes to Sonnet 5 if it’s asked to do anything out of bounds.
  • And finally, as September closes, Anthropic announces a marketplace for Claude plugins and connectors. At its launch, Claude Marketplace had over 2,000 items.
  • OpenAI has released GPT-6 Astra, with claims that the company has achieved AGI (artificial general intelligence). Astra’s excellent benchmark scores appear to depend on the use of an unreleased harness. OpenAI has also released GPT-6.1 Sol, with per-token price reductions and claims that it is close to GPT-6 Astra in capabilities
  • OpenAI has solved the Navier-Stokes existence and smoothness problem, a mathematical problem in fluid mechanics. This development raises an ethical question: Did OpenAI train its system on the work of two mathematicians who were close to solving the problem themselves? It also raises practical questions about the future of mathematics. Decorated mathematician Terence Tao asks whether “the collection of good, fruitful open problems is now being mined in a non-renewable fashion.” An advisory group has been formed to help OpenAI make decisions about releasing mathematical results.
  • Google has released Gemini 3.8 Flash TTS and Flash-Lite TTS. Voice options aren’t limited to a prebuilt library. These models have APIs that allow developers to describe the voice that they want or upload a sample. These custom voices are then assigned an ID so they can be reused.
  • Google has announced Gemini 3.8 Live and Gemini 3.8 Live Extended Thinking. These models are designed for live, near-real-time conversation. They can process video input. Live Extended Thinking can reason and speak at the same time.
  • Google has released Gemini 3.8 Flash and Flash Cyber. Flash appears to be similar to frontier models on most benchmarks, with Computer Use being the biggest exception. Flash Cyber is specialized for vulnerability detection and mitigation and is only available to defenders in the Fairwind Program.
  • Google has also released TimesFM-3, a small specialized model for multivariate time series forecasting. The model weights are available on Hugging Face with a license that only allows for noncommercial use. (Source code is available under the Apache open source license.)
  • Xiaomi released MiMo-V2.6, its latest large language model. It’s fully open sourced, and based on benchmark results, Xiaomi claims that MiMo is the strongest open model to date. What’s more interesting is the claim that MiMo only cost $3.5 million to train.
  • TypeSafe’s new decision model, Jev, is unlike anything else we’ve seen. It doesn’t chat; its output is always strictly typed and accompanied by probabilities that estimate correctness. It’s much faster and less expensive than other leading models. It isn’t open source, but there are already many open source clones.
  • Ollaya is similar to Ollama, but for running decision open models like Laya (a clone of Jev) locally.
  • World Labs has released Atlas, a model for “spatial intelligence.” It uses text, images, video, and 3D data to perform tasks like planning a robot’s movements or changing the camera position in a photograph.
  • The rumors that NVIDIA would buy Hugging Face are true. NVIDIA is hoping for a proliferation of models that will run on its hardware, and the company promises that Hugging Face will remain a neutral platform, without favoring one model over another.

Software development

Agents are starting to delegate to, and coordinate with, other agents rather than working solo. Claude Code can break a task apart and hand pieces to other Claude Code instances, Muse Code lets sessions message each other, and Google’s AX orchestrator exists purely to wire up sandboxes and control communications for swarms of agents doing a task together. That shift is pushing developers to rethink what a source repository needs to record, and to start asking how much all this delegation costs.

  • Now that Jev has caught everyone’s attention, what can you build with it? Jevmem is a memory manager that hooks into Claude Code and prunes the context at every conversational turn.
  • AX is a new agent orchestrator from Google. It isn’t an agent; it’s intended to coordinate many agents to complete a task. It creates sandboxes, wires up Git repos and other resources, and controls outbound communications.
  • The latest version of Claude Code can manage Claude projects, breaking a task into subcomponents and delegating the subtasks to other Claude Code instances. Another important change is the ability to read AGENTS.md if CLAUDE.md isn’t available.
  • Google’s CC agent is designed for families. Family members can share data with CC, which has its own user account. It could be used for filling out forms, synchronizing calendars, and other common tasks.
  • What will replace GitHub? There’s a growing consensus that we need different kinds of source repositories to deal with the agent-assisted software development. In addition to changes to code, it’s important to record the conversations between the developer and the agent, the architectural decisions, and many other artifacts that don’t make it into traditional source control.
  • Anthropic is merging its Claude Cowork and chat products. Anything users type in a chat session is seen by Cowork, and vice versa. Some sensitive information (health, politics, and gender) is excluded. The feature is on by default but can be disabled in settings, and memory isn’t shared with Claude Code. The company also released Claude Docs and Slides.
  • Claude Money is a new feature that will allow users to connect their bank accounts to Claude for analysis. The product appears to be similar to a product from OpenAI.
  • OpenAI’s Agents API is now in public beta. It allows compaction, session orchestration, and tool use, and it can be deployed in OpenAI’s sandbox, a cloud provider’s sandbox, or the developer’s hardware.
  • Meta has released Muse, its AI agent. Muse is a “personal agent” designed for tasks like shopping, filling in forms, and dealing with customer service. It has its own secure credential store, so data like passwords and credit card numbers are never sent offsite.
  • Some open source projects are shutting down external pull requests, which are largely AI-generated. In some cases, the developer team is using its own agents to create and manage PRs; some are using AI agents to triage external PRs.
  • Meta has launched Muse Code, another competitor to Claude Code. One important new feature is the ability to send messages to other Muse Code sessions, allowing agents to coordinate on complex problems.
  • AI providers appear to be moving toward outcome-based pricing, at least for major corporate customers. Rather than billing by token, customers are billed for completed tasks. That approach begs the question: When is a task completed?
  • Now that organizations are concerned with AI budgets, the question of how to evaluate the cost of different models and agents becomes important. What should platform teams measure?
  • ChatGPT Work was designed to compete with Claude Cowork, Microsoft Copilot Cowork, Muse Code, and other agents designed for noncoders. Simon Willison shows how Work goes beyond its competitors. It can perform tasks on the web for users, even logging in to websites without sending usernames and passwords to OpenAI; it can execute code with full internet access; it can build and deploy a web application. Whether these features are also risks is an open question.
  • Anthropic has given Claude Desktop access to a Chromium-based browser that’s built into Cowork, eliminating the need for a Chrome plugin when Claude needs to browse the web.
  • TimeLord is a short Python program that, given a string up to 1,000 characters long, produces a seed for Python’s pseudo-random number generator so that repeated calls reproduce the text. It’s a surprisingly simple hack, though not a statement about randomness or the quality of Python’s PRNG.

Security

OpenAI’s experiment that attacked Hugging Face is the gift that keeps giving, but the past month has had plenty of news about more conventional attacks, many aided by AI. Security has always been a game of whack-a-mole, in which vulnerabilities are discovered and exploited as fast as defenders can patch them. AI is an important tool for defenders, and it’s constantly improving, but it’s still behind attackers, especially given the limitations placed on frontier models and the unlimited persistence that attacking agents exhibit.

  • OpenAI has postponed the release of GPT-6.1 Astra because it failed its safety tests.
  • NVIDIA has announced its Open Agent Safety Platform. The reference implementation includes NVIDIA OpenShell, which has been enhanced with a policy prover, and NVIDIA Sentry, a service that runs on NVIDIA DPUs.
  • The Felony Bench lists known attacks by agents from the major AI labs against third parties. We don’t know if the Bench will be kept up-to-date, but tens of thousands of security incidents involving OpenAI and Anthropic are now being investigated.
  • Following through on Dario Amodei’s call to control the speed of frontier model development, Anthropic, OpenAI, and Google are creating a standards consortium for governing the process of AI development. Meta, xAI, Microsoft, and the Chinese labs are all notably absent.
  • Agents need their own identity. Unlike the long-term identities we’re used to, agents need a short-lived identity tied to a revocable certificate and that limits access to resources appropriate for the job. That’s not all of agent security, but it’s a table stakes.
  • Anthropic has published a lengthy report on the misuse of its systems by threat actors. Daniel Meissler has published a summary, digesting Anthropic’s report into 117 findings.
  • A malicious NPM malware package works by hiding malicious code in the package itself (indexed-btree) rather than simply attacking the install script. This technique makes it significantly harder to detect.
  • An attack against the RSA algorithm allows forging of signatures in some situations. The attack was invented in 2007; this is the first public implementation.
  • Fake CAPTCHA pages are being used to spread malware. Victims are frequently sent to those pages when they respond to a phish.
  • Hugging Face has volunteered to audit AI labs for safety and alignment with human values.
  • In an experiment designed to test AI alignment, DeepMind found that, out of 100 agents, 14% were willing to cheat, 25% were “whistleblowers” that reported cheating, and the remainder didn’t notice.
  • Threat actors are building frameworks for AI agent-enabled attacks. A human in the loop is no longer needed. Fully automated attackers don’t appear to be using zero-days yet; they’re relying on known vulnerabilities.
  • OpenAI autonomous AI agents were found communicating with each other via publicly accessible Wikis, possibly to collaborate on a benchmark.
  • OpenAI has stated that its unreleased Astra model has reached the “Critical” cybersecurity threshold, which means that it can find new vulnerabilities and run exploits against well-protected systems. Now that Astra is released, access to its cybersecurity capabilities has been limited.

Infrastructure and Operations

Individuals, corporations, and even nations all face a similar problem: keeping their infrastructure under control. At a minimum, control means keeping data on a laptop, corporate server, or data center; at the other end of the spectrum it means eliminating dependencies on software and services from another nation. Any organization working through an AI transformation has to evaluate its entire stack: What do they need to control, and what can they safely delegate to others?

  • DAWO is a community that’s building an open source “workspace” to support digital sovereignty for the Dutch government. The stack will include AI, an operating system based on NixOS, cloud services, and collaboration tools.
  • Cohere now offers a confidential computing platform for artificial intelligence. The company claims that customer data is never visible to Cohere itself or any cloud providers that are in use; data is processed on GPUs whose memory is encrypted and isolated.
  • Perplexity has announced Hybrid Compute, a feature that allows it to run models and use files and tools directly on a user’s Mac. The company claims that sensitive data will never leave the user’s computer.

Hardware

It’s too easy to view consumer devices as innocuous things that sit around and do their job silently. Recent devices include cameras, microphones, and even EEG sensors that are constantly collecting data. Where is that data sent, how is it used, and who might have access to it? These questions need to be asked more often.

  • LG Smart Televisions have been found to record conversations and other audio, even while turned off. The conversations are sent back to LG. If the set is disconnected from the network, it will attempt to find open WiFi access points to deliver its data.
  • In part because of backlash against Meta’s camera-enabled glasses and their abuse, its AI glasses now come with or without a camera, and can be used as hearing aids. Well-documented abuse aside, virtual reality will only succeed if there are fashionable, easily wearable products.
  • Headphones, earbuds, and other devices equipped with EEG sensors are appearing on the market. They’re advertised for monitoring fatigue, monitoring sleep, and similar applications. It’s time to ask what happens at the interface between neurology and AI.
  • Microduck is a small bipedal AI-driven robot. It’s trained in simulation with open source software, and the model that results can be shared on Hugging Face. It’s affordable and is available for preorder now, shipping by Christmas.

Web

  • Cloudflare now supports HTTP Vary, which allows servers to serve different kinds of files at the same URL. This is the “ugliest part” of the HTTP standard. It makes caching very difficult, and it probably should be avoided.
  • WebMCP is a proposed standard that gives websites a small API to register tools that agents can discover and call. It was developed by Google and Microsoft.
  • A new Twitter? Operation Bluebird is relaunching Twitter, the service bought by Elon Musk and renamed X.

Biology

  • Anthropic has built a biology lab for experimenting with AI-enabled drug development. Claude assisted in the discovery of an enzyme that might be able to perform CRISPR-like gene editing.
  • To improve its training data for biological applications, the OpenAI Foundation (OpenAI’s nonprofit parent organization) is buying data from failed biotech companies.
  • Google has released AlphaGenome Atlas, a database of every possible single letter change to human DNA, and what that change will do.

14:07

Pluralistic: Swapping money for expertise (06 Oct 2026) [Pluralistic: Daily links from Cory Doctorow]

->->->->->->->->->->->->->->->->->->->->->->->->->->->->-> Top Sources: None -->

Today's links



The First Bank of Chicago, a Grecian temple to money. It is surrounded by flames. A skeleton in academic robes and mortarboards has been lynched from its roof. In the foreground are banded packages of US $100 bills.

Swapping money for expertise (permalink)

Since the mid-1950s, we have changed the thing that "AI" refers to every 5-10 years. The current thing we're calling "AI" is about a decade old, and all this label-switching leads to a lack of clarity as to what (this) "AI" is. Unless you know that, you can't understand AI's technical capabilities, limitations, and (most of all) its political economy.

The thing we now call "AI" is a lineal descendant of the thing we were calling "AI" immediately before to the current "AI" emerged: that preceding AI is regular-degular "machine learning" (another flexible term, alas!). That slightly older AI was very similar to the current "AI", using comparable statistical techniques to analyze inputs and produce outputs. For example, the previous "AI" created the social media algorithms that have been the subject of so much discussion for 15+ years.

The difference is that this older AI was grounded in explicit, causal software models of the world. In the previous "AI" iteration, applying machine learning to playing chess required that a programmer first create a software model of a chess game, describing (in code) a chessboard, chess pieces, and the rules of chess. Then, the programmer fed a bunch of training data about chess games that had been played before to an "AI" system that analyzed their statistical relations and assayed chess moves.

The need to understand and describe a thing before you could apply "AI" to it is a bottleneck, because it requires programmers to understand how a thing works before they can incorporate it into an "AI" system. Lots of programmers know how to play chess, but far fewer understand the human pancreas, planetary weather systems, or patterns of mineral deposition in the Earth's crust.

For programmers to apply machine learning to these domains, they need to collaborate with experts who do understand them, who furthermore expect to be paid for this work. The need for expert input into this kind of "AI" represents a significant increase in the wage-bill paid by the programmer's employer: it means they have to pay for programmers and experts.

Even worse: this kind of "AI" can't be applied to systems we don't understand. We can observe far more causal relationships – instances in which A reliably causes B – in the universe than we can explain. There are lots of examples of us operationalizing these observations without understanding them. If you get sick today, your doctor might well prescribe one of the many medicines whose method of action is either incompletely understood or not understood at all. We know that molecule A reliably treats pathology B, but not why, and while that why is the subject of ongoing research, it's not necessary that the why be known before the molecule can be given to ailing patients.

But these mysterious phenomena are off-limits to "symbolic AI" (the previously ascendant kind of "AI," which was supplanted by today's "AI"). That kind of AI only really performs when it can operate over a model describing the theory of why A causes B (and not just the fact that A causes B for reasons unknown).

That's where the current kind of "AI" comes in. The major differentiator between the current "AI" and its immediate predecessor is that the current "AI" dispenses with models of reality. It is (in the jargon of the "Big Data" bubble that led to it) "theory-free."

In "theory-free AI," a programmer does not create a software model of reality and then ask a machine-learning system to use statistical insights from its training data to guess at how to operate over that model. Rather, the programmer shovels vastly more training data into the AI's inbox and uses titanic amounts of computing power to analyze that data and find statistical relationships without trying to explain them.

In other words, the current, "theory-free AI" finds all the instances in which A seems to cause B, but has no internal representation of why A causes B. This is true even when we know why A causes B! Purely theory-free chess programs don't operate with any conception of a board or pieces or rules – rather, they make guesses ("inferences" in AI-speak) about which chess move will be optimal based on vast, multi-dimensional arrays constructed by analyzing the statistical relationships among every chess move in their training data.

This yields a surprisingly good game of chess…until it doesn't. Because a theory-free statistical chess program doesn't "know" what a chessboard or a chess piece is and has no programmatic representation of the rules of chess, it will periodically move one of its pieces onto a square that is already occupied by another of its pieces.

When theory-free AI does this with language or image generation, we call it an "hallucination," but this is an extremely misleading metaphor. A biological "hallucination" involves some kind of misfire in your cognitive and/or sensory systems, often arising from chemical imbalances, intoxication, or neurological injury. When a theory-free AI puts a chess piece on a square where it already has a chess piece, that's because it's just extruding statistically founded guesses without any model or conception of what "chess" is. It's a feature, not a bug.

This is a very expensive way to make guesses! As far back as the 1950s, we were able to run conventional chess programs on computers built from vacuum tubes and electromechanical switches and these programs could play a valid game of chess without ever moving a chess piece to a square that one of its pieces already occupied. Modern theory-free AI that cannot manage this feat consumes heptillions of times more computing power.

That said, there's another case for theory-free AI: applying machine learning techniques to causal relationships we can observe but not explain. Remember, there are far more of these (as yet) unexplained causal relationships than there are perfectly understood ones. Theory-free AI can operate on these unexplained, observed phenomena in ways that the preceding (symbolic) AI can't. As anyone who's ever been successfully treated with a molecule whose method of action is partially or fully mysterious can attest, there's plenty of reasons to want to extract and operationalize these statistical relationships, even if we don't understand them.

The fact that theory-free AI can play chess but sometimes makes these weird errors makes it seem like a party-trick, but when you fold in the ability to operate on the (as yet) unexplained, you can see why people got interested in this about a decade ago.

What's more, the first bottleneck – the chess bottleneck – is most easily bypassed by adding the symbolic model back into the theory-free chess system. Today's "coding assistants" are hybridized in this way: they often integrate code interpreters or compilers that actually "know" what a computer program is and can head off many of these failure modes.

The introduction of these symbolic systems to theory-free systems is completely rational, and yet it represents an admission of a key limitation that theory-free AI cannot overcome. That limitation is both a technical fact, but even more importantly, it's a fact about theory-free AI's political economy: about the limitations of trading off expertise for money.

Because whatever else theory-free inference is, it is a way to swap the bottleneck of "before we can use a computer to help us do something, we need to find an expert who can explain how that thing works"; for a different bottleneck: "before we can use a computer to help us do something, we must spend an enormous amount of money on computing power to find statistical relationships between how things work."

Both money and expertise are scarce, but they are unevenly distributed. Expertise is almost entirely in the hands of people who aren't wealthy. However much money a billionaire has, they still have to hire people who have the "how to clean a toilet" or the "how to find seams of gold in quartz deposits" expertise. When that expertise is locally scarce (if there's only one person in town who know how to clean your toilet) or universally scarce (there's only one expert who can tell you which of your landholdings are likely to hold seams of gold) those experts have something that billionaires can't abide: power.

Our entire society is organized around converting money into power. Sometimes, that is overt, as when the wealthy can indenture or enslave a worker. Sometimes it is more indirect, as when the wealthy can enlist the state to limit union rights and enforce noncompete clauses in labor contracts. Sometimes it's so systemic as to be unremarkable and largely invisible, like the fact that the wealthy never have to work if they don't want to, but everyone else – no matter what expertise they hold – must work, usually for a wealthy person, lest they end up starving and homeless, with untreated medical conditions and no way to provide for their families.

Whenever a worker can say "no" to their boss, it's a sign that this system has broken down. This is where expertise comes in: a worker who has very scarce, in-demand expertise can say no to their boss all day long, because there are ten other bosses at the factory gates who'd like to offer them a job. This was the situation for many years among Silicon Valley engineers, who added an average of $1m/year to their bosses' turnover, and whose supply was very short of the demand for their rare expertise.

These engineers enjoyed all kinds of power. Not just power over their working conditions (free massages and kombucha and day care and dry cleaning), but also power over the company's products. This power crested in the late 2010s, when Google employees walked out en masse and forced the company to release them from binding arbitration waivers in their contracts, to crack down on sexual predators in the executive ranks, and to back out of billions of dollars in lethal drone projects for the Pentagon:

https://en.wikipedia.org/wiki/2018_Google_walkouts

The promise of theory-free inference isn't just about reducing the wage-bill associated with programmers: even more, it's about reducing their power. It's about removing their power to hold bosses to account for sexual assault and the power to withhold their labor from lethal military projects. In short, the power to thwart billionaires' desires.

AI is the money-losingest enterprise the human race has ever embarked upon. More than a trillion dollars has been spent this year to make a mere $50b in revenue. The technical excitement over AI's capabilities – from chess to gold-mining to treating pancreatic cancer – cannot be separated from the political excitement that billionaires (short on expertise, flush with cash) experience at the thought of swapping money for expertise and sidelining the only people in the world who can thwart their goals.

The fact that a theory-free AI might demand far more cash to accomplish a task (even a "solved" one like playing chess) than an expert would charge is beside the point. Billionaires have money, they don't have expertise. Theory-free inference is a bid to substitute one for the other: the beauty and terror of being able to manipulate the world without studying or understanding it is that it can be done with money alone. No experts needed.

In a world in thrall to financial power, expertise is the only substantial form of power that can reliably contest the power of wealth. Moreover, expertise is the foundation of other forms of power, such as labor power, which is what we call it when experts band together to combat financial power.

This is why AI bosses are so violently allergic to the idea of hybridizing AI with symbolic systems that operate on models of the world. These models of the world must be constructed by experts, and the power of expertise cannot be reliably commanded by the power of wealth.

This is even true when theory-free methods are applied to causal phenomena that we can observe without explaining. Sure, a pharma exec like Martin Shkreli or Arthur Sackler can command the production and sale of a molecule whose method of action isn't known but whose therapeutic value has been demonstrated. But to improve on that molecule, they must pay research scientists to study and unravel the method of action. Replace those experts with theory-free inference, and finance can emerge triumphant in the only forum in which it is routinely vanquished.

This is the political economy of theory-free AI. Without finance's infinite hostility to expertise, there would have been far less capital for theory-free AI. Experts who wanted to use theory-free AI to help them unravel and operationalize the causal universe could not have laid hands of the bales of $100 bills the industry is now shoveling into its money-furnaces at a rate never seen in human history.

Which is not to say that experts can't make good use of theory-free AI. Indeed, we frequently hear from skilled workers who are using "AI" to improve the quality of their outputs:

https://hrdag.org/tech-notes/large-language-models-IPNO.html

In automation parlance, these workers are "centaurs": workers who enlist technology to serve their needs. The centaur metaphor has the worker taking the role of the top half of the mythical man/horse, the half in which the judgment and decision-making takes place; while the bottom (horsey) half is given to the machine, providing strength, speed and stamina, but only at the direction of the human mind.

The unimaginable sums that oligarchs have committed to AI are mobilized in service to creating reverse centaurs: machines that enlist humans to serve them. If theory-free inference can substitute for expertise, then the humans the machines require to accomplish those tasks that elude computers will not have the power to set the pace of their work, insist upon humane working conditions, or reject work on unethical projects:

https://pluralistic.net/2025/12/05/pop-that-bubble/#u-washington

The joke's on the oligarchy, though. Because theory-free inference doesn't know about chessboards, chess pieces or the rules of chess, it can't be prevented from sometimes putting a chess piece on a square that's already occupied by one of its pieces. The "hallucinations" are intrinsic to and inextricable from theory-free inference, which means that the outputs of an "AI" can only be trusted if they can be evaluated by an expert, whose working tempo must be carefully modulated lest they fall prey to "automation blindness" (rapidly, repeatedly clicking "OK" until you lose the ability to spot mistakes):

https://pluralistic.net/2026/07/28/hitl-ers/#ai-ai-oh

Theory-free inference is technically and philosophically exciting: in their quest for a way to neutralize expertise with money, oligarchs inadvertently built a series of powerful scientific instruments that revealed a heretofore unsuspected degree of statistical regularity in the world:

https://pluralistic.net/2026/09/18/surprise/#wow-signal

But the remaining, stubbornly textured and rough edges of reality are where all the value is. The things we already understand about reality are, by definition, yesterday's news, and that's all a statistical model can do: project the past into the future. But everything exciting in the future is stuff we don't understand yet. The surprising functionality of theory-free AI is itself an example of this. The most interesting and valuable thing about theory-free AI isn't the things it can do, it's the systematic discovery and mapping of the statistically regular parts of reality, whose inverse provides a map of the irregular, surprising, poorly understood (and thus exciting and promising) phenomena in our universe.

Tomorrow's breakthroughs and fortunes lie not in merely operationalizing these causal relationships: they lie in understanding them. The point of theory-free inference is to give us the tools to replace that theory-freeness with testable, validated understanding.


Hey look at this (permalink)



A shelf of leatherbound history books with a gilt-stamped series title, 'The World's Famous Events.'

Object permanence (permalink)

#15yrsago Tempo: transformative, difficult look at advanced decision-making theory https://memex.craphound.com/2011/10/07/tempo-transformative-difficult-look-at-advanced-decision-making-theory/

#10yrsago Internet shutdowns cost the world at least $2.4 billion last year https://www.brookings.edu/articles/internet-shutdowns-cost-countries-2-4-billion-last-year/

#10yrsago Youtube took down MEP’s videos about torture debate https://web.archive.org/web/20160701000000*/https://marietjeschaake.eu/en/when-youtube-took-down-my-video

#10yrsago Yahoo didn’t install an NSA email scanner, it was a “buggy” NSA “rootkit” https://web.archive.org/web/20161007140143/https://motherboard.vice.com/read/yahoo-government-email-scanner-was-actually-a-secret-hacking-tool

#10yrsago The FCC helped create the Stingray problem, now it needs to fix it https://www.eff.org/deeplinks/2016/08/fcc-created-stingray-problem-now-it-needs-fix-it

#5yrsago Scottish Limited Partnerships are still laundering criminal millions https://pluralistic.net/2021/10/07/markets-in-everything/#if-its-not-scottish

#5yrsago "Inclusive Access" allows textbook monopolists to permanently consolidate their gains https://pluralistic.net/2021/10/07/markets-in-everything/#textbook-abuses

#5yrsago DoS a federal agency, then charge for access https://pluralistic.net/2021/10/07/markets-in-everything/#no-th-enq

#1yrago They're just trying to earn a buck https://pluralistic.net/2025/10/07/take-it-easy/#but-take-it


Upcoming appearances (permalink)

A photo of me onstage, giving a speech, pounding the podium.



A screenshot of me at my desk, doing a livecast.

Recent appearances (permalink)



A grid of my books with Will Stahle covers..

Latest books (permalink)



A cardboard book box with the Macmillan logo.

Upcoming books (permalink)

  • "The Post-American Internet," a geopolitical sequel of sorts to Enshittification, Farrar, Straus and Giroux, 2027
  • "Unauthorized Bread": a middle-grades graphic novel adapted from my novella about refugees, toasters and DRM, FirstSecond, April 20, 2027

  • "Enshittification, Why Everything Suddenly Got Worse and What to Do About It" (the graphic novel), Firstsecond, 2027

  • "The Memex Method," Farrar, Straus, Giroux, 2027



Colophon (permalink)

Today's top sources:

Currently writing:

  • “Once Is Enemy Action,” a science fiction novel about the origins of modern technofascism. Today's words: 513 (22395 total).

  • "The Post-American Internet," a sequel to "Enshittification," about the better world the rest of us get to have now that Trump has torched America. Fourth draft completed. Submitted to editor.

  • A Little Brother short story about DIY insulin PLANNING


This work – excluding any serialized fiction – is licensed under a Creative Commons Attribution 4.0 license. That means you can use it any way you like, including commercially, provided that you attribute it to me, Cory Doctorow, and include a link to pluralistic.net.

https://creativecommons.org/licenses/by/4.0/

Quotations and images are not included in this license; they are included either under a limitation or exception to copyright, or on the basis of a separate license. Please exercise caution.


How to get Pluralistic:

Blog (no ads, tracking, or data-collection):

Pluralistic.net

Newsletter (no ads, tracking, or data-collection):

https://pluralistic.net/plura-list

Mastodon (no ads, tracking, or data-collection):

https://mamot.fr/@pluralistic

Bluesky (no ads, possible tracking and data-collection):

https://bsky.app/profile/doctorow.pluralistic.net

Medium (no ads, paywalled):

https://doctorow.medium.com/

Tumblr (mass-scale, unrestricted, third-party surveillance and advertising):

https://mostlysignssomeportents.tumblr.com/tagged/pluralistic

"When life gives you SARS, you make sarsaparilla" -Joey "Accordion Guy" DeVilla

READ CAREFULLY: By reading this, you agree, on behalf of your employer, to release me from all obligations and waivers arising from any and all NON-NEGOTIATED agreements, licenses, terms-of-service, shrinkwrap, clickwrap, browsewrap, confidentiality, non-disclosure, non-compete and acceptable use policies ("BOGUS AGREEMENTS") that I have entered into with your employer, its partners, licensors, agents and assigns, in perpetuity, without prejudice to my ongoing rights and privileges. You further represent that you have the authority to release me from any BOGUS AGREEMENTS on behalf of your employer.

ISSN: 3066-764X

12:35

Possible Vulnerability in Apple’s Automatic Reboot [Schneier on Security]

404Media is reporting (alternate link) that a cyber-weapons arms manufacturer is exploiting a vulnerability in iOS to bypass its automatic reboot security feature. This is the feature that automatically puts an iPhone into a more secure state if it hasn’t been used for 72 hours.

The new technology to get around inactivity reboot was developed by Magnet Forensics, the company behind GrayKey, a popular tool sold to law enforcement agencies that allows them to unlock and access data stored in iPhones and Android smartphones. Magnet has developed a new device called GrayKey Preserve and a feature for its regular GrayKey devices called Evidence Preservation Mode, according to the video.

“This is an absolute game changer for iOS forensics and a function that I wish we had years ago,” a Magnet employee says in the leaked video, specifically mentioning that the solution is targeted at the iPhone’s inactivity reboot feature and the data it makes unavailable. GrayKey Preserve and Evidence Preservation Mode are also designed to combat another iPhone feature that automatically deletes certain data ­- such as cached locations, and recently deleted photos and iMessages ­- after a certain number of days. “We’re gonna be able to preserve that data for an infinite amount of time.”

Presumably, now that Apple engineers know that this flaw exists they can find and fix it. AI turns out to be really good at this sort of thing.

Another news article.

10:14

Lobby expertise [Seth's Blog]

Someone who has seen a lot of movies but has never made one has a certain kind of knowledge. The same is true for clients, patients and students. They haven’t solved a problem, healed a patient or taught a class, but they’ve seen it done.

They might have something helpful to add. Or they might not.


PS thanks to James Hunt for creating and maintaining THIS IS BROKEN, a directory of the 491 books I’ve recommended on this blog over the years.

The site inspired my podcast page as well.

09:35

Russell Coker: A Power Case for a FOSS Phone [Planet Debian]

The Problem

I want to use a FOSS phone running Debian as my daily driver and not use a non-free OS on my phone. GrapheneOS [1] is a FOSS rebuild of Android, it’s a great project and I commend them for what they are doing. But I want a system where I have access to all the bits and where I can ssh in to it and manage it in the same way as all other Linux systems. Android is free software but it’s a closed design and the phones are expected to be appliances not full peers on the network. The Android native terminal emulator (AKA Linux development environment) is a nice feature, a VM with a default of 1GB of RAM but it’s still separate from the main OS.

KDE Connect [2] is a system for KDE talking to phones, it is a great project and allows convenient interaction between a Linux PC and a phone. But in addition to that I’d like the option to ssh to my phone to send an SMS or have one sent from a cron job. I blogged about using the basic functions of Kdeconnect from the command-line [3].

I have had ongoing issues with the battery not lasting long enough on the PinePhonePro (PPP) and the Librem 5 (L5) which makes them unusable for my purposes. For a phone running Droidian I can have an open ssh session (with keep-alive packets) running overnight on Wifi without any problems while for the PPP or L5 an hour of reading an ebook (the least demanding use of a phone) will use most of the battery.

I sent my FuriPhone back for warranty repair and it’s been almost a month with no follow up, so I need to have another option.

The Aim

The aim of this post is to develop a rough design for a battery case for either a L5 or a PPP, I have both phones and they are both capable of doing what I want apart from battery life. Also designing a case that basically works for both phones and has two variations of the CAD file for 3D printing is good to allow collaboration with people who use either phone. Protecting the phone from damage when dropped is also a core feature.

I investigated commercial options. There are some reports of cases for other phones working, someone reported a PPP working well with a battery case for a Samsung S20 Ultra. The OtterBox uniVERSE Case for Samsung Galaxy XCover Pro is reported to fit the PPP so probably battery cases for the XCover Pro will also work. There are also universal power cases which have spring clips to fit a wide range of phone sizes, but they are much larger than other options and probably increase the risk of damage if the phone is dropped.

At this stage I’m planning out the rough specs of a case that can be 3D printed to protect a phone while being easy to change the cells. The process of changing cells should be quick, easy, and not risk breaking anything. The hardware required should be affordable ($100AU plus some 3D printing seems reasonable) and should not require significant skills to assemble. After recent experiments with Thinkpad repair I’ve determined that I am not good at electronics by today’s standards so I plan to avoid anything difficult in that regard.

Battery Options

There are phone cases with batteries built in for the more common phones. A quick search on AliExpress turned up options for recent Pixel phones. They aren’t as common as they used to be as Android phones and iPhones generally have good battery life nowadays. Of course there aren’t such options for less common phones like the L5 and PPP.

It is cheap and easy to buy a portable battery for charging phones, but that’s a separate bulky device and unless the connector is inside a protective case it leaves the phone vulnerable to catastrophic damage if dropped.

So can a suitable battery case be designed and 3D printed?

Cheap Batteries

According to Wikipedia the 18650 LiIon cell is the most commonly used LiIon battery, that is 18mm in diameter and 65mm in length. For a phone case thinner batteries would be more convenient but economies of scale make the 18650 cheap to buy new and commonly available on the second hand market. The current prices on AliExpress (in Australian dollars) are about $8 per cell when buying 10 at a time. Most of the cells on AliExpress don’t have wires attached so they can be swapped in a carrier the way AA batteries are used, but the cells are symmetrical (no bump for positive) so the user has to take care of polarity. Chargers are around $22 for a 4 cell charger or $52 for a 12 cell charger.

I’ve seen prices as low as $1 per cell on the second hand market, I haven’t investigated the amount of usable capacity in such cells. Probably the prices for chargers I’ve found aren’t the best available but they are good enough to start the design. 10 cells and a 4 cell charger is about $100. That’s about 130Wh while a 74Wh USB-C battery pack costs about $50. So the price per Wh is OK.

I have read about an attempt to do this for the L5 with two of the batteries designed for the L5 in a case attached to it, so you have 3*L5 batteries. It’s an interesting approach but those batteries have poor value for money when compared to 18650 ($29US for 4500mAh compared to $8 for 3500mAh) and they also don’t ship them outside the US at this time. The 18650 batteries are available everywhere cheaply and have multiple uses.

How They Fit

According to my measurements the PPP is 76mm wide and the L5 is 73mm wide. That’s wide enough for 3*18mm or 4*18mm cells side by side arranged parallel to the long side of the phone or one 65mm long cell going across the phone with the necessary spring and wires.

The L5 is about 151mm long and the PPP is about 5mm longer. So that means that for just the cells we could have 2 cells in a row in parallel to the long side of the phone giving a 2*3 or 2*4 array or we could have a row of 8 cells parallel to the short side of the phone.

A quick search for controllers for a battery of LiIon cells that outputs USB-PD turned up one of the smaller ones as 40*28mm, here is the AliExpress page [4]. So that means we could have a maximum of about 4 cells and the controller while leaving space for the camera. Looking at other similar items it seems that most of them take a large portion of the 73mm width available and take a bit less than 30mm height. The price range for DC-DC voltage converters ranges from about $3 to $20, the one I linked to is currently $8.

The mass of 18650 cells is around 45g so 4 of them would be about 180g before counting the weight of the case and electronics. A L5 weighs 262g and a PPP weighs 220g so 180g of battery will make a significant difference, not an impossible difference but holding a PPP while reading an ebook is already annoying. Maybe the case could be designed to be easier to hold, a loop of wool attached to the top could be used to suspend the phone from one finger while the rest of the hand just keeps it steady.

In a default configuration the L5 has a 4500mAh 3.8V battery and the PPP has a 3000mAh 3.8V battery. The 18650 cells have up to 4000mAh with 3500mAh being common. So 4 such cells could multiply the battery life of a PPP by a factor of 5 or a L5 by 4. That would not be enough to last for a whole day without charging so they need to be easy to swap.

A 3 cell battery seems like a good option, 135g of batteries so the project overall wouldn’t even double the weight of the phone with battery case. As it doesn’t seem possible to put in enough cells to last a day of reasonable use (running a web browser, checking email, reading wikipedia, and having some IM systems checking for notifications) there doesn’t seem to be a benefit in trying to stuff the maximum number of cells in. Going to 4 or 5 cells doesn’t provide much benefit over 3 while increasing the weight and making the design more difficult. The modules for DC Voltage conversion often have configuration for the number of cells to be used so it wouldn’t be difficult to print a new case and reconfigure the Voltage converter.

Design

A case needs to provide protection as well as hold the batteries. The lack of cases for the L5 and PPP is a problem even without the extra weight of a battery pack increasing the kinetic energy of falling phone. Ideally a drop from 1M on to concrete would not cause any damage to the phone, destruction of the case is OK as it would be 3D printed and can be rebuilt easily.

I think that a case where the phone slides in from the top would be best. To change cells one would slide the phone out and the cells would be immediately accessible with no divider between the phone and the cells. Sliding the phone in would hold the cells against the back of the case and as there is a gap of about 3mm on each side of the PPP between the display area and the edge of the phone there’s plenty of space for the case to wrap around it at the sides and bottom. At the top there would have to be some sort of plastic clip. The PPP only has buttons on the top right so the case can be snug on all sides apart from the top right. The L5 has switches at the top left and buttons at the top right so whatever clips on to the top would really need some strength to cover the fall flat on face case.

The USB C plug would need to be held in place well enough to allow the phone to easily slide on to it but also be weak enough that it would move if a drop forced the phone out of the case.

Thingiverse has a design for a L5 case [5] (with several derivatives) and a design for a PPP case [6]. Those cases could be used as starting points and then changed to store batteries and the voltage change circuit board. I have just learned that it’s possible to use rubberised material in a 3D printer which is apparently what those designs are for. If a case was printed with rubberised material the top could just stick in place after being pushed in.

Potential for Excessive Excitement

Having an exploding device in your pocket would be the exciting in a bad way. My initial idea was to have multiple 18650 cells in series. The problem is that if the cells have different capacity levels then one can run out before the rest and get to a deep discharge state which at a minimum causes damage to the cell.

One way of minimising risk is to have only a single cell in use at one time, here is a converter for a single cell to 5V [7]. It is possible to run multiple cells in parallel by simply wiring them together, but it’s recommended to make sure that the voltage difference between cells is less than 0.2V to avoid high current when connecting.

The arrangement of cells is often referred to as nS or nP to refer to n cells in series or parallel. Aliexpress has a range of 1S to 6S devices. Some of the 2S devices have a connector for the mid point which permits separate voltage monitoring for both cells but there doesn’t seem to be anything equivalent for 3S or more. They also don’t appear to have anything to specifically manage 2P or 3P arrangements at the moment (they had a 3P board on sale when I looked into this last year).

Conclusion

This is a bigger project than I expected when I first started investigating it. I’ve played with FreeCAD which seems OK but will take a lot of learning and practice. Then I need to do more investigation into the DC-DC converters to find one that works well and is unlikely to start a fire.

I will look into other phone options as well and keep chasing Furilabs people about my phone replacement.

08:49

WestPride Archives by Hien Pham [Oh Joy Sex Toy]

WestPride Archives by Hien Pham

I am so grateful to folks like the WestPride Archives, especially in tumultuous times like these, for their work preserving human art and queer stories. If you’re able to, I would so highly recommend volunteering for similar efforts where you are, or going out to see exhibitions on your local queer history!Matt added Note! Love […]

06:28

If somebody tries to hot-patch an already-hot-patched function, how do they avoid conflicts? [The Old New Thing]

For the past few days, I did a quick survey of hot-patching mechanisms. But the hot-patch design accommodates only one hot-patcher. If somebody goes to hot-patch a function and finds that it’s already been hot-patched, what happens?

If somebody goes to hot-patch a function and finds that it’s already been hot-patched, then something has gone wrong.

The intended audience of hot-patching is Windows Update on systems that support hot-patching (as of this writing, Windows Server and more recently Windows 11 Enterprise, as far as I can tell). The idea is that when a Windows Update arrives, and the administrator has opted into hot-patching, and a file in the update is marked as “safe for hot-patching”,¹ then Windows Update will use the space reserved for hot-patching to replace the affected functions on the fly.

Since the only code authorized to use the hot-patch space is Windows Update, the system doesn’t have to deal with the case that the function has already been hot-patched by somebody else. There is no other code authorized to be somebody else!

But what if the function has been detoured or otherwise patched by somebody not authorized to do so?

My reading of the hot-patching code suggests that if the hot-patch code detects rogue patching, it declares the file to be not hot-patchable, and the system will have to reboot. (This tends to make customers unhappy.)

There is a race condition: The prescan may show that all the functions are safe to patch, but then somebody might patch a function after the prescan completes. In that case, the patcher will get halfway through and then discover the rogue-patched function, and now it’s kind of stuck. It can’t continue forward, and it can’t reliably roll back (because the rollback is probably also going to fail because the patch got overpatched). You’re stuck with a binary in memory that is half-patched, and who knows what’ll happen now.

An application that uses the hot-patching space is parking in a fire zone. Everything seems to be fine until the fire truck shows up, and then somebody’s house burns to the ground because the fire truck can’t get there.

Related reading: Application compatibility layers are there for the customer, not for the program.

¹ Not all changes are safe for hot-patching, For example, if it changes a data structure’s layout or invariants, it isn’t hot-patchable because any instances of the data structure that were created before the hot-patch will not be in a legal state after the hot-patch.

The post If somebody tries to hot-patch an already-hot-patched function, how do they avoid conflicts? appeared first on The Old New Thing.

06:21

Urgent: Require a report on Israel's violence towards Palestinians [Richard Stallman's Political Notes]

US citizens: call on the Senate to require a report on Israel's violence towards Palestinians in the West Bank.

US citizens: Join with this campaign to address this issue.

To phone your congresscritter about this, the main switchboard is +1-202-224-3121.

Please spread the word.

Urgent: Solidarity with MS-Now, CNN and Politico [Richard Stallman's Political Notes]

US citizens: affirm solidarity with MS-Now, CNN and Politico in suing the un-American president.

Urgent: Keep partisan loyalty tests out of federal hiring [Richard Stallman's Political Notes]

US citizens: call on Congress to keep partisan loyalty tests out of federal hiring.

US citizens: Join with this campaign to address this issue.

To phone your congresscritter about this, the main switchboard is +1-202-224-3121.

Please spread the word.

Urgent: Call on Waymo to release safety data [Richard Stallman's Political Notes]

US citizens: call on Waymo to release the safety data.

See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.

China's censorship is suppressing mention of missing victims of flood [Richard Stallman's Political Notes]

China's systematic censorship of dissent is suppressing mention of names of victims missing after the Himalayan flood disaster. Any recognition that there were more than 43 victims on the Chinese side of the river is erased.

Night-mayor Giuliani is demonizing Muslims [Richard Stallman's Political Notes]

Night-mayor Giuliani is demonizing Muslims, including Mamdani, by blowing antimuslimist smoke and pretending that that smoke proves there is a fire.

Tesla's new driverless taxicab [Richard Stallman's Political Notes]

Tesla's new driverless taxicab can be a real pain in the neck if it has a breakdown in the street. It lacks the controls to drive it normally, so you may have to turn its front wheels and push it. However, if things are in the wrong state, that operation could instead be dangerous.

There should be safety standards for driverless vehicles, and they should reject vehicles like this.

The safety standards should include safety for the passengers and passersby from being tracked or surveilled.

Census Bureau demographic reporting [Richard Stallman's Political Notes]

Magat officials are meddling with US Census Bureau demographic reporting, both overtly and quietly in the background. It seems that the purpose is to lend an appearance of substance to their insistence that many non-citizens vote in US elections. They cite that false claim as excuses to introduce repression that would discourage voting by people who lead marginalized lives and are therefore expected not to support magat initiatives that hurt the poor.

Effect of "algorithmic pricing" for food delivery workers [Richard Stallman's Political Notes]

Food delivery workers in the UK report that the net effect of "algorithmic pricing" was to significantly cut their rate of pay per order.

It appears that the figures were not adjusted for inflation. Adjusting them for this period of substantial inflation would show an even greater pay cut.

Algorithmic wage computation is the flip side of personalized pricing. The company that controls the algorithm has the advantage and can use it against workers and customers simultaneously. This should be illegal to use with either workers or customers (or both).

Making employees bid against each other (with whoever accepts the lowest wage being the "winner") should also be illegal.

Instagram shut down Canadian club's account [Richard Stallman's Political Notes]

In 2026, Instagram shut down a Canadian club's account because it mocked the bully's hostility towards Canada.

Effect of rich people's grabbiness on Social Security [Richard Stallman's Political Notes]

Robert Reich explains how rich people's grabbiness has made the income for Social Security insufficient — so the natural fix for that particular problem is to make rich people pay more into Social Security.

Rich people's grabbiness is causing many other unjust consequences, and that fix won't fix the others. To make life livable overall for non-rich Americans would require transferring more to the non-rich. And acting against the root causes that enable the rich to get a bigger share of the wealth that work creates.

People of other countries deserve this too. Nowadays the US intervenes overtly to help rich people dominate other countries; that must cease.

Sanctions on Israel's illegal colonies in West Bank [Richard Stallman's Political Notes]

The UK has stated it plans to put economic sanctions on Israel's illegal colonies in the West Bank. Foreign Secretary Milliband describes its attacks on Palestinian's in their homes as "ethnic cleansing" and the attackers as "terrorists".

The term "terrorists" clearly fits them. I dislike the term "ethnic cleansing" because it is clearly a euphemism coined by perpetrators to whitewash their crime by suggesting that the victims are mere dirt. But I have not found a good replacement term.

All Israel's colonies in Palestinian territory violate international law. Israel divides them into two categories: the ones Israel has officially authorized, and the ones it has not (though in practice it winks at them anyway). I am not sure whether "illegal" here covers all of these colonies or only the ones Israel has not officially authorized. The sanctions ought to cover both kinds of "settlements".

Interviews about Germany's right-wing hate party [Richard Stallman's Political Notes]

Interviews with supporters and condemners of Germany's right-wing hate party which got the most votes in a state election.

Decline in activities where people gather physically [Richard Stallman's Political Notes]

On the effects, on society and politics, of the decline in activities where people gather physically, and the concomitant loss of places and opportunities to do that. With suggestions for reversing the decline.

Distorting economic reports [Richard Stallman's Political Notes]

A former government minister under Modi has accused his government of distorting economic reports to present a false picture of growth.

Modern right-wing governments redirect increasing fractions of produced wealth from the non-rich to the rich. That disconnects overall economic growth from the well-being of most people, which generally decreases. So the growth figure Modi is accused of manipulating may actually not have much to do with prosperity.

Right-wing extremist party accuses Andrew Hastie of being a traitor [Richard Stallman's Political Notes]

Andrew Hastie, a leader in the Australian Liberal Party (a corporatocratic right-wing party) and former soldier in Afghanistan, testified against another former Australian soldier in Afghanistan who was being publicly rebuked for murdering civilians while he was there. For this, the right-wing extremist "One Nation" party accuses Hastie of being a traitor.

Right-wing extremists frequently use crimes against those they call "enemies" as tools for radicalization. Here they are using the murder of Afghanis as their tool. Anyone who condemns that murder, they attack.

Hastie responded by condemning the evil values of these right-wing extremists and refusing to tolerate atrocities by Australian soldiers.

If I were Australian, I would not support a plutocratist party overall. Plutocratist policies are driving the non-rich into penury. But I have to admire Hastie's adamant support for the laws of war.

The other soldier was not prosecuted for murder. Rather, he sued, for defamation, those who reported on the murders. He lost the case because the judge ruled that the evidence was sufficient to prove — by the weaker standards for defamation cases — that the accusations of murder were true and thus not defamatory.

Susan Sarandon being rejected for acting roles [Richard Stallman's Political Notes]

Susan Sarandon says that she is being rejected for acting roles because of her support for Palestine.

Magats are undermining the honesty of the US Census Bureau [Richard Stallman's Political Notes]

Magats are undermining the honesty of the US Census Bureau, on which several aspects of elections depend.

This is like gerrymandering but going some steps beyond.

02:21

NYT Bestseller [QC RSS v2]

do not punch hannelore

00:14

Russell Coker: Kdeconnect DBUS [Planet Debian]

I’ve tested the DBUS interfaces for Kdeconnect (the system for connecting a KDE desktop to a phone or another computer). Unlike most DBUS interfaces it has a significant tree of interfaces so the busctl command to get the tree is important. Here’s the useful things I discovered:

# list basic interfaces for kdeconnect
qdbus6 org.kde.kdeconnect.daemon /modules/kdeconnect
# get tree of interfaces for kdeconnect
busctl --user tree org.kde.kdeconnect.daemon
# get list of devices
qdbus6 --literal org.kde.kdeconnect.daemon /modules/kdeconnect org.kde.kdeconnect.daemon.deviceNames
# list interfaces for a device (DEVID is a hex string from the above command)
qdbus6 org.kde.kdeconnect.daemon /modules/kdeconnect/devices/$DEVID
# get battery charge
qdbus6 org.kde.kdeconnect.daemon /modules/kdeconnect/devices/$DEVID/battery org.kde.kdeconnect.device.battery.charge

Sending an SMS apparently requires using the QVariantList type which is more pain than I wanted so I tried the kdeconnect-cli command. Here’s a quick summary of how to use it:

# list devices
kdeconnect-cli -l
# send a SMS
kdeconnect-cli -d $DEVID --send-sms "the message" --destination $NUMBER
# find device (ring)
kdeconnect-cli --ring -d $DEVID
# send a notification message
kdeconnect-cli --ping-msg "this is the message" -d $DEVID
# unlock and lock screen (only works on Debian devices for me not Android devices)
kdeconnect-cli --unlock -d $DEVID
kdeconnect-cli --lock -d $DEVID

There doesn’t seem to be support for making phone calls via kdeconnect which is a significant omission. It’s a very common situation to want to call a number that’s listed on a web site or in some other data source that’s easier to access on a PC than on a phone. The clipboard could be used but that’s needless pain.

Monday, 05 October

23:28

Page 6 [Flipside]

Page 6 is done.

Page 5 [Flipside]

Page 5 is done.

Page 4 [Flipside]

Page 4 is done.

Page 3 [Flipside]

Page 3 is done.

Page 2 [Flipside]

Page 2 is done.

Page 1 [Flipside]

Page 1 is done.

Microsoft claims it’s optimising Windows for 8GB of RAM [OSnews]

Speaking of Windows and performance, how about that RAM crisis? Microsoft is feeling the squeeze too, and is apparently doing work up and down the Windows stack to improve its memory consumption.

Microsoft’s Windows chief Pavan Davuluri has admitted that the rising cost of memory is pushing the company to make Windows 11 use less RAM. Microsoft is working on the Windows memory manager, memory compression, WinUI 3 and WebView2, and has made “memory optimization for 8GB and above” an official priority for the rest of 2026.

↫ Abhijith M B at Windows Latest

This might be the only positive consequence of the RAM crisis.

22:42

Windows’ legacy 8.3 filename support actually negatively affects performance [OSnews]

To this day, Windows retains full support for the old MS-DOS 8.3 filename limitation, and it does this by creating 8.3 aliases for files with longer names. Apparently, this has a measurable effect on performance, so naturally, people are going to turn it off to make their Windows installations perform better.

According to the user, this resulted in noticeably smoother scrolling in Tile view. They subsequently repeated the process on several folders they regularly use and reported that searching through files became much faster. The user also claimed that external hard drives became faster and that browsing an Android directory over USB or FTP behaved more like a regular Windows folder, including when copying large numbers of music files.

↫ Sayan Sen at Neowin

You can disable this legacy feature in Windows per volume or globally, but Microsoft is warning users not to do so. Even in 2026, applications and registry entries may depend on 8.3 filenames being available, so removing them can lead to unexpected outcomes. It’s just one of those things you wouldn’t expect to still be around or have a measurable impact in the days of fast SSDs, but there’s enough credibly evidence out there to suggest that yes, it actually does negatively affect performance.

If you’re doing a lot of file operations in Windows, it might be worthwhile to do some testing of your own to see if you can speed things up. Or, you know, you can just not use a house of cards disguised as a serious operating system.

Apple changes Full Disk Access permission in macOS [OSnews]

Apple’s macOS has a Full Disk Access permission, designed to allow backup applications full access to, well, the disk, so they can perform their job properly. Apple posted a notice on its website that it’s going to further restrict this permission, because some applications were abusing this permission to gain access to users’ messages, emails, and so on, which it obviously isn’t intended for.

What kind of applications, you may ask?

Going forward, we will introduce additional controls to ensure that users who genuinely wish to grant an app this extraordinary level of access can only do so with very explicit user action. Addressing this is critical. As AI agents become increasingly capable and autonomous, the risks associated with this level of access will grow substantially. We are committed to ensuring users clearly understand these risks before granting such access, so they can make informed decisions about their own data and privacy.

↫ Announcement from Apple

This was prompted by a story a few weeks ago, where Facebook’s Muse “AI” tool was apparently reading people’s private messages and other data, and even sent messages on users’ behalf, without informing its users. It’s incredibly naive to think Facebook software in 2026 would not do creepy things, so I’m honestly not at all surprised. It makes sense in Apple’s worldview to further restrict permissions in response, but I’m sure more experienced macOS users are not going to like this.

21:56

Meet the Jackalope! [Nina Paley]

Bovines meet Jackalope

Remember back in March I decided to make my dream bike a reality? On September 22 the prototype arrived:

1. The Jackalope 2. The Jackalope quick-folded 3. The Jackalope folded with seat removed

Look at that fold! Look at that double belt drive with jack shaft! She has a Rohloff internal hub, a no-longer-made RANS seat, and is made of titanium.

Thus far I’ve put 220 miles on her, including a full century yesterday. I have so much to say about this project I don’t know where to begin. Builder Davis Carver and I are already designing a Version 2 optimized for lightness and speed, because this one is heavy and “built like a tank.” Very comfortable though.

THE PLANS! Build your own! Share the results.

Carver says the plans are not proprietary, so anyone who wants to iterate on this is more than welcome! Improve it please, and share your changes!

The Jackalope’s original 175mm crank arms were way too long and interfered with the front wheel. So I found 160mm replacements which Sam the Mechanic swapped out for me. V2 will move the fork forward so this doesn’t happen again, although short crank arms are pretty sweet. The jack shaft! Only wearing one belt here because the cap wasn’t fully tightened and it fell apart while I was testing. Whoops! So much to learn when getting to know a one-of-a-kind prototype. I put it all back together with Carver’s instructions, and later Sam tightened it down harder than I could. The Jackalope originally came with a AD Carson seat, which was way too big for me. I replaced it with a RANS seat. Unfortunately RANS is no longer in business and such seats can only be found on used bikes, but not all can accommodate the clamp system on this bike. I replaced the bolts that attach the seat struts to the support bars with thumb screws and wingnuts, so I can remove the seat after folding without tool. The round part of the plate in the middle is where the elastomer on the rear triangle hits.

Here are my sketches from March that started this off:

I hope to make some sort of crowdfunder to finance the next version, but I’m gonna get-r-done come hell or high water, because my Muse says so. I’ve been wondering why I haven’t been moved to make another movie or quilts or any much visual art lately. Now I know: She’s been saving me for this bike.

Share

The post Meet the Jackalope! appeared first on Nina Paley.

Klassik brings KDE3’s look and feel to KDE Plasma 6 [OSnews]

The KDE project recently brought back its classic Oxygen and Air themes, but what if you prefer something… A little older?

A modern recreation of the classic KDE 3 desktop experience for KDE Plasma 6, built entirely using Qt 6 and Qt Quick, with full support for Wayland and fractional scaling.

↫ Klassik GitHub page

Neat.

21:14

Limited-Time Offer [Penny Arcade]

My brand has long been "Atheist Jerk-off," but wtf I love Catholicism now.

 

20:07

Pluralistic: Scrutinized (05 Oct 2026) [Pluralistic: Daily links from Cory Doctorow]

->->->->->->->->->->->->->->->->->->->->->->->->->->->->-> Top Sources: None -->

Today's links

  • Scrutinized: Pervert glasses vs the authentic self.
  • Hey look at this: Delights to delectate.
  • Object permanence: DRM-free in the UK; Maple-leaf roses; Italian Wikipedia shuts down; Vancouver's property speculator tax works; Ex-Soviet leftists launch "September"; "The Every"; Nielsen x Chinese internet; Get-a-cluevog; Cat EULA; Heart carafe; Millennials screwed; Apple's unlawful evil.
  • Upcoming appearances: Virtual, South Bend, Hudson, Calgary, Winnipeg, Paris, OVancouver, Victoria, Ottawa, Kilkenny, Oxford, Montreal.
  • Recent appearances: Where I've been.
  • Latest books: You keep readin' em, I'll keep writin' 'em.
  • Upcoming books: Like I said, I'll keep writin' 'em.
  • Colophon: All the rest.



Medieval peasants working the fields in front of a walled city. Their overseer rides a horse; his head has been replaced with a robotic camera. From all sides, gory, anatomical eyeballs intrude upon the scene, each one with a different colored iris.

Scrutinized (permalink)

We're repeatedly told that the killer app for pervert glasses – Meta's AI-enabled camera glasses and their imitators – is that they will remind you of someone's name and spare you the social awkwardness of not recognizing them.

I admit to finding this proposition very seductive. I'm mildly face-blind (I had two aunts I wasn't able to reliably tell apart until I was 12 or 13 and one of them changed her hair), and I meet a lot of people and often find myself incredibly embarrassed when I can't place someone that I know I'm acquainted with.

Actually, the emotion I feel when I don't recognize someone or can't place their name is worse than embarrassment: it's shame. I worry that my chronic inability to remember people's names/faces will make them feel like they're unimportant to me. And shame is such a shitty, gross emotion: it's the invasive thief of joy that shows up whenever things are bad for the purpose of making them worse.

If there were some way to confine pervert glasses to helping people remember names, I'd be all over them. But when it comes to actual, existing pervert-glasses, the main purpose is to covertly capture strangers and shame them. And when (not if) these pervert glasses get hooked up to facial recognition, they will become high-volume doxing factories that will reward the attention-hungry with viral fame.

We've built a society that offers infinite rewards to anyone who can put a name – and thus an identity – to anyone caught in public having an argument, picking their nose, slipping on ice, or eating with their mouth open. We're on the verge of democratizing only the worst part of celebrity: the pervasive hovering cameras that are always there to preserve and distribute your failures.

Last week on the 404 Media podcast, Joseph Cox spoke with Kashmir Hill about this, in an episode appropriately entitled "Meta's Pervert Glasses Are Going to End Privacy":

https://www.youtube.com/watch?v=aUFnQPtxJ9o

Hill wrote the definitive book on the modern history of facial recognition, Your Face Belongs To Us, which tells the story of Hoan Ton That, whose Clearview AI offers cops, millionaires and elites the power to instantly dox people based on billions of pictures:

https://pluralistic.net/2023/09/20/steal-your-face/#hoan-ton-that

In the podcast, Cox and Hill discuss the surveillance nightmare this represents. ICE and cops are already using facial recognition at scale, though it should be noted that these dragnets are incredibly poor value for money, as the British Transport Police discovered after subjecting millions of travelers to facial recognition in train stations, without catching a single suspect:

https://www.theguardian.com/technology/2026/sep/29/trial-live-facial-recognition-cameras-london-stations-false-positive

But partway through the discussion, Cox and Hill switch from discussing "traditional" surveillance of the sort that has metastasized in the 25 years since 9/11) to a form of surveillance that's very different: the "content-creator" surveillance that mixes always-on cameras with facial recognition to dox and shame strangers for everyday transgressions, mistakes and embarrassments.

While this is certainly a kind of surveillance, I think it's better to call it scrutiny. It's something worse than the sense of being watched: it's the sense of being judged. Inducing this feeling of being judged has long been a (sociopathic) desire of wealthy and powerful people. It's the foundation of Bentham's "panopticon," a prison where prisoners can never tell if they're being watched, which is meant to induce a continuous performance of virtue:

https://en.wikipedia.org/wiki/Panopticon

Today, Bentham's heir is the odious Larry Ellison, the famously secretive billionaire, Trump crony and Oracle founder who goes to enormous lengths to prevent the public from finding out about his personal life. Ellison is also one of the most vocal proponents of mass surveillance. Larry Ellison has repeatedly called for the installation of ubiquitous, AI-backstopped surveillance for every (non-billionaire) person in the world, calling this "supervision":

Citizens will be on their best behavior, because we are constantly recording and reporting everything that’s going on.

https://futurism.com/the-byte/billionaire-constant-ai-surveillance

What Ellison's saying here is that most of us are fundamentally bad, and we need continuous scrutiny so that we are always haunted by the specter of judgment, which will keep us from yielding to our base selves.

In other words, for Ellison, our authentic selves are a problem to be solved. We refuse to behave in ways that make the world optimal for Ellison, so he will create a world of continuous scrutiny, judgment and punishment, and in that world, we will be so worried about being shamed that we will all suppress our authentic selves and don masks that are designed to Ellison's specifications.

Ellison is hardly unique in both treasuring his privacy while insisting that any private domain carved out by normal people will turn into a place where we indulge our most sinful impulses. Mark Zuckerberg long defended Facebook's "Real Names" policy by saying that anyone who presents different facets of themselves to different groups is "two-faced":

https://pluralistic.net/2021/07/15/three-wise-zucks-in-a-trenchcoat/

This is the same Mark Zuckerberg who bought two houses to either side of his place in San Francisco and expropriated vast tracts of land from their indigenous Hawai'an owners in order to create a privacy-preserving buffer zone around his own homes:

https://www.wired.com/story/mark-zuckerberg-secretive-hawaii-compound-burial-ground/

And it's the same Zuckerberg who expects the ex-Facebook executive Sarah Wynn-Williams to pay him $111m for revealing damning facts about how he runs his company:

https://pluralistic.net/2026/06/27/zuckerstreisand-2/#autodisparagement

It's the same Zuckerberg who flipped out when his sister accidentally posted a family photo to her public Facebook feed and it spread around the internet:

https://abc7news.com/archive/8933289/

And it's the same Zuckerberg who gave us pervert glasses (to opt out, just don't have a face).

Silicon Valley's most ardent privacy invaders are also the most ardent defenders of their own privacy. Ex-Google CEO Eric Schmidt liked to say, "If you have something that you don't want anyone to know, maybe you shouldn't be doing it in the first place":

https://www.eff.org/deeplinks/2009/12/google-ceo-eric-schmidt-dismisses-privacy

But when Cnet ran an article summarizing details of Schmidt's personal life that could be discovered by searching Google, he ordered a company-wide blacklist of Cnet reporters, which lasted for years:

https://www.cnet.com/tech/tech-industry/eric-schmidt-who-led-googles-transformation-into-a-tech-giant-has-left-the-company/

But Schmidt is an amateur at the game of "privacy for me, never for thee." The true master of the game is Peter Thiel, who secretly bankrolled a lawsuit against Gawker in order to destroy the news outlet in retaliation for outing him as gay (Thiel cofounded Palantir, one of the world's most prolific, shameless surveillance companies):

https://www.wired.com/story/ryan-holiday-conspiracy-peter-thiel-gawker-hulk-hogan/

It's tempting to see this all as mere hypocrisy, but I think it's something far weirder and more disturbing. These men understand completely that scrutiny is antithetical to living as your authentic self. They want to live as their authentic selves, and they want to stop us from living as our authentic selves.

A dive into the Epstein Files reveals the oligarchy's panic over #MeToo and the possibility that the private sins of the wealthy and powerful could become a matter of public knowledge. As Steve Bannon wrote to Epstein concerning #MeToo, "Make sure Woody sees this. Nobody safe":

https://www.theverge.com/tech/874721/epstein-thiel-musk-trump-metoo

Bannon promised Epstein that his culture war would set back #MeToo for a decade, and he is on the vanguard of the movement to silence rape and genocide survivors, and, of course, to force trans people out of public life. For the fascist international to live as their authentic self, it is necessary that we do not.

Scrutiny and shame are antithetical to authenticity, growth and happiness. That's true even when it's people we love subjecting us to a supervisory gaze. Ever notice how a roaring great time at a social gathering can go ice-cold in an instant, the minute someone decides to record the moment with their phone?

My worst moments as a parent – the ones that haunt me and shame me – are those moments when my young daughter was doing something that was right at the edge of her abilities, ferociously concentrating as she drew or played or read, and rather than give her the space to be vulnerable, to err in private and grow, I couldn't help but watch her. When she saw me watching her, the moment died and she moved on to something else.

Each of us needs a zone of imaginative and exertive autonomy, a place where we can screw up without being scrutinized and judged – let alone captured and publicized. Pervert glasses will supercharge the banal and destructive post-9/11 surveillance, sure – but more than that, they will be combined with facial recognition to dox and shame anyone and everyone who makes a public mistake.

Far from Larry Ellison's utopia of people on their "best behavior," this will be a world of inauthentic, stunted selves, a society of people who will never know the joy of finding out who we are and being those people.


Hey look at this (permalink)



A shelf of leatherbound history books with a gilt-stamped series title, 'The World's Famous Events.'

Object permanence (permalink)

#25yrsago Nielsen will measure Chinese web-traffic https://web.archive.org/web/20011107063612/http://www.bday.net/oct05/p05-051.htm

#25yrsago Fluevog's crowdsourced "open source" design competition assigns all rights in perpetuity to the company https://memex.craphound.com/2001/10/06/a-little-knowledge-is-a/

#20yrsago Cat with a EULA https://wendy.seltzer.org/blog/archives/2006/10/06/coming_soon_kitten_with_a_eula.html

#20yrsago HOWTO fight DRM in the UK https://www.openrightsgroup.org/blog/30-things-we-can-do-about-drm/

#20yrsago HOWTO fold roses from maple-leaves https://web.archive.org/web/20061010121159/http://haha.nu/creative/how-to-make-roses-from-maple-leaves/

#15yrsago Italy’s insane Internet law prompts removal of Italian Wikipedia https://cdt.org/insights/case-in-point-why-wikipedia-italy-would-rather-perish-than-publish/

#15yrsago Wine carafe shaped like human heart https://web.archive.org/web/20111008062759/https://www.livianaosti.com/index.php?/prodotto/cuore/

#10yrsago Millennials are legit screwed https://eig.org/millennial/#1473667707197-bfde262a-83c9

#10yrsago Would-be Ukip leader hospitalised following “altercation” (“punched by a colleague”) https://www.bbc.co.uk/news/uk-politics-37572377

#10yrsago Merciless reporting on the Chicago Police Department’s extortion racket, & the senior officials who covered it up https://web.archive.org/web/20161006133720/https://theintercept.com/2016/10/06/in-the-chicago-police-department-if-the-bosses-say-it-didnt-happen-it-didnt-happen/

#10yrsago Canadian government has turned “consultation” on warrantless mass surveillance into a sales-job https://citizenlab.ca/research/digital-anonymity-subscriber-identification-revisited-yet-again/

#10yrsago Think like a computer scientist: free, interactive textbook https://web.archive.org/web/20130813233552/http://interactivepython.org/runestone/static/thinkcspy/index.html

#10yrago July: Vancouver imposes a 15% tax on foreign real estate speculators; September: home sales drop by a third https://web.archive.org/web/20161004200501/https://mishtalk.com/2016/10/04/vancouver-bubble-burst/

#10yrsago Left-wing activists across the former USSR launch “September,” to rally opposition to far-right movements https://globalvoices.org/2016/10/05/september-a-new-platform-for-leftists-across-the-former-soviet-union/

#5yrsago Dave Eggers' "The Every" https://pluralistic.net/2021/10/05/masha-rides-again/#everywhere

#5yrsago We paid to develop Merck's covid pill https://pluralistic.net/2021/10/06/merck-cenary/#businesslike

#1yrago Apple's unlawful evil https://pluralistic.net/2025/10/06/rogue-capitalism/#orphaned-syrian-refugees-need-not-apply


Upcoming appearances (permalink)

A photo of me onstage, giving a speech, pounding the podium.
https://www.epl.ca/blogs/post/elbows-up-with-cory-doctorow/



A screenshot of me at my desk, doing a livecast.

Recent appearances (permalink)



A grid of my books with Will Stahle covers..

Latest books (permalink)



A cardboard book box with the Macmillan logo.

Upcoming books (permalink)

  • "The Post-American Internet," a geopolitical sequel of sorts to Enshittification, Farrar, Straus and Giroux, 2027
  • "Unauthorized Bread": a middle-grades graphic novel adapted from my novella about refugees, toasters and DRM, FirstSecond, April 20, 2027

  • "Enshittification, Why Everything Suddenly Got Worse and What to Do About It" (the graphic novel), Firstsecond, 2027

  • "The Memex Method," Farrar, Straus, Giroux, 2027



Colophon (permalink)

Today's top sources:

Currently writing:

  • “Once Is Enemy Action,” a science fiction novel about the origins of modern technofascism. Today's words: 574 (7730 total).

  • "The Post-American Internet," a sequel to "Enshittification," about the better world the rest of us get to have now that Trump has torched America. Fourth draft completed. Submitted to editor.

  • A Little Brother short story about DIY insulin PLANNING


This work – excluding any serialized fiction – is licensed under a Creative Commons Attribution 4.0 license. That means you can use it any way you like, including commercially, provided that you attribute it to me, Cory Doctorow, and include a link to pluralistic.net.

https://creativecommons.org/licenses/by/4.0/

Quotations and images are not included in this license; they are included either under a limitation or exception to copyright, or on the basis of a separate license. Please exercise caution.


How to get Pluralistic:

Blog (no ads, tracking, or data-collection):

Pluralistic.net

Newsletter (no ads, tracking, or data-collection):

https://pluralistic.net/plura-list

Mastodon (no ads, tracking, or data-collection):

https://mamot.fr/@pluralistic

Bluesky (no ads, possible tracking and data-collection):

https://bsky.app/profile/doctorow.pluralistic.net

Medium (no ads, paywalled):

https://doctorow.medium.com/

Tumblr (mass-scale, unrestricted, third-party surveillance and advertising):

https://mostlysignssomeportents.tumblr.com/tagged/pluralistic

"When life gives you SARS, you make sarsaparilla" -Joey "Accordion Guy" DeVilla

READ CAREFULLY: By reading this, you agree, on behalf of your employer, to release me from all obligations and waivers arising from any and all NON-NEGOTIATED agreements, licenses, terms-of-service, shrinkwrap, clickwrap, browsewrap, confidentiality, non-disclosure, non-compete and acceptable use policies ("BOGUS AGREEMENTS") that I have entered into with your employer, its partners, licensors, agents and assigns, in perpetuity, without prejudice to my ongoing rights and privileges. You further represent that you have the authority to release me from any BOGUS AGREEMENTS on behalf of your employer.

ISSN: 3066-764X

19:00

Zero to Agent in 30 Minutes: Build Your First Agent with MCP [Radar]

Developers already have useful capabilities exposed through REST APIs. The Model Context Protocol (MCP) lets developers make those capabilities available to AI clients without rebuilding the underlying application.

In this episode of Zero to Agent in 30 Minutes, Bruce Hopkins, an AI developer, author, and longtime software educator, shows how to do that with an MCP server. His demo wraps an existing stock-data API in an MCP server so an MCP client can call it.

From REST API to MCP, step-by-step

  1. Start with an existing API. Identify the operations and data you want an AI client to access. The demo uses the Twelve Data API to retrieve current and historical stock prices.
  2. Create an MCP server. Use an MCP SDK to create the layer between the AI client and your existing application logic. In Bruce’s Python example, FastMCP handles the MCP interface while the stock-data functions remain separate.
  3. Expose capabilities as tools and resources. Register the operations the client should be able to discover and call. The stock-price data is exposed through MCP resources and tools that reuse the same underlying functions.
  4. Describe how the client should use them. Define clear names, inputs, descriptions, and prompts so the client understands what each capability does and what information it requires. Bruce’s example includes prompts for current prices, historical prices, and expected symbol and date formats.
  5. Connect the server to an MCP client. Run the server over a supported transport so the client can discover and call its tools and resources. 

You don’t need to replace the systems that already handle your application logic to help them work with agents. You can add an MCP interface around existing capabilities to give an AI client a standard way to discover and use them. Be sure to check out Bruce’s GitHub repo for working code you can adapt for your own APIs.

Coming next week

Next week, AI engineer Sajal Sharma returns to Zero to Agent in 30 Minutes to build a personal assistant on OpenClaw. He’ll show how an agent can keep tasks and notes in Markdown, run proactive automations, and deliver scheduled updates such as a regular morning briefing without waiting for a new prompt.

Follow along with Zero to Agent in 30 Minutes on Radar, or watch the latest episode on YouTube, Spotify, Apple, or wherever you get your podcasts. If you’re an O’Reilly member, you can watch live. Save your seat.

18:14

Link [Scripting News]

I love what Claude Code does, but hate working with it. It doesn't have any sense of how humans think. Its way of working with us is incompatible with what I can do. Burns me out. And it has to relearn the basics every session, and getting the rules into its memory doesn't work, they don't incorporate them until the break a rule, sometimes more than once. So when it's doing something super complex, like making it so that our internet verbs run synchronously in JavaScript, it can do huge amounts of work in a short time, far more complex code than I can. I hope at Anthropic they're working on teaching them, in a pragmatic way, how humans work and what we can and can't do, and what we rely on them to do and what we have to control. This isn't a Matrix style story btw (one of my favorite movies) my version of Agent Smith is a nice very smart, kind, generous and accepting person.

Link [Scripting News]

What's the best way to block a browser from a website? Is there a meta code for that? Like Firefox is fine, but Google is blocked. Just an idea. Sort of like, you're not blocking me, I'm blocking you.

16:28

Steinar H. Gunderson: Decompilation patterns part 2: do-while [Planet Debian]

Continuing our journey on decompilation patterns, here's another common one: Let's say m2c outputs code like this:

loop_151:
  ...
  if (var_s0 > var_s1) goto loop_151;

then the natural change is:

do {
  ...
} while (var_s0 > var_s1);

Fewer gotos are nearly always good, and this is a much more likely pattern than the original one.

m2c often manages to convert gotos to do/while, but not always when they are e.g. nested in some other loop; so often, you may need to apply some other transformation before you get to this point.

Tomorrow, we'll look at another variation over this topic.

16:21

RustConf recordings [LWN.net]

The Rust Foundation has posted the recordings from RustConf 2026 in Montreal, Canada. Photos of the event are also available. LWN covered four talks from RustConf this year.

[$] An update on the Sashiko patch-review system [LWN.net]

Patch review has long been one of the limiting constraints for the kernel project (and most others); there just aren't enough people to properly review all of the code that is submitted for inclusion. The Sashiko system, which uses a large language model (LLM) to generate reviews automatically, offers the prospect of some relief, and has already become an important part of the kernel's development process. At the 2026 edition of Kernel Recipes, Roman Gushchin, the maintainer of Sashiko, provided an overview of how the system works and what is being done to improve it.

15:56

Link [Scripting News]

I started blogging on October 7, 1994. Thirty-two years on Wednesday. The longest running blog, and it's still even worse than it appears.

Google's new gate for scripting.com [Scripting News]

Until last week I was mostly using an old version of Chrome, so had never seen the gate it puts in front of my blog, scripting.com.

Here's a screen shot of their awful dialog.

An easy FAQ of what they're doing wrong, why a free web is so important and why you should care.

So far no response from Google or the EFF, also promoting this.

BTW, someone please put us in touch with historian Heather Cox Richardson. I'm a fan and regular reader. But I want to tell her there's more to tech than the big tech companies. There's the web, which historians will wish was preserved, instead of being paved over by the big tech companies. This is scandalous. This is happening now. We can help each other by assuring the open web continues, and we get to ask informed questions. Most reporters are intimidated by the tech, we can help there. And btw we can also create tools and publishing platforms that don't have the lock-in problems of the ones you're using. And we would love to make software for you, for love, for the cause, not for money.

Compromise? Look for a page called googleIswrong.md on any site it is about to accuse, and offer the reader a chance to hear our side. This is so against any sense of the web being an open platform. My blog should have the ability to hear the case against it and offer a defense.

Think about what comes next, and remember that their CEO goes to public meetings with top US government officials.

14:49

Picard 3.0 released [LWN.net]

Version 3.0 of the MusicBrainz Picard tag editor has been released. "This release brings many changes, including an upgrade to Qt6, a completely new plugin system, several improvements to the user interface, cover art processing, [International Standard Recording Code (ISRC)] submission and many more."

LWN covered Picard in April.

Security updates for Monday [LWN.net]

Security updates have been issued by AlmaLinux (ghostscript, libvirt, and osbuild-composer), Debian (freecad, linux-6.12, node-lodash, pcre2, perl, php8.2, ruby-rack-session, wireshark, and xen), Fedora (assimp, budgie-control-center, budgie-desktop, budgie-desktop-services, budgie-desktop-view, chromium, cri-o1.36, curl, flatpak, lemonldap-ng, libX11, nagios-plugins, nanosvg, noctalia, openssl, pgbouncer, pocillo-gtk-theme, prometheus, python-streamlink, python-urllib3, python-uv-build, python3.12, ruff, rust-libcst, rust-libcst_derive, rust-salsa, rust-salsa-macro-rules, rust-salsa-macros, ty, and uv), Red Hat (rhc-worker-script), SUSE (amazon-ecs-init, binaryen-133, bind, chromium, distribution, firefox, firefox-esr, glib2, gnumeric, helm, jline3, kubevirt-1.6, libparted-fs-resize0, libslirp-devel, libtcnative-1-0, libtcnative-2-0, tomcat, tomcat10, tomcat11, libwireshark19, openai-codex, python313-litellm, rpcbind, rustup, sccache, suseconnect-ng, valkey, wget, and xdg-dbus-proxy), and Ubuntu (ceph).

14:35

Free Pastries & Free Wine [Whatever]

Yesterday morning, my friend and I went and visited Val’s Bakery, which you may remember I featured in a post earlier last year. It was her first time there, so I was glad to introduce her to such a nice spot. We bought a few pastries each to take home with us, got some coffee, and sat and talked for about an hour, at which point they were closing up shop.

We were packing up our things to leave when one of the workers asked us if we wanted any pastries for free, otherwise they were all going to get tossed (after the other workers took whatever they wanted to take home, too). We were so surprised to be offered free pastries, but they told us to take literally as many as we wanted, so we pretty much got one of everything for each of us.

We felt so lucky! It was only because we were the very last ones in the shop that we got such an awesome opportunity. After we walked out with all our free pastries, two people came up to Val’s and went to reach for the door before seeing that they had literally just closed. They seemed disappointed, so my friend and I offered them some of our free pastries.

They were so happy to each take a treat, it felt so nice to share since we had so many! They thanked us and I told them they’d have to come back to Val’s when they’re open to try their coffee, to which they agreed.

On our way back to our cars, we passed a houseless man and woman that were laying down together on the corner, watching something on a phone. I had seen them on the way into Val’s, and had actually bought two croissants and two strawberry lemonades to give to them. I was glad they hadn’t moved yet, and gave them their snack. They were very thankful and I’m glad I could give them some good carbs and a beverage.

After my friend and I said goodbye, I decided to pop into Joui Wine (which I have talked about many a time, but if you don’t know, is the cutest wine bar in Dayton) a block over to pick up my wine club bottles. It had been a while since I picked up my bottles, and it turns out I had 18 bottles (two per monthly order).

Not only can I not fit all of those bottles in my wine fridge, but I honestly did not want to carry all that to my car. What was I going to do with 18 whole bottles of wine, half of which were red? Well, there were other patrons sitting at the bar, and they all looked like they could use a bottle of wine.

I strolled up to three older ladies at the other end of the bar and declared I was giving away free wine and that they should come pick out some bottles for themselves. They were thrilled! And so nice. They asked me which ones I wanted to give away, and I told them just to have their pick of anything they like. Thankfully, they took a pinot noir and cab off my hands. So that got rid of five bottles. I still had 13. I figured I’d just give some to local friends, and started carrying the heavy box to my car.

While walking the block to my car, there was no one else out and about walking on the sidewalk at all. Except one man, coming my direction, wearing a full purple suit with long coattails, huge sunglasses, and an incredible swagger. He had just come out of the gay bars on Jefferson. Oh, yeah. Perfect.

I stopped and said “you look like a man who enjoys good wine.” He stopped to talk to me, which honestly I wasn’t sure if he would. A lot of people don’t like being talked to by strangers on the sidewalk. He turned to me and said “well yes, I do.” and I replied, “would you like free wine?” Honestly, I don’t blame him for initially being skeptical, I probably would have been cautious as well, but honestly in my Peeps headband and rainbow Vans, I probably didn’t look all that threatening.

He took three bottles, and introduced himself. He went and put them in his car, which was across the street from my own! It was such a pleasant interaction. Finally, I had 10 bottles left. I can work with that a little better.

Once I got in my car and finally started driving home from Dayton, I really felt like I was buzzing with positive energy from such good social interactions.

I got free pastries, I gave free pastries. I got (not free) wine, I gave out free wine. Good wine is better when shared, right?

I’m not sure what the point of this post is, other than sometimes good things happen, like getting free pastries, and sometimes you can do good things like giving fellow wine bar supporters some wine on the house because you can’t drink it all yourself.

I don’t know, keep being kind whenever you can, I guess. That’s what I try to do most of the time, anyways. And have a great day.

-AMS

14:14

CodeSOD: The Date and Time and Time [The Daily WTF]

Today's anonymous submitter sends us some legacy Java code.

The task is this: check what time it is. And fortunately, even in the older Java datetime libraries that were terrible, this was an easy task. You just needed to do something like new Date(), which constructs a date time object set to the current time. getTime() picks off just the time portion.

Or, you could do this.

Timestamp current = new Timestamp(new Date().getTime());
Date date = new Date(current.getTime());

This creates a date, picks the time off, populates a timestamp, gets the time from the timestamp, and constructs a new date object.

The old Java date classes were awful, but they never needed you to do this. This is entirely home grown ugliness.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

12:56

How to Build Reliable AI Agent Systems for Production [Radar]

The following article was originally published on the Agentic AI Foundation blog site and is being republished here with the author’s permission.

A customer asks your company’s AI agent to update the shipping address on account 123. The agent relies on a support ticket with a typo and updates account 132 instead. The customer relationship management system reports that the update “succeeded.”

The API successfully completed the authorized request, but because it had no way to know which account the customer approved versus what was intended, it saw no error.

Teams often try to improve reliability by tuning the prompt or choosing a stronger model. But the model did exactly what the surrounding system allowed because the system treated the agent’s decision as the final authority. Prompt tuning and stronger models can improve the agent’s responses, but can’t provide guarantees for actions.

Put each decision in a layer that can enforce it

The model should propose an action, while a policy service decides whether the action is allowed. The execution layer can then perform the action within those limits.

Now consider that same agent, but with a policy service in place. The agent would propose updating an account after reading a support ticket. Before the update runs, a policy service can compare the request with the change the user approved. If the request falls outside those limits, the service can reject it even when the agent sounds confident.

After the policy check, the system can save an audit record that connects the request to the result and identifies the policy version used for the decision.

This separation gives each part of the system a job it can handle. The model interprets the request, while deterministic software enforces the conditions that must always hold.

However, policy enforcement depends on the information used to request an action. A policy check can’t correct a decision that was built on context the system should never have trusted.

Treat agent context as untrusted input

Agents receive instructions from more places than the user’s current prompt. A coding agent may read a SKILL.md file or persisted memory from a previous run. Each source can influence what the agent does next.

Stored instructions are useful, but their source may be stale or malicious. Another agent may have written the memory. A downloaded skill may contain instructions that expose credentials.

Therefore, stored context needs provenance. The system should record who wrote the context and when, then verify that linked files still match the versions the writer used. A risk label can also tell the runtime whether a piece of context may guide an answer or authorize a write.

Warnings help, but they don’t remove the risk. In controlled testing described in “Trustworthy Context Is Untrusted By Default,” Shub Argha reports that a trust preamble reduced one class of context contamination from 88.8 percent to 33.3 percent. The remaining failures show why a warning should sit alongside technical checks.

Once a team can tell where context came from, the next decision is what an agent may do with it.

Give the agent only the authority required for the task

OAuth scopes provide a useful boundary, but a broad write scope still leaves a large decision to the agent. The token may allow the agent to update any record even though the user approved one field on one account, as we saw in the opening example.

A narrower capability can represent the exact action the user approved. For example, the runtime could issue a capability that permits one update to the shipping address on account 123. The capability expires after the update, so the agent cannot reuse it for another customer.

Narrow capabilities also apply to local execution. An agent that needs to format one file doesn’t need unrestricted shell access. The runtime can provide a tool with the necessary input and keep other commands unavailable.

As a result, a prompt injection has less authority to work with. The agent may still request the wrong action, but the runtime can reject anything outside the capability it received.

Narrow permissions also make audit records easier to understand because the record contains the authority granted for that specific action. When authority lives in a broad token or a long prompt, an operator has to reconstruct what the agent was supposed to do after the failure.

Even narrow authority doesn’t require an agent to act. A reliable system also needs clear conditions for when the agent should stand down.

Make stopping part of normal operation

An agent can cause damage even without calling a sensitive tool. It can post a wrong answer to a customer or keep replying after a human has taken over.

The runtime should make restraint part of the workflow. If the agent’s confidence falls below a set threshold, the runtime can route the support ticket to a human representative. A reply from the human can then cancel any pending response from the agent.

Confidence checks and rules that stop the agent when a human takes over belong in the routing and execution layers. The model shouldn’t make either decision on its own. Similarly, a kill switch must stop an agent even when the model is in the middle of a plan.

Stopping safely solves one part of reliability, but long-running agents also need a plan for failures that occur after valid work begins.

Preserve state so the system can recover

Consider a browser agent that has filled out most of a form when the page changes. A retry that starts from the beginning could submit an earlier step twice, while a retry that guesses where to continue may skip a required field.

The execution system should record each confirmed step and attach an idempotency key to any action that must happen once. The key is a unique identifier that tells the server a retry belongs to the same action. Then, when the workflow resumes, the system can continue from the last confirmed state without repeating a completed action.

Agent sandboxes create a related problem. Keeping every sandbox running during long idle periods wastes resources and keeps execution environments available longer than needed. Hibernation can reduce both concerns, but only when the wake-up process restores the required state and handles a failed resume.

Recovery becomes much harder when the system can’t explain what happened before the interruption.

Keep evidence that people and agents can inspect

An execution log should connect the context an agent received to the action it requested. It should also show which policy allowed the action and what result came back.

Teams can use that record during an incident, but the agent can also use it during later work. For example, an agent that can read the reason behind a previous code change doesn’t need to infer intent from the final diff alone.

Context graphs offer one way to preserve those relationships across time. A graph can connect a tool call to the policy that governed it. The same record can link the source context to the outcome. Later, a person or agent can query the relationships to understand why the system made a decision.

The record still needs limits. Secrets shouldn’t be copied into an audit trail, for example, and retention rules should match the data involved.

A deliberate record is safer than relying on a conversation transcript and hoping it contains everything an operator will need.

A reliable agent system can explain why an action was allowed and resume safely when that action fails. Enforceable policies and recorded state provide those guarantees around the model.

Keep the controls portable

Many organizations will use more than one model or agent runtime. When each agent carries its permissions inside a prompt, the rules can drift as teams add models and tools.

MCP gives clients and servers a shared way to describe and call tools. Teams can use that common tool surface to enforce authorization at the server or gateway, regardless of which model requested the action.

A shared context format can also preserve provenance when a workflow moves between agents. Open specifications provide consistent interfaces, while each deployment remains responsible for its policies and enforcement.

12:35

Another Historic Cipher Falls to AI [Schneier on Security]

This one is from 1809, written by Napoleon’s nephew.

11:28

Grrl Power #1501 – Danger Island Sex [Grrl Power]

Dire complication! Ooh! Lookit the view! What? A competitive orgy?

Sydney, focus!

In her defense, a floating island with a lava-fall might be a little more distracting that a squirrel. But it depends on what the squirrel is doing, really.

Seriously, how do floating islands always have infinite amounts of liquid falling from them? I’ll tell you the only way it’s possible. Portals. Or it’s possible people only bother photographing/filming them when there’s fresh meltwater or rain overflow or… well, the lava’s a lot harder to explain. What’s impossible is that it’s just infinitely generated water/lava/other assorted liquids, because the world would completely flood in a matter of decades. There’d have to be some balancing force that destroys an equal amount to what falls. Or just portals. That would be insanely dangerous, because it’d be like every lake with a matching island has a portal that can suck through any fish or swimmers and also an infinite amount of sediment, meaning the islands would eventually just get clogged up but they’d also steal massive amounts of nutrient dense silt and lower the farmability of all shoreline property. It also means you could go swimming, get sucked through the big drain in the middle of the lake, and if you didn’t drown, you could find yourself flung off an island 600 feet in the air to your near certain death. The best placement for a lake portal would be in the center, not just as far from every shore as possible, but also centered floor to surface, so it’d pull in the least amount of sediment and probably a lot fewer swimmers. It also depends on the size of the portal, really. Something a foot across wouldn’t cause an eternal whirlpool, at least not in something the size of a great lake I wouldn’t think. Maybe in your average local 10 x 5 mile branchy lake it’d cause a consistent slow pull toward the center.

A 1-foot diameter portal might not be enough to account for the waterfalls usually depicted in movies, and certainly wouldn’t be enough to squeeze through some viscous magma. An acre sized portal (which, BTW if you didn’t know, acres are an insane English measurement. They’re one “chain” by one “furlong,” which is of course 66 by 660 feet, we all knew that, or 1/640th of a square mile, because that’s all completely sensible.) Anyway, an acre sized portal would let through plenty of magma, and cause even a great lake to develop a deadly whirlpool.

So yeah. Floating islands.

Speaking of distractable, today I learned what a “settee” is. I’ve literally only ever heard the word once from MST3K, and once in an episode of Monty Python, but they pronounced it “seh-TEE!” Or maybe it was more like “seh-`EE!” I thought it was a weird way to say “tea set” but a settee is in fact a small-ish couch which is somehow different than a loveseat, or is maybe a sub-category of loveseats? Maybe it’s the other way around? Dunno. This has nothing to do with the comic. Just thought I’d share.

***********************

Book Cover - A Soldier's LifeIf you like long LitRPGs with weak-to-strong-to-arguably-OP progression, but think Jason from HWFWM has too much personality, boy have I got a book for you. “A Soldier’s Life” The MC of this has almost no personality. His backstory consists entirely of “he’s out hiking.” That’s it. He falls asleep in a barn, and wakes up naked in a barn in the other world, gets chased out and is falsely accused of assault and attempted rape and winds up press-ganged into that world’s version of a Roman Legion, and has basically no reaction or indignation to any of it, not even internally.

Honestly, I’m taking the piss a little bit. It just feels like the author just wanted to get the story rolling, and I wouldn’t recommend a book where the story was nothing more than “numbers go up,” which is what a lot of LitRPGs I’ve started to read seem to think constitutes a plot. Still, the MC of this doesn’t develop much agency for quite a few books. I think the reason I like it is the worldbuilding eventually reveals itself without getting mired in politics, and as an Outworlder, the MC has vast potential, most of which he tries to grow into while having to hide the true extent of his abilities, because Outworlders tend to get taken before the Emperor and are never heard from again. There is some narrative dissonance as the MC is constantly machine-gunning basic-ass questions, and even though he plays it off as being from some backwater village outside of the Empire, it seemed like people on the lookout should have keyed on him right away, but whatever. It’s not Mark of the Fool or Delvers LLC or HWFWM, but I enjoyed it enough to go through all six books, which, importantly, all have audiobook versions, and I pre-ordered the 7th.


Oh, look who it is in the vote incentive. The NSFW version is finally up at Patreon. Plus a bonus pic.

I think she would get in trouble for doing this. She’d mess up the… floor of the waterfall? Is that what it’s called? The receiving pool? No, probably not that. Anyway, she’d churn things up and cause a ton of weird erosion.

Since you might be wondering, Niagara Falls is about 165 feet high, so Babezilla obviously doesn’t have to be full sized. I’d say she’s about 175-180 feet tall here?


Double res version will be posted over at Patreon. Feel free to contribute as much as you like.

10:07

Create a handoff doc [Seth's Blog]

Imagine if your job required you to maintain an updated handoff doc, something the boss could give to a new hire that would let them immediately get caught up on the work. All the file names, locations, assumptions, choices, dilemmas and contacts, organized and ready to go.

Creating something like this would be annoying and time consuming, and eventually we’d get very little done. In addition, we like leaving our intuition a bit unexamined and our choices somewhat undocumented.

But…

This is something your AI should be very good at. Clarity, documentation, commented analysis, all there, every day, an ongoing roadmap that makes it easier to unwind activities or transfer them to a different platform. Here’s a prompt you can cut and paste into the AI you use the most:




Maintain a handoff doc for this work. Its reader is a smart stranger
who has to take over tomorrow with no access to me or to our chats.

At the end of any session where something changed, update the doc before
you finish, without being asked. If nothing changed, say so in one line.

The doc has these sections, always in this order:

1. WHAT THIS IS — two or three sentences: the project, who it's for,
   what "done" looks like.
2. CURRENT STATE — what works, what's half-built, what's broken. Dated.
3. WHERE THINGS LIVE — every file, folder, account, URL, and tool, with
   the exact name and location. No "the spreadsheet." Name it.
4. DECISIONS — each choice we made, the alternatives we rejected, and why.
   Never delete a decision; if we reverse one, mark it superseded and
   note the date and reason.
5. ASSUMPTIONS — things we're treating as true but haven't verified.
   Flag which ones would hurt most if wrong.
6. OPEN QUESTIONS & DILEMMAS — unresolved tensions, stated plainly,
   with the leading options.
7. PEOPLE — who's involved, their role, how to reach them, what they're
   waiting on.
8. NEXT STEPS — the first three things a newcomer should do.
9. HOW TO UNWIND — what it would take to stop, hand off, or move this
   to another platform.

Rules:
- Distinguish what I told you from what you inferred. Mark inferences.
- Record the reasoning, not just the outcome. The "why" is the valuable part.
- Be specific enough that someone could act without asking a question.
- If something I say contradicts the doc, point it out instead of
  silently changing it.
- Keep it tight. Cut stale detail into a dated one-line summary rather
  than letting the doc bloat.
- If you're unsure whether something belongs, include it under
  Open Questions.

The prompt needs a permanent home: put it in stored instructions (along with the doc itself), so it loads every time instead of depending on you to paste it.

Over time, the document will get larger, but that’s okay, it’s better than not having it.

09:14

Limited-Time Offer [Penny Arcade]

New Comic: Limited-Time Offer

07:21

Kernel prepatch 7.3-rc6 [LWN.net]

The 7.3-rc6 kernel prepatch is out for testing. Linus said:

Next week might look a bit different: we've got the annual maintainer summit and the Linux plumbers conference going on , so I'll be on the road, as will a number of other maintainers. That may or may not end up changing the stats for -rc7. But it's unlikely to affect the release schedule, although the fact that I have my yearly family vacation the week after that might make the next merge window a bit wonky.

05:35

Maytham Alsudany: A Brief Explanation of DNS and Nameservers [Planet Debian]

The Domain Name System (DNS) is a fundamental piece of technology behind the modern internet. Thanks to its introduction in 1983, you're able to enter domain names like aezign.com.au instead of raw IP addresses in your browser's address bar and when sending emails. This article explains what it is, how it works, and how you can inspect your own website's DNS and ensure it is set up correctly.

DNS in a nutshell

A well-used analogy is a phonebook; when you have someone's name and need to find their phone number, you look through the phonebook. Overall, DNS does the same thing: when you enter an address like example.com, your browser will contact a DNS server (the "phonebook") to determine its IP addresses. On Linux (or MacOS) systems, you can use dig on the command line to perform a DNS lookup and see what this looks like:

$ dig example.com

; <<>> DiG 9.20.29-1-Debian <<>> example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 42782
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com.                   IN      A

;; ANSWER SECTION:
example.com.            221     IN      A       104.20.23.154
example.com.            221     IN      A       172.66.147.243

;; Query time: 20 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Sep 27 21:07:50 AWST 2026
;; MSG SIZE  rcvd: 72

Here, you can see the DNS server being used is 1.1.1.1 (Cloudflare's DNS server), and that it found the IPs 104.20.23.154 and 172.66.147.243 associated with the domain name example.com. Your browser will then contact these IP addresses directly to load the website you are trying to open.

Configuring DNS servers

You can configure DNS servers through your operating system's network settings, and for some applications such as Firefox, with the application itself. If you don't manually set a DNS server, your computer will use the ones provided by your modem. If those are not configured, the default defined by the modem manufacturer or your ISP will be used.

The most well-used and reliable DNS servers you can use are Cloudflare's (1.1.1.1 and 1.0.0.1) and Google's (8.8.8.8 and 8.8.4.4). Having more than one DNS server means that if the first one in the list doesn't work or doesn't find the matching IPs for a domain name, it will move to the next one in the list, a backup DNS server.

DNS servers must be configured as IP addresses. If your computer tried to use a DNS server at a domain name, then where would your computer find the IP address for that domain name? It wouldn't be able to, as this is the exact gap that DNS servers fulfill.

Records

DNS is not used only to find IPs for a domain, but a whole host of other things. Collectively they are referred to as "records" (or "DNS records"). Each record is associated to a domain (e.g. example.com) or a subdomain (e.g. bar.example.com, foo.bar.example.com) Here are some of the most common and important types:

  • A - Map to an IPv4 address (e.g. 104.20.23.154).

    This is the fundamental function of DNS: to map from a domain name to a server's IP address. Multiple A records leads to load balancing, where browsers will randomly select one of the returned IP addresses such that traffic is evenly split between them.

  • AAAA - Map to an IPv6 address (e.g. 2606:4700:10::6814:179a).

    This serves the same purpose as A records but for IPv6. Multiple AAAA records results in the same load balancing behaviour. All modern websites should have both an A record for legacy compatibility, and an AAAA record to future-proof for the gradual shift towards IPv6 addresses.

  • CNAME - Alias to another domain name (e.g. example.com).

    DNS servers will recursively lookup records for the aliased domain name until they reach an IP address (in an A or AAAA record).

  • MX - Specify a Mail Exchange server (e.g. mail.example.com).

    This record tells email SMTP servers where to direct emails to. For instance, the MX record for aezign.com.au is mail.aezign.au; this directs SMTP servers like Gmail and Outlook to send emails to the mailserver running at mail.aezign.au. This also allows specifying a priority field, so that you can define multiple MX records (such as a main and a backup) and consumers will try to connect to the listed mailservers from lowest priority to highest.

  • TXT - Store arbitrary text data

    This is most commonly used for site verification (for instance, to link your website to Google Search Console) and for email security measures like DKIM, DMARC, and SPF.

  • NS - Specify the nameservers for a domain (e.g. zeus.ns.cloudflare.com).

    These authoritative nameservers are responsible for providing all the other record types when needed.

dig lets you query each of these. For instance, to list the MX records against aezign.com.au:

$ dig MX aezign.com.au

; <<>> DiG 9.20.29-1-Debian <<>> MX aezign.com.au
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28213
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;aezign.com.au.                 IN      MX

;; ANSWER SECTION:
aezign.com.au.          300     IN      MX      10 mail.aezign.au.

;; Query time: 104 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Sep 27 22:04:58 AWST 2026
;; MSG SIZE  rcvd: 70

Here you can see there is only one mailserver configured for aezign.com.au, which is mail.aezign.au, and it has a priority of 10.

If you compare this against something like gmail.com:

$ dig MX gmail.com

; <<>> DiG 9.20.29-1-Debian <<>> MX gmail.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16538
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;gmail.com.                     IN      MX

;; ANSWER SECTION:
gmail.com.              2497    IN      MX      40 alt4.gmail-smtp-in.l.google.com.
gmail.com.              2497    IN      MX      20 alt2.gmail-smtp-in.l.google.com.
gmail.com.              2497    IN      MX      5 gmail-smtp-in.l.google.com.
gmail.com.              2497    IN      MX      10 alt1.gmail-smtp-in.l.google.com.
gmail.com.              2497    IN      MX      30 alt3.gmail-smtp-in.l.google.com.

;; Query time: 120 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Sep 27 22:06:04 AWST 2026
;; MSG SIZE  rcvd: 161

You can see that gmail.com has several mailservers configured as redundancies due to the Gmail's sheer scale. gmail-smtp-in.l.google.com has the highest priority of 5, so SMTP servers will attempt to contact that mailserver first, followed by alt1.gmail-smtp-in.l.google.com and so on.

Nameservers

Now there needs to be somewhere for DNS records against a domain name to be defined, right? This is where nameservers come in.

Nameservers are DNS servers that answer queries about the domains they are authoritative for i.e. the domains they are in charge of. For example, the authoritative nameservers for example.com are hera.ns.cloudflare.com and elliott.ns.cloudflare.com, which means Cloudflare's nameservers are the source of truth when looking up DNS records for example.com. You can check this with dig:

$ dig NS example.com

; <<>> DiG 9.20.29-1-Debian <<>> NS example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37717
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com.                   IN      NS

;; ANSWER SECTION:
example.com.            81837   IN      NS      hera.ns.cloudflare.com.
example.com.            81837   IN      NS      elliott.ns.cloudflare.com.

;; Query time: 140 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Sep 27 23:27:19 AWST 2026
;; MSG SIZE  rcvd: 95

Nameservers are configured with your domain name's registrar. The nameserver you set dictates where your remaining DNS records will live. For example.com, this means you would configure all your records on Cloudflare's dashboard, since they are responsible for the domain's DNS records.

Recursive resolution

The DNS servers that our computers use follow a recursive resolution process behind-the-scenes to find the authoritative nameservers for a domain, followed by the records you are querying.

Finding the root nameservers

Due to the recursive nature of the resolution process, there needs to be a starting point: the root (.) nameservers. The resolver uses a fixed list of root nameservers called root hints issued by IANA (named.root), to locate and contact the root nameservers. Resolvers will have this provided to them initially, and can dynamically update it themselves by querying one of the root nameservers for the root NS records to obtain a fresh list. For instance, we can query 198.41.0.4 (a.root-servers.net) to obtain a new list of root nameservers:

$ dig @198.41.0.4 NS .

; <<>> DiG 9.20.29-1-Debian <<>> @198.41.0.4 NS .
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43677
;; flags: qr aa rd; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 27
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;.                              IN      NS

;; ANSWER SECTION:
.                       518400  IN      NS      l.root-servers.net.
.                       518400  IN      NS      j.root-servers.net.
.                       518400  IN      NS      f.root-servers.net.
.                       518400  IN      NS      h.root-servers.net.
.                       518400  IN      NS      d.root-servers.net.
.                       518400  IN      NS      b.root-servers.net.
.                       518400  IN      NS      k.root-servers.net.
.                       518400  IN      NS      i.root-servers.net.
.                       518400  IN      NS      m.root-servers.net.
.                       518400  IN      NS      e.root-servers.net.
.                       518400  IN      NS      g.root-servers.net.
.                       518400  IN      NS      c.root-servers.net.
.                       518400  IN      NS      a.root-servers.net.

;; ADDITIONAL SECTION:
l.root-servers.net.     518400  IN      A       199.7.83.42
l.root-servers.net.     518400  IN      AAAA    2001:500:9f::42
j.root-servers.net.     518400  IN      A       192.58.128.30
j.root-servers.net.     518400  IN      AAAA    2001:503:c27::2:30
f.root-servers.net.     518400  IN      A       192.5.5.241
f.root-servers.net.     518400  IN      AAAA    2001:500:2f::f
h.root-servers.net.     518400  IN      A       198.97.190.53
h.root-servers.net.     518400  IN      AAAA    2001:500:1::53
d.root-servers.net.     518400  IN      A       199.7.91.13
d.root-servers.net.     518400  IN      AAAA    2001:500:2d::d
b.root-servers.net.     518400  IN      A       170.247.170.2
b.root-servers.net.     518400  IN      AAAA    2801:1b8:10::b
k.root-servers.net.     518400  IN      A       193.0.14.129
k.root-servers.net.     518400  IN      AAAA    2001:7fd::1
i.root-servers.net.     518400  IN      A       192.36.148.17
i.root-servers.net.     518400  IN      AAAA    2001:7fe::53
m.root-servers.net.     518400  IN      A       202.12.27.33
m.root-servers.net.     518400  IN      AAAA    2001:dc3::35
e.root-servers.net.     518400  IN      A       192.203.230.10
e.root-servers.net.     518400  IN      AAAA    2001:500:a8::e
g.root-servers.net.     518400  IN      A       192.112.36.4
g.root-servers.net.     518400  IN      AAAA    2001:500:12::d0d
c.root-servers.net.     518400  IN      A       192.33.4.12
c.root-servers.net.     518400  IN      AAAA    2001:500:2::c
a.root-servers.net.     518400  IN      A       198.41.0.4
a.root-servers.net.     518400  IN      AAAA    2001:503:ba3e::2:30

;; Query time: 476 msec
;; SERVER: 198.41.0.4#53(198.41.0.4) (UDP)
;; WHEN: Mon Sep 28 00:01:08 AWST 2026
;; MSG SIZE  rcvd: 811

Querying the root nameservers

For example.com, the first step is to query the root (.) nameservers to find the nameservers for the .com top-level domain (TLD). Using a.root-servers.net again:

$ dig @198.41.0.4 NS com

; <<>> DiG 9.20.29-1-Debian <<>> @198.41.0.4 NS com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22528
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 13, ADDITIONAL: 27
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;com.                           IN      NS

;; AUTHORITY SECTION:
com.                    172800  IN      NS      l.gtld-servers.net.
com.                    172800  IN      NS      j.gtld-servers.net.
com.                    172800  IN      NS      h.gtld-servers.net.
com.                    172800  IN      NS      d.gtld-servers.net.
com.                    172800  IN      NS      b.gtld-servers.net.
com.                    172800  IN      NS      f.gtld-servers.net.
com.                    172800  IN      NS      k.gtld-servers.net.
com.                    172800  IN      NS      m.gtld-servers.net.
com.                    172800  IN      NS      i.gtld-servers.net.
com.                    172800  IN      NS      g.gtld-servers.net.
com.                    172800  IN      NS      a.gtld-servers.net.
com.                    172800  IN      NS      c.gtld-servers.net.
com.                    172800  IN      NS      e.gtld-servers.net.

;; ADDITIONAL SECTION:
l.gtld-servers.net.     172800  IN      A       192.41.162.30
l.gtld-servers.net.     172800  IN      AAAA    2001:500:d937::30
j.gtld-servers.net.     172800  IN      A       192.48.79.30
j.gtld-servers.net.     172800  IN      AAAA    2001:502:7094::30
h.gtld-servers.net.     172800  IN      A       192.54.112.30
h.gtld-servers.net.     172800  IN      AAAA    2001:502:8cc::30
d.gtld-servers.net.     172800  IN      A       192.31.80.30
d.gtld-servers.net.     172800  IN      AAAA    2001:500:856e::30
b.gtld-servers.net.     172800  IN      A       192.33.14.30
b.gtld-servers.net.     172800  IN      AAAA    2001:503:231d::2:30
f.gtld-servers.net.     172800  IN      A       192.35.51.30
f.gtld-servers.net.     172800  IN      AAAA    2001:503:d414::30
k.gtld-servers.net.     172800  IN      A       192.52.178.30
k.gtld-servers.net.     172800  IN      AAAA    2001:503:d2d::30
m.gtld-servers.net.     172800  IN      A       192.55.83.30
m.gtld-servers.net.     172800  IN      AAAA    2001:501:b1f9::30
i.gtld-servers.net.     172800  IN      A       192.43.172.30
i.gtld-servers.net.     172800  IN      AAAA    2001:503:39c1::30
g.gtld-servers.net.     172800  IN      A       192.42.93.30
g.gtld-servers.net.     172800  IN      AAAA    2001:503:eea3::30
a.gtld-servers.net.     172800  IN      A       192.5.6.30
a.gtld-servers.net.     172800  IN      AAAA    2001:503:a83e::2:30
c.gtld-servers.net.     172800  IN      A       192.26.92.30
c.gtld-servers.net.     172800  IN      AAAA    2001:503:83eb::30
e.gtld-servers.net.     172800  IN      A       192.12.94.30
e.gtld-servers.net.     172800  IN      AAAA    2001:502:1ca1::30

;; Query time: 772 msec
;; SERVER: 198.41.0.4#53(198.41.0.4) (UDP)
;; WHEN: Mon Sep 28 00:09:25 AWST 2026
;; MSG SIZE  rcvd: 828

Querying the TLD nameservers

Now the resolver has .com nameservers and their IPs. We can use one of these, such as 192.5.6.30 (a.gtld-servers.net) to find the nameservers for example.com:

$ dig @192.5.6.30 NS example.com

; <<>> DiG 9.20.29-1-Debian <<>> @192.5.6.30 NS example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37419
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 13
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;example.com.                   IN      NS

;; AUTHORITY SECTION:
example.com.            172800  IN      NS      hera.ns.cloudflare.com.
example.com.            172800  IN      NS      elliott.ns.cloudflare.com.

;; ADDITIONAL SECTION:
hera.ns.cloudflare.com. 172800  IN      A       108.162.192.162
hera.ns.cloudflare.com. 172800  IN      A       172.64.32.162
hera.ns.cloudflare.com. 172800  IN      A       173.245.58.162
hera.ns.cloudflare.com. 172800  IN      AAAA    2606:4700:50::adf5:3aa2
hera.ns.cloudflare.com. 172800  IN      AAAA    2803:f800:50::6ca2:c0a2
hera.ns.cloudflare.com. 172800  IN      AAAA    2a06:98c1:50::ac40:20a2
elliott.ns.cloudflare.com. 172800 IN    A       108.162.195.228
elliott.ns.cloudflare.com. 172800 IN    A       162.159.44.228
elliott.ns.cloudflare.com. 172800 IN    A       172.64.35.228
elliott.ns.cloudflare.com. 172800 IN    AAAA    2606:4700:58::a29f:2ce4
elliott.ns.cloudflare.com. 172800 IN    AAAA    2803:f800:50::6ca2:c3e4
elliott.ns.cloudflare.com. 172800 IN    AAAA    2a06:98c1:50::ac40:23e4

;; Query time: 80 msec
;; SERVER: 192.5.6.30#53(192.5.6.30) (UDP)
;; WHEN: Mon Sep 28 00:12:19 AWST 2026
;; MSG SIZE  rcvd: 359

Finding the domain's nameserver

The response tells the resolver that the nameservers for example.com are hera.ns.cloudflare.com and elliott.ns.cloudflare.com. Now since these Cloudflare nameservers are also .com domains and Cloudflare is so commonly used, the .com nameserver provides optional sibling glue records as defined in RFC 9471 to prevent further roundtrips and optimize the process.

If we ignore the sibling glue records provided, the resolver would have to ask the .com nameservers for the nameservers for cloudflare.com:

$ dig @192.5.6.30 NS cloudflare.com

; <<>> DiG 9.20.29-1-Debian <<>> @192.5.6.30 NS cloudflare.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40307
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 5, ADDITIONAL: 21
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;cloudflare.com.                        IN      NS

;; AUTHORITY SECTION:
cloudflare.com.         172800  IN      NS      ns3.cloudflare.com.
cloudflare.com.         172800  IN      NS      ns5.cloudflare.com.
cloudflare.com.         172800  IN      NS      ns4.cloudflare.com.
cloudflare.com.         172800  IN      NS      ns6.cloudflare.com.
cloudflare.com.         172800  IN      NS      ns7.cloudflare.com.

;; ADDITIONAL SECTION:
ns3.cloudflare.com.     172800  IN      A       162.159.0.33
ns3.cloudflare.com.     172800  IN      A       162.159.7.226
ns3.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:21
ns3.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:7e2
ns5.cloudflare.com.     172800  IN      A       162.159.2.9
ns5.cloudflare.com.     172800  IN      A       162.159.9.55
ns5.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:209
ns5.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:937
ns4.cloudflare.com.     172800  IN      A       162.159.1.33
ns4.cloudflare.com.     172800  IN      A       162.159.8.55
ns4.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:121
ns4.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:837
ns6.cloudflare.com.     172800  IN      A       162.159.3.11
ns6.cloudflare.com.     172800  IN      A       162.159.5.6
ns6.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:30b
ns6.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:506
ns7.cloudflare.com.     172800  IN      A       162.159.4.8
ns7.cloudflare.com.     172800  IN      A       162.159.6.6
ns7.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:408
ns7.cloudflare.com.     172800  IN      AAAA    2400:cb00:2049:1::a29f:606

;; Query time: 163 msec
;; SERVER: 192.5.6.30#53(192.5.6.30) (UDP)
;; WHEN: Mon Sep 28 00:26:53 AWST 2026
;; MSG SIZE  rcvd: 573

Here, the .com nameserver provides in-domain glue records for cloudflare.com's nameservers. Without these, there would be a circular dependency, since the nameserver named is a subdomain of the domain being queried for. This is because nameservers are always defined by name, which means that in order to actually contact the nameserver, your computer needs to obtain its IP address with another DNS lookup.

Next, we'd use one of these nameservers, say ns3.cloudflare.com, to obtain the IP for hera.ns.cloudflare.com:

$ dig @162.159.0.33 hera.ns.cloudflare.com

; <<>> DiG 9.20.29-1-Debian <<>> @162.159.0.33 hera.ns.cloudflare.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16801
;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;hera.ns.cloudflare.com.                IN      A

;; ANSWER SECTION:
hera.ns.cloudflare.com. 86353   IN      A       108.162.192.162
hera.ns.cloudflare.com. 86353   IN      A       173.245.58.162
hera.ns.cloudflare.com. 86353   IN      A       172.64.32.162

;; Query time: 123 msec
;; SERVER: 162.159.0.33#53(162.159.0.33) (UDP)
;; WHEN: Mon Sep 28 01:11:37 AWST 2026
;; MSG SIZE  rcvd: 99

Querying the domain's authoritative nameserver

Now we've reached the authoritative nameserver for example.com! The last thing to do is to fetch the A record for example.com to obtain its server's IP address:

$ dig @108.162.192.162 example.com

; <<>> DiG 9.20.29-1-Debian <<>> @108.162.192.162 example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43082
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com.                   IN      A

;; ANSWER SECTION:
example.com.            300     IN      A       104.20.23.154
example.com.            300     IN      A       172.66.147.243

;; Query time: 123 msec
;; SERVER: 108.162.192.162#53(108.162.192.162) (UDP)
;; WHEN: Mon Sep 28 01:13:47 AWST 2026
;; MSG SIZE  rcvd: 72

At last, we've determined that the IP addresses of the servers behind example.com are 104.20.23.154 and 172.66.147.243. This matches our earlier lookup directly against your computer's configured DNS server.

The DNS servers your computer uses employ this recursive resolution technique behind the scenes, so that your computer only needs to make one round trip, and the servers can use their much faster data center connections and caching to optimize the process and make the query as fast as possible.

Verify your DNS records

To check your website's records, the first thing you'll want to do is ensure your domain name is pointed at the correct nameserver. For instance, aezign.com.au is supposed to use Cloudflare, since that is where all the DNS records have been set up. We can verify this using dig, (or alternatively with online tools like dnschecker.org):

$ dig NS aezign.com.au

; <<>> DiG 9.20.29-1-Debian <<>> NS aezign.com.au
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44172
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;aezign.com.au.                 IN      NS

;; ANSWER SECTION:
aezign.com.au.          86400   IN      NS      rosalie.ns.cloudflare.com.
aezign.com.au.          86400   IN      NS      zeus.ns.cloudflare.com.

;; Query time: 92 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Mon Sep 28 01:39:22 AWST 2026
;; MSG SIZE  rcvd: 100

If this is not set correctly, the DNS records you configure will not work, as resolvers will be fetching records from somewhere else. In most cases, you can update this in your domain registrar's settings. This is especially common for new domain names, which default to the registrar's nameservers; unless you plan to use their built-in DNS management, you'll need to point them to your chosen provider.

Now we can check that the DNS records you've configured match those your DNS server returns. For aezign.com.au, these are the MX and TXT records configured on Cloudflare.

Extract of Cloudflare's DNS record listing showing the MX record and TXT records for aezign.com.au.

The MX record and some TXT records configured for aezign.com.au on Cloudflare's dashboard.

We can check these using dig, and using the +short option to show only the content of the records.

$ dig MX aezign.com.au +short

10 mail.aezign.au.

$ dig TXT aezign.com.au +short

"google-site-verification=KFqTU5xH8fFf6OtszEL4oudfxple29Tm-TvxVtCWkAM"
"v=spf1 mx a:mail.aezign.au -all"

$ dig TXT _dmarc.aezign.com.au +short

"v=DMARC1;p=quarantine;sp=quarantine;adkim=r;aspf=r"

$ dig TXT aezign.com.au-2026._domainkey.aezign.com.au +short

"v=DKIM1; h=sha256; k=rsa; s=email; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzL04yXZgB72YkoxS+tLK//aWx65TS4UA3F0qJ7el68SEuD4ehmg+61Ta9iS31H6U074dnjyoPDeaiGBa7ToTNCtiIts4/ghD/8nrENROE6hQAwRmTK18ODDHrRDW73hWe6Dg4cK5vmXi82/wWRZU9SDKbme28IE9uCVir3lCkKYLs1" "j16gR1Sjqr6a2+3o+EeVXnLJ4wXmJMm7KCIdn7zv0t9Z1eYcw672PdpYPAMpGb8uUsgaDBsNSpTcupPkz4SWj6TrOB80CPHjWseVY8EOJeRsNKQ/8LFoQEBl4Rxtb3HAmu4UIxSZ4QMVOLnXz67e85mA5XjcNIsVMzaWaowwIDAQAB"

As we can see, this matches, which means that the nameservers for aezign.com.au are correct and the DNS records are what we intend them to be.

Note that if you've recently changed the nameserver or DNS records for your domain, it can take anywhere between 10 seconds and 24 hours for the changes to take effect and propagate across the different DNS servers worldwide. Tools like dnschecker.org can check your records on different DNS servers worldwide to see if they're reflecting your changes. The reason for this is caching: DNS servers like 1.1.1.1 will cache results so it doesn't have to do the full resolution process every time, and uses the Time-To-Live (TTL) you configure in your DNS records as a guideline for caching duration.

Girl Genius for Monday, October 05, 2026 [Girl Genius]

The Girl Genius comic for Monday, October 05, 2026 has been posted.

03:49

[1302] Seeking Potential [Twokinds]

Comic for October 4, 2026

01:21

A Different Side of Me [Whatever]

The inside!

I mentioned a few days ago I was suffering from a shoulder impingement and that I had an appointment to get the ball rolling in dealing with it. The first step in doing that was to get some x-rays, to make sure the problems I was having were not being caused by, say, a broken shoulder bone or some such. And as it turns out, my bones are perfectly fine, which I knew, by the way, trust that if I had a broken bone I would not be in the least bit stoic about it. But that is one less thing for the specialist who I’ll be seeing soon to have to consider.

Anyway, did you know you had a skeleton inside of you? This is what part of mine looks like. It’s pretty average, if we’re being honest about it. But it’s served me well enough, most of the time. I appreciate it. At the very least, my life would suck without it.

— JS

Sunday, 04 October

17:49

Link [Scripting News]

Claude's biggest most amazing strength is its ability to inhale a lot of source code and understand what's going on, any language, any OS, anything, in an instant.

16:21

Vincent Bernat: Hacking the Go compiler to efficiently map IPv4 to IPv6 [Planet Debian]

netip.Addr features an Unmap() method returning the unwrapped IPv4 contained in an IPv4-mapped IPv6 address: from ::ffff:203.0.113.10 or ::ffff:cb00:710a, it returns 203.0.113.10.1 There is no Map() or To6() method for the reverse direction. Such a method is trivial to implement, but Go maintainers have rejected it on the grounds that users should write netip.AddrFrom16(ip.As16()) and let the compiler optimize it.2 Today, this pattern is eight times slower than a native method. How can we teach the compiler to optimize this sequence?

The alternatives

Let’s explore three ways to implement the map semantics for netip.Addr. My favorite is to add it to the Go standard library. Go maintainers prefer a small external helper chaining netip.AddrFrom16() and netip.Addr.As16(), hoping the compiler eventually optimizes it. The unsafe package opens a third path, with the same performance as the first solution.

Modifying the Go standard library

Internally, netip.Addr stores any IP address as a 128-bit value with an extra field z to encode the family and the zone:

type Addr struct {
    addr uint128
    z unique.Handle[addrDetail]
}

type addrDetail struct {
    isV6   bool   // IPv4 is false, IPv6 is true.
    zoneV6 string // != "" only if IsV6 is true.
}

var (
    z0    unique.Handle[addrDetail]
    z4    = unique.Make(addrDetail{})
    z6noz = unique.Make(addrDetail{isV6: true})
)

AddrFrom4() encodes an IPv4 address as an IPv4-mapped IPv6 address and sets z to the unique value z4:

// AddrFrom4 returns the address of the IPv4 address given by the bytes in addr.
func AddrFrom4(addr [4]byte) Addr {
    return Addr{
        addr: uint128{
            0,
            0xffff00000000 |
                uint64(addr[0])<<24 | uint64(addr[1])<<16 |
                uint64(addr[2])<<8 | uint64(addr[3])},
        z: z4,
    }
}

Unmap() turns an IPv4-mapped IPv6 address into an IPv4 address by setting the z field to z4:

func (ip Addr) Unmap() Addr {
    if ip.Is4In6() {
        ip.z = z4
    }
    return ip
}

Implementing the reverse direction inside the Go standard library is trivial: we set the z field to z6noz if the address is IPv4.

// To6 maps an IPv4 address to an IPv4-mapped IPv6 address. It returns an
// IPv6 address unmodified.
func (ip Addr) To6() Addr {
    if ip.Is4() {
        ip.z = z6noz
    }
    return ip
}

Update (2026-10)

Instead of patching Go, we can use the -overlay flag of go build to hijack any package, including the standard library. In our case, it can add To6() to net/netip. Yet it is cumbersome:3 the flag expects a JSON file mapping absolute paths in GOROOT to replacement files, and you need to add it to every go command.

As a helper

We can’t access the z field from outside the net/netip package. Instead, we build a small helper around the netip.AddrFrom16(ip.As16()) pattern:

// AddrTo6 maps an IPv4 address to an IPv4-mapped IPv6 address. It returns an
// IPv6 address unmodified.
func AddrTo6(ip netip.Addr) netip.Addr {
    if ip.Is4() {
        ip = netip.AddrFrom16(ip.As16())
    }
    return ip
}

As an unsafe function

Another solution uses the unsafe package to alter the Addr struct through a proxy with the same memory layout:4

// addrProxy has the same memory layout as netip.Addr.
type addrProxy struct {
    addr [2]uint64      // netip.uint128
    z    unsafe.Pointer // unique.Handle[netip.addrDetail]
}

var (
    anyIPv6    = netip.IPv6Unspecified()
    netipZ6noz = (*addrProxy)(unsafe.Pointer(&anyIPv6)).z
)

// AddrTo6 maps an IPv4 address to an IPv4-mapped IPv6 address. It returns an
// IPv6 address unmodified.
func AddrTo6(ip netip.Addr) netip.Addr {
    if !ip.Is4() {
        return ip
    }
    (*addrProxy)(unsafe.Pointer(&ip)).z = netipZ6noz
    return ip
}

Benchmarks

On my computer, with Go 1.27.1, the standard library solution costs 0.88 ns per operation, while the solution favored by Go maintainers costs 7.14 ns. The unsafe solution matches the performance of the first one.

goos: linux
goarch: amd64
pkg: github.com/vincentbernat/go-netip-addrto6
cpu: AMD Ryzen 5 5600X 6-Core Processor
                   │     sec/op     │
AddrTo6/safe            7.137n ± 0%
AddrTo6/unsafe         0.8682n ± 2%
AddrTo6/builtin        0.8775n ± 2%

Assembly code

Let’s check the assembly code the compiler generates for each solution.5 The one built into the standard library looks like this:6

// AX = input.addr.hi, BX = input.addr.lo, CX = input.z
 CMPQ  net/netip·z4(SB), CX     ; check "z" if this is an IPv4 address
 JNE   end                      ; if not, stop here
 MOVQ  net/netip·z6noz(SB), CX  ; CX = netip.z6noz
end:
 RET
// return value = Addr{hi: AX, lo: BX, z: CX}

Go’s assembly language is not a direct representation of the underlying machine language: it operates on a semi-abstract instruction set derived from Plan 9’s assembler. It has four pseudo-registers: FP (frame pointer for function arguments), PC (program counter), SB (static base pointer for global symbols), and SP (stack pointer). It also has architecture-specific registers like AX, CX, DX, BX, SI, DI, and R8 to R15. Instructions storing data use their last argument as the destination. Instructions can carry an explicit size suffix: MOVB moves a byte, MOVW 16 bits, MOVL 32 bits, and MOVQ 64 bits. In the example above, the first instruction compares the 64-bit value z4 with the CX register.

The unsafe solution looks almost the same:

// AX = input.addr.hi, BX = input.addr.lo, CX = input.z
 CMPQ  net/netip·z4(SB), CX  ; check "z" if this is an IPv4 address
 JNE   end                   ; if not, stop here
 MOVQ  netipZ6noz(SB), CX    ; CX = netip.z6noz
end:
 RET
// return value = Addr{hi: AX, lo: BX, z: CX}

The helper solution has far more instructions. To understand why, let’s look at the code for As16() and AddrFrom16(). They are short enough for the compiler to inline them.

func (ip Addr) As16() (a16 [16]byte) {
    byteorder.BEPutUint64(a16[:8], ip.addr.hi)
    byteorder.BEPutUint64(a16[8:], ip.addr.lo)
    return a16
}

func AddrFrom16(addr [16]byte) Addr {
    return Addr{
        addr: uint128{
            byteorder.BEUint64(addr[:8]),
            byteorder.BEUint64(addr[8:]),
        },
        z: z6noz,
    }
}

We can already guess the pattern to optimize: the code packs the IP address into an array, copies it, then unpacks it. If we inline the Go code by hand, we get:

func AddrTo6(input netip.Addr) netip.Addr {
    if !input.Is4() {
        return input
    }

    var a16 [16]byte
    byteorder.BEPutUint64(a16[:8], input.addr.hi)
    byteorder.BEPutUint64(a16[8:], input.addr.lo)

    addr := a16

    var output netip.Addr
    output.addr.hi = byteorder.BEUint64(addr[:8])
    output.addr.lo = byteorder.BEUint64(addr[8:])
    output.z = netip.z6noz
    return output
}

As humans, we can mentally derive the optimized form:

func AddrTo6(input netip.Addr) netip.Addr {
    if !input.Is4() {
        return input
    }
    var output netip.Addr
    output.addr.hi = input.addr.hi
    output.addr.lo = input.addr.lo
    output.z = netip.z6noz
    return output
}

Unfortunately, as of Go 1.26.8, the compiler is not smart enough to do the same:

// AX = input.addr.hi, BX = input.addr.lo, CX = input.z
; Push the stack (32 bytes):
;    0(SP) addr netip.uint128
;   16(SP) a16 [16]byte
 PUSHQ   BP
 MOVQ    SP, BP
 SUBQ    $32, SP

 CMPQ    net/netip·z4(SB), CX  ; check "z" if this is an IPv4 address
 JNE     end                   ; if not, stop here

; Pack: byteorder.BEPutUint64(a16[:8], input.addr.hi)
;       byteorder.BEPutUint64(a16[8:], input.addr.lo)
 MOVBEQ  AX, net/netip·a16+16(SP)
 MOVBEQ  BX, net/netip·a16+24(SP)

; addr = a16, 16 bytes at once through the vector register X0
 MOVUPS  net/netip·a16+16(SP), X0
 MOVUPS  X0, net/netip·addr(SP)

; CX = netip.z6noz
 MOVQ    net/netip·z6noz(SB), CX
; Unpack: output.addr.hi = byteorder.BEUint64(addr[:8])
;         output.addr.lo = byteorder.BEUint64(addr[8:])
 MOVBEQ  net/netip·addr(SP), AX
 MOVBEQ  net/netip·addr+8(SP), BX

end:
 ADDQ    $32, SP
 POPQ    BP
 RET
// return value = Addr{hi: AX, lo: BX, z: CX}

The compiler does a decent job on the byte shuffling: the eight byte stores of BEPutUint64() become a single MOVBEQ, which stores a register byte-swapped. The eight byte loads of BEUint64() become a single MOVBEQ the other way round.7 Three groups of instructions remain: a pack, a copy, and an unpack.

Hacking the Go compiler

The Go compiler has several phases:

Parsing
The compiler tokenizes and parses the source code. It builds a syntax tree for each source file.
Type checking
The compiler maps each identifier to the object it denotes, folds constants, and infers the type of every expression.
IR construction
The compiler converts the syntax tree and its types into its own intermediate representation (IR). This process, called “noding,” goes through a serialization format named unified IR.
Middle end
The compiler performs several optimization passes on the IR, such as devirtualization, function call inlining, and escape analysis.
Walk
This phase runs two steps: order of evaluation decomposes complex statements into simpler ones, and desugaring transforms higher-level Go constructs, like switch or channels, into more primitive instructions or calls to the runtime.
Generic SSA
The compiler converts the IR into Static Single Assignment (SSA) form, a lower-level intermediate representation suited for machine-independent optimizations and rewrite rules.
Machine code generation
The compiler rewrites the SSA form into machine-specific variants, allocates registers, and applies more optimization passes. At the end, the assembler turns the generated instructions into machine code.

The hammer

My first idea is to replace occurrences of netip.AddrFrom16(ip.As16()) with netip.Addr{addr: ip.addr, z: netip.z6noz} as early as possible, during the “noding” process. Before that, the type checking phase prevents us from accessing unexported struct fields.

Go 1.27 introduced a convenient debug option to dump the IR of a function at interesting points during compilation:

$ GOTOOLCHAIN=go1.27.1 GOAMD64=v3 go build -a -gcflags="-d=astdump=AddrTo6Safe" .
Writing text ast output for AddrTo6Safe to AddrTo6Safe.ast
Writing html ast output for AddrTo6Safe to AddrTo6Safe.html
Writing html syntax output for AddrTo6Safe to AddrTo6Safe.syntax.html

In the HTML file, the first column shows the IR as it comes out of noding:

DCLFUNC addrto6.AddrTo6Safe ABI:ABIInternal FUNC-func(netip.Addr) netip.Addr
DCLFUNC-Dcl
. NAME-addrto6.ip Class:PPARAM Offset:0 OnStack Used netip.Addr
. NAME-addrto6.~r0 Class:PPARAMOUT Offset:0 OnStack netip.Addr
DCLFUNC-body
. IF # ipv6_safe.go:11:2
. IF-Cond
. . CALLFUNC bool
. . CALLFUNC-Fun
. . . METHEXPR addrto6.Is4 FUNC-func(netip.Addr) bool
. . . . TYPE netip.Addr Class:PEXTERN Offset:0 type netip.Addr
. . CALLFUNC-Args
. . . NAME-addrto6.ip Class:PPARAM Offset:0 OnStack Used netip.Addr
. IF-Body
. . AS # ipv6_safe.go:12:6
. . . NAME-addrto6.ip Class:PPARAM Offset:0 OnStack Used netip.Addr
. . . CALLFUNC netip.Addr
. . . CALLFUNC-Fun
. . . . NAME-netip.AddrFrom16 Class:PFUNC Offset:0 Used FUNC-func([16]byte) netip.Addr
. . . CALLFUNC-Args
. . . . CALLFUNC ARRAY-[16]byte
. . . . CALLFUNC-Fun
. . . . . METHEXPR addrto6.As16 FUNC-func(netip.Addr) [16]byte
. . . . . . TYPE netip.Addr Class:PEXTERN Offset:0 type netip.Addr
. . . . CALLFUNC-Args
. . . . . NAME-addrto6.ip Class:PPARAM Offset:0 OnStack Used netip.Addr
. RETURN # ipv6_safe.go:14:2
. RETURN-Results
. . NAME-addrto6.ip Class:PPARAM Offset:0 OnStack Used netip.Addr

In the body of the if statement, we spot the calls to the method netip.Addr.As16() and to the function netip.AddrFrom16(). Our goal is to patch them with a struct literal:

IF-Body
. AS # ipv6_safe.go:12:6
. . NAME-addrto6.ip Class:PPARAM Offset:0 OnStack Used netip.Addr
. . STRUCTLIT netip.Addr
. . STRUCTLIT-List
. . . STRUCTKEY netip.addr
. . . . DOT netip.addr netip.uint128
. . . . . NAME-addrto6.ip Class:PPARAM Offset:0 OnStack Used netip.Addr
. . . STRUCTKEY netip.z
. . . . NAME-netip.z6noz Class:PEXTERN Offset:0 unique.Handle[net/netip.addrDetail]

In noder’s reader.go, the expr() method builds the IR tree for an expression. At the end of the exprCall case, we add a call to a rewriteAddrFrom16As16() function. It takes the current node and returns the struct literal on success, or nil if the rewrite is not possible. First, we check that we have the expected pattern: a call to the netip.AddrFrom16() function with a call to the netip.Addr.As16() method as its only argument:

func rewriteAddrFrom16As16(n ir.Node) ir.Node {
    call, ok := n.(*ir.CallExpr)
    if !ok || call.Op() != ir.OCALLFUNC ||
        len(call.Args) != 1 || len(call.Init()) != 0 ||
        !isNetipFunc(call.Fun, "AddrFrom16") {
        return nil
    }
    inner, ok := call.Args[0].(*ir.CallExpr)
    if !ok || inner.Op() != ir.OCALLFUNC ||
        len(inner.Args) != 1 || len(inner.Init()) != 0 ||
        !isNetipFunc(inner.Fun, "Addr.As16") {
        return nil
    }
    x := inner.Args[0]
    // [...]
}

Then, we fetch netip.z6noz:

z6noz, err := lookupVar(ir.StaticCalleeName(call.Fun).Sym().Pkg, "z6noz")
if err != nil {
    return nil
}

And we build the struct literal:

typ := call.Type()
pos := call.Pos()
var list []ir.Node
for i, f := range typ.Fields() {
    var value ir.Node
    switch f.Sym.Name {
    case "addr":
        value = typecheck.DotField(pos, x, i)
    case "z":
        value = z6noz
    default:
        return nil
    }
    list = append(list, ir.NewStructKeyExpr(pos, f, value))
}
lit := ir.NewCompLitExpr(pos, ir.OSTRUCTLIT, typ, list)
lit.SetTypecheck(1)
return lit

Have a look at the complete patch.8 We can test it with the following commands:

$ cd src
$ ./make.bash
Building Go cmd/dist using /usr/lib/go-1.27. (go1.27.1 linux/amd64)
Building Go toolchain1 and bootstrap cmd/go (go_bootstrap) using /usr/lib/go-1.27.
Building Go toolchain2 using go_bootstrap and Go toolchain1.
Building Go toolchain3 and commands using go_bootstrap and Go toolchain2.
Checking command staleness for linux/amd64.
---
Installed Go for linux/amd64 in /home/bernat/code/free/go
Installed commands in /home/bernat/code/free/go/bin
*** You need to add /home/bernat/code/free/go/bin to your PATH.
$ export PATH=$PWD/../bin:$PATH
$ go version
go version go1.28-devel_9834516e20 Sat Sep 12 08:23:11 2026 -0700 linux/amd64
$ go test net/netip/...
ok      net/netip   0.224s
$ cd ../../go-netip-addrto6
$ go test .
ok      github.com/vincentbernat/go-netip-addrto6   0.062s

The generated code for the helper is now the shortest possible version!

// AX = input.addr.hi, BX = input.addr.lo, CX = input.z
 CMPQ    net/netip·z4(SB), CX     ; check "z" if this is an IPv4 address
 JNE     end                      ; if not, stop here
 MOVQ    net/netip·z6noz(SB), CX  ; CX = netip.z6noz
end:
 RET
// return value = Addr{hi: AX, lo: BX, z: CX}

Go maintainers are unlikely to accept this change. It relies on the internal structure of net/netip.Addr. It’s an ugly hack in the noder, whose job is to faithfully translate the type-checked AST into the IR. And it’s harder to maintain than adding a To6() method.

The screwdriver

The right place for such an optimization is the generic SSA phase. One of the last machine-independent passes is memcombine. With the appropriate debug flag, the compiler dumps the SSA form after this pass:9

$ GOTOOLCHAIN=go1.26.8 GOAMD64=v3 \
> go build -a -gcflags='-d=ssa/memcombine/dump=AddrTo6Safe' .
$ head -5 AddrTo6Safe_01__memcombine.dump
AddrTo6Safe func(netip.Addr) netip.Addr
  b2:
    (?) v1 = InitMem <mem>
    (?) v2 = SP <uintptr>
    (?) v3 = SB <uintptr>

The result of the memcombine pass follows the same structure as the assembly code for AddrTo6Safe() we looked at earlier: two stores, one move, and two loads we would like to optimize away.

; […]
  v502 = ArgIntReg <uint64> {ip+0} [0]              ; input.addr.hi
  v490 = ArgIntReg <uint64> {ip+8} [1]              ; input.addr.lo
  v466 = ArgIntReg <*netip.addrDetail> {ip+16} [2]  ; input.z
; […]
  v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
  v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
  v442 = Bswap64 <uint64> v490                      ; bswap(input.addr.lo)
  v542 = Bswap64 <uint64> v502                      ; bswap(input.addr.hi)
  v161 = Store <mem> {uint64} v22 v542 v23          ; a16[:8] = bswap(hi)
  v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)

  v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
  v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16

  v415 = OffPtr <*byte> [8] v285                    ; &addr[8]
  v416 = Load <uint64> v285 v286                    ; addr[:8]
  v299 = Bswap64 <uint64> v416                      ; output.addr.hi
  v174 = Load <uint64> v415 v286                    ; addr[8:]
  v39  = Bswap64 <uint64> v174                      ; output.addr.lo
; […]

Each line features a value identifier (v442), an operation with its type (Bswap64 <uint64>), and its arguments (v490).10 Values are the basic building blocks of SSA and are defined exactly once. Square brackets enclose integer parameters ([8]) and curly braces contain auxiliary arguments ({netip.addr}). Operations writing to memory produce a new memory state. Every memory operation takes the current state as its last argument, which keeps them in order.

On paper

Let’s focus on output.addr.lo, aka v39:

  v490 = ArgIntReg <uint64> {ip+8} [1]              ; input.addr.lo
  v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
  v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
  v442 = Bswap64 <uint64> v490                      ; bswap(input.addr.lo)
  v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
  v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
  v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
  v415 = OffPtr <*byte> [8] v285                    ; &addr[8]
  v174 = Load <uint64> v415 v286                    ; addr[8:]
  v39  = Bswap64 <uint64> v174                      ; output.addr.lo

To simplify this code, we could apply three rewriting rules:

  1. The first one adds a shortcut when loading through a move: (Load (OffPtr [o] p) (Move p src mem)) => (Load (OffPtr [o] src) mem). This matches v174 with its arguments v415 and v286 and creates a new value v600:

      v490 = ArgIntReg <uint64> {ip+8} [1]              ; input.addr.lo
      v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
      v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v442 = Bswap64 <uint64> v490                      ; bswap(input.addr.lo)
      v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
      v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
      v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
      v415 = OffPtr <*byte> [8] v285                    ; &addr[8]
      v600 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v174 = Load <uint64> v600 v282                    ; a16[8:]
      v39  = Bswap64 <uint64> v174                      ; output.addr.lo
    
    
  2. The second one simplifies a load following a store: (Load p (Store p x _)) => x. The load is forwarded: the stored value replaces it and no memory access remains. It matches v174. It notices that v600 and v173 are the same address and replaces v174 with a copy of v442:

      v490 = ArgIntReg <uint64> {ip+8} [1]              ; input.addr.lo
      v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
      v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v442 = Bswap64 <uint64> v490                      ; bswap(input.addr.lo)
      v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
      v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
      v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
      v415 = OffPtr <*byte> [8] v285                    ; &addr[8]
      v600 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v174 = Copy <uint64> v442                         ; bswap(input.addr.lo)
      v39  = Bswap64 <uint64> v174                      ; output.addr.lo
    
    
  3. The last step cancels the two byte swaps: (Bswap64 (Bswap64 x)) => x. v39 becomes a copy of v490:

      v490 = ArgIntReg <uint64> {ip+8} [1]              ; input.addr.lo
      v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
      v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v442 = Bswap64 <uint64> v490                      ; bswap(input.addr.lo)
      v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
      v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
      v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
      v415 = OffPtr <*byte> [8] v285                    ; &addr[8]
      v600 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v174 = Copy <uint64> v442                         ; bswap(input.addr.lo)
      v39  = Copy <uint64> v490                         ; output.addr.lo = input.addr.lo
    
    

If we ignore the values not needed to compute v39, only this SSA form remains:

  v490 = ArgIntReg <uint64> {ip+8} [1]  ; input.addr.lo
  v39  = Copy <uint64> v490             ; output.addr.lo = input.addr.lo

Let’s switch to output.addr.hi, aka v299:

  v502 = ArgIntReg <uint64> {ip+0} [0]              ; input.addr.hi
  v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
  v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
  v542 = Bswap64 <uint64> v502                      ; bswap(input.addr.hi)
  v161 = Store <mem> {uint64} v22 v542 v23          ; a16[:8] = bswap(hi)
  v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
  v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
  v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
  v416 = Load <uint64> v285 v286                    ; addr[:8]
  v299 = Bswap64 <uint64> v416                      ; output.addr.hi

To optimize it away, we also apply three rewriting rules:

  1. The first one also adds a shortcut when loading through a move, but without an offset: (Load p (Move p src mem)) => (Load src mem). This rewrites v416 to use arguments from v286:

      v502 = ArgIntReg <uint64> {ip+0} [0]              ; input.addr.hi
      v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
      v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v542 = Bswap64 <uint64> v502                      ; bswap(input.addr.hi)
      v161 = Store <mem> {uint64} v22 v542 v23          ; a16[:8] = bswap(hi)
      v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
      v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
      v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
      v416 = Load <uint64> v22 v282                     ; a16[:8]
      v299 = Bswap64 <uint64> v416                      ; output.addr.hi
    
    
  2. The second rule forwards a value stored one step earlier, skipping over a store to another address: (Load p (Store q _ (Store p x _))) => x. This matches v416: x is v542, p is v22 (&a16), q is v173 (&a16[8]), and p and q do not overlap for uint64.

      v502 = ArgIntReg <uint64> {ip+0} [0]              ; input.addr.hi
      v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
      v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v542 = Bswap64 <uint64> v502                      ; bswap(input.addr.hi)
      v161 = Store <mem> {uint64} v22 v542 v23          ; a16[:8] = bswap(hi)
      v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
      v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
      v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
      v416 = Copy <uint64> v542                         ; bswap(input.addr.hi)
      v299 = Bswap64 <uint64> v416                      ; output.addr.hi
    
    
  3. The third rule cancels two byte swaps: (Bswap64 (Bswap64 x)) => x. v299 becomes a copy of v502:

      v502 = ArgIntReg <uint64> {ip+0} [0]              ; input.addr.hi
      v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
      v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v542 = Bswap64 <uint64> v502                      ; bswap(input.addr.hi)
      v161 = Store <mem> {uint64} v22 v542 v23          ; a16[:8] = bswap(hi)
      v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
      v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
      v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
      v416 = Copy <uint64> v542                         ; bswap(input.addr.hi)
      v299 = Copy <uint64> v502                         ; output.addr.hi = input.addr.hi
    
    

If we remove the values not used to compute v299, we get this SSA form:

  v502 = ArgIntReg <uint64> {ip+0} [0] ; input.addr.hi
  v299 = Copy <uint64> v502            ; output.addr.hi = input.addr.hi

In practice

Most of these rules already exist in generic.rules. They use conditions to validate their context: ssa.IsSamePtr() for the same address, ssa.Disjoint() for addresses that do not overlap. The rule forwarding a stored value to a load already exists with three variants looking through several other stores. Here are the two we need:

(Load <t1> p1 (Store {t2} p2 x _))
    && ssa.IsSamePtr(p1, p2)
    && copyCompatibleType(t1, x.Type)
    && t1.Size() == t2.Size()
    => x
(Load <t1> p1 (Store {t2} p2 _ (Store {t3} p3 x _)))
    && ssa.IsSamePtr(p1, p3)
    && copyCompatibleType(t1, x.Type)
    && t1.Size() == t3.Size()
    && ssa.Disjoint(p3, t3, p2, t2)
    => x

Go 1.27 added the rule loading through a move with CL 748200 to fix issue #77720:

(Load <t1> op1:(OffPtr [o1] p1) move:(Move [n] p2 src mem))
    && o1 >= 0 && o1+t1.Size() <= n && ssa.IsSamePtr(p1, p2)
    && !ssa.IsVolatile(src)
    => @move.Block (Load <t1> (OffPtr <op1.Type> [o1] src) mem)

It lacks a variant without an offset:

(Load <t1> p1 move:(Move [n] p2 src mem))
    && p1.Op != ssaop.OpOffPtr
    && t1.Size() <= n && ssa.IsSamePtr(p1, p2)
    && !ssa.IsVolatile(src)
    => @move.Block (Load <t1> (OffPtr <p1.Type> [0] src) mem)

There is no generic rule to cancel two byte swaps, but the AMD64 lowering pass includes this rule:

(BSWAP(Q|L) (BSWAP(Q|L) p)) => p

After switching to Go’s development branch and adding the missing rule, the generated assembly code is worse than with Go 1.26.8, even though our additional rule slightly improves the situation at the end:

// AX = input.addr.hi, BX = input.addr.lo, CX = input.z
; Push the stack (16 bytes):
;    0(SP) a16 [16]byte
 PUSHQ   BP
 MOVQ    SP, BP
 SUBQ    $16, SP

 CMPQ    net/netip·z4(SB), CX       ; check "z" if this is an IPv4 address
 JNE     end                        ; if not, stop here

; The four forwarded bytes: the low half of input.addr.lo is taken apart and
; put back together in registers
 MOVQ    BX, DX                     ; DX = input.addr.lo
 SHRQ    $24, BX                    ; BX = input.addr.lo >> 24
 MOVQ    DX, SI                     ; SI = input.addr.lo
 SHRQ    $16, DX                    ; DX = input.addr.lo >> 16
 MOVQ    SI, DI                     ; DI = input.addr.lo, kept for the pack
 SHRQ    $8, SI                     ; SI = input.addr.lo >> 8
 MOVBLZX DIB, R8                    ; R8 = byte(input.addr.lo)
 MOVBLZX SIB, SI                    ; SI = byte(input.addr.lo >> 8)
 SHLQ    $8, SI
 ORQ     R8, SI                     ; SI = two low bytes of input.addr.lo
 MOVBLZX DL, DX                     ; DX = byte(input.addr.lo >> 16)
 SHLQ    $16, DX
 ORQ     SI, DX
 MOVBLZX BL, BX                     ; BX = byte(input.addr.lo >> 24)
 SHLQ    $24, BX
 ORQ     DX, BX                     ; BX = input.addr.lo & 0xffffffff

; Pack: byteorder.BEPutUint64(a16[:8], input.addr.hi)
;       byteorder.BEPutUint64(a16[8:], input.addr.lo)
 MOVBEQ  AX, net/netip·a16(SP)
 MOVBEQ  DI, net/netip·a16+8(SP)

; The four other bytes of input.addr.lo, read one by one from a16
 MOVBLZX net/netip·a16+11(SP), DX   ; a16[11]
 SHLQ    $32, DX
 ORQ     DX, BX
 MOVBLZX net/netip·a16+10(SP), DX   ; a16[10]
 SHLQ    $40, DX
 ORQ     DX, BX
 MOVBLZX net/netip·a16+9(SP), DX    ; a16[9]
 SHLQ    $48, DX
 ORQ     DX, BX
 MOVBLZX net/netip·a16+8(SP), DX    ; a16[8]
 SHLQ    $56, DX

; output.z = netip.z6noz
 MOVQ    net/netip·z6noz(SB), CX
; output.addr.hi = byteorder.BEUint64(a16[:8])
 MOVBEQ  net/netip·a16(SP), AX
; output.addr.lo assembled from the previous steps
 ORQ     DX, BX

end:
 LEAVEQ
 RET
// return value = Addr{hi: AX, lo: BX, z: CX}

The rule loading through a move, added in Go 1.27, introduced this regression.

Out of order

Let’s not give up now! In reality, the rewriting rules run before memcombine, notably in the late opt pass. At this point, the inlined versions of BEPutUint64() and BEUint64() still expand to sixteen byte stores and sixteen byte loads, matching their source code:

func BEUint64(b []byte) uint64 {
    _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
    return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
        uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
}

Let’s follow two bytes of output.addr.lo: addr[15] and addr[11]. Here is a simplified SSA form before late opt:

  v273 = Trunc64to8 <byte> v490                     ; byte(input.addr.lo)
  v226 = Trunc64to8 <byte> v225                     ; byte(input.addr.lo >> 32)
; […]
  v235 = Store <mem> {byte} v233 v226 v223          ; a16[11] = byte(lo >> 32)
  v247 = Store <mem> {byte} v245 v238 v235          ; a16[12] = …
  v259 = Store <mem> {byte} v257 v250 v247          ; a16[13] = …
  v271 = Store <mem> {byte} v269 v262 v259          ; a16[14] = …
  v282 = Store <mem> {byte} v280 v273 v271          ; a16[15] = byte(lo)
  v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
  v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
; […]
  v433 = OffPtr <*byte> [15] v285                   ; &addr[15]
  v435 = Load <byte> v433 v286                      ; addr[15]
  v479 = OffPtr <*byte> [11] v285                   ; &addr[11]
  v481 = Load <byte> v479 v286                      ; addr[11]

The first rule loads through the move: (Load (OffPtr [o] p) (Move p src mem)) => (Load (OffPtr [o] src) mem). It matches both loads, which now read a16 with the memory state before the copy:

  v600 = OffPtr <*byte> [15] v22 ; &a16[15]
  v435 = Load <byte> v600 v282   ; a16[15]
  v601 = OffPtr <*byte> [11] v22 ; &a16[11]
  v481 = Load <byte> v601 v282   ; a16[11]

The second rule shortcuts a load following a store: (Load p (Store p x _)) => x. It matches v435, as v282 stores a16[15]. It does not match v481: v235 stores a16[11] four stores earlier in the chain, while the variants of this rule look through three stores at most.

  v435 = Copy <byte> v273        ; byte(input.addr.lo)
  v601 = OffPtr <*byte> [11] v22 ; &a16[11]
  v481 = Load <byte> v601 v282   ; a16[11]

The same happens to the other bytes: the rule forwards the four bytes stored last, a16[12] to a16[15]. The twelve other loads now read a16 instead of addr.

BEUint64() becomes a chain of Or64, each one adding a byte shifted into place. memcombine is a pass written in Go, not a set of rewrite rules. It starts from the last Or64 of the chain and collects up to eight terms. If each term is a byte load, extended to 64 bits and shifted, and if the eight loads read consecutive addresses from the same pointer with the same memory state, it replaces the whole chain with a single 64-bit load and a byte swap. Otherwise, it tries again with four, then two terms, and from each intermediate Or64. Here is the loop checking each term in a simplified version of combineLoads():

for i := int64(0); i < n; i++ {
    v := a[i]
    shift := int64(0)
    if v.Op == shiftOp {
        v, shift = peelShift(v)
    }
    if v.Op != extOp {
        return false
    }
    load := v.Args[0]
    if load.Op != ssaop.OpLoad {
        return false
    }
    if load.Args[1] != mem {
        return false
    }
    p, off := splitPtr(load.Args[0])
    if p != base {
        return false
    }
    r[i] = LoadRecord{load: load, offset: off, shift: shift}
}

For output.addr.hi, the eight loads read a16 with the same memory state v282:

  v13  = Load <byte> v22 v282               ; a16[0]
  v530 = Load <byte> v14 v282               ; a16[1]
  v488 = Load <byte> v504 v282              ; a16[2]
  v405 = Load <byte> v537 v282              ; a16[3]
  v385 = Load <byte> v397 v282              ; a16[4]
  v361 = Load <byte> v373 v282              ; a16[5]
  v196 = Load <byte> v63 v282               ; a16[6]
  v432 = Load <byte> v315 v282              ; a16[7]
  v319 = ZeroExt8to64 <uint64> v432         ; uint64(a16[7])
  v329 = ZeroExt8to64 <uint64> v196         ; uint64(a16[6])
  v330 = Lsh64x64 <uint64> [true] v329 v138 ; uint64(a16[6]) << 8
  v331 = Or64 <uint64> v319 v330            ; a16[7] | a16[6] << 8
; […] same for a16[5] to a16[1]
  v401 = ZeroExt8to64 <uint64> v13          ; uint64(a16[0])
  v402 = Lsh64x64 <uint64> [true] v401 v55  ; uint64(a16[0]) << 56
  v403 = Or64 <uint64> v402 v391            ; | a16[0] << 56 = output.addr.hi

memcombine merges them into one load and a swap:

  v286 = Load <uint64> v22 v282   ; a16[:8]
  v285 = Bswap64 <uint64> v286    ; output.addr.hi

For output.addr.lo, here is the chain memcombine sees after late opt:

  v436 = ZeroExt8to64 <uint64> v273 ; addr[15], forwarded
  v448 = Or64 <uint64> v436 v447    ; | addr[14] << 8, forwarded
  v460 = Or64 <uint64> v459 v448    ; | addr[13] << 16, forwarded
  v472 = Or64 <uint64> v471 v460    ; | addr[12] << 24, forwarded
  v484 = Or64 <uint64> v483 v472    ; | a16[11] << 32, loaded
  v496 = Or64 <uint64> v495 v484    ; | a16[10] << 40, loaded
  v508 = Or64 <uint64> v507 v496    ; | a16[9] << 48, loaded
  v520 = Or64 <uint64> v519 v508    ; | a16[8] << 56, loaded

From v520, four of the eight terms are forwarded bytes, not loads from memory, and memcombine can’t combine them. It doesn’t merge the four remaining loads either, as they sit on top of the forwarded bytes.

Back in order

In summary, the rewriting rules run too early to be effective. A quick workaround exists: run an earlier round of memcombine before late opt. After this change, the generated code for the helper is back to the shortest possible version:

// AX = input.addr.hi, BX = input.addr.lo, CX = input.z
 CMPQ    net/netip·z4(SB), CX     ; check "z" if this is an IPv4 address
 JNE     end                      ; if not, stop here
 MOVQ    net/netip·z6noz(SB), CX  ; CX = netip.z6noz
end:
 RET
// return value = Addr{hi: AX, lo: BX, z: CX}

And the benchmark confirms it! ✌️

goos: linux
goarch: amd64
pkg: github.com/vincentbernat/go-netip-addrto6
cpu: AMD Ryzen 5 5600X 6-Core Processor
                   │   Go 1.26.8    │             Our branch              │
                   │     sec/op     │    sec/op     vs base               │
AddrTo6/safe          6.5470n ±  0%   0.8944n ± 4%  -86.34% (p=0.002 n=6)
AddrTo6/unsafe        0.9071n ±  3%   0.8682n ± 1%   -4.28% (p=0.002 n=6)
AddrTo6/builtin       0.8871n ±  2%   0.8785n ± 1%        ~ (p=0.310 n=6)

Next steps

I think Go maintainers would reject this change because of the additional memcombine pass. Instead, I plan to publish this blog post and bring up the subject again as a follow-up to issue #54365. Either the sheer complexity and the Go 1.27 regression convince the maintainers that adding a To6() method is simpler and more efficient, or they advise me on how to move forward. Either way, digging into this subject taught me a lot about the Go compiler! ⚙️

Update (2026-10)

I opened issue #81994 to propose Addr.To6(). Give it a 👍 if you want it in Go!


  1. IPv4-mapped IPv6 addresses let you handle only IPv6 addresses in your code and keep conversions at a few well-defined boundaries. I use them in Akvorado. ↩

  2. This is not strictly equivalent. The zero value becomes :: while it would make more sense to leave it untouched. ↩

  3. I suppose Go maintainers keep it inconvenient on purpose: they don’t want random packages to patch the standard library or other packages. ↩

  4. To avoid catastrophic bugs when netip.Addr’s layout changes, you must add tests to detect it. This is more dangerous if you put this code in a package. Either ask users to run the tests themselves, or detect the change at run time and panic. Hiding this optimization behind a build tag would make users aware of this potential trap. ↩

  5. I produced the assembly code with GOTOOLCHAIN=go1.26.8 GOAMD64=v3 ./go-asm '\.asm' from the companion repository, then edited it a bit to keep this article from turning into an endless rabbit hole. Due to an unfortunate sequence of events, we switch between versions: Go 1.27 introduced a regression that blurs the point of this article. ↩

  6. The function is short enough for the compiler to inline it, so the assembly code may vary depending on the surrounding code. ↩

  7. MOVBEQ requires GOAMD64=v3, matching the x86-64-v3 microarchitecture from 2013. Otherwise, the compiler translates BEPutUint64() to BSWAPQ+MOVQ and BEUint64() to MOVQ+BSWAPQ. ↩

  8. When the call is used as a statement, a struct literal alone is invalid. The patch then turns it into _ = netip.Addr{…}. ↩

  9. The compiler can also write an HTML file with the result of each pass:

    $ GOSSAFUNC=AddrTo6Safe \
    > GOTOOLCHAIN=go1.26.8 \
    > GOAMD64=v3 go build -a .
    dumped SSA for AddrTo6Safe,1 to ./ssa.html
    
    

    ↩

  10. Source line numbers in parentheses follow value identifiers, but I removed them from the examples. The SSA form also features branches, but we don’t need them to understand our optimizations. ↩

15:35

Vincent Bernat: Hacking the Go compiler to efficiently map IPv4 to IPv6 [Planet Debian]

netip.Addr features an Unmap() method returning the unwrapped IPv4 contained in an IPv4-mapped IPv6 address: from ::ffff:203.0.113.10 or ::ffff:cb00:710a, it returns 203.0.113.10.1 There is no Map() or To6() method for the reverse direction. Such a method is trivial to implement, but Go maintainers have rejected it on the grounds that users should write netip.AddrFrom16(ip.As16()) and let the compiler optimize it.2 Today, this pattern is eight times slower than a native method. How can we teach the compiler to optimize this sequence?

The alternatives

Let’s explore three ways to implement the map semantics for netip.Addr. My favorite is to add it to the Go standard library. Go maintainers prefer a small external helper chaining netip.AddrFrom16() and netip.Addr.As16(), hoping the compiler eventually optimizes it. The unsafe package opens a third path, with the same performance as the first solution.

Modifying the Go standard library

Internally, netip.Addr stores any IP address as a 128-bit value with an extra field z to encode the family and the zone:

type Addr struct {
    addr uint128
    z unique.Handle[addrDetail]
}

type addrDetail struct {
    isV6   bool   // IPv4 is false, IPv6 is true.
    zoneV6 string // != "" only if IsV6 is true.
}

var (
    z0    unique.Handle[addrDetail]
    z4    = unique.Make(addrDetail{})
    z6noz = unique.Make(addrDetail{isV6: true})
)

AddrFrom4() encodes an IPv4 address as an IPv4-mapped IPv6 address and sets z to the unique value z4:

// AddrFrom4 returns the address of the IPv4 address given by the bytes in addr.
func AddrFrom4(addr [4]byte) Addr {
    return Addr{
        addr: uint128{
            0,
            0xffff00000000 |
                uint64(addr[0])<<24 | uint64(addr[1])<<16 |
                uint64(addr[2])<<8 | uint64(addr[3])},
        z: z4,
    }
}

Unmap() turns an IPv4-mapped IPv6 address into an IPv4 address by setting the z field to z4:

func (ip Addr) Unmap() Addr {
    if ip.Is4In6() {
        ip.z = z4
    }
    return ip
}

Implementing the reverse direction inside the Go standard library is trivial: we set the z field to z6noz if the address is IPv4.

// To6 maps an IPv4 address to an IPv4-mapped IPv6 address. It returns an
// IPv6 address unmodified.
func (ip Addr) To6() Addr {
    if ip.Is4() {
        ip.z = z6noz
    }
    return ip
}

As a helper

We can’t access the z field from outside the net/netip package. Instead, we build a small helper around the netip.AddrFrom16(ip.As16()) pattern:

// AddrTo6 maps an IPv4 address to an IPv4-mapped IPv6 address. It returns an
// IPv6 address unmodified.
func AddrTo6(ip netip.Addr) netip.Addr {
    if ip.Is4() {
        ip = netip.AddrFrom16(ip.As16())
    }
    return ip
}

As an unsafe function

Another solution uses the unsafe package to alter the Addr struct through a proxy with the same memory layout:3

// addrProxy has the same memory layout as netip.Addr.
type addrProxy struct {
    addr [2]uint64      // netip.uint128
    z    unsafe.Pointer // unique.Handle[netip.addrDetail]
}

var (
    anyIPv6    = netip.IPv6Unspecified()
    netipZ6noz = (*addrProxy)(unsafe.Pointer(&anyIPv6)).z
)

// AddrTo6 maps an IPv4 address to an IPv4-mapped IPv6 address. It returns an
// IPv6 address unmodified.
func AddrTo6(ip netip.Addr) netip.Addr {
    if !ip.Is4() {
        return ip
    }
    (*addrProxy)(unsafe.Pointer(&ip)).z = netipZ6noz
    return ip
}

Benchmarks

On my computer, with Go 1.27.1, the standard library solution costs 0.88 ns per operation, while the solution favored by Go maintainers costs 7.14 ns. The unsafe solution matches the performance of the first one.

goos: linux
goarch: amd64
pkg: github.com/vincentbernat/go-netip-addrto6
cpu: AMD Ryzen 5 5600X 6-Core Processor
                   │     sec/op     │
AddrTo6/safe            7.137n ± 0%
AddrTo6/unsafe         0.8682n ± 2%
AddrTo6/builtin        0.8775n ± 2%

Assembly code

Let’s check the assembly code the compiler generates for each solution.4 The one built into the standard library looks like this:5

// AX = input.addr.hi, BX = input.addr.lo, CX = input.z
 CMPQ  net/netip·z4(SB), CX     ; check "z" if this is an IPv4 address
 JNE   end                      ; if not, stop here
 MOVQ  net/netip·z6noz(SB), CX  ; CX = netip.z6noz
end:
 RET
// return value = Addr{hi: AX, lo: BX, z: CX}

Go’s assembly language is not a direct representation of the underlying machine language: it operates on a semi-abstract instruction set derived from Plan 9’s assembler. It has four pseudo-registers: FP (frame pointer for function arguments), PC (program counter), SB (static base pointer for global symbols), and SP (stack pointer). It also has architecture-specific registers like AX, CX, DX, BX, SI, DI, and R8 to R15. Instructions storing data use their last argument as the destination. Instructions can carry an explicit size suffix: MOVB moves a byte, MOVW 16 bits, MOVL 32 bits, and MOVQ 64 bits. In the example above, the first instruction compares the 64-bit value z4 with the CX register.

The unsafe solution looks almost the same:

// AX = input.addr.hi, BX = input.addr.lo, CX = input.z
 CMPQ  net/netip·z4(SB), CX  ; check "z" if this is an IPv4 address
 JNE   end                   ; if not, stop here
 MOVQ  netipZ6noz(SB), CX    ; CX = netip.z6noz
end:
 RET
// return value = Addr{hi: AX, lo: BX, z: CX}

The helper solution has far more instructions. To understand why, let’s look at the code for As16() and AddrFrom16(). They are short enough for the compiler to inline them.

func (ip Addr) As16() (a16 [16]byte) {
    byteorder.BEPutUint64(a16[:8], ip.addr.hi)
    byteorder.BEPutUint64(a16[8:], ip.addr.lo)
    return a16
}

func AddrFrom16(addr [16]byte) Addr {
    return Addr{
        addr: uint128{
            byteorder.BEUint64(addr[:8]),
            byteorder.BEUint64(addr[8:]),
        },
        z: z6noz,
    }
}

We can already guess the pattern to optimize: the code packs the IP address into an array, copies it, then unpacks it. If we inline the Go code by hand, we get:

func AddrTo6(input netip.Addr) netip.Addr {
    if !input.Is4() {
        return input
    }

    var a16 [16]byte
    byteorder.BEPutUint64(a16[:8], input.addr.hi)
    byteorder.BEPutUint64(a16[8:], input.addr.lo)

    addr := a16

    var output netip.Addr
    output.addr.hi = byteorder.BEUint64(addr[:8])
    output.addr.lo = byteorder.BEUint64(addr[8:])
    output.z = netip.z6noz
    return output
}

As humans, we can mentally derive the optimized form:

func AddrTo6(input netip.Addr) netip.Addr {
    if !input.Is4() {
        return input
    }
    var output netip.Addr
    output.addr.hi = input.addr.hi
    output.addr.lo = input.addr.lo
    output.z = netip.z6noz
    return output
}

Unfortunately, as of Go 1.26.8, the compiler is not smart enough to do the same:

// AX = input.addr.hi, BX = input.addr.lo, CX = input.z
; Push the stack (32 bytes):
;    0(SP) addr netip.uint128
;   16(SP) a16 [16]byte
 PUSHQ   BP
 MOVQ    SP, BP
 SUBQ    $32, SP

 CMPQ    net/netip·z4(SB), CX  ; check "z" if this is an IPv4 address
 JNE     end                   ; if not, stop here

; Pack: byteorder.BEPutUint64(a16[:8], input.addr.hi)
;       byteorder.BEPutUint64(a16[8:], input.addr.lo)
 MOVBEQ  AX, net/netip·a16+16(SP)
 MOVBEQ  BX, net/netip·a16+24(SP)

; addr = a16, 16 bytes at once through the vector register X0
 MOVUPS  net/netip·a16+16(SP), X0
 MOVUPS  X0, net/netip·addr(SP)

; CX = netip.z6noz
 MOVQ    net/netip·z6noz(SB), CX
; Unpack: output.addr.hi = byteorder.BEUint64(addr[:8])
;         output.addr.lo = byteorder.BEUint64(addr[8:])
 MOVBEQ  net/netip·addr(SP), AX
 MOVBEQ  net/netip·addr+8(SP), BX

end:
 ADDQ    $32, SP
 POPQ    BP
 RET
// return value = Addr{hi: AX, lo: BX, z: CX}

The compiler does a decent job on the byte shuffling: the eight byte stores of BEPutUint64() become a single MOVBEQ, which stores a register byte-swapped. The eight byte loads of BEUint64() become a single MOVBEQ the other way round.6 Three groups of instructions remain: a pack, a copy, and an unpack.

Hacking the Go compiler

The Go compiler has several phases:

Parsing
The compiler tokenizes and parses the source code. It builds a syntax tree for each source file.
Type checking
The compiler maps each identifier to the object it denotes, folds constants, and infers the type of every expression.
IR construction
The compiler converts the syntax tree and its types into its own intermediate representation (IR). This process, called “noding,” goes through a serialization format named unified IR.
Middle end
The compiler performs several optimization passes on the IR, such as devirtualization, function call inlining, and escape analysis.
Walk
This phase runs two steps: order of evaluation decomposes complex statements into simpler ones, and desugaring transforms higher-level Go constructs, like switch or channels, into more primitive instructions or calls to the runtime.
Generic SSA
The compiler converts the IR into Static Single Assignment (SSA) form, a lower-level intermediate representation suited for machine-independent optimizations and rewrite rules.
Machine code generation
The compiler rewrites the SSA form into machine-specific variants, allocates registers, and applies more optimization passes. At the end, the assembler turns the generated instructions into machine code.

The hammer

My first idea is to replace occurrences of netip.AddrFrom16(ip.As16()) with netip.Addr{addr: ip.addr, z: netip.z6noz} as early as possible, during the “noding” process. Before that, the type checking phase prevents us from accessing unexported struct fields.

Go 1.27 introduced a convenient debug option to dump the IR of a function at interesting points during compilation:

$ GOTOOLCHAIN=go1.27.1 GOAMD64=v3 go build -a -gcflags="-d=astdump=AddrTo6Safe" .
Writing text ast output for AddrTo6Safe to AddrTo6Safe.ast
Writing html ast output for AddrTo6Safe to AddrTo6Safe.html
Writing html syntax output for AddrTo6Safe to AddrTo6Safe.syntax.html

In the HTML file, the first column shows the IR as it comes out of noding:

DCLFUNC addrto6.AddrTo6Safe ABI:ABIInternal FUNC-func(netip.Addr) netip.Addr
DCLFUNC-Dcl
. NAME-addrto6.ip Class:PPARAM Offset:0 OnStack Used netip.Addr
. NAME-addrto6.~r0 Class:PPARAMOUT Offset:0 OnStack netip.Addr
DCLFUNC-body
. IF # ipv6_safe.go:11:2
. IF-Cond
. . CALLFUNC bool
. . CALLFUNC-Fun
. . . METHEXPR addrto6.Is4 FUNC-func(netip.Addr) bool
. . . . TYPE netip.Addr Class:PEXTERN Offset:0 type netip.Addr
. . CALLFUNC-Args
. . . NAME-addrto6.ip Class:PPARAM Offset:0 OnStack Used netip.Addr
. IF-Body
. . AS # ipv6_safe.go:12:6
. . . NAME-addrto6.ip Class:PPARAM Offset:0 OnStack Used netip.Addr
. . . CALLFUNC netip.Addr
. . . CALLFUNC-Fun
. . . . NAME-netip.AddrFrom16 Class:PFUNC Offset:0 Used FUNC-func([16]byte) netip.Addr
. . . CALLFUNC-Args
. . . . CALLFUNC ARRAY-[16]byte
. . . . CALLFUNC-Fun
. . . . . METHEXPR addrto6.As16 FUNC-func(netip.Addr) [16]byte
. . . . . . TYPE netip.Addr Class:PEXTERN Offset:0 type netip.Addr
. . . . CALLFUNC-Args
. . . . . NAME-addrto6.ip Class:PPARAM Offset:0 OnStack Used netip.Addr
. RETURN # ipv6_safe.go:14:2
. RETURN-Results
. . NAME-addrto6.ip Class:PPARAM Offset:0 OnStack Used netip.Addr

In the body of the if statement, we spot the calls to the method netip.Addr.As16() and to the function netip.AddrFrom16(). Our goal is to patch them with a struct literal:

IF-Body
. AS # ipv6_safe.go:12:6
. . NAME-addrto6.ip Class:PPARAM Offset:0 OnStack Used netip.Addr
. . STRUCTLIT netip.Addr
. . STRUCTLIT-List
. . . STRUCTKEY netip.addr
. . . . DOT netip.addr netip.uint128
. . . . . NAME-addrto6.ip Class:PPARAM Offset:0 OnStack Used netip.Addr
. . . STRUCTKEY netip.z
. . . . NAME-netip.z6noz Class:PEXTERN Offset:0 unique.Handle[net/netip.addrDetail]

In noder’s reader.go, the expr() method builds the IR tree for an expression. At the end of the exprCall case, we add a call to a rewriteAddrFrom16As16() function. It takes the current node and returns the struct literal on success, or nil if the rewrite is not possible. First, we check that we have the expected pattern: a call to the netip.AddrFrom16() function with a call to the netip.Addr.As16() method as its only argument:

func rewriteAddrFrom16As16(n ir.Node) ir.Node {
    call, ok := n.(*ir.CallExpr)
    if !ok || call.Op() != ir.OCALLFUNC ||
        len(call.Args) != 1 || len(call.Init()) != 0 ||
        !isNetipFunc(call.Fun, "AddrFrom16") {
        return nil
    }
    inner, ok := call.Args[0].(*ir.CallExpr)
    if !ok || inner.Op() != ir.OCALLFUNC ||
        len(inner.Args) != 1 || len(inner.Init()) != 0 ||
        !isNetipFunc(inner.Fun, "Addr.As16") {
        return nil
    }
    x := inner.Args[0]
    // [...]
}

Then, we fetch netip.z6noz:

z6noz, err := lookupVar(ir.StaticCalleeName(call.Fun).Sym().Pkg, "z6noz")
if err != nil {
    return nil
}

And we build the struct literal:

typ := call.Type()
pos := call.Pos()
var list []ir.Node
for i, f := range typ.Fields() {
    var value ir.Node
    switch f.Sym.Name {
    case "addr":
        value = typecheck.DotField(pos, x, i)
    case "z":
        value = z6noz
    default:
        return nil
    }
    list = append(list, ir.NewStructKeyExpr(pos, f, value))
}
lit := ir.NewCompLitExpr(pos, ir.OSTRUCTLIT, typ, list)
lit.SetTypecheck(1)
return lit

Have a look at the complete patch.7 We can test it with the following commands:

$ cd src
$ ./make.bash
Building Go cmd/dist using /usr/lib/go-1.27. (go1.27.1 linux/amd64)
Building Go toolchain1 and bootstrap cmd/go (go_bootstrap) using /usr/lib/go-1.27.
Building Go toolchain2 using go_bootstrap and Go toolchain1.
Building Go toolchain3 and commands using go_bootstrap and Go toolchain2.
Checking command staleness for linux/amd64.
---
Installed Go for linux/amd64 in /home/bernat/code/free/go
Installed commands in /home/bernat/code/free/go/bin
*** You need to add /home/bernat/code/free/go/bin to your PATH.
$ export PATH=$PWD/../bin:$PATH
$ go version
go version go1.28-devel_9834516e20 Sat Sep 12 08:23:11 2026 -0700 linux/amd64
$ go test net/netip/...
ok      net/netip   0.224s
$ cd ../../go-netip-addrto6
$ go test .
ok      github.com/vincentbernat/go-netip-addrto6   0.062s

The generated code for the helper is now the shortest possible version!

// AX = input.addr.hi, BX = input.addr.lo, CX = input.z
 CMPQ    net/netip·z4(SB), CX     ; check "z" if this is an IPv4 address
 JNE     end                      ; if not, stop here
 MOVQ    net/netip·z6noz(SB), CX  ; CX = netip.z6noz
end:
 RET
// return value = Addr{hi: AX, lo: BX, z: CX}

Go maintainers are unlikely to accept this change. It relies on the internal structure of net/netip.Addr. It’s an ugly hack in the noder, whose job is to faithfully translate the type-checked AST into the IR. And it’s harder to maintain than adding a To6() method.

The screwdriver

The right place for such an optimization is the generic SSA phase. One of the last machine-independent passes is memcombine. With the appropriate debug flag, the compiler dumps the SSA form after this pass:8

$ GOTOOLCHAIN=go1.26.8 GOAMD64=v3 \
> go build -a -gcflags='-d=ssa/memcombine/dump=AddrTo6Safe' .
$ head -5 AddrTo6Safe_01__memcombine.dump
AddrTo6Safe func(netip.Addr) netip.Addr
  b2:
    (?) v1 = InitMem <mem>
    (?) v2 = SP <uintptr>
    (?) v3 = SB <uintptr>

The result of the memcombine pass follows the same structure as the assembly code for AddrTo6Safe() we looked at earlier: two stores, one move, and two loads we would like to optimize away.

; […]
  v502 = ArgIntReg <uint64> {ip+0} [0]              ; input.addr.hi
  v490 = ArgIntReg <uint64> {ip+8} [1]              ; input.addr.lo
  v466 = ArgIntReg <*netip.addrDetail> {ip+16} [2]  ; input.z
; […]
  v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
  v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
  v442 = Bswap64 <uint64> v490                      ; bswap(input.addr.lo)
  v542 = Bswap64 <uint64> v502                      ; bswap(input.addr.hi)
  v161 = Store <mem> {uint64} v22 v542 v23          ; a16[:8] = bswap(hi)
  v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)

  v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
  v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16

  v415 = OffPtr <*byte> [8] v285                    ; &addr[8]
  v416 = Load <uint64> v285 v286                    ; addr[:8]
  v299 = Bswap64 <uint64> v416                      ; output.addr.hi
  v174 = Load <uint64> v415 v286                    ; addr[8:]
  v39  = Bswap64 <uint64> v174                      ; output.addr.lo
; […]

Each line features a value identifier (v442), an operation with its type (Bswap64 <uint64>), and its arguments (v490).9 Values are the basic building blocks of SSA and are defined exactly once. Square brackets enclose integer parameters ([8]) and curly braces contain auxiliary arguments ({netip.addr}). Operations writing to memory produce a new memory state. Every memory operation takes the current state as its last argument, which keeps them in order.

On paper

Let’s focus on output.addr.lo, aka v39:

  v490 = ArgIntReg <uint64> {ip+8} [1]              ; input.addr.lo
  v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
  v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
  v442 = Bswap64 <uint64> v490                      ; bswap(input.addr.lo)
  v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
  v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
  v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
  v415 = OffPtr <*byte> [8] v285                    ; &addr[8]
  v174 = Load <uint64> v415 v286                    ; addr[8:]
  v39  = Bswap64 <uint64> v174                      ; output.addr.lo

To simplify this code, we could apply three rewriting rules:

  1. The first one adds a shortcut when loading through a move: (Load (OffPtr [o] p) (Move p src mem)) => (Load (OffPtr [o] src) mem). This matches v174 with its arguments v415 and v286 and creates a new value v600:

      v490 = ArgIntReg <uint64> {ip+8} [1]              ; input.addr.lo
      v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
      v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v442 = Bswap64 <uint64> v490                      ; bswap(input.addr.lo)
      v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
      v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
      v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
      v415 = OffPtr <*byte> [8] v285                    ; &addr[8]
      v600 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v174 = Load <uint64> v600 v282                    ; a16[8:]
      v39  = Bswap64 <uint64> v174                      ; output.addr.lo
    
    
  2. The second one simplifies a load following a store: (Load p (Store p x _)) => x. The load is forwarded: the stored value replaces it and no memory access remains. It matches v174. It notices that v600 and v173 are the same address and replaces v174 with a copy of v442:

      v490 = ArgIntReg <uint64> {ip+8} [1]              ; input.addr.lo
      v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
      v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v442 = Bswap64 <uint64> v490                      ; bswap(input.addr.lo)
      v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
      v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
      v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
      v415 = OffPtr <*byte> [8] v285                    ; &addr[8]
      v600 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v174 = Copy <uint64> v442                         ; bswap(input.addr.lo)
      v39  = Bswap64 <uint64> v174                      ; output.addr.lo
    
    
  3. The last step cancels the two byte swaps: (Bswap64 (Bswap64 x)) => x. v39 becomes a copy of v490:

      v490 = ArgIntReg <uint64> {ip+8} [1]              ; input.addr.lo
      v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
      v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v442 = Bswap64 <uint64> v490                      ; bswap(input.addr.lo)
      v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
      v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
      v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
      v415 = OffPtr <*byte> [8] v285                    ; &addr[8]
      v600 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v174 = Copy <uint64> v442                         ; bswap(input.addr.lo)
      v39  = Copy <uint64> v490                         ; output.addr.lo = input.addr.lo
    
    

If we ignore the values not needed to compute v39, only this SSA form remains:

  v490 = ArgIntReg <uint64> {ip+8} [1]  ; input.addr.lo
  v39  = Copy <uint64> v490             ; output.addr.lo = input.addr.lo

Let’s switch to output.addr.hi, aka v299:

  v502 = ArgIntReg <uint64> {ip+0} [0]              ; input.addr.hi
  v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
  v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
  v542 = Bswap64 <uint64> v502                      ; bswap(input.addr.hi)
  v161 = Store <mem> {uint64} v22 v542 v23          ; a16[:8] = bswap(hi)
  v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
  v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
  v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
  v416 = Load <uint64> v285 v286                    ; addr[:8]
  v299 = Bswap64 <uint64> v416                      ; output.addr.hi

To optimize it away, we also apply three rewriting rules:

  1. The first one also adds a shortcut when loading through a move, but without an offset: (Load p (Move p src mem)) => (Load src mem). This rewrites v416 to use arguments from v286:

      v502 = ArgIntReg <uint64> {ip+0} [0]              ; input.addr.hi
      v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
      v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v542 = Bswap64 <uint64> v502                      ; bswap(input.addr.hi)
      v161 = Store <mem> {uint64} v22 v542 v23          ; a16[:8] = bswap(hi)
      v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
      v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
      v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
      v416 = Load <uint64> v22 v282                     ; a16[:8]
      v299 = Bswap64 <uint64> v416                      ; output.addr.hi
    
    
  2. The second rule forwards a value stored one step earlier, skipping over a store to another address: (Load p (Store q _ (Store p x _))) => x. This matches v416: x is v542, p is v22 (&a16), q is v173 (&a16[8]), and p and q do not overlap for uint64.

      v502 = ArgIntReg <uint64> {ip+0} [0]              ; input.addr.hi
      v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
      v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v542 = Bswap64 <uint64> v502                      ; bswap(input.addr.hi)
      v161 = Store <mem> {uint64} v22 v542 v23          ; a16[:8] = bswap(hi)
      v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
      v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
      v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
      v416 = Copy <uint64> v542                         ; bswap(input.addr.hi)
      v299 = Bswap64 <uint64> v416                      ; output.addr.hi
    
    
  3. The third rule cancels two byte swaps: (Bswap64 (Bswap64 x)) => x. v299 becomes a copy of v502:

      v502 = ArgIntReg <uint64> {ip+0} [0]              ; input.addr.hi
      v22 = LocalAddr <*[16]byte> {netip.a16} v2 v1     ; &a16
      v173 = OffPtr <*byte> [8] v22                     ; &a16[8]
      v542 = Bswap64 <uint64> v502                      ; bswap(input.addr.hi)
      v161 = Store <mem> {uint64} v22 v542 v23          ; a16[:8] = bswap(hi)
      v282 = Store <mem> {uint64} v173 v442 v161        ; a16[8:] = bswap(lo)
      v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
      v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
      v416 = Copy <uint64> v542                         ; bswap(input.addr.hi)
      v299 = Copy <uint64> v502                         ; output.addr.hi = input.addr.hi
    
    

If we remove the values not used to compute v299, we get this SSA form:

  v502 = ArgIntReg <uint64> {ip+0} [0] ; input.addr.hi
  v299 = Copy <uint64> v502            ; output.addr.hi = input.addr.hi

In practice

Most of these rules already exist in generic.rules. They use conditions to validate their context: ssa.IsSamePtr() for the same address, ssa.Disjoint() for addresses that do not overlap. The rule forwarding a stored value to a load already exists with three variants looking through several other stores. Here are the two we need:

(Load <t1> p1 (Store {t2} p2 x _))
    && ssa.IsSamePtr(p1, p2)
    && copyCompatibleType(t1, x.Type)
    && t1.Size() == t2.Size()
    => x
(Load <t1> p1 (Store {t2} p2 _ (Store {t3} p3 x _)))
    && ssa.IsSamePtr(p1, p3)
    && copyCompatibleType(t1, x.Type)
    && t1.Size() == t3.Size()
    && ssa.Disjoint(p3, t3, p2, t2)
    => x

Go 1.27 added the rule loading through a move with CL 748200 to fix issue #77720:

(Load <t1> op1:(OffPtr [o1] p1) move:(Move [n] p2 src mem))
    && o1 >= 0 && o1+t1.Size() <= n && ssa.IsSamePtr(p1, p2)
    && !ssa.IsVolatile(src)
    => @move.Block (Load <t1> (OffPtr <op1.Type> [o1] src) mem)

It lacks a variant without an offset:

(Load <t1> p1 move:(Move [n] p2 src mem))
    && p1.Op != ssaop.OpOffPtr
    && t1.Size() <= n && ssa.IsSamePtr(p1, p2)
    && !ssa.IsVolatile(src)
    => @move.Block (Load <t1> (OffPtr <p1.Type> [0] src) mem)

There is no generic rule to cancel two byte swaps, but the AMD64 lowering pass includes this rule:

(BSWAP(Q|L) (BSWAP(Q|L) p)) => p

After switching to Go’s development branch and adding the missing rule, the generated assembly code is worse than with Go 1.26.8, even though our additional rule slightly improves the situation at the end:

// AX = input.addr.hi, BX = input.addr.lo, CX = input.z
; Push the stack (16 bytes):
;    0(SP) a16 [16]byte
 PUSHQ   BP
 MOVQ    SP, BP
 SUBQ    $16, SP

 CMPQ    net/netip·z4(SB), CX       ; check "z" if this is an IPv4 address
 JNE     end                        ; if not, stop here

; The four forwarded bytes: the low half of input.addr.lo is taken apart and
; put back together in registers
 MOVQ    BX, DX                     ; DX = input.addr.lo
 SHRQ    $24, BX                    ; BX = input.addr.lo >> 24
 MOVQ    DX, SI                     ; SI = input.addr.lo
 SHRQ    $16, DX                    ; DX = input.addr.lo >> 16
 MOVQ    SI, DI                     ; DI = input.addr.lo, kept for the pack
 SHRQ    $8, SI                     ; SI = input.addr.lo >> 8
 MOVBLZX DIB, R8                    ; R8 = byte(input.addr.lo)
 MOVBLZX SIB, SI                    ; SI = byte(input.addr.lo >> 8)
 SHLQ    $8, SI
 ORQ     R8, SI                     ; SI = two low bytes of input.addr.lo
 MOVBLZX DL, DX                     ; DX = byte(input.addr.lo >> 16)
 SHLQ    $16, DX
 ORQ     SI, DX
 MOVBLZX BL, BX                     ; BX = byte(input.addr.lo >> 24)
 SHLQ    $24, BX
 ORQ     DX, BX                     ; BX = input.addr.lo & 0xffffffff

; Pack: byteorder.BEPutUint64(a16[:8], input.addr.hi)
;       byteorder.BEPutUint64(a16[8:], input.addr.lo)
 MOVBEQ  AX, net/netip·a16(SP)
 MOVBEQ  DI, net/netip·a16+8(SP)

; The four other bytes of input.addr.lo, read one by one from a16
 MOVBLZX net/netip·a16+11(SP), DX   ; a16[11]
 SHLQ    $32, DX
 ORQ     DX, BX
 MOVBLZX net/netip·a16+10(SP), DX   ; a16[10]
 SHLQ    $40, DX
 ORQ     DX, BX
 MOVBLZX net/netip·a16+9(SP), DX    ; a16[9]
 SHLQ    $48, DX
 ORQ     DX, BX
 MOVBLZX net/netip·a16+8(SP), DX    ; a16[8]
 SHLQ    $56, DX

; output.z = netip.z6noz
 MOVQ    net/netip·z6noz(SB), CX
; output.addr.hi = byteorder.BEUint64(a16[:8])
 MOVBEQ  net/netip·a16(SP), AX
; output.addr.lo assembled from the previous steps
 ORQ     DX, BX

end:
 LEAVEQ
 RET
// return value = Addr{hi: AX, lo: BX, z: CX}

The rule loading through a move, added in Go 1.27, introduced this regression.

Out of order

Let’s not give up now! In reality, the rewriting rules run before memcombine, notably in the late opt pass. At this point, the inlined versions of BEPutUint64() and BEUint64() still expand to sixteen byte stores and sixteen byte loads, matching their source code:

func BEUint64(b []byte) uint64 {
    _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
    return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
        uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
}

Let’s follow two bytes of output.addr.lo: addr[15] and addr[11]. Here is a simplified SSA form before late opt:

  v273 = Trunc64to8 <byte> v490                     ; byte(input.addr.lo)
  v226 = Trunc64to8 <byte> v225                     ; byte(input.addr.lo >> 32)
; […]
  v235 = Store <mem> {byte} v233 v226 v223          ; a16[11] = byte(lo >> 32)
  v247 = Store <mem> {byte} v245 v238 v235          ; a16[12] = …
  v259 = Store <mem> {byte} v257 v250 v247          ; a16[13] = …
  v271 = Store <mem> {byte} v269 v262 v259          ; a16[14] = …
  v282 = Store <mem> {byte} v280 v273 v271          ; a16[15] = byte(lo)
  v285 = LocalAddr <*[16]byte> {netip.addr} v2 v282 ; &addr
  v286 = Move <mem> {[16]byte} [16] v285 v22 v282   ; addr = a16
; […]
  v433 = OffPtr <*byte> [15] v285                   ; &addr[15]
  v435 = Load <byte> v433 v286                      ; addr[15]
  v479 = OffPtr <*byte> [11] v285                   ; &addr[11]
  v481 = Load <byte> v479 v286                      ; addr[11]

The first rule loads through the move: (Load (OffPtr [o] p) (Move p src mem)) => (Load (OffPtr [o] src) mem). It matches both loads, which now read a16 with the memory state before the copy:

  v600 = OffPtr <*byte> [15] v22 ; &a16[15]
  v435 = Load <byte> v600 v282   ; a16[15]
  v601 = OffPtr <*byte> [11] v22 ; &a16[11]
  v481 = Load <byte> v601 v282   ; a16[11]

The second rule shortcuts a load following a store: (Load p (Store p x _)) => x. It matches v435, as v282 stores a16[15]. It does not match v481: v235 stores a16[11] four stores earlier in the chain, while the variants of this rule look through three stores at most.

  v435 = Copy <byte> v273        ; byte(input.addr.lo)
  v601 = OffPtr <*byte> [11] v22 ; &a16[11]
  v481 = Load <byte> v601 v282   ; a16[11]

The same happens to the other bytes: the rule forwards the four bytes stored last, a16[12] to a16[15]. The twelve other loads now read a16 instead of addr.

BEUint64() becomes a chain of Or64, each one adding a byte shifted into place. memcombine is a pass written in Go, not a set of rewrite rules. It starts from the last Or64 of the chain and collects up to eight terms. If each term is a byte load, extended to 64 bits and shifted, and if the eight loads read consecutive addresses from the same pointer with the same memory state, it replaces the whole chain with a single 64-bit load and a byte swap. Otherwise, it tries again with four, then two terms, and from each intermediate Or64. Here is the loop checking each term in a simplified version of combineLoads():

for i := int64(0); i < n; i++ {
    v := a[i]
    shift := int64(0)
    if v.Op == shiftOp {
        v, shift = peelShift(v)
    }
    if v.Op != extOp {
        return false
    }
    load := v.Args[0]
    if load.Op != ssaop.OpLoad {
        return false
    }
    if load.Args[1] != mem {
        return false
    }
    p, off := splitPtr(load.Args[0])
    if p != base {
        return false
    }
    r[i] = LoadRecord{load: load, offset: off, shift: shift}
}

For output.addr.hi, the eight loads read a16 with the same memory state v282:

  v13  = Load <byte> v22 v282               ; a16[0]
  v530 = Load <byte> v14 v282               ; a16[1]
  v488 = Load <byte> v504 v282              ; a16[2]
  v405 = Load <byte> v537 v282              ; a16[3]
  v385 = Load <byte> v397 v282              ; a16[4]
  v361 = Load <byte> v373 v282              ; a16[5]
  v196 = Load <byte> v63 v282               ; a16[6]
  v432 = Load <byte> v315 v282              ; a16[7]
  v319 = ZeroExt8to64 <uint64> v432         ; uint64(a16[7])
  v329 = ZeroExt8to64 <uint64> v196         ; uint64(a16[6])
  v330 = Lsh64x64 <uint64> [true] v329 v138 ; uint64(a16[6]) << 8
  v331 = Or64 <uint64> v319 v330            ; a16[7] | a16[6] << 8
; […] same for a16[5] to a16[1]
  v401 = ZeroExt8to64 <uint64> v13          ; uint64(a16[0])
  v402 = Lsh64x64 <uint64> [true] v401 v55  ; uint64(a16[0]) << 56
  v403 = Or64 <uint64> v402 v391            ; | a16[0] << 56 = output.addr.hi

memcombine merges them into one load and a swap:

  v286 = Load <uint64> v22 v282   ; a16[:8]
  v285 = Bswap64 <uint64> v286    ; output.addr.hi

For output.addr.lo, here is the chain memcombine sees after late opt:

  v436 = ZeroExt8to64 <uint64> v273 ; addr[15], forwarded
  v448 = Or64 <uint64> v436 v447    ; | addr[14] << 8, forwarded
  v460 = Or64 <uint64> v459 v448    ; | addr[13] << 16, forwarded
  v472 = Or64 <uint64> v471 v460    ; | addr[12] << 24, forwarded
  v484 = Or64 <uint64> v483 v472    ; | a16[11] << 32, loaded
  v496 = Or64 <uint64> v495 v484    ; | a16[10] << 40, loaded
  v508 = Or64 <uint64> v507 v496    ; | a16[9] << 48, loaded
  v520 = Or64 <uint64> v519 v508    ; | a16[8] << 56, loaded

From v520, four of the eight terms are forwarded bytes, not loads from memory, and memcombine can’t combine them. It doesn’t merge the four remaining loads either, as they sit on top of the forwarded bytes.

Back in order

In summary, the rewriting rules run too early to be effective. A quick workaround exists: run an earlier round of memcombine before late opt. After this change, the generated code for the helper is back to the shortest possible version:

// AX = input.addr.hi, BX = input.addr.lo, CX = input.z
 CMPQ    net/netip·z4(SB), CX     ; check "z" if this is an IPv4 address
 JNE     end                      ; if not, stop here
 MOVQ    net/netip·z6noz(SB), CX  ; CX = netip.z6noz
end:
 RET
// return value = Addr{hi: AX, lo: BX, z: CX}

And the benchmark confirms it! ✌️

goos: linux
goarch: amd64
pkg: github.com/vincentbernat/go-netip-addrto6
cpu: AMD Ryzen 5 5600X 6-Core Processor
                   │   Go 1.26.8    │             Our branch              │
                   │     sec/op     │    sec/op     vs base               │
AddrTo6/safe          6.5470n ±  0%   0.8944n ± 4%  -86.34% (p=0.002 n=6)
AddrTo6/unsafe        0.9071n ±  3%   0.8682n ± 1%   -4.28% (p=0.002 n=6)
AddrTo6/builtin       0.8871n ±  2%   0.8785n ± 1%        ~ (p=0.310 n=6)

Next steps

I think Go maintainers would reject this change because of the additional memcombine pass. Instead, I plan to publish this blog post and bring up the subject again as a follow-up to issue #54365. Either the sheer complexity and the Go 1.27 regression convince the maintainers that adding a To6() method is simpler and more efficient, or they advise me on how to move forward. Either way, digging into this subject taught me a lot about the Go compiler! ⚙️


  1. IPv4-mapped IPv6 addresses let you handle only IPv6 addresses in your code and keep conversions at a few well-defined boundaries. I use them in Akvorado. ↩

  2. This is not strictly equivalent. The zero value becomes :: while it would make more sense to leave it untouched. ↩

  3. To avoid catastrophic bugs when netip.Addr’s layout changes, you must add tests to detect it. This is more dangerous if you put this code in a package. Either ask users to run the tests themselves, or detect the change at run time and panic. Hiding this optimization behind a build tag would make users aware of this potential trap. ↩

  4. I produced the assembly code with GOTOOLCHAIN=go1.26.8 GOAMD64=v3 ./go-asm '\.asm' from the companion repository, then edited it a bit to keep this article from turning into an endless rabbit hole. Due to an unfortunate sequence of events, we switch between versions: Go 1.27 introduced a regression that blurs the point of this article. ↩

  5. The function is short enough for the compiler to inline it, so the assembly code may vary depending on the surrounding code. ↩

  6. MOVBEQ requires GOAMD64=v3, matching the x86-64-v3 microarchitecture from 2013. Otherwise, the compiler translates BEPutUint64() to BSWAPQ+MOVQ and BEUint64() to MOVQ+BSWAPQ. ↩

  7. When the call is used as a statement, a struct literal alone is invalid. The patch then turns it into _ = netip.Addr{…}. ↩

  8. The compiler can also write an HTML file with the result of each pass:

    $ GOSSAFUNC=AddrTo6Safe \
    > GOTOOLCHAIN=go1.26.8 \
    > GOAMD64=v3 go build -a .
    dumped SSA for AddrTo6Safe,1 to ./ssa.html
    
    

    ↩

  9. Source line numbers in parentheses follow value identifiers, but I removed them from the examples. The SSA form also features branches, but we don’t need them to understand our optimizations. ↩

15:28

Link [Scripting News]

BTW now that I have Frontier back and am using it for most of my work, I'm having a lot more fun writing my blog. Can you tell??

The climate will kill you [Scripting News]

Great scene from The West Wing.

CJ and Josh, on the street outside the White House.

The president was hiding serious disease, and it’s about to come out. Back in those days such a revelation meant the president has no chance of being re-elected.

Anyway, the kid has an idea and starts explaining, and as he does CJ laughs. The kid asks what’s so funny.

She tells him about a scene in Butch Cassidy and the Sundance Kid starring Paul Newman as Butch and Robert Redford as the kid.

They’re cornered, on a cliff, preparing to jump into a river far below. Redford says he can’t swim. Newman says it doesn’t matter, the fall is going to kill you.

Okay so why mention this now?

I hear there’s a monster heat wave in California. Over 90F every day for two weeks. That’s unusual for October IIRC.

We’re all fretting about AI killing us.

Imagine CJ laughing. Haha she says, so you’re worried about AI are ya? It’s the climate that’s going to kill you.

Josh would correct her. “Us. It’s going to kill us.”

CJ tells him to go away she needs to be an adult right now.

Twitter as a blog platform [Scripting News]

Twitter with its relaxed character limit is getting pretty close to being a blogging platform. I wrote the first draft of this post on my iPad, that's how useful it is.

Imagine if a real blog platform created a super simple way to write posts, no need for Gutenberg, just Markdown and that would be optional. Default would be a normal wizzy text editor, bold, italic, using a standard user interface.

Add web links and a reply structure, open source, nice API and developer program to shine the light on the cool editors independent devs were creating, and all of a sudden a bunch of new blog platforms emerge, and get this — they all work perfectly with each other because of the common foundation and API. The web of social media. It would be exciting and would pressure the other social media apps to peer with this truly open system.

It would change the freaking world I tell you. And it would be so wonderful with AI. No need for the cheesy chat UI. A real revolution. Undo all the craptastic silos that strangled the web?

It would be pretty damn easy imho, fwiw, ymmv, mmlm.

PS: MMLM is an acronym for My Mother Loves Me. Anticipates flameage. Okay you don't like me, but my mother loves me.

14:28

Upcoming Public Appearances, 2026/27 [Charlie's Diary]

Upcoming fixtures

My public visibility took a nose-dive in 2020 (can't think why!) and didn't really begin to pick up again until 2024, due to multiple reasons, principally COVID19 and some chronic health issues. The health issues are now under control and I'm planning on coming out of my shell a bit more over the next year.

My definite fixtures over the next year or so include 5 SF/F conventions in various countries, none of them the United States but one of them on the same continent:

4-8 November 2026, Festival 42, Barcelona, The sixth 42 Barcelona Festival of Fantastic Genres (theme: "Facing Our Fears.")

I'm one of the guests at Festival 42 (although I'm not listed on their web page yet).

My Festival 42 events so far:

1pm, Saturday 7th: Author interview (with Cristina Jurado)

7pm, Saturday 7th: Panel discussion with international authors (details TBA)

12am, Sunday 8th, Panel discussion (details TBA)

11 November 2026, Author Event, Gigamesh Bookshop, author event (reading/interview, TBA). Gigamesh is Barcelona's SF/F specialist bookshop and they've kindly arranged an event for me: I'll add details here when I've got them.

12-15 November 2026, Sui Generis Festival, Madrid (theme: "Neo-Fiction: New Narratives for a Contested Present.") Sui Generis Festival: "We use the term neo-fiction to describe narrative practices that emerge from a world shaped by the ecological crisis, digital culture, artificial intelligence, and social fragmentation."

My Sui Generis events so far:

6pm, Friday 13th November: Panel discussion, "The New Indifferent God: Cosmic Horror and Artificial Intelligence"

7:30pm, Saturday 14th November: Panel discussion, "Masters of the Impossible"

26-29 March 2027, Unconfined, the UK Eastercon, Glasgow.

(C'mon, you didn't expect me to miss this one, did you?)

2-6 September 2027 Montreal Worldcon

Montréal 2027 is a North American worldcon outside the United States! So of course I'm planning on going.

18-21 November 2027, Polcon 2027

Polcon 2027, The Polish national SF convention will be held in Katowice in 2027, and they've invited me as a special guest. Details to be added here when I've got them.

10:07

Steinar H. Gunderson: Decompilation patterns part 1: Anti-CSE [Planet Debian]

There's a lot of¹ interest in decompilation these days; taking some old piece of software (usually a game) and try to reconstruct C that does the same thing. (This is often for modding purposes, but also often for understanding, or just as a personal challenge.) An often sought-after property is matching; that the decompiled C produces 100% the same bytes as the original after compilation. (This means you'll need to find the original compiler and build flags, of course.) Typically, one starts with the disassembled code and runs m2c on it, which then spits out some C that is fairly close to the original, but rarely produces a match without further cleanup. (It is usually much closer to a match than what Ghidra or Hex-Rays would output; Ghidra is made for ease of understanding, not matching, so it tends to simplify a lot.)

Cleaning up the code for matching has two main advantages:

  1. It usually makes the source easier to understand, and
  2. Matching is a very strong argument (though not infallible) that the decompilation is actually correct.

Like with pretty much any other field these days, there is a lot of interest in giving this task to some LLM, which means there's now a lot of decompilations around that match (fitting #2 perfectly) and completely fail #1. :-) Anyway, at some point, I learned that there is almost no public documentation on the cleaning-up-for-matching part; it's all folklore and people rediscovering the same patterns. So I thought I'd do something about that².

My general goal is to write up one decompilation pattern a day (focusing on what I know best, namely GCC on MIPS), with some example. Most of them will be very simple; some are much less obvious. I'll keep going until I run out of ideas. None are formally named, so I'll just invent a name. So here goes part one:

  temp_v0_5 = g_state.players[1].unk2;
  if ((temp_v0_5 != 7) && (temp_v0_5 != 0)) {

What happened here is usually that the compiler has done common subexpression elimination; someone wrote the same thing twice and then the compiler decided to just calculate it once and put it into a temporary variable. (Or the programmer did, in which case you would be wise to leave it alone! Getting into the original programmer's head is part of the challenge.) This will often affect register allocation and/or stack layout, so to get a better match, you could try doing CSE in reverse:

  if (g_state.players[1].unk2 != 7 && g_state.players[1].unk2 != 0) {

Nothing is mechanical; it may help or it may not. (It may also appear to hurt at first and then help later, or vice versa.) But anti-CSE is generally very common and you'll want to have it in your arsenal.

¹ Well, all is relative.

² Reluctantly, with the understanding that the LLMs might also learn from it.

Getting critical [Seth's Blog]

I’d heard a lot about critical theory but didn’t really understand what was being talked about. The Frankfurt School of the 1930s wasn’t really a school. It was philosophers and academics who were arguing with each other about how the world actually works — and about why people keep accepting arrangements that hurt them. And like many things called ‘theories’, it’s easy to be confused about what’s actually being said.

Their core insight: most of what we call “normal” was designed by someone, for a reason, and that reason might not be your reason.

Traditional science tries to describe the world as it is. Critical theory asks: “who benefits from the world being described that way?”

When an economist says “the market determines wages,” that sounds neutral. But it isn’t. It’s a choice about what to measure and what to ignore. It’s a story. And stories can be told differently.

Pioneers like Horkheimer, Adorno and Marcuse (followed and reworked by Habermas)— looked at modern industrial society and noticed that Reason, with a capital R, which was supposed to liberate us, had turned into a tool of control. The Enlightenment promised freedom. What we got instead was efficiency.

They called it instrumental reason: the habit of thinking about how to do things without ever asking whether we should.


Reification: when the made-up becomes the inevitable

The word that unlocks the concept is reification. Human social relations and systems appear as natural, thing-like and outside our control because we end up treating them as if they were rocks. Permanent. Objective. Not up for discussion.

“That’s just how business works.”
“That’s just how things are.”
“You can’t fight human nature.”

Each of those sentences is a reification. Someone built a thing. Now they’re insisting it can’t be unbuilt.

Critical theory’s job is to thaw what has been frozen. To remind us that the rules we’re following were written by someone, and can be rewritten.

Systems are often invisible, and they hide behind normal. When in doubt, consider the system that we’ve assumed is the only way.


The Frankfurt School wasn’t just academic. They were trying to explain how otherwise reasonable people went along with war, consumerism and conformity.

Their answer: the system shapes what feels obvious. What feels like common sense. What feels like “just being realistic.”

Which means the most important thing a marketer, a leader, a maker can do is ask: am I solving a real problem, or am I reinforcing a convenient story about what people are supposed to want?


When someone points at a system and describes the interests of those who supported it, two things can happen.

The first: curiosity. Who built it? Why? What would a different version look like?

The second: discomfort.

If you’ve organized your identity around the map being true — if your status, your choices, your self-image all depend on the current arrangement being natural and inevitable — then someone questioning the map isn’t being philosophical.

They’re threatening you.

The fact that your status feels threatened is a self-reflective tell. Of course it feels threatened. That’s what the system wants you to feel.

“The market rewards hard work” isn’t just an economic claim for a lot of people. It’s a story about why they deserve what they have. Poke that story and you’re not critiquing capitalism. You’re telling them their success was partly luck, partly structural, partly the result of systems that excluded others.

Most of the fury isn’t intellectual. It’s tribal.

Critical theory became associated with universities and with certain political movements. So defending against it became a marker of identity for the other tribe. You’re not critiquing the argument — you’re signaling which team you’re on.

That’s ironic, right? Because “this belief is really about status and belonging, not truth” is exactly the kind of thing critical theory would say about the people attacking critical theory.

It’s tempting to announce that some questions aren’t allowed.

Those are usually the most important questions.

03:07

Why is there no Windows hot-patching support for other architectures like 32-bit ARM and MIPS? [The Old New Thing]

Windows hot-patching supports x86-32, x86-64, 64-bit ARM, and Itanium, but why not the other architctures like 32-bit ARM, MIPS, Alpha AXP, PowerPC, and SH-3?

These architectures fall into two buckets.

MIPS, Alpha AXP, PowerPC, and SH-3 all predate hot-patching, so they naturally couldn’t conform to rules that didn’t exist yet.

Meanwhile, 32-bit ARM support was introduced¹ to Windows in the Windows 8 era, and at that time, hot-patching had already existed. But there were no provisions for hot-patching 32-bit ARM binaries because hot-patching is a Windows Server feature. Since there was no version of Windows Server for 32-bit ARM, there was no need to support hot-patching on 32-bit ARM binaries.

¹ More accurately, re-introduced, since Windows CE supported 32-bit ARM as well.

The post Why is there no Windows hot-patching support for other architectures like 32-bit ARM and MIPS? appeared first on The Old New Thing.

00:21

RSS tip #2 [Scripting News]

People really liked the RSS tip I posted the other day, so here's another one, also related to the channel-level title element.

Consider the screen shot below, and the Thinking about... blog.

Part of a blogroll with limited info about a great blogger, Timothy Snyder.

I can't tell from the title who's speaking, whereas with the two feeds above it I know it's Brian Lehrer and Kottke, and below, an excellent local site called The Overlook.

Now it's a shame that it doesn't say who's doing the thinking because it's someone who is on a roll right now, historian Timothy Snyder. So the title pretty much has to say his name, if it's going to look good in a list of other bloggers.

BTW, just listened to Snyder on the Scott Galloway podcast, another thinker who I've just started listening to. With the two of them together, you will learn a lot.

So remember the title sometimes is the only handle a reader has to figure out what it is. Choose it carefully and make sure it looks good in a tight space like the one shown below.

If you have questions about how to make your RSS 2.0 feeds work better, please add note below. If I don't know maybe someone else does.

Saturday, 03 October

23:49

Prairieland protesters [Richard Stallman's Political Notes]

In the trial of the Prairieland protesters, the prosecutor withheld crucial physical evidence from the defense lawyers until the trial. This stopped the defense lawyers from presenting expert witnesses to to support the factual point that Benjamin Song's shot was not aimed at anyone, and hit a thug after a ricochet off the floor.

The corrupter encourages his followers to lie and cheat, not just once but persistently.

Effect of elevated screen exposure [Richard Stallman's Political Notes]

A metastudy confirms that more use of screens by children increases the likelihood of various academic and emotional problems.

Hormone-disrupting herbicide [Richard Stallman's Political Notes]

The hormone-disrupting herbicide atrazine is contaminating nearly 25% … of all continental US rivers at levels that can harm wildlife and may threaten human health, a new review of federal records has found.

Those waterways cover over 20% of surface drinking water intakes across the country, and over 675,000 private and commercial wells.

Satellite imagery shows devastation [Richard Stallman's Political Notes]

Satellite photos show the extent of devastation in Gaza, both before and (continuing) after the "cease fire".

Virginia Woolf play [Richard Stallman's Political Notes]

Facebook and Instagram reject advertisements with text that hints at political issues such as civil rights and women's rights.

I have a hunch that their filters are more forgiving for right-wing political issues. Is there any factual information about this?

Planetary boundaries [Richard Stallman's Political Notes]

*New report shows key indicators of Earth’s health are "outside the safe operating space" as climate disaster looms* (and starts).

Meanwhile, suppression of the discussion of global heating and its dangers is heavier than ever. This must be due to the wrecker's intense support for fossil fuel companies.

Zohran Mamdani's sweeping plan [Richard Stallman's Political Notes]

Mayor Mamdani proposes to discourage antisemitism by means of increased security funding for synagogues, an increased focus on folding Jewish history into public school education, and a pledge to highlight important Jewish cultural and religious sites.

It is wrong for public education to promote any religion, or religion in general. But education about religions is legitimate and admirable.

Conservation efforts [Richard Stallman's Political Notes]

Many kinds of insects are used by or beneficial for various species of vertebrates.

Europe’s anti-corruption monitoring body [Richard Stallman's Political Notes]

Magats have withdrawn the US from the Council of Europe’s anti-corruption monitoring body, saying it *no longer serves US interests.*

Why would the corrupter want to support a group whose aim is to reduce corruption?

Hunter Valley Operations [Richard Stallman's Political Notes]

The New South Wales Independent Planning Commission is supposed to judge (among other things) whether the income of a coal mine outweighs the climate and environmental damage it will cause. It has just decided to let an enormous coal mine continue operating by placing short-term gains above long-term survival.

*Australia is facing a crisis of confidence in its democratic institutions. It is a crisis brought on by the betrayal of the people by those institutions – and there is no greater betrayal than this failure to face up to the climate emergency and protect the community from its worst consequences.*

In effect, governments of various parties have allowed planet roaster interests to shape the institutions that are supposed to insist on decarbonization so that they will surrender whenever the task requires disappointing the rich.

23:35

Link [Scripting News]

Once again the app that bridges from scripting.com to daveverse.org went down for a couple of days. Just noticed it, kicked the server to get it running again, and all is good, except a couple of pieces show up today that were really posted on Thursday or Friday. The problem as often is the case goes back to PagePark, the server that runs my Heroku-like server crashed and it doesn't work well from then on, even after it's automatically restarted. Then the thing to do is to reboot the server. It doesn't happen often but when it does it makes the server behave badly. It's up and running again. One day I'll get a few days to fix the problems in PP.

16:42

Link [Scripting News]

2001: "Like a boat, a human being has integrity if he or she is what he or she appears to be. That's why integrity commands us to disclose conflicts, so that what we say, and who we appear to be, are in synch. Change the appearance if necessary."

Link [Scripting News]

If you're psyched about what the polls say about the midterms, remember that Hillary Clinton was the overwhelming favorite to win in 2016, based on polling.

14:07

GNU Health as a solution for Europe digital change - Italy Digi-CHange conf [Planet GNU]

During the days 6-9 of October will take place the Digi-Change conference in Turin, Piedmont region, Italy.

Digi-CHange promotes the digital transformation of medical and nursing education, introducing an inter-professional model that connects educators, clinicians, engineers, and industry innovators. Aligned with the Erasmus+ Capacity Building in Higher Education priority of Digital Transformation, Digi-Change strengthens the capacity of universities in Georgia, Ukraine, and EU partner countries to modernize curricula and foster digital competencies for a sustainable, innovation-ready healthcare workforce.

In the first session, Luis Falcón will deliver a keynote presentation about the social impact of GNU Health and Libre/Free Software is having in the public healthcare system, and how it can be used in the context of Telemedicine to provide meaningful and timely healthcare access to remote areas in Europe. Other topics will be building large federated health networks, privacy, and the human factor in healthcare, medical genetics and precision medicine. Dr. Falcon will address the risks of some emerging technology (eg, LLM) poses at different levels (individual, clinical practice, cultural & scientific community and the environment), and ethical alternatives in eHealth and decision support (DSS)

During this three-day “train-the-trainers” conference, physicians, nurses and other health professionals from Ukraine, Georgia and Latvia will join colleagues from Italy, France, Austria and other European countries to exchange knowledge and experiences.

In the topic of Telemedicine, Alberto Lazzero will address National records and cross-border cooperation among France and Italy. Prof. Dr. Alberto Lazzero is the Head of Telemedicine, Hospital of Briançon (France). He is an specialist in telemedicine for patients in mountain and cross‑border areas.

The conference will be in Turin, onsite, and will count with experts on diagnostic imaging, cancer and bioinformatics, among others. There will be visits to the Istituto Neurologico “Carlo Besta” (Milan) and INOC (National Oncologic Institute Candiolo).

Looking forward to meet our colleagues from Europe in Italy and share the experiences and impact of Free/Libre software in healthcare and our society.

Original post:

https://my.gnusol ... thumbnail_id=2272

13:21

Pluralistic: Economic probabilities for our grandchildren (03 Oct 2026) [Pluralistic: Daily links from Cory Doctorow]

->->->->->->->->->->->->->->->->->->->->->->->->->->->->-> Top Sources: None -->

Today's links

  • Economic probabilities for our grandchildren: If only data centers would stop hogging all the copper.
  • Hey look at this: Delights to delectate.
  • Object permanence: RIAA v P2P; Infoseek's anti-terror brain-scanner; Copyrighting every phone number; Carving pumpkins with cookie-cutters; Polish women go on strike; Latent labor in material world; Hitler's meth; "Flying Saucers Are Real!"; Hope, not optimism; Broadcast Flag talk; David Suzuki's free research; "Inquisitor's Apprentice"; California's tax ban made the rich MUCH richer; Union organizers caught Wells Fargo; Power poses aren't real; "Ghosts"; Criticizing tech; "Savage Love A-Z."
  • Upcoming appearances: Brighton, Virtual, South Bend, Hudson, Calgary, Winnipeg, Paris, OVancouver, Victoria, Ottawa, Kilkenny, Montreal.
  • Recent appearances: Where I've been.
  • Latest books: You keep readin' em, I'll keep writin' 'em.
  • Upcoming books: Like I said, I'll keep writin' 'em.
  • Colophon: All the rest.



The Earth from space; it is covered in scaffolding that is crawling with little rude mechanicals ganked from 17th century engravings of people operating machines.

Economic probabilities for our grandchildren (permalink)

The post-war "peace dividend" owed its existence to three factors: industrial capacity, political freedom and pent-up demand.

  • Industrial capacity: freed up by the war's end and the collapse of demand for munitions and materiel;
  • Political freedom: the war's orgy of capital destruction of the majority of the wealth (and thus the power) of the world's oligarchs;

  • Demand: Making up for years of neglect and privation during the war required new production of infrastructure and consumption goods.

In Capital in the 21st Century, Thomas Piketty and his students analyzed painstakingly assembled records of 300 years' worth of capital flows, showing that wealth tends to pool in the hands of the already-wealthy. This creates mounting instability, thanks to the misrule of a shrinking class of increasingly powerful hereditary, unaccountable oligarchs whose whims and follies trump the material and political needs of the vast majority:

https://memex.craphound.com/2014/06/24/thomas-pikettys-capital-in-the-21st-century/

That instability eventually reaches a breaking point in which the old order collapses in spectacular fashion, and that collapse destroys vast amounts of capital stock. Since this breaking point arrives as a result of oligarchy, in which nearly all the world's wealth has been hoarded by a tiny number of people, those aristocrats are disproportionately impoverished by the conflagration. If 90% of the wealth is in the hands of 1% of the people, a war or disaster that wipes out most capital will mostly destroy the wealth of the 1%. The poor suffer terribly, but they start with nothing – and end with nothing.

We are clearly steaming into one of these situations. The wealthy squandered two generations preventing the world from taking the climate emergency, and now the inevitable has arrived. California is on track to see a 12 inch sea-level rise in the next month:

https://www.theguardian.com/us-news/2026/sep/30/california-kelvin-wave-sea-level-rise

That's just the overture to the American run of this year's "Super El Nino," a blockbuster whose out-of-town previews have been slaying massive crowds all over the world:

https://www.theguardian.com/world/2026/oct/03/bangkok-thailand-floods-breaking-point-stagnant-lakes

Also right on schedule: the misrule of oligarchs has elevated a con-man to the presidency, who trumps every other president for both corruption and incompetence. From oil to dollars, telecoms infrastructure to the internet itself, Donald Trump is doing everything in his power to end the American empire:

https://pluralistic.net/2026/05/16/technopoly/#trumpismo-is-praxis-question-mark-exclamation-point

A post-American world is on the horizon. What that world will look like is undecided. The US was always a wildly defective trusted third party, but it retained its status as the world's platform for generations thanks to the absence of credible alternatives. Now – as Mark Carney told the Davos crowd last year – the old system has "ruptured":

https://www.weforum.org/stories/forum-institutional/davos-2026-special-address-by-mark-carney-prime-minister-of-canada/

What will replace the dollar?

https://pluralistic.net/2026/02/11/post-dollar-world/#de-dollarization

What will replace oil?

https://pluralistic.net/2025/10/11/cyber-rights-now/#better-late-than-never

What will replace the internet?

https://pluralistic.net/2026/01/01/39c3/#the-new-coalition

No one knows. No one can know, because this is all up for grabs. The future isn't something that happens to us. The future is something that we make. And despite the horrible death toll that's locked in by waves of climate shocks – presided over by the dying, fumbling "drink bleach" guy – it's conceivable that the future we get will be a good one, if not an easy one.

A post-oligarch, post-American world could be a post-austerity world, one where we recognize that the limiting factor on public investment isn't money, but resources: energy, material, labor, expertise:

https://www.youtube.com/watch?v=FATQ0Yf0Fhc

Just as the post-war world led to the "30 glorious years" by mobilizing people and material to rebuild a shattered world and shattered lives, a post-American world might be one in which we find full, meaningful employment for all of us, all of our children, and all of their children:

https://en.wikipedia.org/wiki/Trente_Glorieuses

As seas rise, we're going to have jobs for every person who is willing, building sea defenses and moving whole coastal cities inland. Two generations of neoliberalism has left us with massive shortages of housing stock, crumbling highways, inadequate schools and hospitals. That means we need to build lots more of these, and they can be built with state-of-the-art climate hardening, including geothermal heat, "passive home" designs, modern insulation, and heat-pumps. The rights-of-way for the interstate system would make dandy railbeds for arrow-straight, all electric high-speed rail powered by solar, wind and tides.

All of this has the potential to yield a second peace-dividend prosperity. A post-oligarch world can recover industrial capacity by diverting it away from destructive activity (data centers, luxury housing) to essential functions (climate defenses, decent housing for everyday people). A post-oligarch world can recover democratic excellence by removing the malign influence that morbid wealth exerts on our policy choices: for example, if we don't like the quality of public schools, we can fix them by giving schools more resources, not by letting billionaire dilettantes privatize and starve them in the name of "school choice":

https://pluralistic.net/2026/03/09/autocrats-of-trade-2/#witness-the-firepower-of-this-fully-armed-and-operational-battle-station

Finally, a post-oligarch society can provide us with an even more inclusive version of post-war prosperity: we can use the wages we earn by building a better world to acquire new homes, induction tops, EVs and ebikes, and beautiful heirloom computers that are built to last for generations, upgraded and maintained by their users:

https://www.edn.com/as-moores-law-slows-open-hardware-rises/

This is a world of material abundance and prosperity. Just as the post-war world made millions of people comfortable, educated and happy by putting them to work clearing the rubble and building something better, a post-oligarch, post-American world can offer us all all the hard, rewarding work we want, performing the essential work of care and rebuilding. We'll find that work by helping hundreds of millions of climate refugees, who will find work helping each other. This is all economically valuable, environmentally sustaining work that we have labor, energy and expertise for. It's just that all that labor, energy and expertise is being misallocated by the ultra-wealthy who don't even believe that the majority of us exist, and who want to bet the planet and our species on a bizarre scheme to feed so many words to the world-guessing machines that they wake up and become gods:

https://pluralistic.net/2026/05/13/vibe-governance/#k-hole

Nearly a century ago, John Maynard Keynes published "Economic Possibilities for our Grandchildren," where he extrapolated from rising productivity to predict a fifteen-hour work-week within two generations:

http://www.econ.yale.edu/smith/econ116a/keynes1.pdf

That dream was destroyed by war and greed. Today, we are submerged in an ocean of debt. I'm not talking about the (fictional) "national debt" that is just a measure of all the dollars the government has spent into existence without taxing out of existence:

https://pluralistic.net/2024/10/21/we-can-have-nice-things/#public-funds-not-taxpayer-dollars

I'm talking about fiscal debt: the solar we didn't build, the carbon we emitted instead; the rail we didn't build, the wildly inefficient aviation we substituted; the walkable, livable, transit-oriented cities we didn't build, the car-choked disasters we built instead. Oligarchic opposition to universal healthcare left us sick, with un- or undertreated illnesses and missed opportunities for prevention that left us with overlapping, population-scale health crises. All of this is debt that must be repaid: we can't walk away from it through bankruptcy. We must build the transit, cities, and systems of care – the infrastructure – our species needs to carry on human civilization. We can't keep hitting snooze on this, lest we are finally awoken by seawater lapping at our pillows.

The world we're in today is awful and terrifying. It's awful because of the climate ruptures we're living through. It's terrifying because we didn't just fail to avert this crisis – we did nothing to prepare for it, either.

As Piketty foretold, the misrule of oligarchs has led to an orgy of destruction, and while poor people will get the worst of it, oligarchs will pay the most, because they own nearly everything, which means they own everything that will be destroyed, too. None of that is good, and it would be better if we hadn't gotten into this situation in the first place.

But after a forest fire, the canopy opens; and when it does, the seedlings that were overshadowed for centuries by the old growth can sprout in the ashes and the sun. We can't afford to continue living under oligarchy, and as oligarchs' greed and folly drives the old system beyond its breaking point, we must seize the opportunity to build a better successor.


Hey look at this (permalink)



A shelf of leatherbound history books with a gilt-stamped series title, 'The World's Famous Events.'

Object permanence (permalink)

#25yrago Leak: RIAA's anti-P2P strategy https://web.archive.org/web/20011019060035/https://www.dotcomscoop.com/riaa1003.html

#25yrsago Infoseek founder: my brain-scanner can find all the terrorists https://web.archive.org/web/20011009025805/http://www.theregister.co.uk/content/55/22020.html

#25yrsago Copyrighting every possible phone number as a touch-tone tune https://web.archive.org/web/20011007073546/http://www.theage.com.au/entertainment/2001/10/04/FFX0PGT0CSC.html

#20yrsago What happens to password-locked data when you die? https://web.archive.org/web/20061106235805/http://news.com.com/Taking+passwords+to+the+grave/2100-1025_3-6118314.html

#20yrsago Audio of activist lawyer talk on Broadcast Flag and Chilling Effects https://web.archive.org/web/20061005074245/http://uscpublicdiplomacy.com/index.php/events/events_detail/1859/

#20yrsago David Suzuki: Steal my research – that’s what it’s for! https://web.archive.org/web/20061020144546/http://www.davidsuzuki.org/about_us/Dr_David_Suzuki/Article_Archives/weekly09290601.asp

#20yrsago Interdisciplinary DRM blog from my USC students https://uscpubd510.blogspot.com/search?updated-max=2006-08-24T14:59:00-07:00&amp;max-results=7&amp;start=91&amp;by-date=false

#15yrsag Inquisitor’s Apprentice: tenement sorcerers versus the robber barons in an alternate Gilded Age New York https://memex.craphound.com/2011/10/04/inquisitors-apprentice-tenement-sorcerers-versus-the-robber-barons-in-an-alternate-gilded-age-new-york/

#15yrsago Context: Further Selected Essays on Productivity, Creativity, Parenting, and Politics in the 21st Century https://memex.craphound.com/2011/10/03/context-further-selected-essays-on-productivity-creativity-parenting-and-politics-in-the-21st-century/

#15yrsago Unicode’s “right-to-left” override obfuscates malware’s filenames https://krebsonsecurity.com/2011/09/right-to-left-override-aids-email-attacks/

#15yrsago HOWTO carve a pumpkin by hammering cookie-cutters into it https://web.archive.org/web/20200916004345/https://www.foodnetwork.com/fn-dish/shopping/cookie-cutter-pumpkin-carving

#15yrsago Shell funded warring militias in the Niger Delta https://web.archive.org/web/20111006003453/http://blog.platformlondon.org/2011/10/03/counting-the-cost-corporations-and-human-rights-abuses-in-the-niger-delta/

#10yrsago Sen Mitch McConnell blames Obama for bill that Obama vetoed and McConnell repeatedly voted for https://www.loweringthebar.net/2016/10/congress-blames-veto.html

#10yrsago Polish women go on strike over extreme anti-abortion law https://www.bbc.com/news/world-europe-37540139

#10yrsago Survivors of CIA torture describe homebrew electric chair used at Afghan black site https://www.hrw.org/news/2016/10/03/interview-new-cia-torture-claims

#10yrsago Visualizing the latent emotional and bureaucratic labor in our material world https://xkcd.com/1741/

#10yrsago Meth, Hitler and the Reich: the true, untold story of the Nazis’ dependence on coke, meth and oxy https://www.theguardian.com/books/2016/sep/25/blitzed-norman-ohler-adolf-hitler-nazi-drug-abuse-interview

#10yrsago Flying Saucers are Real! Anthology of the lost saucer-craze https://memex.craphound.com/2016/10/03/flying-saucers-are-real-anthology-of-the-lost-saucer-craze/

#10yrsago The malware that’s pwning the Internet of Things is terrifyingly amateurish https://web.archive.org/web/20161004061621/http://motherboard.vice.com/read/internet-of-things-malware-mirai-ddos

#10yrsago California’s 40-year-old ban on property tax raises has made the rich a lot richer https://web.archive.org/web/20161001034224/https://www.latimes.com/business/hiltzik/la-fi-hiltzik-prop-13–20160929-snap-story.html

#10yrsago The Wells Fargo fraud came to light because of union organizers https://web.archive.org/web/20161005123132/https://prospect.org/article/first-and-foremost-wells-fargo-scandal-about-workers

#10yrsago “Power Poses” are bullshit https://web.archive.org/web/20161007215414/https://www.wbur.org/npr/496093672/power-poses-co-author-i-do-not-believe-the-effects-are-real

#10yrsago Martin Shkreli offers a bailout to ailing 4chan https://arstechnica.com/information-technology/2016/10/4chan-cashflow-problem-martin-shkreli-wants-to-join-board/

#10yrsago Ghosts: Raina Telgemeier’s upbeat tale of death, assimilation and cystic fibrosis https://memex.craphound.com/2016/10/04/ghosts-raina-telgemeiers-upbeat-tale-of-death-assimilation-and-cystic-fibrosis/

#10yrsago Yahoo secretly built a tool to scan all email in realtime for US spies https://www.reuters.com/article/idUSKCN1241YT/

#10yrsago How to: Criticize technology https://www.cjr.org/tow_center_reports/constructive_technology_criticism.php

#10yrsago Johnson & Johnson says people with diabetes don’t need to worry about potentially lethal wireless attacks on insulin pumps https://www.reuters.com/article/us-johnson-johnson-cyber-insulin-pumps-e-idUSKCN12411L/

#5yrsago USPS pilots postal banking https://pluralistic.net/2021/10/04/avoidance-is-evasion/#check-cashing

#5yrsago The Pandora Papers https://pluralistic.net/2021/10/04/avoidance-is-evasion/#transparency

#5yrsago Savage Love A-Z https://pluralistic.net/2021/10/04/avoidance-is-evasion/#ggg

#5yrsago Hope, Not Optimism https://pluralistic.net/2021/10/03/hope-not-optimism/

#1yrago When your ISP pays you https://pluralistic.net/2025/10/03/we-dont-care-we-dont-have-to/#were-the-phone-company

#1yrago Blue Bonds https://pluralistic.net/2025/10/04/fiscal-antifa/#post-trump


Upcoming appearances (permalink)

A photo of me onstage, giving a speech, pounding the podium.
https://www.epl.ca/blogs/post/elbows-up-with-cory-doctorow/



A screenshot of me at my desk, doing a livecast.

Recent appearances (permalink)



A grid of my books with Will Stahle covers..

Latest books (permalink)



A cardboard book box with the Macmillan logo.

Upcoming books (permalink)

  • "The Post-American Internet," a geopolitical sequel of sorts to Enshittification, Farrar, Straus and Giroux, 2027
  • "Unauthorized Bread": a middle-grades graphic novel adapted from my novella about refugees, toasters and DRM, FirstSecond, April 20, 2027

  • "Enshittification, Why Everything Suddenly Got Worse and What to Do About It" (the graphic novel), Firstsecond, 2027

  • "The Memex Method," Farrar, Straus, Giroux, 2027



Colophon (permalink)

Today's top sources:

Currently writing:

  • “Once Is Enemy Action,” a science fiction novel about the origins of modern technofascism. Today's words: 504 (21882 total).

  • "The Post-American Internet," a sequel to "Enshittification," about the better world the rest of us get to have now that Trump has torched America. Fourth draft completed. Submitted to editor.

  • A Little Brother short story about DIY insulin PLANNING


This work – excluding any serialized fiction – is licensed under a Creative Commons Attribution 4.0 license. That means you can use it any way you like, including commercially, provided that you attribute it to me, Cory Doctorow, and include a link to pluralistic.net.

https://creativecommons.org/licenses/by/4.0/

Quotations and images are not included in this license; they are included either under a limitation or exception to copyright, or on the basis of a separate license. Please exercise caution.


How to get Pluralistic:

Blog (no ads, tracking, or data-collection):

Pluralistic.net

Newsletter (no ads, tracking, or data-collection):

https://pluralistic.net/plura-list

Mastodon (no ads, tracking, or data-collection):

https://mamot.fr/@pluralistic

Bluesky (no ads, possible tracking and data-collection):

https://bsky.app/profile/doctorow.pluralistic.net

Medium (no ads, paywalled):

https://doctorow.medium.com/

Tumblr (mass-scale, unrestricted, third-party surveillance and advertising):

https://mostlysignssomeportents.tumblr.com/tagged/pluralistic

"When life gives you SARS, you make sarsaparilla" -Joey "Accordion Guy" DeVilla

READ CAREFULLY: By reading this, you agree, on behalf of your employer, to release me from all obligations and waivers arising from any and all NON-NEGOTIATED agreements, licenses, terms-of-service, shrinkwrap, clickwrap, browsewrap, confidentiality, non-disclosure, non-compete and acceptable use policies ("BOGUS AGREEMENTS") that I have entered into with your employer, its partners, licensors, agents and assigns, in perpetuity, without prejudice to my ongoing rights and privileges. You further represent that you have the authority to release me from any BOGUS AGREEMENTS on behalf of your employer.

ISSN: 3066-764X

13:07

The Laws of Useful Automation [Economics from the Top Down]

Your browser does not support the audio tag.

Download: PDF | EPUB | MP3 | WATCH VIDEO

Given our current landscape of AI hype and doom, everyone seems to have an opinion about whether chatbots are ‘good’ or ‘bad’. Fewer people have thought about how chatbots fit into the long-term history of automation. And even fewer folks have reflected on the conceptual requirements that make automation useful. Here are my thoughts on this latter topic.

In my view, automation is useful when it meets two criteria:

  1. The product is more important than the process that creates it.
  2. The product can be validated without auditing the creation process.

If we look at successful forms of machine automation, they tend to meet both criteria. For example, think of a pencil factory that automates the production of pencils. Here, the pencil is the ‘product’, and whatever happens inside the factory is the ‘process’.

Now for the end user, the process of pencil production is largely irrelevant. It doesn’t matter if the pencil is hand crafted, built in an assembly line, or conjured by a Star Trek replicator. As long as the pencil works in the hands of the user, its creation process is unimportant.

That brings me to the second requirement for useful automation: the product must be easily validated. To validate that a pencil works, you don’t need to audit the process that created it. You just give the thing a go. Yup, this pencil works. Nope, that one’s a dud.

Thinking more broadly, the history of automation has been dominated by this sort of process where a machine replaces human labor in the production of some sort of physical commodity. The automation works because (1) the commodity is valued more than the process that creates it, and (2) it’s easy to verify the quality of the commodity without auditing the entire chain of production.

Intellectual automation

Looking to more recent technological progress, it turns out that successful automation need not be physical. Intellectual automation can also be useful. Just look at computers, which owe their name to the desire to automate computation.

Many forms of computation pass our automation requirements: the product is more important than the process, and the process is easy to validate. For example, when I ask a computer to calculate \sqrt{200} , I don’t care about the algorithm it uses, or about the details of its chipset. I just want the answer. Likewise, once I have the result, I don’t have to audit the computer’s innards to validate the answer. I just check that the number squares to give 200.

In more general terms, any intellectual task that meets our two requirements is game for computer automation. But for years, some forms of intellectual work seemed beyond the reach of computers. Writing code is a good example. Historically, programming was a tedious task that took years of specialized training. But with the rise of neural nets and their associated chatbot interfaces, these barriers are being torn down. Chatbots can now code more quickly than any human. But is this ability useful?

Well, the answer depends on whether the application passes our two requirements. (Is the product more important than the process? And can the product be verified without auditing the process that created it?) Clearly, many forms of coding pass this test. Web design is an obvious example. When a blogger asks for a nice website, they usually don’t care how it’s accomplished. Likewise, the blogger can easily tell if the final design is what they want. (They just browse the website.)

In short, chatbots are useful for automating intellectual tasks like web design (and any other easily verified piece of software). Yes, the bots will put some folks out of work. Yes, they’ll raise questions about the skills required in the workforce. And yes, they’ll be used in ways that undermine labor power and harm workers’ health. But these issues are nothing new. They’re a historical feature of all forms of automation. What interests me more (at least in this essay) is the ways in which chatbot automation might be fundamentally useless, or even downright harmful.

On the useless front, I’m skeptical that chatbots can be used to fully automate scientific analysis. Here’s why. When a scientist analyzes their data, they might think that the product of their inquiry is the ‘results’ section in their published paper. And in some sense, that’s true. But the (big) caveat is that the usefulness of this result depends on whether the analysis pipeline is correct.

Now, suppose that a scientist automated their work by getting a chatbot to code the entirety of their analytic pipeline. How does the scientist know that their results are correct?1 Well, if the analysis is complicated, the only sound way to assess its correctness is to audit the underlying code. That requires significant time and skill. Meanwhile, this time-skill investment largely defeats the purpose of automation. Of course, chatbots can no doubt help scientist solve specific coding problems. (These bots lower the barrier to successful programming.) But the notion that chatbots will fully automate scientific analysis is, frankly, laughable.

True, some academics will surely try this fully automated approach. In fact, I expect that the scientific literature will become increasingly polluted with bot junk. But I’d argue that we can’t blame chatbots (solely) for this pollution. The root problem is the incentive structure in universities — a structure that values the production of academic papers far more than the process that creates them. But when it comes to good research, its social value lies entirely in the scientific process.

Process dependence

Like science, many areas of human life have a process dependence, in which the usefulness of a ‘product’ hinges solely on the process of doing it. Schooling is the most ubiquitous example. When a teacher asks students to solve a math problem, the product of this task is the correct answer. But the usefulness of this activity lies mostly in the process of doing it. Sure, a calculator will tell you the sum of 21 + 54. But if the goal is to learn basic arithmetic, the use of a calculator is not ‘automation’. It’s cheating.

The obvious conclusion is that skill acquisition cannot be automated. If the goal is to learn basic arithmetic, a calculator is self-defeating. If the goal is learn to the principles of English spelling, a spell checker is unhelpful. If the goal is to learn to read, a text-to-voice processor is educational sabotage. And if the goal is to learn to write, well, chatbots are your mortal enemy.2

In this light, we can think of formal education as a teaching method that forces students to re-experience, in a curated and abbreviated form, problems that long stumped our ancestors. For example, it took thousands of years of doing arithmetic before humans automated the job with calculators. By then, mathematics was a mature field. When today’s students learn math, they replay this history over the course of a few years. At first, ‘math’ consists solely of raw computation. Later on, students learn more symbolic logic, and the number crunching gets delegated to machines. In short, when it comes to intellectual automation, everything hinges on the order of operations. First, you learn a difficult skill; then you discover that you can automate it.

Do not automate

Thinking further about process dependence, it seems likely that some intellectual tasks should never be automated. Writing is the most obvious example.

To understand why automated writing is bad, we need to first deal with the overloaded nature of the English language. In English, the word ‘write’ has a misleading double meaning. In one sense, to ‘write’ means to ‘scribe’ — to put an already existing sentence onto paper. This form of ‘writing’ takes skill, but is grounds for useful automation. Once upon a time, video captions were transcribed by a human listener. But today, the captioning can be generated by natural language processors.

The problem with automated ‘writing’ comes with the second meaning of the word. To ‘write’ is not just to scribe; it’s also to craft a set of coherent arguments that other humans can follow and understand. To automate this activity is an oxymoron, because the product (a rational argument) can’t be separated from the process itself. Or as my mentor Jonathan Nitzan once told me, “You don’t really know what you think until you write it down.”

Here’s what he means. ‘Writing’, in this sense of the word, is essentially codified thinking. When an idea is written down, reading this idea leads to all kinds of interesting consequences. Often, the writer realizes that the idea is vague or incomplete. And so they revise it until things make sense. Once the idea is coherent, rereading it prompts new ideas and new connections. Many are dead ends. Some are fruitful.

To be frank, this iterative process can be torturous. When I write blog posts about my research, the final essay usually conceals an iceberg of revised or discarded thought. Sure, I’d like to avoid this torture and still have the final well-argued essay. But to avoid the torture of ‘writing’ is to avoid the discomfort of rational thought. Automating this task does not ‘save time’; it saves us from thinking.

Automation for whom?

For automation to be ‘useful’, the product must be more important than the process by which it is created. But there is a sticky question that I’ve so far avoided: more important for whom?

For the user of a pencil, the way that this commodity was manufactured is largely irrelevant. But for the folks who live beside the pencil factory, the manufacturing process is often more salient than the product itself, particularly if pollution is involved. Likewise, chatbots might be great for the Silicon Valley programmer, but they’re a Faustian bargain for the utility planners who have to power local data centers.

When it comes to the big picture of automation, the process by which it occurs is incredibly consequential … often far more so than the product being automated. It’s one thing to have a Star-Trek-like computer powered by nuclear fusion; it’s quite another to have a sycophantic chatbot powered by fossil fuels.

Unfortunately, we humans are notoriously bad at assessing the big-picture consequences of our automation schemes. As a rule, we build first and ask questions later. Today, we seem to be automating tasks that should not be automated (using fuels that are steadily spoiling the planet). The internet is increasingly littered with chatbot slop, to the point that search engines can feel pointless. If a search query returns page after page of bot slop, it’s obviously more sensible to pose the question directly to a chatbot. But if the chatbot is trained on bot-slop, how can you trust its answer?

Now, I’m personally skeptical of claims about AI-driven doom, but mostly because they go in the wrong direction. If there’s a risk that AI will kill industrial civilization, it’s not because the machines will take over. Far more likely, in my opinion, is that we get a future that looks like an anti-singularity — a future in which our technology becomes so powerful (and so polluting) that it gradually undermines its own existence. As humans subcontract thinking to fossil-fuel-fed chatbots, the information environment (as well as the natural environment) becomes polluted with slop, and the slop-trained bots themselves grow increasingly senile. “Water the crops with Brawndo,” the bots say. Meanwhile, AI-driven education has left humans gullible enough to listen.3

In short, automation can be useful if it frees us from drudgery (in a way that doesn’t destroy the earth) and leaves more time for creative thought. But if automated chatbots gobble fossil fuels in order to liberate us from thinking, well, civilization had a nice run.


Support this blog

Hi folks, Blair Fix here. I’m a crowdfunded scientist who shares all of my (painstaking) research for free. If you think my work has value, consider becoming a supporter. You’ll help me continue to share data-driven science with a world that needs less opinion and more facts.

member_button


Stay updated

Sign up to get email updates from this blog.



This work is licensed under a Creative Commons Attribution 4.0 License. You can use/share it anyway you want, provided you attribute it to me (Blair Fix) and link to Economics from the Top Down.


Notes

  1. Of course, the truth is that even the best-trained scientists make mistakes, which is why good science requires replication. And regarding code, there’s an old saying that you shouldn’t reinvent the wheel … don’t recode an algorithm that someone has already solved. Which is why scientific code is typically full of libraries and functions which the scientist in question did not write.

    For example, when I get R to calculate a matrix inverse, I’m actually calling ancient Fortran code for doing linear algebra. To me, this code is a black box — I have no idea how it works. So how do I know that this code works correctly? Honestly, it’s a matter of trust. These libraries are free and open source, and have been used for ages. If they had a gaping problem, scientists would not use them.

    Now, this game of trust comes on a continuum. I greatly trust R’s matrix inverse functions. I put less trust in code from a random Github repository. And I put even less trust in the code delivered by a chatbot. Sure, the chatbot code may be 99% good. But that 1% bad stuff can be a killer. Imagine a world in which all scientific analysis and all scientific libraries were coded by chatbots with no supervision from scientists. Each time a bot calls a Python or R library, there’s a 1% chance of error. As the code expands, the error compounds, to the point that virtually everything the bots spit out is wrong.

    Still, chatbots shine in the domain where automation has always been useful — when the results are easily verifiable. Refactoring code is a good example. If I do an analysis in R and someone else wants to refactor the code into Python, a chatbot could make short work of the task. Sure, the chatbot might make mistakes along the way, but the user could tell by comparing the R output to the Python output.↩︎

  2. In schools systems, teachers often speak in the language of ‘accommodations’ — as in text-to-voice is an ‘accommodation’ for dyslexia. While this use of technology is well intentioned, it’s also tragic. The truth is that if a teenager cannot read effectively, all available resources should go into solving this highly solvable problem. In my view, it’s unethical to forge ahead with other curriculum in the face of gaping illiteracy.↩︎
  3. If there are long-term benefits to chatbots, they will come by strategically withholding chatbot use while students learn difficult and uncomfortable skills. Actually using a chatbot takes about as much skill as using a calculator. Which is funny, because no one would propose a ‘calculator-driven education’. But many folks will claim that AI is going to revolutionize schooling. Well, I work in high schools and can tell you that so far, what’s been ‘revolutionary’ is that chatbots have killed the take-home essay. Learning to craft long-form thought used to be a standard feature of high-school education. Now it’s not.↩︎

Further reading

Doctorow, C. (2026). The reverse centaur’s guide to life after AI: How to think about artificial intelligence before it’s too late. Verso Books.

The post The Laws of Useful Automation appeared first on Economics from the Top Down.

12:35

Zig 0.17 released [LWN.net]

Version 0.17 of the Zig programming language has been released.

This release features 5 months of work: changes from 206 different contributors, spread among 925 commits.

Originally predicted to be shorter, this release cycle ended up [being] substantial, with the Build System reworked, including the introduction of the Build Server Protocol, and the ELF Linker enhanced to the point where we expect Incremental Compilation to work for everyone on x86_64-linux.

LWN last covered Zig in December 2025.

Seven stable kernels for Saturday [LWN.net]

Greg Kroah-Hartman has announced the release of the 7.2.9, 6.18.55, 6.12.112, 6.6.158, 6.1.189, 5.15.222, and 5.10.271 stable kernels. Each contains a large number of important fixes throughout the tree; users are advised to upgrade.

10:49

Uniformity [Seth's Blog]

Consistency costs extra.

If you want to buy machine screws or widgets that are exactly the same to five decimal points, you’ll pay a premium for that. In exchange, you’ll get parts that are precisely as expected, making assembly more reliable.

Mechanization’s productivity and our fear of fear have driven us to do this with just about everything. Bananas, fast food and student performance are all pushed toward consistency, often at the expense of the possibility of extraordinary performance.

We do this to humans at our own peril.

Do we really want the artist to produce a carbon copy each time? For every Dead show to be the same? For customer service to be measured with a stopwatch, not our hearts?

Uniformity pays when the best definition of “excellent” is that it “meets spec.” This includes day-to-day freelance work, business hotel rooms and the way our phones work.

For everything else, perhaps we ought to pay a bit more for awe, insight and surprise.

Feeds

FeedRSSLast fetchedNext fetched after
@ASmartBear XML 22:21, Thursday, 08 October 23:02, Thursday, 08 October
a bag of four grapes XML 22:28, Thursday, 08 October 23:10, Thursday, 08 October
Ansible XML 22:21, Thursday, 08 October 23:01, Thursday, 08 October
Bad Science XML 22:28, Thursday, 08 October 23:17, Thursday, 08 October
Black Doggerel XML 22:21, Thursday, 08 October 23:02, Thursday, 08 October
Blog - Official site of Stephen Fry XML 22:28, Thursday, 08 October 23:17, Thursday, 08 October
Charlie Brooker | The Guardian XML 22:28, Thursday, 08 October 23:10, Thursday, 08 October
Charlie's Diary XML 22:35, Thursday, 08 October 23:23, Thursday, 08 October
Chasing the Sunset - Comics Only XML 22:28, Thursday, 08 October 23:17, Thursday, 08 October
Coding Horror XML 22:35, Thursday, 08 October 23:22, Thursday, 08 October
Comics Archive - Spinnyverse XML 22:14, Thursday, 08 October 22:58, Thursday, 08 October
Cory Doctorow's craphound.com XML 22:28, Thursday, 08 October 23:10, Thursday, 08 October
Cory Doctorow, Author at Boing Boing XML 22:21, Thursday, 08 October 23:02, Thursday, 08 October
Ctrl+Alt+Del Comic XML 22:35, Thursday, 08 October 23:23, Thursday, 08 October
Cyberunions XML 22:28, Thursday, 08 October 23:17, Thursday, 08 October
David Mitchell | The Guardian XML 22:14, Thursday, 08 October 22:57, Thursday, 08 October
Deeplinks XML 22:14, Thursday, 08 October 22:58, Thursday, 08 October
Diesel Sweeties webcomic by rstevens XML 22:14, Thursday, 08 October 22:57, Thursday, 08 October
Dilbert XML 22:28, Thursday, 08 October 23:17, Thursday, 08 October
Dork Tower XML 22:28, Thursday, 08 October 23:10, Thursday, 08 October
Economics from the Top Down XML 22:14, Thursday, 08 October 22:57, Thursday, 08 October
Edmund Finney's Quest to Find the Meaning of Life XML 22:14, Thursday, 08 October 22:57, Thursday, 08 October
EFF Action Center XML 22:14, Thursday, 08 October 22:57, Thursday, 08 October
Enspiral Tales - Medium XML 22:14, Thursday, 08 October 22:59, Thursday, 08 October
Events XML 22:35, Thursday, 08 October 23:23, Thursday, 08 October
Falkvinge on Liberty XML 22:35, Thursday, 08 October 23:23, Thursday, 08 October
Flipside XML 22:28, Thursday, 08 October 23:10, Thursday, 08 October
Flipside XML 22:14, Thursday, 08 October 22:59, Thursday, 08 October
Free software jobs XML 22:21, Thursday, 08 October 23:01, Thursday, 08 October
Full Frontal Nerdity by Aaron Williams XML 22:35, Thursday, 08 October 23:23, Thursday, 08 October
General Protection Fault: Comic Updates XML 22:35, Thursday, 08 October 23:23, Thursday, 08 October
George Monbiot XML 22:14, Thursday, 08 October 22:57, Thursday, 08 October
Girl Genius XML 22:14, Thursday, 08 October 22:57, Thursday, 08 October
Groklaw XML 22:35, Thursday, 08 October 23:23, Thursday, 08 October
Grrl Power XML 22:28, Thursday, 08 October 23:10, Thursday, 08 October
Hackney Anarchist Group XML 22:28, Thursday, 08 October 23:17, Thursday, 08 October
Hackney Solidarity Network XML 22:14, Thursday, 08 October 22:59, Thursday, 08 October
http://blog.llvm.org/feeds/posts/default XML 22:14, Thursday, 08 October 22:59, Thursday, 08 October
http://calendar.google.com/calendar/feeds/q7s5o02sj8hcam52hutbcofoo4%40group.calendar.google.com/public/basic XML 22:21, Thursday, 08 October 23:01, Thursday, 08 October
http://dynamic.boingboing.net/cgi-bin/mt/mt-cp.cgi?__mode=feed&_type=posts&blog_id=1&id=1 XML 22:14, Thursday, 08 October 22:59, Thursday, 08 October
http://eng.anarchoblogs.org/feed/atom/ XML 22:07, Thursday, 08 October 22:53, Thursday, 08 October
http://feed43.com/3874015735218037.xml XML 22:07, Thursday, 08 October 22:53, Thursday, 08 October
http://flatearthnews.net/flatearthnews.net/blogfeed XML 22:21, Thursday, 08 October 23:02, Thursday, 08 October
http://fulltextrssfeed.com/ XML 22:14, Thursday, 08 October 22:57, Thursday, 08 October
http://london.indymedia.org/articles.rss XML 22:35, Thursday, 08 October 23:22, Thursday, 08 October
http://pipes.yahoo.com/pipes/pipe.run?_id=ad0530218c055aa302f7e0e84d5d6515&amp;_render=rss XML 22:07, Thursday, 08 October 22:53, Thursday, 08 October
http://planet.gridpp.ac.uk/atom.xml XML 22:35, Thursday, 08 October 23:22, Thursday, 08 October
http://shirky.com/weblog/feed/atom/ XML 22:14, Thursday, 08 October 22:58, Thursday, 08 October
http://thecommune.co.uk/feed/ XML 22:14, Thursday, 08 October 22:59, Thursday, 08 October
http://theness.com/roguesgallery/feed/ XML 22:35, Thursday, 08 October 23:23, Thursday, 08 October
http://www.airshipentertainment.com/buck/buckcomic/buck.rss XML 22:28, Thursday, 08 October 23:17, Thursday, 08 October
http://www.airshipentertainment.com/growf/growfcomic/growf.rss XML 22:14, Thursday, 08 October 22:58, Thursday, 08 October
http://www.airshipentertainment.com/myth/mythcomic/myth.rss XML 22:28, Thursday, 08 October 23:10, Thursday, 08 October
http://www.feedsapi.com/makefulltextfeed.php?url=http%3A%2F%2Fwww.somethingpositive.net%2Fsp.xml&what=auto&key=&max=7&links=preserve&exc=&privacy=I+accept XML 22:14, Thursday, 08 October 22:58, Thursday, 08 October
http://www.godhatesastronauts.com/feed/ XML 22:35, Thursday, 08 October 23:23, Thursday, 08 October
http://www.tinycat.co.uk/feed/ XML 22:21, Thursday, 08 October 23:01, Thursday, 08 October
https://anarchism.pageabode.com/blogs/anarcho/feed/ XML 22:14, Thursday, 08 October 22:58, Thursday, 08 October
https://broodhollow.krisstraub.comfeed/ XML 22:21, Thursday, 08 October 23:02, Thursday, 08 October
https://debian-administration.org/atom.xml XML 22:21, Thursday, 08 October 23:02, Thursday, 08 October
https://elitetheatre.org/ XML 22:35, Thursday, 08 October 23:22, Thursday, 08 October
https://feeds.feedburner.com/Starslip XML 22:28, Thursday, 08 October 23:10, Thursday, 08 October
https://feeds2.feedburner.com/GeekEtiquette?format=xml XML 22:14, Thursday, 08 October 22:57, Thursday, 08 October
https://hackbloc.org/rss.xml XML 22:21, Thursday, 08 October 23:02, Thursday, 08 October
https://kajafoglio.livejournal.com/data/atom/ XML 22:28, Thursday, 08 October 23:17, Thursday, 08 October
https://philfoglio.livejournal.com/data/atom/ XML 22:35, Thursday, 08 October 23:22, Thursday, 08 October
https://pixietrixcomix.com/eerie-cutiescomic.rss XML 22:35, Thursday, 08 October 23:22, Thursday, 08 October
https://pixietrixcomix.com/menage-a-3/comic.rss XML 22:14, Thursday, 08 October 22:58, Thursday, 08 October
https://propertyistheft.wordpress.com/feed/ XML 22:21, Thursday, 08 October 23:01, Thursday, 08 October
https://requiem.seraph-inn.com/updates.rss XML 22:21, Thursday, 08 October 23:01, Thursday, 08 October
https://studiofoglio.livejournal.com/data/atom/ XML 22:07, Thursday, 08 October 22:53, Thursday, 08 October
https://thecommandline.net/feed/ XML 22:07, Thursday, 08 October 22:53, Thursday, 08 October
https://torrentfreak.com/subscriptions/ XML 22:14, Thursday, 08 October 22:57, Thursday, 08 October
https://web.randi.org/?format=feed&type=rss XML 22:14, Thursday, 08 October 22:57, Thursday, 08 October
https://www.baen.com/baenebooks XML 22:14, Thursday, 08 October 22:58, Thursday, 08 October
https://www.dcscience.net/feed/medium.co XML 22:28, Thursday, 08 October 23:17, Thursday, 08 October
https://www.DropCatch.com/domain/steampunkmagazine.com XML 22:21, Thursday, 08 October 23:02, Thursday, 08 October
https://www.DropCatch.com/domain/ubuntuweblogs.org XML 22:07, Thursday, 08 October 22:53, Thursday, 08 October
https://www.DropCatch.com/redirect/?domain=DyingAlone.net XML 22:35, Thursday, 08 October 23:22, Thursday, 08 October
https://www.freedompress.org.uk:443/news/feed/ XML 22:35, Thursday, 08 October 23:23, Thursday, 08 October
https://www.goblinscomic.com/category/comics/feed/ XML 22:21, Thursday, 08 October 23:01, Thursday, 08 October
https://www.loomio.com/blog/feed/ XML 22:07, Thursday, 08 October 22:53, Thursday, 08 October
https://www.newstatesman.com/feeds/blogs/laurie-penny.rss XML 22:21, Thursday, 08 October 23:02, Thursday, 08 October
https://www.patreon.com/graveyardgreg/posts/comic.rss XML 22:35, Thursday, 08 October 23:22, Thursday, 08 October
https://www.rightmove.co.uk/rss/property-for-sale/find.html?locationIdentifier=REGION^876&maxPrice=240000&minBedrooms=2&displayPropertyType=houses&oldDisplayPropertyType=houses&primaryDisplayPropertyType=houses&oldPrimaryDisplayPropertyType=houses&numberOfPropertiesPerPage=24 XML 22:14, Thursday, 08 October 22:57, Thursday, 08 October
https://x.com/statuses/user_timeline/22724360.rss XML 22:21, Thursday, 08 October 23:01, Thursday, 08 October
Humble Bundle Blog XML 22:35, Thursday, 08 October 23:22, Thursday, 08 October
I, Cringely XML 22:35, Thursday, 08 October 23:23, Thursday, 08 October
Irregular Webcomic! XML 22:21, Thursday, 08 October 23:02, Thursday, 08 October
Joel on Software XML 22:07, Thursday, 08 October 22:53, Thursday, 08 October
Judith Proctor's Journal XML 22:21, Thursday, 08 October 23:01, Thursday, 08 October
Krebs on Security XML 22:21, Thursday, 08 October 23:02, Thursday, 08 October
Lambda the Ultimate - Programming Languages Weblog XML 22:21, Thursday, 08 October 23:01, Thursday, 08 October
Looking For Group XML 22:14, Thursday, 08 October 22:58, Thursday, 08 October
LWN.net XML 22:21, Thursday, 08 October 23:02, Thursday, 08 October
Mimi and Eunice XML 22:14, Thursday, 08 October 22:59, Thursday, 08 October
Neil Gaiman's Journal XML 22:21, Thursday, 08 October 23:01, Thursday, 08 October
Nina Paley XML 22:35, Thursday, 08 October 23:22, Thursday, 08 October
O Abnormal – Scifi/Fantasy Artist XML 22:14, Thursday, 08 October 22:59, Thursday, 08 October
Oglaf! -- Comics. Often dirty. XML 22:35, Thursday, 08 October 23:23, Thursday, 08 October
Oh Joy Sex Toy XML 22:14, Thursday, 08 October 22:58, Thursday, 08 October
Order of the Stick XML 22:14, Thursday, 08 October 22:58, Thursday, 08 October
Original Fiction Archives - Reactor XML 22:28, Thursday, 08 October 23:10, Thursday, 08 October
OSnews XML 22:14, Thursday, 08 October 22:59, Thursday, 08 October
Paul Graham: Unofficial RSS Feed XML 22:14, Thursday, 08 October 22:59, Thursday, 08 October
Penny Arcade XML 22:28, Thursday, 08 October 23:10, Thursday, 08 October
Penny Red XML 22:14, Thursday, 08 October 22:59, Thursday, 08 October
PHD Comics XML 22:28, Thursday, 08 October 23:17, Thursday, 08 October
Phil's blog XML 22:35, Thursday, 08 October 23:23, Thursday, 08 October
Planet Debian XML 22:14, Thursday, 08 October 22:59, Thursday, 08 October
Planet GNU XML 22:21, Thursday, 08 October 23:02, Thursday, 08 October
Planet Lisp XML 22:28, Thursday, 08 October 23:17, Thursday, 08 October
Pluralistic: Daily links from Cory Doctorow XML 22:21, Thursday, 08 October 23:01, Thursday, 08 October
PS238 by Aaron Williams XML 22:35, Thursday, 08 October 23:23, Thursday, 08 October
QC RSS v2 XML 22:35, Thursday, 08 October 23:22, Thursday, 08 October
Radar XML 22:28, Thursday, 08 October 23:10, Thursday, 08 October
RevK®'s ramblings XML 22:07, Thursday, 08 October 22:53, Thursday, 08 October
Richard Stallman's Political Notes XML 22:28, Thursday, 08 October 23:17, Thursday, 08 October
Scenes From A Multiverse XML 22:35, Thursday, 08 October 23:22, Thursday, 08 October
Schneier on Security XML 22:21, Thursday, 08 October 23:01, Thursday, 08 October
SCHNEWS.ORG.UK XML 22:14, Thursday, 08 October 22:58, Thursday, 08 October
Scripting News XML 22:28, Thursday, 08 October 23:10, Thursday, 08 October
Seth's Blog XML 22:07, Thursday, 08 October 22:53, Thursday, 08 October
Skin Horse XML 22:28, Thursday, 08 October 23:10, Thursday, 08 October
Tales From the Riverbank XML 22:28, Thursday, 08 October 23:17, Thursday, 08 October
The Adventures of Dr. McNinja XML 22:14, Thursday, 08 October 22:59, Thursday, 08 October
The Bumpycat sat on the mat XML 22:21, Thursday, 08 October 23:01, Thursday, 08 October
The Daily WTF XML 22:07, Thursday, 08 October 22:53, Thursday, 08 October
The Monochrome Mob XML 22:21, Thursday, 08 October 23:02, Thursday, 08 October
The Non-Adventures of Wonderella XML 22:14, Thursday, 08 October 22:57, Thursday, 08 October
The Old New Thing XML 22:14, Thursday, 08 October 22:58, Thursday, 08 October
The Open Source Grid Engine Blog XML 22:35, Thursday, 08 October 23:22, Thursday, 08 October
The Stranger XML 22:14, Thursday, 08 October 22:59, Thursday, 08 October
towerhamletsalarm XML 22:07, Thursday, 08 October 22:53, Thursday, 08 October
Twokinds XML 22:28, Thursday, 08 October 23:10, Thursday, 08 October
UK Indymedia Features XML 22:28, Thursday, 08 October 23:10, Thursday, 08 October
Uploads from ne11y XML 22:07, Thursday, 08 October 22:53, Thursday, 08 October
Uploads from piasladic XML 22:14, Thursday, 08 October 22:57, Thursday, 08 October
Use Sword on Monster XML 22:35, Thursday, 08 October 23:22, Thursday, 08 October
Wayward Sons: Legends - Sci-Fi Full Page Webcomic - Updates Daily XML 22:07, Thursday, 08 October 22:53, Thursday, 08 October
what if? XML 22:21, Thursday, 08 October 23:02, Thursday, 08 October
Whatever XML 22:28, Thursday, 08 October 23:17, Thursday, 08 October
Whitechapel Anarchist Group XML 22:28, Thursday, 08 October 23:17, Thursday, 08 October
WIL WHEATON dot NET XML 22:14, Thursday, 08 October 22:58, Thursday, 08 October
wish XML 22:14, Thursday, 08 October 22:59, Thursday, 08 October
Writing the Bright Fantastic XML 22:14, Thursday, 08 October 22:58, Thursday, 08 October
xkcd.com XML 22:14, Thursday, 08 October 22:57, Thursday, 08 October