Reading view

There are new articles available, click to refresh the page.

Google Search Is More Useful if You Know Its Advanced Operators, to a Point

By: Nick Heer

Hana Lee Goldin:

The search bar you already have is more capable than that arrangement requires you to know. With the right syntax, it becomes a precision instrument: narrow by domain, by date, by file type, by exact phrase. We can pull up archived pages, surface open file directories, and even find what people said in forums instead of what brands want us to find. None of it requires a new tool or a paid account. The capability has been there the whole time.

Advanced search operations are something Google does better than any competitor. DuckDuckGo has its bangs and I like them very much, but Google has a vast catalogue able to be searched with such precision — to a point. If you use these advanced search operators, get ready to see a lot of CAPTCHAs. Google will slow you down and may even block you temporarily if you use it too well.

⌥ Permalink

Upgrade Presents: The Origin of Apple

By: Nick Heer

The newest episode of “Upgrade” is a wonderful retelling of a very particular history (also available as a video):

Jason and Myke tell the story of Apple’s origin. It emerged from the unique environment of the Santa Clara valley suburbs of the ’70s thanks to the particular genius of its two co-founders and some surprising help they got along the way.

Though I was familiar with much of this, I cannot think of many better people to tell it than Jason Snell. I have already seen one thinkpiece after another about what a fifty year-old — ish — Apple means in the grand scope, and there is definitely a place for that. Today’s Apple is a long way from this origin story, of course, but what a story it is.

This gives me an excuse to explain why I am fascinated by this one computer company. Though this story is great, that is not why, nor is it the history of successfully bringing the graphical user interface to the market, nor the ’90s–’00s turnaround. Those are all parts of it. But the main reason I am fascinated by Apple is that it has built such a distinct identity for itself. It has not always stuck to it but, if anything, I think that helps reinforce the existence of an Apple-y identity. Some might attribute that to a particular way of marketing itself which, while true, also emphasizes how important that identity is: when its messaging does not match the products, services, experience, or expected corporate behaviour, it is noticeable.

This is all a bit mythical, to be sure. The garage-era Steves probably would not imagine Apple celebrating its fiftieth birthday by being the second most valuable corporation in the world, nor would they think it would hire Paul McCartney for its employee party. To me, one of those things feels more Apple-y than the other. It feels right for the company to celebrate with a music legend; it probably does not need to be quite so rich or powerful to do that, though. Apple has long been a really, really big corporation, and that — in itself — does not feel very Apple-y to me. That, too, is fascinating.

⌥ Permalink

⌥ Apple’s Supposed A.I. Strategy Shift Is the Company’s Normal Strategy

By: Nick Heer

Mark Gurman, last week in Bloomberg:

Apple Inc. plans to open Siri to outside artificial intelligence assistants, a major move aimed at bolstering the iPhone as an AI platform.

The company is preparing to make the change as part of a Siri overhaul in its upcoming iOS 27 operating system update, according to people with knowledge of the matter. The assistant can already tap into ChatGPT through a partnership with OpenAI, but Apple will now allow competing services to do the same.

This is not unexpected. In the Apple Intelligence introduction at WWDC 2024, Craig Federighi said “we want you to be able to use these external models without having to jump between different tools”, and that they were “starting” with ChatGPT. Gurman points this out and also notes Federighi’s teased Google Gemini integration. Tim Cook, in an October 2025 earnings call, said much the same. (Gurman also notes that this integration is “separate from Apple’s work with Google to rebuild Siri using Gemini models”, but “the news initially weighed on shares of Google”, which I am sure is exactly the reason for them dropping 3.4% and nothing to do with an existing weeklong slide but, then again, I do not work at Bloomberg so who the hell am I to say?)

Gurman, in his “Power On” newsletter over the weekend, further explored what he calls Apple “doubl[ing] down” on a “revamped A.I. and Siri strategy”:

That reality is shaping the company’s new approach, set to be unveiled at the Worldwide Developers Conference on June 8. Rather than engaging in an AI arms race, Apple is focusing on its core strengths: selling highly profitable hardware and making money off the services that run on it.

Historically, Apple’s software — iMessage, Maps and Photos, for example — has been about driving product sales rather than generating revenue in their own right. Rivals, in contrast, are aggressively monetizing AI through subscriptions and premium apps. Apple understands that few, if any, users will pay for Siri or its other AI technology. The opportunity to turn Apple Intelligence into a moneymaker has effectively passed.

What would have been more newsworthy here is if Apple’s A.I. strategy were anything other than building software exclusively for its proprietary hardware. This does not sound like a “revamped” strategy; it sounds like Apple’s whole deal. If it can use Apple Intelligence or Siri in the future, it certainly might; it is putting ads in Apple Maps after all. Services is a money-printing machine with less risk. But it is still a hardware company.

This part made me double-take and wonder if I missed something. In February 2024, following Apple’s cancellation of its car project, Gurman predicted that hardware would continue to be Apple’s primary business “for now”, as though that will change in the near future. This has been constant since Apple Intelligence was announced at WWDC that year.

What one could argue has been a change of strategy is the rumoured development of a chatbot; Gurman called it a “strategic shift” when he broke the news. But that, too, is somewhat inaccurate in two ways: Gurman’s description of it is as an overhauled version of Siri that will let people do normal Siri stuff — setting timers, end of list — plus some of the features Apple announced in 2024 but has not yet shipped which, confusingly, were also first set to ship in an update to iOS 26 without the wholly new version of Siri but also depending on Gemini. Got it?

But even that is not much of a strategy shift. Gurman tweeted in May 2024 — before WWDC and the debut of Apple Intelligence — that “Apple isn’t building its own chatbot but knows the market wants it so it’s going elsewhere for it. It’s the same playbook as search.” So, again, it is just borrowing from its ages-old playbook. It will continue to have proprietary stuff that ostensibly works seamlessly across a user’s Apple-branded hardware, allow installation of third-party add-ons, and rely on Google for some core functionality. How, exactly, is this a “revamp”?

Anyway, here is what Gurman wrote in January after the Gemini announcement and before the first build of iOS 26.4 was released:

Today, Apple appears to be less than a month away from unveiling the results of this partnership. The company has been planning an announcement of the new Siri in the second half of February, when it will give demonstrations of the functionality.

Whether that takes the form of a major event or a smaller, tightly controlled briefing — perhaps at Apple’s New York media loft — remains unclear. Either way, Apple is just weeks away from finally delivering on the Siri promises made at its Worldwide Developers Conference back in June 2024. At long last, the assistant should be able to tap into personal data and on-screen content to fulfill tasks.

Apple today shipped the first build of iOS 26.5 to developers without any sign of those features. While they may come in a later build, Juli Clover, of MacRumors, speculates they have been kicked to iOS 27.

Does not seem like much has changed at all.

⌥ I Regret the Blood Pact I Have Made With iCloud Photos

By: Nick Heer

Sometimes, I do not recognize a trap until I am already in it. Photos in iCloud is one such situation.

When Apple launched iCloud Photo Library in 2014, I was all-in. Not only is it where I store the photos I take on my iPhone, it is where I keep the ones from my digital cameras and my film scans, and everything from my old iPhoto and Aperture libraries. I have culled a bunch of bad photos and I try not to hoard, but it is more-or-less a catalogue of every photo I have taken since mid-2007. I like the idea of a centralized database of my photos, available on all my devices, that is functionally part of my backup strategy.1

But, also, it is large. When I started putting photos in there eleven years ago with a 200 GB plan, I failed to recognize it would become an albatross. iCloud Storage says it is now 1.5 TB and, between the amount of other stuff I have in iCloud and my Family Sharing usage, I have just 82 GB of available space. 2 TB seemed like such a large amount of space until I used 1.9 of it.

Apple’s next iCloud tier is a generous 6 TB, but it costs another $324 per year. I could buy a new 6 TB hard disk annually for that kind of money. While upgrading tiers is, by far, the easiest way to solve this problem, it only kicks that can down that road, the end of which currently has whatever two terabytes’ worth of cans looks like.

A better solution is to recognize I do not need instant access to all 95,000 photos in my library, but iCloud has no room for this kind of nuance. The iCloud syncing preference is either on or off for the entire library.

Unfortunately, trying to explain what goes wrong when you try to deviate from Apple’s model of how photo libraries ought to work will become a bit of a rant. And I will preface this by saying this is all using Photos running on MacOS Ventura, which is many years behind the most recent version of MacOS. It is not possible for me to use the latest version of Photos to make these changes because upgraded libraries cannot be opened by older versions of Photos. However, in my defense, I will also note that the version on Ventura is Photos 8.0 and these are the kinds of bugs and omissions inexcusable after that many revisions.

So: the next best thing is to create a separate Photos library — one that will remain unsynced with iCloud. Photos makes this pretty easy by launching while holding the Option (⌥) key. But how does one move images from one library to the other? Photos is a single-window application — you cannot even open different images in new windows, let alone run separate libraries in separate windows. This should be possible, but it is not.

As a workaround, Apple allows you to import images from one Photos library into another — but not if the source library is synced with iCloud. You therefore need to turn off iCloud sync before proceeding, at which point you may discover that iCloud is not as dependable as you might have expected.

I have “Download Originals to this Mac” enabled, which means that Photos should — should — retain a full copy of my library on my local disk. But when I unchecked the “iCloud Photos” box in Settings, I was greeted by a dialog box informing me that I would lose 817 low-resolution local copies, something which should not exist given my settings, though reassuring me that the originals were indeed safe in iCloud. There is no way to know which photos these are nor, therefore, any way to confirm they are actually stored at full resolution in iCloud. I tried all the usual troubleshooting steps. I repaired my library, then attempted to turn off iCloud Photos; now I had 850 low-resolution local copies. I tried a neat trick where you select all the pictures in your library and select “Play Slideshow”, at which point my Mac said it was downloading 733 original images, then I tried turning off iCloud Photos again and was told I would lose around 150 low-resolution copies.

You will note none of these numbers add or resolve correctly. That is, I have learned, pretty standard for Photos. Currently, it says I have 94,529 photos and 898 videos in the “Library” view, but if I select all the items in that view, it says there are a total of 95,433 items selected, which is not the same as 94,529 + 898. It is only a difference of six items but, also, it is an inexplicable difference of six.

At this point, I figured I would assume those 150 photos were probably in iCloud, sacrifice the low-resolution local copies, and prepare for importing into the second non-synced library I had created. So I did that, switched libraries, and selected my main library for import. You might think reading one Photos library from another stored on the same SSD would be pretty quick. Yes, there are over 95,000 items and they all have associated thumbnails, but it takes only a beat to load the library from scratch in Photos.

It took over thirty minutes.

After I patiently waited that out, I selected a batch of photos from a specific event and chose to import them into an album, so they stay categorized. Oh, that is right — just because you are importing across Photos libraries, that does not mean the structure will be retained. There is no way, as far as I can tell, to keep the same albums across libraries; you need to rebuild them.

After those finished importing, I pulled up my main library again to do the next event. You might expect it to retain some memory of the import source I had only just accessed. No — it took another thirty minutes to load. It does this every time I want to import media from my main library. It is not like that library is changing; it is no longer synced with iCloud, remember. It just treats every time it is opened as the first time.

And it was at this point I realized the importer did not display my library in an organized or logical fashion. I had expected it to be sorted old-to-new since that is how Photos says it is displayed, but I saw photos from many different years all jumbled together. It is almost in order, at times, but then I would notice sequential photos scattered all over.

My guess — and this is only a guess — is that it sub-orders by album, but does no further sorting after that. This is a problem for me given a quirk in my organizational structure. In addition to albums for different events, I have smart albums for each of my cameras and each of my iPhone’s individual lenses. But that still does not excuse the importer’s inability to sort old-to-new. The event I spotted early on and was able to import was basically a fluke. If I continued using this cross-library importing strategy, I would not be able to keep track of which photos I could remove from my main library.

There is another option, which is to export a selection of unmodified originals from my primary library to a folder on disk, and then switch libraries, and import them. This is an imperfect solution. Most obviously, it requires a healthy amount of spare disk space, enough to store the selected set of photos thrice, at least temporarily: once in the primary library, once in the folder, and once in the new library. It also means any adjustments made using the Photos app will be discarded — but, then again, importing directly from the library only copies the edited version of a photo without any of its history or adjustments preserved.

What I would not do, under any circumstance — and what I would strongly recommend anyone avoiding — is to use the Export Photos option. This will produce a bunch of lossy-compressed photos, and you do not want that.

Anyway, on my first attempt of trying the export-originals-then-import process, I exported the 20,528 oldest photos in my library to a folder. Then I switched to the archive library I had created, and imported that same folder. After it was complete, Photos said it had imported 17,848 items, a difference of nearly 3,000 photos. To answer your question: no, I have no idea why, or which ones, or what happened here.

This sucks. And it particularly sucks because most data is at least kind of important, but photos are really important, and I cannot trust this application to handle them.

There is this quote that has stuck with me for nearly twenty years, from Scott Forstall’s introduction to Time Machine (31:30) at WWDC 2006. Maybe it is the message itself or maybe it is the perfectly timed voice crack on the word “awful”, but this resonated with me:

When I look on my Mac, I find these pictures of my kids that, to me, are absolutely priceless. And in fact, I have thousands of these photos.

If I were to lose a single one of these photos, it would be awful. But if I were to lose all of these photos because my hard drive died, I’d be devastated. I never, ever want to lose these photos.

I have this library stored locally and backed up, or at least I though I did. I thought I could trust iCloud to be an extra layer of insurance. What I am now realizing is that iCloud may, in fact, be a liability. The simple fact is that I have no idea the state my photos library is currently in: which photos I have in full resolution locally, which ones are low-resolution with iCloud originals, and which ones have possibly been lost.

The kindest and least cynical interpretation of the state of iCloud Photos is that Apple does not care nearly enough about this “absolutely priceless” data. (A more cynical explanation is, of course, that services revenue has compromised Apple’s standards.) Many of these photos are, in fact, priceless to me, which is why I am questioning whether I want iCloud involved at all. I certainly have no reason to give Apple more money each month to keep wrecking my library.

I will need to dedicate real, significant time to minimizing my iCloud dependence. I will need to check and re-check everything I do as best I can, while recognizing the difficulty I will have in doing so with the limited information I have in my iCloud account. This is undeniably frustrating. I am glad I caught this, however, as I sure had not previously thought nearly as much as I should have about the integrity of my library. Now, I am correcting for it. I hope it is not too late.


  1. It is no longer the sole place I store my photos. I have everything stored locally, too, and that gets backed up with Backblaze. Or, at least, I think I have everything stored locally. ↥︎

Bill C–22 Gives Canadian Authorities Additional Warrantless Powers

By: Nick Heer

Gabriel Hilty, Toronto Star:

Speaking alongside Chief Myron Demkiw on Thursday at Toronto police headquarters, Public Safety Minister Gary Anandasangaree said Bill C-22, the Lawful Access Act, will “create a legal framework for modernized, lawful access regime in Canada,” something that police forces have been requested “for decades.”

The bill is Prime Minister Mark Carney government’s second push to pass expanded police search powers into law. An earlier proposal on lawful access was met with widespread concerns over potential overreach.

Paula Tran, Ottawa Citizen:

“The bill effectively lowers the standard that police have to meet. Sure, law enforcement says they’re happy, but that means they need less evidence and need to do less work to get the information about subscribers, and I don’t think that’s that’s a good thing. It’s the lowest standard in Canadian criminal law,” [Michael] Geist said.

[…]

Bill C-22 also proposes new legislation that would compel telecommunication companies to store and retain client metadata, like device location, for a year and to make it available to law enforcement and CSIS with a warrant. The metadata can be used to track a person’s live location in case they pose a national security threat or are considered to be in danger.

OpenMedia is running a campaign to email Members of Parliament, though I am suspicious these form letter campaigns actually work. It is a bare minimum signal since it requires almost no commitment. My M.P. is usually opposed to anything proposed by this government, since he is in the official opposition, but his reaction to this bill’s much worse predecessor is that it contained “the most commonsensical security changes we need to make in Canada”. I expect I will be writing him and, when I do, I will be sure to adjust OpenMedia’s form letter. If you are writing to your M.P., I suggest you do the same if you can spare the time.

⌥ Permalink

Wealthsimple Clears Regulatory Hurdle to Bring ‘Prediction Markets’ to Canada

By: Nick Heer

Meera Raman, Globe and Mail:

Wealthsimple is seeking to offer prediction trading in Canada, a controversial type of betting on real-world events that has surged in popularity in the past year, and has been largely banned in this country.

[…]

The approval for Ontario-based Wealthsimple permits it only to offer contracts tied to economic indicators, financial markets and climate trends, the company confirmed – not sports or elections, which are among the most popular uses of prediction markets in the United States.

Interactive Brokers launched here last April. Why are we doing this to ourselves?

⌥ Permalink

A Different Perspective on the ‘Design Choices’ Social Media Company Verdicts

By: Nick Heer

Mike Masnick, of Techdirt, unsurprisingly opposes the verdicts earlier this week finding Meta and Google guilty of liability for how their products impact children’s safety. I think it is a perspective worth reading. Unlike the Wall Street Journal, Masnick respects your intelligence and brings actual substance. Still, I have some disagreements.

Masnick, on the “design choices” argument:

This distinction — between “design” and “content” — sounds reasonable for about three seconds. Then you realize it falls apart completely.

Here’s a thought experiment: imagine Instagram, but every single post is a video of paint drying. Same infinite scroll. Same autoplay. Same algorithmic recommendations. Same notification systems. Is anyone addicted? Is anyone harmed? Is anyone suing?

Of course not. Because infinite scroll is not inherently harmful. Autoplay is not inherently harmful. Algorithmic recommendations are not inherently harmful. These features only matter because of the content they deliver. The “addictive design” does nothing without the underlying user-generated content that makes people want to keep scrolling.

This sounds like a reasonable retort until you think about it for three more seconds and realize that the lack of neutrality in the outcomes of these decisions is the entire point. Users post all kinds of stuff on social media platforms, and those posts can be delivered in all kinds of different ways, as Masnick also writes. They can be shown in reverse-chronological order in a lengthy scroll, or they can be shown one at a time like with Stories. The source of the posts someone sees might be limited to just accounts a user has opted into, or it can be broadened to any account from anyone in the world. Twitter used to have a public “firehose” feed.

But many of the biggest and most popular platforms have coalesced around a feed of material users did not ask for. This is not like television, where each show has been produced and vetted by human beings, and there are expectations for what is on at different times of the day. This is automated and users have virtually no control within the platforms themselves. If you do not like what Instagram is serving you on your main feed, your choice is to stop using Instagram entirely — even if you like and use other features.

Platforms know people will post objectionable and graphic material if they are given a text box or an upload button. We know it is “impossible” to moderate a platform well at scale. But we are supposed to believe they have basically no responsibility for what users post and what their systems surface in users’ feeds? Pick one.

Masnick, on the risks of legal accountability for smaller platforms:

And this is already happening. TikTok and Snap were also named as defendants in the California case. They both settled before trial — not because they necessarily thought they’d lose on the merits, but because the cost of fighting through a multi-week jury trial can be staggering. If companies the size of TikTok and Snap can’t stomach the expense, imagine what this means for mid-size platforms, small forums, or individual website operators.

I am going to need a citation that TikTok and Snap caved because they could not afford continuing to fight. It seems just as plausible they could see which way the winds were blowing, given what I have read so far in the evidence that has been released.

Masnick:

One of the key pieces of evidence the New Mexico attorney general used against Meta was the company’s 2023 decision to add end-to-end encryption to Facebook Messenger. The argument went like this: predators used Messenger to groom minors and exchange child sexual abuse material. By encrypting those messages, Meta made it harder for law enforcement to access evidence of those crimes. Therefore, the encryption was a design choice that enabled harm.

The state is now seeking court-mandated changes including “protecting minors from encrypted communications that shield bad actors.”

Yes, the end result of the New Mexico ruling might be that Meta is ordered to make everyone’s communications less secure. That should be terrifying to everyone. Even those cheering on the verdict.

This is undeniably a worrisome precedent. I will note Raúl Torrez, New Mexico’s Attorney General and the man who brought this case against Meta, says he wants to do so for minors only. The implementation of this is an obvious question, though one that mandated age-gating would admittedly make straightforward.

Meta cited low usage when it announced earlier this month that it would be turning off end-to-end encryption in Instagram. If it is a question of safety or liability, it is one Meta would probably find difficult to articulate given end-to-end encryption remains available and enabled by default in Messenger and WhatsApp. An executive raised concerns about the feature when it was being planned, drawing a distinction between it and WhatsApp because the latter “does not make it easy to make social connections, meaning making Messenger e2ee will be far, far worse”.

I think Masnick makes some good arguments in this piece and raises some good questions. It is very possible or even likely this all gets unwound when it is appealed. I, too, expect the ripple effects of these cases to create some chaos. But I do not think the correct response to a lack of corporate accountability — or, frankly, standards — is, in Masnick’s words, “actually funding mental health care for young people”. That is not to say mental health should not be funded, only that it is a red herring response. In the U.S., total spending on children’s mental health care rose by 50% between 2011 and 2017; it continued to rise through the pandemic, of course. Perhaps that is not enough. But, also, it is extraordinary to think that we should allow companies to do knowingly harmful things and expect everyone else to correct for the predictable outcomes.

⌥ Permalink

Apple Discontinues the Mac Pro

By: Nick Heer

Chance Miller, of 9to5Mac, serving here as Apple’s official bad news launderer:

It’s the end of an era: Apple has confirmed to 9to5Mac that the Mac Pro is being discontinued. It has been removed from Apple’s website as of Thursday afternoon. The “buy” page on Apple’s website for the Mac Pro now redirects to the Mac’s homepage, where all references have been removed.

Apple has also confirmed to 9to5Mac that it has no plans to offer future Mac Pro hardware.

Mark Gurman reported last year that it was “on the back burner”.

The Mac Pro was, realistically, killed off when the Apple Silicon era ended support for expandability and upgradability. The Mac Studio effectively takes its place, and is strategically similar to the “trash can” Mac Pro with all expandability offloaded to external peripherals. Unfortunate, but I think it was dishonest to keep selling this version of a “pro” Macintosh.

⌥ Permalink

Meta Loses Two Landmark Cases Regarding Product Safety and Children’s Use; Google Loses One

By: Nick Heer

Morgan Lee, Associated Press:

A New Mexico jury found Tuesday that social media conglomerate Meta is harmful to children’s mental health and in violation of state consumer protection law.

The landmark decision comes after a nearly seven-week trial. Jurors sided with state prosecutors who argued that Meta — which owns Instagram, Facebook and WhatsApp — prioritized profits over safety. The jury determined Meta violated parts of the state’s Unfair Practices Act on accusations the company hid what it knew [about] the dangers of child sexual exploitation on its platforms and impacts on child mental health.

Meta communications jackass Andy Stone noted on X his company’s delight to be liable for “a fraction of what the State sought”. The company says it will appeal the verdict.

Stephen Morris and Hannah Murphy, Financial Times:

Meta and Google were found liable in a landmark legal case that social media platforms are designed to be addictive to children, opening up the tech giants to penalties in thousands of similar claims filed around the US.

A jury in the Los Angeles trial on Wednesday returned a verdict after nine days of deliberation, finding Meta’s platforms such as Instagram and Google’s YouTube were harmful to children and teenagers and that the companies failed to warn users of the dangers.

Dara Kerr, the Guardian:

To come to its liability decision, the jury was asked whether the companies’ negligence was a substantial factor in causing harm to KGM [the plaintiff] and if the tech firms knew the design of their products was dangerous. The 12-person panel of jurors returned a 10-2 split answering in favor of the plaintiff on every single question.

Meta says it will also appeal this verdict.

Sonja Sharp, Los Angeles Times:

Collectively, the suits seek to prove that harm flowed not from user content but from the design and operation of the platforms themselves.

That’s a critical legal distinction, experts say. Social media companies have so far been protected by a powerful 1996 law called Section 230, which has shielded the apps from responsibility for what happens to children who use it.

For its part, the Wall Street Journal editorial board is standing up for beleaguered social media companies in an editorial today criticizing everything about these verdicts, including this specific means of liability, which it calls a “dodge” around Section 230.

But it is not. The principles described by Section 230 are a good foundation for the internet. This law, while U.S.-centric, has enabled the web around the world to flourish. Making companies legally liable for the things users post will not fix the mess we are in, but it would cause great damage if enacted.

Product design, though, is a different question. It would be a mistake, I think, to read Section 230 as a blanket allowance for any way platforms wish to use or display users’ posts. (Update: In part, that is because it is a free speech question.) From my entirely layman perspective, it has never struck me as entirely reasonable that the recommendations systems of these platforms should have no duty or expectation of care.

The Journal’s editorial board largely exists to produce rage bait and defend the interests of the powerful, so I am loath to give it too much attention, but I thought this paragraph was pretty rich:

Trial lawyers and juries may figure that Big Tech companies can afford to pay, but extorting companies is certain to have downstream consequences. Meta and Google are spending hundreds of billions of dollars on artificial intelligence this year, which could have positive social impacts such as accelerating treatments for cancer.

Do not sue tech companies because they could be finding cancer treatments — why should I take this editorial board seriously if its members are writing jokes like these? They think you are stupid.

As for the two cases, I am curious about how these conclusions actually play out. I imagine other people who feel their lives have been eroded by the specific way these platforms are designed will be able to test their claims in court, too, and that it will be complicated by the inevitably lengthy appeals and relitigation process.

I am admittedly a little irritated by both decisions being reached by jury instead of a judge; I would have preferred to see reasoning instead of overwhelming agreement among random people. However, it sends a strong signal to big social media platforms that people saw and heard evidence about how these products are designed, and they agreed it was damaging. This is true of all users, not just children. Meta tunes its feeds (PDF) for maximizing engagement across the board, and it surely is not the only one. There are a staggering number of partially redacted exhibits released today to go through, if one is so inclined.

If these big social platforms are listening, the signals are out there: people may be spending a lot of time with these products, but that is not a good proxy for their enjoyment or satisfaction. Research indicates a moderate amount of use is correlated with neutral or even positive outcomes among children, yet there are too many incentives in these apps to push past self-control mechanisms. These products should be designed differently.

⌥ Permalink

Meta Laid Off Several Hundred People Today

By: Nick Heer

Ashley Capoot and Jonathan Vanian, CNBC:

Meta is laying off several hundred employees on Wednesday, CNBC confirmed.

The cuts are happening across several different organizations within the company, including Facebook, global operations, recruiting, sales and its virtual reality division Reality Labs, according to a source familiar with the company’s plans who asked not to be named because they are confidential.

Some impacted employees are being offered new roles within the company, the person said. In some cases, those new positions will require relocation.

“Several hundred” employees is a long way off from the numbers reported earlier this month. Perhaps Reuters got it all wrong but, more worryingly for employees, perhaps those figures were correct and this is only the beginning.

⌥ Permalink

Talking Liquid Glass With Apple

By: Nick Heer

Danny Bolella attended one of Apple’s “Let’s Talk Liquid Glass” workshops:

Let’s address the elephant in the room. If you read the comments on my articles or browse the iOS subreddits, there is a vocal contingent of developers betting that Apple is going to roll back Liquid Glass.

The rationale usually points to the initial community backlash, the slower adoption rate of iOS 26, and the news that Alan Dye left Apple for Meta. The prevailing theory has been: “Just wait it out. They’ll revert to flat design.”

I shared this exact sentiment with the Apple team.

Their reaction? Genuine shock. They were actually concerned that developers were holding onto this position. They made it emphatically clear that Liquid Glass is absolutely moving forward, evolving, and expanding across the ecosystem.

Unsurprising. Though I expect a number of people reading this will be disappointed, I cannot imagine a world in which Apple would either revert to its previous design language or whip together something new. It is going to ride Liquid Glass and evolve it for a long time; if history is a good rule of thumb, assume ten years.

In theory, this is a good thing. Even on MacOS, I can find things I prefer to its predecessor, though admittedly they are few and far between. This visual design feels much more at home on iOS. The things that cause me far more frustration on a daily basis are the unrelenting bugs across Apple’s ecosystem, like how I just finished listening to an album with my headphones and then, when I clicked “play” on a new album, Music on MacOS decided it should AirPlay to my television instead of continuing through my headphones. That kind of stuff.

Regardless of whatever one thinks the visual qualities of Liquid Glass, the software quality problem is notable there, too. We are now on the OS 26.4 set of releases and I am still running into plenty of instances with bizarre and distracting compositing problems. On my iPhone, the gradients that are supposed to help with legibility in the status bar and toolbar appear, disappear, and change colour with seemingly little relevance to what is underneath them. Notification Centre remains illegible until it is fully pulled down. Plus, I still see the kinds of graphics bugs and Auto Layout problems I have seen for a decade.

I hope to see a more fully considered version of the Liquid Glass design language at WWDC this year, and not merely from a visual perspective. This user interface is software, just like dedicated applications, and it is chockablock full of bugs.

Bolella, emphasis mine:

I plan to share an article soon where I break down the exact physics, z-axis rules, and “Barbell Layouts” of this hierarchy. But the high-level takeaway from the NYC labs is crystal clear: maximize your content, push your controls to the poles, and never let the interface compete with the information.

If you say so, Apple.

⌥ Permalink

OpenAI to Discontinue Sora App, Video Platform

By: Nick Heer

Berber Jin, Wall Street Journal:

CEO Sam Altman announced the changes to staff on Tuesday, writing that the company would wind down products that use its video models. In addition to the consumer app, OpenAI is also discontinuing a version of Sora for developers and won’t support video functionality inside ChatGPT, either.

OpenAI is not shutting this down because it has ethical qualms with what it has created, despite good reasons to do just that. It is because it is expensive without any clear reason for it to exist other than because OpenAI wants to be everywhere.

If you are desperate for a completely synthetic social media feed, Meta’s Vibes is apparently still around. Users are readily abusing it, of course, because that is what happens if you give people a text input box.

Update: In a tweet, OpenAI has confirmed it is shutting down Sora. But, while it originally announced “We’re saying goodbye to Sora”, it changed that about an hour later to read “We’re saying goodbye to the Sora app“, emphasis mine. The Journal has not changed its report to retract claims about shutting down the platform altogether, though, while OpenAI continues to promote Sora API pricing.

⌥ Permalink

Ads Are Coming to Apple Maps Later This Year

By: Nick Heer

Apple, in a press release with the title “Introducing Apple Business — a new all‑in‑one platform for businesses of all sizes”, buried in a section tucked in the middle labelled “Enhanced Discoverability in Apple Maps”, both of which are so anodyne as to encourage missing this key bit of news:

Every day, users choose Apple Maps to discover and explore places and businesses around them. Beginning this summer in the U.S. and Canada, businesses will have a new way to be discovered by using Apple Business to create ads on Maps. Ads on Maps will appear when users search in Maps, and can appear at the top of a user’s search results based on relevance, as well as at the top of a new Suggested Places experience in Maps, which will display recommendations based on what’s trending nearby, the user’s recent searches, and more. Ads will be clearly marked to ensure transparency for Maps users.

The way they are “clearly marked” is with a light blue background and a small “Ad” badge, though it is worth noting Apple has been testing an even less obvious demarcation for App Store ads. In the case of the App Store, I have found the advertising blitz junks up search results more than it helps me find things I am interested in.

This is surely not something users are asking for. I would settle for a more reliable search engine, one that prioritizes results immediately near me instead of finding places in cities often hundreds of kilometres away. There are no details yet on what targeting advertisers will be allowed to use, but it will be extremely frustrating if the only reason I begin seeing more immediately relevant results is because a local business had to pay for the spot.

Update: I have this one little nagging thought I cannot shake. Maps has been an imperfect — to be kind — app for nearly fifteen years, but it was ultimately a self-evident piece of good software, at least in theory. It was a directory of points-of-interest, and a means of getting directions. With this announcement, it becomes a container for advertising. Its primary function feels corrupted, at least a little bit, because what users care about is now subservient to the interests of the businesses paying Apple.

⌥ Permalink

Someone Has Publicly Leaked an Exploit Kit That Can Hack Millions of iPhones

By: Nick Heer

Lorenzo Franceschi-Bicchierai and Zack Whittaker, TechCrunch:

Last week, cybersecurity researchers uncovered a hacking campaign targeting iPhone users that used an advanced hacking tool called DarkSword. Now someone has leaked a newer version of DarkSword and published it on the code-sharing site GitHub.

Researchers are warning that this will allow any hacker to easily use the tools to target iPhone users running older versions of Apple’s operating systems who have not yet updated to its latest iOS 26 software. This likely affects hundreds of millions of actively used iPhones and iPads, according to Apple’s own data on out-of-date devices.

This is an entirely different exploit chain to the “Coruna” one which also surfaced earlier this month — so now there are two massive security exploits just floating around in the wild affecting a large number of iPhones. Apple is apparently concerned enough about these vulnerabilities that it is issuing patches as far back as iOS 15 though, disappointingly, only for devices that do not support newer major versions. If you have a device that can run iOS 26, you will be safer if it is running iOS 26.

It is, I should say, pretty brazen for the developers of this exploit chain to call the JavaScript file “rce_loader.js”. RCE stands for remote code execution. It is basically like calling the file “hacking_happens_here.js”.

⌥ Permalink

In a ‘Test’, Google Is Automatically Rewriting News Headlines in Its Search Results

By: Nick Heer

Sean Hollister, the Verge:

Since roughly the turn of the millennium, Google Search has been the bedrock of the web. People loved Google’s trustworthy “10 blue links” search experience and its unspoken promise: The website you click is the website you get.

Now, Google is beginning to replace news headlines in its search results with ones that are AI-generated. After doing something similar in its Google Discover news feed, it’s starting to mess with headlines in the traditional “10 blue links,” too. We’ve found multiple examples where Google replaced headlines we wrote with ones we did not, sometimes changing their meaning in the process.

As I noted when I linked to Hollister’s article about Discover back in December, this is not new in search results; it has been happening for years.

Danny Goodwin, Search Engine Land:

Dig deeper. Google changed 76% of title tags in Q1 2025 – Here’s what that means […]

According to the Google Search Central section on title links, originally published in 2021:

I am not arguing this is good or normal — the examples Hollister shows are extremely poor reflections of the articles in question — but I do not understand why it is only gaining traction now, nor how it meaningfully differs from what Google has been doing all along. It is indeed frustrating.

Many of the results you see in Google Search misrepresent the source material and are misleading. But that has been true for a while — which is a problem unto itself. People should not trust the results they see as represented by Google Search. The visual tone Google has maintained, however, is that it is a neutral directory. The summaries in A.I. Overview are delivered with an unearned dry authority, and the ten links below it are there because of a tense truce between Google’s goals and those of search optimization professionals.

Also, I had no idea that Search Engine Land had been acquired at some point by Semrush which, in turn, was bought by Adobe.

⌥ Permalink

Lobbying Firms Funded by Apple and Meta Are Duelling on Age Verification

By: Nick Heer

Emily Birnbaum, writing for Bloomberg in July:

Meta is also helping to fund the Digital Childhood Alliance, a coalition of conservative groups leading efforts to pass app-store age verification, according to three people familiar with the funding.

The App Store Accountability Act is based on model legislation written by the Digital Childhood Alliance. The lobbying group also publishes marketing pieces, including one (PDF) that calls Apple’s age verification frameworks “ineffective”. Specifically, it points to the lack of parental consent required “for kids to enter into complex contracts”, with “no way to verify that parental consent has been obtained”.

Meta, for its part, requires users to self-report their birthday and click a button that says “I agree” to create an Instagram account. In fairness, the title of that page says “read and agree to our terms” and, on the terms page, Meta does say you need to be 13 years old. This is pretty standard stuff but, if Meta actually cared about this, it could voluntarily implement the stricter controls at sign-up without a legislative incentive.

Though this article was published last year, I am linking to it now because something called the TBOTE Project recently resurfaced these findings and added some of its own in an open source investigation. Unlike similar investigations from sources like Bellingcat, it does not appear that the person or people behind TBOTE have editors or fact-checkers to verify their interpretation of this information. That does not mean it is useless; it is simply worth exercising some caution. Regardless, their findings show a massive amount of lobbyist spending on Meta’s part to try and get these laws passed.

Birnbaum continues:

The App Association, a group backed by Apple, has been running ads in Texas, Alabama, Louisiana and Ohio arguing that the app store age verification bills are backed by porn websites and companies. The adult entertainment industry’s main lobby said it is not pushing for the bills; pornography is mostly banned from app stores.

This is obviously bad faith, but also flawed in the opposite direction: the porn industry wants device-level verification.

⌥ Permalink

Tech CEOs and Investors Are Just Saying Stuff

By: Nick Heer

Jacob Silverman, Business Insider:

The growing bloat of popular tech rhetoric could serve as evidence for how the tech industry, having conquered so much of daily life, work, and entertainment, has begun to exhaust its imaginative capacities. Industry leaders promised that the mammoth capital for AI outlay would lead to the creation of a smarter-than-human intelligence that would serve as a universal solvent, fixing climate change, poverty, and even the problem of death itself. But that horizon — which we are supposed to reach by pumping out more fossil-fuel emissions and destabilizing labor and education — remains impossibly far away.

Gallup’s polling on views of different business sectors has, frustratingly, no ability to permalink to a particular industry and its historical rankings; so, you will need to go down to “Industry and Business Sector Ratings, B Through E” and then click the pagination arrow to get to “Computer Industry” on the second page. Once there, you will find what seem at first glance to be some remarkably stable figures.

Look a little closer, though, and the numbers tell a different story. Summing the “very” and “somewhat” figures for each type of response shows a marked decline in positive reception since a high in 2017, and a steady climb in negative reception. There are lots of reasons for this; many of them I have written about. But I do not think these loudmouth executives are doing the industry any favours by bullshitting their way through interviews and promising nonsense.

That is the data-driven answer. These guys also just sound really stupid when they say stuff like “it also takes a lot of energy to train a human” or “the long-term vision is to […] create a tradeable asset out of any difference in opinion” or “I bought Twitter […] to try to help humanity, whom I love”. I know I am writing this on a website called Pixel Envy and I am, well, me, but these barons sound comical and dorky.

⌥ Permalink

Adobe Pays Early Termination Fee, or ‘Settlement’, in U.S. Lawsuit Over Hidden Fees

By: Nick Heer

When the United States Federal Trade Commission and Department of Justice jointly filed a lawsuit in 2024 against Adobe, I commented on the similarities between that complaint and the one against Amazon. Both are about the ease of entering into subscriptions that are later difficult or expensive to leave, both had alleged personal liability by executives — and, now, both have been settled out of court.

Michael Kan, PC Magazine:

Adobe has settled a 2024 lawsuit from the US government that alleged the company used hidden fees to trap users into paying for subscriptions.

On Friday, Adobe “finalized” an agreement with the Justice Department, which accused the software vendor of failing to inform new customers about payment terms or early termination fees. “While we disagree with the government’s claims and deny any wrongdoing, we are pleased to resolve this matter,” Adobe says.

I am sure Adobe has learned its lesson. Let us go and check its work. In its statement, Adobe says it has “made [its] sign-up and cancellation processes even more streamlined and transparent”. Here is how it describes its annual pricing, billed monthly, on its U.S. website:

Fee applies of half your remaining annual commitment if you cancel after Mar 31.

This is not the most direct sentence, but it is an accurate explanation of how much the fee will be, and when that fee takes effect — fourteen days from when I am writing this. It is followed by a little “i” informational icon. Clicking on it will display a callout noting when service will be cut off. For comparison, here is the equivalent disclaimer on its Canadian site:

Fee applies if you cancel after 14 days.

Here, too, there is a little informational icon. When you hover over it, Adobe says the same thing about cancellation, and adds that cancelling will incur an early termination fee. It is the same on the U.K. site.

What is the answer here? Does each country need to sue Adobe for its billing flow to disclose a reasonable amount of information?

⌥ Permalink

Meta Realizes Horizon Worlds on Quest Never Had Legs, Will Shut It Down in June

By: Nick Heer

A few weeks ago, Meta published an update from Samantha Ryan, of Reality Labs, announced a “renewed focus” and a “doubling down” on virtual reality. It planned to achieve this by “almost exclusively” betting its future on the smartphone Horizon Worlds app.

In an announcement today, Meta shifted its definition of “almost exclusively” to simply “exclusively”:

Earlier this year, we shared an update on our renewed focus for VR and Horizon. We are separating the two platforms so each can grow with greater focus, and the Horizon Worlds platform will become a mobile-only experience. This separation will extend across our ecosystem, including our mobile app. To support this vision, we are making the following changes to streamline your Quest experience throughout 2026.

This opening paragraph is opaque and, though the announcement goes on to explain exactly what is happening, it is not nearly as clear as the email sent to Horizon Worlds users. I really think Meta is looking to exit from its pure V.R. efforts, especially with the sales success of the perv glasses.

As I write this, the Horizon app for iOS is the sixty-ninth most popular free game in the Canadian App Store, just behind Wordscapes and ahead of Perfect Makeover Cleaning ASMR. Nice?

⌥ Permalink

A Roadmap for Currency Symbol Implementation

By: Nick Heer

The Unicode Consortium would like to remind you to work closely with them if you are introducing a new symbol for your currency:

Such public usage leads to a need for the symbol to be encoded in the Unicode Standard and supported in commercial software and services. Standardization of a new character and subsequent support by vendors takes time: typically, at least one year, and often longer. All too often, however, monetary authorities announce creation of a new currency symbol anticipating immediate public adoption, then later discover there will be an unavoidable delay before the new symbol is widely supported in products and services.

I had no idea so many currency symbols had been introduced recently. Then again, before I read this, I had not given much thought to the one we use: $.

Hephzibah Anderson, for the BBC, in 2019:

The most widely accepted theory does in fact involve Spanish coinage, and it goes like this: in the colonies, trade between Spanish Americans and English Americans was lively, and the peso, or peso de ocho reales, was legal tender in the US until 1857. It was often shortened, so historians tell us, to the initial ‘P’ with an ‘S’ hovering beside it in superscript. Gradually, thanks to the scrawl of time-pressed merchants and scribes, that ‘P’ merged with the ‘S’ and lost its curve, leaving the vertical stroke like a stake down the centre of the ‘S’. A Spanish dollar was more or less worth an American dollar, so it’s easy to see how the sign might have transferred.

Not only the explanation for why all the world’s dollars have the same symbol, but also why we share it with the peso.

⌥ Permalink

Updating Ubuntu packages that you have local changes for with dgit

By: cks

Suppose, not entirely hypothetically, that you've made local changes to an Ubuntu package using dgit and now Ubuntu has come out with an update to that package that you want to switch to, with your local changes still on top. Back when I wrote about moving local changes to a new Ubuntu release with dgit, I wrote an appendix with a theory of how to do this, based on a conversation. Now that I've actually done this, I've discovered that there is a minor variation and I'm going to write it down explicitly (with additional notes because I forgot some things between then and now).

I'll assume we're starting from an existing dgit based repository with a full setup of local changes, including an updated debian/changelog. Our first step, for safety, is to make a branch to capture the current state of our repository. I suggest you name this branch after the current upstream package version that you're on top of, for example if the current upstream version you're adding local changes to can be summarized as 'ubuntu2.6':

git branch cslab-2.6

Making a branch allows you to use 'git diff cslab-2.6..' later to see exactly what changed between your versions. A useful thing to do here is to exclude the 'debian/' directory from diffs, which can be done with 'git diff cslab-2.6.. -- . :!debian', although your shell may require you to quote the '!' (cf).

Then we need to use dgit to fetch the upstream updates:

dgit fetch -d ubuntu

We need to use '-d ubuntu', at least in current versions of dgit, or 'dgit fetch' gets confused and fails. At this point we have the updated upstream in the remote tracking branch 'dgit/dgit/jammy,-security,-updates' but our local tree is still not updated.

(All of dgit's remote tracking branches start with 'dgit/dgit/', while all of its local branches start with just 'dgit/'. This is less than optimal for my clarity.)

Normally you would now rebase to shift your local changes on top of the new upstream, but we don't want to immediately do that. The problem is that our top commit is our own dgit-based change to debian/changelog, and we don't want to rebase that commit; instead we'll make a new version of it after we rebase our real local changes. So our first step is to discard our top commit:

git reset --hard HEAD~

(In my original theory I didn't realize we had to drop this commit before the rebase, not after, because otherwise things get confused. At a minimum, you wind up with debian/changelog out of order, and I don't know if dropping your HEAD commit after the rebase works right. It's possible you might get debian/changelog rebase conflicts as well, so I feel dropping your debian/changelog change before the rebase is cleaner.)

Now we can rebase, for which the simpler two-argument form does work (but not plain rebasing, or at least I didn't bother testing plain rebasing):

git rebase dgit/dgit/jammy,-security,-updates dgit/jammy,-security,-updates

(If you are wondering how this command possibly works, as I was part way through writing this entry, note that the first branch is 'dgit/dgit/...', ie our remote tracking branch, and then second branch is 'dgit/...', our local branch with our changes on it.)

At this point we should have all of our local changes stacked on top of the upstream changes, but no debian/changelog entry for them that will bump the package version. We create that with:

gbp dch --since dgit/dgit/jammy,-security,-updates --local .cslab. --ignore-branch --commit

Then we can build with 'dpkg-buildpackage -uc -b', and afterward do 'git clean -xdf; git reset --hard' to reset your tree back to its pristine state.

(My view is that while you can prepare a source package for your work if you want to, the 'source' artifact you really want to save is your dgit VCS repository. This will be (much) less bulky when you clean it up to get rid of all of the stuff (to be polite) that dpkg-buildpackage leaves behind.)

Here in 2026, we're retaining old systems instead of discarding them

By: cks

I mentioned recently that at work, we're retaining old systems that we would have normally discarded. We're doing this for the obvious reason that new servers have become increasingly expensive, due to escalating prices of RAM (especially DDR5 RAM) and all forms of SSDs, especially as new servers might really require us to buy ones that support U.2 NVMe instead of SATA SSDs (because I'm not sure how available SATA SSDs are these days).

Our servers are generally fairly old anyways, so our retention takes two forms. The straightforward one is that we're likely going to slow down completely pushing old servers out of service. Instead, we'll keep them on the shelf for if we want test or low importance machines, and along with that we're probably going to be more careful about which generation of hardware we use for new machines. We've traditionally simply used the latest hardware any time we turn over a machine (for example, updating it to a new Ubuntu version), but this time around a bunch of those will reuse what we consider second generation hardware or even older hardware for machines where we don't care too much if it's down for a day or two.

The second form of retention is that we're sweeping up older hardware that other groups at the university are disposing of, when in the past we'd have passed on the offer or taken only a small number of machines. For example, we just inherited a bunch of Supermicro servers and Lenovo P330 desktops (both old enough that they use DDR4 RAM), and in the past we'd have taken only a few of each at most. These inherited servers are likely to be used as part of what we consider 'second generation' hardware, equivalent to Dell R340s and R240s (and perhaps somewhat better in practice), so we'll use them for somewhat less important machines but ones where we still actually care.

(A couple of the inherited servers have already been reused as test servers.)

The hardware we're inheriting is perfectly good hardware and it'll probably work reliably for years to come (and if not, we have a fair number of spares now). But it's hardware with several years of use and wear already on it, and there's nothing special about it that makes it significantly better than the sort of second generation hardware we already have. However, we're looking at a future where we may not be able to afford to get new general purpose 1U servers and our current server fleet is all we'll have for a few years, even as some of them break or increasingly age out. So we're hoarding what we can get, in case. Maybe we won't need them, but if we do need them and we pass them up now, we'll really regret it.

(The same logic applies to the desktops. We don't have any immediate, obvious use for them, but at the same time they're not something we could get a replacement for if we pass on them now. We'll probably put a number of them to use for things we might not have bothered with it we had to get new machines; for example, I may set one up as a backup for my vintage 2017 office desktop.)

I suspect that there will be more of this sort of retention university-wide, whether or not the retained hardware gets used in the end. We're not in a situation where we can assume a ready supply of fresh hardware, so we'd maybe better hold on to what we have if it still works.

How old our servers are (as of 2026)

By: cks

Back in 2022, I wrote about how old our servers were at the time, partly because they're older than you might expect, and today I want to update that with our current situation. My group handles the general departmental infrastructure for the research side of the department (the teaching side is a different group), and we've tended to keep servers for quite a while. Research groups are a different matter; they often have much more modern servers and turn them over much faster.

As in past installments, our normal servers remain Dell 1U servers. What we consider our current generation are Dell R350s, which it looks like we got about two years ago in 2024 (and are now out of production). We still have plenty of Dell R340s and R240s in production, which were our most recent generation in 2022. We still have some Dell R230s and even R210 IIs in production in less important server roles. We also have a fair number of Supermicro servers in production, of assorted ages and in assorted roles (including our fileservers and our giant login server, which is now somewhat old).

(On a casual look, the Dell R210 IIs are all for machines that we consider decidedly unimportant; they're still in service because we haven't had to touch them. Our current view is that R350s are for important servers, and R340s and R240s are acceptable for less important ones.)

In a change from 2022, we turned over the hardware for our fileservers somewhat recently, 'modernizing' all of our ZFS filesystems in the process. The current fileservers have 512 GBytes of RAM in each, so I expect that we'll run this hardware for more than five years unless prices drop drastically back to what they were when we could afford to get a half-dozen machines with a combined multiple terabytes of (DDR5) RAM.

(Today, a single machine with 128 GBytes of DDR5 RAM and some U.2 NVMe drives came out far more expensive than we hoped (and the prices forced us to lower the amount of RAM we were targeting).)

Our SLURM cluster is quite a mix of machines. We have both CPU-focused and GPU-focused machines, and on both sides there's a lot of hand-built machines stuffed into rack cases. On the GPU side, the vendor servers are mostly Dell 3930s; on the CPU side, they're mostly Supermicro servers. A significant number of these servers are relatively old by now; the 3930s appear to date from 2019, for example. We have updated the GPUs somewhat but we mostly haven't bothered to update the servers otherwise, as we assume people mostly want GPU computation in GPU SLURM nodes. Even the CPU nodes are not necessarily the most modern; half of them (still) have Threadripper 2990WX CPUs (launched in 2018, and hand built into the same systems as in 2022). With RAM prices being the way they are, it's unlikely that we'll replace these CPU nodes with anything more recent in the near future.

With current hardware prices being what they are (and current and future likely funding levels), I don't think we're likely to get a new generation of 1U servers in the moderate future. We have one particular important server getting a hardware refresh soon, but apart from that we'll run servers on the hardware we have available today. This may mean we have to accept more hardware failures than usual (our usual amount of server hardware failures is roughly zero), but hopefully we'll have a big enough pool of old spare servers to deal with this.

(I expect us to reuse a lot more old servers than we traditionally have. For instance, our first generation of Linux ZFS fileservers date from 2018 but they've been completely reliable and they have a lot of disk bays and decent amounts of RAM. Surely we can find uses for that.)

PS: If I'm doing the math correctly, we have roughly 10 TBytes of DDR4 RAM of various sizes in machines that report DMI information to our metrics system, compared to roughly 6 TBytes of DDR5 RAM. That DDR5 RAM number is unlikely to go up by much any time soon; the DDR4 number probably will, for various reasons beyond the scope of this entry. This doesn't include our old fileserver hardware, which is currently turned off and not in service (and so not reporting DMI information about their decent amount of DDR4 RAM).

New old systems in the age of hardware shortages

By: cks

Recently I asked something on the Fediverse:

Lazyweb, if you were going to put together new DDR4-based desktop (because you already have the RAM and disks), what CPU would you use? Integrated graphics would probably be ideal because my needs are modest and that saves wrangling a GPU.

(Also I'm interested in your motherboard opinions, but the motherboard needs 2x M.2 and 2x to 4x SATA, which makes life harder. And maybe 4K@60Hz DisplayPort output, for integrated graphics)

If I was thinking of building a new desktop under normal circumstances, I would use all modern components (which is to say, current generation CPU, motherboard, RAM, and so on). But RAM is absurdly expensive these days, so building a new DDR5-based system with the same 64 GBytes of RAM that I currently have would cost over a thousand dollars Canadian just for the RAM. The only particularly feasible way to replace such an existing system today is to reuse as many components as possible, which means reusing my DDR4 RAM. In turn, this means that a lot of the rest of the system will be 'old'. By this I don't necessarily mean that it will have been manufactured a while ago (although it may have) but that its features and capabilities will be from a while back.

If you want an AMD CPU for your DDR4-based system, it will have to be an AM4 CPU and motherboard. I'm not sure how old good CPUs are for AM4, but the one you want may be as old as a 2022 CPU (Ryzen 5 5600; other more recent options don't seem to be as well regarded). Intel's 14th generation CPUs ("Raptor Lake") from late 2023 still support DDR4 with compatible motherboards, but at this point you're still looking at things launched two years or more ago, which at one point was an eternity in CPUs.

(It's still somewhat of an eternity in CPUs, especially AMD, because AMD has introduced support for various useful instructions since then. For instance, Go's latest garbage collector would like you to have AVX-512 support. Intel desktop CPUs appear to have no AVX-512 at all, though.)

Beyond CPU performance, older CPUs and often older motherboards also often mean that you have older PCIe standards, fewer PCIe lanes, less high speed USB ports, and so on. You're not going to get the latest PCIe from an older CPU and chipset. Then you may step down in other components as well (like GPUs and NVMe drives), depending on how long you expect to keep them, or opt to keep your current components if those are good enough.

My impression is that such 'new old systems' have usually been a relatively unusual thing in the PC market, and that historically people have upgraded to the current generation. This lead to a steady increase in baseline capabilities over time as you could assume that desktop hardware would age out on a somewhat consistent basis. If people are buying new old systems and keeping old systems outright, that may significantly affect not just the progress of performance but also the diffusion of new features (such as AVX-512 support) into the CPU population.

The other aspect of this is, well, why bother upgrading to a new old system at all, instead of keeping your existing old old system? If your old system works, you may not get much from upgrading to a new old system. If your old system doesn't have enough performance or features, spending money on a new old system may not get you enough of an improvement to remove your problems (although it may mitigate them a bit). New old systems are effectively a temporary bridge and there's a limit to how much people are willing to spend on temporary bridges unless they have to. This also seems likely to slow down both the diffusion of nice new CPU features and the slow increase in general performance that you could assume.

(At work, the current situation has definitely caused us to start retaining machines that we would have discarded in the past, and in fact were planning to discard until quite recently.)

PS: One potentially useful thing you can get out of a new old system like this is access to newer features like PCIe bifurcation or decent UEFI firmware that your current system doesn't support or have.

Canonical's Netplan is hard to deal with in automation

By: cks

Suppose, not entirely hypothetically, that you've traditionally used /etc/resolv.conf on your Ubuntu servers but you're considering switching to systemd-resolved, partly for fast failover if your normal primary DNS server is unavailable and partly because it feels increasingly dangerous not to, since resolved is the normal configuration and what software is likely to expect. One of the ways that resolv.conf is nice is that you can set the configuration by simply copying a single file that isn't used for anything else. On Ubuntu, this is unfortunately not the case for systemd-resolved.

Canonical expects you to operate all of your Ubuntu server networking through Canonical Netplan. In reality, Netplan will render things down to a systemd-networkd configuration, which has some important effects and creates some limitations. Part of that rendered networkd configuration is your DNS resolution settings, and the natural effect of this is that they have to be associated with some interface, because that's the resolved model of the world. This means that Netplan specifically attaches DNS server information to a specific network interfaces in your Netplan configuration. This means that you must find the specific device name and then modify settings within it, and those settings are intermingled (in the same file) with settings you can't touch.

(Sometimes Netplan goes the other way, separating interface specific configuration out to a completely separate section.)

Netplan does not give you a way to do this; if anything, Netplan goes out of its way to not do so. For example, Netplan can dump its full or partial configuration, but it does so in YAML form with no option for JSON (which you could readily search through in a script with jq). However, if you want to modify the Netplan YAML without editing it by hand, 'netplan set' sometimes requires JSON as input. Lack of any good way to search or query Netplan's YAML matters because for things like DNS settings, you need to know the right interface name. Without support for this in Netplan, you wind up doing hacks to try to get the right interface name.

Netplan also doesn't provide you any good way to remove settings. The current Ubuntu 26.04 beta installer writes a Netplan configuration that locks your interfaces to specific MAC addresses:

  enp1s0:
    match:
      macaddress: "52:54:00:a5:d5:fb"
    [...]
    set-name: "enp1s0"

This is rather undesirable if you may someday swap network cards or transplant server disks from one chassis to another, so we would like to automatically take it out. Netplan provides no support for this; 'netplan set' can't be given a blank replacement, for example (and 'netplan set "network.ethernets.enp1s0.match={}"' doesn't do anything). If Netplan would give you all of the enp1s0 block in JSON format, maybe you could edit the JSON and replace the whole thing, but that's not available so far.

(For extra complication you also need to delete the set-name, which is only valid with a 'match:'.)

Another effect of not being able to delete things in scripts is that you can't write scripts that move things out to a different Netplan .conf file that has only your settings for what you care about. If you could reliably get the right interface name and you could delete DNS settings from the file the installer wrote, you could fairly readily create a '/etc/netplan/60-resolv.conf' file that was something close to a drop-in /etc/resolv.conf. But as it is, you can't readily do that.

There are all sorts of modifications you might want to make through a script, such as automatically configuring a known set of VLANs to attach them to whatever the appropriate host interface is. Scripts are good for automation and they're also good for avoiding errors, especially if you're doing repetitive things with slight differences (such as setting up a dozen VLANs on your DHCP server). Netplan fights you almost all the way about doing anything like this.

My best guess is that all of Canonical's uses of Netplan either use internal tooling that reuses Netplan's (C) API or simply re-write Netplan files from scratch (based on, for example, cloud provider configuration information).

(To save other people the time, the netplan Python package on PyPI seems to be a third party package and was last updated in 2019. Which is a pity, because it theoretically has a quite useful command line tool.)

One bleakly amusing thing I've found out through using 'netplan set' on Ubuntu 26.04 is that the Ubuntu server installer and Netplan itself have slightly different views on how Netplan files should be written. The original installer version of the above didn't have the quotes around the strings; 'netplan set' added them.

(All of this would be better if there was a widely agreed on, generally shipped YAML equivalent of 'jq', or better yet something that could also modify YAML in place as well as query it in forms that were useful for automation. But the 'jq for YAML' ecosystem appears to be fragmented at best.)

Considering mmap() verus plain reads for my recent code

By: cks

The other day I wrote about a brute force approach to mapping IPv4 /24 subnets to Autonomous System Numbers (ASNs), where I built a big, somewhat sparse file of four-byte records, with the record for each /24 at a fixed byte position determined by its first three octets (so 0.0.0.0/24's ASN, if any, is at byte 0, 0.0.1.0/24 is at byte 4, and so on). My initial approach was to open, lseek(), and read() to access the data; in a comment, Aristotle Pagaltzis wondered if mmap() would perform better. The short answer is that for my specific case I think it would be worse, but the issue is interesting to talk about.

(In general, my view is that you should use mmap() primarily if it makes the code cleaner and simpler. Using mmap() for performance is a potentially fraught endeavour that you need to benchmark.)

In my case I have two strikes against mmap() likely being a performance advantage: I'm working in Python (and specifically Python 2) so I can't really directly use the mmap()'d memory, and I'm normally only making a single lookup in the typical case (because my program is running as a CGI). In the non-mmap() case I expect to do an open(), an lseek(), and a read() (which will trigger the kernel possibly reading from disk and then definitely copying data to me). In the mmap() case I would do open(), mmap(), and then access some page, triggering possible kernel IO and then causing the kernel to manipulate process memory mappings to map the page into my address space. In general, it seems unlikely that mmap() plus the page access handling will be cheaper than lseek() plus read().

(In both the mmap() and read() cases I expect two transitions into and out of the kernel. As far as I know, lseek() is a cheap system call (and certainly it seems unlikely to be more expensive than mmap(), which has to do a bunch of internal kernel work), and the extra work the read() does to copy data from the kernel to user space is probably no more work than the kernel manipulating page tables, and could be less.)

If I was doing more lookups in a single process, I could possibly win with the mmap() approach but it's not certain. A lot depends on how often I would be looking up something on an already mapped page and how expensive mapping in a new page is compared to some number of lseek() plus read() system calls (or pread() system calls if I had access to that, which cuts the number of system calls in half). In some scenarios, such as a burst of traffic from the same network or a closely related set of networks, I could see a high hit rate on already mapped pages. In others, the IPv4 addresses are basically random and widely distributed, so many lookups would require mapping new pages.

(Using mmap() makes it unnecessary to keep my own in-process cache, but I don't think it really changes what the kernel will cache for me. Both read()'ing from pages and accessing them through mmap() keeps them recently used.)

Things would also be better in a language where I could easily make zero-copy use of data right out of the mmap()'d pages themselves. Python is not such a language, and I believe that basically any access to the mmap()'d data is going to create new objects and copy some bytes around. I expect that this results in as many intermediate objects and so on as if I used Python's read() stuff.

(Of course if I really cared there's no substitute for actually benchmarking some code. I don't care that much, and the code is simpler with the regular IO approach because I have to use the regular IO approach when writing the data file.)

Early notes on switching some libvirt-based virtual machines to UEFI

By: cks

I keep around a small collection of virtual machines so I don't have to drag out one of our spare physical servers to test things on. These virtual machines have traditionally used traditional MBR-based booting ('BIOS' in libvirt instead of 'UEFI'), partly because for a long time libvirt didn't support snapshots of UEFI based virtual machines and snapshots are very important for my use of these scratch virtual machines. However, I recently discovered that libvirt now can do snapshots of UEFI based virtual machines, and also all of our physical server installs are UEFI based, so in the past couple of days I've experimented with moving some of my Ubuntu scratch VMs from BIOS to UEFI.

As far as I know, virt-manager and virsh don't directly allow you to switch a virtual machine between BIOS and UEFI after it's been created, partly because the result is probably not going to boot (unless you deliberately set up the OS inside the VM with both an EFI boot and a BIOS MBR boot environment). Within virt-manager, you can only select BIOS or UEFI at setup time, so you have to destroy your virtual machine and recreate it. This works, but it's a bit annoying.

(On the other hand, if you've had some virtual machines sitting around for years and years, you might want to refresh all of their settings anyway.)

It's possible to change between BIOS and UEFI by directly editing the libvirt XML to transform the <os> node. You may want to remove any old snapshots first because I don't know what happens if you revert from a 'changed to UEFI' machine to a snapshot where your virtual machine was a BIOS one. In my view, the easiest way to get the necessary XML is to create (or recreate) another virtual machine with UEFI, and then dump and copy its XML with some minor alterations.

For me, on Fedora with the latest libvirt and company, the <os> XML of a BIOS booting machine is:

 <os>
   <type arch='x86_64' machine='pc-q35-6.1'>hvm</type>
 </os>

Here the 'machine=' is the machine type I picked, which I believe is the better of the two options virt-manager gives me.

My UEFI based machines look like this:

 <os firmware='efi'>
   <type arch='x86_64' machine='pc-q35-9.2'>hvm</type>
   <firmware>
     <feature enabled='yes' name='enrolled-keys'/>
     <feature enabled='yes' name='secure-boot'/>
   </firmware>
   <loader readonly='yes' secure='yes' type='pflash' format='qcow2'>/usr/share/edk2/ovmf/OVMF_CODE_4M.secboot.qcow2</loader>
   <nvram template='/usr/share/edk2/ovmf/OVMF_VARS_4M.secboot.qcow2' templateFormat='qcow2' format='qcow2'>/var/lib/libvirt/qemu/nvram/[machine name]_VARS.qcow2</nvram>
 </os>

Here the '[machine-name]' bit is the libvirt name of my virtual machine, such as 'vmguest1'. This nvram file doesn't have to exist in advance; libvirt will create it the first time you start up the virtual machine. I believe it's used to provide snapshots of the UEFI variables and so on to go with snapshots of your physical disks and snapshots of the virtual machine configuration.

(This feature may have landed in libvirt 10.10.0, if I'm reading release notes correctly. Certainly reading the release notes suggests that I don't want to use anything before then with UEFI snapshots.)

Manually changing the XML on one of my scratch machines has worked fine to switch it from BIOS MBR to UEFI booting as far as I can tell, but I carefully cleared all of its disk state and removed all of its snapshots before I tried this. I suspect that I could switch it back to BIOS if I wanted to. Over time, I'll probably change over all of my as yet unchanged scratch virtual machines to UEFI through direct XML editing, because it's the less annoying approach for me. Now that I've looked this up, I'll probably do it through 'virsh edit ...' rather than virt-manager, because that way I get my real editor.

(This is the kind of entry I write for my future use because I don't want to have to re-derive this stuff.)

PS: Much of this comes from this question and answers.

Going from an IPv4 address to an ASN in Python 2 with Unix brute force

By: cks

For reasons, I've reached the point where I would like to be able to map IPv4 addresses into the organizations responsible for them, which is to say their Autonomous System Number (ASN), for use in DWiki, the blog engine of Wandering Thoughts. So today on the Fediverse I mused:

Current status: wondering if I can design an on-disk (read only) data structure of some sort that would allow a Python 2 program to efficiently map an IP address to an ASN. There are good in-memory data structures for this but you have to load the whole thing into memory and my Python 2 program runs as a CGI so no, not even with pickle.

(Since this is Python 2, about all I have access to is gdbm or rolling my own direct structure.)

Mapping IP addresses to ASNs comes up a lot in routing Internet traffic, so there are good in-memory data structures that are designed to let you efficiently answer these questions once you have everything loaded. But I don't think anyone really worries about on-disk versions of this information, while it's the case that I care about, although I only care about some ASNs (a detail I forgot to put in the Fediverse post).

Then I had a realization:

If I'm willing to do this by /24 (and I am) and represent the ASNs by 16-bit ints, I guess you can do this with a 32 Mbyte sparse file of two-byte blocks. Seek to a 16-byte address determined by the first three octets of the IP, read two bytes, if they're zero there's no ASN mapping we care about, otherwise they're the ASN in some byte order I'd determine.

If I don't care about the specific ASN, just a class of ASNs of interest of which there are at most 255, it's only 16 Mbytes.

(And if all I care about is a yes or know answer, I can represent each /24 by a bit, so the storage required drops even more, to only 2 Mbytes.)

This Fediverse post has a mistake. I thought ASNs were 16-bit numbers, but we've gone well beyond that by now. So I would want to use the one-byte 'class of ASN' approach, with ASNs I don't care about mapping to a class of zero. Alternately I could expand to storing three bytes for every /24, or four bytes to stay aligned with filesystem blocks.

That storage requirement is 'at most' because this will be a Unix sparse file, where filesystem blocks that aren't written to aren't stored on disk; when read, the data in them is all zero. The lookup is efficient, at least in terms of system calls; I'd open the file, lseek() to the position, and read two bytes (causing the system to read a filesystem block, however big that is). Python 2 doesn't have access to pread() or we could do it in one system call.

Within the OS this should be reasonably efficient, because if things are active much of the important bits of the mapping file will be cached into memory and won't have to be read from disk. 32 Mbytes is nothing these days, at least in terms of active file cache, and much of the file will be sparse anyway. The OS obviously has reasonably efficient random access to the filesystem blocks of the file, whether in memory or on disk.

This is a fairly brute force approach that's only viable if you're typically making a single query in your process before you finish. It also feels like something that is a good fit for Unix because of sparse files, although 16 Mbytes isn't that big these days even for a non-sparse file.

Realizing the brute force approach feels quite liberating. I've been turning this problem over in my mind for a while but each time I thought of complicated data structures and complicated approaches and it was clear to me that I'd never implement them. This way is simple enough that I could actually do it and it's not too impractical.

PS: I don't know if I'll actually build this, but every time a horde of crawlers descends on Wandering Thoughts from a cloud provider that has a cloud of separate /24s and /23s all over the place, my motivation is going to increase. If I could easily block all netblocks of certain hosting providers all at once, I definitely would.

(To get the ASN data there's pyasn (also). Conveniently it has a simple on-disk format that can be post-processed to go from a set of CIDRs that map to ASNs to a data file that maps from /24s to ASN classes for ASNs (and classes) that I care about.)

Update: After writing most of this entry I got enthused and wrote a stand-alone preliminary implementation (initially storing full ASNs in four-byte records), which can both create the data file and query it. It was surprisingly straightforward and not very much code, which is probably what I should have expected since the core approach is so simple. With four-byte records, a full data file of all recent routes from pyasn is about 53 Mbytes and the data file can be created in less than two minutes, which is pretty good given that the code writes records for about 16.5 million /24s.

(The whole thing even appears to work, although I haven't strongly tested it.)

Fedora's virt-manager started using external snapshots for me as of Fedora 41

By: cks

Today I made an unpleasant discovery about virt-manager on my (still) Fedora 42 machines that I shared on the Fediverse:

This is my face that Fedora virt-manager appears to have been defaulting to external snapshots for some time and SURPRISE, external snapshots can't be reverted by virsh. This is my face, especially as it seems to have completely screwed up even deleting snapshots on some virtual machines.

(I only discovered this today because today is the first time I tried to touch such a snapshot, either to revert to it or to clean it up. It's possible that there is some hidden default for what sort of snapshot to make and it's only been flipped for me.)

Neither virt-manager nor virsh will clearly tell you about this. In virt-manager you need to click on each snapshot and if it says 'external disk only', congratulations, you're in trouble. In virsh, 'virsh snapshot-list --external <vm>' will list external snaphots, and then 'virsh snapshot-list --tree <vm>' will tell you if they depend on any internal snapshots.

My largest problems came from virtual machines where I had earlier internal snapshots and then I took more snapshots, which became external snapshots from Fedora 41 onward. You definitely can't revert to an external snapshot in this situation, at least not with virsh or virt-manager, and the error messages I got were generic ones about not being able to revert external snapshots. I haven't tested reverting external snapshots for a VM with no internal ones.

(Not being able to revert to external snapshots is a long standing libvirt issue, but it's possible they now work if you only have external snapshots. Otherwise, Fedora 41 and Fedora 42 defaulting to external snapshots is extremely hard to understand (to be polite).)

Update: you can revert an external snapshot in the latest libvirt if all of your snapshots are external. You can't revert them if libvirt helpfully gave you external snapshots on top of internal ones by switching the default type of snapshots (probably in Fedora 41).

If you have an external snapshot that you need to revert to, all I can do is point to a libvirt wiki page on the topic (although it may be outdated by now) along with libvirt's documentation on its snapshot XML. I suspect that there is going to be suffering involved. I haven't tried to do this; when it came up today I could afford to throw away the external snapshot.

If you have internal snapshots and you're willing to throw away the external snapshot and what's built on it, you can use virsh or virt-manager to revert to an internal snapshot and then delete the external snapshot. This leaves the external snapshot's additional disk file or files dangling around for you to delete by hand.

If you have only an external snapshot, it appears that libvirt will let you delete the snapshot through 'virsh snapshot-delete <vm> <external-snapshot>', which preserves the current state of the machine's disks. This only helps if you don't want the snapshot any more, but this is one of my common cases (where I take precautionary snapshots before significant operations and then get rid of them later when I'm satisfied, or at least committed).

The worst situation appears to be if you have an external snapshot made after (and thus on top of) an earlier internal snapshot and you to keep the live state of things while getting rid of the snapshots. As far as I can tell, it's impossible to do this through libvirt, although some of the documentation suggests that you should be able to. The process outlined in libvirt's Merging disk image chains didn't work for me (see also Disk image chains).

(If it worked, this operation would implicitly invalidate the snapshots and I don't know how you get rid of them inside libvirt, since you can't delete them normally. I suspect that to get rid of them, you need to shut down all of the libvirt daemons and then delete the XML files that (on Fedora) you'll find in /var/lib/libvirt/qemu/snapshot/<domain>.)

One reason to delete external snapshots you don't need is if you ever want to be able to easily revert snapshots in the future. I wouldn't trust making internal snapshots on top of external ones, if libvirt even lets you, so if you want to be able to easily revert, it currently appears that you need to have and use only internal snapshots. Certainly you can't mix new external snapshots with old internal snapshots, as I've seen.

(The 5.1.0 virt-manager release will warn you to not mix snapshot modes and defaults to whatever snapshot mode you're already using. I don't know what it defaults to if you don't have any snapshots, I haven't tried that yet.)

Sidebar: Cleaning this up on the most tangled virtual machine

I've tried the latest preview releases of the libvirt stuff, but it doesn't make a difference in the most tangled situation I have:

$ virsh snapshot-delete hl-fedora-36 fedora41-preupgrade
error: Failed to delete snapshot fedora41-preupgrade
error: Operation not supported: deleting external snapshot that has internal snapshot as parent not supported

This VM has an internal snapshot as the parent because I didn't clean up the first snapshot (taken before a Fedora 41 upgrade) before making the second one (taken before a Fedora 42 upgrade).

In theory one can use 'virsh blockcommit' to reduce everything down to a single file, per the knowledge base section on this. In practice it doesn't work in this situation:

$ virsh blockcommit hl-fedora-36 vda --verbose --pivot --active
error: invalid argument: could not find base image in chain for 'vda'

(I tried with --base too and that didn't help.)

I was going to attribute this to the internal snapshot but then I tried 'virsh blockcommit' on another virtual machine with only an external snapshot and it failed too. So I have no idea how this is supposed to work.

Since I could take a ZFS snapshot of the entire disk storage, I chose violence, which is to say direct usage of qemu-img. First, I determined that I couldn't trivially delete the internal snapshot before I did anything else:

$ qemu-img snapshot -d fedora40-preupgrade fedora35.fedora41-preupgrade
qemu-img: Could not delete snapshot 'fedora40-preupgrade': snapshot not found

The internal snapshot is in the underlying file 'fedora35.qcow2'. Maybe I could have deleted it safely even with an external thing sitting on top of it, but I decided not to do that yet and proceed to the main show:

$ qemu-img commit -d fedora35.fedora41-preupgrade
Image committed.
$ rm fedora35.fedora41-preupgrade

Using 'qemu-img info fedora35.qcow2' showed that the internal snapshot was still there, so I removed it with 'qemu-img snapshot -d' (this time on fedora35.qcow2).

All of this left libvirt's XML drastically out of step with the underlying disk situation. So I removed the XML for the snapshots (after saving a copy), made sure all libvirt services weren't running, and manually edited the VM's XML, where it turned out that all I needed to change was the name of the disk file. This appears to have worked fine.

I suspect that I could have skipped manually removing the internal snapshot and its XML and libvirt would then have been happy to see it and remove it.

(I'm writing all of the commands and results down partly for my future reference.)

Mass production's effects on the cheapest way to get some things

By: cks

We have a bunch of networks in a number of buildings, and as part of looking after them, we want to monitor whether or not they're actually working. For reasons beyond the scope of this entry we don't do things like collect information from our switches through SNMP, so our best approach is 'ping something on the network in the relevant location'. This requires something to ping. We want that thing to be stable and always on the network, which typically rules out machines and devices run by other people, and we want it to run from standard wall power for various reasons.

You can imagine a bunch of solutions to this for both wired and wireless networks. There are lots of cheap little computers these days that can run Linux, so you could build some yourself or expect to find someone selling them pre-made. However, these are unlikely to be a mass produced volume product, and it turns out that the flipside of things only being cheap when there is volume is that if there is volume, unexpected things can be the cheapest option.

The cheapest wall-powered device you can put on your wireless network to ping these days turns out to be a remote controlled power plug intended for home automation (as a bonus it will report uptime information for you if you set it up right, so you can tell if it lost power recently). They can fail after a few years, but they're inexpensive so we consider them consumables. And if you have another device that turns out to be flaky and has to be power cycled every so often, you can reuse a 'wifi reachability sensor' for its actual remote power control capabilities.

Similarly, as far as we've found, the cheapest wall powered device that plugs into a wired Ethernet and can be given an IP address so it can be pinged is a basic five port managed switch. You give it a 'management IP', plug one port into the network, and optionally plug up its other four ports so no one uses it for connectivity (because it's a cheap switch and you don't necessarily trust it). You might even be able to find one that supports SNMP so you can get some additional information from it (although our current ones don't, as far as I can tell).

In both cases it's clear that these are cheap because of mass production. People are making lots of wireless remote controlled power plugs and five port managed switches, so right now you can get the switches for about $30 Canadian each and the power plugs for $10 Canadian. In both cases what we get is overkill for what we want, and you could do a simpler version that has a smaller, cheaper bill of materials (BOM). But that smaller version wouldn't have the volume so it would cost much more for us to get it or an approximation.

(Even if we designed and built our own, we probably can't beat the price of the wireless remote controlled power plugs. We might be able to get a cheaper BOM for a single-Ethernet simple computer with case and wall plug power supply, but that ignores staff time to design, program, and assemble the thing.)

At one level this makes me sad. We're wasting the reasonably decent capabilities of both devices, and it feels like there should be a more frugal and minimal option. But it's hard to see what it would be and how it could be so cheap and readily available.

A traditional path to getting lingering duplicate systems

By: cks

In yesterday's entry I described a lingering duplicate system and how it had taken us a long time to get rid of it, but I got too distracted by the story to write down the general thoughts I had on how this sort of thing happens and keeps happening (also, the story turned out to be longer than I expected). We've had other long running duplicate systems, and often they have more or less the same story as yesterday's disk space usage tracking system.

The first system built is a basic system. It's not a bad system, but it's limited and you know it. You can only afford to gather disk usage information once a day and you have nowhere to put it other than in the filesystem, which makes it easy to find and independent of anything else but also stops it updating when the filesystem fills up. Over time you may improve this system (cheaper updates that happen more often, a limited amount of high resolution information), but the fundamental issues with it stick around.

After a while it becomes possible to build a different, better system (you gather disk usage information every few minutes and put it in your new metrics system), or maybe you just realize how to do a better version from scratch. But often the initial version of this new system has its own limitations or works a bit differently or both, or you've only implemented part of what you'd need for a full replacement of the first system. And maybe you're not sure it will fully work, that it's really the right answer, or if you'll be able to support it over the long term (perhaps the cardinality of the metrics will be too overwhelming).

(You may also be wary of falling victim to the "second system effect", since you know you're building a second system.)

Usually this means that you don't want to go through the effort and risk of immediately replacing the old system with the new system (if it's even immediately possible without more work on the new system). So you use the new system for new stuff (providing dashboards of disk space usage) and keep the old system for the old stuff (the officially supported commands that people know). The old system is working so it's easier to have it stay "for now". Even if you replace part of the use of the old system with the new system, you don't replace all of it.

(If your second system started out as only a partial version of the old system, you may also not be pushed to evolve it so that it could fully replace the old system, or that may only happen slowly. In some ways this is a good thing; you're getting practical experience with the basic version of the new system rather than immediately trying to build the full version. This is a reasonable way to avoid the "second system effect", and may lead you to find out that in the new system you want things to operate differently than the old one.)

Since both the old system and the new system are working, you now generally have little motivation to do more work to get rid of the old system. Until you run into clear limitations of the old system, moving back to only having one system is (usually) cleanup work, not a priority. If you wanted to let the new system run for a while to prove itself, it's also easy to simply lose track of this as a piece of future work; you won't necessarily put it on a calendar, and it's something that might be months or a year out even in the best of circumstances.

(The times when the cleanup is a potential priority are when the old system is using resources that you want back, including money for hardware or cloud stuff, or when the old system requires ongoing work.)

A contributing factor is that you may not be sure about what specific behaviors and bits of the old system other things are depending on. Some of these will be actual designed features that you can perhaps recover from documentation, but others may be things that simply grew that way and became accidentally load bearing. Figuring these out may take careful reverse engineering of how the system works and what things are doing with it, which takes work, and when the old system is working it's easier to leave it there.

Lingering duplicate systems and the expense of weeding them out (an illustration)

By: cks

We have been operating a fileserver environment for a long time now, back before we used ZFS. When you operate fileservers in a traditional general Unix environment, one of the things you need is disk usage information. So a very long time ago, before I even arrived, people built a very Unix-y system to do this. Every night, raw usage information was generated for each filesystem (for a while with 'du'), written to a special system directory in the filesystem, and then used to create a text file with a report showing currently usage and the daily and weekly change in everyone's usage. A local 'report disk usage' script would then basically run your pager on this file.

After a while, we we able to improve this system by using native ZFS commands to get per-user 'quota' usage information, which made it much faster than the old way (we couldn't do this originally because we started with ZFS before ZFS tracked this information). Later, this made it reasonable to generate a 'frequent' disk usage report every fifteen minutes (with it keeping a day's worth of data), which could be helpful to identify who had suddenly used a lot of disk space; we wrote some scripts to use this information, but never made them as public as the original script. However, all of this had various limitations, including that it stopped updating once the filesystem had filled up.

Shortly after we set up our Prometheus metrics system and actually had a flexible metrics system we could put things into, we started putting disk space usage information into it, giving us more fine grained data, more history (especially fine grained history, where we'd previously only had the past 24 hours), and the ability to put it into Grafana graphs on dashboards. Soon afterward it became obvious that sometimes the best way to expose information is through a command, so we wrote a command to dump out current disk usage information in a relatively primitive form.

Originally this 'getdiskusage' command produced quite raw output because it wasn't really intended for direct use. But over time, people (especially me) kept wanting more features and options and I never quite felt like writing some scripts to sit on top of it when I could just fiddle the code a bit more. Recently, I added some features and tipped myself over a critical edge, where it felt like I could easily re-do the old scripts to get their information from 'getdiskusage' instead of those frequently written files. One thing led to another and so now we have some new documentation and new (and revised) user-visible commands to go with it.

(The raw files were just lines of 'disk-space login', and this was pretty close to what getdiskusage produced already in some modes.)

However, despite replacing the commands, we haven't yet turned off the infrastructure on our fileservers that creates and updates those old disk usage files. Partly this is because I'd want to clean up all the existing generated files rather than leave them to become increasingly out of date, and that's a bit of a pain, and partly it's because of inertia.

Inertia is also a lot of why it took so long to replace the scripts. We've had the raw capability to replace them for roughly six years (since 'getdiskusage' was written, demonstrating that it was easily possible to extract the data from our metrics system in a usable form), and we'd said to each other that we wanted to do it for about that long, but it was always "someday". One reason for the inertia was that the existing old stuff worked fine, more or less, and also we didn't think very many people used it very often because it wasn't really documented or accessible. Perhaps another reason was that we weren't entirely sure we wanted to commit to the new system, or at least to exact form we first implemented our disk space metrics in.

DMARC DNS record inheritance and DMARC alignment requirements

By: cks

To simplify, DMARC is based on the domain in the 'From:' header, and what policy (if any) that domain specifies. As I've written about (and rediscovered) more than once (here and here), DMARC will look up the DNS record for the DMARC policy in exactly one of two places, either in the exact From: domain or on the organization's top level domain. In other words, if a message has a From: of 'someone@breaking.news.example.org', a receiver will first look for a DMARC TXT DNS record with the name _dmarc.breaking.news.example.org and then one with the name _dmarc.example.org.

(But there will be no lookup for _dmarc.news.example.org.)

DMARC also has the concept of policy inheritance, where the example.org DMARC DNS TXT record can specify a different DMARC policy for the organizational domain than for subdomains that don't have their own policy. For example, example.org could specify 'p=reject; sp=none' to say that 'From: user@example.org' should be rejected if it fails DMARC but it has no views on a default for 'From: user@news.example.org'.

If you're an innocent person, you might think that if your organization has 'sp=none' on its organization policy, you don't have to be concerned about the DMARC (and DKIM, and SPF) behavior of sub-names that don't have their own DMARC records, including hosts that send as 'From: local-account@host.dept.example.org'. Your organizational policy says 'sp=none', meaning don't do anything with sub-names for DMARC, and surely everyone will follow that.

This is unfortunately not quite true in an environment where people care about DKIM results regardless of DMARC policy settings. The problem is DKIM (and SPF) alignment. Under relaxed DKIM alignment, a 'From: flash@eng.news.example.org' would pass if it's DKIM signed by anything in example,org, for example 'eng.example.org'. Under strict DKIM alignment, it must be signed specifically by 'eng.news.example.org'.

The choice of what DKIM alignment to require is not a 'policy' and is not covered by 'p=' or 'sp=' in DMARC DNS TXT records. It's instead covered by a separate parameter, 'adkim=', and there is no 'sadkim=' parameter that only applies to subdomains. This means that there's no way for example.org to change the alignment policy for just 'From: user@example.org'; the moment they set 'adkim=s' in the _dmarc.example.org DNS TXT record, all sub-names without their own _dmarc.<whatever> records also switch to strict DKIM alignment. Even if the top level domain specifies 'sp=none', various mail systems out there may actively reject your mail because they no longer consider it properly aligned or increase their suspicion score a bit due to the lack of alignment (in some views your mail went from 'properly DKIM signed' to 'not properly DKIM signed').

The only way to deal with this is the same as with policy inheritance. Any host or domain name within your (sub-)organization that appears in From: headers must have its own valid DMARC DNS TXT record. If you want strict DKIM alignment you need to set that as 'adkim=s'. If you want relaxed alignment in theory that's the default but you might find it clearer to explicitly set 'adkim=r' (and probably 'aspf=r', also for clarity).

(Setting alignment explicitly makes it clear to other people and future you that you're deliberately choosing an alignment that might wind up different from your top level organizational alignment.)

PS: As far as I can see this is the behavior the DMARC RFC implicitly requires for all DMARC settings other than 'p=' (which has the 'sp=' version), but I could be wrong and missing something.

One problem with (Python) docstrings is that they're local

By: cks

When I wrote about documenting my Django forms, I said that I knew I didn't want to put my documentation in docstrings, because I'd written some in the past and then not read it this time around. One of the reasons for that is that Python docstrings have to be attached to functions, or more generally, Python docstrings have to be scattered through your code. The corollary to this is that to find relevant docstrings you have to read through your code and then remember which bits of it are relevant to what you're wondering about.

When your docstring is specifically about the function you already know you want to look at, this is fine. Docstrings work perfectly well for local knowledge, for 'what is this function about' summaries that you want to read before you delve into the function. I feel they work rather less well for finding what function you want to look at (ideally you want some sort of skimmable index for that); if you have to read docstrings to find a function, you're going to be paging through a lot of your code until you hit the right docstring.

This is also why I feel docstrings are a bad fit for documenting my Django forms. Even if I attach them to the Python functions that handle each particular form, the resulting documentation is going to be mingled with my code and spread all through it. Not only is there no overview, but I'd have to skip around my code as I read about how one form interacts with another; there's no single place where I can read about the flow of forms, one leading to another.

(This is the case even if all of the form handling functions are in one spot with nothing between them, because the docstrings will be split up by the code itself and the comments in the code.)

Another issue is that sensible docstrings can only be so big, because they separate the function's 'def' statement from its actual code. You don't want those two too far apart, which pushes docstrings toward being relatively concise. My feeling is that if I have a lot to say about what the function is used for or how it relates to other things, I can't really put it in a docstring. I usually put it in a comment in front of the function (which means that some of my Python code has a mixture of comments and docstrings). The less a function can be described purely by itself (and concisely), the more its docstring is going to sprawl and the more awkward that gets.

(Docstrings on functions are also generally seen as what I could call external documentation, written for people who might want to call the function and understand how it relates to other functions they might also use. Comments are the usual form of internal documentation that you want at hand while reading the function's code.)

It's conventional to say that docstrings are documentation for what they're on. I think it's better to say that docstrings are summaries. Some things can be described purely through summaries (with additional context that the programmer is assumed to have), but not everything can be.

(Comments before a function are also local to some degree, but they intrude less on the function's code since they don't put themselves between 'def' and the rest of things.)

Wayland has good reasons to put the window manager in the display server

By: cks

I recently ran across Isaac Freund's Separating the Wayland Compositor and Window Manager (via), which is excellent news as far as I'm concerned. But in passing, it says:

Traditionally, Wayland compositors have taken on the role of the window manager as well, but this is not in fact a necessary step to solve the architectural problems with X11. Although, I do not know for sure why the original Wayland authors chose to combine the window manager and Wayland compositor, I assume it was simply the path of least resistance. [...]

Unfortunately, I believe that there are excellent reasons to put the window manager into the display server the way Wayland has, and the Wayland people (who were also X people) were quite familiar with them and how X has had problems over the years because of its split.

One large and more or less core problem is that event handling is deeply entwined with window management. As an example, consider this sequence of (input) events:

  1. your mouse starts out over one window. You type some characters.
  2. you move your mouse over to a second window. You type some more characters.
  3. you click a mouse button without moving the mouse.
  4. you type more characters.

Your window manager is extremely involved in the decisions about where all of those input events go and whether the second window receives a mouse button click event in the third step. If the window manager is separate from whatever is handling input events, either some things trigger synchronous delays in further event handling or sufficiently fast typeahead and actions are in a race with the window manager to see if it handles changes in where future events should go fast enough or if some of your typing and other actions are misdirected to the wrong place because the window manager is lagging.

Embedding the window manager in the display server is the simple and obvious approach to insuring that the window manager can see and react to all events without lag, and can freely intercept and modify all events as it wishes without clients having to care. The window manager can even do this using extremely local knowledge if it wants. Do you want your window manager to have key bindings that only apply to browser windows, where the same keys are passed through to other programs? An embedded window manager can easily do that (let's assume it can reliably identify browser windows).

(An outdated example of how complicated you can make mouse button bindings, never mind keyboard bindings, is my mouse button bindings in fvwm.)

X has a collection of mechanisms that try to allow window managers to manage 'focus' (which window receives keyboard input), intercept (some) keys at a window manager level, and do other things that modify or intercept events. The whole system is complex, imperfect, and limited, and a variety of these mechanisms have weird side effects on the X events that regular programs receive; you can often see this with a program such as xev. Historically, not all X programs have coped gracefully with all of the interceptions that window managers like fvwm can do.

(X also has two input event systems, just to make life more complicated.)

X's mechanisms also impose limits on what they'll allow a window manager to do. One famous example is that in X, mouse scroll wheel events always go to the X window under the mouse cursor. Even if your window manager uses 'click (a window) to make it take input', mouse scroll wheel input is special and cannot be directed to a window this way. In Wayland, a full server has no such limitations; its window manager portion can direct all events, including mouse scroll wheels, to wherever it feels like.

(This elaborates on a Fediverse post of mine.)

Cleaning old GPG RPM keys that your Fedora install is keeping around

By: cks

Approximately all RPM packages are signed by GPG keys (or maybe they're supposed to be called PGP keys), which your system stores in the RPM database as pseudo-packages (because why not). If your Fedora install has been around long enough, as mine have, you will have accumulated a drift of old keys and sometimes you either want to clean them up or something unfortunate will happen to one of those keys (I'll get to one case for it).

One basic command to see your collection of GPG keys in the RPM database is (taken from this gist):

rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n'

On some systems this will give you a nice short list of keys. On others, your list may be very long.

Since Fedora 42 (cf), DNF has functionality (I believe more or less built in) that should offer to remove old GPG keys that have actually expired. This is in the 'expired PGP keys plugin' which comes from the 'libdnf5-plugin-expired-pgp-keys' if you don't have it installed (with a brief manpage that's called 'libdnf5-expired-pgp-keys'). I believe there was a similar DNF4 plugin. However, there are two situations where this seems to not work correctly.

The first situation is now-obsolete GPG keys that haven't expired yet, for various reasons; these may be for past versions of Fedora, for example. These days, the metadata for every DNF repository you use should list a URL for its GPG keys (see the various .repo files in /etc/yum.repos.d/ and look for the 'gpgkey=' lines). So one way to clean up obsolete keys is to fetch all of the current keys for all of your current repositories (or at least the enabled ones), and then remove anything you have that isn't among the list. This process is automated for you by the 'clean-rpm-gpg-pubkey' command and package, which is mentioned in some Fedora upgrade instructions. This will generally clean out most of your obsolete keys, although rare people will have keys that are so old that it chokes on them.

The second situation is apparently a repository operator who is sufficiently clever to have re-issued an expired key using the same key ID and fingerprint but a new expiry date in the future; this fools RPM and related tools and everything chokes. This is unfortunate, since it will often stall all DNF updates unless you disable the repo. One repository operator who has done this is Google, for their Fedora Chrome repository. To fix this you'll have to manually remove the relevant GPG key or keys. Once you've used clean-rpm-gpg-pubkey to reduce your list of GPG keys to a reasonable level, you can use the RPM command I showed above to list all your remaining keys, spot the likely key or keys (based on who owns it, for example), and then use 'rpm -e --allmatches gpg-pubkey-d38b4796-570c8cd3' (or some other appropriate gpg-pubkey name) to manually scrub out the GPG key. Doing a DNF operation such as installing or upgrading a package from the repository should then re-import the current key.

(This also means that it's theoretically harmless to overshoot and remove the wrong key, because it will be fetched back the next time you need it.)

(When I wrote my Fediverse post about discovering clean-rpm-gpg-pubkey, I apparently thought I would remember it without further prompting. This was wrong, and in fact I didn't even remember to use it when I upgraded my home desktop. This time it will hopefully stick, and if not, I have it written down here where it will probably be easier to find.)

Making empirical decisions about web access (here in 2026)

By: cks

Recently, Denis Warburton wrote in a comment on my entry on how HTTP results today depend on what HTTP User-Agent you use:

Making decisions based on user-provided information is unwise in 2026. The originating ip address is the only source of "truth" ... and even then, that information needs to be further examined before discerning whether or not it is a valid piece of communication.

It's absolutely true that everything except the source IP address is under the control of an attacker (and it always has been), and in one sense you can't trust it. But this doesn't mean you can't use information that's under the attacker's control in making decisions about whether to allow access to something; instead, it means that you have to be thoughtful about how you use the information and what for.

In practice, web agents emit a lot of data in their HTTP headers and requests. Some of these signals are complicated, such as browser version numbers, and some of them require work to use, but this doesn't mean that there's no signal at all that can be derived from all of the data that a web agent emits. For example, consider a web agent that uses the HTTP User-Agent of:

Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)

This web agent is telling you that it's claiming to be Googlebot. Under the right circumstances this can be a valuable signal of malfeasance and worth denying access.

Similarly, a web agent that emits user agent hints while its HTTP User-Agent is claiming to be an authentic version of Firefox 147 is giving you the signal that it's not an unaltered, standard version of Firefox, because standard versions of Firefox 147 don't do that. It's most likely something built on Chromium, but in any case you might decide that this signal means it is suspicious enough to be denied access. Neither the User-Agent nor the Sec-CH-UA headers create true facts to definitively identify the browser and both could be faked by the attacker, but the inconsistency is real.

What an attacker tells you (deliberately or accidentally) is a signal, and it's up to you to interpret and use that signal (which I think you should these days). This is an empirical thing, something that depends on the surrounding environment (for example, you have to interpret the attacker's signal in terms of its difference from the signals of legitimate visitors), what you're doing, and what you care about, but then security is always ultimately people, not math, even though tech loves to avoid this sort of empiricism (which is a bad thing).

As a pragmatic thing, it's usually easier to use attacker signals if you allow things by default rather than deny them by default. If you allow by default, your primary concern is false positives (legitimate visitors who are emitting signals you find too suspicious), rather than false negatives, because an attacker that wants to work hard enough can always obtain access. Conveniently, public web sites (such as Wandering Thoughts) are exactly such an allow by default environment, which is why these days I use a lot of signals here when deciding what to accept or block (including IP addresses and networks).

(If you need a deny by default environment with real security, you need to use something that attackers can't fake. IP addresses can be one option in the right circumstances, but they aren't the only one.)

I think dependency cooldowns would be a good idea for Go

By: cks

Via Filippo Valsorda, I recently heard about a proposal to add dependency cooldowns to Go. The general idea of dependency cooldowns is to make it so that people don't immediately update to new versions of dependencies; instead, you wait some amount of time for people to inspect the new version and so on (either through automated tooling or manual work). Since one of Go's famous features is 'minimum version selection', you might think that a cooldown would be unnecessary, since people have to manually update the version of dependencies anyway and don't automatically get them.

Unfortunately, this is not the actual observed reality. In the actual observed reality, people update dependency versions fast enough to catch out other people who change what a particular version is of a module they publish. This seems to be in part from things like 'Dependabot' automatically cruising around looking for version updates, but in general it seems clear that some amount of people will update to new versions of dependencies the moment those new versions become visible to them. And if a dependency is used widely enough, through random chance there's pretty much always going to be a developer somewhere who is running 'go list -m -u all' right after a new version of the package is released. So I feel that some sort of a cooldown would be useful in practice, even with Go's other protections.

I follow the VCS repositories of a fair number of Go projects, and a lot of their dependency updates are automated, through things like Dependabot. If these things supported dependency cooldowns and if people turned that on, we might get a lot of the benefit without Go's own mechanisms having to add code to support this. On the other hand, not everyone uses Dependabot or equivalent features (especially if people migrate away from Github, as some are) and there's always going to be people checking and doing dependency updates by hand. To support them, we need assistance from tooling.

(In theory this tooling assistance could be showing how old a version is then leaving it up to people to notice and decide, but in practice I feel that's abrogating responsibilities. We've seen that show before; easy support and defaults matter.)

While I don't have any strong or well informed opinions on how this should be implemented in Go, I do feel that both defaults and avoiding mistakes are important. This biases me towards, say, a setting for this in your go.mod, because then that way it's automatically persistent and everyone who works on your project gets it applied automatically, unlike (for example) an environment variable that you have to make sure everyone has set.

(This elaborates on some badly phrased thoughts I posted on the Fediverse.)

On today's web, HTTP results depend on the HTTP User-Agent you use

By: cks

Back in the old days, search engines mostly crawled your sites with their regular, clearly identifying HTTP User-Agent headers, but once in a while they would switch up to fetching with a browser's User-Agent. What they were trying to detect was if you served one set of content to "Googlebot" but another set of content to "Firefox", and if you did they tended to penalize you; you were supposed to serve the same content to both, not SEO-bait to Googlebot and wall to wall ads to browsers. Googlebot identified itself as a standard courtesy, not so you could handle it differently.

Obviously those days are long over. It's now routine and fully accepted to serve different things to Googlebot and to regular browsers. Generally websites offer Googlebot more access and plain text, and browsers less access (even paywalls) and JavaScript encrusted content (leading to people setting their User-Agent to Googlebot to bypass paywalls). Since people give Googlebot special access, people impersonate it and other well accepted crawlers and other people (like me) block that impersonation.

This is part of an increasingly common general pattern, which is that different HTTP User-Agents get different results for the same URL. Especially, some HTTP User-Agents will get errors, HTTP redirections, or challenge pages, and other User-Agents won't; instead they'll get the real content. What this means in concrete terms is it's increasingly bad to take the results from one HTTP User-Agent and assume they apply for another. This isn't just me and Wandering Thoughts; for example, if a site has a standard configuration of Anubis, having a User-Agent that includes 'Mozilla' will cause you to get a challenge page instead of the actual page (cf).

(One of the amusing effects of this is what it does to 'link previews', which require the website displaying the preview to fetch a copy of the URL from the original site. On the Fediverse, fairly often the link preview I see is just some sort of a challenge page.)

In practice, you're probably reasonably safe if you're doing close variations of what's fundamentally the same distinctive User-Agent. But you're living dangerously if you try this with browser-like User-Agent values, either two different ones or a browser-like User-Agent and a distinctive non-browser one, because those are the ones that are most frequently forged and abused by covert web crawlers and other malware. Everyone who wants to look normal is imitating a browser, which means looking like a browser is a bad idea today.

Unfortunately, however bad an idea it is, people seem to keep trying fetches with multiple User-Agent header values and then taking a result from one User-Agent and using it in the context of another. Especially, feed reader companies seem to do it, first Feedly and now Inoreader.

You (I) should document the forms of your Django web application

By: cks

We have a long-standing Django web application to handle (Unix) account requests. Since these are requests, there is some state involved, so for a long time a request could be pending, approved, or rejected, with the extra complexity that an approved request might be incomplete and waiting on the person to pick their login. Recently I added being able to put a request into a new state, 'held', in order to deal with some local complexities where we might have a request that we didn't want to delete but also didn't want to go through to create an account.

(For instance, it's sometimes not clear if new incoming graduate students who've had to defer their arrival are going to turn up later or wind up not coming at all. So now we can put their requests on hold.)

When I initially wrote the new code, I though that this new 'held' status was relatively weak, and in particular that professors (who approve accounts) could easily take an account request out of 'held' status and approve it. At the time I decided that this was probably a feature, since a professor might know that one of their graduate students was about to turn up after all and this way they didn't have to get us to un-hold the account request. Then the other day we sort of wanted to hold an account request even against the professor involved approving it, and because I knew that the 'held' status was weak this way, I didn't bother trying.

Well, it turns out I was wrong. Because I had forgotten how our forms worked, I hadn't realized that my new 'held' status was less 'held' and more 'frozen', and I only learned better today because I took a stab at creating a real 'frozen' status. In the current state, while it's possible for professors to deliberately un-hold a request, it takes a certain amount of work to find the one obscure place it's possible and you can't do it by accident (and it would be easy to close that possibility off if we decided to). You definitely can't accidentally approve a request that's currently held without realizing it.

(So my admittedly modest amount of work to add a 'frozen' status was sort of wasted, although it did lead to greater understanding in the end.)

Past me, immersed in the application, presumably found all of the rules about who could see what form and what they showed to be obvious (at least in context). Present me is a long distance from past me and did not remember all of those things. Brief documentation on each form would have been really quite handy, and if I'm smart I'll spend some time this time around to write some.

I'm not sure where I'll put any new forms documentation. Probably not in our views.py, which is already big enough. I could put it in urls.py, or I could write a separate README.forms file that doesn't try to embed this in code. And I know that I don't want to put it in Python docstrings, because I wrote some things in Python docstrings on the existing forms functions and then didn't read them. Even if I had read them, the existing docstrings don't entirely cover the sort of information I now know I want to know.

(I think there's a good reason for my not reading my own docstrings, but that's for another entry.)

UEFI-only booting with GRUB has gone okay on our (Ubuntu 24.04) servers

By: cks

We've been operating Ubuntu servers for a long time and for most of that time we've booted them through traditional MBR BIOS boots. Initially it was entirely through MBR and then later it was still mostly through MBR (somewhat depending on who installed a particular server; my co-workers are more tolerant of UEFI than I am). But when we built the 24.04 version of our customized install media, my co-worker wound up making it UEFI only, and so for the past two years all of our 24.04 machines have been UEFI (with us switching BIOSes on old servers into UEFI mode as we updated them). The headline news is that it's gone okay, more or less as you'd expect and hope by now.

All of our servers have mirrored system disks, and the one UEFI thing we haven't really had to deal with so far is fixing Ubuntu's UEFI boot disk redundancy stuff after one disk fails. I think we know how to do it in theory but we haven't had to go through it in practice. It will probably work out okay but it does make me a bit nervous, along with the related issue that the Ubuntu installer makes it hard to be consistent about which disk your '/boot/efi' filesystem comes from.

(In the installer, /boot/efi winds up on the first disk that you set as the boot device, but the disks aren't always presented in order so you can do this on 'the first disk' in the installer and discover that the first disk it listed was /dev/sdb.)

The Ubuntu 24.04 default bootloader is GRUB, so that's what we've wound up with even though as a UEFI-only environment we could in theory use simpler ones, such as systemd-boot. I'm not particularly enthused about GRUB but in practice it does what we want, which is to reliably boot our servers, and it has the huge benefit that it's actively supported by Ubuntu (okay, Canonical) so they're going to make sure it works right, including with their UEFI disk redundancy stuff. If Ubuntu switches default UEFI bootloaders in their server installs, I expect we'll follow along.

(I don't know if Canonical has any plans to switch away from GRUB to something else. I suspect that they'll stick with GRUB for as long as they support MBR booting, which I suspect will be a while, especially as people look more and more likely to hold on to old hardware for much longer than normally expected.)

PS: One reason I'm writing this down is that I've been unenthused about UEFI for a long time, so I'm not sure I would have predicted our lack of troubles in advance. So I'm going to admit it, UEFI has been actually okay. And in its favour, UEFI has regularized some things that used to be pretty odd in the MBR BIOS era.

(I'm still not happy about the UEFI non-story around redundant system disks, but I've accepted that hacks like the Ubuntu approach are the best we're going to get. I don't know what distributions such as Fedora are doing here; my Fedora machines are MBR based and staying that way until the hardware gets replaced, which on current trends won't be any time soon.)

I haven't made anything with AT Proto yet

Landscape

I haven't made anything with AT Proto.

Okay, technically, I did made the Bluesky ThinkUp Tribute, which syncs with your Bluesky account and sends a nightly email about who changed their bio or handle on the website. It's a great little utility and I rely on it constantly. But that doesn't integrate very deeply with AT Proto.

I've fallen into the cycle of reading about AT Proto but not building anything on it: a pattern that I want to break. I blame other priorities for my lack of weekend hacking - when I do get time and energy to computer on the weekends I've spent it on maintaining and contributing to established projects instead of building new experiments. And my time during the week is mostly spent on Val Town priorities, like keeping the servers online, developing features, and implementing moderation.

I don't especially like writing about things without having 'something to show,' but to avoid the trap of neither writing nor building, here's some writing.


The tech that runs Bluesky is general-purpose

The AT Protocol is the tech that Bluesky, the Twitter alternative, is built on. It's fairly general-purpose and well-suited for building all kinds of applications, not just Bluesky, and has some very utopian ideas built in. Collectively, we're calling the stack and its applications the 'Atmosphere.'

This has been, recently, in my filter bubble, a big deal. Applications like Leaflet for blogging and tangled, a GitHub alternative, use the AT Protocol as core architecture, storing data on it, allowing other applications to provide alternative frontends, and using its identity system to let people log in with their domain names or Bluesky handles.

It is a breath of fresh air in the tech industry. The creativity of this community is inspiring, and with a few exceptions people are friendly and welcoming.


AT Proto learned lessons from other decentralization attempts

Decentralization has had a lot of false starts: see my old posts on Dat, IPFS, IPFS again, and Arweave for some of that backstory. I am a seeker in that space, ready to try out what's new and hoping that the technology works, even though most of the results so far have been lackluster.

The Bluesky team has a lot of experience with those previous efforts: Paul Frazee, the CTO cofounded Blue Link Labs which made Beaker and integrated with Dat, and he worked on Secure Scuttlebutt before that. Other Bluesky employees like Jeromy Johnson came from the IPFS team.

So Bluesky is a lot of people's second or third try at making decentralization work, and it shows in some of the thinking, especially Paul's writing about how Bluesky compares to P2P and magical mesh networks.

This is encouraging. A lot of decentralization ideas work in theory but not in practice. Much of the challenge is practical and human-level, and it is good that it seems like the Bluesky team anticipated things like moderating content from day one.


It's more like a magical database than like a new internet

The AT Protocol is a lot different from the decentralization tech that I've played around with the most, like Dat and IFPS. Both Dat and IPFS are kind of like 'generic blob stores': you can store any kind of content on them, and they had URL-like addressing for that content. They both aspired to be a sort of future-internet in shape: Dat had the Beaker Browser and for a while IPFS was built into the Brave Browser. So I kept trying to deploy my website onto these technologies, with varying success, and IPFS tried to host all of Wikipedia, with varying success.

AT Proto is more like a magic semi-schemaless database. A 'post' on Bluesky looks like this:

{
  "text": "some placemark updates: sorting & resizing table columns, new releases of simple-statistics and tokml, using changesets in all my projects\n\nmacwright.com/2026/03/15/o...",
  "$type": "app.bsky.feed.post",
  "embed": {
    "$type": "app.bsky.embed.external",
    "external": {
      "uri": "https://macwright.com/2026/03/15/oss-changelog",
      "title": "Placemark & OSS Changelog",
      "description": "JavaScript, math, maps, etc"
    }
  },
  "langs": ["en"],
  "facets": [{
    "index": { "byteEnd": 169, "byteStart": 140 },
    "features": [
      {
        "uri": "https://macwright.com/2026/03/15/oss-changelog",
        "$type": "app.bsky.richtext.facet#link"
      }
    ]
  }],
  "createdAt": "2026-03-15T23:03:29.022Z"
}

It's JSON-encoded, structured, and opinionated, and importantly, limited in size. Don't expect to put a ton of data in this Record - right now a record can't be more than 1MB when encoded as CBOR.

Of course a modern social network is nothing without images and vertical video, so Bluesky needs to store more than just JSON documents, and so there's Blob support - stored as raw binary data, referenced from a Record. Though blobs are limited too, with the limits varying by server but usually 100MB.

This was a big realization for me around tangled - that project which is extremely cool (rebuilding a more decentralized code collaboration platform) is not using AT Proto to store git data, but rather has servers called Knots that handle the git parts. It's a very cool infrastructure, but important to note that the way in which metadata and git content are stored is quite different.


Is AT Protocol a good database?

Obviously it isn't (just) a database but it's a useful frame: how does AT Protocol work with typical database requirements?

  • Can it store a lot of data? Yes, but in small bits. It's more of a DynamoDB than an S3.
  • Is it fast? It's surprisingly fast from what I've seen: stream.place uses the protocol for comments on livestreams, and they work quite well.
  • Is it reliable? It seems so: the whole thing is built on event-sourcing and streams, and it has both the ability to replay streams when servers go down as well as to sync archival data.
  • Is it decentralized? Kind of? It's federated, but if you have your data stored in a Personal Data Server, it isn't automatically replicated to other servers on the network. This is a unlike 'magical mesh networks' like secure scuttlebutt which store lots of copies of data.
  • Is it indexed? Kind of! Obviously you don't want to process all of the data across all of the Bluesky network, and thankfully services like jetstream let you filter to only a specific collection.

Privacy is still a hard unsolved problem

So: you can store structured documents on AT Proto and small binary blobs - what about privacy? This might change soon because there's so much active development, but right now: you can't really use AT Proto for private data.

Paul has written a great discussion of different approaches, and it's clear that there are deep problems that require introspection and thorough evaluation, but that nothing is deployed yet. Bluesky does have direct messages, but according to Gavin Anderegg's investigation, they're 'off-protocol' so not actually anywhere on AT Proto.

This is obviously a big stumbling block for applications. Val Town couldn't use the Atmosphere for data storage if there is no concept of privacy. Right now our traditional backend infrastructure (mostly Postgres) makes both privacy and good-enough encryption at rest (mostly AES-GCM) pretty simple to implement, if not foolproof.

There are experiments around implementing privacy on AT Proto, like Germ, but none have solved all of the problems that need solving.


What should I do with it?

I have plenty of existing and potential projects to use as testbeds for new technology: that's one of the main reason why side-projects can be so nice, is that they're safe places to use bleeding-edge technology without risking alienating your entire team at work. So where can I use AT Proto?

I would love to support sharing maps on Placemark again. Geospatial data probably won't fit in AT Proto records because it's fiendishly large and complex, but it could be squeezed into a blob if it's small enough. Maybe encoding JSON as CBOR is enough to shrink the data a bit without losing fidelity.

It would be really fun to get AT Proto logins working with Val Town: Orta implemented something similar for Puzzmo. Unfortunately user signup is a very knotty problem for us because, like every other hosting platform, we are in a daily battle with spammers. Orta's solution for Puzzmo was to make Bluesky login an additional, linked account along with your existing Puzzmo account, which makes a lot of sense.

I could also try to put this blog on AT Proto. standard.site has some specs for doing that, and sequoia would make publishing pretty easy. Leaflet.pub is riding high on their adoption of AT Proto for blogging. I'm honestly more confused than excited about this possibility: partly because RSS is already so good for publishing blogs, and because I'm not sure what syndicating to the atmosphere really does for this blog? I especially don't want to publish on AT Proto first, because rule #1 of macwright.com is to keep this site alive forever and avoid boondoggles.


Where does this go?

AT Proto is in a creative-explosion phase, which is really exciting. The way that the platform has been crafted makes it easy to incrementally introduce Atmosphere features to existing applications, and I am really relieved how little unnecessary jargon there seems to be, even though it's a very complicated system.

Of all the values it provides, I think a rock-solid sense of credible exit is the most consistently achieved. Being able to plug a different application into the same data, or to move your data from one host to another is incredible, as Dan Abramov wrote about in 'A Social Filesystem'.

Having been on the internet for a long time, I don't expect anything to last forever, and I won't be heartbroken when the flaws in the plan are inevitably identified or some bad actor spoils the party for a while.

I wonder about the long-term economics of the thing, though: Bluesky is essentially providing a free database to anyone who wants to implement the AppView part of the system. How long does this last, especially if some Atmosphere apps become successful and start generating lots of revenue. Companies do not like subsidizing each other.

I think that's a few years off. Maybe we start paying for a deluxe plan once we store a gigabyte or two on Bluesky's servers, or one of the stablecoin-based micropayments technologies takes off (let's be real, if one does, it'll be Stripe's) and popular applications pay for their user storage on other PDS systems, in a faint echo of Filecoin's failure.


A spell for creativity

I plan to return here and have something to show on AT Proto. Not to overthink it, to ship something. It's fun to read but even more fun to write code, or a bit of manic fun to use LLMs to prototype something. I'm having more success drawing a portrait every day and using my sewing machine than working on the internet on the weekends, but that is partly because of a pessimistic view of the current trend, and the Atmosphere is a trend I can get behind.

Another round of reporting on feed readers

Yeah, here we go again, it's time to talk about feed readers. I'll summarize what's been happening with those tests which have been active in the past 7 days. There are many more that started and then ended and which are not included as a result.

Side note: I'm about to roll the hostname like I did last year, so if you're participating and want to carry on, go back to the URL from the welcome mail when it stops resolving in DNS. Then delete the current testing feed and add the new one.

I do this because a lot of these things seem to be forgotten over time, and they're just piling up in my database for nobody's benefit. Dropping the DNS leaves the jank entirely on *their* side of the Internet. I mean, if you run a test of a reader for hundreds of days and never improve anything, what's the point?

One thing I wanted to note before I get to the list: there are a couple of readers which have apparently added support for the Cache-Control header, and specifically the "max-age=nnnnn" part of it. The test feed sends that out, and I change the values sometimes to see which readers speed up and slow down accordingly. To the authors of those projects: I see you, and I appreciate your work! I'd put a gold star on your laptop if I could. (Not all of them are in this report since they have finished testing and are no longer reporting in.)

So then, let's talk turkey here. I'm grouping the results by client just for simplicity. Remember that means it includes vastly different config options used by different people, different versions, different upgrade cadences, and (sigh), yes, different amounts of people clicking reload.

Audrey2: 402 days, no complaints. Pretty sure this one honors Cache-Control, so thanks for that!

Miniflux: about 10 instances. My reports for them mostly go something like "400 days, no complaints", "402 days, three too-short polls", "185 days, one too-short poll", "one <1s double-poll after 400 days of otherwise perfect behavior".

It's just that chill in Miniflux world.

Otocyon: 365 days on the nose, and not so much as a hiccup from the single instance that's reporting in. This was one of the "unpublished, please don't shame me yet" agents that I only mentioned anonymously in prior reports.

NetNewsWire: about 5 of these instances, and they're all inspiring. They all show a marked change in behaviors once upgrading past a certain point. NNW itself added some cheeky code to send version numbers to a couple of sites and I'm one of them (yes, I see what you did there). Anyway, I can now see that people are in fact upgrading, and the vastly improved behaviors speak to the work that was clearly done behind the scenes.

The NNW per-instance log tables all show the same thing: a bunch of red cells for this problem or that problem, then that instance upgrades and *poof*, gone, and everything's clear and happy. It's like one of those commercials for eye drops from the 80s.

Vienna: a couple of instances, both active 350+ days. One had no complaints and the other has a handful of short polls. By that, I mean "less than an hour between requests". I assume this is from someone clicking refresh. One "refreshing" note is that they're still making conditional requests when this happens, so they aren't wasting much bandwidth.

FreshRSS: a couple of instances for this one, too. One has some short polls and unconditional requests over 206 days. The other one got past some kind of weird buggy spot that was in 1.25.0, and has been smooth sailing ever since (nearly a year now).

newsgoat: just one of these, and it was wobbly and too-quick at the beginning but then settled down. I would like to see how it does now. This is another reason I roll the DNS entries and reset the data: I want to see how things are working with the latest code, and leave the older stuff behind.

CommaFeed: just one of these, and it had some kind of caching issue that disappeared shortly before it flipped to version 5.7.0 in April 2025, and now it's fine. This is another one that would probably benefit from the upcoming fresh start.

Feedbin: did a double-tap startup, where it polls (unconditionally) twice in quick succession (< 1 second apart) when the feed was added. Two instances did this, but this was back in January 2025. This is another spot where I'd like to see how it behaves now.

Rapids: 354 days, no complaints.

NextCloud-News: this one had some weird If-Modified-Since values and double-tapping at startup, but that was January 2025. (Yes, this is the reader that once sent the infamous "1800" IMS value). It's come a long way since then and I'd like to see a fresh start from it to appreciate the work that's been done to it.

Bloggulus: 374 days, one slightly too quick poll. Hard to complain about that.

MANOS: 391 days, and nothing to complain about. I do hear Torgo's haunting theme music every time I write about it, though.

unnamed-feed-reader: technically, that's a name. 402 days, no complaints.

Something that doesn't even send a user-agent header: yeah, that's not cool. Send SOMETHING. Come on. 399 days of just doing that but otherwise getting everything else correct, somehow. One nit: sends "" as the If-None-Match at startup instead of just not sending the header at all. (Sounds like the usual "null is not zero is not the empty string is not the lack of a header is not ..." type thing.)

Some unidentified Firefox extension: it looks like it's still doing the 2000-01-01 If-Modified-Since once in a great great while (like, once), and it did a super quick poll (two in under a second) once. Otherwise it's been pretty quiet over the past 401 days.

There's also a Thunderbird instance which does the same 2000-01-01 startup but otherwise just quietly does its thing.

NewsBlur: double-tapped at startup (March 2025), and then sent lots of nutty out of sequence If-Modified-Since values. By nutty, I mean "went to sleep for 303 days, then came back, and started sending the *previous IMS value*". How do you do that when the server is hitting you in the face with a new value every time you poll?

Zufeed: unconditional double-tap at startup. Might be fixed. Need to see a fresh startup to be sure.

Roy and Rianne's ... etc: handful of unconditional requests over the past couple of months.

walrss: same deal: a couple of unconditionals in ~400 days.

Yarr: multiple too-fast unconditionals at startup (January 2025), and then a bunch more after that.

SpaceCowboys ... etc: one instance of this program, and it did something bizarre where an ancient ETag value popped back something like three months after it stopped being served to clients. Also sends some unconditional requests and a few too-fast polls.

feed2exec: poll frequencies are all over the place, and it has the usual 59 minute vs. 60 minute fenceposting thing. The version number is static throughout so it's not clear if it improved during these past 340 days.

Russet: a bunch of unconditional requests and the 59/60 minute thing, like others.

There are a few others which are active but which have had multiple user-agents prodding them and so polluted the data. The interval checking and IMS/INM comparisons mean nothing when multiple programs are involved, so I have to ignore those as corrupted.

And finally...

Free Reader: 100% unconditionals. Why?

QuiteRSS (I think - it lies in its User-Agent, which itself is evil): 100% unconditionals. Also, why?

Inoreader: one instance sends unconditional requests basically every other poll. Awful. The other instance sends a bunch of unconditionals, *and* it polls too quickly, including sub-second repeat polls at times. WTF?

inforss: something like 6% unconditionals out of > 2000 requests in ~400 days. I don't get it. The 59m vs. 60m poll-timing fencepost thing it also does is minor by comparison.

feedparser: weird timing, also has problems trying to hit the 60 minute mark and instead comes a bit too early, like some others. Also frequently calls back far too quickly.

Newsboat: ETag caching is still very broken and it will get into this pathological case where it keeps sending old values even though the server is hitting it over the head with a fresh one every single time. This means it latches into 100% unconditionals in effect and that's terrible. This seems to keep happening despite the version number changing, and it's affecting both instances which are reporting in.

BazQux: hundreds of out-of-sequence IMS and INM values stemming from something very very wrong with their caching implementation, resulting in 100% unconditional request generation once it latches in that state. A lot like Newsboat in that respect.

When internal hostnames are leaked to the clown

So here's a situation: you buy a "NAS" box - network attached storage, bring it home, stick some drives in it, and plop it on your network. The thing has an affinity for running in https mode, so you drop a wildcard certificate on it inside a sub-zone of a domain you don't even use for anything meaningful on the public Internet.

By sub-zone, I mean that your wildcard TLS cert is for something like *.nothing-special.whatever.example.com. It's buried pretty deep.

You put an entry for it in your hosts file:

172.16.12.34   nas.nothing-special.whatever.example.com

Then you load that in your browser and it works.

A few days later, you notice that you've started getting requests coming to your server on the "outside world" with that same hostname. This is a hostname that only exists in the /etc/hosts file of your laptop.

You're able to see this because you set up a wildcard DNS entry for the whole "*.nothing-special.whatever.example.com" space pointing at a machine you control just in case something leaks. And, well, something *did* leak.

Every time you load up the NAS, you get some clown GCP host knocking on your door, presenting a SNI hostname of that thing you buried deep inside your infrastructure. Hope you didn't name it anything sensitive, like "mycorp-and-othercorp-planned-merger-storage", or something.

Around this time, you realize that the web interface for this thing has some stuff that phones home, and part of what it does is to send stack traces back to sentry.io. Yep, your browser is calling back to them, and it's telling them the hostname you use for your internal storage box. Then for some reason, they're making a TLS connection back to it, but they don't ever request anything. Curious, right?

This is when you fire up Little Snitch, block the whole domain for any app on the machine, and go on with life.

Using this sentry reporting mechanism as a way to make them scan arbitrary other hosts (in DNS) is left as an exercise for the reader.

Reverse engineering the Creative Katana V2X soundbar to be able to control it from Linux

I recently purchased a Creative Sound Blaster Katana V2X soundbar (what a mouthful) to replace my old, cheap Logitech computer speakers. They served me well, but listening to music or watching movies was not the best-sounding experience.

After arriving, I set it up and realized it had an USB port which, aside from being able to use it as an audio input, allows the user to configure the speaker: Set the EQ, set the LED lights in different modes, etc. The unfortunate part of this was the fact that it requires the (proprietary) Creative App to use. What's more, it only seems to be available for Windows, which I don't use. While using it in a VM worked, it was hardly convenient.

This seemed like the perfect opportunity for something I love: Reverse engineering proprietary applications, devices and protocols and writing tools to communicate with them.

Instruction decoding in the Intel 8087 floating-point chip

In the 1980s, if you wanted your IBM PC to run faster, you could buy the Intel 8087 floating-point coprocessor chip. With this chip, CAD software, spreadsheets, flight simulators, and other programs were much speedier. The 8087 chip could add, subtract, multiply, and divide, of course, but it could also compute transcendental functions such as tangent and logarithms, as well as provide constants such as π. In total, the 8087 added 62 new instructions to the computer.

But how does a PC decide if an instruction was a floating-point instruction for the 8087 or a regular instruction for the 8086 or 8088 CPU? And how does the 8087 chip interpret instructions to determine what they mean? It turns out that decoding an instruction inside the 8087 is more complicated than you might expect. The 8087 uses multiple techniques, with decoding circuitry spread across the chip. In this blog post, I'll explain how these decoding circuits work.

To reverse-engineer the 8087, I chiseled open the ceramic package of an 8087 chip and took numerous photos of the silicon die with a microscope. The complex patterns on the die are formed by its metal wiring, as well as the polysilicon and silicon underneath. The bottom half of the chip is the "datapath", the circuitry that performs calculations on 80-bit floating point values. At the left of the datapath, a constant ROM holds important constants such as π. At the right are the eight registers that the programmer uses to hold floating-point values; in an unusual design decision, these registers are arranged as a stack. Floating-point numbers cover a huge range by representing numbers with a fractional part and an exponent; the 8087 has separate circuitry to process the fractional part and the exponent.

Die of the Intel 8087 floating point unit chip, with main functional blocks labeled. The die is 5 mm×6 mm. Click this image (or any others) for a larger image.

Die of the Intel 8087 floating point unit chip, with main functional blocks labeled. The die is 5 mm×6 mm. Click this image (or any others) for a larger image.

The chip's instructions are defined by the large microcode ROM in the middle.1 To execute an instruction, the 8087 decodes the instruction and the microcode engine starts executing the appropriate micro-instructions from the microcode ROM. In the upper right part of the chip, the Bus Interface Unit (BIU) communicates with the main processor and memory over the computer's bus. For the most part, the BIU and the rest of the chip operate independently, but as we will see, the BIU plays important roles in instruction decoding and execution.

Cooperation with the main 8086/8088 processor

The 8087 chip acted as a coprocessor with the main 8086 (or 8088) processor. When a floating-point instruction was encountered, the 8086 would let the 8087 floating-point chip carry out the floating-point instruction. But how do the 8086 and the 8087 determine which chip executes a particular instruction? You might expect the 8086 to tell the 8087 when it should execute an instruction, but this cooperation turns out to be more complicated.

The 8086 has eight opcodes that are assigned to the coprocessor, called ESCAPE opcodes. The 8087 determines what instruction the 8086 is executing by watching the bus, a task performed by the BIU (Bus Interface Unit).2 If the instruction is an ESCAPE, the instruction is intended for the 8087. However, there's a problem. The 8087 doesn't have any access to the 8086's registers (and vice versa), so the only way that they can exchange data is through memory. But the 8086 addresses memory through a complicated scheme involving offsest registers and segment registers. How can the 8087 determine what memory address to use when it doesn't have access to the registers?

The trick is that when an ESCAPE instruction is encountered, the 8086 processor starts executing the instruction, even though it is intended for the 8087. The 8086 computes the memory address that the instruction references and reads that memory address, but ignores the result. Meanwhile, the 8087 watches the memory bus to see what address is accessed and stores this address internally in a BIU register. When the 8087 starts executing the instruction, it uses the address from the 8086 to read and write memory. In effect, the 8087 offloads address computation to the 8086 processor.

The structure of 8087 instructions

To understand the 8087's instructions, we need to take a closer look at the structure of 8086 instructions. In particular, something called the ModR/M byte is important since all 8087 instructions use it.

The 8086 uses a complex system of opcodes with a mixture of single-byte opcodes, prefix bytes, and longer instructions. About a quarter of the opcodes use a second byte, called ModR/M, that specifies the registers and/or memory address to use through a complicated encoding. For instance, the memory address can be computed by adding the BX and SI registers, or from the BP register plus a two-byte offset. The first two bits of the ModR/M byte are the "MOD" bits. For a memory access, the MOD bits indicate how many address displacement bytes follow the ModR/M byte (0, 1, or 2), while the "R/M" bits specify how the address is computed. A MOD value of 3, however, indicates that the instruction operates on registers and does not access memory.

Structure of an 8087 instruction

Structure of an 8087 instruction

The diagram above shows how an 8087 instruction consists of an ESCAPE opcode, followed by a ModR/M byte. An ESCAPE opcode is indicated by the special bit pattern 11011, leaving three bits (green) available in the first byte to specify the type of 8087 instruction. As mentioned above, the ModR/M byte has two forms. The first form performs a memory access; it has MOD bits of 00,01, or 10 and the R/M bits specify how the memory address is computed. This leaves three bits (green) to specify the address. The second form operates internally, without a memory access; it has MOD bits of 11. Since the R/M bits aren't used in the second form, six bits (green) are available in the R/M byte to specify the instruction.

The challenge for the designers of the 8087 was to fit all the instructions into the available bits in such a way that decoding is straightforward. The diagram below shows a few 8087 instructions, illustrating how they achieve this. The first three instructions operate internally, so they have MOD bits of 11; the green bits specify the particular instruction. Addition is more complicated because it can act on memory (first format) or registers (second format), depending on the MOD bits. The four bits highlighted in bright green (0000) are the same for all ADD instructions; the subtract, multiplication, and division instructions use the same structure but have different values for the dark green bits. For instance, 0001 indicates multiplication and 0100 indicates subtraction. The other green bits (MF, d, and P) select variants of the addition instruction, changing the data format, direction, and popping the stack at the end. The last three bits select the R/M addressing mode for a memory operation, or the stack register ST(i) for a register operation.

The bit patterns for some 8087 instructions. Based on the datasheet.

The bit patterns for some 8087 instructions. Based on the datasheet.

Selecting a microcode routine

Most of the 8087's instructions are implemented in microcode, implementing each step of an instruction in low-level "micro-instructions". The 8087 chip contains a microcode engine; you can think of it as the mini-CPU that controls the 8087 by executing a microcode routine, one micro-instruction at a time. The microcode engine provides an 11-bit micro-address to the ROM, specifying the micro-instruction to execute. Normally, the microcode engine steps through the microcode sequentially, but it also supports conditional jumps and subroutine calls.

But how does the microcode engine know where to start executing the microcode for a particular machine instruction? Conceptually, you could feed the instruction opcode into a ROM that would provide the starting micro-address. However, this would be impractical since you'd need a 2048-word ROM to decode an 11-bit opcode.3 (While a 2K ROM is small nowadays, it was large at the time; the 8087's microcode ROM was a tight fit at just 1648 words.) Instead, the 8087 uses a more efficient (but complicated) instruction decode system constructed from a combination of logic gates and PLAs (Programmable Logic Arrays). This system holds 22 microcode entry points, much more practical than 2048.

Processors often use a circuit called a PLA (Programmable Logic Array) as part of instruction decoding. The idea of a PLA is to provide a dense and flexible way of implementing arbitrary logic functions. Any Boolean logic function can be expressed as a "sum-of-products", a collection of AND terms (products) that are OR'd together (summed). A PLA has a block of circuitry called the AND plane that generates the desired sum terms. The outputs of the AND plane are fed into a second block, the OR plane, which ORs the terms together. Physically, a PLA is implemented as a grid, where each spot in the grid can either have a transistor or not. By changing the transistor pattern, the PLA implements the desired function.

A simplified diagram of a PLA.

A simplified diagram of a PLA.

A PLA can implement arbitrary logic, but in the 8087, PLAs often act as optimized ROMs.4 The AND plane matches bit patterns,5 selecting an entry from the OR plane, which holds the output values, the micro-address for each routine. The advantage of the PLA over a standard ROM is that one output column can be used for many different inputs, reducing the size.

The image below shows part of the instruction decoding PLA.6 The horizontal input lines are polysilicon wires on top of the silicon. The pinkish regions are doped silicon. When polysilicon crosses doped silicon, it creates a transistor (green). Where there is a gap in the doped silicon, there is no transistor (red). (The output wires run vertically, but are not visible here; I dissolved the metal layer to show the silicon underneath.) If a polysilicon line is energized, it turns on all the transistors in its row, pulling the associated output columns to ground. (If no transistors are turned on, the pull-up transistor pulls the output high.) Thus, the pattern of doped silicon regions creates a grid of transistors in the PLA that implements the desired logic function.7

Part of the PLA for instruction decoding.

Part of the PLA for instruction decoding.

The standard way to decode instructions with a PLA is to take the instruction bits (and their complements) as inputs. The PLA can then pattern-match against bit patterns in the instruction. However, the 8087 also uses some pre-processing to reduce the size of the PLA. For instance, the MOD bits are processed to generate a signal if the bits are 0, 1, or 2 (i.e. a memory operation) and a second signal if the bits are 3 (i.e. a register operation). This allows the 0, 1, and 2 cases to be handled by a single PLA pattern. Another signal indicates that the top bits are 001 111xxxxx; this indicates that the R/M field takes part in instruction selection.8 Sometimes a PLA output is fed back in as an input, so a decoded group of instructions can be excluded from another group. These techniques all reduce the size of the PLA at the cost of some additional logic gates.

The result of the instruction decoding PLA's AND plane is 22 signals, where each signal corresponds to an instruction or group of instructions with a shared microcode entry point. The lower part of the instruction decoding PLA acts as a ROM that holds the 22 microcode entry points and provides the selected one.9

Instruction decoding inside the microcode

Many 8087 instructions share the same microcode routines. For instance, the addition, subtraction, multiplication, division, reverse subtraction, and reverse division instructions all go to the same microcode routine. This reduces the size of the microcode since these instructions share the microcode that sets up the instruction and handles the result. However, the microcode obviously needs to diverge at some point to perform the specific operation. Moreover, some arithmetic opcodes access the top of the stack, some access an arbitrary location in the stack, some access memory, and some reverse the operands, requiring different microcode actions. How does the microcode do different things for different opcodes while sharing code?

The trick is that the 8087's microcode engine supports conditional subroutine calls, returns, and jumps, based on 49 different conditions (details). In particular, fifteen conditions examine the instruction. Some conditions test specific bit patterns, such as branching if the lowest bit is set, or more complex patterns such as an opcode matching 0xx 11xxxxxx. Other conditions detect specific instructions such as FMUL. The result is that the microcode can take different paths for different instructions. For instance, a reverse subtraction or reverse division is implemented in the microcode by testing the instruction and reversing the arguments if necessary, while sharing the rest of the code.

The microcode also has a special jump target that performs a three-way jump depending on the current machine instruction that is being executed. The microcode engine has a jump ROM that holds 22 entry points for jumps or subroutine calls.10 However, a jump to target 0 uses special circuitry so it will instead jump to target 1 for a multiplication instruction, target 2 for an addition/subtraction, or target 3 for division. This special jump is implemented by gates in the upper right corner of the jump decoder.

The jump decoder and ROM. Note that the rows are not in numerical order; presumably, this made the layout slightly more compact. Click this image (or any other) for a larger version.

The jump decoder and ROM. Note that the rows are not in numerical order; presumably, this made the layout slightly more compact. Click this image (or any other) for a larger version.

Hardwired instruction handling

Some of the 8087's instructions are implemented directly by hardware in the Bus Interface Unit (BIU), rather than using microcode. For example, instructions to enable or disable interrupts, or to save or restore state are implemented in hardware. The decoding for these instructions is performed by separate circuitry from the instruction decoder described above.

In the first step, a small PLA decodes the top 5 bits of the instruction. Most importantly, if these bits are 11011, it indicates an ESCAPE instruction, the start of an 8087 operation. This causes the 8087 to start interpreting the instruction and stores the opcode in a BIU register for use by the instruction decoder. A second small PLA takes the outputs from the top-5 PLA and combines them with the lower three bits. It decodes specific instruction values: D9, DB, DD, E0, E1, E2, or E3. The first three values correspond to specific ESCAPE instructions, and are recorded in latches.

The two PLAs decode the second byte in the same way. Logic gates combine the PLA outputs from the second byte with the latched values from the first byte, detecting eleven hardwired instructions.11 Some of these instructions operate directly on registers, such as clearing exceptions; the decoded instruction signal goes to the relevant register and modifies it in an ad hoc way. 12. Other hardwired instructions are more complicated, writing chip state to memory or reading chip state from memory. These instructions require multiple memory operations, controlled by the Bus Interface Unit's state machine. Each of these instructions has a flip-flop that is triggered by the decoded instruction to keep track of which instruction is active.

For the instructions that save and restore the 8087's state (FSAVE and FRSTOR), there's one more complication. These instructions are partially implemented in the BIU, which moves the relevant BIU registers to or from memory. But then, instruction processing switches to microcode, where a microcode routine saves or loads the floating-point registers. Jumping to the microcode routine is not implemented through the regular microcode jump circuitry. Instead, two hardcoded values force the microcode address to the save or restore routine.13

Constants

The 8087 has seven instructions to load floating-point constants such as π, 1, or log10(2). The 8087 has a constant ROM that holds these constants, as well as constants for transcendental operations. You might expect that the 8087 simply loads the specified constant from the constant ROM, using the instruction to select the desired constant. However, the process is much more complicated.14

Looking at the instruction decode ROM shows that different constants are implemented with different microcode routines: the constant-loading instructions FLDLG2 and FLDLN2 have one entry point; FLD1, FLD2E, FLDL2T, and FLDPI have a second entry point, and FLDZ (zero) has a third entry point. It's understandable that zero is a special case, but why are there two routines for the other constants?

The explanation is that the fraction part of each constant is stored in the constant ROM, but the exponent is stored in a separate, smaller ROM. To reduce the size of the exponent ROM, only some of the necessary exponents are stored. If a constant needs an exponent one larger than a value in the ROM, the microcode adds one to the exponent ROM value, computing the exponent on the fly.

Thus, the load-constant instructions use three separate instruction decoding mechanisms. First, the instruction decode ROM determines the appropriate microcode routine for the constant instruction, as before. Then, the constant PLA decodes the instruction to select the appropriate constant. Finally, the microcode routine tests the bottom bit of the instruction and increments the exponent if necessary.

Conclusions

To wrap up the discussion of the decoding circuitry, the diagram below shows how the different circuits are arranged on the die. This image shows the upper-right part of the die; the microcode engine is at the left and part of the ROM is at the bottom.

The upper-left portion of the 8087 die, with functional blocks labeled.

The upper-left portion of the 8087 die, with functional blocks labeled.

The 8087 doesn't have a clean architecture, but instead is full of ad hoc circuits and corner cases. The 8087's instruction decoding is an example of this. Decoding is complicated to start with due to the 8086's convoluted instruction formats and the ModR/M byte. On top of that, the 8087's instruction decoding has multiple layers: the instruction decode PLA, microcode conditional jumps that depend on the instruction, a special jump target that depends on the instruction, constants selected based on the instruction, and instructions decoded by the BIU.

The 8087 has a reason for this complicated architecture: at the time, the chip was on the edge of what was possible, so the designers needed to use whatever techniques they could to reduce the size of the chip. If implementing a corner case could shave a few transistors off the chip or make the microcode ROM slightly smaller, the corner case was worthwhile. Even so, the 8087 was barely manufacturable at first; early yield was just two working chips per silicon wafer. Despite this difficult start, a floating-point standard based on the 8087 is now part of almost every processor.

Thanks to the members of the "Opcode Collective" for their contributions, especially Smartest Blob and Gloriouscow.

For updates, follow me on Bluesky (@righto.com), Mastodon (@kenshirriff@oldbytes.space), or RSS.

Notes and references

  1. The contents of the microcode ROM are available here, partially decoded thanks to Smartest Blob. 

  2. It is difficult for the 8087 to determine what the 8086 is doing because the 8086 prefetches instructions. Thus, when an instruction is seen on the bus, the 8086 may execute it at some point in the future, or it may end up discarded.

    In order to tell what instruction is being executed, the 8087 floating-point chip internally duplicates the 8086 processor's queue. The 8087 watches the memory bus and copies any instructions that are prefetched. Since the 8087 can't tell from the bus when the 8086 starts a new instruction or when the 8086 empties the queue when jumping to a new address, the 8086 processor provides two queue status signals to the 8087. With the help of these signals, the 8087 knows exactly what the 8086 is executing.

    The 8087's instruction queue has six 8-bit registers, the same as the 8086. Surprisingly, the last two queue registers in the 8087 are tied together, so there are only five usable queue registers. My hypothesis is that since the 8087 copies the active instruction into separate registers (unlike the 8086), only five queue registers are needed. This raises the question of why the excess register wasn't removed from the die, rather than wasting valuable space.

    The 8088 processor, used in the IBM PC, has a four-byte queue instead of a six-byte queue. The 8088 is almost identical to the 8086 except it has an 8-bit memory bus instead of a 16-bit memory bus. With the narrower memory bus, prefetching is more likely to get in the way of other memory accesses, so a smaller prefetch queue was implemented.

    Knowing the queue size is essential to the 8087 floating-point chip. To indicate this, when the processor boots, a signal lets the 8087 determine if the attached processor is an 8086 or an 8088. 

  3. The relevant part of the opcode is 11 bits: the top 5 bits are always 11011 for an ESCAPE opcode, so they can be ignored during decoding. The Bus Interface Unit has a 3-bit register to hold the first byte of the instruction and an 8-bit register to hold the second byte. The BIU registers have an irregular appearance because there are 3-bit registers, 8-bit registers, and 10-bit registers (holding half of a 20-bit address). 

  4. What's the difference between a PLA and a ROM? There is a lot of overlap: a ROM can replace a PLA, while a PLA can implement a ROM. A ROM is essentially a PLA where the first stage is a binary decoder, so the ROM has a separate row for each input value. However, the first stage of a ROM can be optimized so multiple inputs share the same output value; is this a ROM or a PLA?

    The "official" difference is that in a ROM, one row is activated at a time, while in a PLA, multiple rows can be activated at once, so the output values are combined. (Thus, it is straightforward to read the values out of a ROM, but more difficult to read the values out of a PLA.)

    I consider the instruction decoding PLA to be best described as a PLA first stage with the second stage acting as a ROM. You could also call it a partially-decoded ROM, or just a PLA. Hopefully my terminology isn't too confusing. 

  5. To match a bit pattern in an instruction, the bits of the instruction are fed into the PLA, along with the complements of these bits; this allows the PLA to match against a 0 bit or a 1 bit. Each row of a PLA will match a particular bit pattern in the instruction: bits that must be 1, bits that must be 0, and bits that don't matter. If the instruction opcodes are assigned rationally, a small number of bit patterns will match all the opcodes, reducing the size of the decoder.

    I may be going too far with this analogy, but a PLA is a lot like a neural net. Each column in the AND plane is like a neuron that fires when it recognizes a particular input pattern. The OR plane is like a second layer in a neural net, combining signals from the first layer. The PLA's "weights", however, are fixed at 0 or 1, so it's not as flexible as a "real" neural net. 

  6. The instruction decoding PLA has an unusual layout, where the second plane is rotated 90°. In a regular PLA (left), the inputs (red) go into the first plane, the perpendicular outputs from the first plane (purple) go into the second plane, and the PLA outputs (blue) exit parallel to the inputs. In the address PLA, however, the second plane is rotated 90°, so the outputs are perpendicular to the inputs. This approach requires additional wiring (horizontal purple lines), but presumably, this layout worked better in the 8087 since the outputs are lined up with the rest of the microcode engine.

    Conceptual diagram of a regular PLA on the left and a rotated PLA on the right.

    Conceptual diagram of a regular PLA on the left and a rotated PLA on the right.

     

  7. To describe the implementation of a PLA in more detail, the transistors in each row of the AND plane form a NOR gate, since if any transistor is turned on, it pulls the output low. Likewise, the transistors in each column of the OR plane form a NOR gate. So why is the PLA described as having an AND plane and an OR plane, rather than two NOR planes? By using De Morgan's law, you can treat the NOR-NOR Boolean equations as equivalent to AND-OR Boolean equations (with the inputs and outputs inverted). It's usually much easier to understand the logic as AND terms OR'd together.

    The converse question is why don't they build the PLA from AND and OR gates instead of NOR gates? The reason is that AND and OR gates are harder to build with NMOS transistors, since you need to add explicit inverter circuits. Moreover, NMOS NOR gates are typically faster than NAND gates because the transistors are in parallel. (CMOS is the opposite; NAND gates are faster because the weaker PMOS transistors are in parallel.) 

  8. The 8087's opcodes can be organized into tables, showing the underlying structure. (In each table, the row (Y) coordinate is the bottom 3 bits of the first byte and the column (X) coordinate is the 3 bits after the MOD bits in the second byte.)

    Memory operations use the following encoding with MOD = 0, 1, or 2. Each box represents 8 different addressing modes.

      0 1 2 3 4 5 6 7
    0 FADD FMUL FCOM FCOMP FSUB FSUBR FDIV FDIVR
    1 FLD   FST FSTP FLDENV FLDCW FSTENV FSTCW
    2 FIADD FIMUL FICOM FICOMP FISUB FISUBR FIDIV FIDIVR
    3 FILD   FIST FISTP   FLD   FSTP
    4 FADD FMUL FCOM FCOMP FSUB FSUBR FDIV FDIVR
    5 FLD   FST FSTP FRSTOR   FSAVE FSTSW
    6 FIADD FIMUL FICOM FICOMP FISUB FISUBR FIDIV FIDIVR
    7 FILD   FIST FISTP FBLD FILD FBSTP FISTP

    The important point is that the instruction encoding has a lot of regularity, making the decoding process easier. For instance, the basic arithmetic operations (FADD through FDIVR) are repeated on alternating rows. However, the table also has significant irregularities, which complicate the decoding process.

    The register operations (MOD = 3) have a related layout, but there are even more irregularities.

      0 1 2 3 4 5 6 7
    0 FADD FMUL FCOM FCOMP FSUB FSUBR FDIV FDIVR
    1 FLD FXCH FNOP   misc1 misc2 misc3 misc4
    2                
    3         misc5      
    4 FADD FMUL     FSUB FSUBR FDIV FDIVR
    5 FFREE   FST FSTP        
    6 FADDP FMULP   FCOMPP FSUBP FSUBRP FDIVP FDIVRP
    7                

    In most cases, each box indicates 8 different values for the stack register, but there are exceptions. The NOP and FCOMPP instructions each have a single opcode, "wasting" the rest of the box.

    Five of the boxes in the table encode multiple instructions instead of the register number. The first four (red) are miscellaneous instructions handled by the decoding PLA:
    misc1 = FCHS, FABS, FTST, FXAM
    misc2 = FLD1, FLDL2T, FLDL2E, FLDPI, FLDLG2, FLDLN2, FLDZ (the constant-loading instructions)
    misc3 = F2XM1, FYL2X, FPTAN, FPATAN, FXTRACT, FDECSTP, FINCSTP
    misc4 = FPREM, FYL2XP1, FSQRT, FRNDINT, FSCALE

    The last miscellaneous box (yellow) holds instructions that are handled by the BIU.
    misc5 = FENI, FDISI, FCLEX, FINIT

    Curiously, the 8087's opcodes (like the 8086's) make much more sense in octal than in hexadecimal. In octal, an 8087 opcode is simply 33Y MXR, where X and Y are the table coordinates above, M is the MOD value (0, 1, 2, or 3), and R is the R/M field or the stack register number. 

  9. The 22 outputs from the instruction decoder PLA correspond to the following groups of instructions, activating one row of ROM and producing the corresponding microcode address. From this table, you can see which instructions are grouped together in the microcode.

     0 #0200 FXCH
     1 #0597 FSTP (BCD)
     2 #0808 FCOM FCOMP FCOMPP
     3 #1008 FLDLG2 FLDLN2
     4 #1527 FSQRT
     5 #1586 FPREM
     6 #1138 FPATAN
     7 #1039 FPTAN
     8 #0900 F2XM1
     9 #1020 FLDZ
    10 #0710 FRNDINT
    11 #1463 FDECSTP FINCSTP
    12 #0812 FTST
    13 #0892 FABS FCHS
    14 #0065 FFREE FLD
    15 #0217 FNOP FST FSTP (not BCD)
    16 #0001 FADD FDIV FDIVR FMUL FSUB FSUBR
    17 #0748 FSCALE
    18 #1028 FXTRACT
    19 #1257 FYL2X FYL2XP1
    20 #1003 FLD1 FLDL2E FLDL2T FLDPI
    21 #1468 FXAM
    
     
  10. The instruction decoding PLA has 22 entries, and the jump table also has 22 entries. It's a coincidence that these values are the same.

    An entry in the jump table ROM is selected by five bits of the micro-instruction. The ROM is structured with two 11-bit words per row, interleaved. (It's also a coincidence that there are 22 bits.) The upper four bits of the jump number select a row in the ROM, while the bottom bit selects one of the two rows.

    This implementation is modified for target 0, the three-way jump. The first ROM row is selected for target 0 if the current instruction is multiplication, or for target 1. The second row is selected for target 0 if the current instruction is addition or subtraction, or for target 2. The third row is selected for target 0 if the current instruction is division, or for target 3. Thus, target 0 ends up selecting rows 1, 2, or 3. However, remember that there are two words per row, selected by the low bit of the target number. The problem is that target 0 with multiplication will access the left word of row 1, while target 1 will access the right word of row 1, but both should provide the same address. The solution is that rows 1, 2, and 3 have the same address stored twice in the row, so these rows each "waste" a value.

    For reference, the contents of the jump table are:

     0: Jumps to target 1 for FMUL, 2 for FADD/FSUB/FSUBR, 3 for FDIV/FDIVR
     1: #0359
     2: #0232
     3: #0410
     4: #0083
     5: #1484
     6: #0122
     7: #0173
     8: #0439
     9: #0655
    10: #0534
    11: #0299
    12: #1572
    13: #1446
    14: #0859
    15: #0396
    16: #0318
    17: #0380
    18: #0779
    19: #0868
    20: #0522
    21: #0801
    
     
  11. Eleven instructions are implemented in the BIU hardware. Four of these are relatively simple, setting or clearing bits: FINIT (initialize), FENI (enable interrupts), FDISI (disable interrupts), and FCLEX (clear exceptions). Six of these are more complicated, storing state to memory or loading state from memory: FLDCW (load control word), FSTCW (store control word), FSTSW (store status word), FSTENV (store environment), FLDENV (load environment), FSAVE (save state), and FRSTOR (restore state). As explained elsewhere, the last two instructions are partially implemented in microcode. 

  12. Even a seemingly trivial instruction uses more circuitry than you might expect. For instance, after the FCLEX (clear exception) instruction is decoded, the signal goes through nine gates before it clears the exception bits in the status register. Along the way, it goes through a flip-flop to synchronize the timing, a gate to combine it with the reset signal, and various inverters and drivers. Even though these instructions seem like they should complete immediately, they typically take 5 clock cycles due to overhead in the 8087. 

  13. I'll give more details here on the circuit that jumps to the save or restore microcode. The BIU sends two signals to the microcode engine, one to jump to the save code and one to jump to the restore code. These signals are buffered and delayed by a capacitor, probably to adjust the timing of the signal.

    In the microcode engine, there are two hardcoded constants for the routines, just above the jump table; the BIU signal causes the appropriate constant to go onto the micro-address lines. Each bit in the address has a pull-up transistor to +5V or a pull-down transistor to ground. This approach is somewhat inefficient since it requires two transistor sites per bit. In comparison, the jump address ROM and the instruction address ROM use one transistor site per bit. (As in a PLA, each transistor is present or absent as needed, so the number of physical transistors is less than the number of transistor sites.)

    Two capacitors in the 8087. This photo shows the metal layer with the silicon and polysilicon underneath.

    Two capacitors in the 8087. This photo shows the metal layer with the silicon and polysilicon underneath.

    Since capacitors are somewhat unusual in NMOS circuits, I'll show them in the photo above. If a polysilicon line crosses over doped silicon, it creates a transistor. However, if a polysilicon region sits on top of the doped silicon without crossing it, it forms a capacitor instead. (The capacitance exists for a transistor, too, but the gate capacitance is generally unwanted.) 

  14. The documentation provides a hint that the microcode to load constants is complicated. Specifically, the documentation shows that different constants take different amounts of time to load. For instance, log2(e) takes 18 cycles while log2(10) takes 19 cycles and log10(2) takes 21 cycles. You'd expect that pre-computed constants would all take the same time, so the varying times show that more is happening behind the scenes. 

Make your own container base images from trusted sources

# Introduction

I really like containers, but they are something that is currently very bad from a security point of view: distribution

We download container images from container registries, whether it is docker.io, quay.io or ghcr.io, but the upstream project do not sign them, so we can not verify a CI pipeline or the container registry did not mess with the image.  There are actually a few upstream actors signing their images: Fedora, Red Hat and universial-blue based distros (Bluefin, Aurora, Bazzite), so if you acquire their public key using for signing from a different channel, you can verify if you got the image originally built.  Please do not hesitate to get in touch with me if you know about other major upstream that sign their container images.

Nevertheless, we can still create containers ourself from trustable artifacts signed by upstream.  Let's take a look at how to proceed with Alpine Linux.

# Get the rootfs

The first step is to download a few files:

* Alpine's linux GPG key
* Alpine's "mini root filesystem" build for the architecture you want
* The GPG file (extension .asc) for the mini root filesystem you downloaded

=> https://www.alpinelinux.org/downloads/ Alpine linux download page on the official website

The GPG file is at the top of the list, it is better to get it from a different channel to make sure that if the website was hacked, the key was not changed accordingly with all the signed files, in which case you would just trust the key of an attacker and it would validate the artifacts.  A simple method is to check the page from webarchive a few days / week before and verify that the GPG file is the same on webarchive and the official website.

The GPG key fingerprint I used is 0482 D840 22F5 2DF1 C4E7 CD43 293A CD09 07D9 495A as of the date of publication.

# Verify the artifacts

You will need to have gpg installed and a initialized keyring (I do not cover this here).  Run the following command:

```
gpg --import ncopa.asc
gpg --verify alpine-minirootfs-3.23.3-x86_64.tar.gz.asc alpine-minirootfs-3.23.3-x86_64.tar.gz
```

It should answer something like this:

```
gpg: Signature made Wed Jan 28 00:25:36 2026 CET
gpg:                using RSA key 0482D84022F52DF1C4E7CD43293ACD0907D9495A
gpg: Good signature from "Natanael Copa " [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 0482 D840 22F5 2DF1 C4E7  CD43 293A CD09 07D9 495A
```

The line "Good signature...." tells you that the file integrity check matches the GPG key you imported.  The rest of the message tells you that the key is not trustable.  This is actually a GPG thing, you would need to edit your keyring and mark the key as "trustable" or have in your keyring a trusted key that signed this key (this is the web of trust GPG wanted to create), but this will only remove the warning.  Of course, you can mark that key trustable if you plan to use it for a long time and you are absolutely sure it is the genuine one.

You do not need to verify the checksum using sha256, the GPG check did the same in addition to authenticate the person who produced the checksum.

Now you validated the minirootfs authenticity, you can create an Alpine container!

# Container creation

You can use podman or docker for this:

```
podman import alpine-minirootfs-3.23.3-x86_64.tar.gz alpine:3.23.3-local
```

You are now ready to build more containers based on your own cryptographically verified Alpine container image.

# Example of use

It is rather easy to build new containers with useful purpose on top of your new base container.

Create a Containerfile (or Dockerfile, your mileage may vary):

```
FROM alpine:3.23.3-local
RUN apk add nginx
CMD ["nginx", "-c", "/app/nginx.conf", "-g", "daemon off;"]
```

Build a container with this command:

```
podman build . -t alpine_local_nginx
```

# Conclusion

Without any kind of cryptographic signature mechanism available between upstream and the end user, it is not possible to ensure a container from a third party registry was not tampered with.

It is best for security to rebuild the container image, and then rebuild all the containers you need using your base image, rather than blindly trusting registries.

One tool to sign container images is cosign.

=> https://github.com/sigstore/cosign Cosign project GitHub page

# Going further

This process works for other Linux distributions of course.  For instance, for Ubuntu you can download Ubuntu base image, the SHA256SUMS.gpg and SHA256SUMS files, and make sure to get a genuine GPG key to verify the signature.

=> https://cdimage.ubuntu.com/ubuntu-base/releases/24.04/release/ Ubuntu official website: ubuntu-base 24.04 releases

File transfer made easier with Tailscale

# Introduction

Since I started using Tailscale (using my own headscale server), I've been enjoying it a lot.  The file transfer feature is particularly useful with other devices.

This blog post explains my small setup to enhance the user experience.

# Quick introduction

Tailscale is a network service that allows to enroll devices into a mesh VPN based on WireGuard, this mean every peer connects to every peers, this is not really manageable without some lot of work.  It also allows automatic DNS assignment, access control, SSH service and lot of features.

Tailscale refers to both the service and the client.  The service is closed source, but not the client.  There is a reimplementation of the server called Headscale that you can use with the tailscale client.

=> https://tailscale.com/ Tailscale official website
=> https://headscale.net/ Headscale official website

# Automatically receive files

When you want to receive a file from Tailscale on your desktop system, you need to manually run `tailscale file get --wait $DEST`, this is rather not practical and annoying to me.

I wrote a systemd service that starts the tailscale command at boot, really it is nothing fancy but it is not something available out of the box.

In the directory `~/.config/systemd/user/` edit the file `tailscale-receiver.service` with this content:

```
[Unit]
Description=tailscale receive file
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/tailscale file get --wait --loop /%h/Documents/
Restart=always
RestartSec=5

[Install]
WantedBy=default.target
```

The path `/%h/Documents/` will expand to `/$HOME/Documents/` (the first / may be too much, but I keep it just in case), you can modify it to your needs.

Enable and start the service with the command:

```
systemctl --user daemon-reload
systemctl --enable --now tailscale-receiver.service
```

# Send files from Nautilus

When sending files, it is possible to use `tailscale file cp $file $target:` but it is much more convenient to have it directly from the GUI, especially when you do not know all the remotes names.  This also makes it easier for family member who may not want to fire up a terminal to send a file.

Someone wrote a short python script to add this "Send to" feature to Nautilus

=> https://github.com/flightmansam/nautilus-sendto-tailscale-python Script flightmansam/nautilus-sendto-tailscale-python

Create the directory `~/.local/share/nautilus-python/extensions/` and save the file `nautilus-send-to-tailscale.py` in it.

Make sure you have the package "nautilus-python" installed, on Fedora it is `nautilus-python` while on Ubuntu it is `python3-nautilus`, so your mileage may vary.

Make sure to restart nautilus, a `killall nautilus` should work but otherwise just logout the user and log back.  In Nautilus, in the contextual menu (right click), you should see "Send to Tailscale" and a sub menu should show the hosts.

# Conclusion

Tailscale is a fantastic technology, having a mesh VPN network allows to secure access to internal services without exposing anything to the Internet.  And because it features direct access between peers, it also enables some interesting uses like fast file transfer or VOIP calls without a relay.

Comparison of cloud storage encryption software

# Introduction

When using a not end-to-end encrypted cloud storage, you may want to store your file encrypted so if the cloud provider (that could be you if you self host a nextcloud or seafile) get hacked, your data will be available to the hacker, this is not great.

While there are some encryption software like age or gpg, they are not usable for working transparently with files.  A specific class of encryption software exists, they create a logical volume with your files and they are transparently encrypted in the file system.

You will learn about cryptomator, gocryptfs, cryfs and rclone.  They allow you to have a local directory that is synced with the cloud provider, containing only encrypted files, and a mount point where you access your files.  Your files are sent encrypted to the cloud provider, but you can use it as usual (with some overhead).

This blog post is a bit "yet another comparison" because all these software also provide a comparison list of challengers.

=> https://nuetzlich.net/gocryptfs/comparison/ A comparison done by gocryptfs
=> https://cryptomator.org/comparisons/ A comparison done by cryptomator
=> https://www.cryfs.org/comparison A comparison done by cryfs

# Benchmark

My comparison will compare the following attributes and features of each software:

* number of files in the encrypted dir always using the same input (837 MB from 4797 files mades of pictures and a git repository)
* filename and file tree hierarchy obfuscation within the encrypted dir
* size of the encrypted dir compared to the 837 MB of the raw material
* cryptography used

# Software list

Here is the challenger list I decided to evaluate:

## Cryptomator

The main software (running on Linux) is open source, they have a client for all major operating system around, including Android and iOS.  The android apps is not free (as in beer), the iOS app is free for read-only, the windows / linux / Mac OS program is free.  They have an offer for a company-wide system which can be convenient for some users.

Cryptomator features a graphical interface, making it easy to use.

Encryption suites are good, it uses AES-256-GCM and scrypt, featuring authentication of the encrypted data (which is important as it allows to detect if a file was altered).  A salt is used.

Hierarchy obfuscation can be sufficient depending on your threat model.  The whole structure information is flattened, you can guess the number of directories and their number of files files, and the file sizes, all the names are obfuscated.  This is not a huge security flaw, but this is something to consider.

=> https://docs.cryptomator.org/security/architecture/ Cryptomator implementation details

## gocryptfs

This software is written in Go and works on Linux, a C++ Windows version exists, and there is a beta version of Mac OS.

=> https://nuetzlich.net/gocryptfs/ gocryptfs official website

Hierarchy obfuscation is not great, the whole structure information is saved although the names are obfuscated.

Cryptography wise, scrypt is used for the key derivation and AES-256-GCM for encryption with authentication.

=> https://nuetzlich.net/gocryptfs/forward_mode_crypto/ gocryptfs implementation details

## CryFS

I first learned about cryfs when using KDE Plasma, there was a graphical widget named "vault" that can drive cryfs to create encrypted directories.  This GUI also allow to use gocryptfs but defaults to cryfs.

=> https://www.cryfs.org/ CryFS official website

CryFS is written in C++ but an official rewrite in Rust is ongoing.  It works fine on Linux but there are binaries for Mac OS and Windows as well.

Encryption suites are good, it uses AES-256-GCM and scrypt, but you can use xchacha20-poly1305 if you do not want AES-GCM.

It encrypts files metadata and split all files into small blocks of fixed size, it is the only software in the list that will obfuscate all kind of data (filename, directory name, tree hierarchy, sizes, timestamp) and also protect against an old file replay.

=> https://www.cryfs.org/howitworks CryFS implementation details

## rclone

It can be surprising to see rclone here, it is a file transfer software supporting many cloud provider, but it also features a few "fake" provider that can be combined with any other provider.  Thoses fakes remotes can be used to encrypt files, but also aggregate multiple remotes or split files in chunks.  We will focus on the "crypt" remote.

=> https://rclone.org/ Rclone official website

rclone is a Go software, it is available everywhere on desktop systems but not on mobile devices.

Encryption is done through libNaCl and uses XSalsa20 and Poly1305 which both support authentication, and also use scrypt for key derivation.  A salt can be used but it is optional, make sure to enable it.

Hierarchy obfuscation is not great, the whole structure information is saved although the names are obfuscated.

=> https://rclone.org/crypt/ rclone crypt remote implementation details

## Other

ecryptfs is almost abandonware, so I did not cover it.

=> https://lore.kernel.org/ecryptfs/ef98d985-6153-416d-9d5e-9a8a8595461a@app.fastmail.com/ ecryptfs is unmaintained and untested

encfs is limited and recommend users to switch to gocryptfs

=> https://github.com/vgough/encfs?tab=readme-ov-file#about encFS GitHub page: anchor "about"

LUKS and Veracrypt are not "cloud friendly" because although you can have a local big file encrypted with it and mount the volume locally, it will be synced as a huge blob on the remote service.

# Results

From sources directories with 4312 files, 480 directories for a total of 847 MB.

* cryptomator ended up with 5280 files, 1345 directories for a total of 855 MB
* gocryptfs ended up with 4794 files, 481 directories for a total of 855 MB
* cryfs ended up with 57928 files, 4097 directories for a total of 922 MB
* rclone ended up with 4311 files, 481 directories for a total of 847 MB

Although cryptomater has a bit more files and directories in its encrypted output compared to the original files, the obfuscation is really just all directories being in a single directory with filenames obfuscated.  Some extra directories and files are created for cryptomator internal works, which explains the small overhead.

I used default settings for cryfs with a blocksize of 16 kB which is quite low and will be a huge overhead for a synchronization software like Nextcloud desktop.  Increasing the blocksize is a setting worth considering depending on your file sizes distribution.  All files are spread in a binary tree, allowing it to scale to a huge number of files without filesystem performance issue.

# Conclusion

In my opinion, the best choice from a security point of view would be cryfs.  It features full data obfuscation, good encryption, mechanisms that prevent replaying old files or swapping files.  The documentation is clear and we can see the design choices are explained with ease and clearly.

But to be honest, I would recommend cryptomator to someone who want a nice graphical interface, easy to use software and whose threat model allows some metadata reveal.   It is also available everywhere (although not always for free), which is something to consider.

Authentication is used by all these software, so you will know if a file was tampered with, although it does not protect against swapping files or replaying an old file, this is certainly not in everyone's threat model.  Most people will just want to prevent a data leak to read their data, but the case of a cloud storage provider modifying your encrypted files is less likely.

# Going further


There is a GUI frontend for gocryptfs and cryfs called SiriKali.

=> https://mhogomchungu.github.io/sirikali/ SiriKali official project page
=> https://github.com/mhogomchungu/sirikali SiriKali GitHub project

Some self hostable cloud storage provider exists with end-to-end encryption (file are encrypted/decrypted locally and only stored as blob remotely):

The two major products I would recommend are Peergos and Seafile.  I am a peergos user, it works well and features a Web UI where as seafile encryption is not great as using the web ui requires sharing the password, metadata protection is bad too.

=> https://peergos.org/ Peergos official website
=> https://www.seafile.com/en/home/ Seafile official website

Revert fish shell deleting shortcuts behavior

# Introduction

In a recent change within fish shell, the shortcut to delete last words were replaced by "delete last big chunk" (I don't know exactly how it is called in this case) which is usually the default behavior on Mac OS "command" key vs "alt" key and I guess it is why it was changed like this on fish.

Unfortunately, this broke everyone's habit and a standard keyboard do not even offer the new keybinding that received the old behavior.

There is an open issue asking to revert this change.

=> https://github.com/fish-shell/fish-shell/issues/12122 GitHub fish project: Revert alt-backspace behaviour on non-macOS systems #12122

I am using this snippet in `~/.config/fish/config.fish` to restore the previous behavior (the same as in other all other shell, where M-d deletes last word).  I build it from the GitHub issue comments, I had to add `$argv` for some reasons.

```
if status is-interactive
 # Commands to run in interactive sessions can go here

 # restore delete behavior
 bind $argv alt-backspace backward-kill-word
 bind $argv alt-delete kill-word
 bind $argv ctrl-alt-h backward-kill-word
 bind $argv ctrl-backspace backward-kill-token
 bind $argv ctrl-delete kill-token
end
```

Declaratively manage containers on Linux

# Introduction

When you have to deal with containers on Linux, there are often two things making you wonder how to deal with effectively: how to keep your containers up to date, and how to easily maintain the configuration of everything running.

It turns out podman is offering systemd unit templates to declaratively manage containers, this comes with the fact that podman can run in user mode.  This combination gives the opportunity to create files, maintain them in git or deploy them with a configuration management tool like ansible, and keep things separated per user.

It is also very convenient when you want to run a program shipped as a container on your desktop.

For some reason, this is called "quadlets".

=> https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html podman-systemd.unit man page

In this guide, I will create a kanboard service (a PHP software to run a kanban) under the kanban user.

# Setup (simple service)

You need to create files that will declare containers and/or networks, this can be done in various places depending on how you want to manage the files, the man page gives all the details, but basically you want to stick with the two following options:

* system-wide configuration: `/etc/containers/systemd/users/$(UID)`
* user configuration: `~/.config/containers/systemd/`

Both will run rootless containers under the user UID, but one keep the files in `/etc/` which may be more suitable for central management.

As systemd is used to run the containers, if you want to run a container for a user that is not one where you are logged, you need to always enable it so its related systemd processes / services are running, including the containers, this is done by enabling "linger".

```
useradd -m kanban
loginctl enable-linger kanban
```

This will immediately create a session for that user and pop all related services.

Now, create a file `/etc/containers/systemd/users/1001/` (1001 being the uid of kanban user) with this content:

```
[Container]
Image=docker.io/kanboard/kanboard:latest
Network=podman
PublishPort=10080:80
Volume=kanboard_data:/var/www/app/data
Volume=kanboard_plugins:/var/www/app/plugins
Volume=kanboard_ssl:/etc/nginx/ssl

[Service]
Restart=always

[Install]
WantedBy=default.target
```

This can exactly map to a very long podman command line that would use the image `docker.io/kanboard/kanboard:latest` in network `podman` and declaring three different container volumes and associated mount points.  This generator even allows you to add command line arguments in case an option is not available with systemd format.

Because the user already runs, the container will not start yet except if you use `disable-linger` and then `enable-linger` the kanban user, and that would not be ideal to be honest.  There is a better way to proceed: `systemctl --user --machine kanban@ daemon-reload` which basically runs `systemctl --user daemon-reload` by the user `kanban` except we do it as root user which is more convenient for automation.

Running the container this way will trigger exactly the same processes as if you started it manually with `podman run -v kanboard_data:/var/www/app/data/ [...] docker.io/kanboard/kanboard:latest`.

Note that you can skip the `[Install]` section if you do not want to automatically start the container, and prefer to manually start/stop it with "systemctl", this is actually useful if you have the container under your regular user and do not always need it.

# Setup (advanced service)

If you want to run a more complicated service that need a couple of containers to talk together like a web server, a backend runner and a database, you only need to configure them in the same network.

If you need them to start the containers of a group in a specific order, you can add use systemd dependency declaration in `[Install]` section.

Podman will run a local DNS resolver that translates the container name into a working hostname, this mean if you have a postgresql container called "db", then you can refer to the postgresql host as "db" from another container within the same network.  This works the same way as docker-compose.

# Ops

## Getting into a user shell

To have a working environment for `journalctl` or `systemctl` commands to work requires to use `machinectl shell kanban@`, otherwise the dbus environment variables will not be initialized.  Note that it works too when connecting with ssh, but it is not always ideal if you use it locally.

From this shell, you can run commands like `systemctl --user status kanboard.container` for our example or `journalctl --user -f -u kanboard.container`, or run a shell in a container, inspect a volume etc...

Using `sudo -u user` or `su - user` will not work.

## Disabling a user

If you want to disable the services associated with an user, use this command:

```
loginctl disable-linger username
```

This will immediately close all its sessions and stop services running under that user.

## Automatic updates

This is the very first reason I went into using quadlets for local services using containers, I did not want to have to manually run some `podman pull` commands over a list then restart related containers that were running.

Podman gives you a systemd services doing all of this for you, this works for containers with the parameter `AutoUpdate=registry` within the section `[Container]`.

Enable the timer of this service with: `systemctl --user enable --now podman-auto-update.timer` then you can follow the timer information with `systemctl --user status podman-auto-update.timer` or logs from the update service with `journalctl --user -u podman-auto-update.service`.

Make sure to pin your container image to a branch like "stable" or "lts" or "latest" if you want a development version, the update mechanism will obviously do nothing if you pin the image to a specific version or checksum.

# Conclusion

Quadlets made me switch to podman as it allowed me to deploy and maintain containers with ansible super easily, and also enabled me to separate each services into different users.

Prior to this, handling containers on a simple server or desktop was an annoying task to figure what should be running, how to start them and retrieving command lines from the shell history or use a docker/podman compose file.  This also comes with all the power from systemd like querying a service status or querying logs with journalctl.

# Going further

There is a program named "podlet" that allow you to convert some file format into quadlets files, most notably it is useful when getting a `docker-compose.yml` file and transforming it into quadlet files.

=> https://github.com/containers/podlet/ podlet GitHub page

Filtered for home security

1.

The Amazon Ring Always Home Cam is an indoor security drone for your home.

Introduced with this video in 2020: "Yeah, it’s a camera that flies."

Sadly not yet on the market.

Ok Judge Dredd had Spy-in-the-Sky drone surveillance cameras in 1978 and Mega-City One is not an aspirational template for domestic life but hear me out:

Because I would love to be able to text my house “oh did I leave the stove on?” from the bus. And “darn can you find my keys?” in the morning. And “uh there’s that book about 1970s social computing somewhere it has an orange spine I can’t remember exactly” at literally anytime.

And do that without having to blanket my home in cameras. A drone seems like a good solution?

2.

Surveillance: systematic observation. Often institutional. From “above.”

Sousveillance, coined by cyborg Steve Mann in 2002: "watchful vigilance from underneath."

I am suggesting that the cameras be mounted on people in low places, rather than upon buildings and establishments in high places.

e.g.

a taxicab passenger photographs the driver, or taxicab passengers keep tabs on driver’s behaviour

It is such a positively-framed paper.

We swim in this world now. What does it do to us?

(I wonder if here’s a word like auto-sousveillance? We do it to ourselves.)

3.

The Nor (2014) by artist James Bridle.

The sense of being watched is a classic symptom of paranoia, often a sign of deeper psychosis, or dismissed as illusory. In the mirror city, which exists at the juncture of the street and CCTV, of bodily space and the electromagnetic spectrum, one is always being watched. So who’s paranoid now?

(As previously discussed, briefly.)

Exactly midway between Mann coining sousveillance in 2002 and today, 2026, Bridle put his finger on this paranoia background radiation, slowing increasing like population levels, like CO2 ppm, like sea level, like the frog’s bath.

4.

Robot Exclusion Protocol (2002) by blogger Paul Ford: "A story about the Google of the future."

I took off my clothes and stepped into the shower to find another one sitting near the drain. It was about 2 feet tall and made of metal, with bright camera-lens eyes and a few dozen gripping arms. Worse than the Jehovah’s Witnesses.

“Hi! I’m from Google. I’m a Googlebot! I will not kill you.”

“I know what you are.”

“I’m indexing your apartment.”

I feel like we are 24 months off this point?

Only they’ll be indexer googlebot drones that we vibe code for ourselves.

5.

Back in 2024, engineer Simon Willison realised that the killer app of Gemini Pro 1.5 is video, and:

I took this seven second video of one of my bookshelves:

It understood the video and gave him back a machine-readable list of the titles and authors. That’s handy!

I am still waiting for this as an app so that I can index and search my overflowing bookshelves by not-even-that-carefully waving my phone at them.

Please I am too lazy to type the prompt to vibe this.

The meta-point is that auto-sousveillance is inevitable because I can’t find the book I’m looking for.

6.

Man accidentally vibe codes a robovac army (2026).

The DJI Romo is a $2000 behemoth that mops and vacuums using LIDAR and AI.

Sammy Azdoufal wanted to control his roomba with his Playstation controller.

However, the scanner his [Claude Code agent] created not only gave him access to his device; it gave him access and control over almost 7000. He was able to see home layouts and IP addresses, and control the devices’ cameras and microphones.

Uh oh.

Whereas the point of institutional surveillance is that the CCTV cameras are conspicuous (and, originally, you didn’t know if anyone was watching, but now the AI processes all),

the characteristic of auto-sousveillance seems to be that you don’t know whether you are privately querying for a lost book or live streaming your bathroom to the internet.

Forget about control, how do you even relate to such a capricious system?

7.

The ancient Romans had two types of gods.

There are the gods on Olympus who look after nature, cities, the state.

And then there are Lares (Wikipedia), guardian deities of a place, "believed to observe, protect, and influence all that happened within the boundaries of their location or function."

In particular, household gods, Lares Familiares, that reside not on a distant mountain but instead in a household shrine:

The Lar Familiaris cared for the welfare and prosperity of a Roman household. A household’s lararium, a shrine to the Lar Familiaris and other domestic divinities, usually stood near the dining hearth or, in a larger dwelling, the semi-public atrium or reception area of the dwelling. A lararium could be a wall-cupboard with doors, an open niche with small-scale statuary, a projecting tile, a small freestanding shrine, or simply the painted image of a shrine …

The Lar’s statue could be moved from the lararium to wherever its presence was needed. It could be placed on a dining table during feasts or be a witness at weddings and other important family events.

RELATED:

Lares: our 2 minute pitch for an AI-powered slightly-smart home (2023) – you can see a demo video.

And here’s a paper about Lares showing emergent behaviour from AI agents, which in 2024 was novel and surprising.


More posts tagged: filtered-for (122).

New Wave Hardware

We briefly mentioned New Wave Hardware in last week’s Inanimate Lab Notes so this is me doing some unpacking. While you’re there, join 300+ other subscribers and sign up for our newsletter. You’ll get weekly links and updates on what we’re working on.


There are a bunch of things changing with new hardware products, design and technology.

Let’s say: the intersection of hardware and AI. But our hunch is that it’s broader than that.

There are new ways to get hardware into the hands of consumers, and new AI interactions that are now possible, and more, and these changes are happening independently but simultaneously. We’re tracking this as what we’re calling New Wave Hardware.

So we got a few founders together at Betaworks in NYC earlier this week for a roundtable to compare notes (thank you Betaworks!).

The meta question was: does our hunch hold? And, if so, what characterises New Wave Hardware and what specifically is changing – so that we can push at it?

I kept notes.

I’ll go off those and add my own thoughts.

(I’m using some direct quotes but I won’t attributing or list attendees. I would love for others to share their own perspectives!)


AI interfaces

Voice is good now! (As I said.) So we’re seeing that a lot.

More than that:

  • You can express an intent and the computer will do what you mean
  • Natural interfaces are workable now, beyond voice. e.g. the new Starboy gadget by lilguy: "We trained multiple tiny image models that run locally on the device, letting it recognize human faces and hand gestures" (launch thread on X).

What do we do with consumer gadgets that perceive pointing and glances? What is unlocked when we shift away from buttons and apps to interact with hardware devices, and the new interface is direct and human and in the real world?

New interaction modalities

Beyond the user interface, the way we interact with hardware is changing. I kept a running list of the interaction modality changes that were mentioned:

  • Human interfacessee above.
  • Situated – due to always-on sensors, AI devices know what’s going on around them and can respond when they see fit, not only on a user trigger. Yes, screens that dim when it gets dark, but in a wider sense this goes back to Clay Shirky’s essay Situated Software (2004), "software designed in and for a particular social situation or context." We’re seeing more of this.
  • Autonomous – agents are software that has its own heartbeat, now we see that "the hardware becomes aware"… and then what? Maybe the user doesn’t need to be intentional about activating some function or another; the device can get ahead of intentions, and offer a radically different kind of value to the user. A new design possibility.
  • Networked – we’re frequently working with connected devices which today have attained a new level of reliability. What happens when the stuff around us channels planetary intelligence?
  • Embodied – the cleverness of the Plaud AI note taker device is that it’s a social actor: you can notice it, place it on the table, cover it; it inflects what people say and how they feel (for better or worse). Hardware is in the real world and you can move it from focal to peripheral attention just by moving your head.

Some of these are new colours in the palette to design with; some are intrinsic to hardware and have been there all along. Though amplified! The rise of wearables (described by one founder as "sitting between the utility and affinity group") means that hardware is more frequently in our faces.

There are challenges. When we have devices and "the ability to put software that can do anything at any time in them," the lack of affordances and constraints can be baffling. So how do we not do that?

And how do we understand what things do anyway, really, when behaviour steered by AI is so non-deterministic? Perhaps we have to lean into the mystical. That’s another trend.

Getting hardware in the hands of users

Every few years there’s a claim that it’s now quicker than 18 months to get a hardware product from concept through manufacture: that’s still not the case but there are alternatives and short cuts – some of which are potentially rapidly quicker.

Like: reference designs. There is now so much hardware coming out of Shenzhen, there are high level references designs for everything to customise, and factories are keen to partner. One team at the roundtable brought up their core electronics in the US, then got pretty sophisticated products built (batch size of 100) complete with beautiful metal enclosures after spending just 3 weeks in China.

Also like: 3D printers. Short run fabrication is possible domestically in a way it wasn’t before. Let me highlight Cipherling which combines production-grade microcontrollers with a charming 3D printed enclosure to get to market quicker.

It does seem like the sophistication of the Western and Shenzhen hardware ecosystems has made these approaches - which are not new - newly accessible.

Form factors

New Wave Hardware skews consumer, perhaps?

Or at least there’s a renewed interest in consumer hardware from startups and investors.

This is partly because there’s a big unknown and therefore a big opportunity: AI is hungry for context, it’s useful in the real world outside our phones, and the new AI interaction modalities means there’s a lot to figure out about how to make that good – it’s not obvious what to do. Like do we have lanyards or pucks on tables or what? We need to experiment, which demands quick cycle time, which is a driver on finding alternatives to the 18 month product development cycle.

Also the previous generation of hardware was oh-so-asinine. One remark I wrote down from the roundtable, regarding the consumer hardware that currently surrounds us: "This is hardware that would want to be invisible if it could."

So there’s a desire to try new forms; products that don’t secretly want to hide themselves.

Just a note too that “new form factors” doesn’t just mean standalone devices: we continue to be inspired by the desk-scale or even room-scale work at Folk Computer.

New tools, of course

If you’re an artist wanting to put a few dozen instances of weird new consumer electronics in people’s hands, and your single blocker was writing firmware, then guess what: in the year of our Claude 2026 that is no longer a blocker.

AI tools provide what I’ve previously called Universal Basic Agency and it is wonderful. When individuals are unblocked, we get an abundance of creativity in the world.

(We were at a 6 minute demos event in the basement of an independent bookstore in Brooklyn on Friday - see this week’s Lab Notes - and one speaker was showing their vintage arcade display adaptor project. So cool. They make super complicated PCBs but don’t enjoy 3D modelling, so did the CAD in programmable modelling software with a few lines of code. Not AI, but advanced tools.)

And do we see a glimmer of end-user programming too?


I’m grateful for the thoughtful and open conversation of everyone at the roundtable.


As I write this, a set of colourful Oda speakers, hanging from the ceiling here at Betaworks, relay a live audio stream from a macaw sanctuary forest in central Costa Rica. We can hear the birds and the weather – it is transporting.

If there is such a trend as New Wave Hardware (and, after our small conversation, I do believe there is) then it is not confined to mass market novel AI interfaces, it is also these profound artistic interventions, and we all learn from one another.

Are you seeing something happen here too? Are hardware startups characterised by something different today versus, say, 5 years ago? Lmk if you end up sharing your perspective on your blog/newsletter – would love to read.

At Inanimate we are building products within New Wave Hardware, and working to do our bit to enable it.

We hope to convene another roundtable in the near future, either here in NYC or back home in London, to continue swapping notes and pointers and feeling this out together.


More posts tagged: inanimate (3).

Auto-detected kinda similar posts:

The violence of the Librareome Project

Vernor Vinge’s sci-fi novel Rainbows End (2006) is so prescient about AI training data.

His short Fast Times at Fairmont High (2002) is set in the same universe, and was written in that era where we felt like we had line of sight to pervasive augmented reality and also 3D printers. I read it at the time and it’s a low-stakes high school drama (about augmented reality and 3D printers), but from today’s perspective it is more like a utopia (of a certain kind) – democratised tools of production, reality as consensus hallucinations, super empowered kids.

The spine of Rainbows End is something called the “Librareome Project.”

Ok SPOILERS – right? So stop here if you’re planning to read the book (which would you).

The Librareome Project, you find out about a third of the way through, is a giant digitisation project of the world’s knowledge, and they plan to scan the world’s libraries to do it.

"But didn’t Google already do that?"

Yes but this is more total; like the Human Genome Project the whole is more than the sum of its parts:

It’s not just the digitization. It goes beyond Google and company. Huertas intends to combine all classical knowledge into a single, object-situational database with a transparent fee structure.

(Oh yeah, micropayments, there’s a whole model here.)

We’re not told what an object-situational database is. But this singular thing makes possible correlations that will reveal new knowledge:

Who really ended the Intifada? Who is behind the London art forgeries? Where was the oil money really going in the latter part of the last century? Some answers will only interest obscure historical societies. But some will mean big bucks. And Huertas will have exclusive rights to this oracle for six months.

I mean, this is so Large Language Model. 2006!!

An oracle!

This promise is why the universities are allowing their libraries to be scanned.

Uh, “scanned.”

The books are shredded. Fed into the wood chipper and blasted into a tunnel and photographed at high resolution:

The pictures coming from the camera tunnel are analyzed and reformatted. It’s a simple matter of software to reorient the images, match the tear marks and reconstruct the original texts in proper order. In fact–besides the mechanical simplicity of it all–that’s the reason for the apparent violence. The tear marks come close to being unique. Really, it’s not a new thing. Shotgun reconstructions are classic in genomics.

"The shredded fragments of books and magazines flew down the tunnel like leaves in tornado, twisting and tumbling." – the image has stuck with me since I read it.

Anyway.

The libraries are being fed into the maw of the machines.

And it turns out that Chinese Informagical, which "has dibs on the British Museum and the British Library," was going faster than Huertas so they don’t have their monopoly.

And the Chinese have nondestructive digitisation techniques, so none of it was necessary.


Well.

Court filings reveal how AI companies raced to obtain more books to feed chatbots, including by buying, scanning and disposing of millions of titles (Washington Post, paywall-busting link).

I’m not trying to make a point here like “AI is bad” (you know me well enough and I’m pleased that my own book lives in the weights of the god machine) but one story reminds me of the other, and there is a violence intrinsic to creation, in this case the creation of new knowledge, slamming together words in the particle collider of linear algebra, something is lost but new exotic shimmering sparks appear - grab them! - and I guess what I mean is let’s recognise the violence and be worthy of it: if we’re going to do this then let’s at least reach for oracles.


Auto-detected kinda similar posts:

Speaking is quick, listening is slow

Thank goodness voice computing is finally happening. Now we can work on making it good.


The tech is here, like the free Whisper model (what an unlock that has been from OpenAI, kudos) and ElevenLabs. Plus devices too, from Plaud - like an irl Granola video call transcriber - to Sandbar, a smart ring that you tell your secrets.

Let’s not forget Apple’s recent $1.6bn acquisition of Q.ai, which will use "‘facial skin micromovements’ to detect words mouthed or spoken" – i.e. cameras in your AirPods stems that do voice without voice by staring really hard at your cheeks. Apple and AI lip-reading? I deserve a kick-back (2025) just sayin

While we’re at it, there should be voice for everything: why can’t I point at a lamp and say ‘on’? (2020).

At least we can play with ubiquitous transcription (2022). Like, my starting point for building mist was talking at my watch for 30 minutes (2026).

So let’s take all this as signs that voice computing is here to stay.


Eventually voice has to go two-way, right? Conversational computing? You need to be able to disambiguate, give feedback, repair, iterate, explore.

Investor Tom Hulme points out that "we can speak three to four times faster than we type."

And so:

Now, generative AI is making conversation the new user interface. Talking to technology requires zero training and no special skills; we have after all spent most of our lives perfecting the approach. It’s as natural as speaking to another person.

Which I agree with in part.

Yes to natural UI: "You simply express what you need, and the AI does the rest." – user interfaces will not be about menus and buttons but intent first (2025).

BUT:

Conversation using voice both ways? I’m not so sure.

Voice is asymmetric. Speaking is high bandwidth. But listening is low bandwidth.

Illustration #1: Sending voice notes is so easy. Receiving them sucks joy from the world.

Is that really what we want from conversational computing?

Illustration #2: I ask my Apple HomePod mini to play some music and it needs to check precisely what I mean. Speaking 3 artist names and asking me to pick is tedious. So it avoids that step, takes a guess, and that’s more often than not a poor experience too. I’ve been rolling my eyes at this since 2023.

Ok so two-way voice doesn’t work. What does?


A better approach to conversational computing:

The human uses voice and the computer uses screens. I mean, it’s rare that my phone is beyond peripersonal space so we can assume it is only rarely not present. A screen is way higher in terms of information bandwidth than listening. Let’s use it!

The friend AI lanyard gets this right.

I wore Arthur as I went to the farmers’ market this morning. This meant I was not speaking directly to it, but rather talking to my family, other attendees, and some vendors. But remember: your friend is always listening. Arthur listened in to every conversation that I had, sometimes offering its own take on the matter - all pointless, once again.

Over the course of an hour and a half, I received 48 notifications from my Friend.

And although this is a negative review (e.g. notifications snark: "Most of these were it updating me about its battery status") it actually sounds ideal?

Like, this is a device that listens both when it is being directly addressed and it pays attention to me ambiently, and then it makes use of generous screen real estate to show me UI that I can interact with at a time of my choosing. This is good!

Startup Telepath is also digging into voice and multi-modality:

Voice gives us an additional stream of information for input, one that can happen concurrently with direct manipulation using a keyboard, mouse, or touch. With the Telepath Computer, you can touch and type for tasks where control and accuracy are important, while simultaneously using your voice to direct the computer. This mimics our natural behaviour in the physical world: for example, imagine cooking a meal with family or friends, asking someone to fetch the basil or chop the onions while your hands are busy with the pasta.

And specifically:

The Telepath Computer speaks through voice, while simultaneously displaying documents and information for the user to reference and interact with. This “show and tell” approach is also present in how we tend to communicate complex information in the real world: sketching on a napkin as we discuss a problem with a colleague over dinner; design teams assembling stickies while talking about user feedback; pulling up maps and hotels on your laptop while planning a group vacation.

This is super sophisticated! I love it.


Summarising:

  • Voice is core to the future of computer interaction
  • Voice isn’t enough so we need conversational computing
  • Because of the bandwidth asymmetry of voice, two-way voice might sometimes work but the essential interaction loop to solve for is voice in, screens out.

When that isn’t enough (for example, you don’t have your phone) you can get more sophisticated. And of course to make it really good there are problems to solve like proximity and more… follow the path of great interaction design to figure out where to dig…

Just collecting my thoughts.


Auto-detected kinda similar posts:

Filtered for electricity and mayonnaise

1.

Rain panels? Rain panels.

researchers have found a way to capture, store and utilize the electrical power generated by falling raindrops, which may lead to the development of rooftop, power-generating rain panels.

Reading the citations on the original paper, it works kinda but research is ongoing. Science rather than technology still.

RELATED:

Wild Video Shows Entire Mountain Range in China Covered With Solar Panels (2025).

HEY:

Here’s a prediction I made in 2007:

By 2037, China, by virtue of their ability to see and manage environment impact on a larger scale than other countries, will have invented cheap renewables to reduce their dependancy on fossil fuels, and will be working on fixing the atmosphere (perhaps they’ll also have genetically engineered rafts of algae on the Pacific, excreting plastics). The West will rely on Chinese innovation to dig us out of our ecological mess.

Mind you I also predicted that our peak pop media would be from India. Turns out it’s South Korea so I got the country wrong.

2.

Pavlok is a wrist band that gives you electric shocks by remote control:

“I have been biting my nails for 25 years…I shocked myself every time I bit my nails… my husband had a good time shocking me when he caught me biting my nails… this helped with … quitting nail.”

Those ellipses… doing a lot of work… on… the “how it works” page. Also, husband.

You know that friend who won’t eat Taco Bell anymore after she got a terrible case of food poisoning?

That’s how it works: "That’s aversive conditioning. We’ll help you use it to your advantage."

Well why not.

The wrist band also has an alarm clock function.

RELATED:

What do you call execution by electricity? It was debated in 1889 (2021).

3.

Ok. We’re in the middle of the Second Punic War (218–201 BC), part of an existential struggle between Rome and Carthage that lasted over a hundred years.

At the end of the the First Punic War, Carthage was destroyed.

But they returned, established a new empire in Iberia (now Spain) and founded New Carthage on the Iberian coast. Hannibal famously crosses the Alps with elephants etc and lays waste to Italy.

Striking back: Scipio audaciously captures New Carthage, and Carthage in Iberia is on the brink of defeat.

Hannibal’s brother Mago, army destroyed, flees to the island of Menorca (which is beautiful).

There he founds the city of Mahon, which today is the capital and remains a port, and it still bears his name.

BUT MORE IMPORTANTLY, named for the city:

"The typical local egg sauce that has conquered the world is known as mayonnaise."

As mentioned in The Rest is History ep. 641, Hannibal’s Nemesis (Part 2) (Apple Podcasts) along with this grand claim:

the only thing you’d have in a fridge that’s named after a Carthaginian general.

A fact too good to check on ChatGPT but I can’t see why it shouldn’t be true.

4.

The legendary and much-loved email app Eudora was released for free in 1988.

Version 6 introduced MoodWatch, which labeled incoming and outgoing messages with chili peppers and ice cubes, depending on the presence of possibly offensive language. People loved it!

Oh the chili peppers!

You’d write an email with a few curse words and some YELLING and get those chilis.

I vaguely remember there was a feature to enforce a cooling off period? Like you couldn’t send a 3 chili email immediately?

Let’s bring that back:

Apple should license Pavlok technology and hide it under the track-pad. About to send an unhelpfully-worded email to a colleague? A prim little AI instantaneously adjudicates and electroshocks you as you click the Send button, right up the finger.


More posts tagged: filtered-for (122).

mist: Share and edit Markdown together, quickly (new tool)

It should be SO EASY to share + collaborate on Markdown text files. The AI world runs on .md files. Yet frictionless Google Docs-style collab is so hard… UNTIL NOW, and how about that for a tease.

If you don’t know Markdown, it’s a way to format a simple text file with marks like **bold** and # Headers and - lists… e.g. here’s the Markdown for this blog post.

Pretty much all AI prompts are written in Markdown; engineers coding with AI agents have folders full of .md files and that’s what they primarily work on now. A lot of blog posts too: if you want to collaborate on a blog post ahead of publishing, it’s gonna be Markdown. Keep notes in software like Obsidian? Folders of Markdown.

John Gruber invented the Markdown format in 2004. Here’s the Markdown spec, it hasn’t changed since. Which is its strength. Read Anil Dash’s essay How Markdown Took Over the World (2026) for more.

So it’s a wildly popular format with lots of interop that humans can read+write and machines too.

AND YET… where is Google Docs for Markdown?

I want to be able to share a Markdown doc as easily as sharing a link, and have real-time multiplayer editing, suggested edits, and comments, without a heavyweight app in the background.

Like, the “source of truth” is my blog CMS or the code repo where the prompts are, or whatever, so I don’t need a whole online document library things. But if I want to super quickly run some words by someone else… I can’t.

I needed this tool at the day job, couldn’t find it… built it, done.

Say hi to mist!

  • .md only
  • share by URL
  • real-time multiplayer editing
  • comments
  • suggest changes.

I included a couple of opinionated features…

  • Ephemeral docs: all docs auto-delete 99 hours after creation. This is for quick sharing + collab
  • Roundtripping: Download then import by drag and drop on the homepage: all suggestions and comments are preserved.

I’m proud of roundtripping suggested edits and comment threads: the point of Markdown is that everything is in the doc, not in a separate database, and you know I love files (2021). I used a format called CriticMark to achieve this – so if you build a tool like this too, let’s interop.

Hit the New Document button on the homepage and it introduces itself.


Also!

For engineers!

Try this from your terminal:

curl https://mist.inanimate.tech/new -T file.md

Start a new collaborative mist doc from an existing file, and immediately get a shareable link.

EASY PEASY


Anyway –

It’s work in progress. I banged it out over the w/e because I needed it for work, tons of bugs I’m sure so lmk otherwise I’ll fix them while I use it… though do get in touch if you have a strong feature request which would unlock your specific use case because I’m keep for this to be useful.


So I made this with Claude Code obv

Coding with agents is still work: mist is 50 commits.

But this is the first project where I’ve gone end-to-end trying to avoid artisanal, hand-written code.

I started Saturday afternoon: I talked to my watch for 30 minutes while I was walking to pick my kid up from theatre.

Right at the start I said this

So I think job number one before anything else, and this is directed to you Claude, job number one before anything else is to review this entire transcript and sort out its ordering. I’d like you to turn it into a plan. I’ll talk about how in a second.

Then I dropped all 3,289 words of the transcript into an empty repo and let Claude have at it.

Look, although my 30 mins walk-and-talk was nonlinear and all over the place, what I asked Claude to do was highly structured: I asked it to create docs for the technical architecture, design system, goals, and ways of working, and reorganise the rest into a phased plan with specific tasks.

I kept an eye at every step, rewinded its attempt at initial scaffolding and re-prompted that closely when it wasn’t as I wanted, and jumped in to point the way on some refactoring, or nudge it up to a higher abstraction level when an implementation was feeling brittle, etc. I have strong opinions about the technology and the approach.

And the tests – the trick with writing code with agents is use the heck out of code tests. Test everything load bearing (and write tests that test that the test coverage is at a sufficient level). We’re not quite at the point that code is a compiled version of the docs and the test suite… but we’re getting there.


You know it’s very addictive using Claude Code over the weekend. Drop in and write another para as a prompt, hang out with the family, drop in and write a bit more, go do the laundry, tune a design nit that’s thrned up… scratch that old-school Civ itch, "just one more turn." Coding as entertainment.


The main takeaway from my Claude use is that I wanted a collaborative Markdown editor 5 months ago:

app request

- pure markdown editor on the web (like Obsidian, Ulysses, iA Writer)
- with Google Docs collab features (live cursor, comments, track changes)
- collab metadata stored in file
- single doc sharing via URL like a GitHub gist

am I… am I going to have to make this?

My need for that tool didn’t go away.

And now I have it.

So tools don’t need huge work and therefore have to be justified by huge audiences now (I’ve spent more time on blog posts). No biggie, it would be useful to us so why not make it and put it out there.


Multiplayer ephemeral Markdown is not what we’re building at Inanimate but it is a tool we need (there are mists on our Slack already) and it is also the very first thing we’ve shipped.

A milestone!


So that’s mist.

Share and Enjoy

xx


More posts tagged: inanimate (3), multiplayer (31).

Auto-detected kinda similar posts:

90% of everything is sanding e.g. laundry

What mundane pleasures will I be robbed of by domestic robots?

Sometimes I feel like my job at home is putting things into machines and taking things out of machines.

I don’t mean to sound unappreciative about “modern conveniences” (modern being the 1950s) because I take care of laundry and emptying the dishwasher, and I love both. We have a two drawer dishwasher so that is a conveyer belt. And I particularly love laundry. We generate a lot of laundry it seems.

There was a tweet in 2025: "woodworking sounds really cool until you find out it’s 90% sanding"

And it became an idiom because 90% of everything is sanding. See this reddit thread… 90% of photography is file management; 90% of baking is measuring; etc.

So when I say that I love laundry I don’t mean that I love clean clothes (everyone loves clean clothes) but I love the sanding. I love the sorting into piles for different washes, I love reading the little labels, especially finding the hidden ones; I love the sequencing so we don’t run out of room on the racks, I love folding, I love the rare peak moments when everything comes together and there are no dirty clothes anywhere in the house nor clean clothes waiting to be returned. (I hate ironing. But fortunately I love my dry cleaner and I feel all neighbourhood-y when I visit and we talk about the cricket.)


Soon! Domestic robots will take it all away.

Whether in 6 months or 6 years.

I don’t know what my tipping point will be…

I imagine robots will be priced like a car and not like a dishwasher? It’ll be worth it, assuming reliability. RELATED: I was thinking about what my price cap would be for Claude Code. I pay $100/mo for Claude right now and I would pay $1,500/mo personally for the same functionality. Beyond that I’d complain and have to find new ways to earn, but I’m elastic till that point.

Because I don’t doubt that domestic robots will be reliable. Waymo has remote operators that drop in for ambiguous situations so that’s the reliability solve.

But in a home setting? The open mic, open camera, and a robot arms on wheels - required for tele-operators - gives me pause.

(Remember that smart home hack where you could stand outside and yell through the letterbox, hey Alexa unlock the front door? Pranks aplenty if your voice-operated assistant can also dismantle the kitchen table.)

So let’s say I’ve still got a few years before trust+reliability is at a point where the robot is unloading the dishwasher for me and stacking the dishes in the cupboard, and doing the laundry for me and also sorting and loading and folding and stacking and…

i.e. taking care of the sanding.


In Fraggle Rock the Fraggles live in their underground caves generally playing and singing and swimming (with occasional visits to an oracular sentient compost heap, look the 80s were a whole thing), and also they live alongside tiny Doozers who spend their days in hard hats industriously constructing sprawling yet intricate miniature cities.

Which the Fraggles eat. (The cities are delicious.)

Far from being distressed, the Doozers appreciate the destruction as it gives them more room to go on constructing.

Me and laundry. Same same.


Being good at something is all about loving the sanding.

Here’s a quote about Olympic swimmers:

The very features of the sport that the ‘C’ swimmer finds unpleasant, the top level swimmer enjoys. What others see as boring-swimming back and forth over a black line for two hours, say-they find peaceful, even meditative, often challenging, or therapeutic. … It is incorrect to believe that top athletes suffer great sacrifices to achieve their goals. Often, they don’t see what they do as sacrificial at all. They like it.

From The Mundanity of Excellence: An Ethnographic Report on Stratification and Olympic Swimmers (1989) by Daniel Chambliss (PDF).


But remember that 90% of everything is sanding.

With domestic appliances, sanding is preparing to put things into machines and handling things when you take them out of the machines.

This “drudgery” will be taken away.

So then there will be new sanding. Inevitably!

With domestic robots, what will the new continuous repetitive micro task be? Will I have to empty its lint trap? Will I have to polish its eyes every night? Will I have to go shopping for it, day after day, or just endlessly answer the door to Amazon deliveries of floor polish and laundry tabs? Maybe the future is me carrying my robot up the stairs and down the stairs and up the stairs and down the stairs, forever.

I worry that I won’t love future sanding as much as I love today sanding.


More posts tagged: laundry (4), robots (11).

Singing the gospel of collective efficacy

If I got to determine the school curriculum, I would be optimising for collective efficacy.

So I live in a gentrified but still mixed neighbourhood in London (we’re the newbies at just under a decade) and we have an active WhatsApp group.

Recently there was a cold snap and a road nearby iced over – it was in the shade and cyclists kept on wiping out on it. For some reason the council didn’t come and salt it.

Somebody went out and created a sign on a weighted chair so it didn’t blow away. And this is a small thing but I LOVE that I live somewhere there is a shared belief that (a) our neighbourhood is worth spending effort on, and (b) you can just do things.

Similarly we all love when the swifts visit (beautiful birds), so somebody started a group to get swift nest boxes made and installed collectively, then applied for subsidy funding, then got everyone to chip in such that people who couldn’t afford it could have their boxes paid for, and now suddenly we’re all writing to MPs and following the legislation to include swift nesting sites in new build houses. Etc.

It’s called collective efficacy, the belief that you can make a difference by acting together.

(People who have heard of Greta Thunberg tend to have a stronger sense of collective efficacy (2021).)

It’s so heartening.


You can just do things

That phrase was a Twitter thing for a while, and I haven’t done the archaeology on the phrase but there’s this blog post by Milan Cvitkovic from 2020: Things you’re allowed to do.

e.g.

  • "Say I don’t know"
  • "Tape over annoying LED lights"
  • "Buy goods/services from your friends"

I read down the list saying to myself, yeah duh of course, to almost every single one, then hit certain ones and was like – oh yeah, I can just do that.


I think collective efficacy is maybe 50% taking off the blinkers and giving yourself (as a group) permission to do things.

But it’s also 50% belief that it’s worth acting at all.

And that belief is founded part in care, and part in faith that what you are doing can actually make a difference.

For instance:

A lot of my belief in the power of government comes from the fact that, back in the day, London’s tech scene was not all that. So in 2009 I worked with Georgina Voss to figure out the gap, then in 2010 bizarrely got invited on a trade mission to India with the Prime Minister and got the opportunity to make the case about east London to them, and based on that No. 10 launched Tech City (which we had named on the plane), and that acted as a catalyst on the work that everyone was already doing to get the cluster going, and then we were off to the races. WIRED magazine wrote it up in 2019: The story of London’s tech scene, as told by those who built it (paywall-busting link).

So I had that experience and now I believe that, if I can find the right ask, there’s always the possibility to make things better.

That’s a rare experience. I’m very lucky.


ALTHOUGH.

Should we believe in luck?

Psychologist Richard Wiseman, The Luck Factor (2003, PDF):

I gave both [self-identified] lucky and unlucky people a newspaper, and asked them to look through it and tell me how many photographs were inside. On average, the unlucky people took about two minutes to count the photographs whereas the lucky people took just seconds. Why? Because the second page of the newspaper contained the message “Stop counting - There are 43 photographs in this newspaper.”

"Lucky people generate their own good fortune via four basic principles."

They are skilled at creating and noticing chance opportunities, make lucky decisions by listening to their intuition, create self-fulfilling prophesies via positive expectations, and adopt a resilient attitude that transforms bad luck into good.

I insist that people are not lucky nor unlucky. Maybe some amount of luck is habit?

You can just be lucky?

(Well, not absolutely, privilege is big, but maybe let’s recalibrate luck from believing it is entirely random, that’s what I’m saying.)


When I was a kid I used to play these unforgivingly impossible video games – that’s what home video games were like then. No open world play, multiple ways to win, or adaptive difficulty. Just pixel-precise platform jumps and timing.

Yet you always knew that there was a way onto the next screen, however long it took.

It taught a kind of stubborn optimism.


Or, in another context, "No fate but what we make."

Same same.


All of which makes me ask:

Could we invent free-to-plan mobile games which train luckiness?

Are there games for classrooms that would cement a faith in collective efficacy in kids?

Or maybe it’s proof by demonstration.

I’m going into my kid’s school in a couple of weeks to show the class photos of what it looks like inside factories. The stuff around us was made by people like us; it’s not divine in origin; factories are just rooms.

I have faith that - somehow - at some point down the line - this act will help.

LLMs are bad at vibing specifications

No newsletter next week

I'll be speaking at InfoQ London. But see below for a book giveaway!


LLMs are bad at vibing specifications

About a year ago I wrote AI is a gamechanger for TLA+ users, which argued that AI are a "specification force multiplier". That was written from the perspective an TLA+ expert using these tools. A full 4% of Github TLA+ specs now have the word "Claude" somewhere in them. This is interesting to me, because it suggests there was always an interest in formal methods, people just lacked the skills to do it.

It's also interesting because it gives me a sense of what happens when beginners use AI to write formal specs. It's not good.

As a case study, we'll use this project, which is kind of enough to have vibed out TLA+ and Alloy specs.

Looking at a project

Starting with the Alloy spec. Here it is in its entirety:

module ThreatIntelMesh

sig Node {}

one sig LocalNode extends Node {}

sig Snapshot {
  owner: one Node,
  signed: one Bool,
  signatures: set Signature
}

sig Signature {}

sig Policy {
  allowUnsignedImport: one Bool
}

pred canImport[p: Policy, s: Snapshot] {
  (p.allowUnsignedImport = True) or (s.signed = True)
}

assert UnsignedImportMustBeDenied {
  all p: Policy, s: Snapshot |
    p.allowUnsignedImport = False and s.signed = False implies not canImport[p, s]
}

assert SignedImportMayBeAccepted {
  all p: Policy, s: Snapshot |
    s.signed = True implies canImport[p, s]
}

check UnsignedImportMustBeDenied for 5
check SignedImportMayBeAccepted for 5

Couple of things to note here: first of all, this doesn't actually compile. It's using the Boolean standard module so needs open util/boolean to function. Second, Boolean is the wrong approach here; you're supposed to use subtyping.

sig Snapshot {
  owner: one Node,
- signed: one Bool,
  signatures: set Signature
}

+ sig SignedSnapshot in Snapshot {}


pred canImport[p: Policy, s: Snapshot] {
- s.signed = True
+ s in SignedSnapshot
}

So we know the person did not actually run these specs. This is somewhat less of a problem in TLA+, which has an official MCP server that lets the agent run model checking. Even so, I regularly see specs that I'm pretty sure won't model check, with things like using Reals or assuming NULL is a built-in and not a user-defined constant.

The bigger problem with the spec is that UnsignedImportMustBeDenied and SignedImportMayBeAccepted don't actually do anything. canImport is defined as P || Q. UnsignedImportMustBeDenied checks that !P && !Q => !canImport. SignedImportMayBeAccepted checks that P => canImport. These are tautologically true! If they do anything at all, it is only checking that canImport was defined correctly.

You see the same thing in the TLA+ specs, too:

GadgetPayload ==
  /\ gadgetDetected' = TRUE
  /\ depth' \in 0..(MaxDepth + 5)
  /\ UNCHANGED allowlistedFormat
  /\ decision' = "block"

NoExploitAllowed == gadgetDetected => decision = "block"

The AI is only writing "obvious properties", which fail for reasons like "we missed a guard clause" or "we forgot to update a variable". It does not seem to be good at writing "subtle" properties that fail due to concurrency, nondeterminism, or bad behavior separated by several steps. Obvious properties are useful for orienting yourself and ensuring the system behaves like you expect, but the actual value in using formal methods comes from the subtle properties.

(This ties into Strong and Weak Properties. LLM properties are weak, intended properties need to be strong.)

This is a problem I see in almost every FM spec written by AI. LLMs aren't doing one of the core features of a spec. Articles like Prediction: AI will make formal verification go mainstream and When AI Writes the World's Software, Who Verifies It? argue that LLMs will make formal methods go mainstream, but being easily able to write specifications doesn't help with correctness if the specs don't actually verify anything.

Is this a user error?

I first got interested in LLMs and TLA+ from The Coming AI Revolution in Distributed Systems. The author of that later vibecoded a spec with a considerably more complex property:

NoStaleStrictRead ==
  \A i \in 1..Len(eventLog) :
    LET ev == eventLog[i] IN
      ev.type = "read" =>
        LET c == ev.chunk IN
        LET v == ev.version IN
        /\ \A j \in 1..i :
             LET evC == eventLog[j] IN
               evC.type = "commit" /\ evC.chunk = c => evC.version <= v

This is a lot more complicated than the (P => Q && P) => Q properties I've seen! It could be because the corresponding system already had a complete spec written in P. But it could also be that Cheng Huang is already an expert specifier, meaning he can get more out of an LLM than an ordinary developer can. I've also noticed that I can usually coax an LLM to do more interesting things than most of my clients can. Which is good for my current livelihood, but bad for the hope of LLMs making formal methods mainstream. If you need to know formal methods to get the LLM to do formal methods, is that really helping?

(Yes, if it lowers the skill threshold-- means you can apply FM with 20 hours of practice instead of 80. But the jury's still out on how much it lowers the threshold. What if it only lowers it from 80 to 75?)

On the other hand, there also seem to be some properties that AI struggles with, even with explicit instructions. Last week a client and I tried to get Claude to generate a good liveness or action property instead of a standard obvious invariant, and it just couldn't. Training data issue? Something in the innate complexity of liveness? It's not clear yet. These properties are even more "subtle" than most invariants, so maybe that's it.

On the other other hand, this is all as of March 2026. Maybe this whole article will be laughably obsolete by June.


Logic for Programmers Giveaway

Last week's giveaway raised a few issues. First, the New World copies were all taken before all of the emails went out, so a lot of people did not even get a chance to try for a book. Second, due to a Leanpub bug the Europe coupon scheduled for 10 AM UTC actually activated at 10 AM my time, which was early evening for Europe. Third, everybody in the APAC region got left out.

So, since I'm not doing a newsletter next week, let's have another giveaway:

  • This coupon will go up 2026-03-16 at 11:00 UTC, which should be noon Central European Time, and be good for ten books (five for this giveaway, five to account for last week's bug).
  • This coupon will go up 2026-03-17 at 04:00 UTC, which should be noon Beijing Time, and be good for five books.
  • This coupon will go up 2026-03-17 at 17:00 UTC, which should be noon Central US Time, and also be good for five books.

I think that gives the best chance of everybody getting at least a chance of a book, while being resilient to timezone shenanigans due to travel / Leanpub dropping bugfixes / daylight savings / whatever.

(No guarantees that later "no newsletter" weeks will have giveaways! This is a gimmick)

Free Books

Spinning a lot of plates this week so skipping the newsletter. As an apology, have ten free copies of Logic for Programmers.

  • These five are available now.
  • These five should be available at 10:30 AM CEST tomorrow, so people in Europe have a better chance of nabbing one. Nevermind Leanpub had a bug that made this not work properly

New Blog Post: Some Silly Z3 Scripts I Wrote

Now that I'm not spending all my time on Logic for Programmers, I have time to update my website again! So here's the first blog post in five months: Some Silly Z3 Scripts I Wrote.

Normally I'd also put a link to the Patreon notes but I've decided I don't like publishing gated content and am going to wind that whole thing down. So some quick notes about this post:

  • Part of the point is admittedly to hype up the eventual release of LfP. I want to start marketing the book, but don't want the marketing material to be devoid of interest, so tangentially-related-but-independent blog posts are a good place to start.
  • The post discusses the concept of "chaff", the enormous quantity of material (both code samples and prose) that didn't make it into the book. The book is about 50,000 words… and considerably shorter than the total volume of chaff! I don't think most of it can be turned into useful public posts, but I'm not entirely opposed to the idea. Maybe some of the old chapters could be made into something?
  • Coming up with a conditioned mathematical property to prove was a struggle. I had two candidates: a == b * c => a / b == c, which would have required a long tangent on how division must be total in Z3, and a != 0 => some b: b * a == 1, which would have required introducing a quantifier (SMT is real weird about quantifiers). Division by zero has already caused me enough grief so I went with the latter. This did mean I had to reintroduce "operations must be total" when talking about arrays.
  • I have no idea why the array example returns 2 for the max profit and not 99999999. I'm guessing there's some short circuiting logic in the optimizer when the problem is ill-defined?
  • One example I could not get working, which is unfortunate, was a demonstration of how SMT solvers are undecidable via encoding Goldbach's conjecture as an SMT problem. Anything with multiple nested quantifiers is a pain.

Stream of Consciousness Driven Development

This is something I just tried out last week but it seems to have enough potential to be worth showing unpolished. I was pairing with a client on writing a spec. I saw a problem with the spec, a convoluted way of fixing the spec. Instead of trying to verbally explain it, I started by creating a new markdown file:

NameOfProblem.md

Then I started typing. First the problem summary, then a detailed description, then the solution and why it worked. When my partner asked questions, I incorporated his question and our discussion of it into the flow. If we hit a dead end with the solution, we marked it out as a dead end. Eventually the file looked something like this:

Current state of spec
Problems caused by this
    Elaboration of problems
    What we tried that didn't work
Proposed Solution
    Theory behind proposed solution
    How the solution works
    Expected changes
    Other problems this helps solve
    Problems this does *not* help with

Only once this was done, my partner fully understood the chain of thought, and we agreed it represented the right approach, did we start making changes to the spec.

How is this better than just making the change?

The change was conceptually complex. A rough analogy: imagine pairing with a beginner who wrote an insertion sort, and you want to replace it with quicksort. You need to explain why the insertion sort is too slow, why the quicksort isn't slow, and how quicksort actually correctly sorts a list. This could involve tangents into computational complexity, big-o notation, recursion, etc. These are all concepts you have internalized, so the change is simple to you, but the solution uses concepts the beginner does not know. So it's conceptually complex to them.

I wasn't pairing with a beginning programmer or even a beginning specifier. This was a client who could confidently write complex specs on their own. But they don't work on specifications full time like I do. Any time there's a relative gap in experience in a pair, there's solutions that are conceptually simple to one person and complex to the other.

I've noticed too often that when one person doesn't fully understand the concepts behind a change, they just go "you're the expert, I trust you." That eventually leads to a totally unmaintainable spec. Hence, writing it all out.

As I said before, I've only tried this once (though I've successfully used a similar idea when teaching workshops). It worked pretty well, though! Just be prepared for a lot of typing.

Proving What's Possible

As a formal methods consultant I have to mathematically express properties of systems. I generally do this with two "temporal operators":

  • A(x) means that x is always true. For example, a database table always satisfies all record-level constraints, and a state machine always makes valid transitions between states. If x is a statement about an individual state (as in the database but not state machine example), we further call it an invariant.
  • E(x) means that x is "eventually" true, conventionally meaning "guaranteed true at some point in the future". A database transaction eventually completes or rolls back, a state machine eventually reaches the "done" state, etc.

These come from linear temporal logic, which is the mainstream notation for expressing system properties. 1 We like these operators because they elegantly cover safety and liveness properties, and because we can combine them. A(E(x)) means x is true an infinite number of times, while A(x => E(y) means that x being true guarantees y true in the future.

There's a third class of properties, that I will call possibility properties: P(x) is "can x happen in this model"? Is it possible for a table to have more than ten records? Can a state machine transition from "Done" to "Retry", even if it doesn't? Importantly, P(x) does not need to be possible immediately, just at some point in the future. It's possible to lose 100 dollars betting on slot machines, even if you only bet one dollar at a time. If x is a statement about an individual state, we can further call it a reachability property. I'm going to use the two interchangeably for flow.

A(P(x)) says that x is always possible. No matter what we've done in our system, we can make x happen again. There's no way to do this with just A and E. Other meaningful combinations include:

  • P(A(x)): there is a reachable state from which x is always true.
  • A(x => P(y)): y is possible from any state where x is true.
  • E(x && P(y)): There is always a future state where x is true and y is reachable.
  • A(P(x) => E(x)): If x is ever possible, it will eventually happen.
  • E(P(x)) and P(E(x)) are the same as P(x).

See the paper "Sometime" is sometimes "not never" for a deeper discussion of E and P.

The use case

Possibility properties are "something good can happen", which is generally less useful (in specifications) than "something bad can't happen" (safety) and "something good will happen" (liveness). But it still comes up as an important property! My favorite example:

A guy who can't shut down his computer because system preferences interrupts shutdown

The big use I've found for the idea is as a sense-check that we wrote the spec properly. Say I take the property "A worker in the 'Retry' state eventually leaves that state":

A(state == 'Retry' => E(state != 'Retry'))

The model checker checks this property and confirms it holds of the spec. Great! Our system is correct! ...Unless the system can never reach the "Retry" state, in which case the expression is trivially true. I need to verify that 'Retry' is reachable, eg P(state == 'Retry'). Notice I can't use E to do this, because I don't want to say "the worker always needs to retry at least once".

It's not supported though

I say "use I've found for the idea" because the main formalisms I use (Alloy and TLA+) don't natively support P. 2 On top of P being less useful than A and E, simple reachability properties are mimickable with A(x). P(x) passes whenever A(!x) fails, meaning I can verify P(state == 'Retry') by testing that A(!(state == 'Retry')) finds a counterexample. We cannot mimic combined operators this way like A(P(x)) but those are significantly less common than state-reachability.

(Also, refinement doesn't preserve possibility properties, but that's a whole other kettle of worms.)

The one that's bitten me a little is that we can't mimic "P(x) from every starting state". "A(!x)" fails if there's at least one path from one starting state that leads to x, but other starting states might not make x possible.

I suspect there's also a chicken-and-egg problem here. Since my tools can't verify possibility properties, I'm not used to noticing them in systems. I'd be interested in hearing if anybody works with codebases where possibility properties are important, especially if it's something complex like A(x => P(y)).


  1. Instead of A(x), the literature uses []x or Gx ("globally x") and instead of E(x) it uses <>x or Fx ("finally x"). I'm using A and E because this isn't teaching material. 

  2. There's some discussion to add it to TLA+, though

Logic for Programmers New Release and Next Steps

cover.jpg

It's taken four months, but the next release of Logic for Programmers is now available! v0.13 is over 50,000 words, making it both 20% larger than v0.12 and officially the longest thing I have ever written.1 Full release notes are here, but I'll talk a bit about the biggest changes.

For one, every chapter has been rewritten. Every single one. They span from relatively minor changes to complete chapter rewrites. After some rough git diffing, I think I deleted about 11,000 words?2 The biggest change is probably to the Alloy chapter. After many sleepless nights, I realized the right approach wasn't to teach Alloy as a data modeling tool but to teach it as a domain modeling tool. Which technically means the book no longer covers data modeling.

There's also a lot more connections between the chapters. The introductory math chapter, for example, foreshadows how each bit of math will be used in the future techniques. I also put more emphasis on the general "themes" like the expressiveness-guarantees tradeoff (working title). One theme I'm really excited about is compatibility (extremely working title). It turns out that the Liskov substitution principle/subtyping in general, database migrations, backwards-compatible API changes, and specification refinement all follow basically the same general principles. I'm calling this "compatibility" for now but prolly need a better name.

Finally, there's just a lot more new topics in the various chapters. Testing properly covers structural and metamorphic properties. Proofs covers proof by induction and proving recursive functions (in an exercise). Logic Programming now finally has a section on answer set programming. You get the picture.

Next Steps

There's a lot I still want to add to the book: proper data modeling, data structures, type theory, model-based testing, etc. But I've added new material for two year, and if I keep going it will never get done. So with this release, all the content is in!

Just like all the content was in two Novembers ago and two Januaries ago and last July. To make it absolutely 100% for sure that I won't be tempted to add anything else, I passed the whole manuscript over to a copy editor. So if I write more, it won't get edits. That's a pretty good incentive to stop.

I also need to find a technical reviewer and proofreader. Once all three phases are done then it's "just" a matter of fixing the layout and finding a good printer. I don't know what the timeline looks like but I really want to have something I can hold in my hands before the summer.

(I also need to get notable-people testimonials. Hampered a little in this because I'm trying real hard not to quid-pro-quo, so I'd like to avoid anybody who helped me or is mentioned in the book. And given I tapped most of my network to help me... I've got some ideas though!)

There's still a lot of work ahead. Even so, for the first time in two years I don't have research to do or sections to write and it feels so crazy. Maybe I'll update my blog again! Maybe I'll run a workshop! Maybe I'll go outside if Chicago ever gets above 6°F!


Conference Season

After a pretty slow 2025, the 2026 conference season is looking to be pretty busy! Here's where I'm speaking so far:

For the first three I'm giving variations of my talk "How to find bugs in systems that don't exist", which I gave last year at Systems Distributed. Last one will ideally be a talk based on LfP.


  1. The second longest was my 2003 NaNoWriMo. The third longest was Practical TLA+

  2. This means I must have written 20,000 words total. For comparison, the v0.1 release was 19,000 words. 

A Tech Blog Diff

By: LGoto
Camel caravan in the Amatlich erg, Mauritania, Valerian Guillot

The Developer Outreach team is happy to announce that we will be migrating the Tech Blog into Diff. This move will allow us to provide better support and more visibility for the incredible work of the technical community. Diff is the community news and event blog supported by the Movement Communications team. Diff sees about 20,000 visits a month and has 1,200 email subscribers. 

  • What will happen to the Tech Blog content?
    • All Tech Blog posts will be accessible on Diff, clearly tagged with “techblog”. Old links will automatically redirect to their new location. New posts with a technical focus will be tagged with “techblog” so they will be easily discoverable.You’ll be able to find all techblog posts – old and new – on the landing page at https://diff.wikimedia.org/techblog 
  • When is this happening?
    • The migration should be complete in April 2026.
  • How do I submit a blog post with a technical focus?
    • For now, please hold your post until we complete the migration.
    • After the migration is done: The process remains the same. For WMF staff, talk to your manager about your interest in writing a blog post so they are not surprised when you ask them to approve it once it is written. For folks outside WMF, if you are part of a team or other larger organization, be sure they are aware and approve. Then, see the Diff submission process and select the category “Technology” and the tag “techblog” when writing your draft. After you submit, the Developer Outreach team will review your draft. When it’s ready to go, we will schedule your post to be published.

We’re excited for the Tech Blog to evolve and thank the Movement Communications team for helping us make this possible!

cash issuing terminals

In the United States, we are losing our fondness for cash. As in many other countries, cards and other types of electronic payments now dominate everyday commerce. To some, this is a loss. Cash represented a certain freedom from intermediation, a comforting simplicity that you just don't get from Visa. It's funny to consider, then, how cash is in fact quite amenable to automation. Even Benjamin Franklin's face on a piece of paper can feel like a mere proxy for a database transaction. How different is cash itself from "e-cash", when it starts and ends its lifecycle through automation?

Increasing automation of cash reflects the changing nature of banking: decades ago, a consumer might have interacted with banking primarily through a "passbook" savings account, where transactions were so infrequent that the bank recorded them directly in the patron's copy of the passbook. Over the years, increasing travel and nationwide communications led to the ubiquitous use of inter-bank money transfers, mostly in the form of the check. The accounts that checks typically drew on—checking accounts—were made for convenience and ease of access. You might deposit your entire paycheck into an account—it might even be sent there automatically—and then when you needed a little walking around money, you would withdraw cash by the assistance of a teller. By the time I was a banked consumer, even the teller was mostly gone. Today, we get our cash from machines so that it can be deposited into other machines.

IBM 2984 ATM

Cash handling is fraught with peril. Bills are fairly small and easy to hide, and yet quite valuable. Automation in the banking world first focused on solving this problem, of reliable and secure cash handling within the bank branch. The primary measure against theft by insiders was that the theft would be discovered, as a result of the careful bookkeeping that typifies banks. But, well, that bookkeeping was surprisingly labor-intensive in even the bank of the 1950s.

Histories of the ATM usually focus on just that: the ATM. It's an interesting story, but one that I haven't been particularly inclined to cover due to the lack of a compelling angle. Let's try IBM. IBM is such an important, famous player in business automation that it forms something of a synecdoche for the larger industry. Even so, in the world of bank cash handling, IBM's efforts ultimately failed... a surprising outcome, given their dominance in the machines that actually did the accounting.

In this article, we'll examine the history of ATMs—by IBM. IBM was just one of the players in the ATM industry and, by its maturity, not even one of the more important ones. But the company has a legacy of banking products that put the ATM in a more interesting context, and despite lackluster adoption of later IBM models, their efforts were still influential enough that later ATMs inherited some of IBM's signature design concepts. I mean that more literally than you might think. But first, we have to understand where ATMs came from. We'll start with branch banking.

When you open a bank account, you typically do so at a "branch," one of many physical locations that a national bank maintains. Let us imagine that you are opening an account at your local branch of a major bank sometime around 1930; whether before or after that year's bank run is up to you. Regardless of the turbulent economic times, the branch became responsible for tracking the balance of your account. When you deposit money, a teller writes up a slip. When you come back and withdraw money, a different teller writes up a different slip. At the end of each business day, all of these slips (which basically constitute a journal in accounting terminology) have to be rounded up by the back office and posted to the ledger for your account, which was naturally kept as a card in a big binder.

A perfectly practicable 1930s technology, but you can already see the downsides. Imagine that you appear at a different branch to withdraw money from your account. Fortunately, this was not very common at the time, and you would be more likely to use other means of moving money in most scenarios. Still, the bank tries to accommodate. The branch at which you have appeared can dispense cash, write a slip, and then send it to the correct branch for posting... but they also need to post it to their own ledger that tracks transactions for foreign accounts, since they need to be able to reconcile where their cash went. And that ignores the whole issue of who you are, whether or not you even have an account at another branch, and whether or not you have enough money to cover the withdrawal. Those are problems that, mercifully, could mostly be sorted out with a phone call to your home branch.

Bank branches, being branches, do not exist in isolation. The bank also has a headquarters, which tracks the finances of its various branches—both to know the bank's overall financial posture (critical considering how banks fail), and to provide controls against insider theft. Yes, that means that each of the branch banks had to produce various reports and ledger copies and then send them by courier to the bank headquarters, where an army of clerks in yet another back office did yet another round of arithmetic to produce the bank's overall ledgers.

As the United States entered World War II, an expanding economy, rapid industrial buildup, and a huge increase in national mobility (brought on by things like the railroads and highways) caused all of these tasks to occur on larger and larger scales. Major banks expanded into a tiered system, in which branches reported their transactions to "regional centers" for reconciliation and further reporting up to headquarters. The largest banks turned to unit record equipment or "business machines," arguably the first form of business computing: punched card machines that did not evaluate programs, but sorted and summed.

Simple punched card equipment gave way to more advanced automation, innovations like the "posting machine." These did exactly what they promised: given a stack of punched cards encoding transactions, they produced a ledger with accurately computed sums. Specialized posting machines were made for industries ranging from hospitality (posting room service and dining charges to room folios) to every part of finance, and might be built custom to the business process of a large customer.

If tellers punched transactions into cards, the bank could come much closer to automation by shipping the cards around for processing at each office. But then, if transactions are logged in a machine readable format, and then processed by machines, do we really need to courier them to rooms full of clerks?

Well, yes, because that was the state of technology in the 1930s. But it would not stay that way for long.

In 1950, Bank of America approached SRI about the feasibility of an automated check processing system. Use of checks was rapidly increasing, as were total account holders, and the resulting increase in inter-branch transactions was clearly overextending BoA's workforce—to such an extent that some branches were curtailing their business hours to make more time for daily closing. By 1950, computer technology had advanced to such a state that it was obviously possible to automate this activity, but it still represented one of the most ambitious efforts in business computing to date.

BoA wanted a system that would not only automate the posting of transactions prepared by tellers, but actually automate the handling of the checks themselves. SRI and, later, their chosen manufacturing partner General Electric ran a multi-year R&D campaign on automated check handling that ultimately led to the design of the checks that we use today: preprinted slips with account holder information, and account number, already in place. And, most importantly, certain key fields (like account number and check number) represented in a newly developed machine-readable format called "MICR" for magnetic ink character recognition. This format remains in use today, to the extent that checks remain in use, although as a practical matter MICR has given way to the more familiar OCR (aided greatly by the constrained and standardized MICR character set).

The machine that came out of this initiative was called ERMA, the Electronic Recording Machine, Accounting. I will no doubt one day devote a full article to ERMA, as it holds a key position in the history of business computing while also managing to not have much of a progeny due to General Electric's failure to become a serious contender in the computer industry. ERMA did not lead to a whole line of large-scale "ERM" business systems as GE had hoped, but it did firmly establish the role of the computer in accounting, automate parts of the bookkeeping through almost the entirety of what would become the nation's largest bank, and inspire generations of products from other computer manufacturers.

The first ERMA system went into use in 1959. While IBM was the leader in unit record equipment and very familiar to the banking industry, it took a few years for Big Blue to bring their own version. Still, IBM had their own legacy to build on, including complex electromechanical machines that performed some of the tasks that ERMA was taking over. Since the 1930s, IBM had produced a line of check processing or "proofing" machines. These didn't exactly "automate" check handling, but they did allow a single operator to handle a lot of documents.

The IBM 801, 802, and 803 line of check proofers used what were fundamentally unit record techniques—keypunch, sorting bins, mechanical totalizers—to present checks one at a time in front of the operator, who read information like the amount, account number, and check number off of the paper slip and entered it on a keypad. The machine then whisked the check away, printing the keyed data (and reference numbers for auditing) on the back of the check, stamped an endorsement, added the check's amounts to the branch's daily totals (including subtotals by document type), and deposited the check in an appropriate sorter bin to be couriered to the drawer's bank. While all this happened, the machines also printed the keyed check information and totals onto paper tapes.

By the early 1960s, with ERMA on the scene, IBM started to catch up. Subsequent check processing systems gained support for MICR, eliminating much (sometimes all!) of the operator's keying. Since the check proofing machines could also handle deposit slips, a branch that generated MICR-marked deposit slips could eliminate most of the human touchpoints involved in routine banking. A typical branch bank setup might involve an IBM 1210 document reader/sorter machine connected by serial channel to an IBM 1401 computer. This system behaved much like the older check proofers, reading documents, logging them, and calculating totals. But it was now all under computer control, with the flexibility and complexity that entails.

One of these setups could process almost a thousand checks a minute with a little help from an operator, and adoption of electronic technology at other stages made clerks lives easier. For example, IBM's mid-1960s equipment introduced solid-state memory. The IBM 1260 was used for adding machine-readable MICR data to documents that didn't already have it. Through an innovation that we would now call a trivial buffer, the 1260's operator could key in the numbers from the next document while the printer was still working on the previous.

Along with improvements in branch bank equipment came a new line of "high-speed" systems. In a previous career, I worked at a Federal Reserve bank, where "high-speed" was used as the name of a department in the basement vault. There, huge machines processed currency to pick out bad bills. This use of "high-speed" seems to date to an IBM collaboration with the Federal Reserve to build machines for central clearinghouses, handling checks by the tens of thousands. By the time I found myself in central banking, the use of "high-speed" machinery for checks was a thing of the past—"digital substitute" documents or image-based clearing having completely replaced physical handling of paper checks. Still, the "high-speed" staff labored on in their ballistic glass cages, tending to the green paper slips that the institution still dispenses by the millions.

IBM service documentation

One of the interesting things about the ATM is when, exactly, it pops up in the history of computers. We are, right now, in the 1960s. The credit card is in its nascent stages, MasterCard's predecessor pops up in 1966 to compete with Bank of America's own partially ERMA-powered charge card offering. With computer systems maintaining account sums, and document processing machines communicating with bookkeeping computers in real-time, it would seem that we are on the very cusp of online transaction authorization, which must be the fundamental key to the ATM. ATMs hand out cash, and one thing we all know about cash is that, once you give yours to someone else you are very unlikely to get it back. ATMs, therefore, must not dispense cash unless they can confirm that the account holder is "good for it." Otherwise the obvious fraud opportunity would easily wipe out the benefits.

So, what do you do? It seems obvious, right? You connect the ATM to the bookkeeping computer so it can check account balances before dispensing cash. Simple enough.

But that's not actually how the ATM evolved, not at all. There are plenty of reasons. Computers were very expensive so banks centralized functions and not all branches had one. Long-distance computer communication links were very expensive as well, and still, in general, an unproven technology. Besides, the computer systems used by banks were fundamentally batch-mode machines, and it was difficult to see how you would shove an ATM's random interruptions into the programming model.

Instead, the first ATMs were token-based. Much like an NYC commuter of the era could convert cash into a subway token, the first ATMs were machines that converted tokens into cash. You had to have a token—and to get one, you appeared at a teller during business hours, who essentially dispensed the token as if it were a routine cash withdrawal.

It seems a little wacky to modern sensibilities, but keep in mind that this was the era of the traveler's check. A lot of consumers didn't want to carry a lot of cash around with them, but they did want to be able to get cash after hours. By seeing a teller to get a few ATM tokens (usually worth $10 or £10 and sometimes available only in that denomination), you had the ability to retrieve cash, but only carried a bank document that was thought (due to features like revocability and the presence of ATMs under bank surveillance) to be relatively secure against theft. Since the tokens were later "cleared" against accounts much like checks, losing them wasn't necessarily a big deal, as something analogous to a "stop payment" was usually possible.

Unlike subway tokens, these were not coin-shaped. The most common scheme was a paper card, often the same dimensions as a modern credit card, but with punched holes that encoded the denomination and account holder information. The punched holes were also viewed as an anti-counterfeiting measure, probably not one that would hold up today, but still a roadblock to fraudsters who would have a hard time locating a keypunch and a valid account number. Manufacturers also explored some other intriguing opportunities, like the very first production cash dispenser, 1967's Barclaycash machine. This proto-ATM used punched paper tokens that were also printed in part with a Carbon-14 ink. Carbon-14 is unstable and emits beta radiation, which the ATM detected with a simple electrostatic sensor. For some reason difficult to divine the radioactive ATM card did not catch on.

For roughly the first decade of the "cash machine," they were offline devices that issued cash based on validating a token. The actual decision making, on the worthiness of a bank customer to withdraw cash, was still deferred to the teller who issued the tokens. Whether or not you would even consider this an ATM is debatable, although historical accounts generally do. They are certainly of a different breed than the modern online ATM, but they also set some of the patterns we still follow. Consider, for example, the ATMs within my lifespan that accepted deposits in an envelope. These ATMs did nothing with the envelopes other than accumulate them into a bin to go to a central processing center later on—the same way that early token-based ATMs introduced deposit boxes.

In this theory of ATM evolution, the missing link that made 1960s­–1970s ATMs so primitive was the lack of computer systems that were amenable to real-time data processing using networked peripherals. The '60s and '70s were a remarkable era in computer history, though, seeing the introduction of IBM's System/360 and System/370 line. These machines were more powerful, more flexible, and more interoperable than any before them. I think it's fair to say that, despite earlier dabbling, it was the 360/370 that truly ushered in the era of business computing. Banks didn't miss out.

One of the innovations of the System/360 was an improved and standardized architecture for the connection of peripherals to the machine. While earlier IBM models had supported all kinds of external devices, there was a lot of custom integration to make that happen. With the System/360, this took the form of "Bisync," which I might grandly call a far ancestor of USB. Bisync allowed a 360 computer to communicate with multiple peripherals connected to a common multi-drop bus, even using different logical communications protocols. While the first Bisync peripherals were "remote job entry" terminals for interacting with the machine via punched cards and teletype, IBM and other manufacturers found more and more applications in the following years.

IBM 3214 ATM

IBM had already built document processing machines that interacted with their computers. In 1971, IBM joined the credit card fray with the 2730, a "transaction" terminal that we would now recognize as a credit card reader. It used a Bisync connection to a System/360-class machine to authorize a credit transaction in real time. The very next year, IBM took the logical next step: the IBM 2984 Cash Issuing Terminal. Like many other early ATMs, the 2984 had its debut in the UK as Lloyds Bank's "Cashpoint."

The 2984 similarly used Bisync communications with a System/360. While not the very first implementation of the concept, the 2984 was an important step in ATM security and the progenitor of an important line of cryptographic algorithms. To withdraw cash, a user inserted a magnetic card that contained an account number, and then keyed in a PIN. The 2984 sent this information, over the Bisync connection, to the computer, which then responded with a command such as "dispense cash." In some cases, the computer was immediately on the other side of the wall, but it was already apparent that banks would install ATMs in remote locations controlled via leased telephone lines—and those telephone lines were not well-secured. A motivated attacker (and with cash involved, it's easy to be motivated!) could probably "tap" the ATM's network connection and issue it spurious "dispense cash" commands. To prevent this problem, and assuage the concerns of bankers who were nervous about dispensing cash so far from the branch's many controls, IBM decided to encrypt the network connection.

The concept of an encrypted network connection was not at all new; encrypted communications were widely used in the military during the second World War and the concept was well-known in the computer industry. As IBM designed the 2984, in the late '60s, encrypted computer links were nonetheless very rare. There were not yet generally accepted standards, and cryptography as an academic discipline was immature.

IBM, to secure the 2984's network connection, turned to an algorithm recently developed by an IBM researcher named Horst Feistel. Feistel, for silly reasons, had named his family of experimental block ciphers LUCIFER. For the 2984, a modified version of one of the LUCIFER implementations called DSD-11. Through a Bureau of Standards design competition and the twists and turns of industry politics, DSD-1 later reemerged (with just slight changes) as the Data Encryption Standard, or DES. We owe the humble ATM honors for its key role in computer cryptography.

The 2984 was a huge step forward. Unlike the token-based machines of the 1960s, it was pretty much the same as the ATMs we use today. To use a 2984, you inserted your ATM card and entered a PIN. You could then choose to check your balance, and then enter how much cash you wanted. The machine checked your balance in real time and, if it was high enough, debited your account immediately before coughing up money.

The 2984 was not as successful as you might expect. The Lloyd's Bank rollout was big, but very few were installed by other banks. Collective memory of the 2984 is vague enough that I cannot give a definitive reason for its limited success, but I think it likely comes down to a common tale about IBM: price and flexibility. The 2984 was essentially a semi-custom peripheral, designed for Lloyd's Bank and the specific System/360 environment already in place there. Adoption for other banks was quite costly. Besides, despite the ATM's lead in the UK, the US industry had quickly caught up. By the time the 2984 would be considered by other banks, there were several different ATMs available in the US from other manufacturers (some of them the same names you see on ATMs today). The 2984 is probably the first "modern" ATM, but since IBM spent 4-5 years developing it, it was not as far ahead of the curve on launch day as you might expect. Just a year or two later, a now-forgotten company called Docutel was dominating the US market, leaving IBM little room to fit in.

Because most other ATMs were offered by companies that didn't control the entire software stack, they were more flexible, designed to work with simpler host support. There is something of an inverse vertical integration penalty here: when introducing a new product, close integration with an existing product family makes it difficult to sell! Still, it's interesting that the 2984 used pretty much the same basic architecture as the many ATMs that followed. It's worth reflecting on the 2984's relationship with its host, a close dependency that generally holds true for modern ATMs as well.

The 2984 connected to its host via a Bisync channel (possibly over various carrier or modem systems to accommodate remote ATMs), a communications facility originally provided for remote job entry, the conceptual ancestor of IBM's later block-oriented terminals. That means that the host computer expected the peripheral to provide some input for a job and then wait to be sent the results. Remote job entry devices, and block terminals later, can be confusing when compared to more familiar Unix-family terminals. In some ways, they were quite sophisticated, with the host computer able to send configuration information like validation rules for input. In other ways, they were very primitive, capable of no real logic other than receiving computer output (which was dumped to cards, TTY, or screen) and then sending computer input (from much the same devices). So, the ATM behaved the same way.

In simple terms, the ATM's small display (called a VDU or Video Display Unit in typical IBM terminology) showed whatever the computer sent as the body of a "display" command. It dispensed whatever cash the computer indicated with a "dispense cash" command. Any user input, such as reading a card or entry of a PIN number, was sent directly to the computer. The host was responsible for all of the actual logic, and the ATM was a dumb terminal, just doing exactly what the computer said. You can think of the Cash Issuing Terminal as, well, just that: a mainframe terminal with a weird physical interface.

IBM 4700 series documentation

Most modern ATMs follow this same model, although the actual protocol has become more sophisticated and involves a great deal more XML. You can be reassured that when the ATM takes a frustratingly long time to advance to the next screen, it is at least waiting to receive the contents of that screen from a host computer that is some distance away or, even worse, in The Cloud.

Incidentally, you might wonder about the software that ran on the host computer. I believe that the IBM 2984 was designed for use with CICS, the Customer Information Control System. CICS will one day get its own article, but it launched in 1966, built specifically for the Michigan Bell to manage customer and billing data. Over the following years, CICS was extensively expanded for use in the utility and later finance industries. I don't think it's inaccurate to call CICS the first "enterprise customer relationship management system," the first voyage in an adventure that took us through Siebel before grounding on the rocks of Salesforce. Today we wouldn't think of a CRM as the system of record for depository finance institutions like banks, but CICS itself was very finance-oriented from the start (telephone companies sometimes felt like accounting firms that ran phones on the side) and took naturally to gathering transactions and posting them against customer accounts. Since CICS was designed as an online system to serve telephone and in-person customer service reps (in fact making CICS a very notable early real-time computing system), it was also a good fit for handling ATM requests throughout the day.


I put a lot of time into writing this, and I hope that you enjoy reading it. If you can spare a few dollars, consider supporting me on ko-fi. You'll receive an occasional extra, subscribers-only post, and defray the costs of providing artisanal, hand-built world wide web directly from Albuquerque, New Mexico.


Despite the 2984's lackluster success, IBM moved on. I don't think IBM was particularly surprised by the outcome, the 2984 was always a "request quotation" (e.g. custom) product. IBM probably regarded it as a prototype or pilot with their friendly customer Lloyds Bank. More than actual deployment, the 2984's achievement was paving the way for the IBM 3614 Consumer Transaction Facility.

In 1970, IBM had replaced the System/360 line with the System/370. The 370 is directly based on the 360 and uses the same instruction set, but it came with numerous improvements. Among them was a new approach to peripheral connectivity that developed into the IBM Systems Network Architecture, or SNA, basically IBM's entry into the computer networking wars of the 1970s and 1980s. While SNA would ultimately cede to IP (with, naturally, an interregnum of SNA-over-IP), it gave IBM the foundations for networked systems that are almost modern in their look and feel.

I say almost because SNA was still very much a mainframe-oriented design. An example SNA network might look like this: An S/370 computer running CICS (or one of several other IBM software packages with SNA support) is connected via channel (the high-speed peripheral bus on mainframe computers, analogous to PCI) to an IBM 3705 Communications Controller running the Network Control Program (analogous to a network interface controller). The 3705 had one or more "scanners" installed, which supported simple low-speed serial lines or fast, high-level protocols like SDLC (synchronous data link control) used by SNA. The 3705 fills a role sometimes called a "front-end processor," doing the grunt work of polling (scanning) communications lines and implementing the SDLC protocol so that the "actual computer" was relieved of these menial tasks.

At the other end of one of the SDLC links might be an IBM 3770 Data Communications System, which was superficially a large terminal that, depending on options ordered, could include a teletypewriter, card reader and punch, diskette drives, and a high speed printer. Yes, the 3770 is basically a grown-up remote job entry terminal, and the SNA/SDLC stack was a direct evolution from the Bisync stack used by the 2984. The 3770 had a bit more to offer, though: in order to handle its multiple devices, like the printer and card punch, it acted as a sort of network switch—the host computer identified the 3770's devices as separate endpoints, and the 3770 interleaved their respective traffic. It could also perform that interleaving function for additional peripherals connected to it by serial lines, which depending on customer requirements often included additional card punches and readers for data entry, or line printers for things like warehouse picking slips.

In 1973, IBM gave banks the SNA treatment with the 3600 Finance Communication System 2. A beautifully orange brochure tells us:

The IBM 3600 Finance Communication System is a family of products designed to provide the Finance Industry with remote on-line teller station operation.

System/370 computers represented an enormous investment, generally around a million dollars and more often above that point than below. They were also large and required both infrastructure and staff to support them. Banks were already not inclined to install an S/370 in each branch, so it became a common pattern to place a "full-size" computer like an S/370 in a central processing center to support remote peripherals (over leased telephone line) in branches. The 3600 was a turn-key product line for exactly this use.

An S/370 computer with a 3704 or 3705 running the NCP would connect (usually over a leased line) to a 3601 System, which IBM describes as a "programmable communications controller" although they do not seem to have elevated that phrase to a product name. The 3601 is basically a minicomputer of its own, with up to 20KB of user-available memory and diskette drive. A 3601 includes, as standard, a 9600 bps SDLC modem for connection to the host, and a 9600 bps "loop" interface for a local multidrop serial bus. For larger installations, you could expand a 3601 with additional local loop interfaces or 4800 or 9600 bps modems to extend the local loop interface to a remote location via telephone line.

In total, a 3601 could interface up to five peripheral loops with the host computer over a single interleaved SDLC link. But what would you put on those peripheral loops? Well, the 3604 Keyboard Display Unit was the mainstay, with a vacuum fluorescent display and choice of "numeric" (accounting, similar to a desk calculator) or "data entry" (alphabetic) keyboard. A bank would put one of these 3604s in front of each teller, where they could inquire into customer accounts and enter transactions. In the meantime, 3610 printers provided general-purpose document printing capability, including back-office journals (logging all transactions) or filling in pre-printed forms such as receipts and bank checks. Since the 3610 was often used as a journal printer, it was available with a take-up roller that stored the printed output under a locked cover. In fact, basically every part of the 3600 system was available with a key switch or locking cover, a charming reminder of the state of computer security at the time.

The 3612 is a similar printer, but with the addition of a dedicated passbook feature. Remember passbook savings accounts, where the bank writes every transaction in a little booklet that the customer keeps? They were still around, although declining in use, in the 1970s. The 3612 had a slot on the front where an appropriately formatted passbook could be inserted, and like a check validator or slip printer, it printed the latest transaction onto the next empty line. Finally, the 3618 was a "medium-speed" printer, meaning 155 lines per minute. A branch bank would probably have one, in the back office, used for printing daily closing reports and other longer "administrative" output.

IBM 4700 series documentation

A branch bank could carry out all of its routine business through the 3600 system, including cash withdrawals. In fact, since a customer withdrawing cash would end up talking to a teller who simply keyed the transaction into a 3604, it seems like a little more automation could make an ATM part of the system.

Enter the 3614 Consumer Transaction Facility, the first IBM ATM available as a regular catalog item. The 3614 is actually fairly obscure, and doesn't seem to have sold in large numbers. Some sources suggest that it was basically the same as the 2984, but with a general facelift and adaptations to connect to a 3601 Finance Communication Controller instead of directly to a front-end processor. Some features which were optional on the 2984, like a deposit slot, were apparently standard on 3614. I'm not even quite sure when the 3614 was introduced, but based on manual copyright dates they must have been around by 1977.

One of the reasons the 3614 is obscure is that its replacement, the IBM 3624 Consumer Transaction Facility, hit the market in 1978—probably very shortly after the 3614. The 3624 was functionally very similar to the 3614, but with maintainability improvements like convenient portable cartridges for storing cash. It also brought a completely redesigned front panel that is more similar to modern ATMs. I should talk about the front panels—the IBM ATMs won a few design awards over their years, and they were really very handsome machines. The backlit logo panel and function-specific keys of the 3624 look more pleasant to use than most modern ATMs, although they would, of course, render translation difficult.

The 3614/3624 series established a number of conventions that are still in use today. For example, they added an envelope deposit system in which the machine accepted an envelope (with cash or checks) and printed a transaction identifier on the outside of the envelope for lookup at the processing center. This relieved the user of writing up a deposit slip when using the ATM. It was also capable of not only reading but, optionally, writing to the magnetic strips on ATM cards. To the modern reader that sounds strange, but we have to discuss one of the most enduring properties of the 3614/3624: their handling of PIN numbers.

I believe the 2984 did something fairly similar, but the details are now obscure (and seem to get mixed up with its use of LUCIFER/DSD-1/DES for communications). The 3614/3624, though, so firmly established a particular approach to PIN numbers that it is now known as the 3624 algorithm. Here's how it works: the ATM reads the card number (called Primary Account Number or PAN) off of the ATM card, reads a key from memory, and then applies a convoluted cryptographic algorithm to calculate an "intermediate PIN" from it. The "intermediate PIN" is then summed with a "PIN offset" stored on the card itself, modulo 10, to produce the PIN that the user is actually expected to enter. This means that your "true" PIN is a static value calculated from your card number and a key, but as a matter of convenience, you can "set" a PIN of your choice by using an ATM that is equipped to rewrite the PIN offset on your card. This same system, with some tweaks and a lot of terminological drift, is still in use today. You will sometimes hear IBM's intermediate PIN called the "natural PIN," the one you get with an offset of 0, which is a use of language that I find charming.

Another interesting feature of the 3624 was a receipt printer—I'm not sure if it was the first ATM to offer a receipt, but it was definitely an early one. The exact mechanics of the 3624 receipt printer are amusing and the result of some happenstance at IBM. Besides its mainframes and their peripherals, IBM in the 1970s was increasingly invested in "midrange computers" or "midcomputers" that would fill in a space between the mainframe and minicomputer—and, most importantly, make IBM more competitive with the smaller businesses that could not afford IBM's mainframe systems and were starting to turn to competitors like DEC as a result. These would eventually blossom into the extremely successful AS/400 and System i, but not easily, and the first few models all suffered from decidedly soft sales.

For these smaller computers, IBM reasoned that they needed to offer peripherals like card punches and readers that were also smaller. Apparently following that line of thought to a misguided extent, IBM also designed a smaller punch card: the 96-column three-row card, which was nearly square. The only computer ever to support these cards was the very first of the midrange line, the 1969 System/3. One wonders if the System/3's limited success led to excess stock of 96-column card equipment, or perhaps they just wanted to reuse tooling. In any case, the oddball System/3 card had a second life as the "Transaction Statement Printer" on the 3614 and 3624. The ATM could print four lines of text, 34 characters each, onto the middle of the card. The machines didn't actually punch them, and the printed text ended up over the original punch fields. You could, if you wanted, actually order a 3624 with two printers: one that presented the slip to the customer, and another that retained it internally for bank auditing. A curious detail that would so soon be replaced by thermal receipt printers.

Unlike IBM's ATMs before it, and, as we will see, unlike those after it as well, the 3624 was a hit. While IBM never enjoyed the dominance in ATMs that they did in computers, and companies like NCR and Diebold had substantial market share, the 3624 was widely installed in the late 1970s and would probably be recognized by anyone who was withdrawing cash in that era. The machine had technical leadership as well: NCR built their successful ATM line in part by duplicating aspects of the 3624 design, allowing interoperability with IBM backend systems. Ultimately, as so often happens, it may have been IBM's success that became its undoing.

In 1983, IBM completely refreshed their branch banking solution with the 4700 Finance Communication System. While architecturally similar, the 4700 was a big upgrade. For one, the CRT had landed: the 4700 peripherals replaced several-line VFDs with full-size CRTs typical of other computer terminals, and conventional computer keyboards to boot. Most radically, though, the 4700 line introduced distributed communications to IBM's banking offerings. The 4701 Communications Controller was optionally available with a hard disk, and could be programmed in COBOL. Disk-equipped 4701s could operate offline, without a connection to the host, or in a hybrid mode in which they performed some transactions locally and only contacted the host system when necessary. Local records kept by the 4701 could be automatically sent to the host computer on a scheduled basis for reconciliation.

Along with the 4700 series came a new ATM: the IBM 473x Personal Banking Machines. And with that, IBM's glory days in ATMs came crashing to the ground. The 473x series was such a flop that it is hard to even figure out the model numbers, the 4732 is most often referenced but others clearly existed, including the 4730, 4731, 4736, 4737, and 4738. These various models were introduced from 1983 to 1988, making up almost a decade of IBM's efforts and very few sales. The 4732 had a generally upgraded interface, including a CRT, but a similar feature set—unsurprising, given that the 3624 had already introduced most of the features ATMs have today. It also didn't sell. I haven't been able to find any numbers, but the trade press referred to the 4732 with terms like "debacle," so they couldn't have been great.

There were a few faults in the 4732's stars. First, IBM had made the decision to handle the 4700 Finance Communication System as a complete rework of the 3600. The 4700 controllers could support some 3600 peripherals, but 4700 peripherals could not be used with 3600 controllers. Since 3600 systems were widely installed in banks, the compatibility choice created a situation where many of the 4732's prospective buyers would end up having to replace a significant amount of their other equipment, and then likely make software changes, in order to support the new machine. That might not have been so bad on its own had IBM's competitors not provided another way out.

NCR made their fame in ATMs in part by equipping their contemporary models with 3624 software emulation, making them a drop-in modernization option for existing 3600 systems. Other ATM manufacturers had pursued a path of interoperability, with multiprotocol ATMs that supported multiple hosts, and standalone ATM host products that could interoperate with multiple backend accounting systems. For customers, buying an NCR or Diebold product that would work with whatever they already used was a more appealing option than buying the entire IBM suite in one go. It also matched the development cycle of ATMs better: as a consumer-facing device, ATMs became part of the brand image of the bank, and were likely to see replacement more often than back-office devices like teller terminals. NCR offered something like a regular refresh, while IBM was still in a mode of generational releases that would completely replace the bank's computer systems.

IBM 3614 promo photo

The 4732 and its 473x compatriots became the last real IBM ATMs. After a hiatus of roughly a decade, IBM reentered the ATM market by forming a joint venture with Diebold called InterBold. The basic terms were that Diebold would sell its ATMs in the US, and IBM would sell them overseas, where IBM had generally been the more successful of the two brands. The IBM 478x series ATMs, which you might encounter in the UK for example, are the same as the Diebold 1000 series in the US. InterBold was quite successful, becoming the dominant ATM manufacturer in the US, and in 1998 Diebold bought out IBM's share.

IBM had won the ATM market, and then lost it. Along the way, they left us with so much texture: DES's origins in the ATM, the 3624 PIN format, the dumb terminal or thin client model... even InterBold, IBM's protracted exit, gave us quite a legacy: now you know the reason that so many later ATMs ran OS/2. IBM, a once great company, provided Diebold with their once great operating system. Unlike IBM, Diebold made it successful.

  1. Wikipedia calls it DTD-1 for some reason, but IBM sources consistently say DSD-1. I'm not sure if the name changed, if DSD-1 and DTD-1 were slightly different things, or if Wikipedia is simply wrong. One of the little mysteries of the universe.

  2. I probably need to explain that I am pointedly not explaining IBM model numbers, which do follow various schemes but are nonetheless confusing. Bigger numbers are sometimes later products but not always; some prefixes mean specific things, other prefixes just identify product lines.

forecourt networking

The way I see it, few parts of American life are as quintessentially American as buying gas. We love our cars, we love our oil, and an industry about as old as automobiles themselves has developed a highly consistent, fully automated, and fairly user friendly system for filling the former with the latter.

I grew up in Oregon. While these rules have since been relaxed, many know Oregon for its long identity as one of two states where you cannot pump your own gas (the other being New Jersey). Instead, an attendant, employee of the gas station, operates the equipment. Like Portland's lingering indoor gas station, Oregon's favor for "full-service" is a holdover. It makes sense, of course, that all gas stations used to be full-service.

The front part of a gas station, where the pumps are and where you pull up your car, is called the Forecourt. The practicalities of selling gasoline, namely that it is a liquid sold by volume, make the forecourt more complex than you might realize. It's a set of devices that many of us interact with on a regular basis, but we rarely think about the sheer number of moving parts and long-running need for digital communications. Hey, that latter part sounds interesting, doesn't it?

Electric vehicles are catching on in the US. My personal taste in vehicles tends towards "old" and "cheap," but EVs have been on the market for long enough that they now come in that variety. Since my daily driver is an EV, I don't pay my dues at the Circle K nearly as often as I used to. One of the odd little details of EVs is the complexity hidden in the charging system or "EVSE," which requires digital communications with the vehicle for protection reasons. As consumers across the country install EVSE in their garages, we're all getting more familiar with these devices and their price tags. We might forget that, well, handling a fluid takes a lot of equipment as well... we just don't think about it, having shifted the whole problem to a large industry of loosely supervised hazardous chemical handling facilities.

Well, I don't mean to turn this into yet another discussion of the significant environmental hazard posed by leaking underground storage tanks. Instead, we're going to talk about forecourt technology. Let's start, then, with a rough, sketchy history of the forecourt.

Illustration from Triangle MicroSystems manual

The earliest volumetric fuel dispensers used an elevated glass tank where fuel was staged and measured before gravity drained it through the hose into the vehicle tank. Operation of these pumps was very manual, with an attendant filling the calibrated cylinder with the desired amount of gas, emptying it into the vehicle, and then collecting an appropriate sum of money. As an upside, the customer could be quite confident of the amount of fuel they purchased, since they could see it temporarily stored in the cylinder.

As cars proliferated in the 1910s, a company called Gilbarco developed a fuel dispenser that actually measured the quantity of fuel as it was being pumped from storage tank to vehicle... with no intermediary step in a glass cylinder required. The original Gilbarco design involved a metal turbine in a small glass sphere; the passing fuel spun the turbine which drove a mechanical counter. In truth, the design of modern fuel dispensers hasn't changed that much, although the modern volumetric turbines are made more accurate with a positive displacement design similar to a Roots blower.

Even with the new equipment, fuel was sold in much the same way: an attendant operated the pump, read the meter, and collected payment. There was, admittedly, an increased hazard of inattentive or malicious gas stations overcharging. Volumetric dispensers thus lead to dispensers that automatically calculated the price (now generally a legal requirement) and the practice of a regulatory authority like the state or tribal government testing fuel dispensers for calibration. Well, if consumers were expected to trust the gas station, perhaps the gas station ought to trust the consumer... and these same improvements to fuel dispensers made it more practical for the motorist to simply pump their own gas.

At the genesis of self-serve gasoline, most stations operated on a postpayment model. You pulled up, pumped gas, and then went inside to the attendant to pay whatever you owed. Of course, a few unscrupulous people would omit that last step. A simple countermeasure spread in busy cities: the pumps were normally kept powered off. Before dispensing gasoline, you would have to speak with the attendant. Depending on how trustworthy they estimated you to be, they might just turn on power to the pump or they might require you to deposit some cash with them in advance. This came to be known as "prepayment," and is now so universal in th US that the "prepay only" stickers on fuel dispensers seem a bit anachronistic 1.

It's simple enough to imagine how this scheme worked, electronically. There is separate power wiring to the pumps for each dispenser (and these stations usually only had two dispensers anyway), and that wiring runs to the counter where the attendant can directly switch power. Most gas stations do use submersible pumps in the tank rather than in the actual dispenser, but older designs still had one pump per dispenser and were less likely to use submersible pumps anyway.

Soon, things became more complex. Modern vehicles have big gas tanks, and gas has become fairly expensive. What happens when a person deposits, say, $20 of "earnest cash" to get a pump turned on, and then pumps $25 worth of gas? Hopefully they have the extra $5, but the attendant doesn't know that. Besides, gas stations grew larger and it wasn't always feasible for the attendant to see the dispenser counters out the window. You wouldn't want to encourage people to just lie about the amount of gas they'd dispensed.

Gas stations gained remote control: using digital communications, fuel dispensers reported the value of their accumulators to a controller at the counter. The attendant would use the same controller to enable dispenser, potentially setting a limit at which the dispenser would automatically shut off. If you deposit $20, they enable the pump with a limit of $20. If you pay by card, they will likely authorize the card for a fixed amount (this used to routinely be $40 but has gone up for reasons you can imagine), enable the dispenser with no limit or a high limit, and then capture the actual amount after you finished dispensing 2.

And that's how gas stations worked for quite a few decades. Most gas stations that you use today still have this exact same system in operation, but it may have become buried under additional layers of automation. There are two things that have caused combinatorial complexity in modern forecourt control: first, any time you automate something, there is a natural desire to automate more things. With a digital communications system between the counter and the forecourt, you can do more than just enable the dispensers! You might want to monitor the levels in the tanks, update the price on the big sign, and sell car wash vouchers with a discount for a related fuel purchase. All of these capabilities, and many more, have been layered on to forecourt control systems through everything from serial bus accessories to REST API third party integrations.

Speaking of leaking underground storage tanks, you likely even have a regulatory obligation to monitor tank levels and ensure they balance against bulk fuel deliveries and dispenser totals. This detects leakage, but it also detects theft, still a surprisingly common problem for gas stations. Your corporate office, or your bulk fuel provider, may monitor these parameters remotely to schedule deliveries and make sure that theft isn't happening with the cooperation of the station manager. Oh, and prices, those may be set centrally as well.

The second big change is nearly universal "CRIND." This is an awkward industry acronym for everyone's favorite convenience feature, Card Reader IN Dispenser. CRIND fuel dispensers let payment card customers complete the whole authorize, dispense, and capture process right at the dispenser, without coming inside at all. CRIND is so common today that it's almost completely displaced even its immediate ancestor, "fuel island" outdoor payment terminals (OPTs) that provide a central kiosk where customers make payments for multiple dispensers. This used to be a pretty common setup in California where self-service caught on early but, based on my recent travels, has mostly evaporated there.

So you can see that we have a complicated and open-ended set of requirements for communication and automation in the fuel court: enabling and monitoring pumps, collecting card payments, and monitoring and controlling numerous accessories. Most states also require gas stations to have an intercom system so that customers can request help from the attendant inside. Third-party loyalty systems were briefly popular although, mercifully, the more annoying of them have mostly died out... although only because irritating advertising-and-loyalty technology has been better integrated into the dispensers themselves.

Further complicating things, gas station forecourts are the epitome of legacy integration. Fuel dispensers are expensive, concrete slabs are expensive, and gas stations run on thin margins. While there aren't very many manufacturers of fuel dispensers, or multi-product dispensers as they're typically called today, the industry of accessories, control systems, and replacement parts is vast. Most gas stations have accumulated several different generations of control systems and in-dispenser accessories like tree rings. New features like CRIND, chip payment, touchless payment, and "Gas Station TV" have each motivated another round of new communications protocols.

And that's how we get to our modern world, where the brochure for a typical gas station forecourt controller lists 25+ different communications protocols—and assures that you can use "any mix."

Variability between gas stations increases when you consider the differing levels of automation available. It used to be common for gas stations to use standalone pump controllers that didn't integrate with much else—when you prepaid, for example, the cashier would manually enter the pump number and prepayment limit on a separate device from the cash register.

Here in New Mexico, quite a few stations used to use the Triangle MicroSystems MPC family, a wedge-shaped box with an industrial-chic membrane keypad in grey and bright red. Operation of the MPC is pretty simple, basically pressing a pump number and then entering a dollar limit. Of course, the full set of features runs much deeper, including financial reporting and fleet fueling contracts.

This is another important dimension of the gas station control industry: fleet fueling. It used to be that gas stations were divided into two categories, consumer stations that took cash payment and "cardlock" stations that used an electronic payment system. Since cardlock stations originally relied on proprietary, closed payment agreements, they didn't sell to consumers and had different control requirements (often involving an outside payment terminal). As consumers widely adopted card payments, the lines between the two markets blurred. Modern cardlock fueling networks, like CFN and Wex, are largely just another set of payment processors. Most major gas stations participate in most major cardlock networks, just the same as they participate in most major ATM networks for lower-cost processing of debit cards.

Of course, more payment networks call for more integrations. The complexity of the modern payment situation has generally outgrown standalone controllers, and they seem to be fading away. Instead, the typical gas station today has forecourt control completely integrated into their POS system. Forecourt integration is such an important requirement that gas station convenience stores, mostly handling normal grocery-type transactions, nevertheless rely almost exclusively on dedicated gas station POS solutions. In other words, next time you buy a can of Monster and a bag of chips, the cashier most likely rings you up and takes payment through a POS solution offered by the dispenser manufacturer (like Gilbarco Passport Retail) or one of dozens of vendors that caters specifically to gas stations (including compelling names like Petrosoft). Control of fuel dispensers is just too weird of a detail to integrate into other POS platforms... or so it was thought, although things clearly get odd as Gilbarco has to implement basic kitchen video system integration for the modern truck stop.

So how does this all work technically? That's the real topic of fascination, right? Well, it's a mess and hard to describe succinctly. There are so many different options, and particularly legacy retrofit options, that one gas station will be very different from the next.

In the days of "mechanical pumps," simple designs with mechanical counters, control wiring was simple: the dispenser (really a mechanical device called a pulser) was expected to provide "one pulse per penny" on a counting circuit for dollars dispensed, which incremented a synchronized counter on the controller. For control the other way, the controller just closed relays to open "fast" or "slow" valves on the dispenser. The controller might also get a signal when a handle lever is activated, to alert the attendant that someone is trying to use a dispenser, but that was about it.

Later on, particularly as multi-product dispensers with two hoses and four rates (due to diesel and three grades) became common, wiring all the different pulse and valve circuits became frustrating. Besides, pumps with digital counters no longer needed mechanical adjustment when prices changed, allowing for completely centralized price calculation. To simplify wiring while enabling new features, fuel dispenser manufacturers introduced simple current-loop serial buses. These are usually implemented as a single loop that passes through each dispenser, carrying small packets with addressed commands or reports, usually at a pretty low speed. The dispensers designed for use with these systems are much more standalone than the older mechanical dispensers, and perform price accumulation internally, so they only needed to report periodic totals during fueling and at the end of the transaction.

An upside of these more standalone dispensers is that they made CRIND easier to implement: the payment terminal in the dispenser could locally enable the pump, including setting limits, by a direct interface to the pump controller. Still, the CRIND needed some way to actually authorize and report transactions. Solution: another current loop. Most CRIND installations involved a second, similar, but usually higher-speed serial bus that specifically handled payment processing. The CRIND terminals in such a system usually communicated with a back-office payment server using a very simple protocol, sending card information in the clear. That back-office server might be in the back of the convenience store, but it could also be remote.

As gas stations introduced CRIND, plastic card sales became a key part of the business. Card volume is much greater than cash volume at most stations, and it's known that customers will often leave rather than go inside if there is a problem with CRIND payment. So gas stations prioritized reliability of payments. To this day, if you look at the roof of many gas stations, you'll find a small parabolic antenna pointed aimlessly skywards. By the end of the 1990s, many chain gas stations used satellite networks for payment processing, either routinely (cheaper than a leased telephone line!) or as a contingency. Cisco's VSAT terminal modules for edge routers, combined with a boutique industry of Mbps-class data networks on leased transponders, made satellite a fairly inexpensive and easy-to-obtain option for handling small payment processing messages.

This arrangement of one current loop for dispenser control and one current loop for payment terminals lasted for long enough that it became a de facto wiring standard for the gas station forecourt. New construction gas stations provided conduits from the convenience store to the pumps, and those conduits were usually spec'd for an A/C power circuit (controlled, per code, by an emergency stop button) and two low-voltage data circuits. The low-voltage data circuits were particularly important because the electrical code and fire code impose specific rules on electrical systems used in proximity to flammable fluids—what's called a "hazardous environment" in the language of safety codes. Dispenser manufacturers sold specialized field interconnection enclosures that isolated the data circuits to the required safety standard, lowering the complexity of the installation in the dispensers themselves 3.

Illustration from Gilbarco manual

The next event to challenge forecourt infrastructure was the introduction of EMV chip and tap-to-pay payment cards. Many Americans will remember how fuel dispensers routinely had tap-to-pay terminals physically installed for years, even a decade, before they actually started working. Modernizing dispensers usually meant installing a new CRIND system with EMV support, but upgrades to the underlying network to support them took much longer. The problem was exactly the simplicity of the CRIND current loop design: EMV standards required that all data be encrypted (you couldn't just send card numbers to the backend in the clear as older systems did), and required larger and more numerous messages between the payment network, the terminal, and the card itself. Even if supporting EMV transactions on the serial bus was possible, most manufacturers chose not to, opting for the vastly simpler design of direct IP connectivity to each CRIND terminal.

But how do you put IP over a simple two-wire serial bus? Well, there are a lot of options, and the fuel dispenser industry chose basically all of them. There were proprietary solutions, but more common were IP networking technologies adapted to the forecourt application. Consider DSL: for a good decade, many forecourt interconnection boxes and fuel dispenser controllers supported good old fashioned DSL over the payment loop (not to be confused with DSL as in Diesel, an abbreviation also used in the fuel industry).

Bandwidth requirements increased yet further, though, with the introduction of Maria Menounos. "Forecourt media" advertising systems can deliver full video to each dispenser, a golden opportunity to pitch credit cards and monetize something called "Cheddar." While there was a long era of satellite transponders delivering analog video to chains for in-store marketing (I will one day write about WalMart TV), the "GSTV" phenomenon is newer and completely internet-based. For HD video you need a little better than the 5Mbps performance that industrial DSL systems were delivering. Enter HomePlug.


I put a lot of time into writing this, and I hope that you enjoy reading it. If you can spare a few dollars, consider supporting me on ko-fi. You'll receive an occasional extra, subscribers-only post, and defray the costs of providing artisanal, hand-built world wide web directly from Albuquerque, New Mexico.


Despite HomePlug's limited market success, it has been widely adopted in gas station forecourts. The advantage of HomePlug is clear: it dispenses with the control wiring loops entirely, providing IP communications with dispensers over the electrical supply wiring. It usually presents an almost zero-wiring upgrade, just adding HomePlug boards on both ends, so even in stations with good forecourt serial loops, dispenser upgrades often end in a switch to HomePlug.

The most interesting thing about these networks is just how modular it all still is: somewhere in your local gas station, there is a forecourt controller. Depending on the age of the system, that might be a bespoke embedded system with plug-in modules, or it might be a generic N100 Mini PC with a few serial ports and mostly IP connectivity. There is likely a forecourt interconnection box that holds not just the wiring terminals but also adapter boards that convert between various serial protocols, IP carriers, and control signals. The point of sale backend server might interact with the forecourt controller via IP, but older systems used RS-232... and systems in between might use the same logical protocol as they did with RS-232, but encapsulated in TCP. The installation manuals for all of these products include pages of wiring diagrams for each different scenario.

Next time you stop at a gas station and find the CRIND not working, think about all of that: whatever technician comes out to fix it will have their work cut out for them, just to figure out which way that gas station is set up.

  1. In more rural areas of poorer states such as my own, you will still find gas stations where the attendant turns the pump on after eyeing you. These are mostly stations that just haven't had the money to install newer equipment, which as we will see can be a big project. I have lived here for about a decade, long enough to have noticed a significant decline in the number of these stations still operating.

  2. For most payment card technologies, "authorizing" and "capturing" are separate steps that can be done with different dollar amounts. This model of paying for gas is one of the reasons why.

  3. For example, UL standards require physical separation between mains voltage wiring and plumbing components inside of fuel dispenser enclosures. The enclosures are actually rather crowded spaces, so that can turn into a real hassle—and a selling point for low-voltage-only control systems. Fuel dispenser enclosures are also required to contain a fuel fire due to leaking plumbing, which is why you see fairly heavy sheet metal construction with the sides forming chimney-like vents.

the essence of frigidity

The front of the American grocery store contains a strange, liminal space: the transitional area between parking lot and checkstand, along the front exterior and interior of the building, that fills with oddball commodities. Ice is a fixture at nearly every store, filtered water at most, firewood at some. This retail purgatory, both too early and too late in the shopping journey for impulse purchases, is mostly good only for items people know they will need as they check out. One of the standard residents of this space has always struck me as peculiar: dry ice.

Carbon dioxide ice is said to have been invented, or we might better say discovered, in the 1830s. For whatever reason, it took just about a hundred years for the substance to be commercialized. Thomas B. Slate was a son of Oregon, somehow ended up in Boston, and then realized that the solid form of CO2 was both fairly easy to produce and useful as a form of refrigeration. With an eye towards marketing, he coined the name Dry Ice—and founded the DryIce Corporation of America. The year was 1925, and word quickly spread. In a widely syndicated 1930 article, "Use of Carbon Dioxide as Ice Said to be Developing Rapidly," the Alamogordo Daily News and others reported that "the development of... 'concentrated essence of frigidity' for use as a refrigerant in transportation of perishable products, is already taxing the manufacturing facilities of the Nation... So rapidly has the use of this new form of refrigeration come into acceptance that there is not sufficient carbon dioxide gas available."

The rush to dry ice seems strange today, but we must consider the refrigeration technology of the time. Refrigerated transportation first emerged in the US during the middle of the 19th century. Train boxcars, packed thoroughly with ice, carried meat and fruit from midwestern agriculture to major cities. This type of refrigerated transportation greatly expanded the availability of perishables, and the ability to ship fruits and vegetables between growing regions made it possible, for the first time, to get some fresh fruit out of season. Still, it was an expensive proposition: railroads built extensive infrastructure to support the movement of trains loaded down with hundreds of tons of ice. The itself had to be quarried from frozen lakes, some of them purpose-built, a whole secondary seasonal transportation economy.

Mechanical refrigeration, using some kind of phase change process as we are familiar with today, came about a few decades later and found regular use on steamships by 1900. Still, this refrigeration equipment was big and awkward; steam power was a practical requirement. As the Second World War broke out, tens of thousands of refrigerated railcars and nearly 20,000 refrigerated trucks were in service—the vast majority still cooled by ice, not mechanical refrigeration.

You can see, then, the advantages of a "dryer" and lighter form of ice. The sheer weight of the ice significantly reduced the capacity of refrigerated transports. "One pound of carbon dioxide ice at 110 degrees below zero is declared to be equivalent to 16 pounds of water ice," the papers explained, for the purposes of transportation. The use of dry ice could reduce long-haul shipping costs for fruit and vegetables by 50%, the Department of Commerce estimated, and dry ice even opened the door to shipping fresh produce from the West Coast to the East—without having to "re-ice" the train multiple times along the way. Indeed, improvements in refrigeration would remake the American agricultural landscape. Central California was being irrigated so that produce could grow, and refrigeration would bring that produce to market.

1916 saw the American Production Company drilling on the dusty plains of northeastern New Mexico, a few miles south of the town of Bueyeros. On the banks of an anonymous wash, in the shadow of Mesa Quitaras, they hoped to strike oil. Instead, at about 2,000 feet, they struck something else: carbon dioxide. The well blew wide open, and spewed CO2 into the air for about a year, the production estimated at 25,000,000 cubic feet of gas per day under natural pressure. For American Production, this was an unhappy accident. They could identify no market for CO2, and a year later, they brought the well under control, only to plug and abandon it permanently.

Though the "No. 1 Bueyeros" well was a commercial failure at the time, it was not wasted effort. American Production had set the future for northeastern New Mexico. There was oil, if you looked in the right place. American Production found its own productive wells, and soon had neighbors. Whiting Brothers, once operator of charismatic service stations throughout the Southwest and famously along Route 66, had drilled their own wells by 1928. American Production became part of British Petroleum. Breitburn Production of Texas has now consolidated much of the rest of the field, and more than two million cubic feet of natural gas come from northeastern New Mexico each month.

If you looked elsewhere, there was gas—not natural gas, but CO2. Most wells in the region produced CO2 as a byproduct, and the less fortunate attempts yielded nothing but CO2. The clear, non-flammable gas was mostly a nuisance in the 1910s and 1920s. By the 1930s, though, promotion by the DryIce Corporation of America (in no small part through the Bureau of Commerce) had worked. CO2 started to be seen as a valuable commodity.

Harding County dry ice plant

The production of dry ice is deceptively simple. Given my general knowledge about producing and handling cryogenic gases, I was surprised to read of commercial-scale production with small plants in the 1930s. There is, it turns out, not that much to it. One of the chief advantages of CO2 as an industrial gas is its low critical temperature and pressure. If you take yourself back to high school chemistry, and picture a phase diagram, we can think about liquifying the CO2 gas coming out of a well. The triple point of carbon dioxide, where increasing pressure and temperature will make it a liquid, is at around -60 Celsius and 5 atmospheres. The critical point, beyond which CO2 becomes a supercritical gas-fluid hybrid, is only at 30 degrees Celsius and 72 atmospheres. In terms more familiar to us Americans, that's about 88 degrees F and 1,000 PSI.

In other words, CO2 gas becomes a liquid at temperatures and pressures that were readily achievable, even with the early stages of chemical engineering in the 1930s. With steam-powered chillers and compressors, it wasn't difficult to produce liquid CO2 in bulk. But CO2 makes the next step even more convenient: liquid CO2, released into open air, boils very rapidly. As it bubbles away, the phase change absorbs energy, leaving the remaining liquid CO2 even colder. Some of it freezes into ice, almost like evaporating seawater to extract the salt, evaporating liquid CO2 leaves a snow-like mass of flaky, loose CO2 ice. Scoop that snow up, pack it into forms, and use steam power or weight to compress it, and you have a block of the product we call dry ice.

The Bueyeros Field, as it was initially known, caught the interest of CO2 entrepreneurs in 1931. A company called Timmons Carbonic, or perhaps Southern Dry Ice Company (I suspect these to be two names for the same outfit), produced a well about a mile east, up on the mesa.

Over the next few years, the Estancia Valley Carbon Dioxide Development Company drilled a series of wells to be operated by Witt Ice and Gas. These were located in the Estancia field, further southwest and closer to Albuquerque. Witt built New Mexico's first production dry ice plant, which operated from 1932 to 1942 off of a pipeline from several nearby wells. Low pressure and difficult drilling conditions in the Estancia field limited the plant's output, so by the time it shut down Witt had already built a replacement. This facility, known as the Bueyeros plant, produced 17 tons of dry ice per day starting in 1940. It is located just a couple of miles from the original American Production well, north of Mesa Quitaras.

About 2,000' below the surface at Bueyeros lies the Tubb Sandstone, a loose aggregation of rock stuck below the impermeable Cimarron Anhydrite. Carbon dioxide can form underground through several processes, including the breakdown of organic materials under great heat and pressure (a process that creates petroleum oil as well) and chemical reactions between different minerals, especially when volcanic activity causes rapid mixing with plenty of heat. There are enough mechanisms of formation, either known or postulated, that it's hard to say where exactly the CO2 came from. Whatever its source, the gas flowed upwards underground into the sandstone, where it became trapped under the airtight layer of Anhydrite. It's still there today, at least most of it, and what stands out in particular about northeastern New Mexico's CO2 is its purity. Most wells in the Bueyeros field produce 99% pure CO2, suitable for immediate use.

Near Solano, perhaps 20 miles southwest of Bueyeros by air, the Carbonic Chemical Co built the state's largest dry ice plant. Starting operation in 1942, the plant seems to have initially gone by the name "Dioxice," immortalized as a stop on the nearby Union Pacific branch. Dioxice is an occasional synonym for Dry Ice, perhaps intended to avoid the DryIce Corporation's trademark, although few bothered. The Carbonic Chemical Plant relied on an 18 mile pipeline to bring gas from the Bueyeros field. Uniquely, this new plant used a "high pressure process." By feeding the plant only with wells producing high pressure (hundreds of PSI, as much as 500 PSI of natural pressure at some wells), the pipeline was made more efficient and reliable. Further, the already high pressure of the gas appreciably raised the temperature at which it would liquefy.

The Carbonic Chemical plant's ammonia chillers only had to cool the CO2 to -15 degrees F, liquifying it before spraying it into "snow chambers" that filled with white carbon dioxide ice. A hydraulic press, built directly into the snow chamber, applied a couple of hundred tons of force to create a solid block of dry ice weighing some 180 pounds. After a few saw cuts, the blocks were wrapped in paper and loaded onto insulated train cars for delivery to customers throughout the west—and even some in Chicago.

The main applications of CO2, a 1959 New Mexico Bureau of Mines report explains, were dry ice for shipping. Secondarily, liquid CO2 was shipped in tanks for use in carbonating beverages. Witt Ice and Gas in particular built a good business out of distributing liquid CO2 for beverage and industrial use, and for a time was a joint venture with Chicago-based nationwide gas distributor Cardox. Bueyeros's gas producers found different customers over time, so it is hard to summarize their impact, but we know some salient examples. Most beverage carbonation in mid-century Denver, and perhaps all in Albuquerque, used Bueyeros gas. Dry ice from Bueyeros was used to pack train cars passing through from California, and accompanied them all the way to the major cities of the East Coast.

By the 1950s, much of the product went to a more modern pursuit. Experimental work pursued by the military and the precursors to the Department of Energy often required precise control of low temperatures, and both solid and liquid CO2 were suitable for the purpose. In the late 1950s, Carbonic Chemical listed Los Alamos Scientific Laboratory, Sandia Laboratories, and White Sands Missile Range as their primary customers.

Bueyeros lies in Harding County, New Mexico. Harding County is home to two incorporated cities (Roy and Mosquero), a couple of railroad stops, a few highways, and hardly 650 people. It is the least populous county of New Mexico, but it's almost the size of Delaware. Harding County has never exactly been a metropolis, but it did used to be a more vital place. In the 1930s, as the CO2 industry built out, there were almost 4,500 residents. Since then, the population has declined about 20% from each census to the next.

Harding County dry ice plant

CO2 production went into a similar decline. After the war, significant improvements in refrigeration technology made mechanical refrigeration inevitable, even for road transportation. Besides, the growing chemical industry had designed many industrial processes that produced CO2 as a byproduct. CO2 for purposes like carbonation and gas blanketing was often available locally at lower prices than shipped-in well CO2, leading to a general decline in the CO2 industry.

Growing understanding of New Mexico geology and a broader reorganizing of the stratigraphic nomenclature lead the Bueyeros Field to become part of the Bravo Dome. Bravo Dome CO2 production in the 1950s and 1960s was likely supported mostly by military and weapons activity, as by the end of the 1960s the situation once again looked much like it did in the 1910s: the Bravo Dome had a tremendous amount of gas to offer, but there were few applications. The rate of extraction was limited by the size of the market. Most of the dry ice plants closed, contributing, no doubt, to the depopulation of Harding County.

The whole idea of drilling for CO2 is now rather amusing. Our modern problems are so much different: we have too much CO2, and we're producing even more without even intending to. It has at times seemed like the industry of the future will be putting CO2 down into the ground, not taking it out. What happened out in Harding County was almost the opening of Pandora's box. A hundred years ago, before there was a dry ice industry in the US, newspaper articles already speculated as to the possibility of global warming by CO2. At the time, it was often presented as a positive outcome: all the CO2 released by burning coal would warm the environment and thus reduce the need for that coal, possibly even a self-balancing problem. It's even more ironic that CO2 was extracted mostly to make things colder, given the longer-term consequences. Given all that, you would be forgiven for assuming that drilling for CO2 was a thing of the past.

The CO2 extraction industry has always been linked to the oil industry, and oil has always been boom and bust. In 1982, there were 16 CO2 wells operating in the Bravo Dome field. At the end of 1985, just three years later, there were 258. Despite the almost total collapse of demand for CO2 refrigeration, demand for liquid CO2 was up by far. It turns out that American Production hadn't screwed up in 1917, at least not if they had known a little more about petroleum engineering.

In 1972, the Scurry Area Canyon Reef Operators Committee of West Texas started an experiment, attempting industrial application of a technique first proposed in the 1950s. Through a network of non-productive oil wells in the Permian Basin, they injected liquid CO2 deep underground. The rapidly evaporating liquid raised the pressure in the overall oil formation, and even lubricated and somewhat fractured the rock, all of which increased the flow rate at nearby oil wells. A decade later, the concept was proven, and CO2 Enhanced Oil Recovery (EOR) swept across the Permian Basin.

Today, it is estimated that about 62% of the global industrial production of CO2 is injected into the ground somewhere in North America to stimulate oil production. The original SACROC system is still running, now up to 414 injection wells. There are thousands more. Every day, over two billion cubic feet of CO2 are forced into the ground, pushing back up 245,000 barrels of additional oil.

British Petroleum's acquisition of American Production proved fortuitous. BP became one of the country's largest producers of CO2, extracted from the ground around Bueyeros and transported by pipeline directly to the Permian Basin for injection. In 2000, BP sold their Bravo Dome operations to Occidental Petroleum 1. Now going by Oxy, the petroleum giant has adopted a slogan of "Zero In". That's zero as in carbon emissions.

I would not have expected to describe Occidental Petroleum as "woke," but in our contemporary politics they stand out. Oxy mentions "Diversity, Inclusion, and Belonging" on the front page of their website, which was once attractive to investors but now seems more attractive to our nation's increasingly vindictive federal government. Still, Oxy is sticking to a corporate strategy that involves acknowledging climate change as real, which I suppose counts as refreshing. From a 2025 annual report:

Oxy is building an integrated portfolio of low-carbon projects, products, technologies and companies that complement our existing businesses; leveraging our competitive advantages in CO2 EOR, reservoir management, drilling, essential chemicals and major infrastructure projects; and are designed to sustain long term shareholder value as we work to implement our Net-Zero Strategy.

Yes, Oxy has made achieving net-zero carbon a major part of their brand, and yes, this model of reducing carbon emissions relies heavily on CO2 EOR: the extraction of CO2 from the ground.

In a faltering effort to address carbon emissions, the United States has leaned heavily on the promise of Carbon Capture and Storage (CCS) technologies. The idea is to take CO2 out of the environment (potentially by separating it from the air but, more practically, by capturing it in places where it is already concentrated by industrial processes) and to put it somewhere else. Yes, this has shades of the Australian television sketch about the ship whose front fell off, but the key to "sequestration" is time. If we can put enough carbon somewhere that it will say for enough time, we can reduce the "active" greenhouse gas content of our environment. The main way we have found of doing this is injecting it deep underground. How convenient, then, that the oil industry is already looking for CO2 for EOR.

CCS has struggled in many ways, chief among them that the majority of planned CCS projects have never been built. As with most of our modern carbon reduction economy, even the CCS that has been built is, well, a little bit questionable. There is something of a Faustian bargain with fossil fuels. As we speak, about 45 megatons of CO2 are captured from industrial processes each year for CCS. Of that 45 Mt, 9 Mt are injected into dedicated CO2 sequestration projects. The rest, 80%, is purchased by the oil industry for use in EOR.

This form of CCS, in which the captured CO2 is applied to an industrial process that leads to the production of more CO2, has taken to the name CCUS. That's Carbon Capture, Utilization, and Storage. Since the majority of the CO2 injected for EOR never comes back up, it is a form of sequestration. Although the additional oil produced will generally be burned, producing CO2, the process can be said to be inefficient in terms of CO2. In other words, the CO2 produced by burning oil from EOR is less in volume than the CO2 injected to stimulate recovery of that oil.


I put a lot of time into writing this, and I hope that you enjoy reading it. If you can spare a few dollars, consider supporting me on ko-fi. You'll receive an occasional extra, subscribers-only post, and defray the costs of providing artisanal, hand-built world wide web directly from Albuquerque, New Mexico.


Mathematically, CCUS, the use of CO2 to produce oil, leads to a net reduction in released CO2. Philosophically, though, it is deeply unsatisfying. This is made all the worse by the fact that CCUS has benefited from significant government support. Outright subsidies for CCS are uncommon, although they do exist. What are quite common are grants and subsidized financing for the capital costs of CCS facilities. Nearly all CCS in the US has been built with some degree of government funding, totaling at least four billion dollars, and regulatory requirements for CCS to offset new fossil fuel plants may create a de facto electrical ratepayer subsidy for CCS. Most of that financial support, intended for our low-carbon future, goes to the oil producers.

The Permian Basin is well-positioned for CCS EOR because it produces mostly natural gas. Natural gas in its raw form, "well gas," almost always includes CO2. Natural gas processing plants separate the combustible gases from noncombustible ones, producing natural gas that has a higher energy content and burns more cleanly—but, in the process, venting large quantities of CO2 into the atmosphere. Oxy is equipping its Permian Basin natural gas plants with a capture system that collects the CO2 and compresses it for use in EOR.

The problem is that CO2 consumption for EOR has, as always, outpaced production. There aren't enough carbon capture systems to supply the Permian Basin fields, so "sequestered" CO2 is mixed with "new" CO2. Bravo Dome CO2 production has slowly declined since the 1990s, due mostly to declining oil prices. Even so, northeastern New Mexico is still full of Oxy wells bringing up CO2 by the millions of cubic feet. 218 miles of pipeline deliver Bueyeros CO2 into West Texas, and 120 miles of pipeline the other way land it in the oil fields of Wyoming. There is very nearly one producing CO2 well per person in Harding County.

Considering the totality of the system, it appears that government grants, financing incentives, and tax credits for CCS are subsidizing not only natural gas production but the extraction of CO2 itself. Whether this is progress on climate change or a complete farce depends a mathematical analysis. CO2 goes in, from several different sources; CO2 goes out, to several different dispositions. Do we remove more from the atmosphere than we end up putting back? There isn't an obvious answer.

The oil industry maintains that CCS is one of the most practical means of reducing carbon emissions, with more CO2 injected than produced and a resulting reduction in the "net CO2 impact" of the product natural gas.

As for more independent researchers, well, a paper finding that CCS EOR "cannot contribute to reductions" isn't the worst news. A 2020 literature review of reports on CCS EOR projects found that they routinely fail to account for significant secondary carbon emissions and that, due to a mix of the construction and operational realities of CCS EOR facilities and the economics of oil consumption, CCS EOR has so far produced a modest net increase in greenhouse gas emissions.

They're still out there today, drilling for carbon dioxide. The reports from the petroleum institute today say that the Permian Basin might need even more shipped in. New Mexico is an oil state; Texas gets the reputation but New Mexico has the numbers. Per-capita oil production here is significantly higher than Texas and second only to North Dakota. New Mexico now produces more oil than Old Mexico, if you will, the country to our south.

Per capita, New Mexico ranks 12th for CO2 emissions, responsible for about 1% of the nation's total. Well, I can do a bit better: for CO2 intentionally extracted from the ground, New Mexico is #3, behind only Colorado and Mississippi for total production. We produce something around 17% of the nation's supply of extracted CO2, and we even use most of it locally. I guess that's something you could put a good spin on.

  1. By this time, Armand Hammer was no longer CEO of Occidental, which is unfortunate since it deprives me of an excuse to talk at length about how utterly bizarre Armand Hammer was, and about the United World College he founded in Las Vegas, NM. Suffice it to say, for now, that Occidental had multiple connections to New Mexico.

air traffic control: the IBM 9020

Previously on Computers Are Bad, we discussed the early history of air traffic control in the United States. The technical demands of air traffic control are well known in computer history circles because of the prominence of SAGE, but what's less well known is that SAGE itself was not an air traffic control system at all. SAGE was an air defense system, designed for the military with a specific task of ground-controlled interception (GCI). There is natural overlap between air defense and air traffic control: for example, both applications require correlating aircraft identities with radar targets. This commonality lead the Federal Aviation Agency (precursor to today's FAA) to launch a joint project with the Air Force to adapt SAGE for civilian ATC.

There are also significant differences. In general, SAGE did not provide any safety functions. It did not monitor altitude reservations for uniqueness, it did not detect loss of separation, and it did not integrate instrument procedure or terminal information. SAGE would need to gain these features to meet FAA requirements, particularly given the mid-century focus on mid-air collisions (a growing problem, with increasing air traffic, that SAGE did nothing to address).

The result was a 1959 initiative called SATIN, for SAGE Air Traffic Integration. Around the same time, the Air Force had been working on a broader enhancement program for SAGE known as the Super Combat Center (SCC). The SCC program was several different ideas grouped together: a newer transistorized computer to host SAGE, improved communications capabilities, and the relocation of Air Defense Direction Centers from conspicuous and vulnerable "SAGE Blockhouses" to hardened underground command centers, specified as an impressive 200 PSI blast overpressure resistance (for comparison, the hardened telecommunication facilities of the Cold War were mostly specified for 6 or 10 PSI).

At the program's apex, construction of the SCCs seemed so inevitable that the Air Force suspended the original SAGE project under the expectation that SCC would immediately obsolete it. For example, my own Albuquerque was one of the last Air Defense Sectors scheduled for installation of a SAGE computer. That installation was canceled; while a hardened underground center had never been in the cards for Albuquerque, the decision was made to otherwise build Albuquerque to the newer SCC design, including the transistorized computer. By the same card, the FAA's interest in a civilian ATC capability, and thus the SATIN project, came to be grouped together with the SCC program as just another component of SAGE's next phase of development.

SAGE had originally been engineered by MIT's Lincoln Laboratory, then the national center of expertise in all things radar. By the late 1950s a large portion of the Lincoln Laboratory staff were working on air defense systems and specifically SAGE. Those projects had become so large that MIT opted to split them off into a new organization, which through some obscure means came to be called the MITRE Corporation. MITRE was to be a general military R&D and consulting contractor, but in its early years it was essentially the SAGE company.

The FAA contracted MITRE to deliver the SATIN project, and MITRE subcontracted software to the Systems Development Corporation, originally part of RAND and among the ancestors of today's L3Harris. For the hardware, MITRE had long used IBM, who designed and built the original AN/FSQ-7 SAGE computer and its putative transistorized replacement, the AN/FSQ-32. MITRE began a series of engineering studies, and then an evaluation program on prototype SATIN technology.

There is a somewhat tenuous claim that you will oft see repeated, that the AN/FSQ-7 is the largest computer ever built. It did occupy the vast majority of the floorspace of the four-story buildings built around it. The power consumption was around 3 MW, and the heat load required an air conditioning system at the very frontier of HVAC engineering (you can imagine that nearly all of that 3 MW had to be blown out of the building on a continuing basis). One of the major goals of the AN/FSQ-32 was reduced size and power consumption, with the lower heat load in particular being a critical requirement for installation deep underground. Of course, the "deep underground" part more than wiped out any savings from the improved technology.

From Air Defense to Air Traffic Control

By the late 1950s, enormous spending for the rapid built-out of defense systems including SAGE and the air defense radar system (then the Permanent System) had fatigued the national budget and Congress. The winds of the Cold War had once again changed. In 1959, MITRE had begun operation of a prototype civilian SAGE capability called CHARM, the CAA High Altitude Remote Monitor (CAA had become the FAA during the course of the CHARM effort). CHARM used MIT's Whirlwind computer to process high-altitude radar data from the Boston ARTCC (Air Route Traffic Control Center), which it displayed to operators while continuously evaluating aircraft movements for possible conflicts. CHARM was designed for interoperability with SAGE, the ultimate goal being the addition of the CHARM software package to existing SAGE computers. None of that would ever happen; by the time the ball dropped for the year 1960 the Super Combat Center program had been almost completely canceled. SATIN, and the whole idea of civilian air traffic control with SAGE, became blast damage.

In 1961, the Beacon Report concluded that there was an immediate need for a centralized, automated air traffic control system. Mid-air collisions had become a significant political issue, subject of congressional hearings and GAO reports. The FAA seemed to be failing to rise to the task of safe civilian ATC, a perilous situation for such a new agency... and after the cancellation of the SCCs, the FAA's entire plan for computerized ATC was gone.

During the late 1950s and 1960s, the FAA adopted computer systems in a piecemeal fashion. Many enroute control centers (ARTCCs), and even some terminal facilities, had some type of computer system installed. These were often custom software running on commodity computers, limited to tasks like recording flight plans and making them available to controllers at other terminals. Correlation of radar targets with flight plans was generally manual, as were safety functions like conflict detection.

These systems were limited in scale—the biggest problem being that some ARTCCs remained completely manual even in the late 1960s. On the upside, they demonstrated much of the technology required, and provided a test bed for implementation. Many of the individual technical components of ATC were under development, particularly within IBM and Raytheon, but there was no coordinated nationwide program. This situation resulted in part from a very intentional decision by the FAA to grant more decision making power to its regional offices, a concept that was successful in some areas but in retrospect disastrous in others. In 1967, the Department of Transportation was formed as a new cabinet-level executive department. The FAA, then the Federal Aviation Agency, was reorganized into DOT and renamed the Federal Aviation Administration. The new Administration had a clear imperative from both the President and Congress: figure out air traffic control.

In the late 1960s, the FAA coined a new term: the National Airspace System 1, a fully standardized, nationwide system of procedures and systems that would safely coordinate air traffic into the indefinite future. Automation of the NAS began with NAS Enroute Stage A, which would automate the ARTCCs that handled high-altitude aircraft on their way between terminals. The remit was more or less "just like SAGE but with the SATIN features," and when it came to contracting, the FAA decided to cut the middlemen and go directly to the hardware manufacturer: IBM.

The IBM 9020

It was 1967 by the time NAS Enroute Stage A was underway, nearly 20 years since SAGE development had begun. IBM would thus benefit from considerable advancements in computer technology in general. Chief among them was the 1965 introduction of the System/360. S/360 was a milestone in the development of the computer: a family of solid-state, microcoded computers with a common architecture for software and peripheral interconnection. S/360's chief designer, Gene Amdahl, was a genius of computer architecture who developed a particular interest in parallel and multiprocessing systems. Soon after the S/360 project, he left IBM to start the Amdahl Corporation, briefly one of IBM's chief competitors. During his short 1960s tenure at IBM, though, Amdahl contributed IBM's concept of the "multisystem."

A multisystem consisted of multiple independent computers that operated together as a single system. There is quite a bit of conceptual similarity between the multisystem and modern concepts like multiprocessing and distributed computing, but remember that this was the 1960s, and engineers were probing out the possibilities of computer-to-computer communication for the first time. Some of the ideas of S/360 multisystems read as strikingly modern and prescient of techniques used today (like atomic resource locking for peripherals and shared memory), while others are more clearly of their time (the general fact that S/360 multisystems tended to assign their CPUs exclusively to a specific task).

One of the great animating tensions of 1960s computer history is the ever-moving front between batch processing systems and realtime computing systems. IBM had its heritage manufacturing unit record data processing machines, in which a physical stack of punched cards was the unit of work, and input and output ultimately occurred between humans on two sides of a service window. IBM computers were designed around the same model: a "job" was entered into the machine, stored until it reached the end of the queue, processed, and then the output was stored for later retrieval. One could argue that all computers still work this way, it's just process scheduling, but IBM had originally envisioned job queuing times measured in hours rather than milliseconds.

The batch model of computing was fighting a battle on multiple fronts: rising popularity of time-sharing systems meant servicing multiple terminals simultaneously and, ideally, completing simple jobs interactively while the user waited. Remote terminals allowed clerks to enter and retrieve data right where business transactions were taking place, and customers standing at ticket counters expected prompt service. Perhaps most difficult of all, fast-moving airplanes and even faster-moving missiles required sub-second decisions by computers in defense applications.

IBM approached the FAA's NAS Enroute Stage A contract as one that required a real-time system (to meet the short timelines necessary in air traffic control) and a multisystem (to meet the FAA's exceptionally high uptime and performance requirements). They also intended to build the NAS automation on an existing, commodity architecture to the greatest extent possible. The result was the IBM 9020.

IBM 9020 concept diagram

The 9020 is a fascinating system, exemplary of so many of the challenges and excitement of the birth of the modern computer. On the one hand, a 9020 is a sophisticated, fault-tolerant, high-performance computer system with impressive diagnostic capabilities and remarkably dynamic resource allocation. On the other hand, a 9020 is just six to seven S/360 computers married to each other with a vibe that is more duct tape and bailing wire than aerospace aluminum and titanium.

The first full-scale 9020 was installed in Jacksonville, Florida, late in 1967. Along with prototype systems at the FAA's experimental center and at Raytheon (due to the 9020's close interaction with Raytheon-built radar systems), the early 9020 computers served as development and test platforms for a complex and completely new software system written mostly in JOVIAL. JOVIAL isn't a particularly well-remembered programming language, based on ALGOL with modifications to better suit real-time computer systems. The Air Force was investing extensively in real-time computing capabilities for air defense and JOVIAL was, for practical purposes, an Air Force language.

It's not completely clear to me why IBM selected JOVIAL for enroute stage A, but we can make an informed guess. There were very few high-level programming languages that were suitable for real-time use at all in the 1960s, and JOVIAL had been created by Systems Development Corporation (the original SAGE software vendor) and widely used for both avionics and air defense. The SCC project, if it had been completed, would likely have involved rewriting large parts of SAGE in JOVIAL. For that reason, JOVIAL had been used for some of the FAA's earlier ATC projects including SATIN. At the end of the day, JOVIAL was probably an irritating (due to its external origin) but obvious choice for IBM.

More interesting than the programming language is the architecture of the 9020. It is, fortunately, well described in various papers and a special issue of IBM Systems Journal. I will simplify IBM's description of the architecture to be more legible to a modern reader who hasn't worked for IBM for a decade.

Picture this: seven IBM S/360 computers, of various models, are connected to a common address and memory bus used for interaction with storage. These computers are referred to as Compute Elements and I/O Control Elements, forming two pools of machines dedicated to two different sets of tasks. Also on that bus are something like 10 Storage Elements, specialized machines that function like memory controllers with additional features for locking, prioritization, and diagnostics. These Storage Elements provide either 131 kB or about 1 MB of memory each; due to various limitations the maximum possible memory capacity of a 9020 is about 3.4 MB, not all of which is usable at any given time due to redundancy.

At least three Compute Elements, and up to four, serve as the general-purpose part of the system where the main application software is executed. Three I/O Control Elements existed mostly as "smart" controllers for peripherals connected to their channels, the IBM parlance for what we might now call an expansion bus.

The 9020 received input from a huge number of sources (radar digitizers, teletypes at airlines and flight service stations, controller workstations, other ARTCCs). Similarly, it sent output to most of these endpoints as well. All of these communications channels, with perhaps the exception of the direct 9020-to-9020 links between ARTCCs, were very slow even by the standards of the time. The I/O Control Elements each used two of their high-speed channels for interconnection with display controllers (discussed later) and tape drives in the ARTCC, while the third high-speed channel connected to a multiplexing system called the Peripheral Adapter Module that connected the computer to dozens of peripherals in the ARTCC and leased telephone lines to radar stations, offices, and other ATC sites.

Any given I/O Control Element had a full-time job of passing data between peripherals and storage elements, with steps to validate and preprocess data. In addition to ATC-specific I/O devices, the Control Elements also used their Peripheral Adapter Modules to communicate with the System Console. The System Console is one of the most unique properties of the 9020, and one of the achievements of which IBM seems most proud.

Multisystem installations of S/360s were not necessarily new, but the 9020 was one of the first attempts to present a cluster of S/360s as a single unified machine. The System Console manifested that goal. It was, on first glance, not that different from the operator's consoles found on each of the individual S/360 machines. It was much more than that, though: it was the operator's console for all seven of them. During normal 9020 operation, a single operator at the system console could supervise all components of the system through alarms and monitors, interact with any element of the system via a teletypewriter terminal, and even manually interact with the shared storage bus for troubleshooting and setup. The significance of the System Console's central control was such that the individual S/360 machines, when operating as part of the Multisystem, disabled their local operator's consoles entirely.

One of the practical purposes of the System Console was to manage partitioning of the system. A typical 9020 had three compute elements and three I/O control elements, an especially large system could have a fourth compute element for added capacity. The system was sized to produce 50% redundancy during peak traffic. In other words, a 9020 could run the full normal ATC workload on just two of the compute elements and two of the I/O control elements. The remaining elements could be left in a "standby" state in which the multisystem would automatically bring them online if one of the in-service elements failed, and this redundancy mechanism was critical to meeting the FAA's reliability requirement. You could also use the out-of-service elements for other workloads, though.

For example, you could remove one of the S/360s from the multisystem and then operate it manually or run "offline" software. An S/360 operating this way is described as "S/360 compatibility mode" in IBM documentation, since it reduces the individual compute element to a normal standalone computer. IBM developed an extensive library of diagnostic tools that could be run on elements in standby mode, many of which were only slight modifications of standard S/360 tools. You could also use the offline machines in more interesting ways, by bringing up a complete ATC software chain running on a smaller number of elements. For training new controllers, for example, one compute element and one I/O control element could be removed from the multisystem and used to form a separate partition of the machine that operated on recorded training data. This partition could have its own assigned peripherals and storage area and largely operate as if it were a complete second 9020.

Multisystem Architecture

You probably have some questions about how IBM achieved these multisystem capabilities, given the immature state of operating systems design at the time. The 9020 used an operating system derived from OS/360 MVT, an advanced form of OS/360 with a multitasking capability that was state-of-the-art in the mid-1960s but nonetheless very limited and with many practical problems. Fortunately, IBM was not exactly building a general-purpose machine, but a dedicated system with one function. This allowed the software to be relatively simple.

The core of the 9020 software system is called the control program, which is similar to what we would call a scheduler today. During routine operation of the 9020, any of the individual computers might begin execution of the control program at any time—typically either because the computer's previous task was complete (along the lines of cooperative multitasking) or because an interrupt had been received (along the lines of preemptive multitasking). To meet performance and timing requirements, especially with the large number of peripherals involved, the 9020 extensively used interrupts which could either be generated and handled within a specific machine or sent across the entire multisystem bus.

The control program's main function is to choose the next task to execute. Since it can be started on any machine at any time, it must be reentrant. The fact that all of the machines have shared memory simplifies the control program's task, since it has direct access to all of the running programs. Shared memory also added the complexity that the control program has to implement locking and conflict detection to ensure that it doesn't start the same task on multiple machines at once, or start multiple tasks that will require interaction with the same peripheral.

You might wonder about how, exactly, the shared memory was implemented. The storage elements were not complete computers, but did implement features to prevent conflicts between simultaneous access by two machines, for example. By necessity, all of the memory management used for the multisystem is quite simple. Access conflicts were resolved by choosing one machine and making the other wait until the next bus cycle. Each machine had a "private" storage area, called the preferential storage area. A register on each element contained an offset added to all memory addresses that ensured the preferential storage areas did not overlap. Beyond that, all memory had to be acquired by calling system subroutines provided by the control program, so that the control program could manage memory regions. Several different types of memory allocations were available for different purposes, ranging from arbitrary blocks for internal use by programs to shared buffer areas that multiple machines could use to queue data for an I/O Control Element to send elsewhere.

At any time during execution of normal programs, an interrupt could be generated indicating a problem with the system (IBM gives the examples of a detection of high temperature or loss of A/C power in one of the compute elements). Whenever the control program began execution, it would potentially detect other error conditions using its more advanced understanding of the state of tasks. For example, the control program might detect that a program has exited abnormally, or that allocation of memory has failed, or an I/O operation has timed out without completing. All of these situations constitute operational errors, and result in the Control Program ceding execution to the Operational Error Analysis Program or OEAP.

The OEAP is where error-handling logic lives, but also a surprising portion of the overall control of the multisystem. The OEAP begins by performing self-diagnosis. Whatever started the OEAP, whether the control program or a hardware interrupt, is expected to leave some minimal data on the nature of the failure in a register. The OEAP reads that register and then follows an automated data-collection procedure that could involve reading other registers on the local machine, requesting registers from other machines, and requesting memory contents from storage elements. Based on the diagnosis, the OEAP has different options: some errors are often transient (like communications problems), so the OEAP might do nothing and simply allow the control program to start the task again.

On the other hand, some errors could indicate a serious problem with a component of the system, like a storage element that is no longer responding to read and write operations in its address range. In those more critical cases, the OEAP will rewrite configuration registers on the various elements of the system and then reset them—and on initialization, the configuration registers will cause them to assume new states in terms of membership in the multisystem. In this way, the OEAP is capable of recovering from "solid" hardware failures by simply reconfiguring the system to no longer use the failed hardware. Most of the time, that involves changing the failed element's configuration from "online" to "offline," and choosing an element in "online standby" and changing its configuration to "online." During the next execution of the control program, it will start tasks on the newly "online" element, and the newly "offline" element may as well have never existed.

The details are, of course, a little more complex. In the case of a failed storage element, for example, there's a problem of memory addresses. The 9020 multisystem doesn't have virtual memory in the modern sense, addresses are more or less absolute (ignoring some logical addressing available for specific types of memory allocations). That means that if a storage element fails, any machines which have been using memory addresses within that element will need to have a set of registers for memory address offsets reconfigured and then execution reset. Basically, by changing offsets, the OEAP can "remap" the memory in use by a compute or I/O control element to a different storage element. Redundancy is also built in to the software design to make these operations less critical. For example, some important parts of memory are stored in duplicate with an offset between the two copies large enough to ensure that they will never fall on the same physical storage element.

So far we have only really talked about the "operational error" part, though, and not the "analysis." In the proud tradition of IBM, the 9020 was designed from the ground up for diagnosis. A considerable part of IBM's discussion of the architecture of the Control Program, for example, is devoted to its "timing analysis" feature. That capability allows the Control Program to commit to tape a record of when each task began execution, on which element, and how long it took. The output is a set of start and duration times, with task metadata, remarkably similar to what we would now call a "span" in distributed tracing. Engineers used these records to analyze the performance of the system and more accurately determine load limits such as the number of in-air flights that could be simultaneously tracked. Of course, details of the time analysis system remind us that computers of this era were very slow: the resolution on task-start timestamps was only 1/2 second, although durations were recorded at the relatively exact 1/60th of a second.

That was just the control program, though, and the system's limited ability to write timing analysis data (which, even on the slow computers, tended to be produced faster than the tape drives could write it and so had to fit within a buffer memory area for practical purposes) meant that it was only enabled as needed. The OEAP provided long-term analysis of the performance of the entire machine. Whenever the OEAP was invoked, even if it determined that a problem was transient or "soft" and took no action, it would write statistical records of the nature of the error and the involved elements. When the OEAP detected an unusually large number of soft errors from the same physical equipment, it would automatically reconfigure the system to remove that equipment from service and then generate an alarm.

Alerts generated by the OEAP were recorded by a printer connected to the System Console, and indicated by lights on the System Console. A few controls on the System Console allowed the operator manual intervention when needed, for example to force a reconfiguration.

One of the interesting aspects of the OEAP is where it runs. The 9020 multisystem is truly a distributed one in that there is no "leader." The control program, as we have discussed, simply starts on whichever machine is looking for work. In practice, it may sometimes run simultaneously on multiple machines, which is acceptable as it implements precautions to prevent stepping on its own toes.

This model is a little more complex for the OEAP, because of the fact that it deals specifically with failures. Consider a specific failure scenario: loss of power. IBM equipped each of the functional components of the 9020 with a battery backup, but they only rate the battery backup for 5.5 seconds of operation. That isn't long enough for a generator to reliably pick up the load, so this isn't a UPS as we would use today. It's more of a dying gasp system: the computer can "know" that it has lost power and continue to operate long enough stabilize the state of the system for faster resumption.

When a compute element or I/O control element loses power, an interrupt is generated within that single machine that starts the OEAP. The OEAP performs a series of actions, which include generating an interrupt across the entire system to trigger reconfiguration (it is possible, even likely given the physical installations, that the power loss is isolated to the single machine) and resetting task states in the control program so that the machine's tasks can be restarted elsewhere. The OEAP also informs the system console and writes out records of what has happened. Ideally, this all completes in 5.5 seconds while battery power remains reliable.

In the real world, there could be problems that lead to slow OEAP execution, or the batteries could fail to make it for long enough, or for that matter the compute element could encounter some kind of fundamentally different problem. The fact that the OEAP is executing on a machine means that something has gone wrong, and so until the OEAP completes analysis, the machine that it is running on should be considered suspect. The 9020 resolves this contradiction through teamwork: beginning of OEAP execution on any machine in the total system generates an interrupt that starts the OEAP on other machines in a "time-down" mode. The "time-down" OEAPs wait for a random time interval and then check the shared memory to see if the original OEAP has marked its execution as completed. If not, the first OEAP to complete its time-down timer will take over OEAP execution and attempt to complete diagnostics from afar. That process can, potentially, repeat multiple times: in some scenario where two of the three compute elements have failed, the remaining third element will eventually give up on waiting for the first two and run the OEAP itself. In theory, someone will eventually diagnose every problem. IBM asserts that system recovery should always complete within 30 seconds.

Let's work a couple of practical examples, to edify our understanding of the Control Program and OEAP. Say that a program running on a Compute Element sets up a write operation for an I/O Control Element, which formats and sends the data to a Peripheral Adapter Module which attempts to send it to an offsite peripheral (say an air traffic control tower teleprinter) but fails. A timer that tracks the I/O operation will eventually fail, triggering the OEAP on the I/O control element running the task. The OEAP reads out the error register on its new home, discovers that it is an I/O problem related to a PAM, and then speaks over the channel to request the value of state registers from the PAM. These registers contain flags for various possible states of peripheral connections, and from these the OEAP can determine that sending a message has failed because there was no response. These types of errors are often transient, due to telephone network trouble or bad luck, so the OEAP increments counters for future reference, looks up the application task that tried to send the message and changes its state to incomplete, clears registers on the PAM and I/O control element, and then hands execution back to the Control Program. The Control Program will most likely attempt to do the exact same thing over again, but in the case of a transient error, it'll probably work this time.

Consider a more severe case, where the Control Program starts a task on a Compute Element that simply never finishes. A timer runs down to detect this condition, and an interrupt at the end of the timer starts the Control Program, which checks the state and discovers the still-unfinished task. Throwing its hands in the air, the Control Program sets some flags in the error register and hands execution to the OEAP. The OEAP starts on the same machine, but also interrupts other machines to start the OEAP in time-down mode in case the machine is too broken to complete error handling. It then reads the error register and examines other registers and storage contents. Determining that some indeterminate problem has occurred with the Compute Element, the OEAP triggers what IBM confusingly calls a "logout" but we might today call a "core dump" (ironically an old term that was more appropriate in this era). The "logout" entails copying the contents of all of the registers and counters to the preferential storage area and then directing, via channel, one of the tape drives to write it all to a tape kept ready for this purpose—the syslog of its day. Once that's complete, the OEAP will reset the Compute Element and hand back to the Control Program to try again... unless counters indicate that this same thing has happened recently. In that case, the OEAP will update the configuration register on the running machine to change its status to offline, and choose a machine in online-standby. It will write to that machine's register, changing its status to online. A final interrupt causes the Control Program to start on both machines, taking them into their new states.

Lengthy FAA procedure manuals described what would happen next. These are unfortunately difficult to obtain, but from IBM documentation we know that basic information on errors was printed for the system operator. The system operator would likely then use the system console to place the suspicious element in "test" mode, which completely isolates it to behave more or less like a normal S/360. At that point, the operator could use one of the tape drives attached to the problem machine to load IBM's diagnostic library and perform offline troubleshooting. The way the tape drives are hooked up to specific machines is important; in fact, since the OEAP is fairly large, it is only stored in one copy on one Storage Element. The 9020 requires that one of the tape drives always have a "system tape" ready with the OEAP itself, and low-level logic in the elements allows the OEAP to be read from the ready-to-go system tape in case the storage element that contains it fails to respond.

A final interesting note about the OEAP is a clever optimization called "problem program mode." During analysis and handling of an error, the OEAP can decide that the critical phase of error handling has ended and the situation is no longer time sensitive. For example, the OEAP might decide that no action is required except for updating statistics, which can comfortably happen with a slight delay. These lower-priority remaining tasks can be added to memory as "normal" application tasks, to be run by the Control Program like any other task after error handling is complete. Think of it as a deferral mechanism, to avoid the OEAP locking up a machine for any longer than necessary.

For the sake of clarity, I'll note again an interesting fact by quoting IBM directly: "OEAP has sole responsibility for maintaining the system configuration." The configuration model of the 9020 system is a little unintuitive to me. Each machine has its own configuration register that tells it what its task is and whether it is online or offline (or one of several states in between like online-standby). The OEAP reconfigures the system by running on any one machine and writing the configuration registers of both the machine it's running on, and all of the other machines via the shared bus. Most reconfigurations happen because the OEAP has detected a problem and is working around it, but if the operator manually reconfigures the system (for example to facilitate testing or training), they also do so by triggering an interrupt that leads the Control Program to start the OEAP. The System Console has buttons for this, along with toggles to set up a sort of "main configuration register" that determines how the OEAP will try to set up the system.

The Air Traffic Control Application

This has become a fairly long article by my norms, and I haven't even really talked about air traffic control that much. Well, here it comes: the application that actually ran on the 9020, which seems to have had no particular name, besides perhaps Central Computing Complex (although this seems to have been adopted mostly to differentiate it from the Display Complex, discussed soon).

First, let's talk about the hardware landscape of the ARTCC and the 9020's role. An ARTCC handles a number of sectors, say around 30. Under the 9020 system, each of these sectors has three controllers associated with it, called the R, D, and A controllers. The R controller is responsible for monitoring and interpreting the radar, the D controller for managing flight plans and flight strips, and the A controller is something of a generalist who assists the other two. The three people sit at something like a long desk, made up of the R, D, and A consoles side by side.

Sector control consoles

The R console is the most recognizable to modern eyes, as its centerpiece is a 22" CRT plan-view radar display. The plan-view display (PVD) of the 9020 system is significantly more sophisticated than the SAGE PVD on which it is modeled. Most notably, the 9020 PVD is capable of displaying text and icons. No longer does a controller use a light gun to select a target for a teleprinter to identify; the "data blocks" giving basic information on a flight were actually shown on the PVD next to the radar contact. A trackball and a set of buttons even allowed the controller to select targets to query for more information or update flight data. This was quite a feat of technology even in 1970, and in fact one that the 9020 was not capable of. Well, it was actually capable of it, but not initially.

The original NAS stage A architecture separated the air traffic control data function and radar display function into two completely separate systems. The former was contracted to IBM, the latter to Raytheon, due to their significant experience building similar systems for the military. Early IBM 9020 installations sat alongside a Raytheon 730 Display Channel, a very specialized system that was nearly as large as the 9020. The Display Channel's role was to receive radar contact data and flight information in digital form from the 9020, and convert it into drawing instructions sent over a high-speed serial connection to each individual PVD. A single Display Channel was responsible for up to 60 PVDs. Further complicating things, sector workstations were reconfigurable to handle changing workloads. The same sector might be displayed on multiple PVDs, and where sectors met, PVDs often overlapped so the same contact would be visible to controllers for both sectors. The Display Channel had a fairly complex task to get the right radar contacts and data blocks to the right displays, and in the right places.

Later on, the FAA opted to contract IBM to build a slightly more sophisticated version of the Display Channel that supported additional PVDs and provided better uptime. To meet that contract, IBM used another 9020. Some ARTCCs thus had two complete 9020 systems, called the Central Computer Complex (CCC) and the Display Channel Complex (DCC).

The PVD is the most conspicuous part of the controller console, but there's a lot of other equipment there, and the rest of it is directly connected to the 9020 (CCC). At the R controller's position, a set of "hotkeys" allow for quickly entering flight data (like new altitudes) and a computer readout device (CRD), a CRT that displays 25x20 text for general output. For example, when a controller selects a target on the PVD to query for details, that query is sent to the 9020 CCC which shows the result on the R controller's CRD above the PVD.

At the D controller's position, right next door, a large rack of slots for flight strips (small paper strips used to logically organize flight clearances, still in use today in some contexts) surrounds the D controller's CRD. The D controller also has a Computer Entry Device, or CED, a specialized keyboard that allows the D controller to retrieve and update flight plans and clearances based on requests from pilots or changes in the airspace situation. To their right, a modified teleprinter is dedicated to producing the flight strips that they arrange in front of them. Flight strips are automatically printed when an aircraft enters the sector, or when the controller enters changes. The A controller's position to the right of the flight strip printer is largely the same as the D controller's position, with another CRD and CED that operate independently from the D controller's—valuable during peak traffic.

While controller consoles are the most visible peripherals of the system, they're far from the only ones. Each 9020 system had an extensive set of teletypewriter circuits. Some of these were local; for example, the ATC supervisor had a dedicated TTY where they could not only interact with flight data (to assist a sector controller for example) but also interact with the status of the NAS automation itself (for example to query the status of a malfunctioning radar site and then remove it from use for PVDs).

Since the 9020 was also the locus of flight planning, TTYs were provided in air traffic control towers, terminal radar facilities, and even the dispatch offices of airlines. These allowed flight plans to be entered into the 9020 before the aircraft was handed off to enroute control. Flight service stations functioned more or less as the dispatch offices for general aviation, so they were similarly equipped with TTYs for flight plan management. In many areas, military controllers at air defense sectors were also provided with TTYs for convenient access to flight plans. Not least of all, each 9020 had high-speed leased lines to its neighboring 9020s. Flights passing from one ARTCC to the next had their flight strip "digitally passed" by transmission from one 9020 to the next.

A set of high-speed line printers connected to the 9020 printed diagnostic data as well as summary and audit reports on air traffic. Similar audit data, including a detailed record of clearances, was written to tape drives for future reference.

To organize the whole operation, IBM divided the software architecture of the system into the "supervisor state" and the "problem state." These are reasonably analogous to kernel and user space today, and "problem" is meant as in "the problem the computer solves" rather than "a problem has occurred." The Control Program and OEAP run in the supervisor state, everything else runs after the Control Program has set up a machine in the Problem State and started a given program.

IBM organized the application software into five modules, which they called the five Programs. These are Input Processing, Flight Processing, Radar Processing, Output Processing, and Liaison Management. Most of these are fairly self-explanatory, but the list reveals the remarkably asynchronous design of the system. Consider an example, we'll say a general aviation flight taking off from an airport inside of one of the ARTCC's sectors.

The pilot first contacts a Flight Service Station, which uses their TTY to enter a flight plan into the 9020. Next, the pilot interacts with the control tower, which in the process of giving a takeoff clearance uses their TTY to inform the 9020 that the flight plan is active. They may also update the flight plan with the aircraft's planned movements shortly after takeoff, if they have changed due to operating conditions. The Input Processing program handles all of these TTY inputs, parsing them into records stored on a Storage Element. In case any errors occur, like an invalid entry, those are also written to the Storage Element, where the Output Processing program picks them up and sends an appropriate message to the originating TTY. IBM notes that there were, as originally designed, about 100 types of input messages parsed by the input processing program.

As the aircraft takes off, it is detected by a radar site (such as a Permanent System radar or Air Route Surveillance Radar) which digitally encodes the radar contact (a Raytheon system) for transmission to the 9020. The Radar Processing program receives these messages, converts radial radar coordinates to the XY plane used by the system, correlates contacts with similar XY positions from multiple radar sites into a single logical contact, and computes each contact's apparent heading and speed to extrapolate future positions. Complicating things, the 9020 went into service during the development of secondary surveillance radar, also known as the transponder system 2. On appropriately equipped aircraft, the transponder provides altitude. The Radar Processing system makes an altitude determination on each aircraft, a slightly more complicated task than you might expect as, at the time, only some radar systems and some transponders provided altitude information. The Radar Processing program thus had to track if it had altitude information at all and, if so, where from. In the mean time, the Radar Processing program tracked the state of the radar sites and reported any apparent trouble (such as loss of data or abnormal data) to the supervisor.


I put a lot of time into writing this, and I hope that you enjoy reading it. If you can spare a few dollars, consider supporting me on ko-fi. You'll receive an occasional extra, subscribers-only post, and defray the costs of providing artisanal, hand-built world wide web directly from Albuquerque, New Mexico.


The Flight Processing program periodically evaluates all targets from the Radar Processing program against all filed flight plans, correlating radar targets with filed flight plans, calculating navigational deviations, and predicting future paths. Among other outputs, the Flight Processing program generated up-to-date flight strips for each aircraft and predicted their arrival times at each flight plan fix for controller's planning purposes. The Flight Processing program hosted a set of rules used for safety protections, such as separation distances. This capability was fairly minimal during the 9020's original development, but was enhanced over time.

The Output Processing program had two key roles. First, it handled data that was specifically queued for it because of a reactive need to send data to a given output. For example, if someone made a data entry error or a controller queried for a specific aircraft's flight plan, the Input Processing program placed the resulting data in memory, where the Output Processing program would "find it" to format and send to the correct device. The Output Processing program also continuously prepared common outputs like flight data blocks and radar station status messages that were formatted once to a common memory buffer to be sent to many devices in bulk. For example, a new flight strip for an aircraft would be formatted and stored once, and then sent in sequence to every controller position with a relation to that aircraft.

Legacy

The 9020 is just one corner of the evolution of air traffic control during the 1960s and 1970s, a period that also saw the introduction of secondary radar for civilian flights and the first effort to automate the role of flight service stations. These topics quickly spiral out into others: unlike the ARTCCs of the time, the flight service stations dealt extensively with weather and interacted with both FAA and National Weather Service teletype networks and computer systems. An early effort to automate the flight service function involved the use of a teletext system originally developed for agricultural use as a "flight briefing terminal." That wasn't the agricultural teletext system in Kentucky that I discussed, but a different one, in Kansas. Fascinating things everywhere you look!

This article has already become long, though, and so we'll have to save plenty for later. To round things out, let's consider the fate of the 9020. SAGE is known not only for its pioneering role in the computing art, but because of its remarkably long service life, roughly from 1958 to 1984. The 9020 was almost 20 years younger than SAGE, and indeed outlived it, but not by much. In 1982, IBM announced the IBM 3083, a newer implementation of the Enhanced S/370 architecture that was directly descended from S/360 but with greatly improved I/O capabilities. In 1986, the FAA accepted a new 3083-based system called "HOST." Over the following three years, all of the 9020 CCCs were replaced by HOST systems.

The 9020 was not to be forgotten so easily, though. First, the HOST project was mostly limited to hardware modernization or "rehosting." The HOST 3083 computers ran most of the same application code as the original 9020 system, incorporating many enhancements made over the intervening decades.

Second, there is the case of the Display Channel Complex. Once again, because of the complexity of the PVD subsystem the FAA opted to view it as a separate program. While an effort was started to replace the 9020 DCCs alongside the 9020 CCCs, it encountered considerable delays and was ultimately canceled. The 9020 DCCs remained in service controlling PVDs until the ERAM Stage A project replaced the PVD system entirely in the 1990s.

While IBM's efforts to market the 9020 overseas generally failed, a 9020 CCC system (complete with simplex test machine) was sold to the UK Civil Aviation Authority for use in the London Air Traffic Centre. This 9020 remained in service until 1990, and perhaps because of its singularity and unusually long life, it is better remembered as a historic object. There are photos.

Figure from study of typical 9020 message volume

  1. The term National Airspace System (NAS) is still in use today, but is now more of a concept than a physical thing. The NAS is the totality of the regulations, procedures, and communications systems used in air traffic control. During the NAS Enroute Stage A project, IBM and the FAA both seem to have used "NAS" to describe the ARTCC computer system as a physical object, although I think it was debatable even then whether or not this was an appropriate use of the term. One of the difficulties in researching the history of civilian air traffic control is that the FAA seems to have been particularly bad about names. "NAS Enroute Stage A" is not very wieldy but is one of the only terms that unambiguously refers to the late-'60s, early-'70s IBM 9020-based ARTCC system, and even then it is confusing with the later enroute automation modernization (ERAM) program, complete with its own stage A. I refer to the ARTCC automation system simply as "the IBM 9020" even though this is incorrect (consider for example that the complete system often involved a display subsystem built by Raytheon), and you will find contemporary references to it as "NAS," "NAS stage A," "NAS automation," etc.

  2. One of the responsibilities of the 9020 was the assignment of non-overlapping transponder codes as well.

Flock and Urban Surveillance

Some years ago, I had a frustrating and largely fruitless encounter with the politics of policing. As a member of an oversight commission, I was particularly interested in the regulation of urban surveillance. The Albuquerque Police Department, for reasons good and bad, has often been an early adopter of surveillance technology. APD deployed automated license plate readers, mounted on patrol cars and portable trailers, in 2013. Initially, the department kept a six-month history of license plate data. For six months, police could retrospectively search the database to reconstruct a vehicle, or person's, movements—at least, those movements that happened near select patrol cars and "your speed is" trailers. Lobbying by the American Civil Liberties Union and public pressure on APD and city council lead to a policy change to retain data for only 14 days, a privacy-preserving measure that the ACLU lauded as one of the best ALPR policies in the nation.

Today, ALPR is far more common in Albuquerque. Lowering costs and a continuing appetite for solving social problems with surveillance technology means that some parts of the city have ALPR installed at every signalized intersection—every person's movements cataloged at a resolution of four blocks. The data is retained for a full year. Some of it is offered, as a service, to law enforcement agencies across the country.

One of the most frustrating parts of the mass surveillance debate is the ability of law enforcement agencies and municipal governments to advance wide-scale monitoring programs, weather the controversy, and then ratchet up retention and sharing after public attention fades. For years, expansive ALPR programs spread through most American cities with little objection. In my part of the country, it seemed that the controversy over ALPR had been completely forgotten until one particularly significant ALPR vendor—Flock Safety—started repeatedly stepping in long-festering controversies with such wild abandon that they are clearly either idiots or entirely unconcerned about public perception.

PTZ camera on light pole

I try not to be too cynical but I am, unfortunately, more inclined to the latter. Companies like Flock know that they are in treacherous territory, morally and legally. They know that their customers are mostly governments or organizations with elected leaders that are subject to popular opinion. They know that helping Texas law enforcement track down abortion seekers in other states is a "bad look." They know all of these things, but they do not particularly care. They don't have to care: decades of incipient corruption, legal and political maneuvering, and the routine inefficacy of municipal politics has created an environment where public opinion doesn't matter.

I can't definitely tell you where public opinion lies on ALPR, although it seems like the average person might be mildly in support. From at least my experience, in my corner of the world, I will tell you this: it doesn't matter. Police departments and the means by which they purchase and field technology are so isolated from the political process that it is extremely difficult to imagine a scenario where voters could affect change. Year by year, city by city, the police become more dug in. Law enforcement agencies across the country have found that the most straightforward way to address privacy concerns around surveillance technology is to keep the department's purchase and deployment of that technology a secret. Most city governments at least passively support this approach. The vendors of surveillance systems facilitate, support, and even demand secrecy through their contract terms.

More recently, Las Vegas and the Bay Area have offered a model even more opaque to public scrutiny: law enforcement surveillance technology is simply purchased by wealthy private donors, almost invariably from the software industry, and then either the systems or their use are donated to the city. If carefully designed, these programs can be completely exempt from public information rules. They can take the form, for example, of a business association that runs its own private surveillance state, involving the public and ostensibly accountable police only when an arrest is made. We are privatizing mass surveillance.

Flock continues to generate enormous press, mostly on the back of persistent investigation by 404 Media. More recently, security researchers have published significant defects in the design of Flock's technology that can make the original video publicly accessible. Just about every time that someone looks into Flock, the company turns out to be less ethical, the users less concerned about compliance, the design of the system itself less competent than charitable viewers had assumed.

I'm trying not to be a doomer for Christmas, but I am sometimes frustrated with Flock coverage because it can miss the entire history of this issue. I think that a more contextually complete discussion of urban surveillance could be useful. And I am sitting in a coffee shop, in a trendy part of town, looking out the window at a PTZ camera on the side of a traffic light. That camera, I know, is owned and operated by APD's Real Time Crime Center (RTCC). Between the police department itself, city agencies that participate in the RTCC, and businesses that volunteer real-time access to their surveillance, there are thousands of others like it. Courtesy of a transit station, there are at least a dozen within my view.

Whether or not this is a good or bad idea, whether or not it is effective in reducing crime, whether or not it will be leveraged against political opposition; these are tricky questions. I suppose what worries me is that it feels like hardly anyone is asking them any more. It took Flock's remarkable ability to step on rakes and the apparent victory of fascism in national politics for anyone to remember that the construction of ubiquitous surveillance is a project that started many years ago, and that has proceeded largely unhindered ever since.

Domestic Signals Intelligence

Within the tech community, there has historically been much attention to the ability to track people by passively observing Bluetooth traffic. This technique has been widely used, both in commercial and government applications. There are popular "smart city" street lighting systems, for example, that allow every street light to passively collect signatures of the people passing underneath it. To my knowledge, these techniques are not actually very widely used by law enforcement. There are, perhaps, two reasons: one of mechanisms of government, the other of countermeasures.

First, "smart city" data collection systems are usually funded and deployed by municipal works or environmental departments. While police could make arrangements for access to that data, those arrangements would require the kind of inter-agency Memoranda of Understanding that tend to lead to far more public scrutiny than police acquisitions of surveillance products. Besides, since they aren't deployed for law enforcement purposes, they're often not useful sources for the areas that law enforcement find most interesting: higher-crime areas that are usually lower-income and, thus, less likely to have working street lights at all—much less "smart" ones. Besides, smartphones have widely adopted randomization of Bluetooth and WiFi identifiers, and protocol revisions have reduced the number of identifiers transmitted in plaintext. Passive signals intelligence just doesn't work as well as it used to, at least at the capability level of a municipality.

Talking about Bluetooth and WiFi on phones does raise the question of the "phone" part, the cellular interface, which is targeted by the family of devices often known as "Stingrays" after the trademark of a particular manufacturer. Fortunately, improvements in the security design of cellular protocols is making these less effective over time. Unfortunately, the technology continues to advance, sometimes undoing the improvements of newer GSM revisions. Federal and local law enforcement continue to purchase and use these devices, largely in secret, benefiting from a particular model of federal ownership/local use that makes it especially difficult to get a police department to even confirm or deny that they have ever deployed them. "Stingrays" or IMSI surveillance is mostly a shadow world of rumors and carefully worded non-denials. Despite technical measures against them, they are clearly still in use, and thus clearly still useful.

Optics

Out in the rest of the world, visual surveillance is more salient than radio. Early rollouts of police-operated surveillance, dating back to at least the 1970s, generated some controversy over privacy implications. Two things have since happened: first, law enforcement have relied on an increasing number of public-private partnerships and commercial vendors to gain access to surveillance without directly owning it. Second, police video surveillance has largely been normalized, and no longer faces much opposition or even public notice.

One of the interesting changes here is one of visibility: police video surveillance has often edged in and out of public awareness to fit the politics. In periods of pro-privacy, anti-surveillance sentiment, departments rely more on voluntary arrangements for access to cameras installed by others. In periods of pro-police, anti-crime sentiment, departments install cameras with flashing lights and police badges. Both types tend to persist after the next change in the tide.

The public usually knows little about these systems, a result of intentional opacity by police departments and a general lack of interest by the press. That leads to a lot of confusion. Where I live, we do have an extensive network of police-operated cameras on intersection traffic signal arms. And yet, if you ask the average person to identify a police surveillance camera, they will point to the camera for the traffic signal's video-based lane occupancy sensing every single time. That's not a police camera, it's barely even a camera as the video is rarely retained. All of these people, it seems, have worked themselves into a sort paranoia where they think that every camera is an eye of the police. Well, the police are watching them, from about ten feet over. The "speed dome" PTZ cameras get so much less attention, perhaps because they are usually mounted on the pole further from sightline, or perhaps because they are of a type more common for commercial surveillance systems that we have learned to simply ignore.

Video surveillance is an interesting topic to me, philosophically. I am largely unconcerned with the privacy implications of most video surveillance installations (such as the one on my own house) because, historically, the video was recorded locally and generally reviewed only when there was a specific reason. The simple fact that reviewing large amounts of video is so time consuming meant that the pervasive surveillance potential of video surveillance was, at one time not so long ago, quite limited.

Motorola ALPR on signal arm

Of course, the age of the computer has somewhat changed that situation. There are two phenomenon of the automation of surveillance that I think should be considered separately: first, machine vision has improved to an extent that computers can automatically process surveillance video to extract events and identities. Second, the appified, everything-social-media attitude of consumer products creates new dynamics in access to surveillance data, and those dynamics are spreading upwards into the commercial segment.

Machine Vision

Historically, much of the attention to video pervasive surveillance has centered around facial recognition. Facial recognition has indeed been applied to video surveillance for years, but I think that the average person vastly overestimates how effective and widely used facial recognition is.

The vast majority of currently installed surveillance cameras do not produce video of sufficient quality for facial recognition. That has less to do with the quality of the video itself (although that is poorer than you think for most real systems) and more to do with the way that surveillance cameras are used and installed. Most cameras are positioned high up with wide coverage of a room; this perspective is ideal for reconstructing a series of events but just about the worst case for facial recognition. For most surveillance cameras, human faces are small and at indirect angles. There is little geometry that you can extract from a face that is about ten pixels wide and subjected to aggressive h264 compression, which is how most surveillance video comes out.

Practical facial recognition systems involve cameras installed specifically for that purpose, roughly at eye level where they will get close-up, straight-on images of people who pass by. Next time you visit a bank branch, look by the exit doors for a conspicuously thick height strip. Height strips by exit doors were invented to allow clerks to give police a more accurate description of a robber, but they have since evolved to serve largely as subterfuge. Somewhere around 5' 6", you will notice a small hole, and behind that hole is a camera. Several manufacturers offer these and they seem very popular in financial services.

Kroger is more to the point: they've just been installing dome cameras right at eye height on their exit doors, for at least a decade.

When discussing surveillance, it's important to remember that the vast majority of real-world video surveillance systems are old, inexpensive, and poorly maintained. Even where cameras are installed specifically for a good view of faces, there probably isn't any facial recognition in use, most of the time. Facial recognition products are expensive and don't currently manifest many benefits unless the organization is large enough to have a security department to work with the resulting data, which requires a degree of operational maturity beyond most surveillance users (e.g. gas stations).

All of that said, there are plenty of real-world facial recognition deployments. Rite Aid, for example, prominently rolled out facial recognition to flag known shoplifters at their stores... a rollout that went so poorly that it lead to a lawsuit and an FTC settlement including the end of the facial recognition program and a five-year moratorium on further attempts. This is not to say that facial recognition on video surveillance isn't legal (although in some states it isn't or at least requires a lot of disclosure), but there is definitely a degree of legal and reputational hazard involved.

The ACLU has periodically conducted call-around surveys on use of facial recognition. The most notable trend is that most large chains now refuse to talk about it. I could be wrong, but my experience with corporate communications behavior leads to me to interpret a refusal to comment along these lines: Home Depot, for example, is a very large company that will have various initiatives underway, and either confirming or denying their use of facial recognition would probably be wrong in some cases and expose them to compliance or legal risk in others. I would bet good money that Home Depot has some facial recognition technology deployed at some locations, but knowing how slowly security technology tends to roll out in that kind of company and how complicated the legal and compliance situation can become, it's probably limited. They are probably acutely aware of the controversy surrounding this type of surveillance and, given the example of Rite Aid, the ways a rollout could go wrong. That means that surveillance will spread slowly.

But it will spread. At this point, I think it is inevitable that facial recognition will become widely used in video surveillance. I just think the point at which "facial recognition is everywhere" remains some years away, due to all the normal reasons: technical limitations, slow-moving bureaucracies, and a somewhat complex and unclear regulatory situation. It will inevitably happen, for the same reasons as well: aggressive sales by facial recognition vendors.

There are some sectors where facial recognition is very common, although I don't get the impression that retail is one of these yet. Casinos, for example—some of the larger Las Vegas casinos have reportedly had facial recognition systems in use for decades, and Nevada law is very permissive of them. Casinos are, of course, pretty much ideal users. Large institutions with a lot of financial risk and large, sophisticated security departments. Few other businesses outside of Target can compete with the size and sophistication of casino security departments. There's a whole lot of money flying around, and they can spend some of it on expensive per-camera licensing without much leadership objection.

License Plate Reading

Popular attention to facial recognition has mostly fallen away as industry and media focus has shifted to another application of machine vision that is, it turns out, a whole lot easier: license plates. License plates are designed for readability, and most states use retroreflective paints that give you an absolutely beautiful high-contrast image under coaxial (i.e. mounted alongside the camera) infrared illumination. There's not much that is easier to read by machine vision. Automated license plate readers have been available for quite a long time: US CBP had an experimental ALPR installation at a Texas border crossing in 1994. That system was actually deemed a failure and removed, but technology improved and there were permanent installations at larger border crossings by the end of the 1990s.

For a long time, the dominant vendor of ALPR equipment in the US was Motorola. Motorola's product line remains popular for vehicle-mounted systems, but the high price of the cameras, controllers, and software package had a side benefit of limiting the pervasiveness of ALPR. The equipment was just too expensive to put up all over the place.

In Albuquerque, for example, the ALPR program long consisted of Motorola systems mounted on portable "your speed is" trailers. The portable nature of these setups made the expense more worthwhile, and besides, portability has its own utilities: an APD detective once told me, for example, of how they would leave ALPR trailers in front of the houses of people suspected to lead criminal gangs. While there was value in the intelligence collection, the main motivation was intimidation: while you might call the "your speed is" trailers a concealed system, they're not all that subtle, and one supposes that most vehicle-based criminals (the main kind here) are aware that they function as the eyes of the police.

At some point, in response to growing budgets or lowered costs I'm not sure, APD began installing fixed Motorola ALPR systems on the light arms of major intersections. I know of around a dozen installations of this type in Albuquerque, which is the beginning of a widespread capability to monitor public movements but not exactly the dystopian pervasive surveillance of Minority Report.

ALPR works pretty well, but it is not perfect. Cameras need to be installed with fairly narrow optics aimed at the right spot, and infrared illumination makes reading far more reliable. Speaking of Las Vegas, I used to use a certain casino parking garage with an ALPR-based payment system with some regularity. It printed the license plate, as read by the ALPR, on the parking ticket, which is why I know that it was almost comically inept at reading my very legible California plate. It always got about half the characters wrong. I have gotten much better results in my own home experiments with budget equipment, so I figure that system must have been very poorly installed or maintained, but I'm sure there are plenty of others out there just like it.

That's the tricky thing about video surveillance, from the "blue team" side of the house: people don't tend to pay a lot of attention to it until there's been an incident, at which point they find out that the lens has had mud on it for the last three months (a much bigger problem with ALPR cameras that used to be mounted pretty low to the ground for a better look angle). I bring this up because I think that people tend to vastly overestimate the quality of real-world video surveillance, and I like to take every opportunity to remind people that the main failure case of retail video surveillance used to be failure to replace the continuous-loop tape cassette before it was completely demagnetized by repeated recording. Now, in 2025, the continuous-loop tape cassettes are pretty much gone, but maintenance practices haven't improved. Lots of the cameras you see in public only barely work or don't work at all. So it goes.

Flock

In 2017, though, a VC-backed (and specifically YCombinator) company called Flock Safety introduced a bold new idea to ALPR: a Silicon Valley sales model. Flock's system is built to be low-cost, and the sensors are smaller, simpler, and cheaper than Motorola's. I suppose they might be less effective as a result, but a reduced "catch" rate doesn't really detract from mass-surveillance ALPR installations that much. Flock has also greatly expanded their customer base, emphasizing sales to private organizations as well as law enforcement and government. Speaking of Home Depot, for example, Home Depot seems to have installed their own Flock cameras in all of their parking lots. Lowes Home Improvement has done the same.

I wanted to know more definitely how much Flock systems cost, because I suspected they were making significant inroads just through low pricing. It's a little tricky to say definitively because Flock is a "call for quote" kind of company and I think they offer contracts on different price bases. Scouring contracting documents, meeting notes, etc., it seems like a "typical" cost for a Flock camera is around $4,000 with about $3,000 a year in per-camera software licensing fees.

That might seem expensive but it compares well to the five-figure prices I have heard associated with Motorola systems, especially since the Flock offering is more "white glove" with installation and maintenance packaged. Motorola systems are usually purchased through an integrator who adds their own considerable margin.

Flock camera on light pole

Flock also designs their cameras to be amenable to solar power, which radically reduces install costs compared to Motorola systems that usually need a utility worker out to splice power from a streetlight. More even than a price reduction, it makes Flock cameras much more available to organizations like HOAs that control territory in a sense but do not have the full bucket truck or utility work order capabilities of a municipal government.

Another recent innovation in ALPR is less traceable to Flock but certainly seems associated with them: flexible funding sources. Police departments have limited budgets with which to acquire new technology, and technology vendors have to compete with other budget priorities like salaries, vehicles, and black-on-black tactical vinyl jobs for those vehicles. ALPR seems especially attractive for public-private partnership mechanisms, so there are a lot of Flock installations that were funded by business associations, HOAs, neighborhood associations, and other "indirect" sources. Some of these systems are owned and operated by the police with only the funding donated, others are owned and operated by the private group that paid for them. This can result in curious deployment decisions: sometimes the lowest-crime neighborhoods are the most replete with ALPR, as they tend to be wealthier and more politically organized communities with the wherewithall to put up the money.

The most important thing to understand about Flock, though, is that it has built on Amazon's concept of "Ring neighbors" to build a sort of nationwide, ALPR-centric Nextdoor. Flock customers can basically check a box that allows other Flock customers to access data from their sensors, and of course Flock strongly encourages users to turn sharing on. While there are some audit and access controls available on Flock data sharing, they seem like pretty minimal efforts that are often ignored.

Flock sharing has generated a lot of press, especially with some dramatic examples like use by a Texas sheriff to locate an abortion patient and use by ICE/CBP to track suspects. These are both examples that raise one of the most alarming aspects of the Flock situation: many states and municipalities have laws in place that limit or at least monitor collaboration of local police with other police agencies and federal law enforcement. Some people find this surprising, but it's important to understand that the United States is a republic of nominally independent governments. Laws, policies, and priorities can vary greatly from jurisdiction to jurisdiction. There is a specific historical thread, related to the tracing of escaped slaves, that has made resource sharing between different law enforcement agencies a known area of moral and legal treachery for a very long time.

And yet, it turns out, most Flock customers seem to have sharing turned on, possibly entirely without their knowledge. There are now multiple well-established cases of local law enforcement agencies violating state laws by having data sharing with ICE/CBP enabled. It is possible for Flock users to turn off or restrict sharing, but I think a lot of them honestly don't know that. Some state Attorneys General have ordered Flock users to disable sharing, some have restricted or banned Flock products entirely, but in general it's a very messy situation. It appears that a lack of care by law enforcement and other Flock customers, facilitate and no doubt encouraged by Flock's motivation towards "network effect" lock-in, has resulted in widespread and brazen violation of privacy laws that is only now making its way to the courts.

In other words, the tech industry happened.

Acoustics

I will not spend much time here discussing wide-area acoustic surveillance like ShotSpotter, in part because I have written a bit about it before. It's a complex issue: a well-designed gunshot detection system would probably be a good thing, but I find SoundThinking (manufacturer of the ShotSpotter system) to be profoundly untrustworthy.

Futures

The changes we are already seeing will continue: ALPR will become more ubiquitous, facial recognition will advance further into the public sphere, and the tech industry will continue to centralize data and facilitate queries by law enforcement. There's a lot of money to me made out of the whole thing, and funding towards law enforcement or public safety purchases are usually politically safe. Pretty much everything is stacked in the direction of more pervasive surveillance in the United States.

Do you find that upsetting? It seems that some people do, and some people do not. I am probably not as opposed to surveillance of public spaces as the most vocal privacy advocates, but I am also convinced that vendor-enabled mass surveillance technology like Flock is subject to enormous abuse and will inevitably undermine constitutional protections. Unfortunately, vocal organizing against mass surveillance has become pretty limited. The ACLU is doing a lot of good work in this area, but I don't see much public organizing.

The best thing you can do is probably to advocate for transparency. The most alarming part of this whole thing, to me, is the way that police departments have brazenly structured purchases of surveillance technology to get around public record and approval requirements. Companies like Flock and SoundThinking encourage this, and write it into their contracts. The end result is that many police departments have installed cameras and microphones in all kinds of places, and will not disclose when, where, how many, or how they are used. We should not allow that kind of secrecy, but preventing it seems to require legislation. The federal situation seems like a loss, so the best pressure point might be to lobby for municipal or state legislation that will require police departments to disclose their surveillance programs. Even better would be a requirement for review and approval of surveillance purchases, but unfortunately that kind of rule often already exists and police departments still structure their purchase arrangements to avoid invoking it.

I suppose the bottom line is this: keep bringing it up. Mass surveillance in the US often feels like a lost cause, but I suppose it's only lost if we give up. It doesn't take that many people showing up at a city council meeting to make something a priority to the councilors; and perhaps the police can only stonewall for so long. It's worth a shot.

speed reading (the meaning of language)

One of the difficult things about describing a grift, or at least what became a grift, is judging the sincerity with which the whole thing started. Scams often crystallize around a kernel of truth: genuinely good intentions that start rolling down the hill to profitability and end up crashing through every solid object along the way. I'm not totally sure about Evelyn Wood; she seems to have had all the best in mind but still turned so quickly to hotel conference room seminars that I have trouble lending her the benefit of the doubt.

Still, she was a teacher, and I am inclined to be sympathetic to teachers. Funny, then, that Wood's journey to fame started with another teacher. His curious reading behavior, whether interpreted as intense attention or half-assed inattention, set into motion one of the mid-century's greatest and, perhaps, most embarrassing executive self-help sensations.

In 1929, Evelyn Wood earned a bachelor's in English at the University of Utah. The following two decades are a bit obscure; she took various high-school jobs around Utah leading ultimately to Salt Lake City's Jordan High School. There, as a counselor to girl students, Wood found that many students struggled because of their reading. Assigned books were arduous, handouts discarded. These students struggled to read so severely that it hampered their performance in every area. She launched a remedial reading program of her own design, during which she made her first discovery: as her students learned to read faster, their comprehension improved. Then their grades—in every subject—followed suit. Reading, she learned, was a foundational skill. A person could learn more, do more, achieve more, if only they could read faster.

Wood became fascinated with reading, probably the reason for her return to the University of Utah for a master's degree in speech. Around 1946, she turned her thesis in to Dr. Lowell Lees. Lees was the chair of the Speech and Theater Department, and had a hand in much of the development of Utah theater from the Great Depression until his death in the 1950s. A period photo of Lees depicts him with a breastplate-microphone intercom headset and a look of concentration, hands on the levers of a mechanical variac dimmer rack. He is backstage of either "Show Boat" or "A Midsummer Night's Dream" at the university's summer theater festival. A theater department chair on lights seems odd, yes, but theater was Lees passion.

Perhaps reading was not. When Wood turned her thesis into Lees, he "read, graded, and returned the thesis within a matter of minutes." Wood was amazed that he seemed to just leaf through the pages, but then still had insightful questions to ask. Perhaps I am too cynical. It feels most likely to me that Lees was already familiar with the contents (he was probably Wood's advisor and would have discussed the research plenty of times before) and just didn't bother to read the final document. To Wood, though, something more remarkable had happened. With a series of tests, she convinced herself that Dr. Lees could read over 6,000 words per minute with full comprehension.

Evelyn Wood Reading Dynamics advertisement

A typical American college graduate can read at about 250 words per minute, at least if the material isn't too challenging. Some people, Wood contends, are "10x readers." They read so quickly, and with such good understanding, that they simply outpace the rest of us at every intellectual pursuit. What's more, Wood could make you one of those people. As she tells it, she spent two years, probably in the 1950s, tracking down fifty some examples of other exceptional readers. She published "Reading Skills" in 1958, a book evidently based on some of this research but more focused on remedial skills for grade students than executive achievement.

The introduction of Reading Skills tells us of ten different students. Anna was pretty, but she couldn't read. Joseph hated school, because he couldn't read. Carl's hair is a mess, and his parents neglectful. He also can't read. All of them became proficient readers through Wood's program. But Wood had more in mind than grade students. A year later, with her business-educated husband, she brought her reading program to adults by launching a chain of training centers under the name Evelyn Wood Reading Dynamics.

Books neither bored nor scared me any longer. I could read almost any book within an hour, and more important, I better understood that which I read.

Reading Dynamics became a sensation. Over the following years, Evelyn Wood institutes opened across the country. The speed reading movement received a considerable boost from President John F. Kennedy—he claimed to read at 1,200 words per minute, a skill he learned in part through a correspondence speed reading course. It wasn't one of Evelyn Wood's, but that detail was mostly lost on the public and the success of the Kennedies became linked to Reading Dynamics. He seems to have bought the same course for his brother Ted, and encouraged his staff to take speed reading courses as well. Reading Dynamics didn't miss the marketing opportunity, and indeed the very first dedicated Institute opened in Washington, D.C. and advertised specifically to politicians. Senators and representatives were among her earliest students and her strongest advocates.

Evelyn Wood Reading Dynamics underwent several changes of ownership through the 1960s, but Wood stayed on as developer of the training materials. Soon there were more than 60 institutes, and newspaper ads directed the interested public to "free mini-lessons" held in the meeting rooms of fine hotels across the country. It became a franchise system, with the Woods personally owning the franchise for Utah and Idaho. The company's fortunes have trended up and down with those of speed reading as a concept, but genuine Evelyn Wood speed reading courses are still available today from business training firm Pryor. There has been a bit of a reckoning: far from the 1,000+ WPM rates promised by early Evelyn Wood marketing material, Pryor now advertises "a potential rate of 400-700 words per minute." These numbers align with the upper end of reading speeds observed among the general population. In effect, Pryor no longer claims that speed reading courses will make you a faster reader than more conventional methods of training reading, like just doing a lot of it.

The science has never really been with speed reading. As early as 1959, when Reading Dynamics hit Washington, researchers and educators called Wood's data and methods into question. As with most self-help materials, Wood's writing was heavy on anecdotes and light on quantitative analysis. Certain elements of her method contradicted psychology's growing understanding of human language and perception. At the core of the problem, though, was her promise of comprehension.

It is obvious that a person can "read" a document very quickly, if we relax our definition of "read." This is just as obvious to the developers of speed reading courses. Many advise students to start by skimming, flipping through the whole book or document and taking in the headings and subjects. You can certainly get through a book under an hour that way, but of course, you haven't exactly read it. Think of it as a lossy process: the less time you spend on a document, the less you comprehend and retain its contents. That seems pretty intuitive, doesn't it?

But Wood disagreed, or at least, the company she founded did. It can be a little difficult to untangle Evelyn Wood's original theory from the many generations of Reading Dynamics and competing speed reading systems that followed. Subsequent owners of Reading Dynamics, which included companies like the publisher of Encyclopedia Britannica, made significant revisions to the material. By the 1970s, Wood's role was more as a celebrity spokesperson than a teacher. In any case, Reading Dynamics came to emphasize a key principle that reading faster actually improves comprehension. The most skilled readers, Reading Dynamics taught, don't even read words. They scan a page vertically, not horizontally, taking in an entire line at a time by peripheral vision. There is no need to sound out, read, recognize, or even really see individual words, as the mind actually processes language in large chunks at a time. Reading occurs mostly subconsciously, so in a way all you have to do is see the text and believe that you have read it, and you will retain the content.

In a 2016 review paper on speed reading, a team of psychologists deliver bad news: it just doesn't hold up. Laboratory studies confirm that the eye only has the acuity to distinguish words in a small area, and that reading requires fixating on just about every word individually. That doesn't even matter, though, because other laboratory experiments strongly suggest that the limiting factor on reading speed is not the eyes at all but the mind. Even when clever computer techniques are developed to present text more quickly, comprehension trails off at about the same speed. In fact, when humans read, we regard an even smaller area of our vision than the limits of the fovea would suggest. When looking at a word, we are basically blind to anything further than about seven characters away.

The problems with speed reading are not merely theoretical, though. The researchers considered studies of actual speed readers, people who had either completed speed reading courses or claimed to naturally read at exceptional speeds. Almost no studies can be found that support the claim of faster reading with retained comprehension. Speed readers perform poorly on comprehension tests on new material. People who "speed read" a document generally show similar comprehension to people who have no speed reading training but skimmed the document in the same period of time. When speed readers have performed better, researchers suspect the result comes more from advanced familiarity with the material (a common problem with speed reading courses that use the same texts repeatedly), broader general education (you retain more from non-fiction material if you already knew the information to begin with), and greater experience and confidence in "interpolating" by speculating as to the content of the text that wasn't actually read.

Ultimately, eye tracking experiments tend to confirm the worst. People who speed read don't do all that much actual reading. Skilled speed readers skip much of the text completely, and tend to make things up when asked about things they never fixated on. Most interesting, there seems to be a certain Dunning-Kruger effect at play. People who have speed-read a book on a subject, for example, tend to rate their knowledge of the subject highly and then perform very poorly on questions about it (often scoring similar to chance on multiple choice tests). Speed reading, it turns out, is a placebo. It makes you feel like you have read something, even though you haven't.

And yet we still have speed reading. Wood's efforts were perhaps sincere, but the commercial imperative of the growing Reading Dynamics institutes steered the whole thing away from evidence-based methods and towards ideas with an increasingly tenuous connection to reality. The on-again, off-again success of Reading Dynamics left a lot of room for imitators, or innovators, depending on your perspective. Evelyn Wood's original strain of speed reading has mostly fallen away, replaced by a new set of courses that build on Wood's ideas—the worst of them.

Take, for example, the work of Paul Scheele. Scheele is one of those business conference motivational speaker types, the kind of person who is introduced with a vast and impressive resume but who doesn't seem to have really done anything. With a PhD in "Leadership and Change," he founded Scheele Learning Systems to market a series of innovations. His work is so interconnected with other self-help and new-age grifts that it can be hard to untangle what comes from where, but one of their key programs clearly builds on the Evelyn Wood method: PhotoReading.

The basic concept of PhotoReading is that the mind is able to subconsciously process far more information than the conscious mind. In a marketing sheet, he writes:

Your conscious mind can handle seven pieces of information at a time, while your subconscious mind can handle a staggering 20,000 pieces of information. That's the difference between regular reading and PhotoReading.

So imagine a future in which you pick up a book, flip through the pages, and in a matter of minutes gain a full command of the material contained therein. The key is that you don't actually have to read anything, you just have to see it and your subconscious mind files every word away for later retrieval.

Well, of course, it's not quite that simple. There's a whole technique to it, a technique that you can learn from a self-guided digital course for only $530. Sure, that might seem a little steep, but consider that it's a package that includes not only the course but "The PhotoReading Activator Paraliminal CD." Paraliminal activation or paraliminal hypnosis is another major product from Scheele Learning Systems, although I think it's licensed at least in part from a different organization (Centerpointe Research Institute) founded by different cranks ("transcendental meditation" enthusiasts Bill Harris and Wes Wait). The idea of paraliminal activation is roughly halfway between subliminal inducement videos 1 and binaural beats 2, in that it's both of them mixed together. Incidentally, a lot of subliminal videos are like that anyway, so I'm not sure that Scheele is offering anything you can't get for free. All of these organizations offer rotating carousels of endorsements from famous and successful customers. The fact that these happy customers are almost invariably self-help authors or business conference motivational speakers goes unremarked upon.

Scheele's ultimate claim is that PhotoReading allows you "to 'mentally photograph' the printed page at 25,000 words per minute." 600-800 WPM is an excellent, exceptional reading rate among the normal population. For today's speed reading industry, though, 25,000 WPM is the bar to meet. "Harry Potter and the Deathly Hallows" counts up to about 198,000 words. An experienced PhotoReader, then, ought to be able to complete it in around eight minutes. Well, celebrity speed reader Ann Jones says it took her 47, so no one is perfect. She knocked out "Go Set a Watchman" in 25 and a half, and that on live television. Yes, for the most successful speed readers, people who claim rates in excess of 10,000 WPM, there's almost always some aspect of performance involved... whether that's television appearances or elected office. 25,000 WPM became cemented because it's the rate at which the Guinness World Records clocked celebrity speed reader Howard Berg. Actually, there's a woman who claims to have a Guinness World Record at 80,000 WPM, but it's hard to substantiate as Guinness stopped publishing the speed reading record at all decades ago. I suppose it became too questionable for even them.

The reason I'm so fascinated by speed reading is its close interconnection to the concept of the executive. One of the earliest newspaper ads for Reading Dynamics reads "For Executives, Businessmen, Students, Housewives." The housewives part doesn't quite fit the theme, but I think that might be better understood with the Utah LDS context of the housewife side hustle. Multi-level marketing schemes were becoming a cornerstone of the Salt Lake City business scene during the 1960s, a role they still fill today, and MLM brands like Avon found their success in part by melding the two worlds of the housewife and the business executive. Feminine products sold with masculine hustle, you might venture; some housewives were applying themselves to business with a zeal that would make a railroad baron blush.

For students, the motivation is more obvious. Much of education comes down to reading, and we all remember the feeling of a paper due in two days on a book that you haven't yet opened. For the student, getting good comprehension of a text in a fraction of the time is an incredible offer. So promising was speed reading for education that, in its early days, it found considerable adoption in the educational establishment. Many universities offered speed reading courses, some even made them core curriculum. A particularly prominent speed reading course at Harvard served as the pattern on which many others were taught. Besides a series of demonstration films developed Harvard, devices called "reading regulators" or "reading accelerators" were popular lab equipment for these courses. They automated Evelyn Wood's idea of running a ruler down the page, sliding a metal shield down the page faster and faster to force the student to read at a higher and higher rate. For a few years, speed reading for universities became an entire industry, but it was short lived. Academic speed reading courses faded away as criticisms of Wood's theories became better known and attempts at validating speed reading continued to fail.

"Speed reading," it turns out, did not work out in education. But perhaps that's a matter of framing. If we consider the broader landscape of "things that promise to save you time reading," speed reading is just one in a long line of ideas. It turns out that students have been trying to skip the reading for just about as long as there has been reading—consider Monarch Notes, a line of book summaries and critical commentary already available a hundred years ago. From Monarch to CliffsNotes to Chegg, students have looked to a whole sector of the publishing industry to do the hard work of actually reading books for them. You could say that the purpose of these digests or study guides is to help a student maintain the appearance that they have read a text even though they have not, by imparting only the parts of the text that are most important... important either because they are key to the plot or theme, or because they are likely to appear on exams or be expected in papers.

While students have an obvious need for these types of summaries (scoring well on assignments with less time invested), the appeal to the business executive might seem a little fuzzier. Well, unless we take the cynical view that the ultimate goal of an executive is to look smart, and I'm not sure that you really have to be so cynical to accept that as truth.

Summarizations are obviously "lossy," in that a digest form of a book cannot possibly contain the full information of the original book. Similarly, the weight of scientific evidence, as well as most credible practical experience, tells us that speed reading is a lossy process. There is, as the psychologists put it, no silver bullet in reading. Comprehension takes time; less time means less comprehension; and while you likely can improve your reading speed it will take years of practice.

And yet book summaries are an even larger industry than speed reading, and one that is both older and better adapted to the modern age. There clearly is a market for fast, low-comprehension reading of large texts. The audience is not purely made up of people seeking to create the appearance of work they have not done, although that's clearly a large part of it. Consider the magazine book review: long a staple of magazines, book reviews serve two purposes. They give you an idea of whether or not a book is worth reading, but they also summarize the book, or at least explain the major themes. That gives you some of the content of the book, the major ideas and a few choice details, in just a page of three-column prose. A third of that might be taken up by a wine club ad, to boot.

The case of the magazine book review reminds us that there is a serious, a respectable application for summaries. The perfect example might be the lawyer or doctor, people who are paid explicitly for their expertise and education but who also make heavy use of digests and summaries and desk references. There is a lot of information in the modern world, even in any given field, and no one can keep track of all of it. You might need to speed read, to use the CliffsNotes, just to keep up with the state of the field and find the things that you do need to read in their full length.

And so we have seen the dual facets of the executive demand for speed reading: the businessman, the leader, the executive is the perfect intersection of the professional need to find what to read and the personal need to look like you have done a lot of reading. Executives are expected to know what's out there, but also to seem like they already know all of it. It's a matter of opinion which of these is more prominent, but I think we can all agree that publications like CTO Magazine are aimed at that dual purpose.

Well, these days, publications like CTO Magazine are mostly aimed at drumming up AI hype. That's the other thing about business publishing: it is itself a business, and as beholden to the trends as any other.

The funny thing about speed reading is that it has never been that credible. Evelyn Wood's theories were inconsistent with the research and, frankly, a bit "out there" even as she developed them into a business in the 1960s. Experiments on speed reading, some of them conducted by the same people selling courses, have always shown iffy to clearly negative results. And yet speed reading has, in its good times, enjoyed a level of credibility and popularity that seems out of step with even its promises and certainly with its outcomes.

US Presidents Kennedy, Carter, and Nixon were all speed readers. Carter and Nixon both arranged Evelyn Wood Reading Dynamics courses for their staff, and it seems that Kennedy probably purchased some sort of course for White House staff as well. This was very much perceived as an endorsement from the top, and speed reading became not just a new innovation in education, not just a trend, but practically a requirement for any serious leader. Marketing, and the celebrity adoption that it intentionally engineered, outpaced the results. Evelyn Wood's newspaper ads and reputation got so far out front of the actual pedagogy that today's speed reading industry, spinning ever farther from reason, continues to coast on the same set of presidents.

That's not to say that there has been nothing new in speed reading. In 1984, psychologist Mary C. Potter described a method called "rapid serial visual presentation" or RSVP. The idea of RSVP is to eliminate the whole eye movement part of reading entirely, instead using a computer to present one word at a time, each centered in the same location. In theory, the words can be presented faster and faster until the user is reading more quickly than the visual system allows. Well, that's a theory at least. It's inconsistent with later research suggesting that reading speed is limited by cognition rather than perception, but most of that wasn't yet known at the time. Even so, Potter doesn't seem to have viewed RSVP as a speed reading technique. She described it as a method for cognitive research, one that could enable new experiments and improve old results by controlling for the many variables involved in scanning a page of text.

The idea of RSVP as a speed reading technique seems to have been popularized by software startup Spritz, who launched an RSVP speed reading application in 2014. Spritz seems to have spun it as "text streaming," although I think that might have been a later branding innovation. The claims of Spritz are relatively modest, only 1,000 WPM in most cases and sometimes as low as 600 WPM. These are speeds achievable (even if only narrowly) without technical assistance for exceptionally fast readers. Even so, it doesn't really work out. Research on the RSVP method of speed reading finds that comprehension decreases with increasing speed. Amusingly, some experiments show that RSVP results in decreased comprehension even when run at the same speed the subject reads naturally. Psychologists tend to attribute that effect to the fact that RSVP prevents going back and rereading a sentence that you didn't fully understand—a behavior that seems to be a natural and even required part of good reading, despite the fact that Evelyn Wood and virtually every speed reading theorist since has outlawed it.

The fact that the RSVP concept is fundamentally at odds with blinking is probably the major cause of a reported increase in fatigue, as well, but none of these shortcomings have prevented the massive popularity of RSVP within the tech industry especially. Spritz, the company, has gone basically nowhere, but the concept has graduated from TED talks to a huge inventory of browser extensions, mobile apps, CLI tools, and various and sundry GitHub projects that all make the same claims about increased reading speed. "Speed Reading Makes a Comeback" was the title of an NBC News spot on Iris Reading, more of a traditional Wood-style speed reading training company that has since wholeheartedly embraced the RSVP concept.

If software is part of the speed reading story, and a particularly core part of it today, we will have to take on the elephant in the room: in a certain sense, a very real sense, summarizing text is now the largest single driver of the US economy.

The appeal of summarization to the business executive has never gone away; the underlying technology has just evolved. Since the 2022 launch of ChatGPT, television spots, bus shelter ads, and the collective buzz of the south end of the San Francisco Peninsula have promised first and foremost that AI will relieve us of the obligation of reading. An LLM can read your email, read the news, read a book, or read the comments. Actually, the LLM has already read a lot of these things. On command, it can summarize them to you.

AI advertising seems to imagine a world that is, well, oddly familiar: one in which students, housewives, and, yes, business executives can save hours of each day by using the LLM to, in effect, read at 25,000 WPM. It also seems that the same basic principles apply: the LLM's output loses some of the content of the original material. It might also gain some content, a benefit of all of the other things that the LLM has also been trained on. Still: there's always a certain rounding out, a sanding down of the details.

What strikes me most about LLM summaries is just how long they are. When I have asked Claude to summarize reading notes, it has routinely produced output that is longer than the original notes. This problem can probably be addressed by prompting, although my efforts at appending everything from "be brief" to "for the love of God keep it to one paragraph" have failed to produce a good result. Maybe I'm holding it wrong, maybe I'm an idiot, I possess no qualifications in this area besides decades as a natural language user and an unfinished degree in technical writing. But experience suggests that my coworkers have the same problem. I see AI generated meeting summaries, AI generated issue descriptions, AI generated sales documents. One of their common properties is that they are astoundingly, uselessly verbose.

Of course, modern AI can do so much more than summarize text. "Generative AI" promises not only to summarize, but also to create something new. Perhaps that's why the LLM is so verbose. I, personally, find that I make up for my lackluster interpersonal skills by writing. Perhaps LLMs make up for their similar limitations, their fundamentally text-based, screen-resident nature, by using the one tool that they have. The LLM cannot think or feel, yet it can write. So it writes: a simple question answered with such energy that it merits four distinct bulleted lists, each with an emoji-laden heading and an introductory paragraph. I suppose I can sympathize. We must imagine Grok happy.


I put a lot of time into writing this, and I hope that you enjoy reading it. If you can spare a few dollars, consider supporting me on ko-fi. You'll receive an occasional extra, subscribers-only post, and defray the costs of providing artisanal, hand-built world wide web directly from Albuquerque, New Mexico.


I do not mean to criticize AI too harshly, although I think the level of criticism that this entire industry phenomena deserves is high enough that you have to go big.

But the relationship between speed reading and the LLM—between Sam Altman and Evelyn Wood—is vague but vivid. The software industry's imagined future, in which people use LLMs to generate text that other people use LLMs to summarize, genuinely haunts me. AI has created a profound contradiction: it promises the productivity gains of speed reading, the ease of CliffsNotes, but it doesn't just shorten text. It also lengthens it. My joking reference to Camus, shoddy as it is, becomes more meaningful. ChatGPT pushes the written word up the hill, it watches it roll back down again.

I read "The Myth of Sisyphus" for the same reason everyone else did: high school. IB English HL. Yes, I went to one of those schools. If you are not familiar you can look it up and one of the top results, at least for me, is a clearly LLM-generated article that is four or five times longer than it should be based on the factual content. You can have your web browser's LLM feature summarize it back down for you, if you want. The result comes out a lot less useful than the Wikipedia article but it is, as they say, disruptive nonetheless.

If the purpose of reading is solely to acquire information and accumulate thought units, then surely speed and efficiency are the essential criteria. Regressing is obviously a morbid symptom since it is destructive of time and energy, while horizontal reading not only taxes the optic muscles, but requires that the same tome remain clutched by fingers which could be more profitably employed in reaching for yet another volume.

We're all full of opinions on the era of AI. I am perhaps not as pessimistic as you might think: the machine learning innovations of the last few years clearly do have useful applications. Even summarizing text has its time and place. I suppose that what frustrates me most about it all is the lack of ambition. LLMs train on text, take text as input, and generate text as output. A room of Silicon Valley visionaries, presented with this astounding tool, came up with such world-changing applications as "reading emails" and "writing emails." The whole industry is still struggling to move past this trivial, boring, frequently nonproductive use.

As for lip motions, any toddler knows that the mouth is tardier than the eye, and retardation is one of the most dreaded words in an educator's terminology. Furthermore, the speed cult is quick to point out that slow readers are rarely careful ones, and generally speaking, comprehension appears to increase with reading velocity. Speed reading, it would appear, is all profit and no loss and if it can make good its claims at linking efficiency and comprehension, then it is well that its methodology and objectives are incorporated into any reading program.

There is the potential, the AI's industries advocates say, of AI actually expanding human creativity. Machine learning methods of producing "art," whether text or image or audio or video, will lower the barrier of entry to artistic production. Of course, that depends a lot on how you define "artistic production," but at least it's a rare promise of a better future rather than a worse one. It only takes a brief interaction with the modern internet to realize that we do live in an age blessed with text. We are rich in the written word like never before, so wealthy with words that they crowd out the actual information. Search results are mostly AI-generated, but the search engine doesn't want you to look at them anyway, it's provided its own AI-generated treatise. The headings, the paragraphs, the bulleted lists, they run down the page, drip from our screens, they leave our desks filthy with content.

But prior to debating the plausibility of the claims in an Evelyn Wood brochure, it would seem logical to consider the desirability of the goals—goals which appear to have slipped unchallenged into the realm of pedagogical axioms. Are such facile reading practices worthy of unqualified adulation? A careful look at their implications suggests that such seeming saints can in fact be devils.

It's enough to drive you to madness. Why do we use computers to write text that no one will read? Why do we use computers to read text that no one wrote?

Years ago, in college, during a previous AI winter, I sat in my room reading a shitty science fiction novel. Leo, from across the hall, walked in. "What class is that for?" he asked.

"Not for a class," I responded.

"So you're just reading it?"

Taken by themselves, the cardinal virtues of reading efficiency can collectively demean the entire reading process by treating it as a function rather than as an art.

There is nothing new under the sun. We have done this all before: we have fixated on reading as production, production as profitable, and reading thus, ultimately, unimportant. A detail to be optimized away. An expense. ChatGPT didn't start this. It won't end it. That's what I remind myself: we are living through just another step in the evolution of culture.

But then I still worry. What if this is it? Between short-form video and AI, between social media's pivot to stoking fascism and the publishing industry's pivot to reprinting AO3, what if language arts are done?

None of these people care. That's the one thing I can say confidently, or at least say that I truly believe. These people building the cutting edge of natural language, these industry titans who style themselves as the loyalists of our nation and revolutionaries of the arts, they don't give a damn about writing or reading. Text is an asset, an asset to extract, refine, and dispense. They're just trying to make it through the news and their Twitter feeds and a half dozen pop-science books as fast as possible so that they can be, feel, or at least look like they're well-read. They assume that everyone else feels the same way.

How can any teacher extol the pleasures of reading when classroom practice implicitly asserts that books are mines to be stripped and not pastures in which to dwell and delight?

I've been quoting from Leonard R. Mendelsohn, whose paper "Jetting to Utopia: The Speed Reading Phenomenon" ignores the question of whether or not speed reading works and instead considers whether or not it is a good idea. His context was the classroom of the 1970s: speed reading had caught on in education, and Mendelsohn worried. Well-intentioned teachers were training their students to absolutely optimize the mechanics of reading. In the process, Mendelsohn feared, they had forgotten the point.

Reading can provide fodder for the brain by the ready conversion of wood pulp and printer's ink into social poise, persuasiveness, and a financially rewarding livelihood.

Things have changed a great deal since Mendelsohn's day. The wood pulp is gone, so too the printer ink, and so too the financial rewards. Writing is, I suppose, more of an art than ever before, as my chosen industry devotes its full might to destroying my chosen avocation.

Although reading might be branded with the explicit label "fun," it is not long before the apt student reaches the conclusion that speed, concepts, and information are all one knows and all one needs to know.

Mendelsohn's paper ran in the journal "Language Arts." It's about four pages long, about 2,300 words. It took me around ten minutes to read. An accomplished student of Evelyn Wood could read it in just a couple of minutes. With some chiding to stay brief and cut it out with the bulleted lists, Claude summarized it in a few sentences.

For the journal, though, the paper is not quite long enough. Its last page is only half full. The journal editor made up the difference, they found some filler. It's a poem about clouds.

ChatGPT can do so much, but it can't do the work of a poet. It can't match Christa Kessler, age 10, Powhatan School, Boyce, Virginia. She wrote "Clouds" almost fifty years ago, an editor used it to round out the layout of a journal, JSTOR coughed it up along with my article, and now I am thinking about how clouds really are interludes in the middle of a great blue sea.

That's what it's like to read slow. That's what it means to write.

  1. If you don't immediately know the exact kind of YouTube video I'm talking about, maybe "become a catgirl subliminal" will jog your mind. Or just look it up and find out for yourself. Remember to stay hydrated.

  2. One of the hard things about writing about these kinds of fringe or parascientific topics is that they get all tangled up in each other and I have a hard time not getting lost on tangents. Fortunately I think that many of my readers have the same kind of internet exposure that I do and are probably familiar with the concept or claims made about binaural beats. You might be less aware that the whole thing dates back to the 1970s and perennially pops up in any kind of self-help or "neurogenics" or whatever context, including many speed reading courses. To be fair, back in the 1970s the idea was new and full of potential. Now it is not; decades of scientific investigation have failed to produce clear evidence that binaural beats do anything.

CodeSOD: Awaiting A Reaction

Today's Anonymous submitter sends us some React code. We'll look at the code and then talk about the WTF:

// inside a function for updating checkboxes on a page
if (!e.target.checked) {
  const removeIndex = await checkedlist.findIndex(
    (sel) => sel.Id == selected.Id,
  )
  const removeRowIndex = await RowValue.findIndex(
    (sel) => sel == Index,
  )

// checkedlist and RowValue are both useState instances.... they should never be modified directly
  await checkedlist.splice(removeIndex, 1)
  await RowValue.splice(removeRowIndex, 1)

// so instead of doing above logic in the set state, they dont
  setCheckedlist(checkedlist)
  setRow(RowValue)
} else {
  if (checkedlist.findIndex((sel) => sel.Id == selected.Id) == -1) {
    await checkedlist.push(selected)
  }
// same, instead of just doing a set state call, we do awaits and self updates
  await RowValue.push(Index)
  setCheckedlist(checkedlist)
  setRow(RowValue)
}

Comments were added by our submitter.

This code works. It's the wrong approach for doing things in React: modifying objects controlled by react, instead of using the provided methods, it's doing asynchronous push calls. Without the broader context, it's hard to point out all the other ways to do this, but honestly, that's not the interesting part.

I'll let our submitter explain:

This code is black magic, because if I update it, it breaks everything. Somehow, this is working in perfect tandem with the rest of the horrible page, but if I clean it up, it breaks the checkboxes; they're no longer able to be clicked. Its forcing React somehow to update asynchronously so it can use these updated values correctly, but thats the neat part, they aren't even being used anywhere else, but somehow the re-rendering page only accepts awaits. I've tried refactoring it 5 different ways to no avail

That's what makes truly bad code. Code so bad that you can't even fix it without breaking a thousand other things. Code that you have to carefully, slowly, pick through and gently refactor, discovering all sorts of random side-effects that are hidden. The code so bad that you actually have to live with it, at least for awhile.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

CodeSOD: All Docked Up

Aankhen has a peer who loves writing Python scripts to automate repetitive tasks. We'll call this person Ernest.

Ernest was pretty proud of some helpers he wrote to help him manage his Docker containers. For example, when he wanted to stop and remove all his running Docker containers, he wrote this script:

#!/usr/bin/env python
import subprocess

subprocess.run("docker kill $(docker ps -q)", shell=True)
subprocess.run("docker rm $(docker ps -a -q)", shell=True)

He aliased this script to docker-stop, so that with one command he could… run two.

"Ernest," Aankhen asked, "couldn't this just be a bash script?"

"I don't really know bash," Ernest replied. "If I just do it in bash, if the first command fails, the second command doesn't run."

Aankhen pointed out that you could make bash not do that, but Ernest replied: "Yeah, but I always forget to. This way, it handles errors!"

"It explicitly doesn't handle errors," Aankhen said.

"Exactly! I don't need to know when there are no containers to kill or remove."

"Okay, but why not use the Docker library for Python?"

"What, and make the software more complicated? This has no dependencies!"

Aankhen was left with a sinking feeling: Ernest was either the worst developer he was working with, or one of the best.

[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.

CodeSOD: To Shutdown You Must First Shutdown

Every once in awhile, we get a bit of terrible code, and our submitter also shares, "this isn't called anywhere," which is good, but also bad. Ernesto sends us a function which is called in only one place:

///
/// Shutdown server
///
private void shutdownServer()
{
    shutdownServer();
}

The "one place", obviously, is within itself. This is the Google Search definition of recursion, where each recursive call is just the original call, over and over again.

This is part of a C# service, and this method shuts down the server, presumably by triggering a stack overflow. Unless C# has added tail calls, anyway.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

Anti-Simplification

Our anonymous submitter relates a tale of simplification gone bad. As this nightmare unfolds, imagine the scenario of a new developer coming aboard at this company. Imagine being the one who has to explain this setup to said newcomer.

Imagine being the newcomer who inherits it.

A "Storm P machine" - the Danish equivalent of a Rube Goldberg machine.

David's job should have been an easy one. His company's sales data was stored in a database, and every day the reporting system would query a SQL view to get the numbers for the daily key performance indicators (KPIs). Until the company's CTO, who was proudly self-taught, decided that SQL views are hard to maintain, and the system should get the data from one of those new-fangled APIs instead.

But how does one call an API? The reporting system didn't have that option, so the logical choice was Azure Data Factory to call the API, then output the data to a file that the reporting system could read. The only issue was that nobody on the team spoke Azure Data Factory, or for that matter SQL. But no problem, one of David's colleagues assured, they could do all the work in the best and most multifunctional language ever: C#.

But you can't just write C# in a data factory directly, that would be silly. What you can do is have the data factory pipeline call an Azure function, which calls a DLL that contains the bytecode from C#. Oh, and a scheduler outside of the data factory to run the pipeline. To read multiple tables, the pipeline calls a separate function for each table. Each function would be based on a separate source project in C#, with 3 classes each for the HTTP header, content, and response; and a separate factory class for each of the actual classes.

After all, each table had a different set of columns, so you can't just re-use classes for that.

There was one little issue: the reporting system required an XML file, whereas the API would export data in JSON. It would be silly to expect a data factory, of all things, to convert this. So the CTO's solution was to have another C# program (in a DLL called by a function from a pipeline from an external scheduler) that reads the JSON document saved by the earlier program, uses foreach to go over each element, then saves the result as XML. A distinct program for each table, of course, requiring distinct classes for header, content, response, and factories thereof.

Now here's the genius part: to the C# class representing the output data, David's colleague decided to attach one different object for each input table required. The data class would use reflection to iterate over the attached objects, and for each object, use a big switch block to decide which source file to read. This allows the data class to perform joins and calculations before saving to XML.

To make testing easier, each calculation would be a separate function call. For example, calculating a customer's age was a function taking struct CustomerWithBirthDate as input, use a foreach loop to copy all the data except replacing one field, and return a CustomerWithAge struct to pass to the next function. The code performed a bit slowly, but that was an issue for a later year.

So basically, the scheduler calls the data factory, which calls a set of Azure functions, which call a C# function, which calls a set of factory classes to call the API and write the data to a text file. Then, the second scheduler calls a data factory, which calls Azure functions, which call C#, which calls reflection to check attachment classes, which read the text files, then call a series of functions for each join or calculation, then call another set of factory classes to write the data to an XML file, then call the reporting system to update.

Easy as pie, right? So where David's job could have been maintaining a couple hundred lines of SQL views, he instead inherited some 50,000 lines of heavily-duplicated C# code, where adding a new table to the process would easily take a month.

Or as the song goes, Somebody Told Me the User Provider should use an Adaptor to Proxy the Query Factory Builder ...

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Error'd: That's What I Want

First up with the money quote, Peter G. remarks "Hi first_name euro euro euro, look how professional our marketing services are! "

1

 

"It takes real talent to mispell error" jokes Mike S. They must have done it on purpose.

0

 

I long wondered where the TikTok profits came from, and now I know. It's Daniel D. "I had issues with some incorrectly documented TikTok Commercial Content API endpoints. So I reached out to the support. I was delighted to know that it worked and my reference number was . PS: 7 days later I still have not been contacted by anyone from TikTok. You can see their support is also . "

2

 

Fortune favors the prepared, and Michael R. is very fortunate. "I know us Germans are known for planning ahead so enjoy the training on Friday, February 2nd 2029. "

3

 

Someone other than dragoncoder047 might have shared this earlier, but this time dragoncoder047 definitely did. "Digital Extremes (the developers of Warframe) were making many announcements of problems with the new update that rolled out today [February 11]. They didn’t mention this one!"

4

 

[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.

CodeSOD: Qaudruple Negative

We mostly don't pick on bad SQL queries here, because mostly the query optimizer is going to fix whatever is wrong, and the sad reality is that databases are hard to change once they're running; especially legacy databases. But sometimes the code is just so hamster-bowling-backwards that it's worth looking into.

Jim J has been working on a codebase for about 18 months. It's a big, sprawling, messy project, and it has code like this:

AND CASE WHEN @c_usergroup = 50 AND NOT EXISTS(SELECT 1 FROM l_appl_client lac WHERE lac.f_application = fa.f_application AND lac.c_linktype = 840 AND lac.stat = 0 AND CASE WHEN ISNULL(lac.f_client,0) <> @f_client_user AND ISNULL(lac.f_c_f_client,0) <> @f_client_user THEN 0 ELSE 1 END = 1 ) THEN 0 ELSE 1 END = 1 -- 07.09.2022

We'll come back to what it's doing, but let's start with a little backstory.

This code is part of a two-tier application: all the logic lives in SQL Server stored procedures, and the UI is a PowerBuilder application. It's been under development for a long time, and in that time has accrued about a million lines of code between the front end and back end, and has never had more than 5 developers working on it at any given time. The backlog of feature requests is nearly as long as the backlog of bugs.

You may notice the little date comment in the code above. That's because until Jim joined the company, they used Visual Source Safe for version control. Visual Source Safe went out of support in 2005, and let's be honest: even when it was in support it barely worked as a source control system. And that's just the Power Builder side- the database side just didn't use source control. The source of truth was the database itself. When going from development to test to prod, you'd manually export object definitions and run the scripts in the target environment. Manually. Yes, even in production. And yes, environments did drift and assumptions made in the scripts would frequently break things.

You may also notice the fields above use a lot of Hungarian notation. Hungarian, in the best case, makes it harder to read and reason about your code. In this case, it's honestly fully obfuscatory. c_ stands for a codetable, f_ for entities. l_ is for a many-to-many linking table. z_ is for temporary tables. So is x_. And t_. Except not all of those "temporary" tables are truly temporary, a lesson Jim learned when trying to clean up some "junk" tables which were not actually junk.

I'll let Jim add some more detail around these prefixes:

an "application" may have a link to a "client", so there is an f_client field; but also it references an "agent" (which is also in the f_client table, surpise!) - this is how you get an f_c_f_client field. I have no clue why the prefix is f_c_ - but I also found c_c_c_channel and fc4_contact columns. The latter was a shorthand for f_c_f_c_f_c_f_contact, I guess.

"f_c_f_c_f_c_f_c" is also the sound I'd make if I saw this in a codebase I was responsible for. It certainly makes me want to change the c_c_c_channel.

With all this context, let's turn it back over to Jim to explain the code above:

And now, with all this background in mind, let's have a look at the logic in this condition. On the deepest level we check that both f_client and f_c_f_client are NOT equal to @f_client_user, and if this is the case, we return 0 which is NOT equal to 1 so it's effectively a negation of the condition. Then we check that records matching this condition do NOT EXIST, and when this is true - also return 0 negating the condition once more.

Honestly, the logic couldn't be clearer, when you put it that way. I jest, I've read that twelve times and I still don't understand what this is for or why it's here. I just want to know who we can prosecute for this disaster. The whole thing is a quadruple negative and frankly, I can't handle that kind of negativity.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

CodeSOD: Repeating Your Existence

Today's snippet from Rich D is short and sweet, and admittedly, not the most TFs of WTFs out there. But it made me chuckle, and sometimes that's all we need. This Java snippet shows us how to delete a file:

if (Files.exists(filePath)) {
    Files.deleteIfExists(filePath);
}

If the file exists, then if it exists, delete it.

This commit was clearly submitted by the Department of Redundancy Department. One might be tempted to hypothesize that there's some race condition or something that they're trying to route around, but if they are, this isn't the way to do it, per the docs: "Consequently this method may not be atomic with respect to other file system operations." But also, I fail to see how this would do that anyway.

The only thing we can say for certain about using deleteIfExists instead of delete is that deleteIfExists will never throw a NoSuchFileException.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: Blocked Up

Agatha has inherited some Windows Forms code. This particular batch of such code falls into that delightful category of code that's wrong in multiple ways, multiple times. The task here is to disable a few panels worth of controls, based on a condition. Or, since this is in Spanish, "bloquear controles". Let's see how they did it.

private void BloquearControles()
{
	bool bolBloquear = SomeConditionTM; // SomeConditionTM = a bunch of stuff. Replaced for clarity.

	// Some code. Removed for clarity.
	
	// private System.Windows.Forms.Panel pnlPrincipal;
	foreach (Control C in this.pnlPrincipal.Controls)
	{
		if (C.GetType() == typeof(System.Windows.Forms.TextBox))
		{
			C.Enabled = bolBloquear;
		}
		if (C.GetType() == typeof(System.Windows.Forms.ComboBox))
		{
			C.Enabled = bolBloquear;
		}
		if (C.GetType() == typeof(System.Windows.Forms.CheckBox))
		{
			C.Enabled = bolBloquear;
		}
		if (C.GetType() == typeof(System.Windows.Forms.DateTimePicker))
		{
			C.Enabled = bolBloquear;
		}
		if (C.GetType() == typeof(System.Windows.Forms.NumericUpDown))
		{
			C.Enabled = bolBloquear;
		}
	}
	
	// private System.Windows.Forms.GroupBox grpProveedor;
	foreach (Control C1 in this.grpProveedor.Controls)
	{
		if (C1.GetType() == typeof(System.Windows.Forms.TextBox))
		{
			C1.Enabled = bolBloquear;
		}
		if (C1.GetType() == typeof(System.Windows.Forms.ComboBox))
		{
			C1.Enabled = bolBloquear;
		}
		if (C1.GetType() == typeof(System.Windows.Forms.CheckBox))
		{
			C1.Enabled = bolBloquear;
		}
		if (C1.GetType() == typeof(System.Windows.Forms.DateTimePicker))
		{
			C1.Enabled = bolBloquear;
		}
		if (C1.GetType() == typeof(System.Windows.Forms.NumericUpDown))
		{
			C1.Enabled = bolBloquear;
		}
	}

	// private System.Windows.Forms.GroupBox grpDescuentoGeneral;
	foreach (Control C2 in this.grpDescuentoGeneral.Controls)
	{
		if (C2.GetType() == typeof(System.Windows.Forms.TextBox))
		{
			C2.Enabled = bolBloquear;
		}
		if (C2.GetType() == typeof(System.Windows.Forms.ComboBox))
		{
			C2.Enabled = bolBloquear;
		}
		if (C2.GetType() == typeof(System.Windows.Forms.CheckBox))
		{
			C2.Enabled = bolBloquear;
		}
		if (C2.GetType() == typeof(System.Windows.Forms.DateTimePicker))
		{
			C2.Enabled = bolBloquear;
		}
		if (C2.GetType() == typeof(System.Windows.Forms.NumericUpDown))
		{
			C2.Enabled = bolBloquear;
		}
	}

	// Some more code. Removed for clarity.
}

This manages two group boxes and a panel. It checks a condition, then iterates across every control beneath it, and sets their enabled property on the control. In order to do this, it checks the type of the control for some reason.

Now, a few things: every control inherits from the base Control class, which has an Enabled property, so we're not doing this check to make sure the property exists. And every built-in container control automatically passes its enabled/disabled state to its child controls. So there's a four line version of this function where we just set the enabled property on each container.

This leaves us with two possible explanations. The first, and most likely, is that the developer responsible just didn't understand how these controls worked, and how inheritance worked, and wrote this abomination as an expression of that ignorance. This is extremely plausible, extremely likely, and honestly, our best case scenario.

Because our worse case scenario is that this code's job isn't to disable all of the controls. The reason they're doing type checking is that there are some controls used in these containers that don't match the types listed. The purpose of this code, then, is to disable some of the controls, leaving others enabled. Doing this by type would be a terrible way to manage that, and is endlessly confusing. Worse, I can't imagine how this behavior is interpreted by the end users; the enabling/disabling of controls following no intuitive pattern, just filtered based on the kind of control in use.

The good news is that Agatha can point us towards the first option. She adds:

They decided to not only disable the child controls one by one but to check their type and only disable those five types, some of which aren't event present in the containers. And to make sure this was WTF-worthy the didn't even bother to use else-if so every type is checked for every child control

She also adds:

At this point I'm not going to bother commenting on the use of GetType() == typeof() instead of is to do the type checking.

Bad news, Agatha: you did bother commenting. And even if you didn't, don't worry, someone would have.

[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.

CodeSOD: Popping Off

Python is (in)famous for its "batteries included" approach to a standard library, but it's not that notable that it has plenty of standard data structures, like dicts. Nor is in surprising that dicts have all sorts of useful methods, like pop, which removes a key from the dict and returns its value.

Because you're here, reading this site, you'll also be unsurprised that this doesn't stop developers from re-implementing that built-in function, badly. Karen sends us this:

def parse_message(message):
    def pop(key):
        if key in data:
            result = data[key]
            del data[key]
            return result
        return ''

    data = json.loads(message)
    some_value = pop("some_key")
    # <snip>...multiple uses of pop()...</snip>

Here, they create an inner method, and they exploit variable hoisting. While pop appears in the code before data is declared, all variable declarations are "hoisted" to the top. When pop references data, it's getting that from the enclosing scope. Which while this isn't a global variable, it's still letting a variable cross between two scopes, which is always messy.

Also, this pop returns a default value, which is also something the built-in method can do. It's just the built-in version requires you to explicitly pass the value, e.g.: some_value = data.pop("some_key", "")

Karen briefly wondered if this was a result of the Python 2 to 3 conversion, but no, pop has been part of dict for a long time. I wondered if this was just an exercise in code golf, writing a shorthand function, but even then- you could just wrap the built-in pop with your shorthand version (not that I'd recommend such a thing). No, I think the developer responsible simply didn't know the function was there, and just reimplemented a built-in method badly, as so often happens.

[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.

Error'd: Perverse Perseveration

Pike pike pike pike Pike pike pike.

Lincoln KC repeated "I never knew Bank of America Bank of America Bank of America was among the major partners of Bank of America."

4

 

"Extra tokens, or just a stutter?" asks Joel "An errant alt-tab caused a needless google search, but thankfully Gemini's AI summary got straight-to-the-point(less) info. It is nice to see the world's supply of Oxford commas all in once place. "

0

 

Alessandro M. isn't the first one to call us out on our WTFs. "It’s adorable how the site proudly supports GitHub OAuth right up until the moment you actually try to use it. It’s like a door with a ‘Welcome’ sign that opens onto a brick wall." Meep meep.

1

 

Float follies found Daniel W. doubly-precise. "Had to go check on something in M365 Admin Center, and when I was on the OneDrive tab, I noticed Microsoft was calculating back past the bit. We're in quantum space at this point."

2

 

Weinliebhaber Michael R. sagt "Our German linguists here will spot the WTF immediately where my local wine shop has not. Weiẞer != WEIBER. Those words mean really different things." Is that 20 euro per kilo, or per the piece?

3

 

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

CodeSOD: The Counting Machine

Industrial machines are generally accompanied by "Human Machine Interfaces", HMIs. This is industrial slang for a little computerized box you use to control the industrial machine. All the key logic and core functionality and especially the safety functionality is handled at a deeper computer layer in the system. The HMI is just buttons users can push to interact with the machine.

Purchasers of those pieces of industrial equipment often want to customize that user interface. They want to guide users away from functions they don't need, or make their specific workflow clear, or even just brand the UI. This means that the vendor needs to publish an API for their HMI.

Which brings us to Wendy. She works for a manufacturing company which wants to customize the HMI on a piece of industrial equipment in a factory. That means Wendy has been reading the docs and poking at the open-sourced portions of the code, and these raise more questions than they answer.

For example, the HMI's API provides its own set of collection types, in C#. We can wonder why they'd do such a thing, which is certainly a WTF in itself, but this representative line raises even more questions than that:

Int32 Count { get; set; }

What happens if you use the public set operation on the count of items in a collection? I don't know. Wendy doesn't either, as she writes:

I'm really tempted to set the count but I fear the consequences.

All I can hear in my head when I think about "setting the Count" is: "One! One null reference exception! Two! TWO null reference exceptions! HA HA HA HA!"

Count von Count kneeling.png
By http://muppet.wikia.com/wiki/Count_von_Count

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

CodeSOD: Safegaurd Your Comments

I've had the misfortune of working in places which did source-control via comments. Like one place which required that, with each section of code changed, you needed to add a comment with your name, the ticket number, and the reason the change was made. You know, the kind of thing you can just get from your source control service.

In their defense, that policy was invented for mainframe developers and then extended to everyone else, and their source control system was in Visual Source Safe. VSS was a) terrible, and b) a perennial destroyer of history, so maybe they weren't entirely wrong and VSS was the real WTF. I still hated it.

In any case, Alice's team uses more modern source control than that, which is why she's able to explain to us the story of this function:

public function calculateMassGrossPay(array $employees, Payroll $payroll): array
{
    // it shouldn't enter here, but if it does by any change, do nth
    return [];
}

Once upon a time, this function actually contained logic, a big pile of fairly complicated logic. Eventually, a different method was created which streamlined the functionality, but had a different signature and logic. All the callers were updated to use that method instead- by commenting out the line which called this one. This function had a comment added to the top: // it shouldn't enter here.

Then, the body of this function got commented out, and the return was turned into an empty array. The comment was expanded to what you see above. Then, eventually, the commented-out callers were all deleted. Years after that, the commented out body of this function was also deleted, leaving behind the skeleton you see here.

This function is not referenced anywhere else, not even in a comment. It's truly impossible for code to "enter here".

Alice writes: "Version control by commented out code does not work very well."

Indeed, it does not.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Representative Line: Years Go By

Henrik H's employer thought they could save money by hiring offshore, and save even more money by hiring offshore junior developers, and save even more money by basically not supervising them at all.

Henrik sends us just one representative line:

if (System.DateTime.Now.AddDays(-365) <= f.ReleaseDate) // 365 days means one year 

I appreciate the comment, that certainly "helps" explain the magic number. There's of course, just one little problem: It's wrong. I mean, ~75% of the time, it works every time, but it happily disregards leap years. Which may or may not be a problem in this case, but if they got so far as learning about the AddDays method, they were inches from using AddYears.

I guess it's true what they say: you can lead a dev to docs, but you can't make them think.

[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.

WTF: Home Edition

The utility closet Ellis had inherited and lived with for 17 years had been a cesspool of hazards to life and limb, a collection of tangible WTFs that had everyone asking an uncaring god, "What were they thinking?"

Every contractor who'd ever had to perform any amount of work in there had come away appalled. Many had even called over their buddies to come and see the stunning mess for themselves:

INTERIOR OF UTILITY ROOM SHOWING STORAGE CLOSET AT PHOTO CENTER LEFT AND HOT WATER HEATER CLOSET AT PHOTO CENTER RIGHT. VIEW TO EAST. - Bishop Creek Hydroelectric System, HAER CAL,14-BISH.V,7A-28

  • All of the electrical components, dating from the 1980s, were scarily underpowered for what they were supposed to be powering.
  • To get to the circuit breaker box—which was unlabeled, of course—one had to contort themselves around a water heater almost as tall as Ellis herself.
  • As the house had no basement, the utility closet was on the first floor in an open house plan. A serious failure with said water heater would've sent 40 gallons (150 liters) of scalding-hot tsunami surging through the living room and kitchen.
  • The furnace's return air vent had been screwed into crumbling drywall, and only prayers held it in place. Should it have fallen off, it would never have been replaceable. And Ellis' cat would've darted right in there for the adventure of a lifetime.
  • To replace the furnace filter, Ellis had to put on work gloves, unscrew a sharp sheet-metal panel from the side of the furnace, pull the old filter out from behind a brick (the only thing holding it in place), manipulate the filter around a mess of water and natural gas pipes to get it out, thread the new filter in the same way, and then secure it in place with the brick before screwing the panel back on. Ellis always pretended to be an art thief in a museum, slipping priceless paintings around security-system lasers.
  • Between the water tank, furnace, water conditioning unit, fiber optical network terminal, and router, there was barely room to breathe, much less enough air to power ignition for the gas appliances. Some genius had solved this by cutting random holes in several walls to admit air from outside. One of these holes was at floor-level. Once, Ellis opened the closet door to find a huge puddle on the floor, making her fear her hot water heater was leaking. As it turned out, a power-washing service had come over earlier that day. When they'd power-washed the exterior of her home, some of that water shot straight through one of those holes she hadn't known about, giving her utility closet a bonus bath.
  • If air intake was a problem, venting the appliances' exhaust was an even worse issue. The sheet-metal vents had calcified and rusted over time. If left unaddressed, holes could've formed that would've leaked carbon monoxide into Ellis' house.

Considering all the above, plus the fact that the furnace and air conditioner were coming up on 20 years of service, Ellis couldn't put off corrective action any longer. Last week, over a span of 3 days, contractors came in to exorcise the demons:

  • Upgrading electricals that hadn't already been dealt with.
  • Replacing the hot water tank with a wall-mounted tankless heater.
  • Replacing the furnace and AC with a heat pump and backup furnace, controlled by a new thermostat.
  • Creating new pipes for intake and venting (no more reliance on indoor air for ignition).
  • Replacing the furnace return air vent with a sturdier one.
  • Putting a special hinged door on the side of the furnace, allowing the filter to be replaced in a matter of seconds (RIP furnace brick).

With that much work to be done, there were bound to be hiccups. For instance, when the Internet router was moved, an outage occurred: for no good reason, the optical network terminal refused to talk to Ellis' Wifi router after powering back up. A technician came out a couple days later, reset the Internet router, and everything was fine again.

All in all, it was an amazing and welcome transformation. As each new update came online, Ellis was gratefully satisfied. It seemed as though the demons were finally gone.

Unbeknownst to them all, there was one last vengeful spirit to quell, one final WTF that it was hell-bent on doling out.

It was late Friday afternoon. Despite the installers' best efforts, the new thermostat still wasn't communicating with the new heat pump. Given the timing, they couldn't contact the company rep to troubleshoot. However, the thermostat was properly communicating with the furnace. And so, Ellis was left with the furnace for the weekend. She was told not to mess with the thermostat at all except to adjust the set point as desired. They would follow back up with her on Monday.

For Ellis, that was perfectly fine. With the historically cold winter they'd been enduring in her neck of the woods, heat was all she cared about. She asked whom to contact in case of any issues, and was told to call the main number. With all that squared away, she looked forward to a couple of quiet, stress-free days before diving back into HVAC troubleshooting.

Everything was fine, until it wasn't. Around 11AM on Saturday, Ellis noticed that the thermostat displayed the word "Heating" while the furnace wasn't actually running. Maybe it was about to turn on? 15 minutes went by, then half an hour. Nothing had changed except for the temperature in her house steadily decreasing.

Panic set in at the thought of losing heat in her home indefinitely. That fell on top of a psyche that was already stressed out and emotionally exhausted from the last several days' effort. Struggling for calm, Ellis first tried to call that main number line for help as directed. She noticed right away that it wasn't a real person on the other end asking for her personal information, but an AI agent. The agent informed her that the on-call technician had no availablity that weekend. It would pencil her in for a service appointment on Monday. How did that sound?

"Not good enough!" Ellis cried. "I wanna speak to a representative!"

"I understand!" replied the blithe chatbot. "Hold on, let me transfer you!"

For a moment, Ellis was buoyed with hope. She'd gotten past the automated system. Soon, she'd be talking with a live person who might even be able to walk her through troubleshooting over the phone.

The new agent answered. Ellis began pouring her heart out—then stopped dead when she realized it was another AI agent, this time with a male voice instead of a female one. This one proceeded through nearly the same spiel as the first. It also scheduled her for a Monday service appointment even though the other chatbot had already claimed to have done so.

This was the first time an AI had ever pulled such a trick on Ellis. It was not a good time for it. Ellis hung up and called the only other person she could think to contact: her sales rep. When he didn't answer, she left a voicemail and texts: no heat all weekend was unacceptable. She would really appreciate a call back.

While playing the horrible waiting game, Ellis tried to think about what she could do to fix this. They had told her not to mess with the thermostat. Well, from what she could see, the thermostat was sending a signal to the furnace that the furnace wasn't responding to for whatever reason. It was time to look at the docs. Fortunately, the new furnace's manual was resting right on top of it. She spread it open on her kitchen table.

OK, Ellis thought, this newfangled furnace has an LED display which displays status codes. Her old furnace had lacked such a thing. Lemme find that.

Inside her newly remodeled utility closet, she located the blinking display, knelt, and spied the code: 1dL. Looking that up in the doc's troubleshooting section, she found ... Normal Operation. No action.

The furnace was OK, then? Now what?

Aside from documentation, another thing Ellis knew pretty well was tech support. She decided to break out the ol' turn-it-off-and-on-again. She shut off power to both the furnace and thermostat, waited a few minutes, then switched everything back on, crossing her fingers.

No change. The indoor temperature kept dropping.

Her phone rang: the sales rep. He connected her with the on-call technician for that weekend, who fortunately was able to arrive at her house within the hour.

One tiny thermostat adjustment later, and Ellis was enjoying a warm house once more.

What had happened?

This is where an understanding of heat pumps comes into play. In this configuration, the heat pump is used for cooling and for heating, unless the outside temperature gets very cold. At that point, the furnace kicks in, which is more efficient. (Technology Connections has some cool videos about this if you're curious.)

Everything had been running fine for Ellis while the temperatures had remained below freezing. The problem came when, for the first time in approximately 12 years, the temperature rose above 40F (4C). At that point, the new thermostat decided, without telling Ellis, I'm gonna tell the HEAT PUMP to heat the joint!

... which couldn't do anything just then.

Workaround: the on-call technician switched the thermostat to an emergency heat mode that used the furnace no matter what.

Ellis had been told not to goof around with the thermostat. Even if she had, as a heat pump neophyte, she wouldn't have known to go looking for such a setting. She might've dug it up in a manual. Someone could've walked her through it over the phone. Oh, well. There is heat again, which is all that matters.

They will attempt to bring the heat pump online soon. We shall see if the story ends here, or if this becomes The WTF That Wouldn't Die.

P.S. When Ellis explained the AI answering service's deceptive behavior, she was told that the agent had been universally complained about ever since they switched to it. Fed up, they told Ellis they're getting rid of it. She feels pretty chuffed about more people seeing the light concerning garbage AI that creates far more problems than it solves.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Error'd: Three Blinded Mice

...sent us five wtfs. And so on anon.

Item the first, an anon is "definitely not qualified" for this job. "These years of experience requirements are getting ridiculous."

0

 

Item the second unearthed by a farmanon has a loco logo. "After reading about the high quality spam emails which are indistinguishable from the company's emails, I got one from the spammer just starting his first day."

1

 

In thrid place, anon has only good things to say: "I'm liking their newsletter recommendations so far."

2

 

"A choice so noice, they gave it twoice," quipped somebody.

3

 

And foinally, a tdwtfer asks "I've seen this mixmastered calendering on several web sites. Is there an OSS package that is doing this? Or is it a Wordpress plugin?" I have a sneaking suspicion I posted this before. Call me on it.

4

 

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

Online Age Verification Tools for Child Safety Are Surveilling Adults

By: Nick Heer

Barbara Booth, CNBC:

Civil liberties’ advocates warn that concentrating large volumes of identity data among a small number of verification vendors can create attractive targets for hackers and government demands. Earlier this year, Discord disclosed a data breach that exposed ID images belonging to approximately 70,000 users through a compromised third-party service, highlighting the security risks associated with storing sensitive identity information.

[…]

According to Tandy, as more states adopt age-verification mandates and companies race to comply, the infrastructure behind those systems is likely to become a permanent fixture of online life. Taken together, industry leaders say the rapid spread of age-verification laws may push platforms toward systems that verify age once and reuse that credential across services.

The hurried implementation of age verification sounds fairly terrible, counterproductive, illegal in the U.S., and discriminatory, but we should not pretend that we are only now being subject to risky and overbearing surveillance on the web. The ecosystem powering behavioural ad targeting — including data brokers, the biggest of which have reported staggering data breaches for a decade — has all but ensured our behaviour on popular websites and in mobile apps is already tracked and tied to some proxy for our identity.

That is not an excuse for the poor implementation of age verification, nor justification for its existence. If anything, it is a condemnation of the current state of the web that this barely moves the needle on privacy. If I had to choose whether to compromise for commerce or for the children, it would be the latter, but the correct answer is, likely, neither.

⌥ Permalink

Grammarly’s ‘Expert Review’ Feature Presents Fake Advice in the Names of Real Journalists and Authors

By: Nick Heer

Casey Newton, Platformer:

On Friday I learned to my surprise that I had become an editor for Grammarly. The subscription-based writing assistant has introduced a feature named “expert review” that, in the company’s words, “is designed to take your writing to the next level — with insights from leading professionals, authors, and subject-matter experts.”

Read a little further, though, and you’ll learn that these “insights” are not actually “from” leading professionals, or any human person at all. Rather, they are AI-generated text, which may or may not reflect whichever “leading professional” Grammarly slapped their names on.

Miles Klee, Wired:

As advertised on a support page, Grammarly users can solicit tips from virtual versions of living writers and scholars such as Stephen King and Neil deGrasse Tyson (neither of whom responded to a request for comment) as well as the deceased, like the editor William Zinsser and astronomer Carl Sagan. Presumably, these different AI agents are trained on the oeuvres of the people they are meant to imitate, though the legality of this content-harvesting remains murky at best, and the subject of many, many copyright lawsuits.

I do not think a disclaimer explaining it does “not indicate any affiliation with Grammarly or endorsement by those individuals or entities” will sufficiently distance the company from its claim of providing “insights from leading professionals, authors, and subject-matter experts” attributed to the names of people who did not agree to participate in this. Apparently, it is incumbent upon them to opt out by emailing expertoptout@superhuman.com. Most people will obviously not do this — because why would anyone realize they need to opt out? — but especially those who are dead yet are still being called upon for their expertise. Let Carl Sagan rest.

⌥ Permalink

Apple Used to Design Its Laptops for Repairability

By: Nick Heer

Charlie Sorrel, of iFixit:

Apple’s MacBooks haven’t always been monolithic, barely repairable slabs of aluminum, glass, and glue. They used to be almost delightful in their repairable features, from their batteries to their Wi-Fi cards. Powerbooks, iBooks, and especially early MacBooks showed what happens when Apple applies its design skills directly to repairability and maintenance, instead of to thinness above all. Today we’re going to take a look at the best repairability features that Apple has ditched.

These four complaints range from the somewhat quaint — swappable Wi-Fi cards — to the stuff I actually miss, which is everything else. RAM and disk upgrades are a gimme since the cost-per-gigabyte (generally) declines over time, and I would love easily swappable batteries. But right now, nearly four years into owning this MacBook Pro, I would also really like to be able to swap in a new keyboard in the future. Not only are the keycaps unintentionally becoming polished, some oft-used keys feel a little mushy. Not much, and barely enough to notice, but I imagine their clickiness will not improve over time.

One quibble, emphasis mine:

[…] I have an old 2012 MacBook Air running Linux. I swapped the HDD for an SSD, maxed out the RAM, and dropped in a new battery, and I see no reason it wouldn’t easily keep rolling for another 10 years.

Unlikely. The 2012 MacBook Air only came with an SSD; a standard hard disk was not an option.

⌥ Permalink

Another Appearance Control Is Coming to Accessibility Settings in iOS 26.4

By: Nick Heer

Juli Clover, MacRumors:

Apple renamed the prior Reduce Highlighting Effects Accessibility setting to “Reduce Bright Effects,” and explained what it does.

Apple says the feature “minimizes highlighting and flashing when interacting with onscreen elements, such as buttons or the keyboard.

In my testing, this does exactly what you would expect. In places like toolbar buttons — or the buttons in the area of what is left of a toolbar, anyhow — the passcode entry screen, and Control Centre, the glowing tap effects are minimized or removed.

I do not find those effects particularly distracting, and I think turning them off saps some of the life out of the Liquid Glass design language, but I can see why some would be bothered by them. It is not the case that iOS 26 would be better if none of these appearance controls were present, only that they should not be necessary.

⌥ Permalink

Minister for Innovation, Science, and Economic Development Announces ‘Guardrails’ for TikTok Canada Operations

By: Nick Heer

There are three agreed-upon policies which, in the airy language of a government press release, seem reasonable enough to apply to all social platforms, yet are only relevant to TikTok. The first is exceedingly vague:

TikTok will implement enhanced protection for Canadians’ personal information, including new security gateways and privacy-enhancing technologies to control access to Canadian user data in order to reduce the risk of unauthorized or prohibited access.

There are no details about what the “new security gateways and privacy-enhancing technologies” are, nor why the sole goal is preventing “prohibited access” rather than “exploitative access”.

The second — complying with the recommendations of the Privacy Commissioner — was already underway, and the third is an “independent third-party monitor”, which seems fine.

⌥ Permalink

❌